mirror of
https://github.com/ACreTeam/ac-decomp
synced 2026-05-23 06:34:18 -04:00
966 lines
31 KiB
Python
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()
|