Skip to content

Instantly share code, notes, and snippets.

@behcet
Created March 30, 2026 00:55
Show Gist options
  • Select an option

  • Save behcet/7b43122bd16c7e3c27b7d564eff143e2 to your computer and use it in GitHub Desktop.

Select an option

Save behcet/7b43122bd16c7e3c27b7d564eff143e2 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Patch cm0304.exe to add Sleep(1) when the message queue is empty.
Injects a code cave at the end of the .text section that:
1. Resolves Sleep via GetProcAddress(GetModuleHandleA("kernel32.dll"), "Sleep")
2. Calls Sleep(1)
3. Returns
The message pump's exit path is redirected through this cave.
"""
import shutil
import struct
import sys
import os
# Replace the path below before running!!
GAME_DIR = "#### YOUR PATH HERE ###/Program Files (x86)/Eidos/CM 03-04"
EXE_NAME = "cm0304.exe"
BACKUP_NAME = "cm0304.exe.bak"
# The PE image base for this 32-bit exe
IMAGE_BASE = 0x400000
# The message pump exit at VMA 0x987145:
# pop esi; add esp, 28; ret (5 bytes: 5e 83 c4 1c c3)
# We replace these 5 bytes with a JMP to our code cave.
PATCH_VMA = 0x987145
PATCH_ORIGINAL = bytes([0x5e, 0x83, 0xc4, 0x1c, 0xc3])
# Code cave location: end of .text section padding
# .text ends at VMA 0xab4b4a, next section at 0xab5000
# We use 0xab4b50 (aligned)
CAVE_VMA = 0xAB4B50
# IAT addresses (absolute VMAs) for functions we call
IAT_GetModuleHandleA = 0xAB50C8
IAT_GetProcAddress = 0xAB50B0
# String addresses within our cave
STR_KERNEL32_VMA = CAVE_VMA + 0x20
STR_SLEEP_VMA = CAVE_VMA + 0x2D
def vma_to_file(vma):
"""Convert VMA to file offset (.text section)."""
return vma - IMAGE_BASE
def build_cave():
"""Build the code cave bytes."""
code = bytearray()
# pop esi
code += b'\x5e'
# add esp, 0x1c (28)
code += b'\x83\xc4\x1c'
# push STR_KERNEL32_VMA ("kernel32.dll")
code += b'\x68' + struct.pack('<I', STR_KERNEL32_VMA)
# call [IAT_GetModuleHandleA]
code += b'\xff\x15' + struct.pack('<I', IAT_GetModuleHandleA)
# push STR_SLEEP_VMA ("Sleep")
code += b'\x68' + struct.pack('<I', STR_SLEEP_VMA)
# push eax (hKernel32)
code += b'\x50'
# call [IAT_GetProcAddress]
code += b'\xff\x15' + struct.pack('<I', IAT_GetProcAddress)
# push 1
code += b'\x6a\x01'
# call eax (Sleep)
code += b'\xff\xd0'
# ret
code += b'\xc3'
# Pad to offset 0x20 for string data
code += b'\x00' * (0x20 - len(code))
# "kernel32.dll\0" at offset 0x20
code += b'kernel32.dll\x00'
# "Sleep\0" at offset 0x2d
assert len(code) == 0x2D
code += b'Sleep\x00'
return bytes(code)
def build_jmp(from_vma, to_vma):
"""Build a 5-byte JMP rel32 instruction."""
displacement = to_vma - (from_vma + 5)
return b'\xe9' + struct.pack('<i', displacement)
def main():
exe_path = os.path.join(GAME_DIR, EXE_NAME)
bak_path = os.path.join(GAME_DIR, BACKUP_NAME)
if not os.path.exists(exe_path):
print(f"Error: {exe_path} not found")
sys.exit(1)
# Read the exe
with open(exe_path, 'rb') as f:
data = bytearray(f.read())
# Verify the patch site has the expected bytes
patch_offset = vma_to_file(PATCH_VMA)
actual = bytes(data[patch_offset:patch_offset + len(PATCH_ORIGINAL)])
if actual != PATCH_ORIGINAL:
if actual == build_jmp(PATCH_VMA, CAVE_VMA):
print("Already patched!")
sys.exit(0)
print(f"Error: unexpected bytes at patch site 0x{PATCH_VMA:x}")
print(f" Expected: {PATCH_ORIGINAL.hex()}")
print(f" Got: {actual.hex()}")
sys.exit(1)
# Verify the cave area is empty (zeros)
cave_offset = vma_to_file(CAVE_VMA)
cave_bytes = build_cave()
cave_area = bytes(data[cave_offset:cave_offset + len(cave_bytes)])
if cave_area != b'\x00' * len(cave_bytes):
print(f"Error: code cave area at 0x{CAVE_VMA:x} is not empty")
sys.exit(1)
# Backup
if not os.path.exists(bak_path):
shutil.copy2(exe_path, bak_path)
print(f"Backup: {bak_path}")
else:
print(f"Backup already exists: {bak_path}")
# Write the code cave
data[cave_offset:cave_offset + len(cave_bytes)] = cave_bytes
print(f"Code cave: {len(cave_bytes)} bytes at VMA 0x{CAVE_VMA:x} (file 0x{cave_offset:x})")
# Patch the message pump exit to jump to our cave
jmp = build_jmp(PATCH_VMA, CAVE_VMA)
data[patch_offset:patch_offset + len(jmp)] = jmp
print(f"Patched: JMP at 0x{PATCH_VMA:x} -> 0x{CAVE_VMA:x}")
# Write
with open(exe_path, 'wb') as f:
f.write(data)
print("Done! Restart the game to test.")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment