Files
ac-decomp/tools/converters/assetdis.py
T
2025-08-01 17:33:13 -04:00

966 lines
31 KiB
Python

import pathlib
from argparse import ArgumentParser
from gfxdis import convert_binary_to_gfx
from dataclasses import dataclass
import struct
ASSET_ALIGN = "ATTRIBUTE_ALIGN(32)"
NO_ALIGN = ""
def dict_enum_from_list(l: list): return {i: x for i, x, in enumerate(l)}
EFFECT_TYPES = dict_enum_from_list([
"eEC_EFFECT_SHOCK",
"eEC_EFFECT_DUST",
"eEC_EFFECT_MUKA",
"eEC_EFFECT_WARAU",
"eEC_EFFECT_HA",
"eEC_EFFECT_SIBUKI",
"eEC_EFFECT_GIMONHU",
"eEC_EFFECT_KANTANHU",
"eEC_EFFECT_ASE",
"eEC_EFFECT_ASE_CH",
"eEC_EFFECT_KANGAERU",
"eEC_EFFECT_OTIKOMI",
"eEC_EFFECT_BURUBURU",
"eEC_EFFECT_SIAWASE_HIKARI",
"eEC_EFFECT_SIAWASE_HANA",
"eEC_EFFECT_SIAWASE_HANA_CH",
"eEC_EFFECT_TAKURAMI",
"eEC_EFFECT_TAKURAMI_KIRA",
"eEC_EFFECT_KAZE",
"eEC_EFFECT_KAZE_HAPPA",
"eEC_EFFECT_LOVELOVE",
"eEC_EFFECT_LOVELOVE_HEART",
"eEC_EFFECT_PUN",
"eEC_EFFECT_PUN_YUGE",
"eEC_EFFECT_PUN_SEKIMEN",
"eEC_EFFECT_NAKU",
"eEC_EFFECT_NAMIDA",
"eEC_EFFECT_HIRAMEKI_DEN",
"eEC_EFFECT_HIRAMEKI_HIKARI",
"eEC_EFFECT_SITUREN",
"eEC_EFFECT_ASE2",
"eEC_EFFECT_DASH_ASIMOTO",
"eEC_EFFECT_KONPU",
"eEC_EFFECT_LOVELOVE2",
"eEC_EFFECT_KPUN",
"eEC_EFFECT_KISHA_KEMURI",
"eEC_EFFECT_NEBOKE",
"eEC_EFFECT_NEBOKE_AWA",
"eEC_EFFECT_HANABI_YANAGI",
"eEC_EFFECT_HANABI_BOTAN1",
"eEC_EFFECT_HANABI_BOTAN2",
"eEC_EFFECT_HANABI_HOSHI",
"eEC_EFFECT_HANABI_SET",
"eEC_EFFECT_HANABI_SWITCH",
"eEC_EFFECT_FOOTPRINT",
"eEC_EFFECT_TURN_FOOTPRINT",
"eEC_EFFECT_BUBU",
"eEC_EFFECT_TURN_ASIMOTO",
"eEC_EFFECT_STRING",
"eEC_EFFECT_YUKIHANE",
"eEC_EFFECT_ANAHIKARI",
"eEC_EFFECT_BUSH_HAPPA",
"eEC_EFFECT_BUSH_YUKI",
"eEC_EFFECT_TUMBLE",
"eEC_EFFECT_TUMBLE_BODYPRINT",
"eEC_EFFECT_SLIP",
"eEC_EFFECT_TUMBLE_DUST",
"eEC_EFFECT_SWING_NET",
"eEC_EFFECT_AMI_MIZU",
"eEC_EFFECT_MIZUTAMA",
"eEC_EFFECT_YUKIDAMA",
"eEC_EFFECT_KIKUZU",
"eEC_EFFECT_SWING_AXE",
"eEC_EFFECT_STEAM",
"eEC_EFFECT_NIGHT15_MOON",
"eEC_EFFECT_NIGHT13_MOON",
"eEC_EFFECT_NEBOKE_AKUBI",
"eEC_EFFECT_SLIP_FOOTPRINT",
"eEC_EFFECT_WALK_ASIMOTO",
"eEC_EFFECT_TURI_HAMON",
"eEC_EFFECT_TURI_MIZU",
"eEC_EFFECT_TURI_HANE0",
"eEC_EFFECT_TURI_HANE1",
"eEC_EFFECT_TURI_SUITEKI",
"eEC_EFFECT_ONGEN",
"eEC_EFFECT_IMPACT_STAR",
"eEC_EFFECT_SANDSPLASH",
"eEC_EFFECT_YOUNG_TREE",
"eEC_EFFECT_SWING_ROD",
"eEC_EFFECT_DOYON",
"eEC_EFFECT_KAGU_HAPPA",
"eEC_EFFECT_HANABI_DUMMY",
"eEC_EFFECT_DIG_HOLE",
"eEC_EFFECT_DIG_SCOOP",
"eEC_EFFECT_DIG_MUD",
"eEC_EFFECT_KIGAE",
"eEC_EFFECT_KIGAE_LIGHT",
"eEC_EFFECT_TAPE",
"eEC_EFFECT_CLACKER",
"eEC_EFFECT_KAMIFUBUKI",
"eEC_EFFECT_CAR_BLIGHT",
"eEC_EFFECT_TAMAIRE",
"eEC_EFFECT_CAR_LIGHT",
"eEC_EFFECT_RESET_HOLE",
"eEC_EFFECT_GOKI",
"eEC_EFFECT_HANABIRA",
"eEC_EFFECT_WAIT_ASIMOTO",
"eEC_EFFECT_OTOSIANA",
"eEC_EFFECT_IKIGIRE",
"eEC_EFFECT_YUKIDARUMA",
"eEC_EFFECT_KYOUSOU_ONPU",
"eEC_EFFECT_DOUZOU_LIGHT",
"eEC_EFFECT_HALLOWEEN_SMOKE",
"eEC_EFFECT_HALLOWEEN",
"eEC_EFFECT_MOTIYUGE",
"eEC_EFFECT_TABERU",
"eEC_EFFECT_KASAMIZU",
"eEC_EFFECT_KASAMIZUTAMA",
"eEC_EFFECT_HANATIRI",
"eEC_EFFECT_URANAI",
"eEC_EFFECT_SUISOU_AWA",
"eEC_EFFECT_BREAK_AXE",
"eEC_EFFECT_YAJIRUSHI",
"eEC_EFFECT_SOBA_YUGE",
"eEC_EFFECT_FURO_YUGE",
"eEC_EFFECT_SHOOTING",
"eEC_EFFECT_SHOOTING_SET",
"eEC_EFFECT_SHOOTING_KIRA",
"eEC_EFFECT_COIN",
"eEC_EFFECT_TENT_LAMP",
"eEC_EFFECT_FLASH",
"eEC_EFFECT_FLASHC",
"eEC_EFFECT_KILLER",
"eEC_EFFECT_MAKE_HEM",
"eEC_EFFECT_MAKE_HEM_KIRA",
"eEC_EFFECT_MAKE_HEM_LIGHT",
"eEC_EFFECT_NUM"
])
FRAMECONTROL_MODE = dict_enum_from_list([
"cKF_FRAMECONTROL_STOP",
"cKF_FRAMECONTROL_REPEAT",
])
MOUTH_TYPES = dict_enum_from_list([
"aNPC_MOUTH_TEX0",
"aNPC_MOUTH_TEX1",
"aNPC_MOUTH_TEX2",
"aNPC_MOUTH_TEX3",
"aNPC_MOUTH_TEX4",
"aNPC_MOUTH_TEX5",
"aNPC_MOUTH_TEX_NUM"
])
EYE_TYPES = dict_enum_from_list([
"aNPC_EYE_TEX0",
"aNPC_EYE_TEX1",
"aNPC_EYE_TEX2",
"aNPC_EYE_TEX3",
"aNPC_EYE_TEX4",
"aNPC_EYE_TEX5",
"aNPC_EYE_TEX6",
"aNPC_EYE_TEX7",
"aNPC_EYE_TEX_NUM",
])
FEEL_TYPES = dict_enum_from_list([
"mNpc_FEEL_NORMAL",
"mNpc_FEEL_HAPPY",
"mNpc_FEEL_ANGRY",
"mNpc_FEEL_SAD",
"mNpc_FEEL_SLEEPY",
"mNpc_FEEL_PITFALL",
"mNpc_FEEL_6",
"mNpc_FEEL_UZAI_0",
"mNpc_FEEL_UZAI_1",
"mNpc_FEEL_ALL_NUM"
])
@dataclass
class struct_ref:
symbol_name: str
symbol_type: str
@dataclass
class struct_parse_result:
formatted_str: str
referenced_objects: list[struct_ref]
c_type: str = None
def anim_type_conv(anim_type: int):
return {
0: "EVW_ANIME_TYPE_SCROLL1",
1: "EVW_ANIME_TYPE_SCROLL2",
2: "EVW_ANIME_TYPE_COLREG_MANUAL",
3: "EVW_ANIME_TYPE_COLREG_LINEAR",
4: "EVW_ANIME_TYPE_COLREG_NONLINEAR",
5: "EVW_ANIME_TYPE_TEXANIME",
}.get(anim_type, str(anim_type))
def parse_bin_formatted(buff: bytes, in_format: list[tuple[str, str]], _symbols: dict[int, str], val_conv: callable = None, type_conv: callable = None, fmt_conv: callable = None):
struct_format = ">"
symbols_offsets = {}
symbol_order = []
reloc_symbols = []
for format_pair in in_format:
if format_pair == None:
struct_format += "x"
continue
this_offset = struct.calcsize(struct_format)
struct_type, name = format_pair
if struct_type == "p":
reloc_symbols.append(name)
struct_type = "xxxx"
struct_format += struct_type
symbols_offsets[name] = this_offset
symbol_order.append(name)
BUFF_SIZE = struct.calcsize(struct_format)
assert ((len(buff) % BUFF_SIZE) == 0)
found_syms = []
out_c_data = []
i = 0
for i in range(0, len(buff), BUFF_SIZE):
b = buff[i:i+BUFF_SIZE]
if len(b) < BUFF_SIZE:
break
extracted_data: list[int] = list(struct.unpack(struct_format, b))
collapsed_data = {}
for this_symbol_name in symbol_order:
if this_symbol_name in reloc_symbols:
ref_name = _symbols.get(
i + symbols_offsets[this_symbol_name], "NULL")
collapsed_data[this_symbol_name] = ref_name
else:
collapsed_data[this_symbol_name] = extracted_data.pop(0)
new_converted_vals = {}
if val_conv:
for name, val in collapsed_data.items():
new_val = val_conv(name, val, dict(collapsed_data))
if new_val != None:
new_converted_vals[name] = new_val
collapsed_data.update(new_converted_vals)
if type_conv:
for name, value in collapsed_data.items():
new_type = type_conv(name, dict(collapsed_data))
if new_type != None:
found_syms.append(struct_ref(value, new_type))
new_converted_vals = {}
if fmt_conv:
for name, val in collapsed_data.items():
new_val = fmt_conv(name, val, dict(collapsed_data))
if new_val != None:
new_converted_vals[name] = new_val
collapsed_data.update(new_converted_vals)
new_converted_vals = {}
vals = collapsed_data.values()
out_c_data.append(f"\t{{ {', '.join([str(x) for x in vals])} }}")
return struct_parse_result(",\n".join(out_c_data), found_syms)
def parse_evw_anime_data(buff: bytes, symbols) -> struct_parse_result:
this_format = [("b", "segment"), None, ("h", "anim_type"),
("p", "this_symbol_name")]
def vcf(name, value, all_values):
if name == "anim_type":
return anim_type_conv(value)
def tcf(name, all_values):
if name == "this_symbol_name":
return all_values["anim_type"]
return parse_bin_formatted(buff, this_format, symbols, vcf, tcf)
def parse_evw_anime_col_prim(buff: bytes, symbols):
this_format = [("B", "r"), ("B", "g"), ("B", "b"), ("B", "a"), ("B", "l")]
return parse_bin_formatted(buff, this_format, symbols)
def parse_evw_anime_col_env(buff: bytes, symbols: list[str]):
this_format = [("B", "r"), ("B", "g"), ("B", "b"), ("B", "a")]
return parse_bin_formatted(buff, this_format, symbols)
def parse_evw_anime_colreg(buff: bytes, symbols: list[str]):
this_format = [("H", "prim_colors"), ("H", "key_count"),
("p", "prim_colors"), ("p", "env_colors"), ("p", "keyframes")]
def tcf(name, symbols):
return {
"prim_colors": "EVW_ANIME_COL_PRIM",
"env_colors": "EVW_ANIME_COL_ENV",
"keyframes": "u16",
}.get(name, None)
return parse_bin_formatted(buff, this_format, symbols, type_conv=tcf)
def parse_evw_anime_scroll(buff: bytes, symbols: list[str]):
this_format = [("b", "x"), ("b", "y"), ("B", "width"), ("B", "height")]
res = parse_bin_formatted(buff, this_format, symbols)
res.c_type = "EVW_ANIME_SCROLL"
return res
def parse_evw_texanime(buff: bytes, symbols: list[str]):
this_format = [("H", "frame_count"), ("H", "key_count"), ("p",
"texture_tbl"), ("p", "animation_pattern"), ("p", "keyframes")]
def tcf(name, symbols):
return {
"texture_tbl": "VOID*_LIST",
"animation_pattern": "u8",
"keyframes": "u16",
}.get(name, None)
def fcf(name, value, symbols):
if name == "frame_count":
return F"ARRAY_COUNT({symbols['animation_pattern']})"
res = parse_bin_formatted(
buff, this_format, symbols, type_conv=tcf, fmt_conv=fcf)
res.c_type = "EVW_ANIME_TEXANIME"
return res
def parse_voidstarlist(buff: bytes, symbols: dict[int:str]):
return struct_parse_result(", ".join(symbols.values()), [], "void*")
def parse_aNPC_se_data_table_c(buff: bytes, symbols: dict[int:str]):
this_format = [("p", "lfoot_data"), ("p", "rfoot_data"),
("l", "other_se_type"), ("p", "other_se_data")]
def tcf(name, symbols):
return {
"lfoot_data": "aNPC_se_data_c",
"rfoot_data": "aNPC_se_data_c",
"other_se_data": "aNPC_other_se_data_c",
}.get(name, None)
def vcf(name, value, symbols):
if name == "other_se_type":
return {
0: "aNPC_OTHER_SE_TYPE_NONE",
1: "aNPC_OTHER_SE_TYPE1",
}.get(value, str(value))
return parse_bin_formatted(buff, this_format, symbols, vcf, tcf)
def parse_aNPC_se_data_c(buff: bytes, symbols: dict[int:str]):
this_format = [("l", "num_check_frames"), ("p", "check_frame_tbl")]
def tcf(name, symbols):
return {
"check_frame_tbl": "int"
}.get(name, None)
def fcf(name, value, symbols):
if name == "num_check_frames":
return f"ARRAY_COUNT({symbols['check_frame_tbl']})"
# def vcf(name, value, symbols):
# if name == "other_se_type":
# return {
# 0: "aNPC_OTHER_SE_TYPE_NONE",
# 1: "aNPC_OTHER_SE_TYPE1",
# }.get(value, str(value))
return parse_bin_formatted(buff, this_format, symbols, type_conv=tcf, fmt_conv=fcf)
def parse_aNPC_feel_effect_c(buff: bytes, symbols: dict[int:str]):
this_format = [("h", "feel_type"),
("B", "max"), ("B", "set_num"), ("p", "set_p")]
def tcf(name, symbols):
return {
"set_p": "u32"
}.get(name, None)
def fcf(name, value, symbols):
if name == "feel_type":
return FEEL_TYPES.get(value, str(value))
if name == "set_num":
return f"ARRAY_COUNT({symbols['set_p']})"
return parse_bin_formatted(buff, this_format, symbols, type_conv=tcf, fmt_conv=fcf)
def parse_aNPC_other_se_data_c(buff: bytes, symbols: dict[int:str]):
this_format = [("l", "num_check_frames"),
("p", "check_frame_tbl"), ("H", "se_no"), None, None]
def tcf(name, symbols):
return {
"check_frame_tbl": "int"
}.get(name, None)
def fcf(name, value, symbols):
if name == "num_check_frames":
return f"{{ ARRAY_COUNT({symbols['check_frame_tbl']})"
if name == "check_frame_tbl":
return f"{value}}}"
return parse_bin_formatted(buff, this_format, symbols, type_conv=tcf, fmt_conv=fcf)
def parse_cKF_Skeleton_R_c(buff: bytes, symbols: list[str]):
this_format = [("B", "num_joints"), ("B", "num_shown_joints"),
None, None, ("p", "joint_table")]
def tcf(name, symbols):
return {
"joint_table": "cKF_Joint_R_c",
}.get(name, None)
def vcf(name, value, symbols):
if name == "num_joints":
return f"ARRAY_COUNT({symbols["joint_table"]})"
return parse_bin_formatted(buff, this_format, symbols, vcf, tcf)
def parse_cKF_Animation_R_c(buff: bytes, symbols: list[str]):
this_format = [("p", "flag_table"), ("p", "data_table"),
("p", "key_table"), ("p", "fixed_table"), ("h", "pad"), ("h", "frames")]
def tcf(name, symbols):
return {
"flag_table": "u8",
"data_table": "s16",
"key_table": "s16",
"fixed_table": "s16",
}.get(name, None)
return parse_bin_formatted(buff, this_format, symbols, type_conv=tcf)
def parse_aNPC_Animation_c(buff: bytes, symbols: list[str]):
this_format = [
("p", "flag_table"),
("p", "data_table"),
("p", "key_table"),
("p", "fixed_table"),
("h", "pad"),
("h", "frames"),
("f", "start_time"),
("f", "end_time"),
("l", "mode"),
("f", "morph_counter"),
("p", "eye_seq_p"),
("h", "eye_seq_type"),
("h", "eye_seq_stop_frame"),
("p", "mouth_seq_p"),
("h", "mouth_seq_type"),
("h", "mouth_seq_stop_frame"),
("h", "feel_effect_set_frame"),
("h", "feel_effect_type"),
("p", "feel_effect"),
("p", "se_data_table")]
def tcf(name, symbols):
return {
"flag_table": "u8",
"data_table": "s16",
"key_table": "s16",
"fixed_table": "s16",
"eye_seq_p": "u8",
"mouth_seq_p": "u8",
"feel_effect": "aNPC_feel_effect_c",
"se_data_table": "aNPC_se_data_table_c",
}.get(name, None)
def fcf(name, value, symbols):
if name == "flag_table":
return f"{{ {value}"
if name == "frames":
return f"{value} }}"
if name == "se_data_table":
return f"{value},"
if name in ["start_time", "end_time", "morph_counter"]:
return f"{value}f"
def vcf(name, value, symbols):
if name == "feel_effect_type":
return EFFECT_TYPES.get(value, str(value))
if name == "mode":
return FRAMECONTROL_MODE.get(value, str(value))
if name == "mouth_seq_type":
return MOUTH_TYPES.get(value, str(value))
if name == "eye_seq_type":
return EYE_TYPES.get(value, str(value))
return parse_bin_formatted(buff, this_format, symbols, vcf, tcf, fcf)
def parse_cKF_Joint_R_c(buff: bytes, symbols: list[str]):
this_format = [("p", "model"), ("B", "child"),
("B", "flags"), ("H", "x"), ("H", "y"), ("H", "z")]
def tcf(name, symbols):
return {
"joint_table": "Gfx",
}.get(name, None)
def fcf(name, value, symbols):
if name == "x":
return f"{{ {value}"
if name == "z":
return f"{value} }}"
def vcf(name, value, symbols):
if name == "flags":
return {
0: "cKF_JOINT_FLAG_DISP_OPA",
1: "cKF_JOINT_FLAG_DISP_XLU"
}.get(value, None)
return parse_bin_formatted(buff, this_format, symbols, vcf, tcf, fcf)
def parse_u16(buff: bytes, symbols: list[str]):
vals = struct.unpack(">" + "H" * (len(buff) // 2), buff)
out_str = ",\n".join(str(x) for x in vals)
return struct_parse_result(out_str, [], "u16")
def parse_int(buff: bytes, symbols: list[str]):
vals = struct.unpack(">" + "l" * (len(buff) // 4), buff)
out_str = ",\n".join(str(x) for x in vals)
return struct_parse_result(out_str, [])
def parse_u32(buff: bytes, symbols: list[str]):
vals = struct.unpack(">" + "L" * (len(buff) // 4), buff)
out_str = ",\n".join(str(x) for x in vals)
return struct_parse_result(out_str, [])
def parse_s16(buff: bytes, symbols: list[str]):
vals = struct.unpack(">" + "h" * (len(buff) // 2), buff)
out_str = ",\n".join(str(x) for x in vals)
return struct_parse_result(out_str, [], "s16")
def parse_u8(buff: bytes, symbols: list[str]):
vals = struct.unpack(">" + "B" * len(buff), buff)
out_str = ",\n".join(str(x) for x in vals)
return struct_parse_result(out_str, [], "u8")
def lookup_rel_vtx_offset(lines: list[str], name: str):
symbol_name, offset_label = name.split(", ")
offset = 0
inside = False
for line in lines:
if line.startswith(f".obj {symbol_name},"):
inside = True
if not inside:
continue
if line.startswith(offset_label):
return offset
if ".4byte" in line:
offset += 4
elif ".2byte" in line:
offset += 2
elif ".byte" in line:
offset += line.count(",") + 1
else:
assert (not line.endswith(f".endobj {symbol_name}"))
assert (False)
def lookup_bins_and_symbols(lines: list[str], name: str):
out_bin = bytearray()
out_symbols = []
begin_ind = 0
end_ind = 0
for i, line in enumerate(lines):
if line.startswith(f".obj {name},"):
begin_ind = i
if line == f".endobj {name}":
end_ind = i
data_lines = lines[begin_ind+1:end_ind]
for line in data_lines:
if ".4byte" in line:
data = line.split(".4byte ")[1]
try:
out_bin.extend(int(data, 16).to_bytes(4, 'big'))
except Exception as e:
out_bin.extend(b'\0\0\0\0')
out_symbols.append(data)
elif ".rel" in line:
out_bin.extend(b'\0\0\0\0')
rel_plus_offset = line.split(".rel ")[1]
vtx_offset = lookup_rel_vtx_offset(lines, rel_plus_offset)
out_symbols.append(
f"&{rel_plus_offset.split(", ")[0]}[{vtx_offset//0x10}]")
elif ".byte" in line:
data = line.split(".byte ")[1]
out_bin.extend(int(data, 16).to_bytes(1, 'big'))
return out_bin, out_symbols
def is_static(lines: list[str], name: str):
lookfor = f".obj {name},"
for line in lines:
if line.startswith(lookfor):
if line.startswith(lookfor + " local"):
return True
break
return False
def lookup_static(lines: list[str], name: str):
if is_static(lines, name):
return "static "
return ""
def lookup_align(lines: list[str], name: str):
out = ""
align_32 = False
inside_obj = False
for i, line in enumerate(lines):
if line == ".balign 32":
align_32 = True
if line.startswith(f".obj {name},"):
if align_32:
return ASSET_ALIGN
else:
return NO_ALIGN
elif line.startswith(".obj"):
inside_obj = True
elif line.startswith(f".endobj"):
inside_obj = False
align_32 = False
elif not inside_obj and line.startswith("\t.4byte"):
align_32 = True
return out
def lookup_bins_and_symbols2(lines: list[str], name: str):
out_bin = bytearray()
out_symbols = {}
begin_ind = 0
end_ind = 0
for i, line in enumerate(lines):
if line.startswith(f".obj {name},"):
begin_ind = i
if line == f".endobj {name}":
end_ind = i
break
data_lines = lines[begin_ind+1:end_ind]
offset_count = 0
for line in data_lines:
if ".4byte" in line:
data = line.split(".4byte ")[1]
try:
out_bin.extend(int(data, 16).to_bytes(4, 'big'))
except Exception as e:
out_bin.extend(b'\0\0\0\0')
out_symbols[offset_count] = data
offset_count += 4
elif ".2byte" in line:
data = line.split(".2byte ")[1]
out_bin.extend(int(data, 16).to_bytes(2, 'big'))
offset_count += 2
elif ".byte" in line:
data = line.split(".byte ")[1]
if ", " in data:
for d in data.split(", "):
out_bin.extend(int(d, 16).to_bytes(1, 'big'))
offset_count += 1
else:
out_bin.extend(int(data, 16).to_bytes(1, 'big'))
offset_count += 1
elif ".rel" in line:
out_bin.extend(b'\0\0\0\0')
data = f"&{line.split(".rel ")[1].split(",")[0]}[xxx]"
out_symbols[offset_count] = data
offset_count += 4
else:
assert (False), line
return out_bin, out_symbols
def lookup_address(lines: list[str], name: str):
for i, line in enumerate(lines):
if line.startswith(f".obj {name},"):
addr = lines[i-1].split(" | ")[1]
return addr
assert (False)
def mark_matching(config_change_path):
config_py = "configure.py"
with open(config_py, "r") as f:
config_txt = f.read()
config_txt = config_txt.replace(f"Object(NonMatching, \"{config_change_path}\"),",
f"Object(Matching, \"{config_change_path}\"),")
with open(config_py, "w") as f:
f.write(config_txt)
def update_extracted_assets(lines: list[str], out: str, src_file_name: str):
config_yaml = "config/GAFE01_00/config.yml"
with open(config_yaml, "a") as f:
for i, line in enumerate(out.split("\n")):
if line.startswith("#include \"assets"):
prev_line = out.split("\n")[i-1]
parts = prev_line.split("=")[0].split(" ")
type = parts[0]
name = parts[1]
im_static = False
if type == "static":
im_static = True
type = parts[1]
name = parts[2]
name = name[:-2] # cut off the array []
if im_static:
out_config = f"""
- symbol: {name}!.data:{lookup_address(lines, name)}
binary: assets/{src_file_name}/{name}.bin
header: assets/{src_file_name}/{name}.inc
"""
else:
out_config = f"""
- symbol: {name}
binary: assets/{name}.bin
header: assets/{name}.inc
"""
if type == "u8":
out_config += " header_type: raw\n"
elif type == "Vtx":
out_config += " header_type: none\n custom_type: vtx\n"
elif type == "u16":
out_config += " header_type: none\n custom_type: pal16\n"
else:
assert (False)
f.write(out_config)
def get_externs(lines: list[str]):
found_objs: set[str] = set()
found_refs: set[str] = set()
for line in lines:
if line.startswith(".obj "):
found_objs.add(line.split(" ")[1][:-1])
elif line.startswith("\t.4byte") and not line.startswith("\t.4byte 0x"):
name = line.split(" ")[1]
if name not in found_objs:
found_refs.add(name)
elif line.startswith("\t.rel"):
name = line.split(" ")[1][:-1]
found_refs.add(name)
if name not in found_objs:
found_refs.add(name)
externs = []
for ex in found_refs:
type = "u8"
if ex.endswith("_pal"):
type = "u16"
elif ex.endswith("_model"):
type = "Gfx"
elif ex.endswith("_v"):
type = "Vtx"
externs.append(f"extern {type} {ex}[];")
return "\n".join(externs) + "\n"
def convert_source_to_gfx_c_source(src_file, dest_path, known_types: dict[str, str], should_extract_symbols=False, should_link=False):
with open(src_file) as f:
lines = f.read().split("\n")
src_file_name = pathlib.Path(src_file).stem
config_change_path = str(
pathlib.PurePosixPath(dest_path).relative_to("src"))
includes = ["libforest/gbi_extensions.h",
"PR/gbi.h", "evw_anime.h", "c_keyframe.h", "ac_npc.h", "ef_effect_control.h"]
header = "\n".join([f'#include "{x}"' for x in includes]) + "\n"
# found_objs = []
found_types = []
all_objs = []
for line in lines:
if line.startswith(".obj"):
this_obj = line.split(" ")[1].strip(",")
all_objs.append(this_obj)
if this_obj in known_types:
found_types.append((this_obj, known_types[this_obj]))
elif this_obj.endswith("_v") or \
this_obj.endswith("_v2"):
found_types.append((this_obj, "Vtx"))
elif "_model" in this_obj or \
this_obj.endswith("_setmode") or \
this_obj.endswith("_modeset") or \
this_obj.endswith("_mode") or \
this_obj.endswith("_gfx") or \
this_obj.endswith("_vtx") or \
this_obj.endswith("_gfx2"):
found_types.append((this_obj, "Gfx"))
elif "_tex_index" in this_obj:
found_types.append((this_obj, "u8"))
elif this_obj.endswith("evw_anime"):
found_types.append((this_obj, "EVW_ANIME_DATA"))
elif this_obj.endswith("_pal"):
found_types.append((this_obj, "PAL"))
elif this_obj.endswith("_tex"):
found_types.append((this_obj, "TEX"))
elif this_obj.startswith("cKF_bs_r"):
found_types.append((this_obj, "cKF_Skeleton_R_c"))
elif this_obj.startswith("cKF_ba_r_npc"):
found_types.append((this_obj, "aNPC_Animation_c"))
elif this_obj.startswith("cKF_ba_r"):
found_types.append((this_obj, "cKF_Animation_R_c"))
# we now have a list of all objects, and we have a partial mapping of what type they could have
# what we want to do is go through all the objects we know, and get the out-data
# there are some objs that will add to this list, we can queue them onto the list
converted_types = {}
lookup_table = {
"EVW_ANIME_DATA": parse_evw_anime_data,
"EVW_ANIME_TYPE_SCROLL1": parse_evw_anime_scroll,
"EVW_ANIME_TYPE_SCROLL2": parse_evw_anime_scroll,
"EVW_ANIME_TYPE_COLREG_MANUAL": parse_evw_anime_colreg,
"EVW_ANIME_TYPE_COLREG_LINEAR": parse_evw_anime_colreg,
"EVW_ANIME_TYPE_COLREG_NONLINEAR": parse_evw_anime_colreg,
"EVW_ANIME_TYPE_TEXANIME": parse_evw_texanime,
"EVW_ANIME_COL_PRIM": parse_evw_anime_col_prim,
"EVW_ANIME_COL_ENV": parse_evw_anime_col_env,
"cKF_Skeleton_R_c": parse_cKF_Skeleton_R_c,
"cKF_Joint_R_c": parse_cKF_Joint_R_c,
"cKF_Animation_R_c": parse_cKF_Animation_R_c,
"u8": parse_u8,
"u16": parse_u16,
"s16": parse_s16,
"int": parse_int,
"u32": parse_u32,
"aNPC_Animation_c": parse_aNPC_Animation_c,
"VOID*_LIST": parse_voidstarlist,
"aNPC_se_data_table_c": parse_aNPC_se_data_table_c,
"aNPC_se_data_c": parse_aNPC_se_data_c,
"aNPC_other_se_data_c": parse_aNPC_other_se_data_c,
"aNPC_feel_effect_c": parse_aNPC_feel_effect_c,
}
# we now have a list of objects+type
while len(found_types) > 0:
obj_name, type = found_types.pop()
default_data = f'#include "assets/{obj_name}.inc"'
if is_static(lines, obj_name):
default_data = f'#include "assets/{src_file_name}/{obj_name}.inc"'
if obj_name in converted_types:
continue
if type == "Vtx":
converted_types[obj_name] = (type, default_data)
elif type == "Gfx":
bins, symbols = lookup_bins_and_symbols(lines, obj_name)
data = convert_binary_to_gfx(bins, symbols)
converted_types[obj_name] = (type, data + ",")
elif type == "PAL":
converted_types[obj_name] = ("u16", default_data)
elif type == "TEX":
converted_types[obj_name] = ("u8", default_data)
elif type in lookup_table:
res: struct_parse_result = lookup_table[type](
*lookup_bins_and_symbols2(lines, obj_name))
if res.c_type != None:
type = res.c_type
data = res.formatted_str
converted_types[obj_name] = (type, data)
if len(res.referenced_objects) > 0:
found_types = [(ref.symbol_name, ref.symbol_type)
for ref in res.referenced_objects] + found_types
out = header + "\n"
# find extern referenced files
out += get_externs(lines)
for obj in all_objs:
default_include = f'#include "assets/{obj}.inc"'
if is_static(lines, obj):
default_include = f'#include "assets/{src_file_name}/{obj}.inc"'
this_type, out_data = converted_types.get(
obj, ("u8", default_include))
static_typing = lookup_static(lines, obj)
align = lookup_align(lines, obj)
if this_type in ["aNPC_Animation_c", "cKF_Animation_R_c", "cKF_Skeleton_R_c"]:
out += f"{static_typing}{this_type} {obj} {align}= \n{out_data}\n;\n\n"
else:
out += f"{static_typing}{this_type} {obj}[] {align}= {{\n{out_data}\n}};\n\n"
with open(dest_path, "w") as f:
f.write(out)
if should_link:
mark_matching(config_change_path)
if should_extract_symbols:
update_extracted_assets(lines, out, src_file_name)
def main():
parser = ArgumentParser(
description="Converts a binary file into gfx calls"
)
parser.add_argument("src_path", type=str, help="Binary source file path")
parser.add_argument('-t', '--types', action='append', required=False)
parser.add_argument('-u', '--update_splits',
action='store_true', default=False)
parser.add_argument('-l', '--link', action='store_true', default=False)
args = parser.parse_args()
presented_path = args.src_path
src_path = str(pathlib.PurePosixPath(
"./build/GAFE01_00/foresta/asm/") / (presented_path[:-2] + ".s"))
dest_path = str(pathlib.PurePosixPath("src") / presented_path)
print(dest_path)
print(f"update splits: {args.update_splits}")
print(f"update link: {args.link}")
known_types = {}
if args.types != None:
known_types = {x.split(",")[1]: x.split(",")[0] for x in args.types}
# print(known_types)
convert_source_to_gfx_c_source(
src_path, dest_path, known_types, args.update_splits, args.link)
if __name__ == "__main__":
main()