Skip to content

Instantly share code, notes, and snippets.

@meunierd
Created June 9, 2012 22:40
Show Gist options
  • Save meunierd/2902870 to your computer and use it in GitHub Desktop.
Save meunierd/2902870 to your computer and use it in GitHub Desktop.
Python ROM Expander Pro
#!/usr/bin/env python
from binascii import unhexlify
import md5
__author__ = "Devon Meunier"
__email__ = "devon.meunier@utoronto.ca"
__license__ = "MIT"
__version__ = "0.3"
def get_md5(source):
"""Return the MD5 hash of the file `source`."""
m = md5.new()
while True:
d = source.read(8196)
if not d:
break
m.update(d)
return m.hexdigest()
def hex_to_bstr(d):
"""Return the bytestring equivalent of a plain-text hex value."""
if len(d) % 2:
d = "0" + d
return unhexlify(d)
def load_line(s):
"""Tokenize a tab-delineated string and return as a list."""
return s.strip().split('\t')
def load_script():
script = {}
script["file"] = "ROM Expander Pro.txt"
with open(script["file"]) as script_file:
script_lines = script_file.readlines()
# Load the `NAME` line from script.
l = load_line(script_lines.pop(0))
assert 'NAME' == l.pop(0)
script["source"], script["target"] = l
assert script["target"] != script["source"]
# Load the `SIZE` and optional `MD5`
l = load_line(script_lines.pop(0))
script["old_size"] = eval("0x" + l[1])
script["new_size"] = eval("0x" + l[2])
if l.index(l[-1]) > 2:
script["MD5"] = l[3].lower()
# Load the replacement `HEADER`.
l = load_line(script_lines.pop(0))
assert 'HEADER' == l.pop(0)
script["header_size"] = eval("0x" + l.pop(0))
assert script["header_size"] > len(l)
# Sanitize and concatenate the header data.
new_header = "".join(["0" * (2 - len(x)) + x for x in l])
# Cast to character data and pad with 0x00 to header_size
new_header = hex_to_bstr(new_header)
script["header"] = new_header + "\x00" * (script["header_size"] - len(l))
# Check the source file MD5.
if "MD5" in script:
with open(script["source"], "rb") as s_file:
# Don't digest the header.
s_file.read(script["header_size"])
assert script["MD5"] == get_md5(s_file)
script["ops"] = []
while script_lines:
script["ops"].append(load_line(script_lines.pop(0)))
script["patches"] = []
for op in script["ops"]:
if op[0] == "REPLACE":
op.pop(0)
script["patches"].append(op)
return script
def expand_rom(script):
print "Expanding..."
with open(script["source"], "rb") as s, open(script["target"], "wb") as t:
def copy(a, b):
source_ptr = script["header_size"] + a
write_ptr = script["header_size"] + b
s.seek(source_ptr)
t.seek(write_ptr)
t.write(s.read(end_ptr - write_ptr))
print "COPY"
def fill(destination, value):
write_ptr = script["header_size"] + destination
t.seek(write_ptr)
t.write(value * (end_ptr - write_ptr))
print "FILL"
# Write Header
t.write(script["header"])
while script["ops"]:
op = script["ops"].pop(0)
cmd = op.pop(0)
if not script["ops"]:
end_ptr = script["header_size"] + script["new_size"]
else:
end_ptr = eval("0x" + script["ops"][0][1]) + \
script["header_size"]
if cmd == "COPY":
copy(eval("0x" + op[1]), # Source
eval("0x" + op[0])) # Target
elif cmd == "FILL":
fill(eval("0x" + op[0]), # Destination
hex_to_bstr(op[1])) # Value
else:
raise Exception
# REPLACE
for patch in script["patches"]:
offset = eval("0x" + patch.pop(0))
data = "".join(["0" * (2 - len(x)) + x for x in patch])
t.seek(offset)
t.write(hex_to_bstr(data))
def run():
script = load_script()
expand_rom(script)
if __name__ == "__main__":
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment