diff --git a/CHANGELOG.md b/CHANGELOG.md index b5200e6c..1f7c634f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ * Allows configuring the style used by the generated `INCLUDE_ASM` macro. It currently allows two possible values: * `default`: Uses the default definition for the macro. This is the default. * `maspsx_hack`: Changes the definition of the generated `INCLUDE_ASM` to be compatible with the one expected by `maspsx` when using the [reordering workaround hack](https://github.com/mkst/maspsx?tab=readme-ov-file#include_asm-reordering-workaround-hack). This value is only relevant for psx projects. +* `incbin` segments now provide a default symbol if the user hasn't defined a symbol at that given address. + * This name is automatically escaped to be a valid symbol name. + * The name of this symbol is based on the segment's name, with a `__` prefix and a suffix depending on the type of incbin used. + * This naming scheme may change at any time. +* `incbin` segments now emit a nonmatching marker. * `spimdisasm` 1.36.1 or above is now required. ### 0.35.2 diff --git a/docs/Segments.md b/docs/Segments.md index d4241154..a82ae965 100644 --- a/docs/Segments.md +++ b/docs/Segments.md @@ -318,6 +318,8 @@ Generating assembly files enables better customization of these binaries, like a If a known symbol (via a symbol_addrs file) matches the vram of a incbin segment then it will be emitted accordingly at the top. If the symbol contains a [`name_end`](Adding-Symbols.md#name_end) property then it will be emitted after the `.incbin` (useful for Nintendo64's RSP ucodes). +If no symbol is provided then splat will emit a symbol based on the segment's name with a `__` prefix and a suffix depending on the type of incbin used. This autogenerated naming scheme may change at any time. + Curretly there are 3 types of incbins, `textbin`, `databin` and `rodatabin`, which are intended for binary blobs of `.text`, `.data` and `.rodata` sections. If a `textbin` section has a corresponding `databin` and/or `rodatabin` section with the same name then those will be included in the same generated assembly file. diff --git a/src/splat/segtypes/common/textbin.py b/src/splat/segtypes/common/textbin.py index c8f53b21..e24eb6f7 100644 --- a/src/splat/segtypes/common/textbin.py +++ b/src/splat/segtypes/common/textbin.py @@ -1,4 +1,5 @@ from pathlib import Path +import re from typing import Optional, TextIO from ...util import log, options @@ -75,45 +76,66 @@ def write_asm_contents(self, rom_bytes, f: TextIO): f.write(f"{self.get_section_asm_line()}\n\n") + sym_name = None + sym_name_end = None + sym_size_matches = None + # Check if there's a symbol at this address - sym = None vram = self.rom_to_ram(self.rom_start) if vram is not None: sym = self.get_symbol(vram, in_segment=True) - - if sym is not None: - f.write(f"{asm_label} {sym.name}\n") - if asm_label == ".globl": - if self.is_text(): - f.write(f".ent {sym.name}\n") - f.write(f"{sym.name}:\n") - sym.defined = True + if sym is not None: + sym.defined = True + sym_name = sym.name + sym_name_end = sym.given_name_end + if ( + sym.given_size is None + or sym.given_size == self.rom_end - self.rom_start + ): + sym_size_matches = self.rom_end - self.rom_start + + if sym_name is None: + # Normalize stuff like slashes and such. + n = regex_sym_name_normalizer.sub("_", self.name) + if self.is_text(): + suffix = "textbin" + elif self.is_data(): + suffix = "databin" + elif self.is_rodata(): + suffix = "rodatabin" + else: + suffix = "incbin" + sym_name = f"__{n}_{suffix}" + + if options.opts.asm_nonmatching_label_macro != "": + siz = f", 0x{sym_size_matches:X}" if sym_size_matches is not None else "" + f.write(f"{options.opts.asm_nonmatching_label_macro} {sym_name}{siz}\n\n") + + f.write(f"{asm_label} {sym_name}\n") + if asm_label == ".globl": + if self.is_text(): + f.write(f".ent {sym_name}\n") + f.write(f"{sym_name}:\n") f.write(f'.incbin "{binpath.as_posix()}"\n') - if sym is not None: - if options.opts.asm_emit_size_directive: - f.write(f".size {sym.name}, . - {sym.name}\n") + if options.opts.asm_emit_size_directive: + f.write(f".size {sym_name}, . - {sym_name}\n") - if self.is_text() and options.opts.asm_end_label != "": - f.write(f"{options.opts.asm_end_label} {sym.name}\n") - elif self.is_data() and options.opts.asm_data_end_label != "": - f.write(f"{options.opts.asm_data_end_label} {sym.name}\n") + if self.is_text() and options.opts.asm_end_label != "": + f.write(f"{options.opts.asm_end_label} {sym_name}\n") + elif options.opts.asm_data_end_label != "": + f.write(f"{options.opts.asm_data_end_label} {sym_name}\n") - if sym.given_name_end is not None: - if ( - sym.given_size is None - or sym.given_size == self.rom_end - self.rom_start - ): - f.write(f"{asm_label} {sym.given_name_end}\n") - if asm_label == ".globl": - f.write(f"{sym.given_name_end}:\n") - if self.is_text() and options.opts.asm_end_label != "": - f.write(f"{options.opts.asm_end_label} {sym.given_name_end}\n") - elif self.is_data() and options.opts.asm_data_end_label != "": - f.write( - f"{options.opts.asm_data_end_label} {sym.given_name_end}\n" - ) + if sym_name_end is not None and sym_size_matches is not None: + f.write(f"{asm_label} {sym_name_end}\n") + if asm_label == ".globl": + f.write(f"{sym_name_end}:\n") + + if self.is_text() and options.opts.asm_end_label != "": + f.write(f"{options.opts.asm_end_label} {sym_name_end}\n") + elif options.opts.asm_data_end_label != "": + f.write(f"{options.opts.asm_data_end_label} {sym_name_end}\n") def split(self, rom_bytes): if self.rom_end is None: @@ -159,3 +181,6 @@ def should_split(self) -> bool: return ( self.extract and self.should_scan() ) # only split if the segment was scanned first + + +regex_sym_name_normalizer = re.compile(r"[^0-9a-zA-Z_]")