Last active
July 12, 2026 20:45
-
-
Save ivan/2c67e1348c17445e380b68b85162a933 to your computer and use it in GitHub Desktop.
Fix the walking speed in The Witness witness64_d3d11.exe (GOG, Patch 21.12.2017 from 06 March 2018) to not have sqrt(2) walking speed on stairs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # Model-output: ChatGPT 5.6 Sol (High) | |
| """Patch The Witness stair movement and bypass its startup corruption guard. | |
| The movement patch rescales a player-only walk-manifold request when traversal adds | |
| vertical displacement. The startup patch removes the conditional jump that routes | |
| a zero critical-asset sentinel to ``fatal_error_data_corruption``. The PE file size | |
| and section count remain unchanged. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import struct | |
| from pathlib import Path | |
| import pefile | |
| from keystone import KS_ARCH_X86, KS_MODE_64, Ks | |
| EXPECTED_SHA256 = "0d582f75aca9ee30d09c9a78ba73cdc19eb99b29c8b36b9fcf3b82fa318a42f6" | |
| CALL_SITE_RVA = 0x0024724F | |
| ORIGINAL_TARGET_RVA = 0x002D24D0 | |
| EXPECTED_CALL_BYTES = bytes.fromhex("e87cb20800") | |
| CORRUPTION_CHECK_RVA = 0x00067249 | |
| EXPECTED_CORRUPTION_CHECK_BYTES = bytes.fromhex("0f84010d0000") | |
| DISABLED_CORRUPTION_CHECK_BYTES = bytes.fromhex("909090909090") | |
| CODE_CAVE_SECTION_NAME = b"_RDATA" | |
| CODE_CAVE_OFFSET_IN_SECTION = 0x8030 | |
| CODE_CAVE_CAPACITY = 0x1D0 | |
| IMAGE_SCN_MEM_EXECUTE = 0x20000000 | |
| HORIZONTAL_COMPLETION_MINIMUM = 0.90 | |
| def align_up(value: int, alignment: int) -> int: | |
| """Round a non-negative integer upward to an alignment boundary. | |
| Args: | |
| value: Byte offset or byte count to align. | |
| alignment: Positive power-of-two boundary. | |
| Returns: | |
| The least aligned integer greater than or equal to value. | |
| """ | |
| assert value >= 0 | |
| assert alignment > 0 and alignment & (alignment - 1) == 0 | |
| return (value + alignment - 1) & ~(alignment - 1) | |
| def sha256_file(path: Path) -> str: | |
| """Compute the SHA-256 digest of a file. | |
| Args: | |
| path: File whose bytes are hashed. | |
| Returns: | |
| Lowercase hexadecimal SHA-256 digest. | |
| """ | |
| digest = hashlib.sha256() | |
| with path.open("rb") as file_handle: | |
| for chunk in iter(lambda: file_handle.read(1024 * 1024), b""): | |
| digest.update(chunk) | |
| return digest.hexdigest() | |
| def assemble_hook(hook_va: int, original_target_va: int) -> bytes: | |
| """Assemble the x86-64 constant-speed movement wrapper. | |
| Args: | |
| hook_va: Runtime virtual address of the wrapper's first byte. | |
| original_target_va: Runtime virtual address of walk_manifold_move_by. | |
| Returns: | |
| Machine code for the wrapper. | |
| """ | |
| threshold_bits = struct.unpack("<I", struct.pack("<f", HORIZONTAL_COMPLETION_MINIMUM))[0] | |
| assembly = f""" | |
| sub rsp, 0x88 | |
| mov qword ptr [rsp + 0x20], rcx | |
| mov qword ptr [rsp + 0x28], rdx | |
| mov qword ptr [rsp + 0x30], r8 | |
| movss dword ptr [rsp + 0x38], xmm3 | |
| mov rax, qword ptr [rdx] | |
| mov qword ptr [rsp + 0x40], rax | |
| mov eax, dword ptr [rdx + 8] | |
| mov dword ptr [rsp + 0x48], eax | |
| call {original_target_va:#x} | |
| mov word ptr [rsp + 0x3c], ax | |
| test al, al | |
| jz return_first | |
| mov rdx, qword ptr [rsp + 0x28] | |
| mov rax, qword ptr [rdx] | |
| mov qword ptr [rsp + 0x50], rax | |
| mov eax, dword ptr [rdx + 8] | |
| mov dword ptr [rsp + 0x58], eax | |
| movss xmm4, dword ptr [rdx + 8] | |
| subss xmm4, dword ptr [rsp + 0x48] | |
| mulss xmm4, xmm4 | |
| xorps xmm5, xmm5 | |
| ucomiss xmm4, xmm5 | |
| jbe return_first | |
| movq xmm0, qword ptr [rsp + 0x30] | |
| movaps xmm1, xmm0 | |
| mulps xmm1, xmm1 | |
| movaps xmm5, xmm1 | |
| shufps xmm5, xmm5, 0x55 | |
| addss xmm1, xmm5 | |
| xorps xmm5, xmm5 | |
| ucomiss xmm1, xmm5 | |
| jbe return_first | |
| movss xmm2, dword ptr [rdx] | |
| subss xmm2, dword ptr [rsp + 0x40] | |
| mulss xmm2, xmm2 | |
| movss xmm3, dword ptr [rdx + 4] | |
| subss xmm3, dword ptr [rsp + 0x44] | |
| mulss xmm3, xmm3 | |
| addss xmm2, xmm3 | |
| movaps xmm5, xmm1 | |
| mov eax, {threshold_bits:#x} | |
| movd xmm3, eax | |
| mulss xmm5, xmm3 | |
| ucomiss xmm2, xmm5 | |
| jb return_first | |
| movaps xmm3, xmm2 | |
| addss xmm3, xmm4 | |
| ucomiss xmm3, xmm1 | |
| jbe return_first | |
| divss xmm1, xmm3 | |
| sqrtss xmm1, xmm1 | |
| shufps xmm1, xmm1, 0 | |
| mulps xmm0, xmm1 | |
| movq r8, xmm0 | |
| mov rcx, qword ptr [rsp + 0x20] | |
| mov rdx, qword ptr [rsp + 0x28] | |
| mov rax, qword ptr [rsp + 0x40] | |
| mov qword ptr [rdx], rax | |
| mov eax, dword ptr [rsp + 0x48] | |
| mov dword ptr [rdx + 8], eax | |
| movss xmm3, dword ptr [rsp + 0x38] | |
| call {original_target_va:#x} | |
| test al, al | |
| jnz return_second | |
| mov rdx, qword ptr [rsp + 0x28] | |
| mov rax, qword ptr [rsp + 0x50] | |
| mov qword ptr [rdx], rax | |
| mov eax, dword ptr [rsp + 0x58] | |
| mov dword ptr [rdx + 8], eax | |
| return_first: | |
| movzx eax, word ptr [rsp + 0x3c] | |
| return_second: | |
| add rsp, 0x88 | |
| ret | |
| """ | |
| assembler = Ks(KS_ARCH_X86, KS_MODE_64) | |
| encoding, _ = assembler.asm(assembly, addr=hook_va) | |
| code = bytes(encoding) | |
| assert code.startswith(bytes.fromhex("4881ec88000000")) | |
| assert code.endswith(bytes.fromhex("4881c488000000c3")) | |
| return code | |
| def build_unwind_info() -> bytes: | |
| """Build Windows x64 unwind metadata for the wrapper's stack allocation. | |
| Args: | |
| None. | |
| Returns: | |
| Eight-byte UNWIND_INFO structure for `sub rsp, 0x88`. | |
| """ | |
| unwind_info = bytes.fromhex("0107020007011100") | |
| assert len(unwind_info) == 8 | |
| return unwind_info | |
| def find_section(pe: pefile.PE, name: bytes) -> pefile.SectionStructure: | |
| """Find a PE section by its unpadded name. | |
| Args: | |
| pe: Parsed PE image. | |
| name: Section name without trailing NUL bytes. | |
| Returns: | |
| Matching PE section structure. | |
| """ | |
| matches = [section for section in pe.sections if section.Name.rstrip(b"\0") == name] | |
| if len(matches) != 1: | |
| raise ValueError(f"Expected exactly one {name!r} section; found {len(matches)}") | |
| return matches[0] | |
| def patch_executable(source_path: Path, destination_path: Path) -> dict[str, int | str]: | |
| """Create a structurally conservative patched executable. | |
| Args: | |
| source_path: Original executable matching EXPECTED_SHA256. | |
| destination_path: Path that receives the patched executable. | |
| Returns: | |
| Patch metadata and output digest. | |
| """ | |
| source_digest = sha256_file(source_path) | |
| if source_digest != EXPECTED_SHA256: | |
| raise ValueError(f"Unsupported executable SHA-256: {source_digest}; expected {EXPECTED_SHA256}") | |
| image = bytearray(source_path.read_bytes()) | |
| original_size = len(image) | |
| pe = pefile.PE(data=bytes(image)) | |
| image_base = int(pe.OPTIONAL_HEADER.ImageBase) | |
| call_site_offset = pe.get_offset_from_rva(CALL_SITE_RVA) | |
| actual_call = bytes(image[call_site_offset:call_site_offset + 5]) | |
| if actual_call != EXPECTED_CALL_BYTES: | |
| raise ValueError(f"Unexpected call-site bytes at RVA {CALL_SITE_RVA:#x}: {actual_call.hex()}") | |
| cave_section = find_section(pe, CODE_CAVE_SECTION_NAME) | |
| cave_raw = int(cave_section.PointerToRawData) + CODE_CAVE_OFFSET_IN_SECTION | |
| cave_rva = int(cave_section.VirtualAddress) + CODE_CAVE_OFFSET_IN_SECTION | |
| cave_end = cave_raw + CODE_CAVE_CAPACITY | |
| if cave_end > int(cave_section.PointerToRawData) + int(cave_section.SizeOfRawData): | |
| raise ValueError("Configured code cave exceeds the existing section's raw data") | |
| if any(image[cave_raw:cave_end]): | |
| raise ValueError("Configured code cave is not empty in this executable") | |
| hook_va = image_base + cave_rva | |
| original_va = image_base + ORIGINAL_TARGET_RVA | |
| hook_code = assemble_hook(hook_va, original_va) | |
| unwind_info = build_unwind_info() | |
| unwind_offset = align_up(len(hook_code), 4) | |
| payload = hook_code + bytes(unwind_offset - len(hook_code)) + unwind_info | |
| if len(payload) > CODE_CAVE_CAPACITY: | |
| raise ValueError(f"Hook payload needs {len(payload)} bytes; cave has {CODE_CAVE_CAPACITY}") | |
| image[cave_raw:cave_raw + len(payload)] = payload | |
| cave_virtual_size_offset = cave_section.get_file_offset() + 8 | |
| cave_virtual_size = max(int(cave_section.Misc_VirtualSize), CODE_CAVE_OFFSET_IN_SECTION + len(payload)) | |
| if cave_virtual_size > int(cave_section.SizeOfRawData): | |
| raise ValueError("Hook payload extends past the existing section's raw data") | |
| struct.pack_into("<I", image, cave_virtual_size_offset, cave_virtual_size) | |
| characteristics_offset = cave_section.get_file_offset() + 36 | |
| characteristics = int(cave_section.Characteristics) | IMAGE_SCN_MEM_EXECUTE | |
| struct.pack_into("<I", image, characteristics_offset, characteristics) | |
| exception_directory = pe.OPTIONAL_HEADER.DATA_DIRECTORY[3] | |
| pdata_section = find_section(pe, b".pdata") | |
| old_exception_size = int(exception_directory.Size) | |
| runtime_entry_rva = int(exception_directory.VirtualAddress) + old_exception_size | |
| runtime_entry_raw = pe.get_offset_from_rva(runtime_entry_rva) | |
| pdata_raw_end = int(pdata_section.PointerToRawData) + int(pdata_section.SizeOfRawData) | |
| if runtime_entry_raw + 12 > pdata_raw_end: | |
| raise ValueError("The .pdata section has no room for wrapper unwind metadata") | |
| if any(image[runtime_entry_raw:runtime_entry_raw + 12]): | |
| raise ValueError("The intended .pdata append slot is not empty") | |
| runtime_function = struct.pack("<III", cave_rva, cave_rva + len(hook_code), cave_rva + unwind_offset) | |
| image[runtime_entry_raw:runtime_entry_raw + 12] = runtime_function | |
| exception_size_offset = exception_directory.get_field_absolute_offset("Size") | |
| struct.pack_into("<I", image, exception_size_offset, old_exception_size + 12) | |
| pdata_virtual_size_offset = pdata_section.get_file_offset() + 8 | |
| new_pdata_virtual_size = max(int(pdata_section.Misc_VirtualSize), runtime_entry_rva + 12 - int(pdata_section.VirtualAddress)) | |
| struct.pack_into("<I", image, pdata_virtual_size_offset, new_pdata_virtual_size) | |
| call_site_va = image_base + CALL_SITE_RVA | |
| displacement = hook_va - (call_site_va + 5) | |
| if not -(1 << 31) <= displacement < (1 << 31): | |
| raise ValueError("Wrapper lies outside rel32 call range") | |
| image[call_site_offset:call_site_offset + 5] = b"\xE8" + struct.pack("<i", displacement) | |
| corruption_check_offset = pe.get_offset_from_rva(CORRUPTION_CHECK_RVA) | |
| actual_corruption_check = bytes( | |
| image[corruption_check_offset:corruption_check_offset + len(EXPECTED_CORRUPTION_CHECK_BYTES)] | |
| ) | |
| if actual_corruption_check != EXPECTED_CORRUPTION_CHECK_BYTES: | |
| raise ValueError( | |
| f"Unexpected corruption-check bytes at RVA {CORRUPTION_CHECK_RVA:#x}: " | |
| f"{actual_corruption_check.hex()}" | |
| ) | |
| image[ | |
| corruption_check_offset:corruption_check_offset + len(DISABLED_CORRUPTION_CHECK_BYTES) | |
| ] = DISABLED_CORRUPTION_CHECK_BYTES | |
| checksum_offset = pe.OPTIONAL_HEADER.get_field_absolute_offset("CheckSum") | |
| struct.pack_into("<I", image, checksum_offset, int(pe.OPTIONAL_HEADER.CheckSum)) | |
| assert len(image) == original_size | |
| destination_path.write_bytes(image) | |
| patched_pe = pefile.PE(str(destination_path)) | |
| if int(patched_pe.FILE_HEADER.NumberOfSections) != int(pe.FILE_HEADER.NumberOfSections): | |
| raise AssertionError("Section count changed unexpectedly") | |
| if int(patched_pe.OPTIONAL_HEADER.SizeOfImage) != int(pe.OPTIONAL_HEADER.SizeOfImage): | |
| raise AssertionError("SizeOfImage changed unexpectedly") | |
| if destination_path.stat().st_size != source_path.stat().st_size: | |
| raise AssertionError("File size changed unexpectedly") | |
| return { | |
| "source_sha256": source_digest, | |
| "output_sha256": sha256_file(destination_path), | |
| "hook_rva": cave_rva, | |
| "hook_size": len(hook_code), | |
| "unwind_rva": cave_rva + unwind_offset, | |
| "file_size": original_size, | |
| "number_of_sections": int(patched_pe.FILE_HEADER.NumberOfSections), | |
| "size_of_image": int(patched_pe.OPTIONAL_HEADER.SizeOfImage), | |
| "code_cave_virtual_size": cave_virtual_size, | |
| "pe_checksum_preserved": int(patched_pe.OPTIONAL_HEADER.CheckSum), | |
| "corruption_check_rva": CORRUPTION_CHECK_RVA, | |
| } | |
| def main() -> None: | |
| """Parse command-line paths and write the patched executable. | |
| Args: | |
| None. | |
| Returns: | |
| None. | |
| """ | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("source", type=Path) | |
| parser.add_argument("destination", type=Path) | |
| arguments = parser.parse_args() | |
| if arguments.source.resolve() == arguments.destination.resolve(): | |
| raise ValueError("Source and destination paths must differ") | |
| metadata = patch_executable(arguments.source, arguments.destination) | |
| for key, value in metadata.items(): | |
| rendered = f"{value:#x}" if isinstance(value, int) else value | |
| print(f"{key}: {rendered}") | |
| if __name__ == "__main__": | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
bsdiff: https://ludios.org/slop/Game%20patches/Fix%20the%20walking%20speed%20in%20The%20Witness%20witness64_d3d11.exe%20%28GOG%2C%20Patch%2021.12.2017%20from%2006%20March%202018%29%20to%20not%20have%20sqrt%282%29%20walking%20speed%20on%20stairs.bsdiff
chat log: https://ludios.org/slop/The%20Witness%20Speed%20Fix%20-%20https%EF%BC%9A_chatgpt.com_c_6a5381e6-0ba8-83ea-8527-f578e1551be8.html