Skip to content

Instantly share code, notes, and snippets.

@veygax
Created February 17, 2025 13:13
Show Gist options
  • Save veygax/5b3136a2d4316f050c08b8a707993c82 to your computer and use it in GitHub Desktop.
Save veygax/5b3136a2d4316f050c08b8a707993c82 to your computer and use it in GitHub Desktop.
Python scripts I made for generating and applying custom hex patches.
#!/usr/bin/env python3
import sys
import json
import os
from pathlib import Path
import argparse
def apply_hex_patches(patches_file, target_file, output_file=None):
with open(patches_file, 'r') as f:
patch_data = json.load(f)
with open(target_file, 'rb') as f:
file_data = bytearray(f.read())
if output_file is None:
target_path = Path(target_file)
output_file = f"{target_path.parent / target_path.stem}_patched{target_path.suffix}"
for patch in patch_data['patches']:
# Convert hex offset to integer
offset = int(patch['offset'], 16)
modified_bytes = bytes.fromhex(patch['modified_bytes'].replace(' ', ''))
# Handle file extension cases
if not patch['original_bytes'] and modified_bytes:
file_data.extend(modified_bytes)
elif not patch['modified_bytes']:
file_data = file_data[:offset]
else:
original_bytes = bytes.fromhex(patch['original_bytes'].replace(' ', ''))
if file_data[offset:offset + len(original_bytes)] != original_bytes:
raise ValueError(f"Original bytes at offset {patch['offset']} do not match the expected values")
file_data[offset:offset + len(modified_bytes)] = modified_bytes
with open(output_file, 'wb') as f:
f.write(file_data)
return output_file
def main():
parser = argparse.ArgumentParser(description='Apply hex patches to a file')
parser.add_argument('patches_file', help='JSON file containing the patches')
parser.add_argument('target_file', help='File to apply patches to')
parser.add_argument('-o', '--output', help='Output file path (optional)')
args = parser.parse_args()
if not args.patches_file.lower().endswith('.json'):
print("Error: Patches file must be a JSON file")
sys.exit(1)
try:
output_path = apply_hex_patches(args.patches_file, args.target_file, args.output)
print(f"Patches applied successfully. Output file: {output_path}")
except Exception as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
import sys
import json
import os
from pathlib import Path
import argparse
def generate_hex_patches(original_file, modified_file, output_file=None):
# Read both files in binary mode
with open(original_file, 'rb') as f1, open(modified_file, 'rb') as f2:
original_data = f1.read()
modified_data = f2.read()
patches = []
offset = 0
current_patch = None
# Compare byte by byte
for orig_byte, mod_byte in zip(original_data, modified_data):
if orig_byte != mod_byte:
if current_patch is None:
current_patch = {
'offset': f'0x{offset:08x}', # Convert to hex string with 8 digits
'original_bytes': [orig_byte],
'modified_bytes': [mod_byte]
}
else:
# If this difference is consecutive, append to current patch
if offset == int(current_patch['offset'], 16) + len(current_patch['original_bytes']):
current_patch['original_bytes'].append(orig_byte)
current_patch['modified_bytes'].append(mod_byte)
else:
# Convert bytes to hex strings and finalize current patch
current_patch['original_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['original_bytes']])
current_patch['modified_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['modified_bytes']])
patches.append(current_patch)
current_patch = {
'offset': f'0x{offset:08x}', # Convert to hex string with 8 digits
'original_bytes': [orig_byte],
'modified_bytes': [mod_byte]
}
else:
if current_patch is not None:
# Convert bytes to hex strings and finalize current patch
current_patch['original_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['original_bytes']])
current_patch['modified_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['modified_bytes']])
patches.append(current_patch)
current_patch = None
offset += 1
if current_patch is not None:
current_patch['original_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['original_bytes']])
current_patch['modified_bytes'] = ' '.join([f'{b:02x}' for b in current_patch['modified_bytes']])
patches.append(current_patch)
if len(original_data) != len(modified_data):
if len(original_data) < len(modified_data):
extra_bytes = modified_data[len(original_data):]
patches.append({
'offset': f'0x{len(original_data):08x}', # Convert to hex string with 8 digits
'original_bytes': '',
'modified_bytes': ' '.join([f'{b:02x}' for b in extra_bytes])
})
else:
missing_bytes = original_data[len(modified_data):]
patches.append({
'offset': f'0x{len(modified_data):08x}', # Convert to hex string with 8 digits
'original_bytes': ' '.join([f'{b:02x}' for b in missing_bytes]),
'modified_bytes': ''
})
if output_file is None:
original_path = Path(original_file)
output_file = f"{original_path.stem}_{original_path.suffix[1:]}_patches.json"
else:
if not output_file.lower().endswith('.json'):
output_file += '.json'
with open(output_file, 'w') as f:
json.dump({
'original_file': original_file,
'modified_file': modified_file,
'patches': patches
}, f, indent=2)
return output_file
def main():
parser = argparse.ArgumentParser(description='Generate hex patches between two files')
parser.add_argument('original_file', help='Path to the original file')
parser.add_argument('modified_file', help='Path to the modified file')
parser.add_argument('-o', '--output', help='Output JSON file path (optional)')
args = parser.parse_args()
try:
output_path = generate_hex_patches(args.original_file, args.modified_file, args.output)
print(f"Patch file generated successfully: {output_path}")
except Exception as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment