Add Python linter to github actions (#1100)

* Add Python linter to github actions

* wip

* Add back splat_ext

* Format files

* C++ -> C

* format 2 files

* split workflow into separate file, line length 120, fix excludes

* -l 120 in ci

* update black locally and apply formatting changes

* pyproject.toject

---------

Co-authored-by: Ethan Roseman <ethteck@gmail.com>
This commit is contained in:
lshamis 2023-07-29 10:03:17 -07:00 committed by GitHub
parent a69ae38bfe
commit ae66312d8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 2033 additions and 1588 deletions

View File

@ -4,7 +4,7 @@ on:
jobs:
cpp_lint:
name: Format and lint
name: C format and lint
runs-on: ubuntu-latest
steps:
- name: Checkout

24
.github/workflows/python.yaml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Python Formatting & Linting
on:
pull_request:
jobs:
py_format:
name: black formatting
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
- name: Run black
run: |
black . --check

View File

@ -12,9 +12,9 @@
},
"includePath": [
"${workspaceFolder}/include",
"${workspaceFolder}/ver/us/build/include",
"${workspaceFolder}/ver/pal/build/include",
"${workspaceFolder}/src",
"${workspaceFolder}/assets/us"
"${workspaceFolder}/assets/pal"
],
"defines": [
"F3DEX_GBI_2",

18
.vscode/settings.json vendored
View File

@ -25,7 +25,9 @@
"docs/doxygen": true,
"expected": true,
"ver/jp/expected": true,
"ver/us/expected": true
"ver/us/expected": true,
"ver/pal/expected": true,
"ver/ique/expected": true
},
"python.autoComplete.extraPaths": [
"./tools"
@ -47,6 +49,7 @@
"*.h": "c",
},
"C_Cpp.autoAddFileAssociations": false,
"C_Cpp.default.cStandard": "c89",
"files.exclude": {
"**/.git": true,
"**/.splat_cache": true,
@ -56,7 +59,14 @@
"**/*.i": true,
"docs/doxygen": true
},
"C_Cpp.default.cStandard": "c89",
"python.linting.mypyEnabled": true,
"python.linting.enabled": true,
"[python]": {
"editor.formatOnType": true,
"editor.wordBasedSuggestions": false,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications",
"editor.defaultFormatter": "ms-python.black-formatter",
},
"black-formatter.args": [
"-l 120"
],
}

View File

@ -5,33 +5,36 @@ import re
import sys
from pathlib import Path
def strip_c_comments(text):
def replacer(match):
s = match.group(0)
if s.startswith('/'):
if s.startswith("/"):
return " "
else:
return s
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
re.DOTALL | re.MULTILINE
re.DOTALL | re.MULTILINE,
)
return re.sub(pattern, replacer, text)
c_func_pattern = re.compile(
r"^(static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+{",
re.MULTILINE
)
c_func_pattern = re.compile(r"^(static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+{", re.MULTILINE)
def funcs_in_c(text):
return (match.group(2) for match in c_func_pattern.finditer(text))
asm_func_pattern = re.compile(
r"INCLUDE_ASM\([^,]+, [^,]+, ([^,)]+)",
re.MULTILINE
)
asm_func_pattern = re.compile(r"INCLUDE_ASM\([^,]+, [^,]+, ([^,)]+)", re.MULTILINE)
def include_asms_in_c(text):
return (match.group(1) for match in asm_func_pattern.finditer(text))
def stuff(version):
DIR = os.path.dirname(__file__)
NONMATCHINGS_DIR = Path(os.path.join(DIR, "ver", version, "asm", "nonmatchings"))
@ -76,6 +79,7 @@ def stuff(version):
if not os.listdir(folder[0]):
os.removedirs(folder[0])
stuff("jp")
stuff("us")
stuff("pal")

View File

@ -13,9 +13,7 @@ sys.path.append("tools")
from old.update_evts import parse_symbol_addrs
from tools.disasm_script import ScriptDisassembler, get_constants
parser = argparse.ArgumentParser(
description="Diff EVT macros."
)
parser = argparse.ArgumentParser(description="Diff EVT macros.")
parser.add_argument(
"start",
@ -26,21 +24,13 @@ parser.add_argument(
"-w",
"--watch",
action="store_true",
help="Watch for file changes and update the diff automatically."
help="Watch for file changes and update the diff automatically.",
)
parser.add_argument(
"-m",
"--make",
action="store_true",
help="Run ninja automatically."
)
parser.add_argument("-m", "--make", action="store_true", help="Run ninja automatically.")
parser.add_argument("-o", action="store_true", help="Ignored for compatibility with diff.py.")
parser.add_argument(
"-o",
action="store_true",
help="Ignored for compatibility with diff.py."
)
class EvtDisplay(Display):
def __init__(self, start):
@ -106,11 +96,13 @@ class EvtDisplay(Display):
refresh_key = (current, target)
return (output, refresh_key)
class FakeConfig():
class FakeConfig:
def __init__(self, args):
self.make = args.make
self.source_extensions = ["c", "h"]
def run_ninja():
return subprocess.run(
["ninja", "ver/current/build/papermario.z64"],
@ -118,6 +110,7 @@ def run_ninja():
stdout=subprocess.PIPE,
)
def main():
args = parser.parse_args()
get_constants()
@ -153,8 +146,7 @@ def main():
ret = run_ninja()
if ret.returncode != 0:
display.update(
ret.stderr.decode("utf-8-sig", "replace")
or ret.stdout.decode("utf-8-sig", "replace"),
ret.stderr.decode("utf-8-sig", "replace") or ret.stdout.decode("utf-8-sig", "replace"),
error=True,
)
continue
@ -164,5 +156,6 @@ def main():
else:
display.run_sync()
if __name__ == "__main__":
main()

View File

@ -22,7 +22,7 @@ parser.add_argument(
action="store",
default=False,
const="prompt",
help="run diff.py on the result with the provided arguments"
help="run diff.py on the result with the provided arguments",
)
parser.add_argument(
"-m", "--make", help="run ninja before finding difference(s)", action="store_true"
@ -101,7 +101,9 @@ def search_rom_address(target_addr):
continue
if rom > target_addr:
return f"{prev_sym} (RAM 0x{prev_ram:X}, ROM 0x{prev_rom:X}, {prev_file})"
return (
f"{prev_sym} (RAM 0x{prev_ram:X}, ROM 0x{prev_rom:X}, {prev_file})"
)
prev_ram = ram
prev_rom = rom
@ -214,9 +216,7 @@ if diffs == 0:
if len(found_instr_diff) > 0:
for i in found_instr_diff:
print(f"Instruction difference at ROM addr 0x{i:X}, {search_rom_address(i)}")
print(
f"Bytes: {hexbytes(mybin[i : i + 4])} vs {hexbytes(basebin[i : i + 4])}"
)
print(f"Bytes: {hexbytes(mybin[i : i + 4])} vs {hexbytes(basebin[i : i + 4])}")
print()
definite_shift = diffs > shift_cap

View File

@ -25,11 +25,7 @@ def load_latest_progress(version):
version = Path("ver/current").resolve().parts[-1]
csv = (
urlopen(f"https://papermar.io/reports/progress_{version}.csv")
.read()
.decode("utf-8")
)
csv = urlopen(f"https://papermar.io/reports/progress_{version}.csv").read().decode("utf-8")
latest = csv.split("\n")[-2]
(
@ -56,14 +52,10 @@ def load_latest_progress(version):
def get_func_info():
try:
result = subprocess.run(
["mips-linux-gnu-objdump", "-x", elf_path], stdout=subprocess.PIPE
)
result = subprocess.run(["mips-linux-gnu-objdump", "-x", elf_path], stdout=subprocess.PIPE)
nm_lines = result.stdout.decode().split("\n")
except:
print(
f"Error: Could not run objdump on {elf_path} - make sure that the project is built"
)
print(f"Error: Could not run objdump on {elf_path} - make sure that the project is built")
sys.exit(1)
sizes = {}
@ -135,19 +127,13 @@ def do_section_progress(
section_vram_end,
):
funcs = get_funcs_in_vram_range(vrams, section_vram_start, section_vram_end)
matching_size, nonmatching_size = get_funcs_sizes(
sizes, matchings, nonmatchings, restrict_to=funcs
)
matching_size, nonmatching_size = get_funcs_sizes(sizes, matchings, nonmatchings, restrict_to=funcs)
section_total_size = matching_size + nonmatching_size
progress_ratio = (matching_size / section_total_size) * 100
matching_ratio = (matching_size / total_size) * 100
total_ratio = (section_total_size / total_size) * 100
print(
f"\t{section_name}: {matching_size} matching bytes / {section_total_size} total ({progress_ratio:.2f}%)"
)
print(
f"\t\t(matched {matching_ratio:.2f}% of {total_ratio:.2f}% total rom for {section_name})"
)
print(f"\t{section_name}: {matching_size} matching bytes / {section_total_size} total ({progress_ratio:.2f}%)")
print(f"\t\t(matched {matching_ratio:.2f}% of {total_ratio:.2f}% total rom for {section_name})")
def main(args):
@ -163,9 +149,7 @@ def main(args):
nonmatching_funcs = get_nonmatching_funcs()
matching_funcs = all_funcs - nonmatching_funcs
matching_size, nonmatching_size = get_funcs_sizes(
sizes, matching_funcs, nonmatching_funcs
)
matching_size, nonmatching_size = get_funcs_sizes(sizes, matching_funcs, nonmatching_funcs)
if len(all_funcs) == 0:
funcs_matching_ratio = 0.0
@ -238,19 +222,9 @@ def main(args):
print(f"Warning: category/total size mismatch on version {args.version}!\n")
print("Matching size: " + str(matching_size))
print("Nonmatching size: " + str(nonmatching_size))
print(
"Sum: "
+ str(matching_size + nonmatching_size)
+ " (should be "
+ str(total_size)
+ ")"
)
print(
f"{len(matching_funcs)} matched functions / {len(all_funcs)} total ({funcs_matching_ratio:.2f}%)"
)
print(
f"{matching_size} matching bytes / {total_size} total ({matching_ratio:.2f}%)"
)
print("Sum: " + str(matching_size + nonmatching_size) + " (should be " + str(total_size) + ")")
print(f"{len(matching_funcs)} matched functions / {len(all_funcs)} total ({funcs_matching_ratio:.2f}%)")
print(f"{matching_size} matching bytes / {total_size} total ({matching_ratio:.2f}%)")
do_section_progress(
"effects",

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[tool.black]
line-length = 120
exclude = 'tools/splat/'
extend-exclude = 'diff.py'

View File

@ -10,7 +10,7 @@ from enum import IntEnum
script_dir = os.path.dirname(os.path.realpath(__file__))
asm_dir = script_dir + "/../ver/current/asm/nonmatchings"
modes = [ "min", "max", "avg", "total", "size" ]
modes = ["min", "max", "avg", "total", "size"]
sizes = {}
@ -47,18 +47,53 @@ def do_dir(root, dir):
avg = 0 if len(files) == 0 else total / len(files)
sizes[root + "/" + dir] = ((min, max, total, avg, len(files)))
sizes[root + "/" + dir] = (min, max, total, avg, len(files))
parser = argparse.ArgumentParser(description="A tool to receive information about the number of non-matching .s files "
+"per .c file, or the size of .s files, measured by their number of instructions. "
+"Option -p is used by default if no option is specified.")
parser = argparse.ArgumentParser(
description="A tool to receive information about the number of non-matching .s files "
+ "per .c file, or the size of .s files, measured by their number of instructions. "
+ "Option -p is used by default if no option is specified."
)
group = parser.add_mutually_exclusive_group()
group.add_argument("-f", "--files", help="Default. Print the number of non-matching .s files per .c file, ordered by size.", action='store_true', required=False)
group.add_argument("-a", "--alphabetical", help="Print the size of .s files, ordered by name.", action='store_true', required=False)
group.add_argument("-s", "--size", help="Print the size of .s files, ordered by size.", action='store_true', required=False)
parser.add_argument("-l", "--limit", help="Only print the .c --files that are greater than or equal to the value.", type=int, default=0, required=False)
parser.add_argument("-m", "--mode", help="Switches between output modes for --files. Allowed values are: {min, max, avg, total, size}.", choices=modes, default="size", metavar='', required=False)
group.add_argument(
"-f",
"--files",
help="Default. Print the number of non-matching .s files per .c file, ordered by size.",
action="store_true",
required=False,
)
group.add_argument(
"-a",
"--alphabetical",
help="Print the size of .s files, ordered by name.",
action="store_true",
required=False,
)
group.add_argument(
"-s",
"--size",
help="Print the size of .s files, ordered by size.",
action="store_true",
required=False,
)
parser.add_argument(
"-l",
"--limit",
help="Only print the .c --files that are greater than or equal to the value.",
type=int,
default=0,
required=False,
)
parser.add_argument(
"-m",
"--mode",
help="Switches between output modes for --files. Allowed values are: {min, max, avg, total, size}.",
choices=modes,
default="size",
metavar="",
required=False,
)
args = parser.parse_args()

View File

@ -12,6 +12,6 @@ if __name__ == "__main__":
with open(infile, "rb") as i:
for char in i.read():
f.write(f'0x{char:02X}, ')
f.write(f"0x{char:02X}, ")
f.write(f"}};\n")

View File

@ -4,10 +4,7 @@ import os
from pathlib import Path
from typing import Tuple
ASSETS_DIR = (
Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
/ "assets"
)
ASSETS_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) / "assets"
@lru_cache(maxsize=None)

View File

@ -28,9 +28,7 @@ PIGMENT_REQ_VERSION = "0.3.0"
def exec_shell(command: List[str]) -> str:
ret = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
ret = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return ret.stdout
@ -50,9 +48,7 @@ def write_ninja_rules(
if use_ccache:
ccache = "ccache "
try:
subprocess.call(
["ccache"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
subprocess.call(["ccache"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
ccache = ""
@ -134,9 +130,7 @@ def write_ninja_rules(
command="sha1sum -c $in && touch $out" if DO_SHA1_CHECK else "touch $out",
)
ninja.rule(
"cpp", description="cpp $in", command=f"{cpp} $in {extra_cppflags} -P -o $out"
)
ninja.rule("cpp", description="cpp $in", command=f"{cpp} $in {extra_cppflags} -P -o $out")
ninja.rule(
"cc",
@ -287,9 +281,7 @@ def write_ninja_rules(
command=f"$python {BUILD_TOOLS}/mapfs/pack_title_data.py $out $in",
)
ninja.rule(
"map_header", command=f"$python {BUILD_TOOLS}/mapfs/map_header.py $in > $out"
)
ninja.rule("map_header", command=f"$python {BUILD_TOOLS}/mapfs/map_header.py $in > $out")
ninja.rule("charset", command=f"$python {BUILD_TOOLS}/pm_charset.py $out $in")
@ -303,25 +295,17 @@ def write_ninja_rules(
command=f"$python {BUILD_TOOLS}/sprite/sprite_shading_profiles.py $in $out $header_path",
)
ninja.rule(
"imgfx_data", command=f"$python {BUILD_TOOLS}/imgfx/imgfx_data.py $in $out"
)
ninja.rule("imgfx_data", command=f"$python {BUILD_TOOLS}/imgfx/imgfx_data.py $in $out")
ninja.rule("shape", command=f"$python {BUILD_TOOLS}/mapfs/shape.py $in $out")
ninja.rule(
"effect_data", command=f"$python {BUILD_TOOLS}/effects.py $in_yaml $out_dir"
)
ninja.rule("effect_data", command=f"$python {BUILD_TOOLS}/effects.py $in_yaml $out_dir")
ninja.rule("pm_sbn", command=f"$python {BUILD_TOOLS}/audio/sbn.py $out $in")
with Path("tools/permuter_settings.toml").open("w") as f:
f.write(
f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n"
)
f.write(
f'assembler_command = "{cross}as -EB -march=vr4300 -mtune=vr4300 -Iinclude"\n'
)
f.write(f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n")
f.write(f'assembler_command = "{cross}as -EB -march=vr4300 -mtune=vr4300 -Iinclude"\n')
f.write(f'compiler_type = "gcc"\n')
f.write(
"""
@ -512,11 +496,7 @@ class Configure:
for object_path in object_paths:
if object_path.suffixes[-1] == ".o":
built_objects.add(str(object_path))
elif (
object_path.suffixes[-1] == ".h"
or task == "bin_inc_c"
or task == "pal_inc_c"
):
elif object_path.suffixes[-1] == ".h" or task == "bin_inc_c" or task == "pal_inc_c":
generated_headers.append(str(object_path))
# don't rebuild objects if we've already seen all of them
@ -580,15 +560,13 @@ class Configure:
if isinstance(seg, segtypes.n64.header.N64SegHeader):
build(entry.object_path, entry.src_paths, "as")
elif isinstance(seg, segtypes.common.asm.CommonSegAsm) or (
isinstance(seg, segtypes.common.data.CommonSegData)
and not seg.type[0] == "."
isinstance(seg, segtypes.common.data.CommonSegData) and not seg.type[0] == "."
):
build(entry.object_path, entry.src_paths, "as")
elif seg.type in ["pm_effect_loads", "pm_effect_shims"]:
build(entry.object_path, entry.src_paths, "as")
elif isinstance(seg, segtypes.common.c.CommonSegC) or (
isinstance(seg, segtypes.common.data.CommonSegData)
and seg.type[0] == "."
isinstance(seg, segtypes.common.data.CommonSegData) and seg.type[0] == "."
):
cflags = None
if isinstance(seg.yaml, dict):
@ -619,16 +597,12 @@ class Configure:
task = "cc_272"
cflags = cflags.replace("gcc_272", "")
encoding = (
"CP932" # similar to SHIFT-JIS, but includes backslash and tilde
)
encoding = "CP932" # similar to SHIFT-JIS, but includes backslash and tilde
if version == "ique":
encoding = "EUC-JP"
# Dead cod
if isinstance(seg.parent.yaml, dict) and seg.parent.yaml.get(
"dead_code", False
):
if isinstance(seg.parent.yaml, dict) and seg.parent.yaml.get("dead_code", False):
obj_path = str(entry.object_path)
init_obj_path = Path(obj_path + ".dead")
build(
@ -677,9 +651,7 @@ class Configure:
src_paths = [seg.out_path().relative_to(ROOT)]
inc_dir = self.build_path() / "include" / seg.dir
bin_path = (
self.build_path() / seg.dir / (seg.name + ".png.bin")
)
bin_path = self.build_path() / seg.dir / (seg.name + ".png.bin")
build(
bin_path,
@ -691,9 +663,7 @@ class Configure:
},
)
assert seg.vram_start is not None, (
"img with vram_start unset: " + seg.name
)
assert seg.vram_start is not None, "img with vram_start unset: " + seg.name
c_sym = seg.create_symbol(
addr=seg.vram_start,
@ -720,9 +690,7 @@ class Configure:
elif isinstance(seg, segtypes.n64.palette.N64SegPalette):
src_paths = [seg.out_path().relative_to(ROOT)]
inc_dir = self.build_path() / "include" / seg.dir
bin_path = (
self.build_path() / seg.dir / (seg.name + ".pal.bin")
)
bin_path = self.build_path() / seg.dir / (seg.name + ".pal.bin")
build(
bin_path,
@ -833,9 +801,7 @@ class Configure:
)
# Sprites .bin
sprite_player_header_path = str(
self.build_path() / "include/sprite/player.h"
)
sprite_player_header_path = str(self.build_path() / "include/sprite/player.h")
build(
entry.object_path.with_suffix(".bin"),
@ -843,9 +809,7 @@ class Configure:
"sprites",
variables={
"header_out": sprite_player_header_path,
"build_dir": str(
self.build_path() / "assets" / self.version / "sprite"
),
"build_dir": str(self.build_path() / "assets" / self.version / "sprite"),
"asset_stack": ",".join(self.asset_stack),
},
implicit_outputs=[sprite_player_header_path],
@ -859,9 +823,7 @@ class Configure:
msg_bins = []
for section_idx, msg_path in enumerate(entry.src_paths):
bin_path = (
entry.object_path.with_suffix("") / f"{section_idx:02X}.bin"
)
bin_path = entry.object_path.with_suffix("") / f"{section_idx:02X}.bin"
msg_bins.append(bin_path)
build(bin_path, [msg_path], "msg")
@ -1005,16 +967,12 @@ class Configure:
)
elif name.endswith("_shape_built"):
base_name = name[:-6]
raw_bin_path = self.resolve_asset_path(
f"assets/x/mapfs/geom/{base_name}.bin"
)
raw_bin_path = self.resolve_asset_path(f"assets/x/mapfs/geom/{base_name}.bin")
bin_path = bin_path.parent / "geom" / (base_name + ".bin")
if c_maps:
# raw bin -> c -> o -> elf -> objcopy -> final bin file
c_file_path = (
bin_path.parent / "geom" / base_name
).with_suffix(".c")
c_file_path = (bin_path.parent / "geom" / base_name).with_suffix(".c")
o_path = bin_path.parent / "geom" / (base_name + ".o")
elf_path = bin_path.parent / "geom" / (base_name + ".elf")
@ -1056,12 +1014,7 @@ class Configure:
rasters = []
for src_path in entry.src_paths:
out_path = (
self.build_path()
/ seg.dir
/ seg.name
/ (src_path.stem + ".bin")
)
out_path = self.build_path() / seg.dir / seg.name / (src_path.stem + ".bin")
build(
out_path,
[src_path],
@ -1079,13 +1032,7 @@ class Configure:
palettes = []
for src_path in entry.src_paths:
out_path = (
self.build_path()
/ seg.dir
/ seg.name
/ "palette"
/ (src_path.stem + ".bin")
)
out_path = self.build_path() / seg.dir / seg.name / "palette" / (src_path.stem + ".bin")
build(
out_path,
[src_path],
@ -1100,9 +1047,7 @@ class Configure:
build(entry.object_path.with_suffix(""), palettes, "charset_palettes")
build(entry.object_path, [entry.object_path.with_suffix("")], "bin")
elif seg.type == "pm_sprite_shading_profiles":
header_path = str(
self.build_path() / "include/sprite/sprite_shading_profiles.h"
)
header_path = str(self.build_path() / "include/sprite/sprite_shading_profiles.h")
build(
entry.object_path.with_suffix(""),
entry.src_paths,
@ -1115,14 +1060,12 @@ class Configure:
build(entry.object_path, [entry.object_path.with_suffix("")], "bin")
elif seg.type == "pm_sbn":
sbn_path = entry.object_path.with_suffix("")
build(sbn_path, entry.src_paths, "pm_sbn") # could have non-yaml inputs be implicit
build(sbn_path, entry.src_paths, "pm_sbn") # could have non-yaml inputs be implicit
build(entry.object_path, [sbn_path], "bin")
elif seg.type == "linker" or seg.type == "linker_offset":
pass
elif seg.type == "pm_imgfx_data":
c_file_path = (
Path(f"assets/{self.version}") / "imgfx" / (seg.name + ".c")
)
c_file_path = Path(f"assets/{self.version}") / "imgfx" / (seg.name + ".c")
build(c_file_path, entry.src_paths, "imgfx_data")
build(
@ -1136,9 +1079,7 @@ class Configure:
},
)
else:
raise Exception(
f"don't know how to build {seg.__class__.__name__} '{seg.name}'"
)
raise Exception(f"don't know how to build {seg.__class__.__name__} '{seg.name}'")
# Run undefined_syms through cpp
ninja.build(
@ -1216,20 +1157,14 @@ if __name__ == "__main__":
action="store_true",
help="Delete assets and previously-built files",
)
parser.add_argument(
"--splat", default="tools/splat", help="Path to splat tool to use"
)
parser.add_argument(
"--split-code", action="store_true", help="Re-split code segments to asm files"
)
parser.add_argument("--splat", default="tools/splat", help="Path to splat tool to use")
parser.add_argument("--split-code", action="store_true", help="Re-split code segments to asm files")
parser.add_argument(
"--no-split-assets",
action="store_true",
help="Don't split assets from the baserom(s)",
)
parser.add_argument(
"-d", "--debug", action="store_true", help="Generate debugging information"
)
parser.add_argument("-d", "--debug", action="store_true", help="Generate debugging information")
parser.add_argument(
"-n",
"--non-matching",
@ -1272,12 +1207,8 @@ if __name__ == "__main__":
pass
if args.cpp is None:
print("error: system C preprocessor is not GNU!")
print(
"This is a known issue on macOS - only clang's cpp is installed by default."
)
print(
"Use 'brew' to obtain GNU cpp, then run this script again with the --cpp option, e.g."
)
print("This is a known issue on macOS - only clang's cpp is installed by default.")
print("Use 'brew' to obtain GNU cpp, then run this script again with the --cpp option, e.g.")
print(f" ./configure --cpp {gcc_cpps[0]}")
exit(1)
@ -1285,15 +1216,11 @@ if __name__ == "__main__":
version = exec_shell([PIGMENT, "--version"]).split(" ")[1].strip()
if version < PIGMENT_REQ_VERSION:
print(
f"error: {PIGMENT} version {PIGMENT_REQ_VERSION} or newer is required, system version is {version}\n"
)
print(f"error: {PIGMENT} version {PIGMENT_REQ_VERSION} or newer is required, system version is {version}\n")
exit(1)
except FileNotFoundError:
print(f"error: {PIGMENT} is not installed\n")
print(
"To build and install it, obtain cargo:\n\tcurl https://sh.rustup.rs -sSf | sh"
)
print("To build and install it, obtain cargo:\n\tcurl https://sh.rustup.rs -sSf | sh")
print(f"and then run:\n\tcargo install {PIGMENT}")
exit(1)
@ -1382,12 +1309,8 @@ if __name__ == "__main__":
# include tools/splat_ext in the python path
sys.path.append(str((ROOT / "tools/splat_ext").resolve()))
configure.split(
not args.no_split_assets, args.split_code, args.shift, args.debug
)
configure.write_ninja(
ninja, skip_files, non_matching, args.modern_gcc, args.c_maps
)
configure.split(not args.no_split_assets, args.split_code, args.shift, args.debug)
configure.write_ninja(ninja, skip_files, non_matching, args.modern_gcc, args.c_maps)
all_rom_oks.append(str(configure.rom_ok_path()))

View File

@ -9,9 +9,7 @@ from splat_ext.pm_effect_loads import effects_from_yaml
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Builds effect table, function declarations, macros, and enum"
)
parser = argparse.ArgumentParser(description="Builds effect table, function declarations, macros, and enum")
parser.add_argument("in_yaml")
parser.add_argument("out_dir", type=Path)
args = parser.parse_args()
@ -31,9 +29,7 @@ if __name__ == "__main__":
effect_enum_text += f" {enum_name} = 0x{i:02X},\n"
if not effect.empty:
effect_table_text += (
f" FX_ENTRY({effect.name}, effect_gfx_{effect.gfx}),\n"
)
effect_table_text += f" FX_ENTRY({effect.name}, effect_gfx_{effect.gfx}),\n"
fx_decls_text += effect.get_macro_call("fx_" + effect.name) + ";\n"
main_decls_text += effect.get_macro_call(effect.name + "_main") + ";\n"
macro_defs += effect.get_macro_def() + "\n"

View File

@ -1,23 +1,23 @@
#!/usr/bin/env python3
import sys, os
#Under normal compilation we rely on splat to use a discard option in the ldscript
#to not include sections in the elf then just output all sections, however under debug we want
#to have debug sections.
#In debugging mode splat is told to output a list of sections it is custom creating, which are
#all of the sections we export to the z64 file with an objcopy. The below chunk of code is
#responsible for adding -j to each of the names and outputting a file for objcopy to use
#so we can still generate a elf file with all the extra debugging sections and still output
# Under normal compilation we rely on splat to use a discard option in the ldscript
# to not include sections in the elf then just output all sections, however under debug we want
# to have debug sections.
# In debugging mode splat is told to output a list of sections it is custom creating, which are
# all of the sections we export to the z64 file with an objcopy. The below chunk of code is
# responsible for adding -j to each of the names and outputting a file for objcopy to use
# so we can still generate a elf file with all the extra debugging sections and still output
# the required sections to the .z64 without outputting everything.
if __name__ == "__main__":
infile, outfile = sys.argv[1:]
infile, outfile = sys.argv[1:]
#generate output based on input
file_data = open(infile,"r").read().split("\n")
if len(file_data[-1]) == 0:
file_data.pop()
# generate output based on input
file_data = open(infile, "r").read().split("\n")
if len(file_data[-1]) == 0:
file_data.pop()
outdata = "-j " + " -j ".join(file_data)
with open(outfile, "w") as f:
f.write(outdata)
outdata = "-j " + " -j ".join(file_data)
with open(outfile, "w") as f:
f.write(outdata)

View File

@ -221,12 +221,8 @@ class Converter:
# header (struct BackgroundHeader)
for i, palette in enumerate(palettes):
out_bytes += (baseaddr + palettes_len + headers_len).to_bytes(
4, byteorder="big"
) # raster offset
out_bytes += (baseaddr + headers_len + 0x200 * i).to_bytes(
4, byteorder="big"
) # palette offset
out_bytes += (baseaddr + palettes_len + headers_len).to_bytes(4, byteorder="big") # raster offset
out_bytes += (baseaddr + headers_len + 0x200 * i).to_bytes(4, byteorder="big") # palette offset
out_bytes += (12).to_bytes(2, byteorder="big") # startX
out_bytes += (20).to_bytes(2, byteorder="big") # startY
out_bytes += (out_width).to_bytes(2, byteorder="big") # width
@ -263,8 +259,6 @@ if __name__ == "__main__":
flip_x = "--flip-x" in argv
flip_y = "--flip-y" in argv
(out_bytes, out_width, out_height) = Converter(
mode, infile, flip_x, flip_y
).convert()
(out_bytes, out_width, out_height) = Converter(mode, infile, flip_x, flip_y).convert()
with open(argv[3], "wb") as f:
f.write(out_bytes)

View File

@ -6,6 +6,7 @@ import json
from pathlib import Path
from typing import Any, List
@dataclass
class Vertex:
idx: int
@ -20,7 +21,16 @@ class Vertex:
a: int
def toJSON(self):
return " { \"pos\": [" + f"{self.x}, {self.y}, {self.z}" + "], \"uv\": [" + f"{self.u}, {self.v}" + "], \"rgba\": [" + f"{self.r}, {self.g}, {self.b}, {self.a}" + "] }"
return (
' { "pos": ['
+ f"{self.x}, {self.y}, {self.z}"
+ '], "uv": ['
+ f"{self.u}, {self.v}"
+ '], "rgba": ['
+ f"{self.r}, {self.g}, {self.b}, {self.a}"
+ "] }"
)
@dataclass
class Triangle:
@ -31,6 +41,7 @@ class Triangle:
def toJSON(self):
return f" [{self.i}, {self.j}, {self.k}]"
@dataclass
class Anim:
name: str
@ -46,13 +57,15 @@ class Anim:
triangles: List[Triangle]
def toJSON(self):
framestr = ",\n".join([" [\n" + ",\n".join([v.toJSON() for v in frame]) + "\n ]" for i, frame in enumerate(self.frames)])
framestr = ",\n".join(
[" [\n" + ",\n".join([v.toJSON() for v in frame]) + "\n ]" for i, frame in enumerate(self.frames)]
)
trianglestr = ",\n".join([t.toJSON() for t in self.triangles])
ret = "{\n"
ret += " \"flags\": " + str(self.flags) + ",\n"
ret += " \"frames\": [\n" + framestr + "],\n"
ret += " \"triangles\": [\n" + trianglestr + "]\n"
ret += ' "flags": ' + str(self.flags) + ",\n"
ret += ' "frames": [\n' + framestr + "],\n"
ret += ' "triangles": [\n' + trianglestr + "]\n"
ret += "}"
return ret
@ -60,7 +73,10 @@ class Anim:
@staticmethod
def fromJSON(name: str, data: Any) -> "Anim":
flags = data["flags"]
frames = [[Vertex(idx, *vtx["pos"], *vtx["uv"], *vtx["rgba"]) for idx, vtx in enumerate(frame)] for frame in data["frames"]]
frames = [
[Vertex(idx, *vtx["pos"], *vtx["uv"], *vtx["rgba"]) for idx, vtx in enumerate(frame)]
for frame in data["frames"]
]
triangles = [Triangle(*t) for t in data["triangles"]]
return Anim(
@ -73,15 +89,16 @@ class Anim:
keyframes=len(frames),
flags=flags,
frames=frames,
triangles=triangles
triangles=triangles,
)
def build(inputs: List[Path], output: Path):
with open(output, "w") as f:
f.write("/* NOTE: This file is autogenerated, do not edit */\n\n")
f.write("#include \"PR/gbi.h\"\n")
f.write("#include \"macros.h\"\n")
f.write("#include \"imgfx.h\"\n\n")
f.write('#include "PR/gbi.h"\n')
f.write('#include "macros.h"\n')
f.write('#include "imgfx.h"\n\n')
for input in inputs:
with open(input, "r") as fin:
@ -101,7 +118,9 @@ def build(inputs: List[Path], output: Path):
for frame in anim.frames:
f.write(" {\n")
for vtx in frame:
f.write(f" {{ {{{vtx.x}, {vtx.y}, {vtx.z}}}, {{{vtx.u}, {vtx.v}}}, {{{vtx.r}, {vtx.g}, {vtx.b}}}, {vtx.a} }},\n")
f.write(
f" {{ {{{vtx.x}, {vtx.y}, {vtx.z}}}, {{{vtx.u}, {vtx.v}}}, {{{vtx.r}, {vtx.g}, {vtx.b}}}, {vtx.a} }},\n"
)
f.write(" },\n")
f.write("};\n\n")
@ -134,7 +153,9 @@ def build(inputs: List[Path], output: Path):
# We need a new chunk
if max_t1 >= 32 and not just_chunked:
chunk_text = f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {min(32, max_t1 + 1)}, 0),\n" + chunk_text
chunk_text = (
f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {min(32, max_t1 + 1)}, 0),\n" + chunk_text
)
just_chunked = True
f.write(chunk_text)
chunk_text = ""
@ -155,7 +176,9 @@ def build(inputs: List[Path], output: Path):
old_max_t = max(max_t1, max_t2)
# Dump final chunk
chunk_text = f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {max(max_t1, max_t2) + 1}, 0),\n" + chunk_text
chunk_text = (
f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {max(max_t1, max_t2) + 1}, 0),\n" + chunk_text
)
f.write(chunk_text)
f.write(" gsSPEndDisplayList(),\n")
f.write("};\n\n")

View File

@ -20,7 +20,7 @@ if __name__ == "__main__":
mode = sys.argv[2]
syms_to_max = {
"entity_data_vram_end" : [
"entity_data_vram_end": [
"entity_default_VRAM_END",
"entity_jan_iwa_VRAM_END",
"entity_sbk_omo_VRAM_END",
@ -44,7 +44,7 @@ if __name__ == "__main__":
"world_action_use_spinning_flower_VRAM_END",
"world_action_use_tweester_VRAM_END",
"world_action_sneaky_parasol_VRAM_END",
]
],
}
addrs: Dict[str, List[int]] = {}
@ -80,7 +80,9 @@ if __name__ == "__main__":
out_addrs = {sym: max(addrs[sym]) for sym in addrs}
out_addrs["entity_data_vram_end"] = out_addrs["entity_data_vram_end"] + out_addrs["world_action_vram_end"] - HARDCODED_ADDR
out_addrs["entity_data_vram_end"] = (
out_addrs["entity_data_vram_end"] + out_addrs["world_action_vram_end"] - HARDCODED_ADDR
)
out = ""
for sym in out_addrs:

View File

@ -4,9 +4,11 @@ from sys import argv
from pathlib import Path
import struct
def next_multiple(pos, multiple):
return pos + pos % multiple
def get_version_date(version):
if version == "us":
return "Map Ver.00/11/07 15:36"
@ -17,6 +19,7 @@ def get_version_date(version):
else:
return "Map Ver.??/??/?? ??:??"
def build_mapfs(out_bin, assets, version):
# every TOC entry's name field has data after the null terminator made up from all the previous name fields.
# we probably don't have to do this for the game to read the data properly (it doesn't read past the null terminator
@ -38,12 +41,12 @@ def build_mapfs(out_bin, assets, version):
decompressed_size = decompressed.stat().st_size
size = next_multiple(compressed.stat().st_size, 2) if compressed.exists() else decompressed_size
#print(f"{name} {offset:08X} {size:08X} {decompressed_size:08X}")
# print(f"{name} {offset:08X} {size:08X} {decompressed_size:08X}")
# write all previously-written names; required to match
lastname = name + lastname[len(name):]
lastname = name + lastname[len(name) :]
f.seek(toc_entry_pos)
f.write(lastname.encode('ascii'))
f.write(lastname.encode("ascii"))
# write TOC entry.
f.seek(toc_entry_pos + 0x10)
@ -61,14 +64,15 @@ def build_mapfs(out_bin, assets, version):
last_name_entry = "end_data\0"
f.seek(toc_entry_pos)
lastname = last_name_entry + lastname[len(last_name_entry):]
f.write(lastname.encode('ascii'))
lastname = last_name_entry + lastname[len(last_name_entry) :]
f.write(lastname.encode("ascii"))
f.seek(toc_entry_pos + 0x18)
f.write((0x903F0000).to_bytes(4, byteorder="big")) # TODO: figure out purpose
f.write((0x903F0000).to_bytes(4, byteorder="big")) # TODO: figure out purpose
if __name__ == "__main__":
argv.pop(0) # python3
argv.pop(0) # python3
version = argv.pop(0)
out = argv.pop(0)
@ -76,6 +80,6 @@ if __name__ == "__main__":
# pairs
for i in range(0, len(argv), 2):
assets.append((Path(argv[i]), Path(argv[i+1])))
assets.append((Path(argv[i]), Path(argv[i + 1])))
build_mapfs(out, assets, version)

View File

@ -4,17 +4,19 @@ from sys import argv, stderr
from os import path
from xml.dom.minidom import parse
def eprint(*args, **kwargs):
print(*args, file=stderr, **kwargs)
if __name__ == "__main__":
_, xml_path = argv
xml = parse(xml_path)
map_name = path.basename(xml_path)[:-4]
print("#include \"common.h\"")
print("#include \"map.h\"")
print('#include "common.h"')
print('#include "map.h"')
print("")
print("#ifndef NAMESPACE")
print(f"#define NAMESPACE {map_name}")

View File

@ -3,7 +3,7 @@
from sys import argv
if __name__ == "__main__":
argv.pop(0) # python3
argv.pop(0) # python3
if len(argv) > 4:
out, img1, img2, img3, img2_pal = argv

View File

@ -90,27 +90,17 @@ class HeaderSegment(Segment):
# note: do not push model root yet
shape.root_node = NodeSegment(self.ptr_root_node, "Node")
shape.vtx_table = shape.push(
VertexTableSegment(self.ptr_vtx_table, "VertexTable")
)
shape.model_names = shape.push(
StringListSegment(self.ptr_model_names, "ModelNames")
)
shape.collider_names = shape.push(
StringListSegment(self.ptr_collider_names, "ColliderNames")
)
shape.zone_names = shape.push(
StringListSegment(self.ptr_zone_names, "ZoneNames")
)
shape.vtx_table = shape.push(VertexTableSegment(self.ptr_vtx_table, "VertexTable"))
shape.model_names = shape.push(StringListSegment(self.ptr_model_names, "ModelNames"))
shape.collider_names = shape.push(StringListSegment(self.ptr_collider_names, "ColliderNames"))
shape.zone_names = shape.push(StringListSegment(self.ptr_zone_names, "ZoneNames"))
def print(self, shape):
shape.print(f"ShapeFileHeader {self.get_sym()} = {{")
shape.print(f" .root = &{shape.get_symbol(self.ptr_root_node)},")
shape.print(f" .vertexTable = {shape.get_symbol(self.ptr_vtx_table)},")
shape.print(f" .modelNames = {shape.get_symbol(self.ptr_model_names)},")
shape.print(
f" .colliderNames = {shape.get_symbol(self.ptr_collider_names)},"
)
shape.print(f" .colliderNames = {shape.get_symbol(self.ptr_collider_names)},")
if self.ptr_zone_names != 0:
shape.print(f" .zoneNames = {shape.get_symbol(self.ptr_zone_names)},")
shape.print("};")
@ -216,9 +206,7 @@ class NodeSegment(Segment):
self.model_name = shape.model_name_map[self.addr]
shape.push(GroupDataSegment(self.ptr_group_data, "GroupData", self.model_name))
shape.push(
DisplayDataSegment(self.ptr_display_data, "DisplayData", self.model_name)
)
shape.push(DisplayDataSegment(self.ptr_display_data, "DisplayData", self.model_name))
shape.push(
PropertyListSegment(
self.ptr_property_list,
@ -284,37 +272,25 @@ class PropertyListSegment(Segment):
if key == 0x5E:
if value == 0:
shape.print(
f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .p = NULL }}}},"
)
shape.print(f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .p = NULL }}}},")
else:
tex_name = read_ascii_string(shape.file_bytes, value)
shape.print(
f' {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .p = "{tex_name}" }}}},'
)
shape.print(f' {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .p = "{tex_name}" }}}},')
elif key == 0x5F:
shape.print(
f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .s = {hex(value)} }}}},"
)
shape.print(f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .s = {hex(value)} }}}},")
else:
if fmt == 0: # int
shape.print(
f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .s = {hex(value)} }}}},"
)
shape.print(f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .s = {hex(value)} }}}},")
elif fmt == 1: # float
temp = struct.pack(">I", value)
(f,) = struct.unpack(">f", temp)
shape.print(
f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .f = {f} }}}},"
)
shape.print(f" {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .f = {f} }}}},")
elif fmt == 2: # pointer
shape.print(
f' {{ .key = {hex(key)}, .dataType = {fmt}, .data = {{ .p = "{shape.get_symbol(value)}" }}}},'
)
else:
raise Exception(
f"Invalid property: 0x{key:08X} 0x{fmt:08X} 0x{value:08X}"
)
raise Exception(f"Invalid property: 0x{key:08X} 0x{fmt:08X} 0x{value:08X}")
shape.print("};")
@ -334,25 +310,15 @@ class GroupDataSegment(Segment):
self.ptr_children,
) = struct.unpack(">IIIII", shape.file_bytes[start : start + 20])
shape.push(
NodeListSegment(
self.ptr_children, "Children", self.model_name, self.num_children
)
)
shape.push(
LightSetSegment(self.ptr_lights, "Lights", self.model_name, self.num_lights)
)
shape.push(NodeListSegment(self.ptr_children, "Children", self.model_name, self.num_children))
shape.push(LightSetSegment(self.ptr_lights, "Lights", self.model_name, self.num_lights))
shape.push(MatrixSegment(self.ptr_transform_mtx, "Mtx", self.model_name))
def print(self, shape):
shape.print(f"ModelGroupData {self.get_sym()} = {{")
if self.ptr_transform_mtx != 0:
shape.print(
f" .transformMatrix = (Mtx*) &{shape.get_symbol(self.ptr_transform_mtx)},"
)
shape.print(
f" .lightingGroup = (Lightsn*) &{shape.get_symbol(self.ptr_lights)},"
)
shape.print(f" .transformMatrix = (Mtx*) &{shape.get_symbol(self.ptr_transform_mtx)},")
shape.print(f" .lightingGroup = (Lightsn*) &{shape.get_symbol(self.ptr_lights)},")
shape.print(f" .numLights = {self.num_lights},")
shape.print(f" .childList = {shape.get_symbol(self.ptr_children)},")
shape.print(f" .numChildren = {self.num_children},")
@ -392,18 +358,14 @@ class MatrixSegment(Segment):
for i in range(4):
(a, b, c, d) = struct.unpack(">hhhh", shape.file_bytes[pos : pos + 8])
pos += 8
shape.print(
f" {{ {hex(a):4}, {hex(b):4}, {hex(c):4}, {hex(d):4} }},"
)
shape.print(f" {{ {hex(a):4}, {hex(b):4}, {hex(c):4}, {hex(d):4} }},")
shape.print(" },")
shape.print(" .frac = {")
for i in range(4):
(a, b, c, d) = struct.unpack(">hhhh", shape.file_bytes[pos : pos + 8])
pos += 8
shape.print(
f" {{ {hex(a):4}, {hex(b):4}, {hex(c):4}, {hex(d):4} }},"
)
shape.print(f" {{ {hex(a):4}, {hex(b):4}, {hex(c):4}, {hex(d):4} }},")
shape.print(" },")
shape.print("};")
@ -416,13 +378,9 @@ class DisplayDataSegment(Segment):
def scan(self, shape):
start = self.addr - BASE_ADDR
(self.ptr_display_list,) = struct.unpack(
">I", shape.file_bytes[start : start + 4]
)
(self.ptr_display_list,) = struct.unpack(">I", shape.file_bytes[start : start + 4])
gfx_segment = shape.push(
DisplayListSegment(self.ptr_display_list, "Gfx", self.model_name)
)
gfx_segment = shape.push(DisplayListSegment(self.ptr_display_list, "Gfx", self.model_name))
# Gfx segments may have been already visited during root Gfx traversal
# so we will now force the associated model name to be the current model
gfx_segment.model_name = self.model_name
@ -449,15 +407,9 @@ class DisplayListSegment(Segment):
if op == GFX_END_DL:
break
elif op == GFX_START_DL:
shape.push(
DisplayListSegment(
w2, f"Gfx_{hex(w2)[2:].upper()}", self.model_name
)
)
shape.push(DisplayListSegment(w2, f"Gfx_{hex(w2)[2:].upper()}", self.model_name))
elif op == GFX_LOAD_MATRIX:
shape.push(
MatrixSegment(w2, f"Mtx_{hex(w2)[2:]}", model_name=self.model_name)
)
shape.push(MatrixSegment(w2, f"Mtx_{hex(w2)[2:]}", model_name=self.model_name))
elif op == GFX_LOAD_VTX:
num = (w1 >> 12) & 0xFFF
idx = (w2 - shape.vtx_table.addr) // 0x10
@ -492,9 +444,7 @@ class DisplayListSegment(Segment):
end = (w1 & 0x00000FFF) // 2
buf_pos = end - num
index = (w2 - shape.vtx_table.addr) // 0x10
shape.print(
f" gsSPVertex(&{shape.vtx_table.get_sym()}[{index}], {num}, {buf_pos}),"
)
shape.print(f" gsSPVertex(&{shape.vtx_table.get_sym()}[{index}], {num}, {buf_pos}),")
elif op == GFX_DRAW_TRI:
i = (w1 & 0x00FF0000) >> 16
j = (w1 & 0x0000FF00) >> 8
@ -507,9 +457,7 @@ class DisplayListSegment(Segment):
d = (w2 & 0x00FF0000) >> 16
e = (w2 & 0x0000FF00) >> 8
f = w2 & 0x000000FF
shape.print(
f" gsSP2Triangles({a // 2}, {b // 2}, {c // 2}, 0, {d // 2}, {e // 2}, {f // 2}, 0),"
)
shape.print(f" gsSP2Triangles({a // 2}, {b // 2}, {c // 2}, 0, {d // 2}, {e // 2}, {f // 2}, 0),")
elif op == GFX_RDP_PIPE_SYNC:
shape.print(" gsDPPipeSync(),")
elif op == GFX_POP_MATRIX:
@ -522,9 +470,7 @@ class DisplayListSegment(Segment):
flags = self.get_geometry_flags(~(w1 | 0xFF000000))
shape.print(f" gsSPClearGeometryMode({flags}),")
elif op == GFX_LOAD_MATRIX:
shape.print(
f" gsSPMatrix(&{shape.get_symbol(w2)}, G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW),"
)
shape.print(f" gsSPMatrix(&{shape.get_symbol(w2)}, G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW),")
elif op == GFX_START_DL:
shape.print(f" gsSPDisplayList({shape.get_symbol(w2)}),")
elif op == GFX_END_DL:
@ -610,9 +556,7 @@ class ShapeFile:
child_start = ptr_children - BASE_ADDR
for i in range(num_children):
(ptr_child,) = struct.unpack(
">I", self.file_bytes[child_start : child_start + 4]
)
(ptr_child,) = struct.unpack(">I", self.file_bytes[child_start : child_start + 4])
self.build_model_name_map(ptr_child, names)
child_start += 4

View File

@ -40,15 +40,11 @@ def img_from_json(json_data, tex_name: str, asset_stack: Tuple[Path, ...]) -> Te
if main_data == None:
raise Exception(f"Texture {ret.img_name} has no definition for 'main'")
(main_fmt_name, ret.main_hwrap, ret.main_vwrap) = ret.read_json_img(
main_data, "main", ret.img_name
)
(main_fmt_name, ret.main_hwrap, ret.main_vwrap) = ret.read_json_img(main_data, "main", ret.img_name)
(ret.main_fmt, ret.main_depth) = get_format_code(main_fmt_name)
# read main image
img_path = get_asset_path(
Path(f"mapfs/tex/{tex_name}/{ret.img_name}.png"), asset_stack
)
img_path = get_asset_path(Path(f"mapfs/tex/{tex_name}/{ret.img_name}.png"), asset_stack)
if not os.path.isfile(img_path):
raise Exception(f"Could not find main image for texture: {ret.img_name}")
(
@ -62,9 +58,7 @@ def img_from_json(json_data, tex_name: str, asset_stack: Tuple[Path, ...]) -> Te
ret.has_aux = "aux" in json_data
if ret.has_aux:
aux_data = json_data.get("aux")
(aux_fmt_name, ret.aux_hwrap, ret.aux_vwrap) = ret.read_json_img(
aux_data, "aux", ret.img_name
)
(aux_fmt_name, ret.aux_hwrap, ret.aux_vwrap) = ret.read_json_img(aux_data, "aux", ret.img_name)
if aux_fmt_name == "Shared":
# aux tiles have blank attributes in SHARED mode
@ -79,9 +73,7 @@ def img_from_json(json_data, tex_name: str, asset_stack: Tuple[Path, ...]) -> Te
ret.extra_tiles = TILES_INDEPENDENT_AUX
# read aux image
img_path = get_asset_path(
Path(f"mapfs/tex/{tex_name}/{ret.img_name}_AUX.png"), asset_stack
)
img_path = get_asset_path(Path(f"mapfs/tex/{tex_name}/{ret.img_name}_AUX.png"), asset_stack)
if not os.path.isfile(img_path):
raise Exception(f"Could not find AUX image for texture: {ret.img_name}")
(
@ -127,9 +119,7 @@ def img_from_json(json_data, tex_name: str, asset_stack: Tuple[Path, ...]) -> Te
f"Texture {ret.img_name} is missing mipmap level {mipmap_idx} (size = {mmw} x {mmh})"
)
(raster, pal, width, height) = ret.get_img_file(
main_fmt_name, str(img_path)
)
(raster, pal, width, height) = ret.get_img_file(main_fmt_name, str(img_path))
ret.mipmaps.append(raster)
if width != mmw or height != mmh:
raise Exception(
@ -160,9 +150,7 @@ def img_from_json(json_data, tex_name: str, asset_stack: Tuple[Path, ...]) -> Te
return ret
def build(
out_path: Path, tex_name: str, asset_stack: Tuple[Path, ...], endian: str = "big"
):
def build(out_path: Path, tex_name: str, asset_stack: Tuple[Path, ...], endian: str = "big"):
out_bytes = bytearray()
json_path = get_asset_path(Path(f"mapfs/tex/{tex_name}.json"), asset_stack)
@ -172,9 +160,7 @@ def build(
json_data = json.loads(json_str)
if len(json_data) > 128:
raise Exception(
f"Maximum number of textures (128) exceeded by {tex_name} ({len(json_data)})`"
)
raise Exception(f"Maximum number of textures (128) exceeded by {tex_name} ({len(json_data)})`")
for img_data in json_data:
img = img_from_json(img_data, tex_name, asset_stack)
@ -189,9 +175,7 @@ if __name__ == "__main__":
parser.add_argument("bin_out", type=Path, help="Output binary file path")
parser.add_argument("name", help="Name of tex subdirectory")
parser.add_argument("asset_stack", help="comma-separated asset stack")
parser.add_argument(
"--endian", choices=["big", "little"], default="big", help="Output endianness"
)
parser.add_argument("--endian", choices=["big", "little"], default="big", help="Output endianness")
args = parser.parse_args()
asset_stack = tuple(Path(d) for d in args.asset_stack.split(","))

View File

@ -6,6 +6,7 @@ import re
import msgpack
import os
class Message:
def __init__(self, d: dict, header_file_index: int):
self.section = d.get("section")
@ -14,6 +15,7 @@ class Message:
self.bytes = d["bytes"]
self.header_file_index = header_file_index
if __name__ == "__main__":
if len(argv) < 3:
print("usage: combine.py [out.bin] [out.h] [compiled...]")
@ -22,7 +24,7 @@ if __name__ == "__main__":
_, outfile, header_file, *infiles = argv
messages = []
#header_files = []
# header_files = []
for i, infile in enumerate(infiles):
# if infile == "--headers":
@ -34,12 +36,12 @@ if __name__ == "__main__":
with open(outfile, "wb") as f:
# sectioned+indexed, followed by just sectioned, followed by just indexed, followed by named (unsectioned & unindexed)
#messages.sort(key=lambda msg: bool(msg.section)<<2 + bool(msg.index))
# messages.sort(key=lambda msg: bool(msg.section)<<2 + bool(msg.index))
names = set()
sections = []
#messages_by_file = {}
# messages_by_file = {}
for message in messages:
if message.section is None:
@ -64,10 +66,10 @@ if __name__ == "__main__":
# else:
# names.add(message.name)
# if message.header_file_index in messages_by_file:
# messages_by_file[message.header_file_index].add(message)
# else:
# messages_by_file[message.header_file_index] = set([message])
# if message.header_file_index in messages_by_file:
# messages_by_file[message.header_file_index].add(message)
# else:
# messages_by_file[message.header_file_index] = set([message])
if message.index in section:
print(f"warning: multiple messages allocated to id {section_idx:02X}:{message.index:03X}")
@ -77,7 +79,7 @@ if __name__ == "__main__":
section[message.index] = message
f.seek((len(sections) + 1) * 4) # skip past table of contents
f.seek((len(sections) + 1) * 4) # skip past table of contents
section_offsets = []
for section in sections:
@ -97,21 +99,15 @@ if __name__ == "__main__":
# padding
while f.tell() % 0x10 != 0:
f.write(b'\0\0\0\0')
f.write(b"\0\0\0\0")
f.seek(0)
for offset in section_offsets:
f.write(offset.to_bytes(4, byteorder="big"))
f.write(b'\0\0\0\0')
f.write(b"\0\0\0\0")
with open(header_file, "w") as f:
f.write(
f"#ifndef _MESSAGE_IDS_H_\n"
f"#define _MESSAGE_IDS_H_\n"
"\n"
'#include "messages.h"\n'
"\n"
)
f.write(f"#ifndef _MESSAGE_IDS_H_\n" f"#define _MESSAGE_IDS_H_\n" "\n" '#include "messages.h"\n' "\n")
for message in messages:
if message.name:

View File

@ -3,7 +3,8 @@
from sys import argv
from collections import OrderedDict
import re
import msgpack # way faster than pickle
import msgpack # way faster than pickle
class Message:
def __init__(self, name, section, index):
@ -11,7 +12,8 @@ class Message:
self.section = section
self.index = index
self.bytes = [] # XXX: bytearray would be better
self.bytes = [] # XXX: bytearray would be better
def try_convert_int(s):
try:
@ -19,10 +21,11 @@ def try_convert_int(s):
except:
return s
def parse_command(source):
if source[0] != "[":
return None, [], {}, source
source = source[1:] # "["
source = source[1:] # "["
inside_brackets = ""
while source[0] != "]":
@ -31,7 +34,7 @@ def parse_command(source):
inside_brackets += source[0]
source = source[1:]
source = source[1:] # "]"
source = source[1:] # "]"
command, *raw_args = inside_brackets.split(" ")
@ -58,6 +61,7 @@ def parse_command(source):
return command.lower(), args, named_args, source
def color_to_code(color, style):
COLORS = {
"diary": {
@ -90,26 +94,30 @@ def color_to_code(color, style):
"red": 0x19,
"blue": 0x1A,
"green": 0x1B,
}
},
}
if type(color) is int:
return color
return COLORS.get(style, {
# [style:left], [style:right]
"normal": 0x0A,
"red": 0x20,
"pink": 0x21,
"purple": 0x22,
"blue": 0x23,
"cyan": 0x24,
"green": 0x25,
"yellow": 0x26,
}).get(color)
return COLORS.get(
style,
{
# [style:left], [style:right]
"normal": 0x0A,
"red": 0x20,
"pink": 0x21,
"purple": 0x22,
"blue": 0x23,
"cyan": 0x24,
"green": 0x25,
"yellow": 0x26,
},
).get(color)
CHARSET = {
#"𝅘𝅥𝅮": 0x00,
# "𝅘𝅥𝅮": 0x00,
"!": 0x01,
'"': 0x02,
"#": 0x03,
@ -323,19 +331,22 @@ CHARSET_CREDITS = {
" ": 0xF7,
}
def strip_c_comments(text):
def replacer(match):
s = match.group(0)
if s.startswith('/'):
if s.startswith("/"):
return " "
else:
return s
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
re.DOTALL | re.MULTILINE
re.DOTALL | re.MULTILINE,
)
return re.sub(pattern, replacer, text)
if __name__ == "__main__":
if len(argv) < 3:
print("usage: parse_compile.py [in.msg] [out.msgpack] [--c]")
@ -435,7 +446,7 @@ if __name__ == "__main__":
print(f"{filename}:{lineno}: expected opening brace ('{{')")
exit(1)
source = source[1:] # {
source = source[1:] # {
# count indent level
indent_level = 0
@ -484,8 +495,8 @@ if __name__ == "__main__":
exit(1)
message.bytes += [0xFF, 0x05, color]
#color_stack.append(color)
#elif command == "/color":
# color_stack.append(color)
# elif command == "/color":
# color_stack.pop()
# message.bytes += [0xFF, 0x05, color_stack[0]]
elif command == "style":
@ -517,7 +528,13 @@ if __name__ == "__main__":
print(f"{filename}:{lineno}: 'choice' style requires size=_,_")
exit(1)
message.bytes += [0x05, pos[0], pos[1], size[0], size[1]]
message.bytes += [
0x05,
pos[0],
pos[1],
size[0],
size[1],
]
elif style == "inspect":
message.bytes += [0x06]
elif style == "sign":
@ -553,7 +570,13 @@ if __name__ == "__main__":
print(f"{filename}:{lineno}: 'upgrade' style requires size=_,_")
exit(1)
message.bytes += [0x0C, pos[0], pos[1], size[0], size[1]]
message.bytes += [
0x0C,
pos[0],
pos[1],
size[0],
size[1],
]
elif style == "narrate":
message.bytes += [0x0D]
elif style == "epilogue":
@ -579,7 +602,7 @@ if __name__ == "__main__":
exit(1)
message.bytes += [0xFF, 0x00, font]
#font_stack.append(font)
# font_stack.append(font)
if font == 3 or font == 4:
charset = CHARSET_CREDITS
@ -709,7 +732,13 @@ if __name__ == "__main__":
print(f"{filename}:{lineno}: {command} command requires raster=_")
exit(1)
message.bytes += [0xFF, 0x16, spriteid >> 8, spriteid & 0xFF, raster]
message.bytes += [
0xFF,
0x16,
spriteid >> 8,
spriteid & 0xFF,
raster,
]
elif command == "itemicon":
itemid = named_args.get("itemid")
@ -722,7 +751,7 @@ if __name__ == "__main__":
message.bytes += [0xFF, 0x17, itemid >> 8, itemid & 0xFF]
elif command == "image":
index = named_args.get("index")
pos = named_args.get("pos") # xx,y
pos = named_args.get("pos") # xx,y
hasborder = named_args.get("hasborder")
alpha = named_args.get("alpha")
fadeamount = named_args.get("fadeamount")
@ -743,7 +772,17 @@ if __name__ == "__main__":
print(f"{filename}:{lineno}: {command} command requires fadeamount=_")
exit(1)
message.bytes += [0xFF, 0x18, index, pos[0] >> 8, pos[0] & 0xFF, pos[1], hasborder, alpha, fadeamount]
message.bytes += [
0xFF,
0x18,
index,
pos[0] >> 8,
pos[0] & 0xFF,
pos[1],
hasborder,
alpha,
fadeamount,
]
elif command == "hideimage":
fadeamount = named_args.get("fadeamount", 0)
@ -933,9 +972,16 @@ if __name__ == "__main__":
exit(1)
message.bytes += [
0xFF, 0x2C,
soundids[0] >> 24, (soundids[0] >> 16) & 0xFF, (soundids[0] >> 8) & 0xFF, soundids[0] & 0xFF,
soundids[1] >> 24, (soundids[1] >> 16) & 0xFF, (soundids[1] >> 8) & 0xFF, soundids[1] & 0xFF,
0xFF,
0x2C,
soundids[0] >> 24,
(soundids[0] >> 16) & 0xFF,
(soundids[0] >> 8) & 0xFF,
soundids[0] & 0xFF,
soundids[1] >> 24,
(soundids[1] >> 16) & 0xFF,
(soundids[1] >> 8) & 0xFF,
soundids[1] & 0xFF,
]
elif command == "volume":
if len(args) != 1:
@ -962,70 +1008,184 @@ if __name__ == "__main__":
exit(1)
message.bytes += [0xFF, 0x2F, sound]
#sound_stack.append(sound)
# sound_stack.append(sound)
# elif command == "/sound":
# sound_stack.pop()
# message.bytes += [0xFF, 0x2F, sound_stack[0]]
elif command == "a":
color_code = color_to_code("blue", "button")
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x98, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x98,
0xFF,
0x25,
]
elif command == "b":
color_code = color_to_code(named_args.get("color", "green"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "green"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x99, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x99,
0xFF,
0x25,
]
elif command == "l":
color_code = color_to_code(named_args.get("color", "gray"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "gray"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9A, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9A,
0xFF,
0x25,
]
elif command == "r":
color_code = color_to_code(named_args.get("color", "gray"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "gray"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9B, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9B,
0xFF,
0x25,
]
elif command == "z":
color_code = color_to_code("grey", "button")
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9C, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9C,
0xFF,
0x25,
]
elif command == "c-up":
color_code = color_to_code(named_args.get("color", "yellow"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9D, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9D,
0xFF,
0x25,
]
elif command == "c-down":
color_code = color_to_code(named_args.get("color", "yellow"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9E, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9E,
0xFF,
0x25,
]
elif command == "c-left":
color_code = color_to_code(named_args.get("color", "yellow"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0x9F, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9F,
0xFF,
0x25,
]
elif command == "c-right":
color_code = color_to_code(named_args.get("color", "yellow"), named_args.get("ctx", "button"))
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0xA0, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0xA0,
0xFF,
0x25,
]
elif command == "start":
color_code = color_to_code(named_args.get("color", "red"), named_args.get("ctx", "button"))#
color_code = color_to_code(
named_args.get("color", "red"),
named_args.get("ctx", "button"),
) #
assert color_code is not None
message.bytes += [0xFF, 0x24, 0xFF, 0x05, color_code, 0xA1, 0xFF, 0x25]
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0xA1,
0xFF,
0x25,
]
elif command == "~a":
message.bytes += [0x98]
elif command == "~b":
message.bytes += [0x99]
elif command == "~l":
message.bytes += [0x9a]
message.bytes += [0x9A]
elif command == "~r":
message.bytes += [0x9b]
message.bytes += [0x9B]
elif command == "~z":
message.bytes += [0x9c]
message.bytes += [0x9C]
elif command == "~c-up":
message.bytes += [0x9d]
message.bytes += [0x9D]
elif command == "~c-down":
message.bytes += [0x9e]
message.bytes += [0x9E]
elif command == "~c-left":
message.bytes += [0x9f]
message.bytes += [0x9F]
elif command == "~c-right":
message.bytes += [0xa0]
message.bytes += [0xA0]
elif command == "~start":
message.bytes += [0xa1]
message.bytes += [0xA1]
elif command == "note":
message.bytes += [0x00]
elif command == "heart":
@ -1043,24 +1203,24 @@ if __name__ == "__main__":
elif command == "restorepos":
message.bytes += [0xFF, 0x23]
elif command == "enablecdownnext":
message.bytes += [0xFF, 0x2b]
message.bytes += [0xFF, 0x2B]
elif command == "beginchoice":
choiceindex = 0
message.bytes += [0xFF, 0x09] # delayoff
message.bytes += [0xFF, 0x09] # delayoff
elif command == "option" and choiceindex >= 0:
message.bytes += [0xFF, 0x1E, choiceindex] # cursor n
message.bytes += [0xFF, 0x21, choiceindex] # option n
message.bytes += [0xFF, 0x1E, choiceindex] # cursor n
message.bytes += [0xFF, 0x21, choiceindex] # option n
choiceindex += 1
elif command == "endchoice" and choiceindex >= 0:
cancel = named_args.get("cancel")
message.bytes += [0xFF, 0x21, 255] # option 255
message.bytes += [0xFF, 0x0A] # delayon
message.bytes += [0xFF, 0x21, 255] # option 255
message.bytes += [0xFF, 0x0A] # delayon
if isinstance(cancel, int):
message.bytes += [0xFF, 0x20, cancel] # setcancel n
message.bytes += [0xFF, 0x20, cancel] # setcancel n
message.bytes += [0xFF, 0x1F, choiceindex] # endchoice n
message.bytes += [0xFF, 0x1F, choiceindex] # endchoice n
choiceindex = -1
elif command == "animation" and choiceindex >= 0:
@ -1074,7 +1234,7 @@ if __name__ == "__main__":
if source[0] == "}":
if not explicit_end:
print(f"{filename}:{lineno}: warning: string lacks an [end] command")
#message.bytes += [0xFD]
# message.bytes += [0xFD]
explicit_end = False
# sanity check
@ -1087,7 +1247,7 @@ if __name__ == "__main__":
message.bytes += [0x00]
message = None
source = source[1:] # }
source = source[1:] # }
indent_level = 0
choiceindex = -1
continue
@ -1124,9 +1284,15 @@ if __name__ == "__main__":
else:
with open(outfile, "wb") as f:
msgpack.pack([{
"section": message.section,
"index": message.index,
"name": message.name,
"bytes": bytes(message.bytes),
} for message in messages], f)
msgpack.pack(
[
{
"section": message.section,
"index": message.index,
"name": message.name,
"bytes": bytes(message.bytes),
}
for message in messages
],
f,
)

View File

@ -10,8 +10,8 @@ if __name__ == "__main__":
f.write(f"unsigned short {cname}[] = {{ ")
with open(infile, "rb") as i:
while (short := i.read(2)):
color = struct.unpack('>H', short)[0]
f.write(f'0x{color:04X}, ')
while short := i.read(2):
color = struct.unpack(">H", short)[0]
f.write(f"0x{color:04X}, ")
f.write("};\n")

View File

@ -3,7 +3,7 @@
from sys import argv
if __name__ == "__main__":
argv.pop(0) # python3
argv.pop(0) # python3
out = argv.pop(0)
with open(out, "wb") as f:
@ -11,4 +11,4 @@ if __name__ == "__main__":
with open(path, "rb") as j:
f.write(j.read())
while f.tell() % 8 != 0:
f.write(b'\0')
f.write(b"\0")

View File

@ -3,7 +3,7 @@
from sys import argv
if __name__ == "__main__":
argv.pop(0) # python3
argv.pop(0) # python3
out = argv.pop(0)
with open(out, "wb") as f:

View File

@ -9,6 +9,7 @@ import xml.etree.ElementTree as ET
from img.build import Converter
import png
def get_img_file(fmt_str, img_file: str):
def pack_color(r, g, b, a):
r = r >> 3
@ -34,6 +35,7 @@ def get_img_file(fmt_str, img_file: str):
return (out_img, out_pal, out_w, out_h)
def build(out_bin: Path, in_xml: Path, out_header: Path, asset_stack: Tuple[Path, ...]):
out_bytes = bytearray()
offsets: Dict[str, int] = {}
@ -47,11 +49,11 @@ def build(out_bin: Path, in_xml: Path, out_header: Path, asset_stack: Tuple[Path
if file is None:
raise Exception("Icon os missing attribute: 'name'")
if type is None:
raise Exception("Icon os missing attribute: 'type'")
name = re.sub("\\W","_",file)
name = re.sub("\\W", "_", file)
if type == "solo" or type == "pair":
img_path = str(get_asset_path(Path(f"icon/{file}.png"), asset_stack))
@ -66,7 +68,7 @@ def build(out_bin: Path, in_xml: Path, out_header: Path, asset_stack: Tuple[Path
if type == "pair":
img_path = str(get_asset_path(Path(f"icon/{file}.disabled.png"), asset_stack))
(out_img, out_pal, out_w, out_h) = get_img_file("CI4", str(img_path))
offsets[name + "_disabled_raster"] = offsets[name + "_raster"]
offsets[name + "_disabled_palette"] = len(out_bytes)
out_bytes += out_pal
@ -88,12 +90,13 @@ def build(out_bin: Path, in_xml: Path, out_header: Path, asset_stack: Tuple[Path
f.write("#ifndef ICON_OFFSETS_H\n")
f.write("#define ICON_OFFSETS_H\n")
f.write(f"/* This file is auto-generated. Do not edit. */\n\n")
for name, offset in offsets.items():
f.write(f"#define ICON_{name} 0x{offset:X}\n")
f.write("\n#endif // ICON_OFFSETS_H\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Icon archive")
parser.add_argument("out_bin", type=Path, help="output binary file path")

View File

@ -50,13 +50,9 @@ if __name__ == "__main__":
for p, palette_name in enumerate(sprite.palette_names):
for a, name in enumerate(sprite.animation_names):
if palette_name == "Default":
f.write(
f"#define ANIM_{sprite_name}_{name} 0x{s:02X}{p:02X}{a:02X}\n"
)
f.write(f"#define ANIM_{sprite_name}_{name} 0x{s:02X}{p:02X}{a:02X}\n")
else:
f.write(
f"#define ANIM_{sprite_name}_{palette_name}_{name} 0x{s:02X}{p:02X}{a:02X}\n"
)
f.write(f"#define ANIM_{sprite_name}_{palette_name}_{name} 0x{s:02X}{p:02X}{a:02X}\n")
f.write("\n")
f.write("#endif\n")

View File

@ -76,9 +76,7 @@ def from_dir(
palettes.append(palette)
palette_names.append(
Palette.get("name", Palette.attrib["src"].split(".png")[0])
)
palette_names.append(Palette.get("name", Palette.attrib["src"].split(".png")[0]))
images = []
image_names: List[str] = []
@ -192,9 +190,7 @@ if __name__ == "__main__":
palette_offsets.append(f.tell())
for rgba in palette:
if rgba[3] not in (0, 0xFF):
print(
"error: translucent pixels not allowed in palette {sprite.palette_names[i]}"
)
print("error: translucent pixels not allowed in palette {sprite.palette_names[i]}")
exit(1)
color = pack_color(*rgba)

View File

@ -8,11 +8,13 @@ from pathlib import Path
import struct
from typing import List, Literal
class LightMode(Enum):
UNIFORM = 0
LINEAR = 4
QUADRATIC = 8
@dataclass
class Light:
flags: int
@ -83,9 +85,14 @@ def groups_from_json(data) -> List[SpriteShadingGroup]:
)
return groups
def build(input: Path, bin_out: Path, header_out: Path, endian: Literal["big", "little"]="big",
matching: bool = True):
def build(
input: Path,
bin_out: Path,
header_out: Path,
endian: Literal["big", "little"] = "big",
matching: bool = True,
):
END = ">" if endian == "big" else "<"
with open(input, "r") as f:
@ -145,11 +152,11 @@ def build(input: Path, bin_out: Path, header_out: Path, endian: Literal["big", "
offsets_table.extend(profile_lists)
if matching:
offsets_table += b'\0' * (0x1D0 - len(offsets_table)) # Pad to 0x1D0
offsets_table += b"\0" * (0x1D0 - len(offsets_table)) # Pad to 0x1D0
final_data = offsets_table + data_table
if matching:
final_data += b'\0' * (0xE70 - len(final_data)) # Pad to 0xE70
final_data += b"\0" * (0xE70 - len(final_data)) # Pad to 0xE70
with open(bin_out, "wb") as f:
f.write(final_data)

View File

@ -33,9 +33,7 @@ import xml.etree.ElementTree as ET
from dataclasses import dataclass
from typing import Dict, List, Tuple
TOOLS_DIR = Path(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
)
TOOLS_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
sys.path.append(str(TOOLS_DIR))
ASSET_DIR = TOOLS_DIR.parent / "assets"
@ -52,11 +50,7 @@ def pack_color(r, g, b, a) -> int:
def get_player_sprite_metadata(
asset_stack: Tuple[Path, ...],
) -> Tuple[str, List[str], List[str]]:
orderings_tree = ET.parse(
get_asset_path(
Path("sprite") / PLAYER_SPRITE_MEDADATA_XML_FILENAME, asset_stack
)
)
orderings_tree = ET.parse(get_asset_path(Path("sprite") / PLAYER_SPRITE_MEDADATA_XML_FILENAME, asset_stack))
build_info = str(orderings_tree.getroot()[0].text)
@ -72,9 +66,7 @@ def get_player_sprite_metadata(
def get_npc_sprite_metadata(asset_stack: Tuple[Path, ...]) -> List[str]:
orderings_tree = ET.parse(
get_asset_path(Path("sprite") / NPC_SPRITE_MEDADATA_XML_FILENAME, asset_stack)
)
orderings_tree = ET.parse(get_asset_path(Path("sprite") / NPC_SPRITE_MEDADATA_XML_FILENAME, asset_stack))
sprite_order: List[str] = []
for sprite_tag in orderings_tree.getroot()[0]:
@ -100,18 +92,14 @@ PALETTE_CACHE: Dict[str, bytes] = {}
PLAYER_XML_CACHE: Dict[str, ET.Element] = {}
# TODO perhaps encode this better
SPECIAL_RASTER_BYTES = (
b"\x80\x30\x02\x10\x00\x00\x02\x00\x00\x00\x00\x01\x00\x10\x00\x00"
)
SPECIAL_RASTER_BYTES = b"\x80\x30\x02\x10\x00\x00\x02\x00\x00\x00\x00\x01\x00\x10\x00\x00"
def cache_player_rasters(raster_order: List[str], asset_stack: Tuple[Path, ...]):
# Read all player rasters and cache them
cur_offset = 0
for raster_name in raster_order:
png_path = get_asset_path(
Path(f"sprite/player/rasters/{raster_name}.png"), asset_stack
)
png_path = get_asset_path(Path(f"sprite/player/rasters/{raster_name}.png"), asset_stack)
# "Weird" raster
if os.path.getsize(png_path) == 0x10:
@ -232,9 +220,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
source = palette_xml.attrib["src"]
front_only = bool(palette_xml.get("front_only", False))
if source not in PALETTE_CACHE:
palette_path = get_asset_path(
Path(f"sprite/player/palettes/{source}"), asset_stack
)
palette_path = get_asset_path(Path(f"sprite/player/palettes/{source}"), asset_stack)
with open(palette_path, "rb") as f:
img = png.Reader(f)
img.preamble(True)
@ -270,9 +256,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
if "back" in raster_xml.attrib:
has_back = True
r = player_raster_from_xml(raster_xml, back=False)
raster_bytes += struct.pack(
">IBBBB", raster_offset, r.width, r.height, r.palette_idx, 0xFF
)
raster_bytes += struct.pack(">IBBBB", raster_offset, r.width, r.height, r.palette_idx, 0xFF)
raster_offset += r.width * r.height // 2
@ -292,9 +276,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
height = int(special[1], 16)
palette = int(raster_xml.attrib.get(BACK_PALETTE_XML, r.palette_idx))
raster_bytes_back += struct.pack(
">IBBBB", raster_offset, width, height, palette, 0xFF
)
raster_bytes_back += struct.pack(">IBBBB", raster_offset, width, height, palette, 0xFF)
if is_back:
raster_offset += width * height // 2
@ -312,9 +294,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
raster_offsets_bytes_back = b""
for i in range(len(xml[1])):
raster_offsets_bytes += int.to_bytes(raster_list_start + i * 8, 4, "big")
raster_offsets_bytes_back += int.to_bytes(
raster_list_start_back + i * 8, 4, "big"
)
raster_offsets_bytes_back += int.to_bytes(raster_list_start_back + i * 8, 4, "big")
raster_offsets_bytes += LIST_END_BYTES
raster_offsets_bytes_back += LIST_END_BYTES
@ -330,9 +310,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
palette_offsets_bytes += int.to_bytes(palette_list_start + i * 0x20, 4, "big")
front_only = bool(palette_xml.attrib.get("front_only", False))
if not front_only:
palette_offsets_bytes_back += int.to_bytes(
palette_list_start_back + i * 0x20, 4, "big"
)
palette_offsets_bytes_back += int.to_bytes(palette_list_start_back + i * 0x20, 4, "big")
palette_offsets_bytes += LIST_END_BYTES
palette_offsets_bytes_back += LIST_END_BYTES
@ -397,9 +375,7 @@ def write_player_sprite_header(
for palette_xml in sprite_xml[0]:
palette_id = int(palette_xml.attrib["id"], 0x10)
palette_name = palette_xml.attrib["name"]
player_palettes[sprite_name][
f"SPR_PAL_{sprite_name}_{palette_name}"
] = palette_id
player_palettes[sprite_name][f"SPR_PAL_{sprite_name}_{palette_name}"] = palette_id
for anim_id, anim_xml in enumerate(sprite_xml[2]):
anim_name = anim_xml.attrib["name"]
@ -413,9 +389,7 @@ def write_player_sprite_header(
for raster_xml in sprite_xml[1]:
raster_id = int(raster_xml.attrib["id"], 0x10)
raster_name = raster_xml.attrib["name"]
player_rasters[sprite_name][
f"SPR_IMG_{sprite_name}_{raster_name}"
] = raster_id
player_rasters[sprite_name][f"SPR_IMG_{sprite_name}_{raster_name}"] = raster_id
raster = RASTER_CACHE[raster_xml.attrib["src"][:-4]]
if max_size < raster.size:
@ -449,9 +423,7 @@ def write_player_sprite_header(
f.write("};\n\n")
for sprite_name in max_sprite_sizes:
f.write(
f"#define MAX_IMG_{sprite_name} 0x{max_sprite_sizes[sprite_name]:04X}\n"
)
f.write(f"#define MAX_IMG_{sprite_name} 0x{max_sprite_sizes[sprite_name]:04X}\n")
f.write("\n")
for sprite_name in sprite_order:
@ -472,15 +444,11 @@ def write_player_sprite_header(
f.write(f"#endif // {ifdef_name}\n")
def build_player_sprites(
sprite_order: List[str], build_dir: Path, asset_stack: Tuple[Path, ...]
) -> bytes:
def build_player_sprites(sprite_order: List[str], build_dir: Path, asset_stack: Tuple[Path, ...]) -> bytes:
sprite_bytes: List[bytes] = []
for sprite_name in sprite_order:
sprite_bytes.extend(
player_xml_to_bytes(PLAYER_XML_CACHE[sprite_name], asset_stack)
)
sprite_bytes.extend(player_xml_to_bytes(PLAYER_XML_CACHE[sprite_name], asset_stack))
# Compress sprite bytes
compressed_sprite_bytes: bytes = b""
@ -560,9 +528,7 @@ def build_player_rasters(sprite_order: List[str], raster_order: List[str]) -> by
if has_back:
if "back" in raster_xml.attrib:
png_info = RASTER_CACHE[raster_xml.attrib["back"][:-4]]
sheet_rtes_back.append(
RasterTableEntry(png_info.offset, png_info.size)
)
sheet_rtes_back.append(RasterTableEntry(png_info.offset, png_info.size))
else:
sheet_rtes_back.append(RasterTableEntry(SPECIAL_RASTER, 0x10))
@ -623,27 +589,21 @@ def build(
build_dir: Path,
asset_stack: Tuple[Path, ...],
) -> None:
build_info, player_sprite_order, player_raster_order = get_player_sprite_metadata(
asset_stack
)
build_info, player_sprite_order, player_raster_order = get_player_sprite_metadata(asset_stack)
npc_sprite_order = get_npc_sprite_metadata(asset_stack)
cache_player_rasters(player_raster_order, asset_stack)
# Read and cache player XMLs
for sprite_name in player_sprite_order:
sprite_xml = ET.parse(
get_asset_path(Path(f"sprite/player/{sprite_name}.xml"), asset_stack)
).getroot()
sprite_xml = ET.parse(get_asset_path(Path(f"sprite/player/{sprite_name}.xml"), asset_stack)).getroot()
PLAYER_XML_CACHE[sprite_name] = sprite_xml
# Encode build_info to bytes and pad to 0x10
build_info_bytes = build_info.encode("ascii")
build_info_bytes += b"\0" * (0x10 - len(build_info_bytes))
player_sprite_bytes = build_player_sprites(
player_sprite_order, build_dir / "player", asset_stack
)
player_sprite_bytes = build_player_sprites(player_sprite_order, build_dir / "player", asset_stack)
player_raster_bytes = build_player_rasters(player_sprite_order, player_raster_order)
npc_sprite_bytes = build_npc_sprites(npc_sprite_order, build_dir)
@ -659,13 +619,7 @@ def build(
npc_sprites_offset,
)
final_data = (
build_info_bytes
+ major_file_divisons
+ player_raster_bytes
+ player_sprite_bytes
+ npc_sprite_bytes
)
final_data = build_info_bytes + major_file_divisons + player_raster_bytes + player_sprite_bytes + npc_sprite_bytes
with open(out_file, "wb") as f:
f.write(final_data)

View File

@ -12,9 +12,7 @@ for root, dirs, files in os.walk("assets/us/mapfs/geom"):
if file.endswith("_shape.bin"):
total += 1
shape_file = os.path.join(root, file)
built_data_file = Path("ver/us/build") / shape_file.replace(
"_shape.bin", "_shape_data.bin"
)
built_data_file = Path("ver/us/build") / shape_file.replace("_shape.bin", "_shape_data.bin")
if filecmp.cmp(shape_file, built_data_file, shallow=False):
matching += 1

View File

@ -2,53 +2,61 @@
import struct
def read(f):
return struct.unpack('>h', f.read(2))[0]
return struct.unpack(">h", f.read(2))[0]
def i2f(x):
return round(x * 180 / 32767 * 200) / 200
def parse(f):
print('AnimScript script = {')
indent = ' '
print("AnimScript script = {")
indent = " "
while True:
op = read(f)
if op == 0:
print(f'{indent}AS_END,')
print(f"{indent}AS_END,")
break
if op == 1:
print(f'{indent}AS_WAIT, {read(f)},')
print(f"{indent}AS_WAIT, {read(f)},")
elif op == 3:
indent = indent[:-4]
print(f'{indent}AS_END_LOOP,')
print(f"{indent}AS_END_LOOP,")
elif op == 5:
print(f'{indent}AS_SET_ROTATION, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),')
print(
f"{indent}AS_SET_ROTATION, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),"
)
elif op == 6:
print(f'{indent}AS_ADD_ROTATION, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),')
print(
f"{indent}AS_ADD_ROTATION, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),"
)
elif op == 8:
print(f'{indent}AS_SET_POS, {read(f)}, {read(f)}, {read(f)}, {read(f)},')
print(f"{indent}AS_SET_POS, {read(f)}, {read(f)}, {read(f)}, {read(f)},")
elif op == 10:
print(f'{indent}AS_LOOP,')
indent += ' '
print(f"{indent}AS_LOOP,")
indent += " "
elif op == 14:
print(f'{indent}AS_SET_FLAGS, {read(f)},')
print(f"{indent}AS_SET_FLAGS, {read(f)},")
elif op == 15:
print(f'{indent}AS_SET_NODE_FLAGS, {read(f)}, {read(f)},')
print(f"{indent}AS_SET_NODE_FLAGS, {read(f)}, {read(f)},")
elif op == 16:
print(f'{indent}AS_CLEAR_NODE_FLAGS, {read(f)}, {read(f)},')
print(f"{indent}AS_CLEAR_NODE_FLAGS, {read(f)}, {read(f)},")
elif op == 17:
print(f'{indent}AS_SET_SCALE, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),')
print(f"{indent}AS_SET_SCALE, {read(f)}, AS_F({i2f(read(f))}), AS_F({i2f(read(f))}), AS_F({i2f(read(f))}),")
elif op == 18:
print(f'{indent}AS_SET_RENDER_MODE, {read(f)},')
print(f"{indent}AS_SET_RENDER_MODE, {read(f)},")
elif op == 19:
print(f'{indent}AS_OP_19,')
print(f"{indent}AS_OP_19,")
else:
raise Exception(str(f'Unknown opcode {op}'))
print('};')
raise Exception(str(f"Unknown opcode {op}"))
print("};")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("file", type=str, help="File to dissassemble from")
parser.add_argument("offset", help="Offset to start dissassembling from")

View File

@ -3,62 +3,63 @@
import struct
import argparse
def fmt_size(val):
if val == 0:
return 'HUD_ELEMENT_SIZE_8x8'
return "HUD_ELEMENT_SIZE_8x8"
elif val == 1:
return 'HUD_ELEMENT_SIZE_16x16'
return "HUD_ELEMENT_SIZE_16x16"
elif val == 2:
return 'HUD_ELEMENT_SIZE_24x24'
return "HUD_ELEMENT_SIZE_24x24"
elif val == 3:
return 'HUD_ELEMENT_SIZE_32x32'
return "HUD_ELEMENT_SIZE_32x32"
elif val == 4:
return 'HUD_ELEMENT_SIZE_48x48'
return "HUD_ELEMENT_SIZE_48x48"
elif val == 5:
return 'HUD_ELEMENT_SIZE_64x64'
return "HUD_ELEMENT_SIZE_64x64"
elif val == 6:
return 'HUD_ELEMENT_SIZE_8x16'
return "HUD_ELEMENT_SIZE_8x16"
elif val == 7:
return 'HUD_ELEMENT_SIZE_16x8'
return "HUD_ELEMENT_SIZE_16x8"
elif val == 8:
return 'HUD_ELEMENT_SIZE_16x24'
return "HUD_ELEMENT_SIZE_16x24"
elif val == 9:
return 'HUD_ELEMENT_SIZE_16x32'
return "HUD_ELEMENT_SIZE_16x32"
elif val == 10:
return 'HUD_ELEMENT_SIZE_64x32'
return "HUD_ELEMENT_SIZE_64x32"
elif val == 11:
return 'HUD_ELEMENT_SIZE_32x16'
return "HUD_ELEMENT_SIZE_32x16"
elif val == 12:
return 'HUD_ELEMENT_SIZE_12x12'
return "HUD_ELEMENT_SIZE_12x12"
elif val == 13:
return 'HUD_ELEMENT_SIZE_48x24'
return "HUD_ELEMENT_SIZE_48x24"
elif val == 14:
return 'HUD_ELEMENT_SIZE_32x8'
return "HUD_ELEMENT_SIZE_32x8"
elif val == 15:
return 'HUD_ELEMENT_SIZE_24x8'
return "HUD_ELEMENT_SIZE_24x8"
elif val == 16:
return 'HUD_ELEMENT_SIZE_64x16'
return "HUD_ELEMENT_SIZE_64x16"
elif val == 17:
return 'HUD_ELEMENT_SIZE_16x64'
return "HUD_ELEMENT_SIZE_16x64"
elif val == 18:
return 'HUD_ELEMENT_SIZE_192x32'
return "HUD_ELEMENT_SIZE_192x32"
elif val == 19:
return 'HUD_ELEMENT_SIZE_40x40'
return "HUD_ELEMENT_SIZE_40x40"
elif val == 20:
return 'HUD_ELEMENT_SIZE_24x16'
return "HUD_ELEMENT_SIZE_24x16"
elif val == 21:
return 'HUD_ELEMENT_SIZE_32x40'
return "HUD_ELEMENT_SIZE_32x40"
elif val == 22:
return 'HUD_ELEMENT_SIZE_40x16'
return "HUD_ELEMENT_SIZE_40x16"
elif val == 23:
return 'HUD_ELEMENT_SIZE_40x24'
return "HUD_ELEMENT_SIZE_40x24"
elif val == 24:
return 'HUD_ELEMENT_SIZE_32x24'
return "HUD_ELEMENT_SIZE_32x24"
else:
return val
class HudElementScript():
class HudElementScript:
def __init__(self, symbol):
self.symbol = symbol
self.buffer = []
@ -172,7 +173,7 @@ if __name__ == "__main__":
if word > 0x8000000:
word = f"0x{word:X}"
else:
word, = struct.unpack(">i", struct.pack(">I", word))
(word,) = struct.unpack(">i", struct.pack(">I", word))
print(word)
except ValueError:
pass

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,7 @@ def get_symbol_bytes(offsets, func):
for ins in insns:
ret.append(ins >> 2)
return bytes(ret).decode('utf-8'), bs
return bytes(ret).decode("utf-8"), bs
def parse_map(fname):
@ -80,12 +80,7 @@ def parse_map(fname):
continue
prev_line = line
if (
ram_offset is None
or "=" in line
or "*fill*" in line
or " 0x" not in line
):
if ram_offset is None or "=" in line or "*fill*" in line or " 0x" not in line:
continue
ram = int(line[16 : 16 + 18], 0)
rom = ram - ram_offset
@ -177,7 +172,7 @@ def do_query(query):
break
match_str = "{:.3f} - {}".format(matches[match], match)
if match not in s_files:
match_str += " (decompiled)"
match_str += " (decompiled)"
print(match_str)
i += 1
print()
@ -203,7 +198,10 @@ def all_matches(all_funcs_flag):
file = to_match_files[0]
i += 1
print("File matching progress: {:%}".format(i / (len(s_files) - iter_limit)), end='\r')
print(
"File matching progress: {:%}".format(i / (len(s_files) - iter_limit)),
end="\r",
)
if get_symbol_length(file) < 16:
to_match_files.remove(file)
@ -241,18 +239,26 @@ def all_matches(all_funcs_flag):
output_match_dict(match_dict, num_decomped_dupes, num_undecomped_dupes, num_perfect_dupes, i)
def output_match_dict(match_dict, num_decomped_dupes, num_undecomped_dupes, num_perfect_dupes, num_checked_files):
out_file = open(datetime.today().strftime('%Y-%m-%d-%H-%M-%S') + "_all_matches.txt", "w+")
def output_match_dict(
match_dict,
num_decomped_dupes,
num_undecomped_dupes,
num_perfect_dupes,
num_checked_files,
):
out_file = open(datetime.today().strftime("%Y-%m-%d-%H-%M-%S") + "_all_matches.txt", "w+")
out_file.write("Number of s-files: " + str(len(s_files)) + "\n"
"Number of checked s-files: " + str(round(num_checked_files)) + "\n"
"Number of decompiled duplicates found: " + str(num_decomped_dupes) + "\n"
"Number of undecompiled duplicates found: " + str(num_undecomped_dupes) + "\n"
"Number of overall exact duplicates found: " + str(num_perfect_dupes) + "\n\n")
out_file.write(
"Number of s-files: " + str(len(s_files)) + "\n"
"Number of checked s-files: " + str(round(num_checked_files)) + "\n"
"Number of decompiled duplicates found: " + str(num_decomped_dupes) + "\n"
"Number of undecompiled duplicates found: " + str(num_undecomped_dupes) + "\n"
"Number of overall exact duplicates found: " + str(num_perfect_dupes) + "\n\n"
)
sorted_dict = OrderedDict(sorted(match_dict.items(), key=lambda item: item[1][0], reverse=True))
print("Creating output file: " + out_file.name, end='\n')
print("Creating output file: " + out_file.name, end="\n")
for file_name, matches in sorted_dict.items():
out_file.write(file_name + " - found " + str(matches[0]) + " matches total:\n")
for match in matches[1]:
@ -261,19 +267,23 @@ def output_match_dict(match_dict, num_decomped_dupes, num_undecomped_dupes, num_
out_file.close()
def is_decompiled(sym):
return sym not in s_files
def do_cross_query():
ccount = Counter()
clusters = []
sym_bytes = {}
for sym_name in map_syms:
if not sym_name.startswith("D_") and \
not sym_name.startswith("_binary") and \
not sym_name.startswith("jtbl_") and \
not re.match(r"L[0-9A-F]{8}_[0-9A-F]{5,6}", sym_name):
if (
not sym_name.startswith("D_")
and not sym_name.startswith("_binary")
and not sym_name.startswith("jtbl_")
and not re.match(r"L[0-9A-F]{8}_[0-9A-F]{5,6}", sym_name)
):
if get_symbol_length(sym_name) > 16:
sym_bytes[sym_name] = get_symbol_bytes(map_offsets, sym_name)
@ -303,14 +313,48 @@ def do_cross_query():
print(ccount.most_common(100))
parser = argparse.ArgumentParser(description="Tool to find duplicates for a specific function or to find all duplicates across the codebase.")
parser = argparse.ArgumentParser(
description="Tool to find duplicates for a specific function or to find all duplicates across the codebase."
)
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "--all", help="find ALL duplicates and output them into a file", action='store_true', required=False)
group.add_argument("-c", "--cross", help="do a cross query over the codebase", action='store_true', required=False)
group.add_argument("-s", "--short", help="find MOST duplicates besides some very small duplicates. Cuts the runtime in half with minimal loss", action='store_true', required=False)
parser.add_argument("query", help="function or file", nargs='?', default=None)
parser.add_argument("-t", "--threshold", help="score threshold between 0 and 1 (higher is more restrictive)", type=float, default=0.9, required=False)
parser.add_argument("-n", "--num-out", help="number of functions to display", type=int, default=100, required=False)
group.add_argument(
"-a",
"--all",
help="find ALL duplicates and output them into a file",
action="store_true",
required=False,
)
group.add_argument(
"-c",
"--cross",
help="do a cross query over the codebase",
action="store_true",
required=False,
)
group.add_argument(
"-s",
"--short",
help="find MOST duplicates besides some very small duplicates. Cuts the runtime in half with minimal loss",
action="store_true",
required=False,
)
parser.add_argument("query", help="function or file", nargs="?", default=None)
parser.add_argument(
"-t",
"--threshold",
help="score threshold between 0 and 1 (higher is more restrictive)",
type=float,
default=0.9,
required=False,
)
parser.add_argument(
"-n",
"--num-out",
help="number of functions to display",
type=int,
default=100,
required=False,
)
args = parser.parse_args()

View File

@ -60,14 +60,10 @@ def get_all_unmatched_functions():
def get_func_sizes() -> Dict[str, int]:
try:
result = subprocess.run(
["mips-linux-gnu-objdump", "-x", elf_path], stdout=subprocess.PIPE
)
result = subprocess.run(["mips-linux-gnu-objdump", "-x", elf_path], stdout=subprocess.PIPE)
nm_lines = result.stdout.decode().split("\n")
except:
print(
f"Error: Could not run objdump on {elf_path} - make sure that the project is built"
)
print(f"Error: Could not run objdump on {elf_path} - make sure that the project is built")
sys.exit(1)
sizes: Dict[str, int] = {}
@ -127,12 +123,7 @@ def parse_map() -> OrderedDict[str, Symbol]:
continue
prev_line = line
if (
ram_offset is None
or "=" in line
or "*fill*" in line
or " 0x" not in line
):
if ram_offset is None or "=" in line or "*fill*" in line or " 0x" not in line:
continue
ram = int(line[16 : 16 + 18], 0)
rom = ram - ram_offset
@ -240,9 +231,7 @@ def group_matches(
continue
if max is not None and query_start > max:
continue
if contains is not None and (
query_start > contains or query_start + length < contains
):
if contains is not None and (query_start > contains or query_start + length < contains):
continue
ret.append(Result(query, target, query_start, target_start, length))
@ -291,11 +280,7 @@ def get_line_numbers(obj_file: Path) -> Dict[int, int]:
def get_tu_offset(obj_file: Path, symbol: str) -> Optional[int]:
objdump = "mips-linux-gnu-objdump"
objdump_out = (
subprocess.run([objdump, "-t", obj_file], stdout=subprocess.PIPE)
.stdout.decode("utf-8")
.split("\n")
)
objdump_out = subprocess.run([objdump, "-t", obj_file], stdout=subprocess.PIPE).stdout.decode("utf-8").split("\n")
if not objdump_out:
return None
@ -398,9 +383,7 @@ def get_matches(
if not matches:
continue
results: list[Result] = group_matches(
query, symbol, matches, window_size, min, max, contains
)
results: list[Result] = group_matches(query, symbol, matches, window_size, min, max, contains)
if not results:
continue
@ -427,12 +410,12 @@ def get_matches(
target_range_str = ""
if c_range:
target_range_str = (
fg.li_cyan + f" (line {c_range} in {obj_file.stem})" + fg.rs
)
target_range_str = fg.li_cyan + f" (line {c_range} in {obj_file.stem})" + fg.rs
query_str = f"query [{result.query_start}-{result.query_end}]"
target_str = f"{symbol} [insn {result.target_start}-{result.target_end}] ({result.length} total){target_range_str}"
target_str = (
f"{symbol} [insn {result.target_start}-{result.target_end}] ({result.length} total){target_range_str}"
)
print(f"\t{query_str} matches {target_str}")
if show_disasm:
@ -441,20 +424,12 @@ def get_matches(
except ImportError:
print("rabbitizer not found, cannot show disassembly")
sys.exit(1)
result_query_bytes = query_bytes.bytes[
result.query_start * 4 : result.query_end * 4
]
result_target_bytes = sym_bytes.bytes[
result.target_start * 4 : result.target_end * 4
]
result_query_bytes = query_bytes.bytes[result.query_start * 4 : result.query_end * 4]
result_target_bytes = sym_bytes.bytes[result.target_start * 4 : result.target_end * 4]
for i in range(0, len(result_query_bytes), 4):
q_insn = rabbitizer.Instruction(
int.from_bytes(result_query_bytes[i : i + 4], "big")
)
t_insn = rabbitizer.Instruction(
int.from_bytes(result_target_bytes[i : i + 4], "big")
)
q_insn = rabbitizer.Instruction(int.from_bytes(result_query_bytes[i : i + 4], "big"))
t_insn = rabbitizer.Instruction(int.from_bytes(result_target_bytes[i : i + 4], "big"))
print(f"\t\t{q_insn.disassemble():35} | {t_insn.disassemble()}")

View File

@ -2,20 +2,32 @@
import sys
def get_variable(arg):
v = arg - 2**32 # convert to s32
v = arg - 2**32 # convert to s32
if v > -250000000:
if v <= -220000000: return f"EVT_FLOAT({(v + 230000000) / 1024})"
elif v <= -200000000: return f"ArrayFlag({v + 210000000})"
elif v <= -180000000: return f"ArrayVar({v + 190000000})"
elif v <= -160000000: return f"GameByte({v + 170000000})"
elif v <= -140000000: return f"AreaByte({v + 150000000})"
elif v <= -120000000: return f"GameFlag({v + 130000000})"
elif v <= -100000000: return f"AreaFlag({v + 110000000})"
elif v <= -80000000: return f"MapFlag({v + 90000000})"
elif v <= -60000000: return f"LocalFlag({v + 70000000})"
elif v <= -40000000: return f"MapVar({v + 50000000})"
elif v <= -20000000: return f"LocalVar({v + 30000000})"
if v <= -220000000:
return f"EVT_FLOAT({(v + 230000000) / 1024})"
elif v <= -200000000:
return f"ArrayFlag({v + 210000000})"
elif v <= -180000000:
return f"ArrayVar({v + 190000000})"
elif v <= -160000000:
return f"GameByte({v + 170000000})"
elif v <= -140000000:
return f"AreaByte({v + 150000000})"
elif v <= -120000000:
return f"GameFlag({v + 130000000})"
elif v <= -100000000:
return f"AreaFlag({v + 110000000})"
elif v <= -80000000:
return f"MapFlag({v + 90000000})"
elif v <= -60000000:
return f"LocalFlag({v + 70000000})"
elif v <= -40000000:
return f"MapVar({v + 50000000})"
elif v <= -20000000:
return f"LocalVar({v + 30000000})"
if arg == 0xFFFFFFFF:
return "-1"
@ -26,6 +38,7 @@ def get_variable(arg):
else:
return f"{arg}"
if __name__ == "__main__":
try:
print(get_variable(int(sys.argv[1], 0)))

View File

@ -20,14 +20,14 @@ CPP_FLAGS = [
"-D_LANGUAGE_C",
"-DF3DEX_GBI_2",
"-D_MIPS_SZLONG=32",
"-DSCRIPT(test...)={}"
"-D__attribute__(test...)=",
"-DSCRIPT(test...)={}" "-D__attribute__(test...)=",
"-D__asm__(test...)=",
"-ffreestanding",
"-DM2CTX",
"-DVERSION_PAL",
]
def import_c_file(in_file) -> str:
in_file = os.path.relpath(in_file, root_dir)
cpp_command = ["gcc", "-E", "-P", "-dM", *CPP_FLAGS, in_file]
@ -42,10 +42,9 @@ def import_c_file(in_file) -> str:
out_text += subprocess.check_output(cpp_command2, cwd=root_dir, encoding="utf-8")
except subprocess.CalledProcessError:
print(
"Failed to preprocess input file, when running command:\n"
+ cpp_command,
"Failed to preprocess input file, when running command:\n" + cpp_command,
file=sys.stderr,
)
)
sys.exit(1)
if not out_text:
@ -56,10 +55,9 @@ def import_c_file(in_file) -> str:
out_text = out_text.replace(line + "\n", "")
return out_text
def main():
parser = argparse.ArgumentParser(
description="""Create a context file which can be used for mips_to_c"""
)
parser = argparse.ArgumentParser(description="""Create a context file which can be used for mips_to_c""")
parser.add_argument(
"c_file",
help="""File from which to create context""",

View File

@ -22,7 +22,7 @@ def data_to_c(file_path):
output = ""
pattern = re.compile(r"(dlabel (jtbl_.*|.+_.*)\n.(\w+) (.*))")
for (all, symbol, type, data) in re.findall(pattern, s):
for all, symbol, type, data in re.findall(pattern, s):
if type == "word":
if symbol.startswith("jtbl_"):
output += "dlabel " + symbol + "\n" + ".word " + data.replace("L", ".L") + "\n\n"
@ -42,7 +42,7 @@ def out_to_file(output, file_path):
if not os.path.exists(output_dir):
os.mkdir(output_dir)
file_name = file_path[file_path.rfind("/"):-7]
file_name = file_path[file_path.rfind("/") : -7]
file = open("data2c/" + file_name + ".c", "w+")
file.write(output)
file.close()
@ -74,9 +74,19 @@ def query(file, to_file):
parser = argparse.ArgumentParser(description="Tool to translate .data.s files to data arrays")
parser.add_argument("query", help="data file", nargs='?', default=None)
parser.add_argument("--all", help="translate all data files at once and output them into /data2c", action='store_true', required=False)
parser.add_argument("--to-file", help="redirect the output into a file. Can not be used in combination with --all", action='store_true', required=False)
parser.add_argument("query", help="data file", nargs="?", default=None)
parser.add_argument(
"--all",
help="translate all data files at once and output them into /data2c",
action="store_true",
required=False,
)
parser.add_argument(
"--to-file",
help="redirect the output into a file. Can not be used in combination with --all",
action="store_true",
required=False,
)
args = parser.parse_args()

View File

@ -26,12 +26,21 @@ if os.path.exists(f"src/{args.src}.c"):
if os.path.exists(f"ver/current/asm/nonmatchings/{args.src}"):
print("moving asm/nonmatchings files")
os.rename(f"ver/current/asm/nonmatchings/{args.src}", f"ver/current/asm/nonmatchings/{args.dest}")
os.rename(
f"ver/current/asm/nonmatchings/{args.src}",
f"ver/current/asm/nonmatchings/{args.dest}",
)
if os.path.exists(f"ver/current/asm/data/{args.src}.data.s"):
print("moving data file")
os.rename(f"ver/current/asm/data/{args.src}.data.s", f"ver/current/asm/data/{args.dest}.data.s")
os.rename(
f"ver/current/asm/data/{args.src}.data.s",
f"ver/current/asm/data/{args.dest}.data.s",
)
if os.path.exists(f"ver/current/asm/data/{args.src}.rodata.s"):
print("moving rodata file")
os.rename(f"ver/current/asm/data/{args.src}.rodata.s", f"ver/current/asm/data/{args.dest}.rodata.s")
os.rename(
f"ver/current/asm/data/{args.src}.rodata.s",
f"ver/current/asm/data/{args.dest}.rodata.s",
)

View File

@ -11,6 +11,7 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.abspath(os.path.join(script_dir, "../.."))
import glob, os
os.chdir(root_dir)
for f in Path(root_dir).rglob("*.bin"):
@ -20,7 +21,10 @@ for f in Path(root_dir).rglob("*.bin"):
continue
ras = []
result = subprocess.run(["mips-linux-gnu-objdump", "-Dz", "-bbinary", "-mmips", "-EB" , f], stdout=subprocess.PIPE)
result = subprocess.run(
["mips-linux-gnu-objdump", "-Dz", "-bbinary", "-mmips", "-EB", f],
stdout=subprocess.PIPE,
)
output = result.stdout.decode().split("\n")
for line in output:

View File

@ -18,7 +18,7 @@ for line in inlines:
break
if area:
fname = line[line.rfind("`") + 1:line.rfind("'")]
fname = line[line.rfind("`") + 1 : line.rfind("'")]
renames[fname] = area
pairs = []

View File

@ -28,7 +28,7 @@ for filename, line_number, bad_symbol_name in problems:
continue
if old_line.startswith("N(") or old_line.startswith("await N("):
good_symbol_name = old_line[old_line.find("N("):].split(")", 1)[0] + ")"
good_symbol_name = old_line[old_line.find("N(") :].split(")", 1)[0] + ")"
else:
good_symbol_name = old_line.split("(", 1)[0]
@ -38,4 +38,3 @@ for filename, line_number, bad_symbol_name in problems:
with open(filename, "w") as f:
f.writelines(lines)

View File

@ -3,14 +3,14 @@
import argparse
import os
def auto_int(x):
return int(x, 0)
script_dir = os.path.dirname(os.path.realpath(__file__))
parser = argparse.ArgumentParser(
description="Generate rename file for effects"
)
parser = argparse.ArgumentParser(description="Generate rename file for effects")
parser.add_argument(
"id",
@ -23,6 +23,7 @@ parser.add_argument(
help="Name (in snake case) to change the effect to",
)
def main(args):
id = args.id
to = args.to
@ -31,7 +32,7 @@ def main(args):
hex_str = f"{id:02x}".upper()
struct_name = ''.join(word.title() for word in to.split('_'))
struct_name = "".join(word.title() for word in to.split("_"))
to_write.append(f"Effect{id} {struct_name}FXData")
to_write.append(f"playFX_{hex_str} fx_{to}")
to_write.append(f"FX_ENTRY_NUMBERED({id}, FX_ENTRY({to},")
@ -47,6 +48,7 @@ def main(args):
for line in to_write:
f.write(f"{line}\n")
if __name__ == "__main__":
args = parser.parse_args()
main(args)

View File

@ -6,8 +6,8 @@ from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("baserom")
parser.add_argument("start", type=lambda x:int(x, 0))
parser.add_argument("end", type=lambda x:int(x, 0))
parser.add_argument("start", type=lambda x: int(x, 0))
parser.add_argument("end", type=lambda x: int(x, 0))
args = parser.parse_args()
baserom_path = Path(__file__).parent.parent / "baserom.z64"
@ -25,9 +25,13 @@ while i < args.end:
while unpack_from("B", baserom, i)[0] == 0:
i += 1
#print(f"Start {hex(dis_start)} end {hex(i)}")
gfxdis = subprocess.run(f"{gfxdis_path.resolve()} " + f"-x " + f"-dc " + f"-d {baserom[dis_start:i].hex()}",
capture_output=True, shell=True, text=True)
# print(f"Start {hex(dis_start)} end {hex(i)}")
gfxdis = subprocess.run(
f"{gfxdis_path.resolve()} " + f"-x " + f"-dc " + f"-d {baserom[dis_start:i].hex()}",
capture_output=True,
shell=True,
text=True,
)
commands = gfxdis.stdout.splitlines()[1:-1]
new_commands = []

View File

@ -3,11 +3,23 @@ import argparse
from struct import unpack_from
CONSTANTS = {}
def get_constants():
global CONSTANTS
valid_enums = { "StoryProgress", "ItemIDs", "PlayerAnims",
"ActorIDs", "Events", "SoundIDs", "SongIDs", "Locations",
"AmbientSounds", "NpcIDs", "Emotes" }
valid_enums = {
"StoryProgress",
"ItemIDs",
"PlayerAnims",
"ActorIDs",
"Events",
"SoundIDs",
"SongIDs",
"Locations",
"AmbientSounds",
"NpcIDs",
"Emotes",
}
for enum in valid_enums:
CONSTANTS[enum] = {}
CONSTANTS["NPC_SPRITE"] = {}
@ -16,7 +28,7 @@ def get_constants():
enums = Path(include_path / "enums.h").read_text().splitlines()
# defines
'''
"""
for line in enums.splitlines():
this_enum = ""
for enum in valid_enums:
@ -31,12 +43,12 @@ def get_constants():
id_ = id_.split(" ",1)[0]
CONSTANTS[this_enum][int(id_, 16)] = name
'''
"""
# enums
for i,line in enumerate(enums):
for i, line in enumerate(enums):
if line.startswith("enum "):
enum_name = line.split(" ",1)[1].split(" {",1)[0]
enum_name = line.split(" ", 1)[1].split(" {", 1)[0]
if enum_name in valid_enums:
CONSTANTS[enum_name] = {}
last_num = 0
@ -47,7 +59,7 @@ def get_constants():
continue
name = enums[i].strip()
val = last_num+1
val = last_num + 1
if "=" in name:
name, val = name.split(" = ")
val = int(val[:-1], 0)
@ -56,14 +68,16 @@ def get_constants():
else:
name = name[:-1]
name = name.strip()
#print("\"" + name + "\"", "===", val)
# print("\"" + name + "\"", "===", val)
CONSTANTS[enum_name][val] = name.strip()
i += 1
last_num = val
# sprites
sprite_path = Path(Path(__file__).resolve().parent.parent / "ver" / "current" / "build" / "include" / "sprite" / "npc")
sprite_path = Path(
Path(__file__).resolve().parent.parent / "ver" / "current" / "build" / "include" / "sprite" / "npc"
)
for file in sprite_path.iterdir():
fd = file.read_text()
for line in fd.splitlines():
@ -76,10 +90,10 @@ def get_constants():
else:
continue
name = line.split(" ",2)[1]
name = line.split(" ", 2)[1]
id_ = line.split("0x", 1)[1]
if " " in id_:
id_ = id_.split(" ",1)[0]
id_ = id_.split(" ", 1)[0]
name = name.split(f"_{enum}_", 1)[1]
if enum == "NPC_SPRITE":
saved_name = name
@ -89,7 +103,11 @@ def get_constants():
if enum == "NPC_SPRITE":
if int(id_, 16) not in CONSTANTS["NPC_SPRITE"]:
CONSTANTS[enum][int(id_, 16)] = {"name":"", "palettes":{}, "anims":{}}
CONSTANTS[enum][int(id_, 16)] = {
"name": "",
"palettes": {},
"anims": {},
}
CONSTANTS[enum][int(id_, 16)]["name"] = name
elif enum == "NPC_PALETTE":
CONSTANTS["NPC_SPRITE"][int(saved_id, 16)]["palettes"][int(id_, 16)] = name
@ -97,20 +115,22 @@ def get_constants():
CONSTANTS["NPC_SPRITE"][int(saved_id, 16)]["anims"][int(id_, 16)] = name
return
STRUCTS = {}
def parse_var(line):
#print(f"Parsing {line}")
# print(f"Parsing {line}")
if "*/ " in line:
line = line.split("*/ ",1)[1]
line = line.split(";",1)[0].strip()
line = line.split("*/ ", 1)[1]
line = line.split(";", 1)[0].strip()
if "," in line or "(*" in line:
return (None, None, None, None)
elif "union " in line:
return ("union", None, None, None)
#print(f"Parsed {line}")
# print(f"Parsed {line}")
if " " in line:
if line.startswith("struct "):
struct, type_, name = line.split(" ")
@ -131,22 +151,23 @@ def parse_var(line):
is_ptr = "*" in type_ or type_ == "UNK_PTR"
return (type_, name, count, is_ptr)
def parse_file(filename):
fd = filename.read_text().splitlines()
i = 0
while i < len(fd):
#supported = [f"typedef struct {x}" in fd[i] for x in SUPPORTED_STRUCTS]
#if any(supported):
# supported = [f"typedef struct {x}" in fd[i] for x in SUPPORTED_STRUCTS]
# if any(supported):
if "typedef struct " in fd[i]:
#supported_name = [SUPPORTED_STRUCTS[i] for i,x in enumerate(supported) if x][0]
# supported_name = [SUPPORTED_STRUCTS[i] for i,x in enumerate(supported) if x][0]
supported_name = fd[i].split("typedef struct ", 1)[1].split(" {", 1)[0]
if supported_name == "{":
supported_name = ""
#print(f"Parsing struct \"{supported_name}\"")
# print(f"Parsing struct \"{supported_name}\"")
struct_to_add = []
i += 1
while ("} " + f"{supported_name.upper()}") not in fd[i].split(";",1)[0].upper():
while ("} " + f"{supported_name.upper()}") not in fd[i].split(";", 1)[0].upper():
type_, name, count, ptr = parse_var(fd[i])
if type_ == None:
@ -158,27 +179,37 @@ def parse_file(filename):
i += 1
while "}" not in fd[i]:
type_, name, count, ptr = parse_var(fd[i])
union.append({"type":type_, "name": name, "num":count, "ptr":ptr})
union.append({"type": type_, "name": name, "num": count, "ptr": ptr})
i += 1
name = fd[i].split("}", 1)[1].split(";", 1)[0]
#print(supported_name, type_, name, count)
struct_to_add.append({"type":type_, "name": name, "num":count, "ptr":ptr, "union":union})
# print(supported_name, type_, name, count)
struct_to_add.append(
{
"type": type_,
"name": name,
"num": count,
"ptr": ptr,
"union": union,
}
)
i += 1
#print(f"Broke on line {fd[i]}")
#print()
# print(f"Broke on line {fd[i]}")
# print()
if supported_name == "":
supported_name = fd[i].split("} ",1)[1].split(";",1)[0]
supported_name = fd[i].split("} ", 1)[1].split(";", 1)[0]
if "[" in supported_name:
supported_name = supported_name[:-2]
STRUCTS[supported_name] = struct_to_add
i += 1
def get_structs():
parse_file(Path(Path(__file__).parent.parent / "include" / "map.h"))
parse_file(Path(Path(__file__).parent.parent / "include" / "common_structs.h"))
def get_vals(fd, offset, var):
global STRUCTS
@ -192,63 +223,63 @@ def get_vals(fd, offset, var):
for var2 in STRUCTS[var["type"]]:
out3, offset = get_vals(fd, offset, var2)
data.extend(out3)
#if var["num"] == 1:
# if var["num"] == 1:
# out.extend(out2)
#else:
#out.append(out2)
# else:
# out.append(out2)
else:
type_ = "int"
fmt = "d"
if var["type"] == "s8" or var["type"] == "char":
if var["type"] == "char":
if var["type"] == "s8" or var["type"] == "char":
if var["type"] == "char":
type_ = "hex"
fmt = "X"
data = unpack_from('>b', fd, offset)[0]
data = unpack_from(">b", fd, offset)[0]
offset += 1
elif var["type"] == "u8":
data = unpack_from('>B', fd, offset)[0]
elif var["type"] == "u8":
data = unpack_from(">B", fd, offset)[0]
fmt = "d"
offset += 1
elif var["type"] == "s16" or var["type"] in ("s16"):
elif var["type"] == "s16" or var["type"] in ("s16"):
offset += offset % 2
data = unpack_from('>h', fd, offset)[0]
data = unpack_from(">h", fd, offset)[0]
fmt = "d"
offset += 2
elif var["type"] == "u16":
elif var["type"] == "u16":
offset += offset % 2
data = unpack_from('>H', fd, offset)[0]
data = unpack_from(">H", fd, offset)[0]
fmt = "d"
offset += 2
elif var["type"] == "s32" or var["type"] in ("s32"):
elif var["type"] == "s32" or var["type"] in ("s32"):
poff = offset
offset += offset % 4
data = unpack_from('>i', fd, offset)[0]
data = unpack_from(">i", fd, offset)[0]
fmt = "d"
offset += 4
elif var["type"] == "u32":
elif var["type"] == "u32":
offset += offset % 4
data = unpack_from('>I', fd, offset)[0]
data = unpack_from(">I", fd, offset)[0]
fmt = "d"
offset += 4
elif var["type"] == "f32":
elif var["type"] == "f32":
offset += offset % 4
data = unpack_from('>f', fd, offset)[0]
data = unpack_from(">f", fd, offset)[0]
type_ = "float"
fmt = ".01f"
offset += 4
elif var["type"] == "X32":
offset += offset % 4
data = unpack_from('>f', fd, offset)[0]
data = unpack_from(">f", fd, offset)[0]
type_ = "Xfloat"
fmt = ".01f"
if data < -1000.0 or data > 1000.0:
type_ = "Xint"
fmt = "d"
data = unpack_from('>i', fd, offset)[0]
data = unpack_from(">i", fd, offset)[0]
offset += 4
elif var["ptr"]:
offset += offset % 4
data = unpack_from('>I', fd, offset)[0]
data = unpack_from(">I", fd, offset)[0]
type_ = "ptr"
fmt = "08X"
offset += 4
@ -256,24 +287,26 @@ def get_vals(fd, offset, var):
print(f"Unknown data type \"{var['type']}\"")
exit()
if var["num"] == 1:
out.append({"name":var["name"], "type":type_, "fmt":fmt, "data":data})
out.append({"name": var["name"], "type": type_, "fmt": fmt, "data": data})
else:
arr.append({"name":var["name"], "type":type_, "fmt":fmt, "data":data})
arr.append({"name": var["name"], "type": type_, "fmt": fmt, "data": data})
if var["num"] > 1:
out.append(arr)
return out, offset
def INDENT(depth):
return f" " * depth
def print_data(vals, indent, needs_name, is_array=False, is_struct=False):
out = []
for val in vals:
line = ""
if needs_name:
line = INDENT(indent)
#print(val)
# print(val)
# array
if type(val) is list:
line += f".{val[0]['name']} = " + "{ "
@ -291,10 +324,10 @@ def print_data(vals, indent, needs_name, is_array=False, is_struct=False):
line += "\n"
line += INDENT(indent)
line += "{ "
for x,val2 in enumerate(val["data"]):
for x, val2 in enumerate(val["data"]):
if x > 0:
line += ", "
#line += f".{val2['name']} = "
# line += f".{val2['name']} = "
fmt = val2["fmt"]
if val2["type"] == "float":
line += f"{val2['data']:{fmt}}f"
@ -315,7 +348,6 @@ def print_data(vals, indent, needs_name, is_array=False, is_struct=False):
if not is_array:
line += ","
else:
if "flags" in val["name"].lower() or "animations" in val["name"].lower():
if val["name"] == "flags":
val["fmt"] = "08X"
@ -338,10 +370,10 @@ def print_data(vals, indent, needs_name, is_array=False, is_struct=False):
elif val["type"] == "hex":
line += f"0x{val['data']:{fmt}}"
elif val["type"] == "ptr":
if val["data"] == 0:
line += f"NULL"
else:
line += f"0x{val['data']:{fmt}}"
if val["data"] == 0:
line += f"NULL"
else:
line += f"0x{val['data']:{fmt}}"
else:
line += f"{val['data']}"
@ -352,6 +384,7 @@ def print_data(vals, indent, needs_name, is_array=False, is_struct=False):
return out
def output_type2(fd, count, offset, var):
ultra_out = []
for i in range(count):
@ -364,10 +397,11 @@ def output_type2(fd, count, offset, var):
out.extend(print_data(vals, 1, True))
out.append("};")
ultra_out.append(out)
return offset, ultra_out #"\n".join(out)
return offset, ultra_out # "\n".join(out)
def check_list(vals, depth = 0):
for x,val in enumerate(vals):
def check_list(vals, depth=0):
for x, val in enumerate(vals):
if type(val) == list:
if check_list(val, depth + 1):
return True
@ -375,9 +409,10 @@ def check_list(vals, depth = 0):
return True
return False
def recurse_check_list(vals):
res = 0
for x,val in enumerate(vals):
for x, val in enumerate(vals):
if type(val) == list:
if check_list(val, 1):
return len(vals) - x
@ -385,14 +420,15 @@ def recurse_check_list(vals):
return len(vals) - x
return -1
def get_single_struct_vals(fd, i):
vals = []
if not fd[i].rstrip().endswith("},"):
# must be a sub-struct over multiple lines
old_i = i
i += 1
while not ("}," in fd[i] and "." in fd[i+1]):
temp = fd[i].split("{",1)[1].split("}",1)[0].split(", ")
while not ("}," in fd[i] and "." in fd[i + 1]):
temp = fd[i].split("{", 1)[1].split("}", 1)[0].split(", ")
a = []
for x in temp:
x = x.strip()
@ -405,7 +441,7 @@ def get_single_struct_vals(fd, i):
i += 1
else:
# single line
temp = fd[i].split("{",1)[1].split("}",1)[0].split(", ")
temp = fd[i].split("{", 1)[1].split("}", 1)[0].split(", ")
a = []
for x in temp:
x = x.strip()
@ -417,10 +453,11 @@ def get_single_struct_vals(fd, i):
vals.extend(a)
return vals, i
def cull_struct(fd, i, entirely=False):
out = []
vals = []
#print(f"Culling Starting at {fd[i]}")
# print(f"Culling Starting at {fd[i]}")
if not fd[i].rstrip().endswith("},"):
# must be a sub-struct over multiple lines
old_i = i
@ -428,9 +465,9 @@ def cull_struct(fd, i, entirely=False):
# reverse and cull entries of only zeros
x = recurse_check_list(vals[::-1])
#print(f"Found first index of empty values at idx {x}, vals: {vals}")
# print(f"Found first index of empty values at idx {x}, vals: {vals}")
if x < 0:
#print(f"Ending at {fd[i]}")
# print(f"Ending at {fd[i]}")
return None, i
out.append(fd[old_i])
@ -441,36 +478,37 @@ def cull_struct(fd, i, entirely=False):
out.append(fd[old_i])
old_i += 1
#print(f"Ending at {fd[i]}")
# print(f"Ending at {fd[i]}")
else:
prefix = fd[i].split("{",1)[0] + "{ "
prefix = fd[i].split("{", 1)[0] + "{ "
suffix = " },"
vals, i = get_single_struct_vals(fd, i)
# reverse and cull entries of only zeros
x = recurse_check_list(vals[::-1])
#print(f"Found first index of empty values at idx {x}, vals: {vals}")
# print(f"Found first index of empty values at idx {x}, vals: {vals}")
if x < 0:
#print(f"Ending at {fd[i]}")
# print(f"Ending at {fd[i]}")
return None, i
#out.append(prefix)
# out.append(prefix)
if entirely:
x = len(vals)
temp = ""
for z,y in enumerate(range(x)):
for z, y in enumerate(range(x)):
if z > 0:
prefix += ", "
prefix += f"{vals[y]}"
out.append(prefix + suffix)
#print(f"Ending at {fd[i]}")
# print(f"Ending at {fd[i]}")
return "\n".join(out), i
def MacroReplaceStaticNPC(fd):
structs = { "unk_1C":True, "movement":False, "unk_1E0":True }
#replace_cull_struct = { "unk_1C", "movement", "unk_1E0" }
#replace_cull = { "tattle", "extraAnimations", "itemDropChance" }
structs = {"unk_1C": True, "movement": False, "unk_1E0": True}
# replace_cull_struct = { "unk_1C", "movement", "unk_1E0" }
# replace_cull = { "tattle", "extraAnimations", "itemDropChance" }
fd = fd.splitlines()
out = []
i = 0
@ -479,7 +517,7 @@ def MacroReplaceStaticNPC(fd):
for x in structs:
if f".{x}" in fd[i]:
found = x
break;
break
if found:
# just cull it if possible
vals, i = cull_struct(fd, i, structs[found])
@ -489,9 +527,9 @@ def MacroReplaceStaticNPC(fd):
i += 1
continue
name = fd[i].split(" = ",1)[0].strip()[1:]
name = fd[i].split(" = ", 1)[0].strip()[1:]
if name not in structs and "{" not in fd[i] and name != ";":
val = fd[i].split(" = ",1)[1][:-1]
val = fd[i].split(" = ", 1)[1][:-1]
if "0x" in val:
val = int(val, 16)
elif "NULL" in val:
@ -507,8 +545,8 @@ def MacroReplaceStaticNPC(fd):
if ".itemDrops" in fd[i]:
vals, x = cull_struct(fd, i)
indent = len(fd[i].split(".",1)[0]) // 4
new_line = fd[i].split("{",1)[0] + "{\n"
indent = len(fd[i].split(".", 1)[0]) // 4
new_line = fd[i].split("{", 1)[0] + "{\n"
if not vals:
i = x
@ -525,7 +563,7 @@ def MacroReplaceStaticNPC(fd):
added_item = True
item_name = CONSTANTS["ItemIDs"][item[0]]
new_line += " " * (indent+1) + "{ " + item_name + f", {item[1]}, {item[2]}" + " },\n"
new_line += " " * (indent + 1) + "{ " + item_name + f", {item[1]}, {item[2]}" + " },\n"
if added_item:
new_line += " " * indent + "},"
@ -534,18 +572,18 @@ def MacroReplaceStaticNPC(fd):
i = x
elif ".animations" in fd[i]:
indent = len(fd[i].split(".",1)[0]) // 4
new_line = fd[i].split("{",1)[0] + "{\n"
indent = len(fd[i].split(".", 1)[0]) // 4
new_line = fd[i].split("{", 1)[0] + "{\n"
vals, x = get_single_struct_vals(fd, i)
for val in vals:
sprite_id = (val & 0x00FF0000) >> 16
sprite_id = (val & 0x00FF0000) >> 16
palette_id = (val & 0x0000FF00) >> 8
anim_id = (val & 0x000000FF) >> 0
sprite = CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
anim_id = (val & 0x000000FF) >> 0
sprite = CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
palette = CONSTANTS["NPC_SPRITE"][sprite_id]["palettes"][palette_id]
anim = CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
new_line += " " * (indent+1) + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
anim = CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
new_line += " " * (indent + 1) + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
new_line += " " * indent + "},"
out.append(new_line)
i = x
@ -553,7 +591,7 @@ def MacroReplaceStaticNPC(fd):
elif ".heartDrops" in fd[i] or ".flowerDrops" in fd[i]:
vals, x = get_single_struct_vals(fd, i)
new_line = fd[i].split("{",1)[0]
new_line = fd[i].split("{", 1)[0]
attempts = vals[0][2]
@ -564,10 +602,16 @@ def MacroReplaceStaticNPC(fd):
new_line += f"GENEROUS_HEART_DROPS({attempts}),"
elif round(vals[0][1] / 327.67, 2) == 80 and round(vals[0][3] / 327.67, 2) == 60:
new_line += f"GENEROUS_WHEN_LOW_HEART_DROPS({attempts}),"
elif round(vals[0][0] / 327.67, 2) == 100 and round(vals[0][1] / 327.67, 2) == 0 and round(vals[0][2] / 327.67, 2) == 0:
elif (
round(vals[0][0] / 327.67, 2) == 100
and round(vals[0][1] / 327.67, 2) == 0
and round(vals[0][2] / 327.67, 2) == 0
):
new_line += f"NO_DROPS,"
else:
print(f"Unknown heart drop macro, values were {round(vals[0][1] / 327.67, 2)} and {round(vals[0][3] / 327.67, 2)}")
print(
f"Unknown heart drop macro, values were {round(vals[0][1] / 327.67, 2)} and {round(vals[0][3] / 327.67, 2)}"
)
exit()
else:
if round(vals[0][1] / 327.67, 2) == 50 and round(vals[0][3] / 327.67, 2) == 40:
@ -576,10 +620,16 @@ def MacroReplaceStaticNPC(fd):
new_line += f"GENEROUS_WHEN_LOW_FLOWER_DROPS({attempts}),"
elif round(vals[0][1] / 327.67, 2) == 40 and round(vals[0][3] / 327.67, 2) == 40:
new_line += f"REDUCED_FLOWER_DROPS({attempts}),"
elif round(vals[0][0] / 327.67, 2) == 100 and round(vals[0][1] / 327.67, 2) == 0 and round(vals[0][2] / 327.67, 2) == 0:
elif (
round(vals[0][0] / 327.67, 2) == 100
and round(vals[0][1] / 327.67, 2) == 0
and round(vals[0][2] / 327.67, 2) == 0
):
new_line += f"NO_DROPS,"
else:
print(f"Unknown flower drop macro, values were {round(vals[0][1] / 327.67, 2)} and {round(vals[0][3] / 327.67, 2)}")
print(
f"Unknown flower drop macro, values were {round(vals[0][1] / 327.67, 2)} and {round(vals[0][3] / 327.67, 2)}"
)
exit()
out.append(new_line)
@ -590,8 +640,9 @@ def MacroReplaceStaticNPC(fd):
i += 1
return "\n".join(out)
def MacroReplaceNpcSettings(fd):
replace_cull = { "unk_00", "unk_24" }
replace_cull = {"unk_00", "unk_24"}
fd = fd.splitlines()
out = []
i = 0
@ -605,6 +656,7 @@ def MacroReplaceNpcSettings(fd):
i += 1
return "\n".join(out)
def MacroReplaceNpcGroupList(fd):
fd = fd.splitlines()
out = []
@ -617,21 +669,31 @@ def MacroReplaceNpcGroupList(fd):
val = 0
else:
if "0x" in fd[i]:
val = int(fd[i].split(" = ",1)[1][:-1], 16)
val = int(fd[i].split(" = ", 1)[1][:-1], 16)
else:
val = int(fd[i].split(" = ",1)[1][:-1], 10)
val = int(fd[i].split(" = ", 1)[1][:-1], 10)
vals.append(val)
i += 1
out.append(f" NPC_GROUP(N(D_{vals[1]:X}), BATTLE_ID({(vals[2] & 0xFF000000) >> 24}, {(vals[2] & 0xFF0000) >> 16}, {(vals[2] & 0xFF00) >> 8}, {vals[2] & 0xFF})),")
out.append(
f" NPC_GROUP(N(D_{vals[1]:X}), BATTLE_ID({(vals[2] & 0xFF000000) >> 24}, {(vals[2] & 0xFF0000) >> 16}, {(vals[2] & 0xFF00) >> 8}, {vals[2] & 0xFF})),"
)
return "\n".join(out)
parser = argparse.ArgumentParser()
parser.add_argument("file", type=str, help="File to decompile struct from")
parser.add_argument("type", type=str, help="Struct type to decompile")
parser.add_argument("offset", type=lambda x: int(x, 0), help="Offset to decompile struct from")
parser.add_argument("--count", "-c", "--c", type=int, default=0, help="Num to try and decompile (NpcGroupList)")
parser.add_argument(
"--count",
"-c",
"--c",
type=int,
default=0,
help="Num to try and decompile (NpcGroupList)",
)
args = parser.parse_args()
get_constants()
@ -641,14 +703,14 @@ if args.type not in STRUCTS:
print(f"Unknown struct type {args.type}")
exit()
'''
"""
out = [f"{args.type} = " + "{\n"]
offset = args.offset
for var in STRUCTS[args.type]:
line, offset = output_type(fd, offset, var, 1)
out.append(line)
out.append("};")
'''
"""
if args.count == 0:
args.count = 1
@ -656,7 +718,7 @@ if args.count == 0:
fd = Path(args.file).resolve().read_bytes()
offset, out = output_type2(fd, args.count, args.offset, STRUCTS[args.type])
for i,entry in enumerate(out):
for i, entry in enumerate(out):
out[i] = "\n".join(entry)
print(f"EvtScript range 0x{args.offset:08X} - 0x{offset:08X}")

View File

@ -90,6 +90,7 @@ data_to_thing = {
"413F20": "star_outline",
}
def handle_symbol(effect, symbol):
for root, dirs, files in os.walk(asm_effects_dir + effect + "/"):
for f_name in files:
@ -139,6 +140,7 @@ def handle_file(f_path):
continue
migrated = handle_symbol(data_to_thing[effect], symbol)
# Walk through asm files and rename stuff
print("Walking through asm files")
for root, dirs, files in os.walk(asm_data_dir):

View File

@ -64,4 +64,3 @@ for segment in config["segments"]:
with open(c_file_path, "w", newline="\n") as f:
f.write("".join(c_lines))

View File

@ -9,6 +9,7 @@ asm_dir = root_dir + "ver/current/asm/"
asm_world_dir = asm_dir + "nonmatchings/world/"
asm_data_dir = asm_dir + "data/"
def handle_symbol(area, symbol):
for root, dirs, files in os.walk(asm_world_dir + area[0] + "/" + area[1]):
for f_name in files:
@ -55,6 +56,7 @@ def handle_file(f_path):
for symbol in reversed(symbols):
migrated = handle_symbol(area, symbol)
# Walk through asm files and rename stuff
print("Walking through asm files")
for root, dirs, files in os.walk(asm_data_dir):

View File

@ -1,5 +1,6 @@
from pathlib import Path
def parse_folder(path):
for entry in path.iterdir():
if entry.is_dir():
@ -10,4 +11,5 @@ def parse_folder(path):
fd.append("")
entry.write_text("\n".join(fd))
parse_folder(Path("src"))

View File

@ -10,7 +10,7 @@ with open("tools/symz.txt") as f:
for line in f.readlines():
if line.strip() and not line.startswith("//"):
name, addr = line.strip().strip(";").split(" = ")
try :
try:
addr = int(addr, 0)
except ValueError:
continue

View File

@ -12,7 +12,10 @@ src_dir = root_dir + "src/"
asm_dir = root_dir + "ver/current/asm/"
parser = argparse.ArgumentParser(description="Replace many functions with one")
parser.add_argument("from_list", help="path to line-separated file of functions to be replaced. first line is the string to replace them with")
parser.add_argument(
"from_list",
help="path to line-separated file of functions to be replaced. first line is the string to replace them with",
)
args = parser.parse_args()

View File

@ -47,11 +47,14 @@ def parse_symbol_addrs():
symbol_addrs = {}
for line in lines:
name = line[:line.find(" ")]
name = line[: line.find(" ")]
attributes = line[line.find("//"):].split(" ")
ram_addr = int(line[:line.find(";")].split("=")[1].strip(), base=0)
rom_addr = next((int(attr.split(":")[1], base=0) for attr in attributes if attr.split(":")[0] == "rom"), None)
attributes = line[line.find("//") :].split(" ")
ram_addr = int(line[: line.find(";")].split("=")[1].strip(), base=0)
rom_addr = next(
(int(attr.split(":")[1], base=0) for attr in attributes if attr.split(":")[0] == "rom"),
None,
)
symbol_addrs[name] = Symbol(ram_addr, rom_addr)
@ -73,7 +76,7 @@ def find_old_script_ranges(lines, filename):
if "#define NAMESPACE " in line_content:
namespace = line_content.split(" ")[2].strip()
#elif namespace == "events" or namespace == "header":
# elif namespace == "events" or namespace == "header":
# namespace = NAMESPACES.get(filename, filename.split("/")[-2].split(".")[0])
elif namespace_temp is not None:
namespace = namespace_temp[0][:-2]
@ -113,7 +116,7 @@ def replace_old_script_macros(filename, symbol_addrs):
lines[range.start] = lines[range.start][:macro_start_idx] + "{\n"
# Remove other lines
lines = lines[:range.start + 1] + lines[range.end + 1:]
lines = lines[: range.start + 1] + lines[range.end + 1 :]
# Find the symbol
try:
@ -128,7 +131,7 @@ def replace_old_script_macros(filename, symbol_addrs):
local_symbol_map = {}
for sym in symbol_addrs:
if sym.startswith(range.namespace):
key = "N(" + sym[len(range.namespace)+1:] + ")"
key = "N(" + sym[len(range.namespace) + 1 :] + ")"
else:
key = sym
@ -143,7 +146,8 @@ def replace_old_script_macros(filename, symbol_addrs):
# Disassemble the script
rom.seek(range_sym.rom_addr)
evt_code = ScriptDisassembler(rom,
evt_code = ScriptDisassembler(
rom,
script_name=range.symbol_name,
romstart=range_sym.rom_addr,
prelude=False,

View File

@ -16,6 +16,7 @@ renames = {}
patterns = []
deletes = []
def handle_file(f_path, try_rename_file=False):
with open(f_path) as f:
f_text_orig = f.read()
@ -35,17 +36,18 @@ def handle_file(f_path, try_rename_file=False):
# replace all matches
for match in matches:
# head part
to_join.append(f_text[pos:match[1]])
to_join.append(f_text[pos : match[1]])
to_replace = patterns[match[0]]
to_join.append(renames[to_replace])
pos = match[2]
# tail part
to_join.append(f_text[pos:])
f_text = ''.join(to_join);
f_text = "".join(to_join)
# save changes
with open(f_path, "w", newline="\n") as f:
f.write(f_text)
# Read input file
# One valid whitespace-separated find-replace pair is given per line
with open(os.path.join(script_dir, "to_rename.txt")) as f:
@ -59,7 +61,7 @@ for line in renames_text:
renames[split[0]] = split[1]
patterns.append(split[0])
elif len(split) != 0:
raise Exception("input contains invalid rename pattern: \n\"" + line.strip() + "\"")
raise Exception('input contains invalid rename pattern: \n"' + line.strip() + '"')
ac = ahocorasick_rs.AhoCorasick(patterns, matchkind=MATCHKIND_LEFTMOST_LONGEST)

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
def decode(data):
length = 0
is_dbl_char = False
@ -20,7 +21,8 @@ def decode(data):
length += 1
return data[:length].decode('shift-jis')
return data[:length].decode("shift-jis")
if __name__ == "__main__":
import sys

View File

@ -1,5 +1,6 @@
from segtypes.n64.gfx import N64SegGfx
class N64SegGfx_common(N64SegGfx):
def format_sym_name(self, sym):
return f"N({sym.name[7:]})"

View File

@ -67,8 +67,6 @@ glabel {name}
ret = []
for shim in self.shims:
ret.append(
LinkerEntry(self, [self.shim_path(shim)], self.shim_path(shim), ".text")
)
ret.append(LinkerEntry(self, [self.shim_path(shim)], self.shim_path(shim), ".text"))
return ret

View File

@ -13,22 +13,22 @@ script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
def indent(elem, level=0):
i = "\n" + level*" "
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
elem.tail = i
def pretty_print_xml(tree : ET.ElementTree, path : Path):
def pretty_print_xml(tree: ET.ElementTree, path: Path):
root = tree.getroot()
indent(root)
xml_str = ET.tostring(root, encoding="unicode")
@ -49,11 +49,11 @@ def parse_palette(data):
class N64SegPm_icons(N64Segment):
def split(self, rom_bytes):
self.out_dir = options.opts.asset_path / "icon"
with open(script_dir / "icon.yaml") as f:
self.icons = yaml_loader.load(f.read(), Loader=yaml_loader.SafeLoader)
data = rom_bytes[self.rom_start: self.rom_end]
data = rom_bytes[self.rom_start : self.rom_end]
pos = 0
self.files = []
@ -65,11 +65,11 @@ class N64SegPm_icons(N64Segment):
IconList = ET.Element("Icons")
for (_, icon) in enumerate(self.icons):
for _, icon in enumerate(self.icons):
# read yaml entry
fmt = icon[0]
name = icon[1]
w = int(icon[2])
w = int(icon[2])
h = int(icon[3])
if fmt == "solo" or fmt == "pair":

View File

@ -41,9 +41,7 @@ class N64SegPm_imgfx_data(N64Segment):
frame: List[Vertex] = []
for j in range(vtx_count):
x, y, z, u, v, r, g, b, a = struct.unpack(
">hhhBBbbbB", data[pos : pos + 12]
)
x, y, z, u, v, r, g, b, a = struct.unpack(">hhhBBbbbB", data[pos : pos + 12])
pos += 12
frame.append(Vertex(j, x, y, z, u, v, r, g, b, a))
@ -121,10 +119,7 @@ class N64SegPm_imgfx_data(N64Segment):
return [
LinkerEntry(
self,
[
self.OUT_DIR / f"{name}.json"
for name, _ in self.yaml.get("animations")
],
[self.OUT_DIR / f"{name}.json" for name, _ in self.yaml.get("animations")],
options.opts.asset_path / "imgfx" / f"{self.name}.c",
self.get_linker_section(),
)

View File

@ -135,65 +135,45 @@ class N64SegPm_map_data(N64Segment):
w = png.Writer(150, 105, palette=parse_palette(bytes[:0x200]))
w.write_array(f, bytes[0x200:])
elif name == "title_data":
if "ver/us" in str(options.opts.target_path) or "ver/pal" in str(
options.opts.target_path
):
if "ver/us" in str(options.opts.target_path) or "ver/pal" in str(options.opts.target_path):
w = 200
h = 112
img = n64img.image.RGBA32(
data=bytes[0x2210 : 0x2210 + w * h * 4], width=w, height=h
)
img = n64img.image.RGBA32(data=bytes[0x2210 : 0x2210 + w * h * 4], width=w, height=h)
img.write(fs_dir / "title/logotype.png")
w = 144
h = 32
img = n64img.image.IA8(
data=bytes[0x10 : 0x10 + w * h], width=w, height=h
)
img = n64img.image.IA8(data=bytes[0x10 : 0x10 + w * h], width=w, height=h)
img.write(fs_dir / "title/copyright.png")
w = 128
h = 32
img = n64img.image.IA8(
data=bytes[0x1210 : 0x1210 + w * h], width=w, height=h
)
img = n64img.image.IA8(data=bytes[0x1210 : 0x1210 + w * h], width=w, height=h)
img.write(fs_dir / "title/press_start.png")
else:
w = 272
h = 88
img = n64img.image.RGBA32(
data=bytes[0x1830 : 0x1830 + w * h * 4], width=w, height=h
)
img = n64img.image.RGBA32(data=bytes[0x1830 : 0x1830 + w * h * 4], width=w, height=h)
img.write(fs_dir / "title/logotype.png")
w = 128
h = 32
img = n64img.image.CI4(
data=bytes[0x10 : 0x10 + (w * h // 2)], width=w, height=h
)
img = n64img.image.CI4(data=bytes[0x10 : 0x10 + (w * h // 2)], width=w, height=h)
img.palette = parse_palette(bytes[0x810:0x830])
img.write(fs_dir / "title/copyright.png")
w = 128
h = 32
img = n64img.image.IA8(
data=bytes[0x830 : 0x830 + w * h], width=w, height=h
)
img = n64img.image.IA8(data=bytes[0x830 : 0x830 + w * h], width=w, height=h)
img.write(fs_dir / "title/press_start.png")
elif name.endswith("_bg"):
def write_bg_png(bytes, path, header_offset=0):
header = bytes[header_offset : header_offset + 0x10]
raster_offset = (
int.from_bytes(header[0:4], byteorder="big") - 0x80200000
)
palette_offset = (
int.from_bytes(header[4:8], byteorder="big") - 0x80200000
)
assert (
int.from_bytes(header[8:12], byteorder="big") == 0x000C0014
) # draw pos
raster_offset = int.from_bytes(header[0:4], byteorder="big") - 0x80200000
palette_offset = int.from_bytes(header[4:8], byteorder="big") - 0x80200000
assert int.from_bytes(header[8:12], byteorder="big") == 0x000C0014 # draw pos
width = int.from_bytes(header[12:14], byteorder="big")
height = int.from_bytes(header[14:16], byteorder="big")
@ -202,9 +182,7 @@ class N64SegPm_map_data(N64Segment):
w = png.Writer(
width,
height,
palette=parse_palette(
bytes[palette_offset : palette_offset + 512]
),
palette=parse_palette(bytes[palette_offset : palette_offset + 512]),
)
w.write_array(f, bytes[raster_offset:])
@ -212,9 +190,7 @@ class N64SegPm_map_data(N64Segment):
# sbk_bg has an alternative palette
if name == "sbk_bg":
write_bg_png(
bytes, fs_dir / "bg" / f"{name}.alt.png", header_offset=0x10
)
write_bg_png(bytes, fs_dir / "bg" / f"{name}.alt.png", header_offset=0x10)
elif name.endswith("_tex"):
TexArchive.extract(bytes, fs_dir / "tex" / name)
else:

View File

@ -188,13 +188,23 @@ CHARSET = {
0x02: "[Style left]\n",
0x03: "[Style center]\n",
0x04: "[Style tattle]\n",
0x05: {None: lambda d: (f"[Style choice pos={d[0]},{d[1]} size={d[2]},{d[3]}]\n", 4)},
0x05: {
None: lambda d: (
f"[Style choice pos={d[0]},{d[1]} size={d[2]},{d[3]}]\n",
4,
)
},
0x06: "[Style inspect]\n",
0x07: "[Style sign]\n",
0x08: {None: lambda d: (f"[Style lamppost height={d[0]}]\n", 1)},
0x09: {None: lambda d: (f"[Style postcard index={d[0]}]\n", 1)},
0x0A: "[Style popup]\n",
0x0C: {None: lambda d: (f"[Style upgrade pos={d[0]},{d[1]} size={d[2]},{d[3]}]\n", 4)},
0x0C: {
None: lambda d: (
f"[Style upgrade pos={d[0]},{d[1]} size={d[2]},{d[3]}]\n",
4,
)
},
0x0D: "[Style narrate]\n",
0x0E: "[Style epilogue]\n",
},
@ -215,17 +225,13 @@ CHARSET = {
# 0x24: "[color:cyan]",
# 0x25: "[color:green]",
# 0x26: "[color:yellow]",
# 0x00: "[color=normal ctx=diary]",
# 0x07: "[color=red ctx=diary]",
# 0x17: "[color=dark ctx=inspect]",
# 0x18: "[color=normal ctx=sign]",
# 0x19: "[color=red ctx=sign]",
# 0x1A: "[color=blue ctx=sign]",
# 0x1B: "[color=green ctx=sign]",
# 0x28: "[color=red ctx=popup]",
# 0x29: "[color=pink ctx=popup]",
# 0x2A: "[color=purple ctx=popup]",
@ -234,7 +240,6 @@ CHARSET = {
# 0x2D: "[color=green ctx=popup]",
# 0x2E: "[color=yellow ctx=popup]",
# 0x2F: "[color=normal ctx=popup]",
None: lambda d: (f"[Color 0x{d[0]:X}]", 1),
},
0x07: "[InputOff]\n",
@ -252,9 +257,19 @@ CHARSET = {
0x13: {None: lambda d: (f"[Down {d[0]}]", 1)},
0x14: {None: lambda d: (f"[Up {d[0]}]", 1)},
0x15: {None: lambda d: (f"[InlineImage index={d[0]}]\n", 1)},
0x16: {None: lambda d: (f"[AnimSprite spriteID=0x{d[0]:02X}{d[1]:02X} raster={d[2]}]\n", 3)},
0x16: {
None: lambda d: (
f"[AnimSprite spriteID=0x{d[0]:02X}{d[1]:02X} raster={d[2]}]\n",
3,
)
},
0x17: {None: lambda d: (f"[ItemIcon itemID=0x{d[0]:02X}{d[1]:02X}]\n", 2)},
0x18: {None: lambda d: (f"[Image index={d[0]} pos={(d[1] << 8) + d[2]},{d[3]} hasBorder={d[4]} alpha={d[5]} fadeAmount={d[6]}]\n", 7)},
0x18: {
None: lambda d: (
f"[Image index={d[0]} pos={(d[1] << 8) + d[2]},{d[3]} hasBorder={d[4]} alpha={d[5]} fadeAmount={d[6]}]\n",
7,
)
},
0x19: {None: lambda d: (f"[HideImage fadeAmount={d[0]}]\n", 1)},
0x1A: {None: lambda d: (f"[AnimDelay index={d[1]} delay={d[2]}]", 3)},
0x1B: {None: lambda d: (f"[AnimLoop {d[0]} {d[1]}]", 2)},
@ -265,20 +280,24 @@ CHARSET = {
0x21: {None: lambda d: (f"[Option {d[0]}]", 1)},
0x22: "[SavePos]",
0x23: "[RestorePos]",
0x24: {0xFF: {0x05: {
0x10: {0x98: {0xFF: {0x25: "[A]"}}},
0x11: {0x99: {0xFF: {0x25: "[B]"}}},
0x12: {0xA1: {0xFF: {0x25: "[START]"}}},
0x13: {
0x9D: {0xFF: {0x25: "[C-UP]"}},
0x9E: {0xFF: {0x25: "[C-DOWN]"}},
0x9F: {0xFF: {0x25: "[C-LEFT]"}},
0xA0: {0xFF: {0x25: "[C-RIGHT]"}},
},
0x14: {0x9C: {0xFF: {0x25: "[Z]"}}},
}}},
#0x24: "[SaveColor]",
#0x25: "[RestoreColor]",
0x24: {
0xFF: {
0x05: {
0x10: {0x98: {0xFF: {0x25: "[A]"}}},
0x11: {0x99: {0xFF: {0x25: "[B]"}}},
0x12: {0xA1: {0xFF: {0x25: "[START]"}}},
0x13: {
0x9D: {0xFF: {0x25: "[C-UP]"}},
0x9E: {0xFF: {0x25: "[C-DOWN]"}},
0x9F: {0xFF: {0x25: "[C-LEFT]"}},
0xA0: {0xFF: {0x25: "[C-RIGHT]"}},
},
0x14: {0x9C: {0xFF: {0x25: "[Z]"}}},
}
}
},
# 0x24: "[SaveColor]",
# 0x25: "[RestoreColor]",
0x26: {
0x00: "[Shake]",
0x01: "[Wave]",
@ -307,7 +326,12 @@ CHARSET = {
0x28: {None: lambda d: (f"[Var {d[0]}]", 1)},
0x29: {None: lambda d: (f"[CenterX {d[0]}]", 1)},
0x2B: "[EnableCDownNext]",
0x2C: {None: lambda d: (f"[CustomVoice soundIDs=0x{d[0]:02X}{d[1]:02X}{d[2]:02X}{d[3]:02X},{d[4]:02X}{d[5]:02X}{d[6]:02X}{d[7]:02X}]", 8)},
0x2C: {
None: lambda d: (
f"[CustomVoice soundIDs=0x{d[0]:02X}{d[1]:02X}{d[2]:02X}{d[3]:02X},{d[4]:02X}{d[5]:02X}{d[6]:02X}{d[7]:02X}]",
8,
)
},
0x2E: {None: lambda d: (f"[Volume {d[0]}]", 1)},
0x2F: {
0: "[Voice normal]\n",
@ -315,7 +339,7 @@ CHARSET = {
2: "[Voice star]\n",
None: lambda d: (f"[Voice {d[0]}]\n", 1),
},
#None: lambda d: (f"[func_{d[0]:02X}]", 1),
# None: lambda d: (f"[func_{d[0]:02X}]", 1),
},
None: lambda d: (f"[Raw 0x{d[0]:02X}]", 1),
}
@ -366,6 +390,7 @@ CHARSET_CREDITS = {
0xF7: " ",
}
class N64SegPm_msg(N64Segment):
def __init__(
self,
@ -393,12 +418,12 @@ class N64SegPm_msg(N64Segment):
self.msg_names = yaml_loader.load(f.read(), Loader=yaml_loader.SafeLoader)
def split(self, rom_bytes):
data = rom_bytes[self.rom_start: self.rom_end]
data = rom_bytes[self.rom_start : self.rom_end]
section_offsets = []
pos = 0
while True:
offset = int.from_bytes(data[pos:pos+4], byteorder="big")
offset = int.from_bytes(data[pos : pos + 4], byteorder="big")
if offset == 0:
break
@ -417,7 +442,7 @@ class N64SegPm_msg(N64Segment):
msg_offsets = []
pos = section_offset
while True:
offset = int.from_bytes(data[pos:pos+4], byteorder="big")
offset = int.from_bytes(data[pos : pos + 4], byteorder="big")
if offset == section_offset:
break
@ -425,7 +450,7 @@ class N64SegPm_msg(N64Segment):
msg_offsets.append(offset)
pos += 4
#self.log(f"Reading {len(msg_offsets)} messages in section {name} (0x{i:02X})")
# self.log(f"Reading {len(msg_offsets)} messages in section {name} (0x{i:02X})")
path = msg_dir / Path(name + ".msg")
@ -449,7 +474,6 @@ class N64SegPm_msg(N64Segment):
self.write_message_markup(data[msg_offset:])
self.f.write("\n}\n")
def get_linker_entries(self):
from segtypes.linker_entry import LinkerEntry
@ -458,7 +482,6 @@ class N64SegPm_msg(N64Segment):
return [LinkerEntry(self, out_paths, base_path, ".data")]
@staticmethod
def get_default_name(addr):
return "msg"

View File

@ -56,9 +56,7 @@ class SBN:
entry_addr = header.tableOffset
seen_entry_offsets = set()
for i in range(header.numEntries):
entry = SBNFileEntry(
*struct.unpack_from(SBNFileEntry.fstring, data, entry_addr)
)
entry = SBNFileEntry(*struct.unpack_from(SBNFileEntry.fstring, data, entry_addr))
entry_addr += SBNFileEntry.length
# Check for duplicate entry offsets
@ -152,9 +150,7 @@ class SBN:
else:
raise ValueError("Unsupported file extension")
entry = SBNFileEntry(
offset=current_file_offset, fmt=format, size=file.fakesize
)
entry = SBNFileEntry(offset=current_file_offset, fmt=format, size=file.fakesize)
struct.pack_into(
SBNFileEntry.fstring,
@ -222,9 +218,7 @@ class SBN:
with open(path / "sbn.yaml", "w") as f:
# Filename->ID map
f.write(
"# Mapping of filenames to entry IDs. Use 'id: auto' to automatically assign a unique ID.\n"
)
f.write("# Mapping of filenames to entry IDs. Use 'id: auto' to automatically assign a unique ID.\n")
f.write(
"""
# 'fakesize is an interesting case. In the final ROM, the size of a file is stored in the file header and the entry table.
@ -267,9 +261,7 @@ class SBN:
f.write("\n")
# INIT mseqs
f.write(
"# AuGlobals::mseqFileList. Not sure why there's non-MSEQ files here!\n"
)
f.write("# AuGlobals::mseqFileList. Not sure why there's non-MSEQ files here!\n")
f.write("mseqs:\n")
for id, entry in enumerate(self.init.mseq_entries):
f.write(f" - id: 0x{id:02x}\n")
@ -357,9 +349,7 @@ class SBN:
assert type(bk_file) == str
bk_file_ids.append(self.lookup_file_id(bk_file))
init_song_entry = InitSongEntry(
file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2]
)
init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2])
# Replace self.init.song_entries[id]
if id < len(self.init.song_entries):
@ -562,9 +552,7 @@ class INIT:
song_addr = header.tblOffset
song_number = 0
while True:
song = InitSongEntry(
*struct.unpack_from(InitSongEntry.fstring, data, song_addr)
)
song = InitSongEntry(*struct.unpack_from(InitSongEntry.fstring, data, song_addr))
if song.bgmFileIndex == 0xFFFF:
break
@ -590,9 +578,7 @@ class INIT:
entries_len = header.entriesSize // 4 - 1
for i in range(entries_len):
entry = BufferEntry(
*struct.unpack_from(BufferEntry.fstring, data, entries_addr)
)
entry = BufferEntry(*struct.unpack_from(BufferEntry.fstring, data, entries_addr))
entries_addr += BufferEntry.length
self.bk_entries.append(entry)
@ -708,9 +694,7 @@ if splat_loaded:
out = options.opts.asset_path / self.dir / (self.name + ".sbn")
sbn = SBN()
config_files = sbn.read(
dir
) # TODO: LayeredFS/AssetsFS read, supporting merges
config_files = sbn.read(dir) # TODO: LayeredFS/AssetsFS read, supporting merges
inputs = config_files + [dir / f.file_name() for f in sbn.files]
return [
LinkerEntry(

View File

@ -140,9 +140,7 @@ def extract(input_data: bytes, endian: Literal["big", "little"] = "big") -> str:
profile_list = []
for _ in range(len(PROFILE_NAMES[g])):
profile_list.append(
struct.unpack(END + "i", offsets_table[pl_it : pl_it + 4])[0]
)
profile_list.append(struct.unpack(END + "i", offsets_table[pl_it : pl_it + 4])[0])
pl_it += 4
for j, pl_offset in enumerate(profile_list):

View File

@ -101,22 +101,22 @@ LIST_END_BYTES = b"\xFF\xFF\xFF\xFF"
def indent(elem, level=0):
i = "\n" + level*" "
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
elem.tail = i
def pretty_print_xml(tree : ET.ElementTree, path : Path):
def pretty_print_xml(tree: ET.ElementTree, path: Path):
root = tree.getroot()
indent(root)
xml_str = ET.tostring(root, encoding="unicode")
@ -136,9 +136,7 @@ class RasterTableEntry:
raster_bytes: bytes = field(default_factory=bytes)
palette: Optional[bytes] = None
def write_png(
self, raster_buffer: bytes, path: Path, palette: Optional[bytes] = None
):
def write_png(self, raster_buffer: bytes, path: Path, palette: Optional[bytes] = None):
if self.height == 0 or self.width == 0:
raise ValueError("Raster size has not been set")
@ -149,9 +147,7 @@ class RasterTableEntry:
raise ValueError("Palette has not been set")
if self.raster_bytes is not None:
self.raster_bytes = raster_buffer[
self.offset : self.offset + (self.width * self.height // 2)
]
self.raster_bytes = raster_buffer[self.offset : self.offset + (self.width * self.height // 2)]
img = CI4(self.raster_bytes, self.width, self.height)
img.set_palette(palette)
@ -264,9 +260,7 @@ class PlayerSprite:
)
def extract_raster_table_entries(
data: bytes, raster_sets: List[PlayerSpriteRasterSet]
) -> Dict[int, RasterTableEntry]:
def extract_raster_table_entries(data: bytes, raster_sets: List[PlayerSpriteRasterSet]) -> Dict[int, RasterTableEntry]:
ret: Dict[int, RasterTableEntry] = {}
current_section_pos = 0
current_section = 0
@ -297,9 +291,7 @@ def extract_raster_table_entries(
return ret
def extract_sprites(
yay0_data: bytes, raster_sets: List[PlayerSpriteRasterSet]
) -> List[PlayerSprite]:
def extract_sprites(yay0_data: bytes, raster_sets: List[PlayerSpriteRasterSet]) -> List[PlayerSprite]:
yay0_splits = []
for i in range(14):
yay0_splits.append(int.from_bytes(yay0_data[i * 4 : i * 4 + 4], "big"))
@ -375,9 +367,7 @@ def write_player_xmls(
raster_table_entry_dict: Dict[int, RasterTableEntry],
raster_names: List[str],
) -> None:
def get_sprite_name_from_offset(
offset: int, offsets: List[int], names: List[str]
) -> str:
def get_sprite_name_from_offset(offset: int, offsets: List[int], names: List[str]) -> str:
return names[offsets.index(offset)]
sprite_idx = 0
@ -426,9 +416,7 @@ def write_player_xmls(
back_raster = cur_sprite_back.rasters[i]
if back_raster.is_special:
raster_attributes[
"special"
] = f"{back_raster.width & 0xFF:X},{back_raster.height & 0xFF:X}"
raster_attributes["special"] = f"{back_raster.width & 0xFF:X},{back_raster.height & 0xFF:X}"
else:
back_name_offset = raster_sets[sprite_idx + 1].raster_offsets[i]
raster_attributes[
@ -483,9 +471,7 @@ def write_player_xmls(
)
for anim in comp.animations:
ET.SubElement(
Component, anim.__class__.__name__, anim.get_attributes()
)
ET.SubElement(Component, anim.__class__.__name__, anim.get_attributes())
xml = ET.ElementTree(SpriteSheet)
pretty_print_xml(xml, out_path / f"{cur_sprite_name}.xml")
@ -547,12 +533,8 @@ def write_player_palettes(
if pal_name not in dumped_palettes:
offset = PLAYER_PAL_TO_RASTER[pal_name]
if pal_name not in PLAYER_PAL_TO_RASTER:
print(
f"WARNING: Palette {pal_name} has no specified raster, not dumping!"
)
raster_table_entry_dict[offset].write_png(
raster_data, path / (pal_name + ".png"), palette
)
print(f"WARNING: Palette {pal_name} has no specified raster, not dumping!")
raster_table_entry_dict[offset].write_png(raster_data, path / (pal_name + ".png"), palette)
###########
@ -606,12 +588,8 @@ class NpcSprite:
@staticmethod
def from_bytes(data: bytearray):
image_offsets = read_offset_list(
data[int.from_bytes(data[0:4], byteorder="big") :]
)
palette_offsets = read_offset_list(
data[int.from_bytes(data[4:8], byteorder="big") :]
)
image_offsets = read_offset_list(data[int.from_bytes(data[0:4], byteorder="big") :])
palette_offsets = read_offset_list(data[int.from_bytes(data[4:8], byteorder="big") :])
max_components = int.from_bytes(data[8:0xC], byteorder="big")
num_variations = int.from_bytes(data[0xC:0x10], byteorder="big")
animation_offsets = read_offset_list(data[0x10:])
@ -683,11 +661,7 @@ class NpcSprite:
)
for i, palette in enumerate(self.palettes):
name = (
self.palette_names[i]
if (self.palette_names and i < len(self.palette_names))
else f"Pal{i:02X}"
)
name = self.palette_names[i] if (self.palette_names and i < len(self.palette_names)) else f"Pal{i:02X}"
if i in palette_to_raster:
img = palette_to_raster[i][0]
@ -710,9 +684,7 @@ class NpcSprite:
AnimationList,
"Animation",
{
"name": self.animation_names[i]
if self.animation_names
else f"Anim{i:02X}",
"name": self.animation_names[i] if self.animation_names else f"Anim{i:02X}",
},
)
@ -727,9 +699,7 @@ class NpcSprite:
)
for anim in comp.animations:
ET.SubElement(
Component, anim.__class__.__name__, anim.get_attributes()
)
ET.SubElement(Component, anim.__class__.__name__, anim.get_attributes())
xml = ET.ElementTree(SpriteSheet)
pretty_print_xml(xml, path / "SpriteSheet.xml")
@ -739,9 +709,7 @@ class N64SegPm_sprites(N64Segment):
DEFAULT_NPC_SPRITE_NAMES = [f"{i:02X}" for i in range(0xEA)]
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml) -> None:
super().__init__(
rom_start, rom_end, type, name, vram_start, args=args, yaml=yaml
)
super().__init__(rom_start, rom_end, type, name, vram_start, args=args, yaml=yaml)
with (Path(__file__).parent / f"npc_sprite_names.yaml").open("r") as f:
self.npc_cfg = yaml_loader.load(f.read(), Loader=yaml_loader.SafeLoader)
@ -752,9 +720,7 @@ class N64SegPm_sprites(N64Segment):
def out_path(self):
return options.opts.asset_path / "sprite" / "sprites"
def split_player(
self, build_date: str, player_raster_data: bytes, player_yay0_data: bytes
) -> None:
def split_player(self, build_date: str, player_raster_data: bytes, player_yay0_data: bytes) -> None:
player_sprite_cfg = self.player_cfg["player_sprites"]
player_raster_names: List[str] = self.player_cfg["player_rasters"]
@ -854,9 +820,7 @@ class N64SegPm_sprites(N64Segment):
npc_yay0_offset = int.from_bytes(sprite_in_bytes[0x18:0x1C], "big") + 0x10
sprite_end_offset = int.from_bytes(sprite_in_bytes[0x1C:0x20], "big") + 0x10
player_raster_data: bytes = sprite_in_bytes[
player_raster_offset:player_yay0_offset
]
player_raster_data: bytes = sprite_in_bytes[player_raster_offset:player_yay0_offset]
player_yay0_data: bytes = sprite_in_bytes[player_yay0_offset:npc_yay0_offset]
npc_yay0_data: bytes = sprite_in_bytes[npc_yay0_offset:sprite_end_offset]
@ -869,14 +833,9 @@ class N64SegPm_sprites(N64Segment):
src_paths = [options.opts.asset_path / "sprite"]
# for NPC
src_paths += [
options.opts.asset_path / "sprite" / "npc" / sprite_name
for sprite_name in self.npc_cfg
]
src_paths += [options.opts.asset_path / "sprite" / "npc" / sprite_name for sprite_name in self.npc_cfg]
return [
LinkerEntry(self, src_paths, self.out_path(), self.get_linker_section())
]
return [LinkerEntry(self, src_paths, self.out_path(), self.get_linker_section())]
def cache(self):
return (self.yaml, self.rom_end, self.player_cfg, self.npc_cfg)

View File

@ -217,7 +217,6 @@ class AnimComponent:
def size(self):
return len(self.commands)
@staticmethod
def parse_commands(command_list: List[int]) -> List[Animation]:
ret: List[Animation] = []
@ -329,10 +328,7 @@ class AnimComponent:
x, y, z = struct.unpack(">hhh", data[6:12])
commands = [
int.from_bytes(d[0:2], byteorder="big", signed=False)
for d in iter_in_groups(commands_data, 2)
]
commands = [int.from_bytes(d[0:2], byteorder="big", signed=False) for d in iter_in_groups(commands_data, 2)]
return AnimComponent(x, y, z, commands)
@property

View File

@ -260,9 +260,7 @@ class TexImage:
self.main_height,
)
if self.main_fmt == FMT_CI:
self.main_img.palette = self.get_n64_pal(
texbuf, self.main_fmt, self.main_depth
)
self.main_img.palette = self.get_n64_pal(texbuf, self.main_fmt, self.main_depth)
# main img + mipmaps
elif self.extra_tiles == TILES_MIPMAPS:
self.has_mipmaps = True
@ -282,9 +280,7 @@ class TexImage:
break
mmw = self.main_width // divisor
mmh = self.main_height // divisor
mipmap = self.get_n64_img(
texbuf, self.main_fmt, self.main_depth, mmw, mmh
)
mipmap = self.get_n64_img(texbuf, self.main_fmt, self.main_depth, mmw, mmh)
self.mipmaps.append(mipmap)
divisor = divisor * 2
@ -334,13 +330,9 @@ class TexImage:
pal = self.get_n64_pal(texbuf, self.main_fmt, self.main_depth)
self.main_img.palette = pal
# read aux
self.aux_img = self.get_n64_img(
texbuf, self.aux_fmt, self.aux_depth, self.aux_width, self.aux_height
)
self.aux_img = self.get_n64_img(texbuf, self.aux_fmt, self.aux_depth, self.aux_width, self.aux_height)
if self.aux_fmt == FMT_CI:
self.aux_img.palette = self.get_n64_pal(
texbuf, self.aux_fmt, self.aux_depth
)
self.aux_img.palette = self.get_n64_pal(texbuf, self.aux_fmt, self.aux_depth)
# constructs a dictionary entry for the tex archive for this texture
def get_json_entry(self):
@ -418,9 +410,7 @@ class TexImage:
return (r << 11) | (g << 6) | (b << 1) | a
(out_img, out_w, out_h) = Converter(
mode=fmt_str.lower(), infile=img_file, flip_y=True
).convert()
(out_img, out_w, out_h) = Converter(mode=fmt_str.lower(), infile=img_file, flip_y=True).convert()
out_pal = bytearray()
if fmt_str == "CI4" or fmt_str == "CI8":

View File

@ -1,5 +1,6 @@
from segtypes.n64.vtx import N64SegVtx
class N64SegVtx_common(N64SegVtx):
def format_sym_name(self, sym):
return f"N({sym.name[7:]})"

View File

@ -12,11 +12,11 @@ def create_enum(file_content, prefix, ordering):
max_size = 0
if ordering:
for (key, value) in re.findall(r'(\S+)\s+=\s+(\S+)', file_content):
for key, value in re.findall(r"(\S+)\s+=\s+(\S+)", file_content):
if len(key) > max_size:
max_size = len(key)
else:
for (key, value) in re.findall(r'(\S+)\s+=\s+(\S+)', file_content):
for key, value in re.findall(r"(\S+)\s+=\s+(\S+)", file_content):
if len(value) > max_size:
max_size = len(value)
@ -25,15 +25,25 @@ def create_enum(file_content, prefix, ordering):
else:
prefix = ""
for (key, value) in re.findall(r'(\S+)\s+=\s+(\S+)', file_content):
for key, value in re.findall(r"(\S+)\s+=\s+(\S+)", file_content):
if ordering:
key = '_'.join(re.sub(r'([A-Z]{1,2})', r' \1', key).split()).replace("N_P_C", "NPC").replace("__", "_").replace("-", "")
key = prefix.upper() + '_{:<{width}}'.format(key, width=max_size + 2).upper()
ret += " " + key + " = 0x" + '{:>{fill}{width}}'.format(value, fill=0, width=8) + ",\n"
key = (
"_".join(re.sub(r"([A-Z]{1,2})", r" \1", key).split())
.replace("N_P_C", "NPC")
.replace("__", "_")
.replace("-", "")
)
key = prefix.upper() + "_{:<{width}}".format(key, width=max_size + 2).upper()
ret += " " + key + " = 0x" + "{:>{fill}{width}}".format(value, fill=0, width=8) + ",\n"
else:
value = '_'.join(re.sub(r'([A-Z]{1,2})', r' \1', value).split()).replace("N_P_C", "NPC").replace("__", "_").replace("-", "")
value = prefix.upper() + '_{:<{width}}'.format(value, width=max_size + 2).upper()
ret += " " + value + " = 0x" + '{:>{fill}{width}}'.format(key, fill=0, width=8) + ",\n"
value = (
"_".join(re.sub(r"([A-Z]{1,2})", r" \1", value).split())
.replace("N_P_C", "NPC")
.replace("__", "_")
.replace("-", "")
)
value = prefix.upper() + "_{:<{width}}".format(value, width=max_size + 2).upper()
ret += " " + value + " = 0x" + "{:>{fill}{width}}".format(key, fill=0, width=8) + ",\n"
ret += "};\n"
@ -51,9 +61,9 @@ def single_translation(file_path):
print("File not found at the given path.")
return "t", "y"
enum_namespace = re.search(r'(\w*)\s+%\s+namespace', file_content).group(1)
enum_name = re.search(r'(\w*)\s+%\s+library name', file_content).group(1)
reversed_order = re.search(r'(\w*)\s+%\s+reversed', file_content).group(1)
enum_namespace = re.search(r"(\w*)\s+%\s+namespace", file_content).group(1)
enum_name = re.search(r"(\w*)\s+%\s+library name", file_content).group(1)
reversed_order = re.search(r"(\w*)\s+%\s+reversed", file_content).group(1)
ret += "enum " + enum_name + " {\n"
if reversed_order == "true":
@ -76,9 +86,9 @@ def recursive_translation(database_path):
except:
continue
enum_namespace = re.search(r'(\w*)\s+%\s+namespace', file_content).group(1)
enum_name = re.search(r'(\w*)\s+%\s+library name', file_content).group(1)
reversed_order = re.search(r'(\w*)\s+%\s+reversed', file_content).group(1)
enum_namespace = re.search(r"(\w*)\s+%\s+namespace", file_content).group(1)
enum_name = re.search(r"(\w*)\s+%\s+library name", file_content).group(1)
reversed_order = re.search(r"(\w*)\s+%\s+reversed", file_content).group(1)
ret += "enum " + enum_name + " {\n"
if reversed_order == "true":
@ -106,9 +116,17 @@ def main(args):
file.close()
parser = argparse.ArgumentParser(description='Convert a StarRod enum into an enum that is in a decomp compatible format')
parser = argparse.ArgumentParser(
description="Convert a StarRod enum into an enum that is in a decomp compatible format"
)
parser.add_argument("query", help="StarRod enum file or folder")
parser.add_argument("-r", "--recursive", help="recursively convert all files to enums", type=bool, required=False)
parser.add_argument(
"-r",
"--recursive",
help="recursively convert all files to enums",
type=bool,
required=False,
)
args = parser.parse_args()

View File

@ -24,12 +24,16 @@ INCLUDES_NEEDED["npcs"] = {}
INCLUDES_NEEDED["sprites"] = set()
INCLUDES_NEEDED["tattle"] = []
def get_flag_name(arg):
v = arg - 2**32 # convert to s32
v = arg - 2**32 # convert to s32
if v > -250000000:
if v <= -220000000: return str((v + 230000000) / 1024)
elif v <= -200000000: return f"ArrayFlag({v + 210000000})"
elif v <= -180000000: return f"ArrayVar({v + 190000000})"
if v <= -220000000:
return str((v + 230000000) / 1024)
elif v <= -200000000:
return f"ArrayFlag({v + 210000000})"
elif v <= -180000000:
return f"ArrayVar({v + 190000000})"
elif v <= -160000000:
if v + 170000000 == 0:
return "GB_StoryProgress"
@ -37,13 +41,20 @@ def get_flag_name(arg):
return "GB_WorldLocation"
else:
return f"GameByte({v + 170000000})"
elif v <= -140000000: return f"AreaByte({v + 150000000})"
elif v <= -120000000: return f"GameFlag({v + 130000000})"
elif v <= -100000000: return f"AreaFlag({v + 110000000})"
elif v <= -80000000: return f"MapFlag({v + 90000000})"
elif v <= -60000000: return f"LocalFlag({v + 70000000})"
elif v <= -40000000: return f"MapVar({v + 50000000})"
elif v <= -20000000: return f"LocalVar({v + 30000000})"
elif v <= -140000000:
return f"AreaByte({v + 150000000})"
elif v <= -120000000:
return f"GameFlag({v + 130000000})"
elif v <= -100000000:
return f"AreaFlag({v + 110000000})"
elif v <= -80000000:
return f"MapFlag({v + 90000000})"
elif v <= -60000000:
return f"LocalFlag({v + 70000000})"
elif v <= -40000000:
return f"MapVar({v + 50000000})"
elif v <= -20000000:
return f"LocalVar({v + 30000000})"
if arg == 0xFFFFFFFF:
return "-1"
@ -54,6 +65,7 @@ def get_flag_name(arg):
else:
return f"{arg}"
def get_function_list(area_name, map_name, rom_offset):
map_file = (Path(__file__).parent.parent / "ver" / "current" / "build" / "papermario.map").read_text().splitlines()
i = 0
@ -70,7 +82,7 @@ def get_function_list(area_name, map_name, rom_offset):
vram = int(vram, 16)
func = func.replace(f"{map_name}_", "")
if func.count("_") == 2:
func = func.rsplit("_",1)[0]
func = func.rsplit("_", 1)[0]
functions[vram] = func
i += 1
if firstFind:
@ -79,6 +91,7 @@ def get_function_list(area_name, map_name, rom_offset):
return functions
def get_include_list(area_name, map_name):
include_path = Path(__file__).parent.parent / "src" / "world" / "common"
includes = set()
@ -87,16 +100,18 @@ def get_include_list(area_name, map_name):
with open(file, "r", encoding="utf8") as f:
for line in f:
if (line.startswith("void N(") or line.startswith("ApiStatus N(")) and "{" in line:
func_name = line.split("N(",1)[1].split(")",1)[0]
func_name = line.split("N(", 1)[1].split(")", 1)[0]
includes.add(func_name)
return includes
def read_enum(num: int, constants_name: str) -> str:
if num in disasm_script.CONSTANTS[constants_name]:
return disasm_script.CONSTANTS[constants_name][num]
else:
return num
def read_flags(flags: int, constants_name: str) -> str:
enabled = []
for x in range(32):
@ -115,6 +130,7 @@ def read_flags(flags: int, constants_name: str) -> str:
return " | ".join(enabled)
def read_ptr(addr: int, symbol_map: dict, needs_ampersand: bool = False) -> str:
if addr == 0:
return "NULL"
@ -126,6 +142,7 @@ def read_ptr(addr: int, symbol_map: dict, needs_ampersand: bool = False) -> str:
else:
return f"(void*) 0x{addr:08X}"
def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace=None):
global INCLUDES_NEEDED, INCLUDED
out = ""
@ -139,7 +156,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
def transform_symbol_name(symbol):
if namespace and symbol.startswith(namespace + "_"):
return "N(" + symbol[len(namespace)+1:] + ")"
return "N(" + symbol[len(namespace) + 1 :] + ")"
return symbol
while len(midx) > 0:
@ -148,7 +165,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
name = struct["name"]
print(name, file=sys.stderr)
#INCLUDED["functions"].add(name)
# INCLUDED["functions"].add(name)
if comments:
out += f"// {romstart+struct['start']:X}-{romstart+struct['end']:X} (VRAM: {struct['vaddr']:X})\n"
@ -165,18 +182,23 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
main_script_name = name
# For PlayMusic script if using a separate header file
#if afterHeader:
# if afterHeader:
# INCLUDES_NEEDED["forward"].append(f"EvtScript " + name + ";")
# afterHeader = False
disasm_script.LOCAL_WORDS = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
disasm_script.LOCAL_WORDS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
script_text = disasm_script.ScriptDisassembler(
bytes, name, symbol_map, romstart, INCLUDES_NEEDED, INCLUDED,
bytes,
name,
symbol_map,
romstart,
INCLUDES_NEEDED,
INCLUDED,
transform_symbol_name=transform_symbol_name,
use_script_lib=False,
).disassemble()
if "EVS_ShakeTree" in name or "EVS_SearchBush" in name:
symbol_map[struct["vaddr"]][0][1] = name.split("_",1)[0] + ")"
symbol_map[struct["vaddr"]][0][1] = name.split("_", 1)[0] + ")"
if not treePrint:
out += f"=======================================\n"
out += f"==========BELOW foliage.inc.c==========\n"
@ -188,9 +210,9 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
script_text = script_text.splitlines()
walkDistance = exitIdx = map_ = entryIdx = ""
if "UseExitHeading" in script_text[2]:
walkDistance, exitIdx = script_text[2].split("(",1)[1].split(")",1)[0].split(",")
walkDistance, exitIdx = script_text[2].split("(", 1)[1].split(")", 1)[0].split(",")
if "GotoMap" in script_text[4]:
map_, entryIdx = script_text[4].split("(",1)[1].split(")",1)[0].split(",")
map_, entryIdx = script_text[4].split("(", 1)[1].split(")", 1)[0].split(",")
if walkDistance and exitIdx and map_ and entryIdx:
out += f"EvtScript {name} = EVT_EXIT_WALK({walkDistance}, {exitIdx}, {map_}, {entryIdx});\n"
else:
@ -210,8 +232,11 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
z = []
w = []
for _ in range(entry_count):
a,b,c,d = unpack_from(">ffff", entry_list, pos)
x.append(f"{a:.01f}"); y.append(f"{b:.01f}"); z.append(f"{c:.01f}"); w.append(f"{d:.01f}")
a, b, c, d = unpack_from(">ffff", entry_list, pos)
x.append(f"{a:.01f}")
y.append(f"{b:.01f}")
z.append(f"{c:.01f}")
w.append(f"{d:.01f}")
pos += 16
x_size = max([len(a) for a in x])
@ -219,7 +244,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
z_size = max([len(a) for a in z])
w_size = max([len(a) for a in w])
for a,b,c,d in zip(x,y,z,w):
for a, b, c, d in zip(x, y, z, w):
out += f"\n {{ {a:>{x_size}}f, {b:>{y_size}}f, {c:>{z_size}}f, {d:>{w_size}}f }},"
out += f"\n}};\n"
@ -233,16 +258,29 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
var_names = ["unk_00", "unk_24"]
data = unpack_from(">4B", npcSettings, i)
if not sum(data) == 0:
tmp_out += INDENT + f".{var_names[0] if i == 0 else var_names[1]} = {{ " + ", ".join(f"0x{x:02X}" for x in data) + f" }},\n"
tmp_out += (
INDENT
+ f".{var_names[0] if i == 0 else var_names[1]} = {{ "
+ ", ".join(f"0x{x:02X}" for x in data)
+ f" }},\n"
)
elif i == 0x4 or i == 0x28:
var_names = ["height", "radius", "level", "unk_2A"]
for x,var in enumerate(unpack_from(">2h", npcSettings, i)):
var_name = var_names[x if i == 0x4 else x+2]
for x, var in enumerate(unpack_from(">2h", npcSettings, i)):
var_name = var_names[x if i == 0x4 else x + 2]
if not var == 0:
tmp_out += INDENT + f".{var_name} = {var},\n"
elif i == 0x8:
var_names = ["otherAI", "onInteract", "ai", "onHit", "aux", "onDefeat", "flags"]
for x,var in enumerate(unpack_from(f">7I", npcSettings, i)):
var_names = [
"otherAI",
"onInteract",
"ai",
"onHit",
"aux",
"onDefeat",
"flags",
]
for x, var in enumerate(unpack_from(f">7I", npcSettings, i)):
var_name = var_names[x]
if not var == 0:
if var == 0x80077F70:
@ -264,8 +302,20 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
npcAISettings = bytes.read(struct["length"])
i = x = 0
var_names = ["moveSpeed", "moveTime", "waitTime", "alertRadius", "unk_10", "unk_14",
"chaseSpeed", "unk_1C", "unk_20", "chaseRadius", "unk_28", "unk_2C"]
var_names = [
"moveSpeed",
"moveTime",
"waitTime",
"alertRadius",
"unk_10",
"unk_14",
"chaseSpeed",
"unk_1C",
"unk_20",
"chaseRadius",
"unk_28",
"unk_2C",
]
while i < struct["length"]:
var_f, var_i1, var_i2 = unpack_from(f">fii", npcAISettings, i)
if not var_f == 0:
@ -274,7 +324,10 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
# account for X32
if var_names[x + 1] in ["unk_10", "unk_1C", "unk_28"]:
if var_i1 < -100000 or var_i1 > 100000:
tmp_out += INDENT + f".{var_names[x + 1]} = {{ .f = {unpack_from('>f', npcAISettings, i+4)[0]:.01f}f }},\n"
tmp_out += (
INDENT
+ f".{var_names[x + 1]} = {{ .f = {unpack_from('>f', npcAISettings, i+4)[0]:.01f}f }},\n"
)
else:
tmp_out += INDENT + f".{var_names[x + 1]} = {{ .s = {var_i1} }},\n"
else:
@ -289,32 +342,56 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
elif struct["type"] == "NpcGroup":
staticNpc = bytes.read(struct["length"])
curr_base = 0
numNpcs = struct['length'] // 0x1F0
numNpcs = struct["length"] // 0x1F0
tmp_out = f"NpcData {name}" + ("[]" if numNpcs > 1 else "") + f" = {{\n"
for z in range(numNpcs):
i = 0
var_names = ["id", "settings", "pos", "flags",
"init", "unk_1C", "yaw", "dropFlags",
"itemDropChance", "itemDrops", "heartDrops", "flowerDrops",
"minCoinBonus", "maxCoinBonus", "movement", "animations",
"unk_1E0", "extraAnimations", "tattle"]
var_names = [
"id",
"settings",
"pos",
"flags",
"init",
"unk_1C",
"yaw",
"dropFlags",
"itemDropChance",
"itemDrops",
"heartDrops",
"flowerDrops",
"minCoinBonus",
"maxCoinBonus",
"movement",
"animations",
"unk_1E0",
"extraAnimations",
"tattle",
]
if numNpcs > 1:
tmp_out += INDENT + f"{{\n"
INDENT = INDENT*2
INDENT = INDENT * 2
while i < 0x1F0:
if i == 0x0 or i == 0x24:
var_name = var_names[0] if i == 0x0 else var_names[6]
var = unpack_from(f">i", staticNpc, curr_base+i)[0]
var = unpack_from(f">i", staticNpc, curr_base + i)[0]
if var_name == "id":
tmp_out += INDENT + f".{var_name} = {disasm_script.CONSTANTS['MAP_NPCS'][var]},\n"
else:
tmp_out += INDENT + f".{var_name} = {var},\n"
elif i == 0x4 or i == 0x14 or i == 0x18 or i == 0x1E8:
var_name = var_names[1] if i == 0x4 else var_names[3] if i == 0x14 else var_names[4] if i == 0x18 else var_names[17]
addr = unpack_from(f">I", staticNpc, curr_base+i)[0]
var_name = (
var_names[1]
if i == 0x4
else var_names[3]
if i == 0x14
else var_names[4]
if i == 0x18
else var_names[17]
)
addr = unpack_from(f">I", staticNpc, curr_base + i)[0]
if not addr == 0:
if var_name != "flags" and addr in symbol_map:
if var_name == "extraAnimations":
@ -338,17 +415,17 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
enabled.append(0)
tmp_out += INDENT + f".{var_name} = " + " | ".join(enabled) + f",\n"
elif i == 0x8:
pos = unpack_from(f">fff", staticNpc, curr_base+i)
pos = unpack_from(f">fff", staticNpc, curr_base + i)
if not sum(pos) == 0:
tmp_out += INDENT + f".pos = {{ {pos[0]:.01f}f, {pos[1]:.01f}f, {pos[2]:.01f}f }},\n"
elif i == 0x1C or i == 0x1E0:
var_name = var_names[5] if i == 0x1C else var_names[16]
data = unpack_from(f">8B", staticNpc, curr_base+i)
data = unpack_from(f">8B", staticNpc, curr_base + i)
if not sum(data) == 0:
tmp_out += INDENT + f".{var_name} = {{ " + ", ".join(f"{x:02X}" for x in data) + f"}},\n"
elif i == 0x28 or i == 0x29:
var_name = var_names[7] if i == 0x28 else var_names[8]
var = unpack_from(f">b", staticNpc, curr_base+i)[0]
var = unpack_from(f">b", staticNpc, curr_base + i)[0]
if not var == 0:
if var_name == "dropFlags":
tmp_out += INDENT + f".{var_name} = 0x{abs(var):02X},\n"
@ -358,10 +435,14 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
var_name = var_names[9]
tmp_tmp = ""
for x in range(8):
item, weight, unk_08 = unpack_from(f">3h", staticNpc, curr_base+i)
item, weight, unk_08 = unpack_from(f">3h", staticNpc, curr_base + i)
if not (item == 0 and weight == 0 and unk_08 == 0):
item = disasm_script.CONSTANTS["ItemIDs"][item] if item in disasm_script.CONSTANTS["ItemIDs"] else f"{item}"
tmp_tmp += INDENT*2 + f"{{ {item}, {weight}, {unk_08} }},\n"
item = (
disasm_script.CONSTANTS["ItemIDs"][item]
if item in disasm_script.CONSTANTS["ItemIDs"]
else f"{item}"
)
tmp_tmp += INDENT * 2 + f"{{ {item}, {weight}, {unk_08} }},\n"
i += 0x6
if tmp_tmp:
@ -374,7 +455,12 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
var_name = var_names[10] if i == 0x5A else var_names[11]
drops = []
for x in range(8):
cutoff, generalChance, attempts, chancePerAttempt = unpack_from(f">4h", staticNpc, curr_base+i)
(
cutoff,
generalChance,
attempts,
chancePerAttempt,
) = unpack_from(f">4h", staticNpc, curr_base + i)
if not (cutoff == 0 and generalChance == 0 and attempts == 0 and chancePerAttempt == 0):
drops.append([cutoff, generalChance, attempts, chancePerAttempt])
i += 0x8
@ -388,10 +474,16 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
tmp_out += f"GENEROUS_HEART_DROPS({drops[0][2]})"
elif round(drops[0][1] / 327.67, 2) == 80 and round(drops[0][3] / 327.67, 2) == 60:
tmp_out += f"GENEROUS_WHEN_LOW_HEART_DROPS({drops[0][2]})"
elif round(drops[0][0] / 327.67, 2) == 100 and round(drops[0][1] / 327.67, 2) == 0 and round(drops[0][2] / 327.67, 2) == 0:
elif (
round(drops[0][0] / 327.67, 2) == 100
and round(drops[0][1] / 327.67, 2) == 0
and round(drops[0][2] / 327.67, 2) == 0
):
tmp_out += f"NO_DROPS"
else:
print(f"Unknown heart drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}")
print(
f"Unknown heart drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}"
)
exit()
else:
if round(drops[0][1] / 327.67, 2) == 50 and round(drops[0][3] / 327.67, 2) == 40:
@ -400,56 +492,70 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
tmp_out += f"GENEROUS_WHEN_LOW_FLOWER_DROPS({drops[0][2]})"
elif round(drops[0][1] / 327.67, 2) == 40 and round(drops[0][3] / 327.67, 2) == 40:
tmp_out += f"REDUCED_FLOWER_DROPS({drops[0][2]})"
elif round(drops[0][0] / 327.67, 2) == 100 and round(drops[0][1] / 327.67, 2) == 0 and round(drops[0][2] / 327.67, 2) == 0:
elif (
round(drops[0][0] / 327.67, 2) == 100
and round(drops[0][1] / 327.67, 2) == 0
and round(drops[0][2] / 327.67, 2) == 0
):
tmp_out += f"NO_DROPS"
else:
print(f"Unknown flower drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}")
print(
f"Unknown flower drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}"
)
exit()
tmp_out += f",\n"
elif i == 0xDA or i == 0xDC:
var_name = var_names[12] if i == 0xDA else var_names[13]
var = unpack_from(">h", staticNpc, curr_base+i)[0]
var = unpack_from(">h", staticNpc, curr_base + i)[0]
if not var == 0:
tmp_out += INDENT + f".{var_name} = {var},\n"
elif i == 0xE0:
data = unpack_from(">48i", staticNpc, curr_base+i)
data = unpack_from(">48i", staticNpc, curr_base + i)
if not sum(data) == 0:
end_pos = len(data)
for x,datum in enumerate(data):
for x, datum in enumerate(data):
if not datum == 0:
end_pos = x
tmp_out += INDENT + f".territory = { .temp = {{ " + ", ".join(f"{x}" for x in data[:end_pos+1]) + f" }}},\n"
tmp_out += (
INDENT
+ f".territory = {{ .temp = {{ "
+ ", ".join(f"{x}" for x in data[: end_pos + 1])
+ f" }}}},\n"
)
elif i == 0x1A0:
tmp_out += INDENT + f".{var_names[15]} = {{\n"
for x in range(16):
anim = unpack_from(">I", staticNpc, curr_base+i)[0]
anim = unpack_from(">I", staticNpc, curr_base + i)[0]
if not anim == 0:
sprite_id = (anim & 0x00FF0000) >> 16
sprite_id = (anim & 0x00FF0000) >> 16
palette_id = (anim & 0x0000FF00) >> 8
anim_id = (anim & 0x000000FF) >> 0
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
anim_id = (anim & 0x000000FF) >> 0
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
palette = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["palettes"][palette_id]
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
if numNpcs > 1:
tmp_out += INDENT + " " + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
else:
tmp_out += INDENT*2 + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
tmp_out += INDENT * 2 + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
INCLUDES_NEEDED["sprites"].add(sprite)
i += 4
tmp_out += INDENT + f"}},\n"
i -= 1
elif i == 0x1EC:
var = unpack_from(">I", staticNpc, curr_base+i)[0]
var = unpack_from(">I", staticNpc, curr_base + i)[0]
if not var == 0:
tmp_out += INDENT + f".{var_names[18]} = MESSAGE_ID(0x{(var & 0xFF0000) >> 16:02X}, 0x{var & 0xFFFF:04X}),\n"
tmp_out += (
INDENT
+ f".{var_names[18]} = MESSAGE_ID(0x{(var & 0xFF0000) >> 16:02X}, 0x{var & 0xFFFF:04X}),\n"
)
i += 1
if numNpcs > 1:
INDENT = INDENT[:len(INDENT)//2]
INDENT = INDENT[: len(INDENT) // 2]
tmp_out += INDENT + f"}},\n"
if z+1 == numNpcs:
if z + 1 == numNpcs:
tmp_out += "};\n"
curr_base += 0x1F0
@ -464,12 +570,12 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
if anim == 0xFFFFFFFF:
tmp_out += INDENT + f"ANIM_LIST_END,\n"
elif not anim == 0:
sprite_id = (anim & 0x00FF0000) >> 16
sprite_id = (anim & 0x00FF0000) >> 16
palette_id = (anim & 0x0000FF00) >> 8
anim_id = (anim & 0x000000FF) >> 0
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
anim_id = (anim & 0x000000FF) >> 0
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
palette = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["palettes"][palette_id]
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
tmp_out += INDENT + f"NPC_ANIM_{sprite}_{palette}_{anim},\n"
INCLUDES_NEEDED["sprites"].add(sprite)
i += 4
@ -487,7 +593,10 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
battle_b = (battle & 0x00FF0000) >> 16
battle_c = (battle & 0x0000FF00) >> 8
battle_d = (battle & 0x000000FF) >> 0
tmp_out += INDENT + f"NPC_GROUP({symbol_map[npcs][0][1]}, BATTLE_ID({battle_a}, {battle_b}, {battle_c}, {battle_d})),\n"
tmp_out += (
INDENT
+ f"NPC_GROUP({symbol_map[npcs][0][1]}, BATTLE_ID({battle_a}, {battle_b}, {battle_c}, {battle_d})),\n"
)
if symbol_map[npcs][0][1] not in INCLUDED["functions"]:
INCLUDES_NEEDED["forward"].append(symbol_map[npcs][0][1])
i += 0xC
@ -502,7 +611,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += f" {disasm_script.CONSTANTS['ItemIDs'][item]},\n"
out += f"}};\n"
elif struct["type"] == "TreeDropList":
new_name = "N(" + name.split('_',1)[1][:-1].lower() + "_Drops)"
new_name = "N(" + name.split("_", 1)[1][:-1].lower() + "_Drops)"
symbol_map[struct["vaddr"]][0][1] = new_name
out += f"FoliageDropList {new_name} = {{\n"
@ -518,7 +627,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
pos = 4
for _ in range(count):
entry = list(unpack_from(">7I", data, pos))
pos += 7*4
pos += 7 * 4
entry[1] = entry[1] - 0x100000000 if entry[1] >= 0x80000000 else entry[1]
entry[2] = entry[2] - 0x100000000 if entry[2] >= 0x80000000 else entry[2]
@ -532,9 +641,9 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += f"{INDENT * 3}.pos = {{ {entry[1]}, {entry[2]}, {entry[3]} }},\n"
if entry[4] != 0:
out += f"{INDENT * 3}.spawnMode = 0x{entry[4]:X},\n"
if flag1 != '0':
if flag1 != "0":
out += f"{INDENT * 3}.pickupFlag = {flag1},\n"
if flag2 != '0':
if flag2 != "0":
out += f"{INDENT * 3}.spawnFlag = {flag2},\n"
out += f"{INDENT * 2}}},\n"
@ -546,7 +655,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
elif struct["type"] == "TreeModelList" or struct["type"] == "TreeEffectVectors":
isModelList = struct["type"] == "TreeModelList"
name_parts = name.split('_')
name_parts = name.split("_")
if isModelList:
new_name = "N(" + name_parts[1].lower() + "_" + name_parts[2]
else:
@ -590,7 +699,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
entry[1] = entry[1] - 0x100000000 if entry[1] >= 0x80000000 else entry[1]
entry[2] = entry[2] - 0x100000000 if entry[2] >= 0x80000000 else entry[2]
pos += 3*4
pos += 3 * 4
out += f"{INDENT * 2}{{ {entry[0]}, {entry[1]}, {entry[2]} }},\n"
@ -600,10 +709,10 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += f"}};\n"
elif struct["type"] == "SearchBushEvent":
new_name = "N(" + name.split('_',1)[1].lower()
new_name = "N(" + name.split("_", 1)[1].lower()
symbol_map[struct["vaddr"]][0][1] = new_name
num = int(new_name.split("bush",1)[1][:-1])
num = int(new_name.split("bush", 1)[1][:-1])
out += f"SearchBushConfig {new_name} = {{\n"
data = bytes.read(struct["length"])
@ -621,10 +730,10 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += f"}};\n"
elif struct["type"] == "ShakeTreeEvent":
new_name = "N(" + name.split('_',1)[1].lower()
new_name = "N(" + name.split("_", 1)[1].lower()
symbol_map[struct["vaddr"]][0][1] = new_name
num = int(new_name.split("tree",1)[1][:-1])
num = int(new_name.split("tree", 1)[1][:-1])
out += f"ShakeTreeConfig {new_name} = {{\n"
data = bytes.read(struct["length"])
@ -656,20 +765,22 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
bytes.read(0x10)
main,entry_list,entry_count = unpack(">IIi", bytes.read(4 * 3))
main, entry_list, entry_count = unpack(">IIi", bytes.read(4 * 3))
out += f" .main = &N(main),\n"
out += f" .entryList = &{entry_list_name},\n"
out += f" .entryCount = ENTRY_COUNT({entry_list_name}),\n"
bytes.read(0x1C)
bg,tattle = unpack(">II", bytes.read(4 * 2))
bg, tattle = unpack(">II", bytes.read(4 * 2))
if bg == 0x80200000:
out += f" .background = &gBackgroundImage,\n"
elif bg != 0:
raise Exception(f"unknown MapSettings background {bg:X}")
#out += f" .tattle = 0x{tattle:X},\n"
INCLUDES_NEEDED["tattle"].append(f"- [0x{(tattle & 0xFF0000) >> 16:02X}, 0x{tattle & 0xFFFF:04X}, {map_name}_tattle]")
# out += f" .tattle = 0x{tattle:X},\n"
INCLUDES_NEEDED["tattle"].append(
f"- [0x{(tattle & 0xFF0000) >> 16:02X}, 0x{tattle & 0xFFFF:04X}, {map_name}_tattle]"
)
out += f" .tattle = {{ MSG_{map_name}_tattle }},\n"
out += f"}};\n"
@ -678,7 +789,7 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
bytes.read(struct["length"])
out += f"s32 {name}();\n"
elif struct["type"] == "FloatTable":
vram = int(name.split("_",1)[1][:-1], 16)
vram = int(name.split("_", 1)[1][:-1], 16)
name = f"N(D_{vram:X}_{(vram - 0x80240000) + romstart:X})"
struct["name"] = name
out += f"f32 {name}[] = {{"
@ -695,10 +806,10 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
if len(data) > 0:
out += f"Vec3f {name}[] = {{\n"
out += f"\t"
for i,pos in enumerate(range(0, len(data), 0xC)):
for i, pos in enumerate(range(0, len(data), 0xC)):
x, y, z = unpack_from(">fff", data, pos)
out += f" {{ {x:.01f}, {y:.01f}, {z:.01f} }},"
if (i+1) % 2 == 0:
if (i + 1) % 2 == 0:
out += f"\n\t"
out += f"\n}};\n"
@ -721,7 +832,6 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
else:
out += f".actor = {actor}, "
if position in symbol_map:
out += f".home = {{ .vec = &{symbol_map[position][0][1]} }}"
@ -829,11 +939,11 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
element = disasm_script.CONSTANTS["Statuses"][element]
value = (anim & 0x00FFFFFF)
value = anim & 0x00FFFFFF
if value in disasm_script.CONSTANTS["NPC_SPRITE"]:
INCLUDES_NEEDED["sprites"].add(disasm_script.CONSTANTS['NPC_SPRITE'][str(value) + ".h"])
anim = disasm_script.CONSTANTS['NPC_SPRITE'][value]
INCLUDES_NEEDED["sprites"].add(disasm_script.CONSTANTS["NPC_SPRITE"][str(value) + ".h"])
anim = disasm_script.CONSTANTS["NPC_SPRITE"][value]
else:
anim = f"{anim:06X}"
@ -851,15 +961,15 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += INDENT + "{\n"
out += INDENT + INDENT + f".flags = {read_flags(d[0], 'ActorPartFlags')},\n"
out += INDENT + INDENT + f".index = {d[1]},\n"
out += INDENT + INDENT + f".posOffset = {{ {d[2]}, {d[3]}, {d[4]} }},\n"
out += INDENT + INDENT + f".targetOffset = {{ {d[5]}, {d[6]} }},\n"
out += INDENT + INDENT + f".opacity = {d[7]},\n"
out += INDENT + INDENT + f".idleAnimations = N(IdleAnimations_{d[8]:08X}),\n"
out += INDENT + INDENT + f".defenseTable = N(DefenseTable_{d[9]:08X}),\n"
out += INDENT + INDENT + f".eventFlags = {read_flags(d[10], 'ActorEventFlags')},\n"
out += INDENT + INDENT + f".elementImmunityFlags = {read_flags(d[11], 'ElementImmunityFlags')},\n"
out += INDENT + INDENT + f".unk_1C = {d[12]},\n"
out += INDENT + INDENT + f".unk_1D = {d[13]},\n"
out += INDENT + INDENT + f".posOffset = {{ {d[2]}, {d[3]}, {d[4]} }},\n"
out += INDENT + INDENT + f".targetOffset = {{ {d[5]}, {d[6]} }},\n"
out += INDENT + INDENT + f".opacity = {d[7]},\n"
out += INDENT + INDENT + f".idleAnimations = N(IdleAnimations_{d[8]:08X}),\n"
out += INDENT + INDENT + f".defenseTable = N(DefenseTable_{d[9]:08X}),\n"
out += INDENT + INDENT + f".eventFlags = {read_flags(d[10], 'ActorEventFlags')},\n"
out += INDENT + INDENT + f".elementImmunityFlags = {read_flags(d[11], 'ElementImmunityFlags')},\n"
out += INDENT + INDENT + f".unk_1C = {d[12]},\n"
out += INDENT + INDENT + f".unk_1D = {d[13]},\n"
out += INDENT + "},\n"
out += f"}};\n"
@ -895,7 +1005,18 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
elif struct["type"] == "Stage":
out += f"Stage NAMESPACE = {{\n"
texture, shape, hit, preBattle, postBattle, bg, unk_18, unk_1C, unk_20, unk_24 = unpack(">IIIIIIIIII", bytes.read(struct["length"]))
(
texture,
shape,
hit,
preBattle,
postBattle,
bg,
unk_18,
unk_1C,
unk_20,
unk_24,
) = unpack(">IIIIIIIIII", bytes.read(struct["length"]))
if texture != 0:
out += f" .texture = {symbol_map[texture][0][1]},\n"
@ -928,9 +1049,9 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
out += f" .unk_24 = {unk_24:X},\n"
out += f"}};\n"
else: # unknown type of struct
else: # unknown type of struct
if struct["name"].startswith("N(unk_802"):
vram = int(name.split("_",1)[1][:-1], 16)
vram = int(name.split("_", 1)[1][:-1], 16)
name = f"N(D_{vram:X}_{(vram - 0x80240000) + romstart:X})"
struct["name"] = name
@ -957,53 +1078,63 @@ def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace
# end of data
return out
def parse_midx(file, prefix="", vram=0x80240000):
structs = []
for line in file.readlines():
s = line.split("#")
if len(s) == 5 or len(s) == 6:
if s[0] == "$Start": continue
if s[0] == "$End": continue
if s[0] == "$Start":
continue
if s[0] == "$End":
continue
structs.append({
"name": "N(" + prefix + name_struct(s[0]) + ")",
"type": s[1],
"start": int(s[2], 16),
"vaddr": int(s[3], 16),
"length": int(s[4], 16),
"end": int(s[2], 16) + int(s[4], 16),
})
structs.append(
{
"name": "N(" + prefix + name_struct(s[0]) + ")",
"type": s[1],
"start": int(s[2], 16),
"vaddr": int(s[3], 16),
"length": int(s[4], 16),
"end": int(s[2], 16) + int(s[4], 16),
}
)
elif "Missing" in s:
start = int(s[1], 16)
end = int(s[2], 16)
vaddr = start + vram
structs.append({
"name": f"{prefix}unk_missing_{vaddr:X}",
"type": "Missing",
"start": start,
"vaddr": vaddr,
"length": end - start,
"end": end,
})
structs.append(
{
"name": f"{prefix}unk_missing_{vaddr:X}",
"type": "Missing",
"start": start,
"vaddr": vaddr,
"length": end - start,
"end": end,
}
)
elif "Padding" in s:
start = int(s[1], 16)
end = int(s[2], 16)
vaddr = start + vram
structs.append({
"name": f"{prefix}pad_{start:X}",
"type": "Padding",
"start": start,
"vaddr": vaddr,
"length": end - start,
"end": end,
})
structs.append(
{
"name": f"{prefix}pad_{start:X}",
"type": "Padding",
"start": start,
"vaddr": vaddr,
"length": end - start,
"end": end,
}
)
else:
raise Exception(str(s))
structs.sort(key=lambda s: s["start"])
return structs
def name_struct(s):
s = s[1:].replace("???", "unk")
@ -1032,10 +1163,11 @@ def name_struct(s):
return s[0].lower() + s[1:]
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Converts split data to C using a Star Rod idx file")
parser.add_argument("idxfile", help="Input .*idx file from Star Rod dump")
parser.add_argument("namespace", nargs='?', help="Value of NAMESPACE macro")
parser.add_argument("namespace", nargs="?", help="Value of NAMESPACE macro")
parser.add_argument("--comments", action="store_true", help="Write offset/vaddr comments")
args = parser.parse_args()
@ -1053,8 +1185,7 @@ if __name__ == "__main__":
segment_name = f"battle/partners/{battle_area}"
elif "/starpower/src/" in args.idxfile:
segment_name = (
f"battle/star/{battle_area}"
.replace("starstorm", "star_storm")
f"battle/star/{battle_area}".replace("starstorm", "star_storm")
.replace("chillout", "chill_out")
.replace("timeout", "time_out")
.replace("upandaway", "up_and_away")
@ -1069,6 +1200,7 @@ if __name__ == "__main__":
is_battle = True
symbol_map = disasm_script.script_lib()
def add_to_symbol_map(addr, pair):
if addr in symbol_map:
symbol_map[addr].append(pair)
@ -1082,7 +1214,9 @@ if __name__ == "__main__":
rom_offset = -1
for segment in splat_config["segments"]:
if isinstance(segment, dict) and (segment.get("dir") == segment_name or segment.get("name") == segment_name):
if isinstance(segment, dict) and (
segment.get("dir") == segment_name or segment.get("name") == segment_name
):
rom_offset = segment["start"]
vram = segment["vram"]
break
@ -1102,11 +1236,11 @@ if __name__ == "__main__":
with open(os.path.join(DIR, "../ver/current/baserom.z64"), "rb") as romfile:
name_fixes = {
"script_NpcAI": "npcAI",
"aISettings": "npcAISettings",
"script_ExitWalk": "exitWalk",
"script_MakeEntities": "makeEntities",
}
"script_NpcAI": "npcAI",
"aISettings": "npcAISettings",
"script_ExitWalk": "exitWalk",
"script_MakeEntities": "makeEntities",
}
total_npc_counts = {}
for struct in midx:
romfile.seek(struct["start"] + rom_offset)
@ -1116,13 +1250,13 @@ if __name__ == "__main__":
if name.startswith("N("):
name = name[2:-1]
if struct['vaddr'] in function_replacements:
name = function_replacements[struct['vaddr']]
if struct["vaddr"] in function_replacements:
name = function_replacements[struct["vaddr"]]
if name.split("_",1)[0] in name_fixes:
name = name_fixes[name.split("_",1)[0]] + "_" + name.rsplit("_",1)[1]
if name.split("_", 1)[0] in name_fixes:
name = name_fixes[name.split("_", 1)[0]] + "_" + name.rsplit("_", 1)[1]
elif name.startswith("script_"):
name = name.split("script_",1)[1]
name = name.split("script_", 1)[1]
elif "_Main_" in name:
name = "main"
elif "ASCII" in name:
@ -1154,19 +1288,19 @@ if __name__ == "__main__":
double_literal = f"{double}"
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], double_literal])
elif struct["type"] == "NpcGroup":
for z in range(struct["length"]//0x1F0):
for z in range(struct["length"] // 0x1F0):
npc = romfile.read(0x1F0)
npc_id = unpack_from(">I", npc, 0)[0]
if npc_id >= 0:
anim = unpack_from(">I", npc, 0x1A0)[0]
if not anim == 0:
sprite_id = (anim & 0x00FF0000) >> 16
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"].upper()
sprite_id = (anim & 0x00FF0000) >> 16
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"].upper()
if npc_id not in total_npc_counts:
total_npc_counts[npc_id] = sprite
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], struct["name"]])
else:
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], struct["name"]])
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], struct["name"]])
# fix NPC names
curr_counts = {}
@ -1186,17 +1320,24 @@ if __name__ == "__main__":
romfile.seek(rom_offset, 0)
disasm = disassemble(romfile, midx, symbol_map, args.comments, rom_offset, namespace=args.namespace)
disasm = disassemble(
romfile,
midx,
symbol_map,
args.comments,
rom_offset,
namespace=args.namespace,
)
print("========== Includes needed: ===========\n")
if is_battle:
print(f"#include \"battle/battle.h\"")
print(f'#include "battle/battle.h"')
else:
print(f"#include \"map.h\"")
print(f"#include \"message_ids.h\"")
print(f'#include "map.h"')
print(f'#include "message_ids.h"')
if INCLUDES_NEEDED["sprites"]:
for npc in sorted(INCLUDES_NEEDED["sprites"]):
print(f"#include \"sprite/npc/{npc}\"")
print(f'#include "sprite/npc/{npc}"')
print()
if INCLUDES_NEEDED["forward"]:
@ -1213,7 +1354,7 @@ if __name__ == "__main__":
print(f"enum {{")
lastnum = -1
for i, (k, v) in enumerate(sorted(INCLUDES_NEEDED["npcs"].items())):
print(f" {v}" + (f" = {k}" if ((k > 0 and i == 0) or (k != lastnum+1)) else "") + ",")
print(f" {v}" + (f" = {k}" if ((k > 0 and i == 0) or (k != lastnum + 1)) else "") + ",")
lastnum = k
print(f"}};")
print()

View File

@ -6,29 +6,24 @@ import argparse
script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.abspath(os.path.join(script_dir, ".."))
parser = argparse.ArgumentParser(
description="Display various information about a symbol or address."
)
parser.add_argument(
"name",
type=str,
default="",
help="symbol name or ROM/RAM address to lookup"
)
parser = argparse.ArgumentParser(description="Display various information about a symbol or address.")
parser.add_argument("name", type=str, default="", help="symbol name or ROM/RAM address to lookup")
parser.add_argument(
"-e",
"--expected",
dest="use_expected",
action="store_true",
help="use the map file in expected/build/ instead of build/"
help="use the map file in expected/build/ instead of build/",
)
def get_map(expected: bool = False):
mymap = os.path.join(root_dir, "ver", "current", "build", "papermario.map")
if expected:
mymap = os.path.join(root_dir, "ver", "current", "expected", "build", "papermario.map")
return mymap
def search_address(target_addr, map=get_map()):
is_ram = target_addr & 0x80000000
ram_offset = None
@ -52,12 +47,7 @@ def search_address(target_addr, map=get_map()):
prev_line = line
if (
ram_offset is None
or "=" in line
or "*fill*" in line
or " 0x" not in line
):
if ram_offset is None or "=" in line or "*fill*" in line or " 0x" not in line:
continue
ram = int(line[16 : 16 + 18], 0)
@ -84,6 +74,7 @@ def search_address(target_addr, map=get_map()):
return "at end of rom?"
def search_symbol(target_sym, map=get_map()):
ram_offset = None
cur_file = "<no file>"
@ -98,12 +89,7 @@ def search_symbol(target_sym, map=get_map()):
prev_line = line
if (
ram_offset is None
or "=" in line
or "*fill*" in line
or " 0x" not in line
):
if ram_offset is None or "=" in line or "*fill*" in line or " 0x" not in line:
continue
ram = int(line[16 : 16 + 18], 0)
@ -122,6 +108,7 @@ def search_symbol(target_sym, map=get_map()):
return None
if __name__ == "__main__":
args = parser.parse_args()

View File

@ -26,6 +26,7 @@ ignores = set()
verbose = False
def read_ignores():
with open(ignores_path) as f:
lines = f.readlines()
@ -35,6 +36,7 @@ def read_ignores():
if name != "":
ignores.add(name)
def scan_map():
ram_offset = None
cur_file = "<no file>"
@ -49,12 +51,7 @@ def scan_map():
prev_line = line
if (
ram_offset is None
or "=" in line
or "*fill*" in line
or " 0x" not in line
):
if ram_offset is None or "=" in line or "*fill*" in line or " 0x" not in line:
continue
ram = int(line[16 : 16 + 18], 0)
@ -70,12 +67,13 @@ def scan_map():
map_symbols[sym] = (rom, cur_file, ram)
def read_symbol_addrs():
unique_lines = set()
with open(symbol_addrs_path, "r") as f:
for line in f.readlines():
unique_lines.add(line)
unique_lines.add(line)
for line in unique_lines:
if "_ROM_START" in line or "_ROM_END" in line:
@ -117,9 +115,10 @@ def read_symbol_addrs():
else:
dead_symbols.append([name, int(addr, 0), type, rom, opts])
def read_elf():
try:
result = subprocess.run(['mips-linux-gnu-objdump', '-x', elf_path], stdout=subprocess.PIPE)
result = subprocess.run(["mips-linux-gnu-objdump", "-x", elf_path], stdout=subprocess.PIPE)
objdump_lines = result.stdout.decode().split("\n")
except:
print(f"Error: Could not run objdump on {elf_path} - make sure that the project is built")
@ -133,13 +132,15 @@ def read_elf():
if "_ROM_START" in name or "_ROM_END" in name:
continue
if "/" in name or \
"." in name or \
name in ignores or \
name.startswith("_") or \
name.startswith("jtbl_") or \
name.endswith(".o") or \
re.match(r"L[0-9A-F]{8}", name):
if (
"/" in name
or "." in name
or name in ignores
or name.startswith("_")
or name.startswith("jtbl_")
or name.endswith(".o")
or re.match(r"L[0-9A-F]{8}", name)
):
continue
addr = int(components[0], 16)
@ -153,14 +154,16 @@ def read_elf():
if name in map_symbols:
rom = map_symbols[name][0]
elif re.match(".*_[0-9A-F]{8}_[0-9A-F]{6}", name):
rom = int(name.split('_')[-1], 16)
rom = int(name.split("_")[-1], 16)
elf_symbols.append((name, addr, type, rom))
def log(s):
if verbose:
print(s)
def reconcile_symbols():
print(f"Processing {str(len(elf_symbols))} elf symbols...")
@ -175,7 +178,9 @@ def reconcile_symbols():
name_match = known_sym
if elf_sym[1] != known_sym[1]:
log(f"Ram mismatch! {elf_sym[0]} is 0x{elf_sym[1]:X} in the elf and 0x{known_sym[1]} in symbol_addrs")
log(
f"Ram mismatch! {elf_sym[0]} is 0x{elf_sym[1]:X} in the elf and 0x{known_sym[1]} in symbol_addrs"
)
# Rom
if not rom_match:
@ -186,7 +191,15 @@ def reconcile_symbols():
if not name_match and not rom_match:
log(f"Creating new symbol {elf_sym[0]}")
symbol_addrs.append([elf_sym[0], elf_sym[1], elf_sym[2], elf_sym[3] if elf_sym[3] else -1, []])
symbol_addrs.append(
[
elf_sym[0],
elf_sym[1],
elf_sym[2],
elf_sym[3] if elf_sym[3] else -1,
[],
]
)
elif not name_match:
log(f"Renaming identical rom address symbol {rom_match[0]} to {elf_sym[0]}")
rom_match[0] = elf_sym[0]
@ -197,6 +210,7 @@ def reconcile_symbols():
log(f"Adding rom address {elf_sym[3]} to symbol {name_match[0]}")
name_match[3] = elf_sym[3]
def write_new_symbol_addrs():
with open(symbol_addrs_path, "w", newline="\n") as f:
for symbol in sorted(symbol_addrs, key=lambda x: (x[3] == -1, x[3], x[1], x[0])):
@ -220,6 +234,7 @@ def write_new_symbol_addrs():
line += f" {thing}"
f.write(line + "\n")
read_ignores()
scan_map()
read_symbol_addrs()

View File

@ -11,8 +11,14 @@ def countFileLines(filename: str) -> int:
def main():
parser = argparse.ArgumentParser()
parser.add_argument('currentwarnings', help="Name of file which contains the current warnings of the repo.")
parser.add_argument('newwarnings', help="Name of file which contains the *new* warnings of the repo.")
parser.add_argument(
"currentwarnings",
help="Name of file which contains the current warnings of the repo.",
)
parser.add_argument(
"newwarnings",
help="Name of file which contains the *new* warnings of the repo.",
)
parser.add_argument("--pr-message", action="store_true")
args = parser.parse_args()