mirror of
https://github.com/encounter/nfsmw.git
synced 2026-03-30 11:31:13 -07:00
321 lines
11 KiB
Python
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
|