From 4623d59206e88132432b6db74945a717057b2f8a Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Mon, 7 Jul 2025 15:51:39 +0200 Subject: [PATCH] Core: ensure slot_data and er_hint_info are only base data types (#5144) --------- Co-authored-by: Doug Hoskisson --- Main.py | 4 ++++ NetUtils.py | 21 +++++++++++++++++++++ test/general/test_implemented.py | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index 442c2ff4..456820a4 100644 --- a/Main.py +++ b/Main.py @@ -12,6 +12,7 @@ import worlds from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, flood_items, \ parse_planned_blocks, distribute_planned_blocks, resolve_early_locations_for_planned +from NetUtils import convert_to_base_types from Options import StartInventoryPool from Utils import __version__, output_path, version_tuple from settings import get_settings @@ -334,6 +335,9 @@ def main(args, seed=None, baked_server_options: dict[str, object] | None = None) } AutoWorld.call_all(multiworld, "modify_multidata", multidata) + for key in ("slot_data", "er_hint_data"): + multidata[key] = convert_to_base_types(multidata[key]) + multidata = zlib.compress(pickle.dumps(multidata), 9) with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f: diff --git a/NetUtils.py b/NetUtils.py index f2ae2a63..cc6e917c 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -106,6 +106,27 @@ def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any: return obj +_base_types = str | int | bool | float | None | tuple["_base_types", ...] | dict["_base_types", "base_types"] + + +def convert_to_base_types(obj: typing.Any) -> _base_types: + if isinstance(obj, (tuple, list, set, frozenset)): + return tuple(convert_to_base_types(o) for o in obj) + elif isinstance(obj, dict): + return {convert_to_base_types(key): convert_to_base_types(value) for key, value in obj.items()} + elif obj is None or type(obj) in (str, int, float, bool): + return obj + # unwrap simple types to their base, such as StrEnum + elif isinstance(obj, str): + return str(obj) + elif isinstance(obj, int): + return int(obj) + elif isinstance(obj, float): + return float(obj) + else: + raise Exception(f"Cannot handle {type(obj)}") + + _encode = JSONEncoder( ensure_ascii=False, check_circular=False, diff --git a/test/general/test_implemented.py b/test/general/test_implemented.py index b74f82b7..cf0624a2 100644 --- a/test/general/test_implemented.py +++ b/test/general/test_implemented.py @@ -1,7 +1,7 @@ import unittest from Fill import distribute_items_restrictive -from NetUtils import encode +from NetUtils import convert_to_base_types from worlds.AutoWorld import AutoWorldRegister, call_all from worlds import failed_world_loads from . import setup_solo_multiworld @@ -47,7 +47,7 @@ class TestImplemented(unittest.TestCase): call_all(multiworld, "post_fill") for key, data in multiworld.worlds[1].fill_slot_data().items(): self.assertIsInstance(key, str, "keys in slot data must be a string") - self.assertIsInstance(encode(data), str, f"object {type(data).__name__} not serializable.") + convert_to_base_types(data) # only put base data types into slot data def test_no_failed_world_loads(self): if failed_world_loads: