Files
nfsmw/tools/replace_rodata.py
2025-05-14 01:29:16 +02:00

321 lines
11 KiB
Python

import sys
import os
import regex as re
from defines import DATADIR, WIPE_TYPE, WIPE_MODE, MOTION_ATTR, ATTR, SPRITE_ATTR
def rc(s: str) -> str:
return s.replace(",", "")
def join(*args) -> str:
return " ".join(args)
# def replace_rodata(file_content: str, name_line: str, value_line: str):
# variable_name = name_line.strip().removeprefix(".endobj ").split(",")[0]
# type, value = value_line.strip().split(" ")[:2]
# if type not in (".float", ".double"):
# return file_content
# if type == ".float":
# value = value + "f" if "." in value else value + ".0f"
# else:
# value = value if "." in value else value + ".0"
# return re.sub(
# r"\b%s\b" % variable_name, value, file_content
# ) # replace only whole words
def replace_double_var_loops(code: str) -> str:
"""
Where only var2 is bound checked
"""
loop_pattern = re.compile(
# r"([0-9a-z_]+) = ([0-9a-z&\._]+);\n\s*([0-9a-z_]+) = ([0-9a-z&\._]+);\n\s*loop_([0-9]+):\n\s*if \(\3 < (\(?.*?\)?)\) {([\s\S]*?)\n\s*(\3.*?);\n\s*(\1.*?);\n\s*goto loop_\5;\n\s+}" # works
r"([0-9a-z_]+) = (.+?);\n\s*?([0-9a-z_]+) = (.+?);\n\s*?loop_([0-9]+):\n\s*?if \(\3 (?:<|>) (\(?.*?\)?)\) {((?:(?!goto loop_\3)[\s\S])*?)\n\s*?(\3.*?);\n\s*?(\1.*?);\n\s*?goto loop_\5;\n\s+?}"
)
def replace(match: re.Match[str]):
loop_var1 = match.group(1)
loop_var_val1 = match.group(2)
loop_var2 = match.group(3)
loop_var_val2 = match.group(4)
loop_limit = match.group(6)
loop_body = match.group(7).strip()
loop_var2_change = match.group(8)
loop_var1_change = match.group(9)
# Recursively replace loops inside the loop body
loop_body = replace_single_var_loops(loop_body)
# TODO: loop_var2 < loop limit or loop_var1 < loop_limit?
for_loop = f"for ({loop_var1} = {loop_var_val1}, {loop_var2} = {loop_var_val2}; {loop_var2} < {loop_limit}; {loop_var2_change}, {loop_var1_change}) {{\n {loop_body}\n}}"
return for_loop
while re.search(loop_pattern, code):
code = re.sub(loop_pattern, replace, code)
return code
def replace_single_var_loops(code: str) -> str:
loop_pattern = re.compile(
# r"([0-9a-z_]+) = ([0-9a-z&\._]+);\n\s*loop_([0-9]+):\n\s*if \(\1 < (\(?.*?\)?)\) {([\s\S]*?)\n\s*(\1.*?);\n\s*goto loop_\3;\n\s+}" # works
# negative lookahead is required because loops might have the same name and if it doesn't match the closest, it might look further
r"([0-9a-z_]+) = (.+?);\n\s*?loop_([0-9]+):\n\s*?if \(\1 (<|>|<=|=>) (\(?.*?\)?)\) \{((?:(?!goto loop_\3)[\s\S])*?)\n\s*?(\1.*?);\n\s*?goto loop_\3;\n\s+?\}"
)
def replace(match: re.Match[str]):
loop_var = match.group(1)
# print(f"Loop var: {loop_var}")
loop_var_val = match.group(2)
# print(f"Loop var val: {loop_var_val}")
compare_operator = match.group(4)
loop_limit = match.group(5)
# print(f"Loop limit: {loop_limit}")
loop_body = match.group(6).strip()
# print(f"Loop body: {loop_body}")
loop_var_change = match.group(7)
# Recursively replace loops inside the loop body
loop_body = replace_single_var_loops(loop_body)
for_loop = f"for ({loop_var} = {loop_var_val}; {loop_var} {compare_operator} {loop_limit}; {loop_var_change}) {{\n {loop_body}\n}}"
return for_loop
while re.search(loop_pattern, code):
code = re.sub(loop_pattern, replace, code)
return code
def parse_number(num: str) -> int:
if num.startswith("0x"):
return int(num, 16)
else:
return int(num)
def replace_make_num_values(code: str) -> str:
num_pattern = re.compile(r"0x([0-9a-fA-F]{2})00([0-9a-fA-F]{2})")
def replace(match: re.Match[str]):
folder = parse_number("0x" + match.group(1))
file = match.group(2)
return f"DATA_MAKE_NUM(DATADIR_{DATADIR[folder]}, {file})"
while re.search(num_pattern, code):
try:
code = re.sub(num_pattern, replace, code)
except IndexError:
pass
return code
def replace_attr_values(code: str) -> str:
hu3d_pattern = re.compile(
r"(Hu3DMotionShiftSet|Hu3DModelAttrSet|Hu3DModelAttrReset)\((.*),\s?((?:0x)?[0-9a-fA-F]+)\);"
)
def replace_hu3d(match: re.Match[str]):
function_name = match.group(1)
rest = match.group(2)
value = parse_number(match.group(3))
array = MOTION_ATTR if value & 0x40000000 else ATTR
if value == 0:
res = [array[0]]
else:
res = [array[i] for i in range(1, len(array)) if value & (1 << (i - 1))]
return f"{function_name}({rest}, {' | '.join(res)});"
while re.search(hu3d_pattern, code):
code = re.sub(hu3d_pattern, replace_hu3d, code)
# sprite
sprite_pattern = re.compile(
r"(HuSprAttrSet|HuSprAttrReset|espAttrSet|espAttrReset)\((.*),\s?((?:0x)?[0-9a-fA-F]+)\);"
)
def replace_sprite(match: re.Match[str]):
function_name = match.group(1)
rest = match.group(2)
value = parse_number(match.group(3))
if value == -1:
res = [SPRITE_ATTR[0]]
else:
res = []
for i in range(1, len(SPRITE_ATTR)):
if value & (1 << (i - 1)):
res.append(SPRITE_ATTR[i])
return f"{function_name}({rest}, {' | '.join(res)});"
while re.search(sprite_pattern, code):
code = re.sub(sprite_pattern, replace_sprite, code)
return code
def replace_wipe_values(code: str) -> str:
wipe_pattern = re.compile(
r"WipeCreate\((-?(?:0x)?[0-9a-fA-F]+),\s*(-?(?:0x)?[0-9a-fA-F]+),(.*)\);"
)
def replace(match: re.Match[str]):
mode = parse_number(match.group(1))
type = parse_number(match.group(2))
duration = match.group(3)
return f"WipeCreate({WIPE_MODE[mode - 1]}, {WIPE_TYPE[type + 1]},{duration});"
while re.search(wipe_pattern, code):
code = re.sub(wipe_pattern, replace, code)
return code
def interpret_file(asm_file: str, c_file: str) -> None:
asm_file = os.path.normpath(asm_file)
c_file = os.path.normpath(c_file)
f = open(asm_file)
g = open(c_file, "r+")
c_file_content = g.read()
g.seek(0)
# line = ""
# for line in f:
# if line.startswith(".obj lbl_1_rodata_"):
# value_line = f.readline()
# # we only replace single values
# variable_name_line = line = f.readline()
# if variable_name_line.startswith(".endobj lbl_1_rodata_"):
# c_file_content = replace_rodata(
# c_file_content, variable_name_line, value_line
# )
c_file_content = (
c_file_content.replace("f32", "float")
.replace("f64", "double")
.replace("Point3d", "Vec")
.replace("sp08", "sp8")
.replace("sp09", "sp9")
.replace("sp0A", "spA")
.replace("sp0B", "spB")
.replace("sp0C", "spC")
.replace("sp0D", "spD")
.replace("sp0E", "spE")
.replace("sp0F", "spF")
.replace("/* irregular */", "")
.replace("/* inferred */", "")
.replace("/* fallthrough */", "")
.replace("(bitwise float)", "")
.replace("(bitwise s32)", "")
.replace("(bitwise s64)", "")
.replace("(bitwise double)", "")
.replace("(bitwise double *)", "")
)
c_file_content = re.sub(
r"(sin|cos)\(\(3\.141592653589793 \* (.*)\) / 180\.0\)",
r"\1d(\2)",
c_file_content,
)
c_file_content = re.sub(
r"180\.0 \* \(atan2\((.*), (.*)\) / 3\.141592653589793\)",
r"atan2d(\1,\2)",
c_file_content,
)
c_file_content = re.sub(
r"Hu3DModelCreate\(HuDataSelHeapReadNum\((.*), 0x10000000, HEAP_DATA\)\)",
r"Hu3DModelCreateFile(\1)",
c_file_content,
)
c_file_content = re.sub(
r"HuSprAnimRead\(HuDataSelHeapReadNum\((.*), 0x10000000, HEAP_DATA\)\)",
r"HuSprAnimReadFile(\1)",
c_file_content,
)
c_file_content = re.sub(
r"Hu3DJointMotion\((.*), HuDataSelHeapReadNum\((.*), 0x10000000, HEAP_DATA\)\)",
r"Hu3DJointMotionFile(\1, \2)",
c_file_content,
)
c_file_content = re.sub(
r"HuMemDirectMallocNum\(HEAP_SYSTEM, (.*), 0x10000000\);",
r"HuMemDirectMallocNum(HEAP_SYSTEM, \1, MEMORY_DEFAULT_NUM);",
c_file_content,
)
c_file_content = re.sub(
r"if \(_CheckFlag\(0x1000C\) == 0\) {\n\s*\(GWPlayer \+ \(([0-9a-zA-Z_]+) \* 0x30\)\)->unk_28 = ([0-9a-z_]+);\n\s*}",
r"GWPlayerCoinWinSet(\1, \2);",
c_file_content,
)
c_file_content = re.sub(
r"struct ([_0-9a-zA-Z]+) {([\s\S]*?\n)};",
r"typedef struct \1 { \2 } \1;",
c_file_content,
)
c_file_content = re.sub(
r"float \(\*([_0-9a-zA-Z]+)\)\[4\]", r"Mtx \1", c_file_content
)
c_file_content = re.sub(r"\(&(sp[_0-9a-zA-Z]+)\[\d\]\)", r"\1", c_file_content)
c_file_content = re.sub(r"&(sp[_0-9a-zA-Z]+)\[\d\]", r"\1", c_file_content)
c_file_content = re.sub(r"\(&(sp[_0-9a-zA-Z]+)\)", r"\1", c_file_content)
# cast thing
c_file_content = re.sub(
r"\s*var_f([0-9]{1,2}) = var_f\1;$", "", c_file_content, flags=re.MULTILINE
)
c_file_content = re.sub(
r"\*([_0-9a-zA-Z]+)->model", r"\1->model[0]", c_file_content
)
c_file_content = re.sub(
r"\*([_0-9a-zA-Z]+)->motion", r"\1->motion[0]", c_file_content
)
c_file_content = re.sub(r"^static ", "", c_file_content, flags=re.MULTILINE)
c_file_content = re.sub(r"\/\* switch \d+(; ?irregular)? \*\/", "", c_file_content)
c_file_content = replace_attr_values(c_file_content)
c_file_content = replace_wipe_values(c_file_content)
# c_file_content = replace_make_num_values(c_file_content) # picks up too much
c_file_content = replace_double_var_loops(c_file_content)
c_file_content = replace_single_var_loops(c_file_content)
# c_file_content = re.sub(
# r"([0-9a-z_]+) = ([0-9a-z_]+);\n\s*loop_([0-9]+):\n\s*if \(\1 < ([0-9a-z_]+)\) {(.*)\n\s*(\1.*);\n\s*goto loop_\3;\n\s+}",
# r"for (\1 = \2; \1 < \4; \6) {\5\n}",
# c_file_content,
# flags = re.S
# )
# c_file_content = re.sub(
# r"\*\(&([0-9a-z_]+[0-9A-F]+) \+ \((.*) \* \d+\)\)", r"\1[\2]", c_file_content
# )
# c_file_content = re.sub(r"([\w\d]+.*) ([\+\-])= 1", r"\1\2\2", c_file_content)
c_file_content = re.sub(
r"([\w\d]+.*) ([\+\-])= 1([;,)])", r"\1\2\2\3", c_file_content
)
c_file_content = re.sub(r"([\w\d]+.*) = \1 ([\+\-]) 1;", r"\1\2\2;", c_file_content)
g.write(c_file_content)
g.truncate()
g.close()
f.close()
try:
interpret_file(sys.argv[1], sys.argv[2])
except Exception:
if len(sys.argv) < 3:
print("replace_rodata.py")
print("Usage: python3 extract_struct.py [asm file] [c file]")
print("Accepts all files that use the generated asm from ninja")
else:
raise Exception