summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorYunataSavior <58997725+YunataSavior@users.noreply.github.com>2025-06-21 21:22:44 -0700
committerGitHub <noreply@github.com>2025-06-21 21:22:44 -0700
commit4278b8ad8d00a9f905f5667358814d045d1e8ddb (patch)
tree988599edcb43668c9a61d019ad2b06d02a6320d4 /tools
parent4f2f3b2a2a7f76e5de760fe1629a7205d3afaf9c (diff)
Further beautify anm data param handling (#2496)
Diffstat (limited to 'tools')
-rw-r--r--tools/utilities/beautify_anm_data.py217
1 files changed, 168 insertions, 49 deletions
diff --git a/tools/utilities/beautify_anm_data.py b/tools/utilities/beautify_anm_data.py
index 63c5f2d044..2f22f618ab 100644
--- a/tools/utilities/beautify_anm_data.py
+++ b/tools/utilities/beautify_anm_data.py
@@ -5,6 +5,7 @@
# Used for decompiling d_a_npc_ETC TUs.
#
+import fire
import os
import re
import sys
@@ -30,6 +31,11 @@ PARAM_TYPE = "::m"
PARAM_PATTERN = r'SECTION_RODATA u8 const (\w+_Param_c)::m\[\d+\] = {'
+def unsigned_val(hexstr):
+ value = int(hexstr, 16)
+ return value
+
+
def twos_complement(hexstr, bits):
# https://stackoverflow.com/questions/6727875/hex-string-to-signed-int-in-python
value = int(hexstr, 16)
@@ -61,10 +67,156 @@ def hex_to_float(hex_str):
def float_to_hex(f):
[d] = struct.unpack(">I", struct.pack(">f", f))
- return f"0x{d:X}"
+ return f"{d:08X}"
+
+
+def handle_npc_param(byte_collection, param_name, type):
+ my_len = len(byte_collection)
+ if my_len % 4 != 0:
+ print(f"Error: len() = '{my_len}' isn't divisble by 4")
+ sys.exit(1)
+
+ # Special handling.
+ instr_arr = []
+ common_name = ""
+ if type is None:
+ print("ERROR: --type <type> must be specified")
+ sys.exit(1)
+ elif type == "daNpcT":
+ common_name = "daNpcT_HIOParam"
+ instr_arr = [
+ "f4", # attention_offset;
+ "f4", # gravity;
+ "f4", # scale;
+ "f4", # real_shadow_size;
+ "f4", # weight;
+ "f4", # height;
+ "f4", # knee_length;
+ "f4", # width;
+ "f4", # body_angleX_max;
+ "f4", # body_angleX_min;
+ "f4", # body_angleY_max;
+ "f4", # body_angleY_min;
+ "f4", # head_angleX_max;
+ "f4", # head_angleX_min;
+ "f4", # head_angleY_max;
+ "f4", # head_angleY_min;
+ "f4", # neck_rotation_ratio;
+ "f4", # morf_frame;
+ "s2", # talk_distance;
+ "s2", # talk_angle;
+ "s2", # attention_distance;
+ "s2", # attention_angle;
+ "f4", # fov;
+ "f4", # search_distance;
+ "f4", # search_height;
+ "f4", # search_depth;
+ "s2", # attention_time;
+ "s2", # damage_time;
+ "s2", # face_expression;
+ "s2", # motion;
+ "s2", # look_mode;
+ "u1", # debug_mode_ON;
+ "u1", # debug_info_ON;
+ "f4", # expression_morf_frame;
+ "f4", # box_min_x;
+ "f4", # box_min_y;
+ "f4", # box_min_z;
+ "f4", # box_max_x;
+ "f4", # box_max_y;
+ "f4", # box_max_z;
+ "f4", # box_offset;
+ ]
+ elif type == "daNpcF":
+ # TODO
+ print("ERROR: --type daNpcF is currently unsupported.")
+ print(" You can try to implement it yourself.")
+ print(" If you need help with this, please seek the help of yunatasavior on Discord.")
+ sys.exit(1)
+ else:
+ print(f"ERROR: --type {type} is unsupported. Please review the script to see which are supported.")
+ sys.exit(1)
+
+ ptr = 0
+ cur_instr = 0
+ hexstr = ""
+ res_array = []
+ common_size = 0
+ while ptr < my_len:
+ curbyte = byte_collection[ptr]
+ ptr += 1
+ if curbyte[:2] != "0x" or len(curbyte) != 4:
+ print(f"Error: '{curbyte}' isn't formatted as a byte")
+ sys.exit(1)
+
+ hexstr += curbyte[-2:]
+ my_type = "h"
+ exp_bytes = 4
+ if cur_instr < len(instr_arr):
+ my_type = instr_arr[cur_instr][0]
+ exp_bytes = int(instr_arr[cur_instr][1])
+
+ if len(hexstr) / 2 == exp_bytes:
+ if my_type == 'u':
+ val = unsigned_val(hexstr)
+ res_array.append(val)
+ elif my_type == 's':
+ val = twos_complement(hexstr, exp_bytes*8)
+ res_array.append(val)
+ elif my_type == 'h':
+ res_array.append("0x" + hexstr)
+ elif my_type == 'f':
+ fvalue = hex_to_float(hexstr)
+ fvalue = round(fvalue, 6)
+ chk_val = float_to_hex(fvalue)
+ assert chk_val == hexstr, f"fvalue({fvalue}): chk_val {chk_val} != hexstr {hexstr}"
+ res_array.append(fvalue)
+ else:
+ print(f"Error: unknown type '{my_type}'")
+ sys.exit(1)
+
+ hexstr = ""
+ if common_size == 0:
+ cur_instr += 1
+
+ if cur_instr == len(instr_arr):
+ if common_size == 0:
+ common_size = ptr
+
+ assert common_size != 0, "Param array is too short for specified type"
+ hio_name = re.sub(r'_Param_c$', '_HIOParam', param_name)
+ res_str = "// Must be put OUTSIDE {}:\n".format(param_name)
+ res_str += "struct {} {{\n".format(hio_name)
+ idx = common_size
+ res_str += " /* 0x00 */ {} common;\n".format(common_name)
+ while idx < my_len:
+ upper = f'{idx:02X}'
+ lower = f'{idx:02x}'
+ res_str += " /* 0x{} */ u32 field_0x{};\n".format(upper, lower)
+ idx += 4
+
+ res_str += "};\n\n"
+ res_str += "// Must be put INSIDE {}:\n".format(param_name)
+ res_str += "static const {} m;\n\n".format(hio_name)
+ res_str += "const {} {}::m".format(hio_name, param_name)
+
+ res_str += " = {\n"
+ for value in res_array:
+ res_str += " "
+ if isinstance(value, int):
+ res_str += str(value)
+ elif isinstance(value, float):
+ res_str += str(value) + "f"
+ else:
+ res_str += value
+ res_str += ",\n"
+
+ res_str += "};\n"
+ print(res_str)
-def build_anm_struct(byte_collection, anm_type, param_name):
+
+def build_anm_struct(byte_collection, anm_type):
my_len = len(byte_collection)
piece_size = 1
instr_arr = []
@@ -103,7 +255,6 @@ def build_anm_struct(byte_collection, anm_type, param_name):
hexstr = ""
full_res_arr = []
pos_arr = []
- prms_is_float: list[bool] = []
while ptr < my_len:
curbyte = byte_collection[ptr]
ptr += 1
@@ -122,8 +273,6 @@ def build_anm_struct(byte_collection, anm_type, param_name):
if anm_type is HEAP_SIZE_TYPE:
trimmed = hexstr.lstrip('0')
hexstr = trimmed if trimmed else '0'
- elif anm_type is PARAM_TYPE:
- prms_is_float.append(prm_is_float(hexstr))
pos_arr.append("0x" + hexstr)
else:
print(f"Error: unknown type '{my_type}'")
@@ -137,20 +286,7 @@ def build_anm_struct(byte_collection, anm_type, param_name):
pos_arr.clear()
res_str = ""
- if anm_type is PARAM_TYPE:
- res_str += "struct Data {\n"
- idx = 0
- while idx < my_len:
- upper = f'{idx:02X}'
- lower = f'{idx:02x}'
- mych = 'f' if (prms_is_float[int(idx / 4)] is True) else 'u'
- res_str += " /* 0x{} */ {}32 field_0x{};\n".format(upper, mych, lower)
- idx += 4
- res_str += "};\n"
- res_str += "static const Data m;\n\n"
- res_str += "{}::Data const {}::m".format(param_name, param_name)
- else:
- res_str += "static {}".format(anm_type)
+ res_str += "static {}".format(anm_type)
res_len = my_len / piece_size
cutoff_num = 1
@@ -159,11 +295,7 @@ def build_anm_struct(byte_collection, anm_type, param_name):
elif anm_type is HEAP_SIZE_TYPE:
cutoff_num = 4
- if anm_type is not PARAM_TYPE:
- res_str += "[{}]".format(int(res_len))
-
- prmfloat_dbg = False
-
+ res_str += "[{}]".format(int(res_len))
res_str += " = {\n"
cur_in_line = 0
cur_idx = 0
@@ -186,17 +318,7 @@ def build_anm_struct(byte_collection, anm_type, param_name):
elif isinstance(value, float):
res_str += str(value) + "f"
else:
- if anm_type is PARAM_TYPE and prms_is_float[cur_idx] is True:
- fvalue = hex_to_float(value[2:])
- fvalue = round(fvalue, 6)
- res_str += f"{fvalue}f"
- chk_val = float_to_hex(fvalue)
- # Sanity check in case rounding is too aggressive:
- assert chk_val == value, f"chk_val {chk_val} != value {value}"
- if prmfloat_dbg is True:
- res_str += f" // {value}"
- else:
- res_str += value
+ res_str += value
if is_array is True:
res_str += "}"
@@ -218,7 +340,12 @@ def build_anm_struct(byte_collection, anm_type, param_name):
print(res_str)
-def run_beautify_anm_data(in_file):
+def run_beautify_anm_data(in_file, type=None):
+ # Check if the file exists
+ if not os.path.isfile(in_file):
+ print(f"Error: File '{in_file}' not found.")
+ sys.exit(1)
+
fDesc = open(in_file, 'r')
fConts = fDesc.read()
lines = fConts.splitlines()
@@ -257,7 +384,10 @@ def run_beautify_anm_data(in_file):
param_name = match.group(1)
else:
if words[0] == '};':
- build_anm_struct(byte_collection, anm_type, param_name)
+ if anm_type == PARAM_TYPE:
+ handle_npc_param(byte_collection, param_name, type)
+ else:
+ build_anm_struct(byte_collection, anm_type)
in_byte_array = False
byte_collection.clear()
else:
@@ -272,15 +402,4 @@ def run_beautify_anm_data(in_file):
if __name__ == "__main__":
- if len(sys.argv) < 2:
- print("Usage: python3 beautify_anm_data.py <filename>")
- sys.exit(1)
-
- in_file = sys.argv[1]
-
- # Check if the file exists
- if not os.path.isfile(in_file):
- print(f"Error: File '{in_file}' not found.")
- sys.exit(1)
-
- run_beautify_anm_data(in_file)
+ fire.Fire(run_beautify_anm_data)