#!/usr/bin/env python3 import sys from pathlib import Path from struct import * import find_sym STRUCTS = {} STRUCT_TYPEDEFS = {} DEFINES = {} BASE_TYPES = { "s8": 1, "u8": 1, "s16": 2, "u16": 2, "s32": 4, "u32": 4, "s64": 8, "u64": 8, "f32": 4, "f64": 8, "char": 1, "short": 2, "short int": 2, "int": 4, "long": 4, "long int": 8, "long long": 8, "long long int": 8, "float": 4, "double": 8, "ptr": 4, "size_t": 4, "uintptr_t": 4, } SIGNED = { "s8", "s16", "s32", "s64", "short", "int", "f32", "f64", "float", "double", } FLOAT = { "f32", "f64", "float", "double", } def calc_struct_sizes(): def round_up(offset, rounding): return offset + (-offset % rounding) def calc_size(name, data): max_size = 0 max_rounding = 0 testMe = False #if name == "arg1_func_80010CA8": # testMe = True #print(data) i = 0 offset = 0 while i < len(data): member = data[i] if testMe: print(member) size = 0 if member["type"] in STRUCTS: if "size" in STRUCTS[member["type"]]: rounding = STRUCTS[member["type"]]["round"] #size = round_up(size, rounding) size += STRUCTS[member["type"]]["size"] else: calc_size(member["type"], STRUCTS[member["type"]]["data"]) rounding = STRUCTS[member["type"]]["round"] #size = round_up(size, rounding) size += STRUCTS[member["type"]]["size"] elif "*" in member["type"]: rounding = 4 #size = round_up(size, rounding) size += 4 elif member["type"] in BASE_TYPES: rounding = BASE_TYPES[member["type"]] #size = round_up(size, rounding) size += BASE_TYPES[member["type"]] elif member["type"] in DEFINES: print(f"DEFINE!") print(member["type"]) exit() else: print(f"What type? {member['type']}") exit() max_rounding = max(max_rounding, rounding) num_entries = 1 for entry in member["counts"]: num_entries *= entry size *= num_entries offset = round_up(offset, rounding) if testMe: print(f"size 0x{size:X} rounding 0x{rounding:X} offset 0x{offset:X}") STRUCTS[name]["data"][i]["size"] = size STRUCTS[name]["data"][i]["round"] = rounding STRUCTS[name]["data"][i]["offset"] = offset offset += size if STRUCTS[name]["union"]: max_size = max(max_size, size) else: max_size = offset i += 1 max_size = round_up(max_size, max_rounding) STRUCTS[name]["size"] = max_size STRUCTS[name]["round"] = max_rounding for struct_name, struct_data in STRUCTS.items(): #print(f"--------------") #print(struct_name) #print(struct_data) #print() calc_size(struct_name, struct_data["data"]) #print(f"####") #print(f"Type {struct_name} has size 0x{STRUCTS[struct_name]['size']:X} and rounding {STRUCTS[struct_name]['round']:X}") #print(STRUCTS[struct_name]["data"]) def parse_struct(filename, fd, i): testMe = False start_line = i indent = 0 while fd[start_line][indent] == " ": indent += 1 while len(fd[i]) <= indent or fd[i][indent] != "}": i += 1 end_line = i if testMe: print(start_line, end_line, indent, fd[start_line:end_line]) #exit() pre_struct_name = fd[start_line] pre_struct_name = pre_struct_name.replace("struct","") pre_struct_name = pre_struct_name.replace("union","") pre_struct_name = pre_struct_name.replace("typedef","") pre_struct_name = pre_struct_name.replace("{","") pre_struct_name = pre_struct_name.strip() #print() #print() #print(start_line, end_line, indent) #print(fd[start_line:end_line]) while "typedef" in fd[start_line] or fd[start_line][indent] == "{" or fd[start_line][indent:indent+6] == "struct" or fd[start_line][indent:indent+5] == "union": start_line += 1 struct_name = fd[end_line].strip().rsplit(";",1)[0].split("}",1)[1].strip() #if "LEOCmd" in struct_name: # testMe = True if testMe: print() print(struct_name) print(start_line, end_line, indent) print(fd[start_line:end_line]) #exit() if struct_name and struct_name in STRUCTS: return i, True, "", [] if pre_struct_name and pre_struct_name != struct_name: STRUCT_TYPEDEFS[pre_struct_name] = struct_name #print(struct_name) #print(f"cccc") #print(fd[start_line:end_line]) skipMe = False struct_data = [] j = start_line startedComment = False while j < end_line: #print(f"xxxxx", j, end_line) data_member = fd[j] j += 1 if not data_member.strip(): continue if testMe: print(f"bbbb {j}") print(f"data_member: \"{data_member}\"") if " : " in data_member: skipMe = True break; elif "#ifdef" in data_member or "#else" in data_member or "#endif" in data_member: continue elif (data_member.strip().startswith("union") or data_member.strip().startswith("struct")) and ";" not in data_member: #print(f"UNION! {data_member}") is_union = "union" in data_member start = j while "{" not in data_member: data_member += fd[j] j += 1 #while "{" not in fd[j]: # j += 1 if testMe: print(f"RECURSE {str(filename)} j {j} -- {fd[start-1]}") j, skipMe, name, data = parse_struct("", fd, start - 1) j += 1 if skipMe: continue # invent a name for this anonymous struct if not name: name = f"{struct_name}_{j}" STRUCTS[name] = {} STRUCTS[name]["data"] = data STRUCTS[name]["union"] = is_union struct_data.append({"name":name, "type":name, "counts":[], "size":0, "round":0, "offset":0}) if testMe: print(f"DONE RECURSE {str(filename)} j {j}") print(f"name \"{name}\" -- data \"{STRUCTS[name]}\"") print(f"appending data {struct_data[-1]}") #exit() continue; elif ";" not in data_member and "/" in data_member: continue elif ";" not in data_member and startedComment: while ";" not in fd[j]: #print(f"yyyyy", j, end_line) j += 1 startedComment = False continue if ";" in data_member and "/" in data_member: startedComment = True data_members = [] if ";" not in data_member: data_members = [data_member] while ";" not in fd[j]: #print(f"zzzzzz", j, end_line) data_members.append(fd[j]) j += 1 data_members.append(fd[j]) j += 1 data_member = "".join(data_members) if testMe: print(f"final data member line \"{data_member}\"") #print(data_members) data_member = data_member.rsplit(";",1)[0] #print(data_member) if "*/" in data_member: data_member = data_member.split("*/",1)[1] type, name = data_member.rsplit(" ",1) if "{" in name or "}" in name: continue; if testMe: print(f"parsed type \"{type}\", name \"{name}\"") type = type.strip() if "," in type: names = type.split(" ",1)[1].split(",") type = type.split(" ",1)[0] x = 0 while x < len(names): names[x] = names[x].strip() x += 1 else: names = [] names.append(name) for name in names: if not name: continue name = name.strip() if "*" in name: ptrs = name.count("*") name = name.rsplit("*",1)[1] type += "*" * ptrs if "struct " in type: type = type.rsplit(" ",1)[1] type = type.replace("unsigned", "") type = type.replace("signed", "") counts = [] while "[" in name: count = name.split("[",1)[1].split("]",1)[0] name = name.split("[",1)[0] + name.split("]",1)[1] try: count = int(count, 0) except Exception: skipMe = True break; counts.append(count) if skipMe: break; type = type.strip() name = name.strip() if testMe: print(f"Appending new data member, name:\"{name}\", type:\"{type}\"") struct_data.append({"name":name, "type":type, "counts":counts, "size":0, "round":0, "offset":0}) #if testMe: # exit() return i, skipMe, struct_name, struct_data def parse_structs(): file_list = [] inc_path = Path(sys.argv[0]).absolute().parent / "../include/" src_path = Path(sys.argv[0]).absolute().parent / "../src/" lib_path = Path(sys.argv[0]).absolute().parent / "../lib/ultralib/include/" file_list += inc_path.glob("**/*.c") file_list += inc_path.glob("**/*.h") file_list += lib_path.glob("**/*.c") file_list += lib_path.glob("**/*.h") file_list += src_path.glob("**/*.c") file_list += src_path.glob("**/*.h") #print("\n".join(str(x) for x in file_list)) for file_name in file_list: fd = file_name.read_text() fd = fd.replace("\t", " ") fd = fd.splitlines() i = 0 while i < len(fd): if not fd[i].startswith("typedef") and not fd[i].startswith("#define"): i += 1 continue if fd[i].startswith("#define"): if "\\" in fd[i]: i += 1 continue line = fd[i].split() if len(line) >= 3: try: val = int(line[2], 0) except Exception: i += 1 continue name = line[1] DEFINES[name] = val i += 1 elif fd[i].startswith("typedef struct") and ";" not in fd[i]: # struct i, skipMe, name, data = parse_struct(file_name, fd, i) if not skipMe: STRUCTS[name] = {} STRUCTS[name]["data"] = data STRUCTS[name]["union"] = False #if "unk_D_83407B00" in name: # print(STRUCTS[name]) # exit() elif fd[i].startswith("typedef union"): i, skipMe, name, data = parse_struct(file_name, fd, i) if not skipMe: STRUCTS[name] = {} STRUCTS[name]["data"] = data STRUCTS[name]["union"] = True elif "(*" in fd[i]: # func ptr #print(fd[i]) func_ptr = fd[i].split("typedef ",1)[1].rsplit(";",1)[0] func_ptr = func_ptr.split("(*",1)[1].split(")",1)[0] STRUCTS[func_ptr] = {} STRUCTS[func_ptr]["data"] = [{"name":func_ptr, "type":"ptr", "counts":[], "size":4, "round":4, "offset":0}] STRUCTS[func_ptr]["size"] = 4; STRUCTS[func_ptr]["round"] = 4; STRUCTS[func_ptr]["union"] = False i += 1 elif "typedef" in fd[i] and ";" in fd[i]: if "va_list" in fd[i]: i += 1 continue #print(fd[i]) line = fd[i].replace("typedef ", "") line = line.replace("unsigned ", "") line = line.replace("signed ", "") line = line.replace("struct ", "") line = line.replace("union ", "") line = line.replace(";", "") #if "Bitmap" in line: # print(line) # exit() if len(line.split(" ",1)) == 1: i += 1 continue type, name = line.split(" ",1) type = type.strip() name = name.strip() if "Bitmap" in name or "Sprite" in name: STRUCT_TYPEDEFS[name] = type i += 1 continue skipMe = False counts = [] while "[" in name: count = name.split("[",1)[1].split("]",1)[0] if count in DEFINES: count = DEFINES[count] else: try: count = int(count, 0) except Exception: #print(f"Skipping {name}") skipMe = True break; name = name.split("[",1)[0] + name.split("]",1)[1] counts.append(count) if skipMe: i += 1 continue if "*" in name: ptr_count = name.count("*") type += "*" * ptr_count name = name.replace("*", "") name = name.strip() type = type.strip() #print(counts) #print(name, type) if type in BASE_TYPES and len(counts) == 0: BASE_TYPES[name] = BASE_TYPES[type] i += 1 continue if name not in STRUCTS: STRUCTS[name] = {} STRUCTS[name]["data"] = [{"name":name, "type":type, "counts":counts, "size":0, "round":0, "offset":0}] STRUCTS[name]["union"] = False i += 1 else: #print(fd[i]) i += 1 #exit() # For every struct, fix change pre-declared name to actual name: # e.g typedef struct mystruct_s {} mystruct_t; # mystruct_s -> mystruct_t for struct_name, struct_data in STRUCTS.items(): #print(struct_name, struct_data["data"]) for member in struct_data["data"]: ptr_count = 0 #print(member) pointerless_type = member["type"] if "*" in pointerless_type: ptr_count = pointerless_type.count("*") pointerless_type = pointerless_type.split("*",1)[0] if pointerless_type in STRUCT_TYPEDEFS: new_name = STRUCT_TYPEDEFS[pointerless_type] + ("*" * ptr_count) member["type"] = new_name def dump_data(offset, struct, counts): def output_struct(data, name, out, offset, indent): def round_up(offset, rounding): return offset + (-offset % rounding) def output_type(type, size, offset, out): is_signed = type in SIGNED is_float = type in FLOAT make_hex = "*" in type or type not in SIGNED match size: case 1: v = unpack_from(">b" if is_signed else ">B", data, offset)[0] if make_hex: out += f"0x{v:02X}" + ", " else: out += f"{v}, " case 2: v = unpack_from(">h" if is_signed else ">H", data, offset)[0] if make_hex: out += f"0x{v:04X}" + ", " else: out += f"{v}, " case 4: v = unpack_from(">f" if is_float else ">i" if is_signed else ">I", data, offset)[0] if make_hex: if v == 0: out += f"NULL" + ", " else: out += f"0x{v:08X}" + ", " else: out += f"{v}" + ("f" if is_float else "") + ", " case 8: v = unpack_from(">d" if is_float else ">q" if is_signed else ">Q", data, offset)[0] if make_hex: out += f"0x{v:16X}" + ", " else: out += f"{v}, " return out if name in STRUCTS: out += indent + "{ " for member in STRUCTS[name]["data"]: if len(member["counts"]) > 0: out += "{ " for count in member["counts"]: for i in range(count): if member["type"] in STRUCTS: #print(f"Reading {member['type']} from offset {offset + member['offset']} with size {STRUCTS[member['type']]['size']}") out = output_struct(data, member["type"], out, offset + member["offset"] + (i * STRUCTS[member['type']]['size']), indent + (" " * i)) else: base_size = BASE_TYPES[member["type"]] out = output_type(member["type"], base_size, offset + member["offset"] + (i * base_size), out) out += "}, " else: if member["type"] in STRUCTS: out = output_struct(data, member["type"], out, offset + member["offset"], indent) else: out = output_type(member["type"], member["size"], offset + member["offset"], out) if len(STRUCTS[name]["data"]) <= 4: out = out[:-2] out += indent + "}, " else: out = output_type(name, BASE_TYPES[name], offset, out) return out def output_array(data, name, counts, depth, out, offset): indent = (depth + 1) * " " if depth < len(counts): for i in range(counts[depth]): #print(f"depth {depth} number {i} offset 0x{offset:X}") if len(counts) > depth + 1: out += indent + "\n{" #print(f"recurse start depth {depth+1} offset 0x{offset:X}") out, offset = output_array(data, name, counts, depth+1, out, offset) #print(f"recurse end depth {depth} offset 0x{offset:X}") out += indent + "\n}," else: out = output_struct(data, name, out, offset, (depth + 1) * " ") offset += STRUCTS[name]["size"] if name in STRUCTS else BASE_TYPES[name] else: out = output_struct(data, name, out, offset, "") return out, offset total_size = STRUCTS[struct]["size"] if struct in STRUCTS else BASE_TYPES[struct] total_count = 1 for count in counts: total_count *= count total_size *= total_count #print(f"size 0x{STRUCTS[struct]['size']:X} x {total_count} = 0x{total_size:X}") with open(Path(sys.argv[0]).absolute().parent / "../baseroms/us/baserom.z64", "rb") as f: f.seek(offset, 0) data = f.read(total_size) out = "{" if len(counts) > 0 else "" out, _ = output_array(data, struct, counts, 0, out, 0) out = out + "};" if (len(counts) > 0 and struct in STRUCTS) else (out[:-2] + "};") return out ####################################################################### parse_structs() calc_struct_sizes() # for s in STRUCTS: # if "unk_D_842115F0" in s or "Color_RGB" in s: # print(s) # print(f"\t\t{STRUCTS[s]}") # print() # exit() #print(f"{len(STRUCTS)} structs parsed in") def dump(offset, type_name): if type(offset) != int: offset = int(offset, 16) counts = [] while "[" in type_name: count = type_name.split("[",1)[1].split("]",1)[0] type_name = type_name.split("[",1)[0] + type_name.split("]",1)[1] count = int(count, 0) counts.append(count) if type_name not in STRUCTS and type_name not in BASE_TYPES: print(f"Could not find type definition \"{type_name}\", maybe it failed to parse...") exit() out = dump_data(offset, type_name, counts) return out if __name__ == "__main__": rom_offset = find_sym.find(sys.argv[1]) out = dump(rom_offset["rom"], sys.argv[2]) print(out)