示例#1
0
def __patch_engine(original_file: Path, file: Path, engine: str,
                   hex_patches: dict[str, HexPatch]) -> bool:
    """Return True if patch went as expected, False if any warnings happened."""
    data = original_file.read_bytes()
    all_as_expected = True
    for hex_patch_name, hex_patch in hex_patches.items():
        replacement = hex_patch['replacement'] % hex_patch['replacement_args']
        pattern = hex_patch['pattern']
        (data, sub_count) = pattern.subn(replacement, data)
        LOGGER.debug(
            f"Replaced {sub_count} occurrences of '{hex_patch_name}' pattern {pattern.pattern} with {replacement} in '{file}'"
        )
        expected = hex_patch['expected_subs']
        if sub_count == 0 and expected != 0:
            raise LookupError(
                f"Failed to apply '{hex_patch_name}' patch in '{file}' (no occurrences found)"
            )
        elif sub_count != expected:
            LOGGER.warning(
                f"Expected {expected} matches for '{hex_patch_name}' patch in '{file}', found {sub_count}"
            )
            all_as_expected = False
    file.write_bytes(data)
    LOGGER.info(f"Patched '{file}'")
    return all_as_expected
示例#2
0
def try_get_profile_sjson_files() -> list[Path]:
    """Try to detect save directory and list all Profile*.sjson files."""
    save_dirs = TRY_SAVE_DIR[config.platform]()
    for save_dir in save_dirs:
        if save_dir.exists():
            LOGGER.debug(f"Found save directory at '{save_dir}'")
            if config.platform == Platform.MS_STORE:
                # Microsoft Store save files are not actually named
                # `Profile*.sjson` and instead use random hexadecimal names with
                # no file extensions, so we need to list them by trying to parse
                # them as SJSON
                profiles = __find_sjsons(save_dir)
            else:
                profiles = [item for item in save_dir.glob('Profile*.sjson')]
            if profiles:
                return profiles
    save_dirs_list = '\n'.join(f"  - {save_dir}" for save_dir in save_dirs)
    msg = f"""Did not find any 'ProfileX.sjson' in save directories:
{save_dirs_list}"""
    LOGGER.warning(msg)
    return []
示例#3
0
def patch_engines() -> None:
    HEX_PATCHES['viewport']['replacement_args'] = (__int_to_bytes(
        config.new_screen.width), __int_to_bytes(config.new_screen.height))
    HEX_PATCHES['fullscreen_vector']['replacement_args'] = (__float_to_bytes(
        config.new_screen.width), __float_to_bytes(config.new_screen.height))
    HEX_PATCHES['width_height_floats']['replacement_args'] = (__float_to_bytes(
        config.new_screen.height), __float_to_bytes(config.new_screen.width))
    HEX_PATCHES['screencenter_vector']['replacement_args'] = (
        __float_to_bytes(config.new_screen.center_x),
        __float_to_bytes(config.new_screen.center_y))
    for engine, filepath in ENGINES[config.platform].items():
        hex_patches = __get_engine_specific_hex_patches(engine)
        file = config.hades_dir.joinpath(filepath)
        LOGGER.debug(f"Patching '{engine}' backend at '{file}'")
        got_any_warnings = False
        with safe_patch_file(file) as (original_file, file):
            if not __patch_engine(original_file, file, engine, hex_patches):
                got_any_warnings = True
    if got_any_warnings:
        LOGGER.warning(
            "Hephaistos managed to apply all hex patches but did not patch everything exactly as expected."
        )
        LOGGER.warning("This is most probably due to a game update.")
        LOGGER.warning(
            "In most cases this is inconsequential and Hephaistos will work anyway, but Hephaistos might need further changes to work properly with the new version of the game."
        )
示例#4
0
def patch_profile_sjsons() -> None:
    if config.custom_resolution:
        profile_sjsons = helpers.try_get_profile_sjson_files()
        if not profile_sjsons:
            msg = """Cannot patch custom resolution to 'ProfileX.sjson'.
This is a non-blocking issue but might prevent you from running Hades at the resolution of your choice."""
            LOGGER.warning(msg)
            return
        edited_list = []
        for file in profile_sjsons:
            LOGGER.debug(f"Analyzing '{file}'")
            data = sjson.loads(file.read_text())
            for key in ['X', 'WindowWidth']:
                data[key] = config.resolution.width
            for key in ['Y', 'WindowHeight']:
                data[key] = config.resolution.height
            # we manually set WindowX/Y in ProfileX.sjson configuration files as a
            # safeguard against WindowX/Y values overflowing when switching to
            # windowed mode while using a custom resolution larger than officially
            # supported by the main monitor, ensuring Hades will not be drawn
            # offscreen and can then be repositioned by the user
            for key in ['WindowX', 'WindowY']:
                if not key in data:
                    data[key] = WINDOW_XY_DEFAULT_OFFSET
                    LOGGER.debug(
                        f"'{key}' not found in '{file.name}', inserted '{key} = {WINDOW_XY_DEFAULT_OFFSET}'"
                    )
                elif data[key] >= WINDOW_XY_OVERFLOW_THRESHOLD:
                    data[key] = WINDOW_XY_DEFAULT_OFFSET
                    LOGGER.debug(
                        f"'{key}' found in '{file.name}' but with overflowed value, reset to '{key} = {WINDOW_XY_DEFAULT_OFFSET}'"
                    )
            file.write_text(sjson.dumps(data))
            edited_list.append(file)
        if edited_list:
            edited_list = '\n'.join(f"  - {file}" for file in edited_list)
            msg = f"""Applied custom resolution to:
{edited_list}"""
            LOGGER.info(msg)