diff --git a/tools/build/img/build.py b/tools/build/img/build.py index 391d66b134..ce814f59f5 100755 --- a/tools/build/img/build.py +++ b/tools/build/img/build.py @@ -50,8 +50,14 @@ class Converter(): self.mode = mode self.infile = infile self.outfile = outfile + self.flip_x = "--flip-x" in argv self.flip_y = "--flip-y" in argv + if self.flip_y: + print(self.infile) + + assert self.flip_x == False, "flip_x is not supported" + self.warned = False def warn(self, msg): @@ -250,7 +256,7 @@ class Converter(): if __name__ == "__main__": if len(argv) < 4: - print("usage: build.py MODE INFILE OUTFILE [--flip-y]") + print("usage: build.py MODE INFILE OUTFILE [--flip-x] [--flip-y]") exit(1) Converter(*argv[1:]).convert() diff --git a/tools/splat/.gitrepo b/tools/splat/.gitrepo index 22839fe0b6..6a4cabaf02 100644 --- a/tools/splat/.gitrepo +++ b/tools/splat/.gitrepo @@ -5,8 +5,8 @@ ; [subrepo] remote = https://github.com/ethteck/splat.git - branch = master - commit = 5e36c4555865e8192653217ca75afe0da661651a - parent = e7de26b831ba89b66285a4847c8c28e562f19ca2 + branch = imgflip + commit = 3144dc17f6fd77211e016cfbbc6a705f59b838e0 + parent = 70e14bea15b1374095da723425d9b59a2ffb5915 method = merge cmdver = 0.4.3 diff --git a/tools/splat/CHANGELOG.md b/tools/splat/CHANGELOG.md index b991375fff..890824349b 100644 --- a/tools/splat/CHANGELOG.md +++ b/tools/splat/CHANGELOG.md @@ -1,6 +1,14 @@ # splat Release Notes -## 0.7: The Path Update +## 0.7.1 + +* Image segment changes: + * Added `flip_x` and `flip_y` boolean parameters to replace `flip`. + * `flip` is deprecated and will produce a warning when used. + * Fixed flipping of `ci4` and `ci8` images. + * Fixed `extract: false` (and `start: auto`) behaviour. + +## 0.7.0: The Path Update * Significantly better performance, especially when using the cache feature (`--use-cache` CLI arg). * BREAKING: Some cli args for splat have been renamed. Please consult the usage output (-h or no args) for more information. diff --git a/tools/splat/segtypes/linker_entry.py b/tools/splat/segtypes/linker_entry.py index d5fa68e347..345726212f 100644 --- a/tools/splat/segtypes/linker_entry.py +++ b/tools/splat/segtypes/linker_entry.py @@ -35,11 +35,8 @@ class LinkerEntry: def __init__(self, segment: Segment, src_paths: List[Path], object_path: Path, section: str): self.segment = segment self.src_paths = [clean_up_path(p) for p in src_paths] + self.object_path = path_to_object_path(object_path) self.section = section - if self.section == "linker": - self.object_path = None - else: - self.object_path = path_to_object_path(object_path) class LinkerWriter(): def __init__(self): @@ -65,7 +62,6 @@ class LinkerWriter(): if entry.section == "linker": # TODO: isinstance is preferable self._end_block() self._begin_segment(entry.segment) - continue start = entry.segment.rom_start if isinstance(start, int): diff --git a/tools/splat/segtypes/n64/asm.py b/tools/splat/segtypes/n64/asm.py index 2f78e616de..3030d50741 100644 --- a/tools/splat/segtypes/n64/asm.py +++ b/tools/splat/segtypes/n64/asm.py @@ -27,7 +27,7 @@ class N64SegAsm(N64SegCodeSubsegment): def scan(self, rom_bytes: bytes): if self.rom_start != "auto" and self.rom_end != "auto" and self.rom_start != self.rom_end: - self.funcs_text = self.disassemble_code(rom_bytes, options.get("asm_endlabels", False)) + self.funcs_text = self.disassemble_code(rom_bytes) def split(self, rom_bytes: bytes): if not self.rom_start == self.rom_end: diff --git a/tools/splat/segtypes/n64/c.py b/tools/splat/segtypes/n64/c.py index f613d6497d..a177b5294c 100644 --- a/tools/splat/segtypes/n64/c.py +++ b/tools/splat/segtypes/n64/c.py @@ -10,7 +10,7 @@ from util import options class N64SegC(N64SegCodeSubsegment): defined_funcs: Set[str] = set() - + STRIP_C_COMMENTS_RE = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE @@ -135,7 +135,7 @@ class N64SegC(N64SegCodeSubsegment): c_lines = self.get_c_preamble() for func in funcs_text: - func_name = self.parent.get_symbol(func, type="func", local_only=True).name + func_name = self.get_symbol(func, type="func", local_only=True).name if options.get_compiler() == "GCC": c_lines.append("INCLUDE_ASM(s32, \"{}\", {});".format(self.name, func_name)) else: diff --git a/tools/splat/segtypes/n64/ci4.py b/tools/splat/segtypes/n64/ci4.py index dba33b1fec..e1345c97fe 100644 --- a/tools/splat/segtypes/n64/ci4.py +++ b/tools/splat/segtypes/n64/ci4.py @@ -1,11 +1,12 @@ from segtypes.n64.ci8 import N64SegCi8 +from util import iter class N64SegCi4(N64SegCi8): @staticmethod def parse_image(data, width, height, flip_h=False, flip_v=False): img_data = bytearray() - for i in range(width * height // 2): + for x, y, i in iter.iter_image_indexes(width, height, 0.5, 1, flip_h, flip_v): img_data.append(data[i] >> 4) img_data.append(data[i] & 0xF) diff --git a/tools/splat/segtypes/n64/ci8.py b/tools/splat/segtypes/n64/ci8.py index 91158b98cd..4185c791d2 100644 --- a/tools/splat/segtypes/n64/ci8.py +++ b/tools/splat/segtypes/n64/ci8.py @@ -3,6 +3,7 @@ from segtypes.n64.rgba16 import N64SegRgba16 import png from util import log from util import options +from util import iter if TYPE_CHECKING: from segtypes.n64.palette import N64SegPalette as Palette @@ -35,7 +36,19 @@ class N64SegCi8(N64SegRgba16): @staticmethod def parse_image(data, width, height, flip_h=False, flip_v=False): - return data + # hot path + if not flip_h and not flip_v: + return data + + flipped_data = bytearray() + + for x, y, i in iter.iter_image_indexes(width, height, 1, 1, flip_h, flip_v): + flipped_data.append(data[i]) + + return flipped_data def max_length(self): return self.width * self.height + + def cache(self): + return (self.config, self.rom_end, 1) diff --git a/tools/splat/segtypes/n64/codesubsegment.py b/tools/splat/segtypes/n64/codesubsegment.py index daf8f4a669..e60d6cb927 100644 --- a/tools/splat/segtypes/n64/codesubsegment.py +++ b/tools/splat/segtypes/n64/codesubsegment.py @@ -39,7 +39,7 @@ class N64SegCodeSubsegment(Segment): def is_branch_insn(mnemonic): return (mnemonic.startswith("b") and not mnemonic.startswith("binsl") and not mnemonic == "break") or mnemonic == "j" - def disassemble_code(self, rom_bytes, addsuffix=False): + def disassemble_code(self, rom_bytes): insns = [insn for insn in N64SegCodeSubsegment.md.disasm(rom_bytes[self.rom_start : self.rom_end], self.vram_start)] funcs = self.process_insns(insns, self.rom_start) @@ -50,7 +50,7 @@ class N64SegCodeSubsegment(Segment): funcs = self.determine_symbols(funcs) self.gather_jumptable_labels(rom_bytes) - return self.add_labels(funcs, addsuffix) + return self.add_labels(funcs) def process_insns(self, insns, rom_addr): assert(isinstance(self.parent, N64SegCode)) @@ -243,6 +243,7 @@ class N64SegCodeSubsegment(Segment): if offset != 0: offset_str = f"+0x{offset:X}" + if self.parent: self.parent.check_rodata_sym(func_addr, sym) @@ -256,7 +257,7 @@ class N64SegCodeSubsegment(Segment): ret[func_addr] = func return ret - def add_labels(self, funcs, addsuffix): + def add_labels(self, funcs): ret = {} for func in funcs: @@ -310,9 +311,6 @@ class N64SegCodeSubsegment(Segment): if insn[0].mnemonic != "branch" and insn[0].mnemonic.startswith("b") or insn[0].mnemonic.startswith("j"): indent_next = True - - if addsuffix: - func_text.append(f"endlabel {sym.name}") ret[func] = (func_text, rom_addr) @@ -360,7 +358,7 @@ class N64SegCodeSubsegment(Segment): rom_offset += 4 def should_scan(self) -> bool: - return options.mode_active("code") + return self.should_split() def should_split(self) -> bool: return self.extract and options.mode_active("code") diff --git a/tools/splat/segtypes/n64/data.py b/tools/splat/segtypes/n64/data.py index 542e4c2b96..4970bbb1fa 100644 --- a/tools/splat/segtypes/n64/data.py +++ b/tools/splat/segtypes/n64/data.py @@ -33,6 +33,13 @@ class N64SegData(N64SegCodeSubsegment): def get_linker_section(self) -> str: return ".data" + def get_linker_entries(self): + from segtypes.linker_entry import LinkerEntry + + path = self.out_path() + + return [LinkerEntry(self, [path], path, self.get_linker_section())] + def get_symbols(self): ret = [] @@ -141,7 +148,7 @@ class N64SegData(N64SegCodeSubsegment): return sym_str def disassemble_data(self, rom_bytes): - rodata_encountered = "rodata" in self.type + rodata_encountered = self.type == "rodata" ret = ".include \"macro.inc\"\n\n" ret += f'.section {self.get_linker_section()}' @@ -154,6 +161,9 @@ class N64SegData(N64SegCodeSubsegment): mnemonic = syms[i].access_mnemonic sym = self.parent.get_symbol(syms[i].vram_start, create=True, define=True, local_only=True) + if sym.vram_start == 0x80097D90: + dog = 5 + sym_str = f"\n\nglabel {sym.name}\n" dis_start = self.parent.ram_to_rom(syms[i].vram_start) dis_end = self.parent.ram_to_rom(syms[i + 1].vram_start) diff --git a/tools/splat/segtypes/n64/group.py b/tools/splat/segtypes/n64/group.py index 28929c908d..d8ae2fd018 100644 --- a/tools/splat/segtypes/n64/group.py +++ b/tools/splat/segtypes/n64/group.py @@ -57,6 +57,7 @@ class N64SegGroup(N64Segment): segment.parent = self if segment.rom_start != "auto": + assert isinstance(segment.rom_start, int) segment.vram_start = self.rom_to_ram(segment.rom_start) # TODO: assumes section order - generalize and stuff diff --git a/tools/splat/segtypes/n64/img.py b/tools/splat/segtypes/n64/img.py index 0f674d5372..ad61827124 100644 --- a/tools/splat/segtypes/n64/img.py +++ b/tools/splat/segtypes/n64/img.py @@ -2,10 +2,47 @@ from pathlib import Path from typing import Optional from segtypes.n64.segment import N64Segment from util import options +from util import log class N64SegImg(N64Segment): + def __init__(self, segment, rom_start, rom_end): + super().__init__(segment, rom_start, rom_end) + + if type(segment) is dict: + if self.extract: + self.width = segment["width"] + self.height = segment["height"] + + self.flip_horizontal = bool(segment.get("flip_x", False)) + self.flip_vertical = bool(segment.get("flip_y", False)) + + if segment.get("flip"): + self.warn(f"'flip' parameter for img segments is deprecated; use flip_x and flip_y instead") + flip = segment.get("flip") + + self.flip_vertical = flip == "both" or flip.startswith("v") or flip == "y" + self.flip_horizontal = flip == "both" or flip.startswith("h") or flip == "x" + else: + if self.extract: + if len(segment) < 5: + log.error(f"Error: {self.name} is missing width and height parameters") + self.width = segment[3] + self.height = segment[4] + + self.flip_horizontal = False + self.flip_vertical = False + + if self.extract and self.max_length() is not None: + expected_len = int(self.max_length()) + actual_len = self.rom_end - self.rom_start + if actual_len > expected_len and actual_len - expected_len > self.subalign: + log.error(f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)") + def out_path(self) -> Optional[Path]: return options.get_asset_path() / self.dir / f"{self.name}.png" def should_split(self) -> bool: return self.extract and options.mode_active("img") + + def max_length(self) -> Optional[int]: + return None diff --git a/tools/splat/segtypes/n64/palette.py b/tools/splat/segtypes/n64/palette.py index 46e8ad80a1..0e297478d0 100644 --- a/tools/splat/segtypes/n64/palette.py +++ b/tools/splat/segtypes/n64/palette.py @@ -27,18 +27,19 @@ class N64SegPalette(N64Segment): self.name.split(".")[0] ) if type(segment) is dict else self.name.split(".")[0] - if self.rom_end == "auto": - log.error(f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it") + if self.extract: + if self.rom_end == "auto": + log.error(f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it") - if self.max_length() and isinstance(self.rom_end, int): - expected_len = int(self.max_length()) - actual_len = self.rom_end - self.rom_start - if actual_len > expected_len and actual_len - expected_len > self.subalign: - log.error(f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)") + if self.max_length() and isinstance(self.rom_end, int): + expected_len = int(self.max_length()) + actual_len = self.rom_end - self.rom_start + if actual_len > expected_len and actual_len - expected_len > self.subalign: + log.error(f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)") def should_split(self): - return super().should_split() or options.mode_active("img") - + return self.extract and (super().should_split() or options.mode_active("img")) + def out_path(self) -> Optional[Path]: return options.get_asset_path() / self.dir / f"{self.name}.png" diff --git a/tools/splat/segtypes/n64/rgba16.py b/tools/splat/segtypes/n64/rgba16.py index 8786d9dc1c..aeec01dcee 100644 --- a/tools/splat/segtypes/n64/rgba16.py +++ b/tools/splat/segtypes/n64/rgba16.py @@ -7,37 +7,6 @@ from util.color import unpack_color # TODO: move common behaviour to N64ImgSegment and have all image segments extend that instead class N64SegRgba16(N64SegImg): - def __init__(self, segment, rom_start, rom_end): - super().__init__(segment, rom_start, rom_end) - - if type(segment) is dict: - self.width = segment["width"] - self.height = segment["height"] - self.flip = segment.get("flip", "noflip") - elif len(segment) < 5: - log.error("missing parameters") - else: - self.width = segment[3] - self.height = segment[4] - self.flip = "noflip" - - if self.max_length(): - expected_len = int(self.max_length()) - actual_len = self.rom_end - self.rom_start - if actual_len > expected_len and actual_len - expected_len > self.subalign: - log.error(f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)") - - @property - def flip_vertical(self): - return self.flip == "both" or self.flip.startswith("v") or self.flip == "y" - - @property - def flip_horizontal(self): - return self.flip == "both" or self.flip.startswith("h") or self.flip == "x" - - def should_split(self): - return super().should_split() or options.mode_active("img") - def split(self, rom_bytes): path = self.out_path() path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tools/splat/util/iter.py b/tools/splat/util/iter.py index b74505f8b4..d84b140111 100644 --- a/tools/splat/util/iter.py +++ b/tools/splat/util/iter.py @@ -6,12 +6,12 @@ def iter_in_groups(iterable, n, fillvalue=None): return zip_longest(*args, fillvalue=fillvalue) def iter_image_indexes(width, height, bytes_per_x=1, bytes_per_y=1, flip_h=False, flip_v=False): - w = int(width * bytes_per_x) - h = int(height * bytes_per_y) + w = int(width * bytes_per_x) + h = int(height * bytes_per_y) - xrange = range(w - ceil(bytes_per_x), -1, -ceil(bytes_per_x)) if flip_h else range(0, w, ceil(bytes_per_x)) - yrange = range(h - ceil(bytes_per_y), -1, -ceil(bytes_per_y)) if flip_v else range(0, h, ceil(bytes_per_y)) + xrange = range(w - ceil(bytes_per_x), -1, -ceil(bytes_per_x)) if flip_h else range(0, w, ceil(bytes_per_x)) + yrange = range(h - ceil(bytes_per_y), -1, -ceil(bytes_per_y)) if flip_v else range(0, h, ceil(bytes_per_y)) - for y in yrange: - for x in xrange: - yield x, y, (y * w) + x \ No newline at end of file + for y in yrange: + for x in xrange: + yield x, y, (y * w) + x diff --git a/tools/splat/util/log.py b/tools/splat/util/log.py index c215441570..5e8036173a 100644 --- a/tools/splat/util/log.py +++ b/tools/splat/util/log.py @@ -1,12 +1,12 @@ import sys from colorama import init, Fore, Style -from typing import Union +from typing import Optional init(autoreset=True) newline = True -Status = Union[None, str] +Status = Optional[str] def write(*args, status=None, **kwargs): global newline diff --git a/tools/splat/util/options.py b/tools/splat/util/options.py index 4a2aee48d8..ed18a0564c 100644 --- a/tools/splat/util/options.py +++ b/tools/splat/util/options.py @@ -22,12 +22,12 @@ def initialize(config: Dict, config_path: str, base_path=None, target_path=None) def set(opt, val): opts[opt] = val - + def get(opt, default=None): return opts.get(opt, default) def get_platform() -> str: - return opts.get("platform", "n64") + return opts.get("platform", "N64") def get_compiler() -> str: return opts.get("compiler", "IDO") diff --git a/ver/us/splat.yaml b/ver/us/splat.yaml index 6bfb4569e2..85691c0512 100644 --- a/ver/us/splat.yaml +++ b/ver/us/splat.yaml @@ -2172,14 +2172,12 @@ segments: subsegments: - [0x38F900, c] - [0x390340, bin] - - [0x3903D0, bin] - - start: 0x390810 - type: ia4 - name: battle/text_action_command_ratings - width: 64 - height: 125 - flip: vertical - - [0x3917B0, bin] + - [0x3903D0, ia4, battle/lucky, 64, 32] + - [0x3907D0, ia4, battle/miss, 64, 32] + - [0x390BD0, ia4, battle/good, 64, 32] + - [0x390FD0, ia4, battle/nice, 64, 32] + - [0x3913D0, ia4, battle/super, 64, 32] + - [0x3917D0, bin] - type: code start: 0x391D30 vram: 0xE0092000 @@ -2471,20 +2469,114 @@ segments: subsegments: - [0x3EB4E0, c] - [0x3EBC30, bin] - - [0x3ED4E0, ia8, world/text_chapter, 128, 38] - - [0x3EE7E0, bin] + - [0x3EBE60, ia8, world/ch0, 144, 40] + - [0x3ED4E0, ia8, world/text_chapter, 128, 40] + - [0x3EE8E0, ia8, world/text_end_of, 104, 40] + - [0x3EF920, ia8, world/exclamation_point, 16, 40] + - [0x3EFBA0, ia8, world/ch1, 64, 64] + - [0x3F0BA0, ia8, world/ch2, 64, 64] + - [0x3F1BA0, ia8, world/ch3, 64, 64] + - [0x3F2BA0, ia8, world/ch4, 64, 64] + - [0x3F3BA0, ia8, world/ch5, 64, 64] + - [0x3F4BA0, ia8, world/ch6, 64, 64] + - [0x3F5BA0, ia8, world/ch7, 64, 64] + - [0x3F6BA0, ia8, world/ch8, 64, 64] + - [0x3F7BA0, rgba16, world/chapter_rainbow, 8, 16] + - [0x3F7CA0, bin] - type: code start: 0x3F83F0 vram: 0xE0110000 subsegments: - [0x3F83F0, c] - [0x3F8CA0, bin] + - [0x3F8CC0, i4, ice_smash, 64, 128] + - [0x3F9CC0, bin] - type: code start: 0x3F9E50 vram: 0xE0112000 subsegments: - [0x3F9E50, c] - [0x3FA480, bin] + - start: 0x3FA4B0 + type: ci4 + name: star_cards/card_front + flip_y: true + width: 32 + height: 32 + - [0x3FA6B0, palette, star_cards/card_front] + - [0x3FA6D0, bin] # PAD + - start: 0x3FA8B0 + type: ci4 + name: star_cards/card_back + flip_y: true + width: 32 + height: 64 + - [0x3FACB0, palette, star_cards/card_back] + - [0x3FACD0, bin] # PAD + - start: 0x3FAEB0 + type: ci4 + name: star_cards/wave + flip_y: true + width: 32 + height: 32 + - [0x3FB0B0, palette, star_cards/wave] + - [0x3FB0D0, bin] # PAD + - start: 0x3FB2B0 + type: ci4 + name: star_cards/squares + flip_y: true + width: 16 + height: 16 + - [0x3FB330, palette, star_cards/squares] + - [0x3FB350, bin] # PAD + - start: 0x3FB530 + type: ci4 + name: star_cards/eldstar + flip_y: true + width: 48 + height: 48 + - [0x3FB9B0, palette, star_cards/eldstar] + - [0x3FB9D0, bin] # PAD + - start: 0x3FBBB0 + type: ci4 + name: star_cards/mamar + flip_y: true + width: 48 + height: 48 + - [0x3FC030, palette, star_cards/mamar] + - [0x3FC050, bin] # PAD + - start: 0x3FC230 + type: ci4 + name: star_cards/skolar + flip_y: true + width: 48 + height: 48 + - [0x3FC6B0, palette, star_cards/skolar] + - [0x3FC6D0, bin] # PAD + - start: 0x3FC8B0 + type: ci4 + name: star_cards/muskular + flip_y: true + width: 48 + height: 48 + - [0x3FCD30, palette, star_cards/muskular] + - [0x3FCD50, bin] # PAD + - start: 0x3FCF30 + type: ci4 + name: star_cards/misstar + flip_y: true + width: 48 + height: 48 + - [0x3FD3B0, palette, star_cards/misstar] + - [0x3FD3D0, bin] # PAD + - start: 0x3FDC30 + type: ci4 + name: star_cards/klevar + flip_y: true + width: 48 + height: 48 + - [0x3FE0B0, palette, star_cards/klevar] + - [0x3FE0D0, bin] - type: code start: 0x3FEAE0 vram: 0xE0114000 @@ -2509,6 +2601,35 @@ segments: subsegments: - [0x404220, c] - [0x404E40, bin] + - start: 0x404F40 + type: ci4 + name: world/cloud + flip_y: true + width: 32 + height: 32 + - [0x405140, palette, world/cloud] + - start: 0x405340 + type: ci4 + name: world/waterblock + flip_y: true + width: 32 + height: 32 + - [0x405540, palette, world/waterblock] + - start: 0x405740 + type: ci4 + name: world/yellow_carpet + flip_y: true + width: 32 + height: 32 + - [0x405940, palette, world/yellow_carpet] + - start: 0x405B40 + type: ci4 + name: world/numbers + flip_y: true + width: 32 + height: 160 + - [0x406540, palette, world/numbers] + - [0x406740, bin] - type: code start: 0x406B40 vram: 0xE011C000