mirror of
https://github.com/pmret/papermario.git
synced 2024-11-08 12:02:30 +01:00
git subrepo pull tools/splat
subrepo: subdir: "tools/splat" merged: "687885c4d" upstream: origin: "https://github.com/ethteck/splat.git" branch: "master" commit: "687885c4d" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596"
This commit is contained in:
parent
dc72859546
commit
15de829b58
2
tools/splat/.gitignore
vendored
2
tools/splat/.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
venv/
|
||||
.vscode/
|
||||
__pycache__/
|
||||
util/Yay0decompress
|
||||
util/n64/Yay0decompress
|
||||
|
@ -6,7 +6,7 @@
|
||||
[subrepo]
|
||||
remote = https://github.com/ethteck/splat.git
|
||||
branch = master
|
||||
commit = 7574db712ef19ca420904c82d3559e9ac4b8c5f5
|
||||
parent = 86760369a5ab977c037c21aebf6f10484570642f
|
||||
commit = 687885c4d8bcb86d399dbb7dcd660ad8548acdf7
|
||||
parent = dc7285954620634e352b43bcae320ba1f8dd8de7
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
|
12
tools/splat/CHANGELOG.md
Normal file
12
tools/splat/CHANGELOG.md
Normal file
@ -0,0 +1,12 @@
|
||||
# splat Release Notes
|
||||
|
||||
## 0.5
|
||||
* n64splat name changed to splat
|
||||
* Some refactoring was done to support other platforms besides n64 in the future
|
||||
* New `platform` option, which defaults to `n64`
|
||||
* This will cause breaking changes in custom segments, so please refer to one of the changes in one of the n64 base segments for details
|
||||
* Support for custom artifact paths
|
||||
* New `undefined_syms_auto_path` option
|
||||
* New `undefined_funcs_auto_path` option
|
||||
* New `cache_path` option
|
||||
* (All path-like options' names now end with `_path`)
|
@ -5,7 +5,7 @@ default: all
|
||||
all: Yay0decompress
|
||||
|
||||
Yay0decompress:
|
||||
gcc $(UTIL_DIR)/Yay0decompress.c -fPIC -shared -O3 -o $(UTIL_DIR)/Yay0decompress
|
||||
gcc $(UTIL_DIR)/n64/Yay0decompress.c -fPIC -shared -O3 -o $(UTIL_DIR)/n64/Yay0decompress
|
||||
|
||||
clean:
|
||||
rm -f $(UTIL_DIR)/Yay0decompress
|
||||
rm -f $(UTIL_DIR)/n64/Yay0decompress
|
||||
|
@ -1,8 +1,10 @@
|
||||
# n64splat
|
||||
A n64 rom splitting tool to assist with decompilation and modding projects
|
||||
# splat
|
||||
A binary splitting tool to assist with decompilation and modding projects
|
||||
|
||||
For example usage, see https://github.com/ethteck/papermario
|
||||
The Makefile `setup` target calls n64splat with a config file that you can use for reference. More documentation coming soon.
|
||||
Currently, only N64 roms in the .z64 format are supported.
|
||||
|
||||
For example usage, see https://github.com/pmret/papermario
|
||||
The Makefile `setup` target calls splat with a config file that you can use for reference. More documentation coming soon.
|
||||
|
||||
### Requirements
|
||||
Python package requirements can be installed via `pip3 install -r requirements.txt`
|
||||
|
@ -1,13 +1,11 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
from capstone import *
|
||||
from capstone.mips import *
|
||||
|
||||
import argparse
|
||||
from util import rominfo
|
||||
from util.n64 import rominfo
|
||||
from util.n64 import find_code_length
|
||||
from segtypes.code import N64SegCode
|
||||
|
||||
parser = argparse.ArgumentParser(description="Create a splat config from a rom")
|
||||
parser = argparse.ArgumentParser(description="Create a splat config from a rom (currently only n64 .z64 roms supported)")
|
||||
parser.add_argument("rom", help="path to a .z64 rom")
|
||||
|
||||
|
||||
@ -25,12 +23,8 @@ options:
|
||||
|
||||
with open(rom_path, "rb") as f:
|
||||
fbytes = f.read()
|
||||
|
||||
rom_addr = 0x1000
|
||||
|
||||
md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + CS_MODE_BIG_ENDIAN)
|
||||
for insn in md.disasm(fbytes[rom_addr:], rom.entry_point):
|
||||
rom_addr += 4
|
||||
|
||||
first_section_end = find_code_length.run(fbytes, 0x1000, rom.entry_point)
|
||||
|
||||
segments = \
|
||||
"""segments:
|
||||
@ -52,7 +46,7 @@ options:
|
||||
- type: bin
|
||||
start: 0x{:X}
|
||||
- [0x{:X}]
|
||||
""".format(rom.entry_point, rom_addr, rom.size)
|
||||
""".format(rom.entry_point, first_section_end, rom.size)
|
||||
|
||||
outstr = header + segments
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
from segtypes.segment import N64Segment
|
||||
from pathlib import Path
|
||||
from util import Yay0decompress
|
||||
from segtypes.n64.segment import N64Segment
|
||||
from util.n64 import Yay0decompress
|
||||
|
||||
class N64SegYay0(N64Segment):
|
||||
def split(self, rom_bytes, base_path):
|
0
tools/splat/segtypes/n64/__init__.py
Normal file
0
tools/splat/segtypes/n64/__init__.py
Normal file
@ -1,5 +1,5 @@
|
||||
import os
|
||||
from segtypes.segment import N64Segment
|
||||
from segtypes.n64.segment import N64Segment
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from segtypes.ci8 import N64SegCi8
|
||||
from segtypes.n64.ci8 import N64SegCi8
|
||||
|
||||
class N64SegCi4(N64SegCi8):
|
||||
def parse_image(self, data):
|
@ -1,8 +1,7 @@
|
||||
from segtypes.segment import N64Segment
|
||||
from segtypes.rgba16 import N64SegRgba16
|
||||
from segtypes.n64.rgba16 import N64SegRgba16
|
||||
import png
|
||||
import os
|
||||
from util import Yay0decompress
|
||||
from util.n64 import Yay0decompress
|
||||
|
||||
|
||||
class N64SegCi8(N64SegRgba16):
|
@ -3,7 +3,7 @@ from capstone import *
|
||||
from capstone.mips import *
|
||||
|
||||
from collections import OrderedDict
|
||||
from segtypes.segment import N64Segment, parse_segment_name
|
||||
from segtypes.n64.segment import N64Segment
|
||||
import os
|
||||
from pathlib import Path, PurePath
|
||||
from ranges import Range, RangeDict
|
||||
@ -508,7 +508,7 @@ class N64SegCode(N64Segment):
|
||||
if sym_type in ["float", "double"]:
|
||||
if sym_type == "float":
|
||||
float_str = floats.format_f32_imm(bits)
|
||||
elif sym_type == "double":
|
||||
else:
|
||||
float_str = floats.format_f64_imm(bits)
|
||||
|
||||
# Fall back to .word if we see weird float values
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
from segtypes.segment import N64Segment
|
||||
from segtypes.n64.segment import N64Segment
|
||||
from pathlib import Path
|
||||
from util import rominfo
|
||||
|
||||
class N64SegHeader(N64Segment):
|
||||
def should_run(self):
|
@ -1,4 +1,4 @@
|
||||
from segtypes.rgba16 import N64SegRgba16
|
||||
from segtypes.n64.rgba16 import N64SegRgba16
|
||||
import png
|
||||
from math import ceil
|
||||
|
@ -1,4 +1,4 @@
|
||||
from segtypes.i4 import N64SegI4
|
||||
from segtypes.n64.i4 import N64SegI4
|
||||
from math import ceil
|
||||
|
||||
class N64SegI8(N64SegI4):
|
@ -1,4 +1,4 @@
|
||||
from segtypes.ia4 import N64SegIa4
|
||||
from segtypes.n64.ia4 import N64SegIa4
|
||||
|
||||
class N64SegIa8(N64SegIa4):
|
||||
def parse_image(self, data):
|
@ -1,5 +1,4 @@
|
||||
import os
|
||||
from segtypes.rgba16 import N64SegRgba16
|
||||
from segtypes.n64.rgba16 import N64SegRgba16
|
||||
import png
|
||||
from math import ceil
|
||||
|
@ -1,5 +1,4 @@
|
||||
from segtypes.ia4 import N64SegIa4
|
||||
import png
|
||||
from segtypes.n64.ia4 import N64SegIa4
|
||||
from math import ceil
|
||||
|
||||
class N64SegIa8(N64SegIa4):
|
@ -1,9 +1,9 @@
|
||||
import os
|
||||
from segtypes.segment import N64Segment
|
||||
from segtypes.n64.segment import N64Segment
|
||||
from util.color import unpack_color
|
||||
from util.n64 import Yay0decompress
|
||||
from util.iter import iter_in_groups
|
||||
|
||||
|
||||
class N64SegPalette(N64Segment):
|
||||
require_unique_name = False
|
||||
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
from segtypes.segment import N64Segment
|
||||
from pathlib import Path
|
||||
from util import Yay0decompress
|
||||
from segtypes.n64.segment import N64Segment
|
||||
from util.n64 import Yay0decompress
|
||||
import png
|
||||
from math import ceil
|
||||
from util.color import unpack_color
|
@ -1,4 +1,4 @@
|
||||
from segtypes.rgba16 import N64SegRgba16
|
||||
from segtypes.n64.rgba16 import N64SegRgba16
|
||||
|
||||
class N64SegRgba32(N64SegRgba16):
|
||||
def parse_image(self, data):
|
4
tools/splat/segtypes/n64/segment.py
Normal file
4
tools/splat/segtypes/n64/segment.py
Normal file
@ -0,0 +1,4 @@
|
||||
from segtypes.segment import Segment
|
||||
|
||||
class N64Segment(Segment):
|
||||
pass
|
@ -1,7 +1,4 @@
|
||||
import os
|
||||
from pathlib import Path, PurePath
|
||||
import re
|
||||
import json
|
||||
from util import log
|
||||
|
||||
default_subalign = 16
|
||||
@ -43,7 +40,7 @@ def parse_segment_subalign(segment):
|
||||
return default_subalign
|
||||
|
||||
|
||||
class N64Segment:
|
||||
class Segment:
|
||||
require_unique_name = True
|
||||
|
||||
def __init__(self, segment, next_segment, options):
|
||||
@ -52,8 +49,7 @@ class N64Segment:
|
||||
self.type = parse_segment_type(segment)
|
||||
self.name = parse_segment_name(segment, self.__class__)
|
||||
self.vram_addr = parse_segment_vram(segment)
|
||||
self.ld_name_override = segment.get(
|
||||
"ld_name", None) if type(segment) is dict else None
|
||||
self.ld_name_override = segment.get("ld_name", None) if type(segment) is dict else None
|
||||
self.options = options
|
||||
self.config = segment
|
||||
self.subalign = parse_segment_subalign(segment)
|
||||
|
@ -5,16 +5,12 @@ import importlib
|
||||
import importlib.util
|
||||
import os
|
||||
from ranges import Range, RangeDict
|
||||
import re
|
||||
from pathlib import Path
|
||||
import segtypes
|
||||
import sys
|
||||
import yaml
|
||||
import pickle
|
||||
from colorama import Style, Fore
|
||||
from collections import OrderedDict
|
||||
from segtypes.segment import N64Segment, parse_segment_type
|
||||
from segtypes.code import N64SegCode
|
||||
from segtypes.segment import parse_segment_type
|
||||
from segtypes.n64.code import N64SegCode
|
||||
from util import log
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
@ -81,11 +77,23 @@ def parse_file_start(split_file):
|
||||
|
||||
|
||||
def get_symbol_addrs_path(repo_path, options):
|
||||
return os.path.join(repo_path, options.get("symbol_addrs", "symbol_addrs.txt"))
|
||||
return os.path.join(repo_path, options.get("symbol_addrs_path", "symbol_addrs.txt"))
|
||||
|
||||
|
||||
def get_undefined_syms_path(repo_path, options):
|
||||
return os.path.join(repo_path, options.get("undefined_syms", "undefined_syms.txt"))
|
||||
return os.path.join(repo_path, options.get("undefined_syms_path", "undefined_syms.txt"))
|
||||
|
||||
|
||||
def get_undefined_syms_auto_path(repo_path, options):
|
||||
return os.path.join(repo_path, options.get("undefined_syms_auto_path", "undefined_syms_auto.txt"))
|
||||
|
||||
|
||||
def get_undefined_funcs_auto_path(repo_path, options):
|
||||
return os.path.join(repo_path, options.get("undefined_funcs_auto_path", "undefined_funcs_auto.txt"))
|
||||
|
||||
|
||||
def get_cache_path(repo_path, options):
|
||||
return os.path.join(repo_path, options.get("cache_path", ".splat_cache"))
|
||||
|
||||
|
||||
def gather_symbols(symbol_addrs_path, undefined_syms_path):
|
||||
@ -156,13 +164,13 @@ def gather_c_variables(undefined_syms_path):
|
||||
return vars
|
||||
|
||||
|
||||
def get_base_segment_class(seg_type):
|
||||
def get_base_segment_class(seg_type, platform):
|
||||
try:
|
||||
segmodule = importlib.import_module("segtypes." + seg_type)
|
||||
segmodule = importlib.import_module(f"segtypes.{platform}.{seg_type}")
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
||||
return getattr(segmodule, "N64Seg" + seg_type[0].upper() + seg_type[1:])
|
||||
return getattr(segmodule, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}")
|
||||
|
||||
|
||||
def get_extension_dir(options, config_path):
|
||||
@ -171,20 +179,20 @@ def get_extension_dir(options, config_path):
|
||||
return os.path.join(Path(config_path).parent, options["extensions"])
|
||||
|
||||
|
||||
def get_extension_class(options, config_path, seg_type):
|
||||
def get_extension_class(options, config_path, seg_type, platform):
|
||||
ext_dir = get_extension_dir(options, config_path)
|
||||
if ext_dir == None:
|
||||
return None
|
||||
|
||||
try:
|
||||
ext_spec = importlib.util.spec_from_file_location(f"segtypes.{seg_type}", os.path.join(ext_dir, f"{seg_type}.py"))
|
||||
ext_spec = importlib.util.spec_from_file_location(f"{platform}.segtypes.{seg_type}", os.path.join(ext_dir, f"{seg_type}.py"))
|
||||
ext_mod = importlib.util.module_from_spec(ext_spec)
|
||||
ext_spec.loader.exec_module(ext_mod)
|
||||
except Exception as err:
|
||||
log.write(err, status="error")
|
||||
return None
|
||||
|
||||
return getattr(ext_mod, "N64Seg" + seg_type[0].upper() + seg_type[1:])
|
||||
return getattr(ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}")
|
||||
|
||||
|
||||
def fmt_size(size):
|
||||
@ -196,7 +204,7 @@ def fmt_size(size):
|
||||
return str(size) + " B"
|
||||
|
||||
|
||||
def initialize_segments(options, config_path, config_segments):
|
||||
def initialize_segments(options, config_path, config_segments, platform):
|
||||
seen_segment_names = set()
|
||||
ret = []
|
||||
|
||||
@ -207,24 +215,16 @@ def initialize_segments(options, config_path, config_segments):
|
||||
|
||||
seg_type = parse_segment_type(segment)
|
||||
|
||||
segment_class = get_base_segment_class(seg_type)
|
||||
segment_class = get_base_segment_class(seg_type, platform)
|
||||
if segment_class == None:
|
||||
# Look in extensions
|
||||
segment_class = get_extension_class(options, config_path, seg_type)
|
||||
segment_class = get_extension_class(options, config_path, seg_type, platform)
|
||||
|
||||
if segment_class == None:
|
||||
log.write(f"fatal error: could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)", status="error")
|
||||
return 2
|
||||
|
||||
try:
|
||||
segment = segment_class(segment, config_segments[i + 1], options)
|
||||
except (IndexError, KeyError) as e:
|
||||
try:
|
||||
segment = N64Segment(segment, config_segments[i + 1], options)
|
||||
segment.error(e)
|
||||
except Exception as e:
|
||||
log.write(f"fatal error (segment type = {seg_type}): " + str(e), status="error")
|
||||
return 2
|
||||
segment = segment_class(segment, config_segments[i + 1], options)
|
||||
|
||||
if segment_class.require_unique_name:
|
||||
if segment.name in seen_segment_names:
|
||||
@ -254,6 +254,7 @@ def main(rom_path, config_path, repo_path, modes, verbose, ignore_cache=False):
|
||||
symbol_addrs_path = get_symbol_addrs_path(repo_path, options)
|
||||
undefined_syms_path = get_undefined_syms_path(repo_path, options)
|
||||
provided_symbols, c_func_labels_to_add, special_labels, ranges = gather_symbols(symbol_addrs_path, undefined_syms_path)
|
||||
platform = options.get("platform", "n64")
|
||||
|
||||
processed_segments = []
|
||||
ld_sections = []
|
||||
@ -267,7 +268,7 @@ def main(rom_path, config_path, repo_path, modes, verbose, ignore_cache=False):
|
||||
seg_cached = {}
|
||||
|
||||
# Load cache
|
||||
cache_path = Path(repo_path) / ".splat_cache"
|
||||
cache_path = get_cache_path(repo_path, options)
|
||||
try:
|
||||
with open(cache_path, "rb") as f:
|
||||
cache = pickle.load(f)
|
||||
@ -275,10 +276,10 @@ def main(rom_path, config_path, repo_path, modes, verbose, ignore_cache=False):
|
||||
cache = {}
|
||||
|
||||
# Initialize segments
|
||||
all_segments = initialize_segments(options, config_path, config["segments"])
|
||||
all_segments = initialize_segments(options, config_path, config["segments"], platform)
|
||||
|
||||
for segment in all_segments:
|
||||
if type(segment) == N64SegCode:
|
||||
if platform == "n64" and type(segment) == N64SegCode: # remove special-case sometime
|
||||
segment.all_functions = defined_funcs
|
||||
segment.provided_symbols = provided_symbols
|
||||
segment.special_labels = special_labels
|
||||
@ -314,7 +315,7 @@ def main(rom_path, config_path, repo_path, modes, verbose, ignore_cache=False):
|
||||
if len(segment.errors) == 0:
|
||||
processed_segments.append(segment)
|
||||
|
||||
if type(segment) == N64SegCode:
|
||||
if platform == "n64" and type(segment) == N64SegCode: # edge case
|
||||
undefined_funcs |= segment.glabels_to_add
|
||||
defined_funcs = {**defined_funcs, **segment.glabels_added}
|
||||
undefined_syms |= segment.undefined_syms_to_add
|
||||
@ -335,21 +336,23 @@ def main(rom_path, config_path, repo_path, modes, verbose, ignore_cache=False):
|
||||
write_ldscript(config['basename'], repo_path, ld_sections, options)
|
||||
|
||||
# Write undefined_funcs_auto.txt
|
||||
undefined_funcs_auto_path = get_undefined_funcs_auto_path(repo_path, options)
|
||||
if verbose:
|
||||
log.write(f"saving undefined_funcs_auto.txt")
|
||||
log.write(f"saving {undefined_funcs_auto_path}")
|
||||
c_predefined_funcs = set(provided_symbols.keys())
|
||||
to_write = sorted(undefined_funcs - set(defined_funcs.values()) - c_predefined_funcs)
|
||||
if len(to_write) > 0:
|
||||
with open(os.path.join(repo_path, "undefined_funcs_auto.txt"), "w", newline="\n") as f:
|
||||
with open(undefined_funcs_auto_path, "w", newline="\n") as f:
|
||||
for line in to_write:
|
||||
f.write(line + " = 0x" + line.split("_")[1][:8].upper() + ";\n")
|
||||
|
||||
# write undefined_syms_auto.txt
|
||||
undefined_syms_auto_path = get_undefined_syms_auto_path(repo_path, options)
|
||||
if verbose:
|
||||
log.write(f"saving undefined_syms_auto.txt")
|
||||
log.write(f"saving {undefined_syms_auto_path}")
|
||||
to_write = sorted(undefined_syms, key=lambda x:x[0])
|
||||
if len(to_write) > 0:
|
||||
with open(os.path.join(repo_path, "undefined_syms_auto.txt"), "w", newline="\n") as f:
|
||||
with open(undefined_syms_auto_path, "w", newline="\n") as f:
|
||||
for sym in to_write:
|
||||
f.write(f"{sym[0]} = 0x{sym[1]:X};\n")
|
||||
|
||||
|
@ -4,9 +4,6 @@ from capstone import *
|
||||
from capstone.mips import *
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import rominfo
|
||||
import zlib
|
||||
|
||||
md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + CS_MODE_BIG_ENDIAN)
|
||||
|
||||
@ -33,7 +30,7 @@ def run(rom_bytes, start_offset, vram, end_offset=None):
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
rom_bytes = rominfo.read_rom(args.rom)
|
||||
rom_bytes = open(args.rom, "rb").read()
|
||||
start = int(args.start, 0)
|
||||
end = None
|
||||
vram = int(args.vram, 0)
|
@ -6,7 +6,7 @@ import zlib
|
||||
|
||||
parser = argparse.ArgumentParser(description='Gives information on n64 roms')
|
||||
parser.add_argument('rom', help='path to a .z64 rom')
|
||||
parser.add_argument('--encoding', help='Text encoding the game header is using, defaults to ASCII, see docs.python.org/2.4/lib/standard-encodings.html for valid encodings', default='ASCII')
|
||||
parser.add_argument('--encoding', help='Text encoding the game header is using; see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings', default='ASCII')
|
||||
|
||||
country_codes = {
|
||||
0x37: "Beta",
|
||||
@ -71,7 +71,7 @@ def get_info_bytes(rom_bytes, encoding):
|
||||
try:
|
||||
name = rom_bytes[0x20:0x34].decode(encoding).strip()
|
||||
except:
|
||||
print("n64splat could not decode the game name, try using a different encoding by passing the --encoding argument (see docs.python.org/2.4/lib/standard-encodings.html for valid encodings)")
|
||||
print("splat could not decode the game name; try using a different encoding by passing the --encoding argument (see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings)")
|
||||
exit(1)
|
||||
|
||||
country_code = rom_bytes[0x3E]
|
12
tools/splat/util/n64/symbol.py
Normal file
12
tools/splat/util/n64/symbol.py
Normal file
@ -0,0 +1,12 @@
|
||||
class N64Symbol:
|
||||
|
||||
@staticmethod
|
||||
def get_default_name(vram):
|
||||
return f"D_{vram:X}"
|
||||
|
||||
def __init__(self, vram, rom=None, name=None, segment=None, length=4):
|
||||
self.vram = vram
|
||||
self.rom = rom
|
||||
self.name = name if name else self.get_default_name(vram)
|
||||
self.segment = segment
|
||||
self.length = length
|
Loading…
Reference in New Issue
Block a user