mgs_reversing/build/include_asm_preprocess.py

121 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
import re
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
RETURN_SIZE = 12 # just the return
NOP_SIZE = 4
# #pragma INCLUDE_ASM("asm/chara/snake/sna_800515BC.s");
PRAGMA_RE = r'^#pragma\s+INCLUDE_ASM\s*\(\s*"([^"]+)"\s*\).*$'
# all on one line to keep error line numbers correct
FUNC_FMT = r'int {}(void) {{ asm("{}"); return {}; }}'
# _800515BC.
ADDR_SUFFIX_RE = r'_([0-9A-F]{8})\.'
TMP_DIR = 'include_asm_tmp'
def main(path, output, obj_path):
with open(path, encoding='utf8') as f:
lines = f.readlines()
processed = []
depends = []
for raw_line in lines:
line = raw_line.strip()
if not line.startswith('#pragma'):
processed.append(raw_line)
continue
m = re.match(PRAGMA_RE, line)
if not m:
processed.append(raw_line)
continue
include_path = m.group(1)
if '\\' in include_path:
print("error: INCLUDE_ASM paths should not use backslashes in: ", include_path, file=sys.stderr)
sys.exit(1)
m = re.search(ADDR_SUFFIX_RE, include_path)
if not m:
print('error: INCLUDE_ASM paths should have the _<ADDR>.s suffix', file=sys.stderr)
sys.exit(2)
addr_str = m.group(1)
addr = int(addr_str, 16)
func_size = open(os.path.join('..', include_path), 'r').read().count('dw 0x') * 4
if func_size < RETURN_SIZE + NOP_SIZE:
print('error: INCLUDE_ASM func too small - consider matching it: ', addr_str, file=sys.stderr)
sys.exit(5)
num_nops = func_size - RETURN_SIZE
assert num_nops % NOP_SIZE == 0
nops = 'nop;' * int(num_nops / NOP_SIZE)
name = os.path.basename(include_path).replace('.s', '')
first_char = ord(name[0])
upper_byte = addr >> 24
assert upper_byte == 0x80
# we need to temporarily rename the func without changing its size so
# encode first char of name into 0x80 part of addr since that's constant for us
addr = (addr & 0x00FFFFFF) | (first_char << 24)
name_chars = list(name)
name_chars[0] = '_'
name = ''.join(name_chars)
func = FUNC_FMT.format(name, nops, hex(addr)) + '\n'
processed.append(func)
depends.append(include_path.replace('.s', '.obj').replace('asm/', f'{obj_path}/'))
with open(output, 'w', encoding='utf8') as f:
f.write(''.join(processed))
with open(output + '.deps', 'w') as f:
f.write('\n'.join(depends) + '\n')
# nina picks this up due to deps=msvc which ensures the post process
# will only run after these required objs are built
#for d in depends:
# print("Note: including file: " + os.path.abspath(d))
#print("output = " + output)
dynDepName = output.replace(".c.asm.preproc", ".c.dyndep")
#print("Dyndep file = " + dynDepName)
targetAddTo = output.replace(".c.asm.preproc", ".obj")
targetAddTo = targetAddTo.replace(":", "$:") # escape colon in drive path
#print("targetAddTo = " + targetAddTo)
with open(dynDepName, 'w') as f:
f.write("ninja_dyndep_version = 1\n")
depsStr = ""
if len(depends) > 0:
for d in depends:
d = '../' + d
d = d.replace("\\", "/")
d = d.replace(":", "$:") # escape colon in drive path
depsStr = depsStr + " " + d
f.write("build " + targetAddTo + ": dyndep | " + depsStr + "\n")
else:
f.write("build " + targetAddTo + ": dyndep" + depsStr + "\n")
if __name__ == '__main__':
obj_path = sys.argv[1].replace('\\', '/')
src = sys.argv[2].replace('\\', '/')
dst = sys.argv[3].replace('\\', '/')
main(src, dst, obj_path)