#! /usr/bin/env python3 import sys def align(start, end, alignment): """Align (, ) on multiples of .""" start = (start + alignment - 1) & ~(alignment - 1) end &= ~(alignment - 1) return start, end def hexa(data): """Readable notation for bytes/bytearray objects""" return " ".join(f"{x:02x}" for x in data) def vardict(vars): """Readable notation for variable dictionaries""" return " ".join(f"{k}={v:x}" for k, v in vars.items()) #--- class Pattern: def __init__(self, string, nibbles): self.string = string self.nibbles = nibbles def __repr__(self): return self.string def make_pattern(string): """Compile a pattern of pure hex intermixed with "" references into a list of either ints or strings wrapped as a Pattern object.""" nibbles = [] i = 0 while i < len(string): if string[i] == "<": end = string.index(">", i+1) nibbles.append(string[i+1:end]) i = end+1 else: nibbles.append(int(string[i], 16)) i += 1 return Pattern(string, nibbles) def match_pattern(p, data, vars): """Match against , unifying with variables ; returns either False or extended set of variables.""" if 2 * len(data) != len(p.nibbles): return False pos = 0 vars = vars.copy() for pn in p.nibbles: dn = (data[pos // 2] >> (0 if pos % 2 else 4)) & 0xf if isinstance(pn, str): if pn in vars and vars[pn] != dn: return False vars[pn] = dn elif pn != dn: return False pos += 1 return vars def search_pattern(p, data, start, end, *, alignment=1): """Returns a list of (, ) for all occurrences of pattern

in -aligned positions of between and .""" start, end = align(start, end, alignment) n = len(p.nibbles) // 2 matches = [] for offset in range(start, end, alignment): v = match_pattern(p, data[offset:offset+n], dict()) if v != False: matches.append((offset, v)) return matches def eval_pattern(p, vars): """Evaluate

by substituting variables from .""" data = [] for n in p.nibbles: if isinstance(n, str): data.append(vars[n]) else: data.append(n) return bytes((data[2*i] << 4) | data[2*i+1] for i in range(len(data) // 2)) #--- class Transform: def __init__(self, name, patterns): self.name = name self.insn = [(make_pattern(p), make_pattern(repl)) for p, repl in patterns] def match_transform(tr, base_vars, data, start, end): """Matches all instructions from in between and , returning either (False, []) or a pair (, [...]) indicating where instructions matched and with what variables.""" start, end = align(start, end, 2) vars = base_vars.copy() offsets = [] for p, repl in tr.insn: n = len(p.nibbles) // 2 for offset in range(start, end, 2): d = data[offset:offset+n] v = match_pattern(p, d, vars) if v != False: offsets.append(offset) vars = v break else: return (False, []) return (vars, offsets) def apply_transform(tr, vars, offsets, data): """Apply replacements from on in-place, using the and returned by match_transform().""" repls = [repl for p, repl in tr.insn] replacements = [(offset, eval_pattern(p, vars)) for offset, p in zip(offsets, repls)] for offset, contents in replacements: n = len(contents) src = data[offset:offset+n] print(f" Patching at 0x{offset:08x}: {hexa(src)} -> {hexa(contents)}") data[offset:offset+n] = contents #--- def pcrel_compute(pc, disp, *, size=2): """Computes the target of a @(disp,pc) argument.""" return (pc & ~(size - 1)) + 4 + (disp * size) def pcrel_find(target, data, start, end): """Finds all instructions that load with @(disp,pc) in between and ") v = match_pattern(movw_atdisppc_rn, data[pc:pc+2], dict()) if v != False: disp = (v["D1"] << 4) | v["D2"] if pcrel_compute(pc, disp, size=2) == target: occurrences.append((pc, v["N"])) # mov.l @(disp,pc), rn movl_atdisppc_rn = make_pattern("d") v = match_pattern(movl_atdisppc_rn, data[pc:pc+2], dict()) if v != False: disp = (v["D1"] << 4) | v["D2"] if pcrel_compute(pc, disp, size=4) == target: occurrences.append((pc, v["N"])) return occurrences #--- ML_display_vram = Transform("ML_display_vram", [ ("e07", "e0a"), # Data register is 10 ("e00", "e04"), # Y-Up mode is 4 ("e04", "e08"), # Command register is 8 ("63", "e00"), # Still 0, but rB was changed; use mov #imm, rn ("ec0", "e80"), # X-address starts at 0x80 ]) ML_set_contrast = Transform("ML_set_contrast", [ ("e06", "e06"), # Old contrast register: 6 (detection only) ("20", "0009"), # Remove: selection of register 6 ("20", "0009"), # Remove: write contrast value ]) ML_get_contrast = Transform("ML_get_contrast", [ ("e06", "e06"), # Old contrast register: 6 (detection only) ("20", "0009"), # Remove: selection of register 6 ("600", "e000"), # Remove: read contrast value; return 0 ]) ALL_TRANSFORMS = [ ML_display_vram, ML_set_contrast, ML_get_contrast, ] def main(g1a_input, g1a_output): with open(g1a_input, "rb") as fp: data = bytearray(fp.read()) # Find 0xb4000000 and 0xb4010000 t6k11_addr1 = make_pattern("b4000000") refs1 = search_pattern(t6k11_addr1, data, 0, len(data), alignment=4) t6k11_addr2 = make_pattern("b4010000") refs2 = search_pattern(t6k11_addr2, data, 0, len(data), alignment=4) if len(refs1) == 0: print(f"no reference to T6K11 address {t6k11_addr1} found") return 1 for offset, _ in refs1: print(f"T6K11 reference address {t6k11_addr1} found at 0x{offset:08x}") fun_occ = pcrel_find(offset, data, 0, len(data)) if len(fun_occ) == 0: print(" Never loaded") for (pc, n) in fun_occ: print(f" 0x{pc:08x}: Load into r{n}") print() # Find function boundaries around, assuming rts marks the end of a function # and the start of the next (which is resonable considering the size and # simplicity of these functions) # Look up to 32 bytes behind, 128 bytes ahead LOOKBEHIND = 32 LOOKAHEAD = 128 RTS = bytes([0x00, 0x0b]) # 000b rts functions = [] for pc, n in fun_occ: print(f"Looking for function around 0x{pc:08x}...") start, end = -1, -1 for i in range(0, LOOKBEHIND // 2): if data[pc-2*(i+1):pc-2*i] == RTS: start = pc-2*(i+1)+4 print(f" Starting at 0x{start:08x} after rts") break else: print(f" No rts found up to {LOOKBEHIND} bytes behind") for i in range(0, LOOKAHEAD // 2): if data[pc+2*i:pc+2*(i+1)] == RTS: end = pc+2*i+4 print(f" Stopping at 0x{end:08x} after rts") break else: print(f" No rts found up to {LOOKAHEAD} bytes ahead") if start >= 0 and end >= 0: # Find register holding 0xb4000000 vars = { "D0": n } print(f" 0xb4000000 loaded in r{vars['D0']}") # Find register holding 0xb4010000 for offset, _ in refs2: occurrences = pcrel_find(offset, data, start, end) if len(occurrences) > 0: vars["D1"] = occurrences[0][1] print(f" 0xb4010000 loaded in r{vars['D1']}") break functions.append((start, end, vars)) patched_functions = 0 ml_display_vram_found = False for start, end, base_vars in functions: print(f"\nFunction analysis for 0x{start:08x} ... 0x{end:08x}:") for tr in ALL_TRANSFORMS: vars, offsets = match_transform(tr, base_vars, data, start, end) if vars == False: continue print(f" Identified {tr.name} with [{vardict(vars)}]:") maxlen = max(len(str(p)) for p, repl in tr.insn) for offset, (p, repl) in zip(offsets, tr.insn): n = len(p.nibbles) // 2 d = data[offset:offset+n] print(f" Matched {str(p):{maxlen}s} at 0x{offset:08x}:", hexa(d)) apply_transform(tr, vars, offsets, data) patched_functions += 1 if tr.name == "ML_display_vram": ml_display_vram_found = True with open(g1a_output, "wb") as fp: fp.write(data) print() print(f"References found to {t6k11_addr1}:", len(refs1)) print(f"References found to {t6k11_addr2}:", len(refs2)) print(f"Functions: {len(fun_occ)} hints, {len(functions)} analyzed, " f"{patched_functions} patched") print(f"ML_display_vram found:", ml_display_vram_found) if not ml_display_vram_found: print("ML_display_vram not found!") return 1 elif patched_functions == 0: print("Nothing patched!") return 1 else: print("Success!") return 0 if __name__ == "__main__": if len(sys.argv) != 3 or "-h" in sys.argv[1:] or "--help" in sys.argv[1:]: print(f"usage: {sys.argv[0]} ",file=sys.stderr) sys.exit(0) sys.exit(main(sys.argv[1], sys.argv[2]))