Пример #1
0
def extract_and_backup_iso(
        input_path: Path, contents_files_path: Path, backup_files_path: Path,
        progress_update: status_update_lib.ProgressUpdateCallable):
    if input_path is not None:
        unpack_updaters = status_update_lib.split_progress_update(
            progress_update, 2)
        shutil.rmtree(contents_files_path, ignore_errors=True)
        shutil.rmtree(backup_files_path, ignore_errors=True)
        iso_packager.unpack_iso(
            iso=input_path,
            game_files_path=contents_files_path,
            progress_update=unpack_updaters[0],
        )
        claris_randomizer.create_pak_backups(contents_files_path,
                                             backup_files_path,
                                             unpack_updaters[1])
    else:
        try:
            claris_randomizer.restore_pak_backups(contents_files_path,
                                                  backup_files_path,
                                                  progress_update)
        except FileNotFoundError:
            raise RuntimeError(
                "Your internal copy is missing files.\nPlease press 'Delete internal copy' and select "
                "a clean game ISO.")
Пример #2
0
    def export_game(self, patch_data: dict, export_params: GameExportParams,
                    progress_update: status_update_lib.ProgressUpdateCallable) -> None:
        assert isinstance(export_params, PrimeGameExportParams)

        input_file = export_params.input_path
        output_file = export_params.output_path

        export_params.cache_path.mkdir(parents=True, exist_ok=True)
        cache_dir = os.fspath(export_params.cache_path)

        symbols = py_randomprime.symbols_for_file(input_file)

        new_config = copy.copy(patch_data)
        has_spoiler = new_config.pop("hasSpoiler")
        room_rando_mode = new_config.pop("roomRandoMode")
        new_config["inputIso"] = os.fspath(input_file)
        new_config["outputIso"] = os.fspath(output_file)
        new_config["gameConfig"]["updateHintStateReplacement"] = list(
            assembler.assemble_instructions(
                symbols["UpdateHintState__13CStateManagerFf"],
                all_prime_dol_patches.remote_execution_patch(),
                symbols=symbols)
        )
        new_config["preferences"]["cacheDir"] = cache_dir

        assets_meta = {}
        updaters = [progress_update]
        if export_params.use_echoes_models:
            assets_path = export_params.asset_cache_path
            asset_conversion_progress = None
            if asset_conversion.get_asset_cache_version(assets_path) != asset_conversion.ECHOES_MODELS_VERSION:
                updaters = status_update_lib.split_progress_update(progress_update, 3)
                asset_conversion_progress = updaters[1]
                from randovania.games.prime2.exporter.game_exporter import extract_and_backup_iso
                extract_and_backup_iso(export_params.echoes_input_path, export_params.echoes_contents_path,
                                       export_params.echoes_backup_path, updaters[0])
            assets_meta = asset_conversion.convert_prime2_pickups(assets_path, asset_conversion_progress)
            new_config["externAssetsDir"] = os.fspath(assets_path)

        # Replace models
        adjust_model_names(new_config, assets_meta, export_params.use_echoes_models)

        patch_as_str = json.dumps(new_config, indent=4, separators=(',', ': '))
        if has_spoiler:
            output_file.with_name(f"{output_file.stem}-patcher.json").write_text(patch_as_str)
            if room_rando_mode != RoomRandoMode.NONE.value:
                self.make_room_rando_maps(output_file, f"{output_file.stem}", new_config["levelData"])

        os.environ["RUST_BACKTRACE"] = "1"

        try:
            py_randomprime.patch_iso_raw(
                patch_as_str,
                py_randomprime.ProgressNotifier(lambda percent, msg: updaters[-1](msg, percent)),
            )
        except BaseException as e:
            if isinstance(e, Exception):
                raise
            else:
                raise RuntimeError(f"randomprime panic: {e}") from e
Пример #3
0
    def patch_game(self, input_file: Optional[Path], output_file: Path,
                   patch_data: dict, game_files_path: Path,
                   progress_update: status_update_lib.ProgressUpdateCallable):
        num_updaters = 2
        if input_file is not None:
            num_updaters += 1
        updaters = status_update_lib.split_progress_update(
            progress_update, num_updaters)

        game_files_path = game_files_path.joinpath("prime2", "contents")
        backup_files_path = game_files_path.joinpath("prime2", "vanilla")

        if input_file is not None:
            self.delete_internal_copy(game_files_path)
            iso_packager.unpack_iso(
                iso=input_file,
                game_files_path=game_files_path,
                progress_update=updaters[0],
            )

        # Apply patcher
        banner_patcher.patch_game_name_and_id(
            game_files_path, "Metroid Prime 2: Randomizer - {}".format(
                patch_data["shareable_hash"]))
        claris_randomizer.apply_patcher_file(game_files_path,
                                             backup_files_path, patch_data,
                                             updaters[-2])

        # Pack ISO
        iso_packager.pack_iso(
            iso=output_file,
            game_files_path=game_files_path,
            progress_update=updaters[-1],
        )
Пример #4
0
    def export_game(self, patch_data: dict, export_params: GameExportParams,
                    progress_update: status_update_lib.ProgressUpdateCallable):
        assert isinstance(export_params, EchoesGameExportParams)
        updaters = status_update_lib.split_progress_update(progress_update, 4)

        contents_files_path = export_params.contents_files_path
        backup_files_path = export_params.backup_files_path

        if export_params.input_path is not None:
            unpack_updaters = status_update_lib.split_progress_update(
                updaters[0], 2)
            shutil.rmtree(contents_files_path, ignore_errors=True)
            shutil.rmtree(backup_files_path, ignore_errors=True)
            iso_packager.unpack_iso(
                iso=export_params.input_path,
                game_files_path=contents_files_path,
                progress_update=unpack_updaters[0],
            )
            claris_randomizer.create_pak_backups(contents_files_path,
                                                 backup_files_path,
                                                 unpack_updaters[1])
        else:
            try:
                claris_randomizer.restore_pak_backups(contents_files_path,
                                                      backup_files_path,
                                                      updaters[0])
            except FileNotFoundError:
                raise RuntimeError(
                    "Your internal copy is missing files.\nPlease press 'Delete internal copy' and select "
                    "a clean game ISO.")

        # Apply patcher
        banner_patcher.patch_game_name_and_id(
            contents_files_path, "Metroid Prime 2: Randomizer - {}".format(
                patch_data["shareable_hash"]), patch_data["publisher_id"])
        randomizer_data = copy.deepcopy(decode_randomizer_data())

        if export_params.use_prime_models:
            from randovania.patching.prime import asset_conversion
            assets_path = export_params.asset_cache_path
            asset_conversion.convert_prime1_pickups(
                export_params.prime_path,
                contents_files_path,
                assets_path,
                patch_data,
                randomizer_data,
                updaters[1],
            )

        adjust_model_name(patch_data, randomizer_data)
        claris_randomizer.apply_patcher_file(contents_files_path, patch_data,
                                             randomizer_data, updaters[2])

        # Change the color of the hud
        hud_color = patch_data["specific_patches"]["hud_color"]
        if hud_color:
            hud_color = [
                hud_color[0] / 255,
                hud_color[1] / 255,
                hud_color[2] / 255,
            ]
            ntwk_file = str(
                contents_files_path.joinpath("files", "Standard.ntwk"))
            mp2hudcolor_c(ntwk_file, ntwk_file, hud_color[0], hud_color[1],
                          hud_color[2])  # RGB 0.0-1.0

        # Pack ISO
        iso_packager.pack_iso(
            iso=export_params.output_path,
            game_files_path=contents_files_path,
            progress_update=updaters[3],
        )
Пример #5
0
def _convert_prime1_assets(input_iso: Path, output_path: Path, randomizer_data: dict,
                           status_update: ProgressUpdateCallable):
    asset_provider = prime_asset_provider(input_iso)

    next_id = 0xFFFF0000

    def id_generator(_):
        nonlocal next_id
        new_id = next_id
        while asset_provider.asset_id_exists(new_id):
            new_id += 1

        next_id = new_id + 1
        return new_id

    start = time.time()
    with asset_provider:
        conversion_updaters = status_update_lib.split_progress_update(status_update, 2)
        conversion_updaters[0]("Loading Prime 1 PAKs", 0)
        converter = AssetConverter(
            target_game=Game.ECHOES,
            asset_providers={Game.PRIME: asset_provider},
            id_generator=id_generator,
            converters=conversions.converter_for,
        )
        conversion_updaters[0]("Finished loading Prime 1 PAKs", 1)
        # logging.debug(f"Finished loading PAKs: {time.time() - start}")

        result = {}
        num_assets = len(prime1_assets)
        for i, (name, asset) in enumerate(prime1_assets.items()):
            conversion_updaters[1](f"Converting {name} from Prime 1", i / num_assets)
            if asset.ancs != 0 and asset.cmdl != 0:
                result[name] = Asset(
                    ancs=converter.convert_id(asset.ancs, Game.PRIME),
                    cmdl=converter.convert_id(asset.cmdl, Game.PRIME),
                    character=asset.character,
                    scale=asset.scale,
                )
        conversion_updaters[1]("Finished converting Prime 1 assets", 1)
        output_path.mkdir(exist_ok=True, parents=True)
        # Cache these assets here
        # This doesn't work properly so skip this for now
        '''
        for asset in converter.converted_assets.values():
            assetdata = format_for(asset.type).build(asset.resource, target_game=Game.ECHOES)
            if len(assetdata) % 32 != 0:
                assetdata += b"\xFF" * (32 - (len(assetdata) % 32))
            assets_path.joinpath(f"{asset.id}.{asset.type.upper()}").write_bytes(
                assetdata
            )
        '''
    end = time.time()

    # logging.debug(f"Time took: {end - start}")
    converted_assets = converter.converted_assets
    converted_dependencies = all_converted_dependencies(converter)
    # logging.debug("Updating RandomizerData.json")
    start = time.time()
    meta_dict = {"version": PRIME_MODELS_VERSION}

    randomizer_data_additions = []
    for name, asset in result.items():
        dependencies = [
            {"AssetID": dep.id, "Type": dep.type.upper()}
            for dep in converted_dependencies[asset.ancs] | converted_dependencies[asset.cmdl]
        ]
        randomizer_data_additions.append({
            "Index": len(randomizer_data["ModelData"]),  # Adjust this one later
            "Name": f"prime1_{name}",
            "Model": asset.cmdl,
            "ScanModel": 0xFFFFFFFF,
            "AnimSet": asset.ancs,
            "Character": asset.character,
            "DefaultAnim": 0,
            "Rotation": [0.0, 0.0, 0.0],
            "Scale": [asset.scale, asset.scale, asset.scale],
            "OrbitOffset": [0.0, 0.0, 0.0],
            "Lighting": {
                "CastShadow": True,
                "UnknownBool1": True,
                "UseWorldLighting": 1,
                "UnknownBool2": False
            },
            "Assets": dependencies
        })
    meta_dict["data"] = randomizer_data_additions
    with open(output_path.joinpath("meta.json"), "w") as data_additions_file:
        json.dump(meta_dict, data_additions_file, indent=4)

    end = time.time()
    # logging.debug(f"Time took: {end - start}")
    return converted_assets, randomizer_data_additions
Пример #6
0
def convert_prime1_pickups(prime1_iso: Path, echoes_files_path: Path, assets_path: Path,
                           patch_data: dict, randomizer_data: dict, status_update: ProgressUpdateCallable):
    """"""
    updaters = status_update_lib.split_progress_update(status_update, 3)

    # Restoring from cache causes game crashes when loading a room with a Prime model. Skipping until resolved
    if get_asset_cache_version(assets_path) >= ECHOES_MODELS_VERSION and False:
        converted_assets, randomizer_data_additions = _read_prime1_from_cache(assets_path, updaters)

    else:
        converted_assets, randomizer_data_additions = _convert_prime1_assets(
            prime1_iso, assets_path,
            randomizer_data, updaters[0],
        )

    for asset in randomizer_data_additions:
        asset["Index"] = len(randomizer_data["ModelData"])
        randomizer_data["ModelData"].append(asset)

    model_name_to_data = {
        asset["Name"]: asset
        for asset in randomizer_data_additions
    }

    wl = default_database.game_description_for(RandovaniaGame.METROID_PRIME_ECHOES).world_list
    pickup_index_to_mrea_asset_id = {
        location["Index"]: wl.nodes_to_area(wl.node_from_pickup_index(PickupIndex(location["Index"]))).extra["asset_id"]
        for location in randomizer_data["LocationData"]
    }

    # Prepare new resources for writing
    pak_resources = {}
    num_assets = len(converted_assets) + 1

    for i, new_asset in enumerate(converted_assets.values()):
        if new_asset.type.upper() == "EVNT":
            continue

        updaters[1](f"Encoding new asset {new_asset.type} 0x{new_asset.id:08X}", i / num_assets)
        pak_resources[new_asset.id] = {
            "compressed": 0,
            "asset": {
                "type": new_asset.type,
                "id": new_asset.id,
            },
            "contents": {
                "value": format_for(new_asset.type).build(new_asset.resource, target_game=Game.ECHOES),
            },
        }

    # Figure out where models are used for duplication
    extra_assets_for_mrea = collections.defaultdict(list)

    for pickup in patch_data["pickups"]:
        model = pickup["model"]
        if model["game"] == RandovaniaGame.METROID_PRIME.value:
            model_name = "{}_{}".format(model["game"], model["name"])
            if model_name not in model_name_to_data:
                continue

            asset_id = pickup_index_to_mrea_asset_id[pickup["pickup_index"]]
            dependency_list = extra_assets_for_mrea[asset_id]
            for dependency in model_name_to_data[model_name]["Assets"]:
                dependency_resource = pak_resources[dependency["AssetID"]]
                if dependency_resource not in dependency_list:
                    dependency_list.append(dependency_resource)

    # Write to paks now
    updaters[2](f"Writing modified PAks", 0)
    for pak_i in range(1, 6):
        pak_path = echoes_files_path.joinpath("files", f"Metroid{pak_i}.pak")

        new_pak = PAK.parse(
            pak_path.read_bytes(),
            target_game=Game.ECHOES,
        )

        additions = []
        for i, resource in enumerate(new_pak.resources):
            new_deps = extra_assets_for_mrea.get(resource["asset"]["id"])
            if new_deps is not None:
                additions.append((i - 1, new_deps))

        for i, new_deps in reversed(additions):
            new_pak.resources[i:i] = new_deps

        # And add all resources at the end of every pak anyway
        for resource in pak_resources.values():
            new_pak.resources.append(resource)

        PAK.build_file(new_pak, pak_path, target_game=Game.ECHOES)
        updaters[2](f"Wrote new {pak_path.name}", pak_i / 6)