def create_tracker(self): self.delete_tracker() game_enum = RandovaniaGame.PRIME2 resource_database = default_database.resource_database_for(game_enum) with get_data_path().joinpath(f"gui_assets/tracker/{game_enum.value}.json").open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) for element in tracker_details["elements"]: text_template = "" if "image_path" in element: image_path = get_data_path().joinpath(element["image_path"]) pixmap = QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) elif "label" in element: label = QLabel(self.inventory_group) label.setAlignment(Qt.AlignCenter) text_template = element["label"] else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] self._tracker_elements.append(Element(label, resources, text_template)) self.inventory_layout.addWidget(label, element["row"], element["column"])
def read_json_then_binary(game: RandovaniaGame) -> Tuple[Path, dict]: json_path = get_data_path().joinpath("json_data", f"{game.value}.json") if json_path.exists(): with json_path.open("r") as open_file: return json_path, json.load(open_file) binary_path = get_data_path().joinpath("binary_data", f"{game.value}.bin") return binary_path, decode_file_path(binary_path)
def decode_default_prime2() -> dict: json_database = get_data_path().joinpath("json_data", "prime2.json") if json_database.exists(): with json_database.open("r") as open_file: return json.load(open_file) return decode_file_path( get_data_path().joinpath("binary_data", "prime2.bin"), get_data_path().joinpath("binary_data", "prime2_extra.json"))
def test_commited_human_readable_description(echoes_game_description): buffer = io.StringIO() pretty_print.write_human_readable_world_list(echoes_game_description, buffer) assert randovania.get_data_path().joinpath( "json_data", "prime2.txt").read_text("utf-8") == buffer.getvalue()
def _all_hash_words() -> Dict[RandovaniaGame, typing.List[str]]: with (get_data_path() / "hash_words" / "hash_words.json").open() as hash_words_file: return { RandovaniaGame(key): words for key, words in json.load(hash_words_file).items() }
def default_prime2_memo_data() -> dict: with get_data_path().joinpath("item_database", "memo_data.json").open("r") as memo_data_file: memo_data = json.load(memo_data_file) item_database.add_memo_data_keys(memo_data) return memo_data
def set_default_window_icon(window: QWidget): """ Sets the window icon for the given widget to the default icon :param window: :return: """ window.setWindowIcon(QIcon(str(get_data_path().joinpath("icons", "sky_temple_key_NqN_icon.ico"))))
def read_preset_list() -> List[Path]: base_path = randovania.get_data_path().joinpath("presets") with base_path.joinpath("presets.json").open() as presets_file: preset_list = json.load(presets_file)["presets"] return [base_path.joinpath(preset["path"]) for preset in preset_list]
def test_full_data_encode_is_equal(game_enum): # The json data may be missing if we're running using a Pyinstaller binary # Setup data_dir = game_enum.data_path.joinpath("json_data") if not data_dir.is_dir() and get_data_path().joinpath( "binary_data", f"{game_enum.value}.bin").is_file(): pytest.skip("Missing json-based data") json_database = data_reader.read_split_file(data_dir) b = io.BytesIO() binary_data.encode(json_database, b) b.seek(0) decoded_database = binary_data.decode(b) # Run assert decoded_database == json_database comparable_json = _comparable_dict(json_database) comparable_binary = _comparable_dict(decoded_database) for a, b in zip(comparable_json, comparable_binary): assert a == b assert comparable_binary == comparable_json
def get_default_ammo_configurations() -> AmmoConfiguration: item_database = default_prime2_item_database() with get_data_path().joinpath("item_database", "default_state", "ammo.json").open() as open_file: data = json.load(open_file) return AmmoConfiguration.from_json(data, item_database)
def get_vanilla_colors_translator_configurations( ) -> Dict[TranslatorGate, LayoutTranslatorRequirement]: with get_data_path().joinpath( "item_database", "prime2", "default_state", "translator_vanilla_colors.json").open() as open_file: data = json.load(open_file) return _raw_translator_configurations(data)
def default_prime2_item_database() -> item_database.ItemDatabase: configuration_path = get_data_path().joinpath("item_database", "configuration") with configuration_path.joinpath("major-items.json").open() as major_items_file: major_items_data = json.load(major_items_file) with configuration_path.joinpath("ammo.json").open() as ammo_file: ammo_data = json.load(ammo_file) return item_database.read_database(major_items_data, ammo_data)
def read_preset_list() -> List[Preset]: base_path = get_data_path().joinpath("presets") with base_path.joinpath("presets.json").open() as presets_file: preset_list = json.load(presets_file)["presets"] return [ read_preset_file(base_path.joinpath(preset["path"])) for preset in preset_list ]
def refresh_presets_command_logic(args): base_path = get_data_path().joinpath("presets") with base_path.joinpath("presets.json").open() as presets_file: preset_list = json.load(presets_file)["presets"] for preset_relative_path in preset_list: preset_path = base_path.joinpath(preset_relative_path["path"]) preset = VersionedPreset.from_file_sync(preset_path) preset.ensure_converted() preset.save_to_file(preset_path)
def create_tracker(self, game_enum: RandovaniaGame): effective_theme = self.theme if not path_for(game_enum, effective_theme).is_file(): effective_theme = TrackerTheme.CLASSIC if (game_enum, effective_theme) == self._last_tracker_config: return self.delete_tracker() resource_database = default_database.resource_database_for(game_enum) with path_for(game_enum, effective_theme).open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) for element in tracker_details["elements"]: text_template = "" if "image_path" in element: image_path = get_data_path().joinpath(element["image_path"]) pixmap = QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) elif "label" in element: label = QLabel(self.inventory_group) label.setAlignment(Qt.AlignCenter) text_template = element["label"] else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] self._tracker_elements.append( Element(label, resources, text_template)) self.inventory_layout.addWidget(label, element["row"], element["column"]) self.inventory_spacer = QSpacerItem(5, 5, QSizePolicy.Expanding, QSizePolicy.Expanding) self.inventory_layout.addItem(self.inventory_spacer, self.inventory_layout.rowCount(), self.inventory_layout.columnCount()) self._update_tracker_from_hook({}) self._last_tracker_config = (game_enum, effective_theme)
def read_json_then_binary(game: RandovaniaGame) -> tuple[Path, dict]: dir_path = game.data_path.joinpath("json_data") if dir_path.exists(): return dir_path, data_reader.read_split_file(dir_path) json_path = dir_path.joinpath(f"{game.value}.json") if json_path.exists(): with json_path.open("r") as open_file: return json_path, data_reader.read_json_file(open_file) binary_path = get_data_path().joinpath("binary_data", f"{game.value}.bin") return binary_path, binary_data.decode_file_path(binary_path)
def get_default_major_items_configurations() -> MajorItemsConfiguration: item_database = default_prime2_item_database() with get_data_path().joinpath("item_database", "default_state", "major-items.json").open() as open_file: data = json.load(open_file) return MajorItemsConfiguration( items_state={ item_database.major_items[name]: MajorItemState.from_json(state_data) for name, state_data in data["items_state"].items() } )
def refresh_presets_command_logic(args): base_path = get_data_path().joinpath("presets") with base_path.joinpath("presets.json").open() as presets_file: preset_list = json.load(presets_file)["presets"] for preset_relative_path in preset_list: preset_path = base_path.joinpath(preset_relative_path["path"]) save_preset_file( read_preset_file(preset_path), preset_path )
def _config_with_data(request): with get_data_path().joinpath("item_database", "default_state", "ammo.json").open() as open_file: data = json.load(open_file) for key, value in request.param.get("items_state", {}).items(): data["items_state"][key] = value for key, value in request.param.get("maximum_ammo", {}).items(): data["maximum_ammo"][key] = value return request.param["encoded"], AmmoConfiguration.from_json( data, default_prime2_item_database())
def _config_with_data(request): with get_data_path().joinpath("item_database", "default_state", "major-items.json").open() as open_file: data = json.load(open_file) data["progressive_suit"] = True data["progressive_grapple"] = True data["progressive_launcher"] = True data["minimum_random_starting_items"] = True data["maximum_random_starting_items"] = True for field, value in request.param["replace"].items(): for key, inner_value in value.items(): data[field][key] = inner_value return request.param["encoded"], MajorItemsConfiguration.from_json(data, default_prime2_item_database())
def _create_config_for(game: RandovaniaGame, replace: dict): with get_data_path().joinpath("item_database", game.value, "default_state", "major-items.json").open() as open_file: default_data = json.load(open_file) default_data["minimum_random_starting_items"] = 0 default_data["maximum_random_starting_items"] = 0 data = copy.deepcopy(default_data) for field, value in replace.items(): for key, inner_value in value.items(): data[field][key] = inner_value return ( MajorItemsConfiguration.from_json(default_data, game), MajorItemsConfiguration.from_json(data, game), )
def _config_with_data(request): game: RandovaniaGame = request.param["game"] with get_data_path().joinpath("item_database", game.value, "default_state", "ammo.json").open() as open_file: default_data = json.load(open_file) default = AmmoConfiguration.from_json(default_data, game) data = copy.deepcopy(default_data) for key, value in request.param.get("items_state", {}).items(): data["items_state"][key] = value for key, value in request.param.get("maximum_ammo", {}).items(): data["maximum_ammo"][key] = value config = AmmoConfiguration.from_json(data, game) return request.param["encoded"], config, default
def test_full_file_round_trip(game): # Setup json_database_path = get_data_path().joinpath("json_data", f"{game.value}.json") if not json_database_path.exists(): pytest.skip("Missing database") with json_database_path.open("r") as json_file: original_data = json.load(json_file) # Run 1 output_io = io.BytesIO() binary_data.encode(original_data, output_io) # Run 2 output_io.seek(0) final_data = binary_data.decode(output_io) # Assert assert final_data == original_data
def test_full_data_encode_is_equal(): # The prime2.json may be missing if we're running using a Pyinstaller binary # Setup json_database_file = get_data_path().joinpath("json_data", "prime2.json") with json_database_file.open("r") as open_file: json_database = json.load(open_file) b = io.BytesIO() binary_data.encode(json_database, b) b.seek(0) decoded_database = binary_data.decode(b) # Run assert json_database == decoded_database comparable_json = _comparable_dict(json_database) comparable_binary = _comparable_dict(decoded_database) assert comparable_json == comparable_binary
def test_full_file_round_trip(): # Setup json_database_path = get_data_path().joinpath("json_data", "prime2.json") if not json_database_path.exists(): pytest.skip("Missing database") with json_database_path.open("r") as json_file: original_data = json.load(json_file) # Run 1 output_io = io.BytesIO() remaining_data = binary_data.encode(original_data, output_io) # Run 2 output_io.seek(0) text_io = io.StringIO(json.dumps(remaining_data)) final_data = binary_data.decode(output_io, text_io) # Assert assert final_data == original_data
def __init__(self, game_connection: GameConnection, options: Options): super().__init__() self.setupUi(self) self.game_connection = game_connection self.options = options common_qt_lib.set_default_window_icon(self) with get_data_path().joinpath(f"gui_assets/tracker/trackers.json" ).open("r") as trackers_file: self.trackers = json.load(trackers_file)["trackers"] self._action_to_name = {} theme_group = QtGui.QActionGroup(self) for name in self.trackers.keys(): action = QtGui.QAction(self.menu_tracker) action.setText(name) action.setCheckable(True) action.setChecked(name == options.selected_tracker) action.triggered.connect(self._on_action_select_tracker) self.menu_tracker.addAction(action) self._action_to_name[action] = name theme_group.addAction(action) self._tracker_elements: List[Element] = [] self.create_tracker() self.game_connection_setup = GameConnectionSetup( self, self.connection_status_label, self.game_connection, options) self.game_connection_setup.create_backend_entries(self.menu_backend) self.game_connection_setup.create_upload_nintendont_action( self.menu_options) self.game_connection_setup.refresh_backend() self.action_force_update.triggered.connect(self.on_force_update_button) self._update_timer = QtCore.QTimer(self) self._update_timer.setInterval(100) self._update_timer.timeout.connect(self._on_timer_update) self._update_timer.setSingleShot(True)
async def on_upload_nintendont_action(self): nintendont_file = get_data_path().joinpath("nintendont", "boot.dol") if not nintendont_file.is_file(): return await async_dialog.warning( self.parent, "Missing Nintendont", "Unable to find a Nintendont executable.") text = f"Uploading Nintendont to the Wii at {self.options.nintendont_ip}..." box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "Uploading to Homebrew Channel", text, QtWidgets.QMessageBox.Ok, self.parent) box.button(QtWidgets.QMessageBox.Ok).setEnabled(False) box.show() try: await wiiload.upload_file(nintendont_file, [], self.options.nintendont_ip) box.setText( f"Upload finished successfully. Check your Wii for more.") except Exception as e: box.setText(f"Error uploading to Wii: {e}") finally: box.button(QtWidgets.QMessageBox.Ok).setEnabled(True)
def decode_randomizer_data() -> dict: randomizer_data_path = get_data_path().joinpath("ClarisPrimeRandomizer", "RandomizerData.json") with randomizer_data_path.open() as randomizer_data_file: return json.load(randomizer_data_file)
def convert_prime2_pickups(output_path: Path, status_update: ProgressUpdateCallable): metafile = output_path.joinpath("meta.json") if get_asset_cache_version(output_path) >= ECHOES_MODELS_VERSION: with open(metafile, "r") as md: return json.load(md) next_id = 0xFFFF0000 delete_converted_assets(output_path) randomizer_data_path = get_data_path().joinpath("ClarisPrimeRandomizer", "RandomizerData.json") with randomizer_data_path.open() as randomizer_data_file: randomizer_data = json.load(randomizer_data_file) def id_generator(asset_type): nonlocal next_id result = next_id while asset_provider.asset_id_exists(result): result += 1 next_id = result + 1 return result start = time.time() with echoes_asset_provider() as asset_provider: logging.info("Loading PAKs") converter = AssetConverter( target_game=Game.PRIME, asset_providers={Game.ECHOES: asset_provider}, id_generator=id_generator, converters=conversions.converter_for, ) logging.info(f"Finished loading PAKs: {time.time() - start}") # These aren't guaranteed to be available in the paks yet, so skip them for now models_to_skip = [ "VioletTranslator", "AmberTranslator", "EmeraldTranslator", "CobaltTranslator", "DarkBeamAmmoExpansion", "LightBeamAmmoExpansion" ] # Fix the Varia Suit's character in the suits ANCS referencing a missing skin. # 0x3A5E2FE1 is Light Suit's skin # this is fixed by Claris' patcher when exporting for Echoes asset_provider.get_asset(0xa3e787b7).character_set.characters[0].skin_id = 0x3A5E2FE1 # Use echoes missile expansion for unlimited missiles instead of missile launcher unlimited_missile_data = next(i for i in randomizer_data["ModelData"] if i["Index"] == 42) unlimited_missile_data["Model"] = 1581025172 unlimited_missile_data["AnimSet"] = 435828657 result = {} assets_to_change = [ data for data in randomizer_data["ModelData"] if (data["Model"] != Game.ECHOES.invalid_asset_id and data["AnimSet"] != Game.ECHOES.invalid_asset_id and data["Name"] not in models_to_skip) ] for i, data in enumerate(assets_to_change): try: status_update(f"Converting {data['Name']} from Prime 2", i / len(assets_to_change)) result["{}_{}".format(RandovaniaGame.METROID_PRIME_ECHOES.value, data["Name"])] = Asset( ancs=converter.convert_id(data["AnimSet"], Game.ECHOES, missing_assets_as_invalid=False), cmdl=converter.convert_id(data["Model"], Game.ECHOES, missing_assets_as_invalid=False), character=data["Character"], scale=data["Scale"][0], ) except (InvalidAssetId, UnknownAssetId) as e: raise RuntimeError("Unable to convert {}: {}".format(data["Name"], e)) end = time.time() logging.info(f"Time took: {end - start}") start = time.time() converted_dependencies = all_converted_dependencies(converter) new_id_to_old = { new_id: old_id for (_, old_id), new_id in converter.converted_ids.items() } unique_anim = {} unique_evnt = [] dont_delete = [] for anim in converter.converted_assets.values(): if anim.type != "ANIM": continue for ancs in converter.converted_assets.values(): if ancs.type != "ANCS": continue # If the anim is a dependency of the ancs if any(anim.id in x for x in converted_dependencies[ancs.id]): for dep in converted_dependencies[ancs.id]: if anim.id in unique_anim: for depb in converted_dependencies[ancs.id]: if depb.type == "EVNT": if depb.id not in unique_evnt: unique_evnt.append(depb.id) converted_dependencies[ancs.id].remove(depb) converted_dependencies[ancs.id].add(Dependency("EVNT", unique_anim[anim.id])) else: dont_delete.append(depb.id) continue if dep.type == "EVNT": unique_anim[anim.id] = dep.id anim.resource["anim"]["event_id"] = dep.id break deleted_evnts = [] for id in unique_evnt: if id not in dont_delete: deleted_evnts.append(id) for id in deleted_evnts: converter.converted_assets.pop(id) output_path.mkdir(exist_ok=True, parents=True) with output_path.joinpath("meta.json").open("w") as meta_out: metadata = { "version": ECHOES_MODELS_VERSION, "items": { name: { "ancs": asset.ancs, "cmdl": asset.cmdl, "character": asset.character, "scale": asset.scale, } for name, asset in result.items() }, "new_assets": [ { "old_id": new_id_to_old.get(asset.id), "new_id": asset.id, "type": asset.type, "dependencies": [ {"type": dep.type, "id": dep.id} for dep in converted_dependencies[asset.id] ] } for asset in converter.converted_assets.values() ], } json.dump(metadata, meta_out, indent=4) for asset in converter.converted_assets.values(): assetdata = format_for(asset.type).build(asset.resource, target_game=Game.PRIME) if len(assetdata) % 32 != 0: assetdata += b"\xFF" * (32 - (len(assetdata) % 32)) output_path.joinpath(f"{asset.id}.{asset.type.upper()}").write_bytes( assetdata ) logging.info(f"Time took: {time.time() - start}") return metadata
# Assert assert final_data == original_data def _comparable_dict(value): if isinstance(value, dict): return [(key, _comparable_dict(item)) for key, item in value.items()] if isinstance(value, list): return [_comparable_dict(item) for item in value] return value @pytest.mark.skipif( not get_data_path().joinpath("json_data", "prime2.json").is_file(), reason="Missing prime2.json") def test_full_data_encode_is_equal(): # The prime2.json may be missing if we're running using a Pyinstaller binary # Setup json_database_file = get_data_path().joinpath("json_data", "prime2.json") with json_database_file.open("r") as open_file: json_database = json.load(open_file) b = io.BytesIO() binary_data.encode(json_database, b) b.seek(0) decoded_database = binary_data.decode(b)