From b3d80aefd853c5aef4f81e598969c8d6967c03b0 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:46:05 -0600 Subject: [PATCH 01/19] WIP add type annotations --- mypy.ini | 3 - pyproject.toml | 20 ++ src/splat/__init__.py | 10 +- src/splat/__main__.py | 2 +- .../disassembler/disassembler_section.py | 124 +++++---- src/splat/scripts/split.py | 2 +- src/splat/segtypes/common/asm.py | 2 +- src/splat/segtypes/common/code.py | 6 +- src/splat/segtypes/common/codesubsegment.py | 78 ++++-- src/splat/segtypes/common/group.py | 64 +++-- src/splat/segtypes/common/header.py | 18 +- src/splat/segtypes/common/segment.py | 4 +- src/splat/segtypes/linker_entry.py | 56 ++-- src/splat/segtypes/psx/header.py | 2 +- src/splat/segtypes/segment.py | 260 +++++++++--------- src/splat/util/cache_handler.py | 18 +- src/splat/util/color.py | 6 +- src/splat/util/log.py | 22 +- src/splat/util/options.py | 99 +++---- src/splat/util/palettes.py | 25 +- src/splat/util/progress_bar.py | 4 +- src/splat/util/relocs.py | 10 +- src/splat/util/statistics.py | 31 ++- src/splat/util/symbols.py | 98 +++---- src/splat/util/utils.py | 6 +- src/splat/util/vram_classes.py | 49 +++- 26 files changed, 581 insertions(+), 438 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 281dd2ec..00000000 --- a/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -ignore_missing_imports = True -check_untyped_defs = True diff --git a/pyproject.toml b/pyproject.toml index 9c4d794e..afb0071d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,3 +51,23 @@ features = ["dev"] [project.scripts] splat = "splat.__main__:splat_main" + + +[tool.mypy] +files = ["src"] +enable_error_code = [ + "truthy-bool", + "mutable-override", + "exhaustive-match", +] +show_column_numbers = true +show_error_codes = true +show_traceback = true +disallow_any_decorated = true +disallow_any_unimported = true +ignore_missing_imports = true +local_partial_types = true +no_implicit_optional = true +strict = true +warn_unreachable = true +check_untyped_defs = true diff --git a/src/splat/__init__.py b/src/splat/__init__.py index 301b77a9..dbe887fd 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -4,9 +4,9 @@ __version__ = "0.37.3" __author__ = "ethteck" -from . import util as util -from . import disassembler as disassembler -from . import platforms as platforms -from . import segtypes as segtypes +from splat import util as util +from splat import disassembler as disassembler +from splat import platforms as platforms +from splat import segtypes as segtypes -from . import scripts as scripts +from splat import scripts as scripts diff --git a/src/splat/__main__.py b/src/splat/__main__.py index 5959d38c..724d5604 100644 --- a/src/splat/__main__.py +++ b/src/splat/__main__.py @@ -5,7 +5,7 @@ import splat -def splat_main(): +def splat_main() -> None: parser = argparse.ArgumentParser( description="A binary splitting tool to assist with decompilation and modding projects", prog="splat", diff --git a/src/splat/disassembler/disassembler_section.py b/src/splat/disassembler/disassembler_section.py index 5493581d..da18944c 100644 --- a/src/splat/disassembler/disassembler_section.py +++ b/src/splat/disassembler/disassembler_section.py @@ -1,94 +1,99 @@ +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Optional import spimdisasm -from ..util import options, symbols +from splat.util import options, symbols class DisassemblerSection(ABC): + __slots__ = () + @abstractmethod - def disassemble(self): + def disassemble(self) -> str: raise NotImplementedError("disassemble") @abstractmethod - def analyze(self): + def analyze(self) -> None: raise NotImplementedError("analyze") @abstractmethod - def set_comment_offset(self, rom_start: int): + def set_comment_offset(self, rom_start: int) -> None: raise NotImplementedError("set_comment_offset") @abstractmethod def make_bss_section( self, - rom_start, - rom_end, - vram_start, - bss_end, - name, - segment_rom_start, - exclusive_ram_id, - ): + rom_start: int, + rom_end: int, + vram_start: int, + bss_end: int, + name: str, + segment_rom_start: int, + exclusive_ram_id: str | None, + ) -> None: raise NotImplementedError("make_bss_section") @abstractmethod def make_data_section( self, - rom_start, - rom_end, - vram_start, - name, - rom_bytes, - segment_rom_start, - exclusive_ram_id, - ): + rom_start: int, + rom_end: int, + vram_start: int, + name: str, + rom_bytes: bytes, + segment_rom_start: int, + exclusive_ram_id: str | None, + ) -> None: raise NotImplementedError("make_data_section") @abstractmethod - def get_section(self): + def get_section(self) -> spimdisasm.mips.sections.SectionBase | None: raise NotImplementedError("get_section") @abstractmethod def make_rodata_section( self, - rom_start, - rom_end, - vram_start, - name, - rom_bytes, - segment_rom_start, - exclusive_ram_id, - ): + rom_start: int, + rom_end: int, + vram_start: int, + name: str, + rom_bytes: bytes, + segment_rom_start: int, + exclusive_ram_id: str | None, + ) -> None: raise NotImplementedError("make_rodata_section") @abstractmethod def make_text_section( self, - rom_start, - rom_end, - vram_start, - name, - rom_bytes, - segment_rom_start, - exclusive_ram_id, - ): + rom_start: int, + rom_end: int, + vram_start: int, + name: str, + rom_bytes: bytes, + segment_rom_start: int, + exclusive_ram_id: str | None, + ) -> None: raise NotImplementedError("make_text_section") class SpimdisasmDisassemberSection(DisassemblerSection): - def __init__(self): - self.spim_section: Optional[spimdisasm.mips.sections.SectionBase] = None + __slots__ = ("spim_section",) + + def __init__(self) -> None: + self.spim_section: spimdisasm.mips.sections.SectionBase | None = None def disassemble(self) -> str: assert self.spim_section is not None return self.spim_section.disassemble() - def analyze(self): + def analyze(self) -> None: assert self.spim_section is not None self.spim_section.analyze() - def set_comment_offset(self, rom_start: int): + def set_comment_offset(self, rom_start: int) -> None: assert self.spim_section is not None self.spim_section.setCommentOffset(rom_start) @@ -100,8 +105,8 @@ def make_bss_section( bss_end: int, name: str, segment_rom_start: int, - exclusive_ram_id, - ): + exclusive_ram_id: str | None, + ) -> None: self.spim_section = spimdisasm.mips.sections.SectionBss( symbols.spim_context, rom_start, @@ -121,8 +126,8 @@ def make_data_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, - ): + exclusive_ram_id: str | None, + ) -> None: self.spim_section = spimdisasm.mips.sections.SectionData( symbols.spim_context, rom_start, @@ -134,7 +139,7 @@ def make_data_section( exclusive_ram_id, ) - def get_section(self) -> Optional[spimdisasm.mips.sections.SectionBase]: + def get_section(self) -> spimdisasm.mips.sections.SectionBase | None: return self.spim_section def make_rodata_section( @@ -145,8 +150,8 @@ def make_rodata_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, - ): + exclusive_ram_id: str | None, + ) -> None: self.spim_section = spimdisasm.mips.sections.SectionRodata( symbols.spim_context, rom_start, @@ -166,8 +171,8 @@ def make_text_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, - ): + exclusive_ram_id: str | None, + ) -> None: self.spim_section = spimdisasm.mips.sections.SectionText( symbols.spim_context, rom_start, @@ -187,8 +192,8 @@ def make_gcc_except_table_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, - ): + exclusive_ram_id: str | None, + ) -> None: self.spim_section = spimdisasm.mips.sections.SectionGccExceptTable( symbols.spim_context, rom_start, @@ -201,12 +206,11 @@ def make_gcc_except_table_section( ) -def make_disassembler_section() -> Optional[SpimdisasmDisassemberSection]: +def make_disassembler_section() -> SpimdisasmDisassemberSection | None: if options.opts.platform in ["n64", "psx", "ps2", "psp"]: return SpimdisasmDisassemberSection() raise NotImplementedError("No disassembler section for requested platform") - return None def make_text_section( @@ -216,7 +220,7 @@ def make_text_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, + exclusive_ram_id: str | None, ) -> DisassemblerSection: section = make_disassembler_section() assert section is not None @@ -239,7 +243,7 @@ def make_data_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, + exclusive_ram_id: str | None, ) -> DisassemblerSection: section = make_disassembler_section() assert section is not None @@ -262,7 +266,7 @@ def make_rodata_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, + exclusive_ram_id: str | None, ) -> DisassemblerSection: section = make_disassembler_section() assert section is not None @@ -285,7 +289,7 @@ def make_bss_section( bss_end: int, name: str, segment_rom_start: int, - exclusive_ram_id, + exclusive_ram_id: str | None, ) -> DisassemblerSection: section = make_disassembler_section() assert section is not None @@ -308,7 +312,7 @@ def make_gcc_except_table_section( name: str, rom_bytes: bytes, segment_rom_start: int, - exclusive_ram_id, + exclusive_ram_id: str | None, ) -> DisassemblerSection: section = make_disassembler_section() assert section is not None diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index a4fc643b..d2a1af1c 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -168,7 +168,7 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: return ret -def assign_symbols_to_segments(): +def assign_symbols_to_segments() -> None: for symbol in symbols.all_symbols: if symbol.segment: continue diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index c1e5f9cc..81579390 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,7 +1,7 @@ from typing import Optional -from .codesubsegment import CommonSegCodeSubsegment +from splat.segtypes.common.codesubsegment import CommonSegCodeSubsegment class CommonSegAsm(CommonSegCodeSubsegment): diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 050c980f..98b93776 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -1,9 +1,11 @@ +from __future__ import annotations + from collections import OrderedDict from typing import List, Optional, Type, Tuple -from ...util import log, options, utils +from splat.util import log, options, utils -from .group import CommonSegGroup +from splat.segtypes.common.group import CommonSegGroup from ..segment import Segment, parse_segment_align diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 18a5ea74..74d82cc9 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -1,32 +1,50 @@ +from __future__ import annotations + from pathlib import Path -from typing import Optional, List import spimdisasm import rabbitizer -from ...util import options, symbols, log +from splat.util import options, symbols, log -from .code import CommonSegCode +from splat.segtypes.common.code import CommonSegCode -from ..segment import Segment, parse_segment_vram +from splat.segtypes.segment import Segment, parse_segment_vram, SerializedSegment -from ...disassembler.disassembler_section import DisassemblerSection, make_text_section +from splat.disassembler.disassembler_section import DisassemblerSection, make_text_section # abstract class for c, asm, data, etc class CommonSegCodeSubsegment(Segment): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__( + self, + rom_start: int | None, + rom_end: int | None, + type: str, + name: str, + vram_start: int | None, + args: list[str], + yaml: SerializedSegment, + ) -> None: + super().__init__( + rom_start=rom_start, + rom_end=rom_end, + type=type, + name=name, + vram_start=vram_start, + args=args, + yaml=yaml, + ) vram = parse_segment_vram(self.yaml) if vram is not None: self.vram_start = vram - self.str_encoding: Optional[str] = ( + self.str_encoding: str | None = ( self.yaml.get("str_encoding", None) if isinstance(self.yaml, dict) else None ) - self.spim_section: Optional[DisassemblerSection] = None + self.spim_section: DisassemblerSection | None = None self.instr_category = rabbitizer.InstrCategory.CPU if options.opts.platform == "ps2": self.instr_category = rabbitizer.InstrCategory.R5900 @@ -35,7 +53,7 @@ def __init__(self, *args, **kwargs): elif options.opts.platform == "psp": self.instr_category = rabbitizer.InstrCategory.R4000ALLEGREX - self.detect_redundant_function_end: Optional[bool] = ( + self.detect_redundant_function_end: bool | None = ( self.yaml.get("detect_redundant_function_end", None) if isinstance(self.yaml, dict) else None @@ -57,13 +75,14 @@ def configure_disassembler_section( "Allows to configure the section before running the analysis on it" section = disassembler_section.get_section() + assert section is not None section.isHandwritten = self.is_hasm section.instrCat = self.instr_category section.detectRedundantFunctionEnd = self.detect_redundant_function_end section.setGpRelHack(not self.use_gp_rel_macro) - def scan_code(self, rom_bytes, is_hasm=False): + def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: self.is_hasm = is_hasm if self.is_auto_segment: @@ -103,7 +122,11 @@ def scan_code(self, rom_bytes, is_hasm=False): self.spim_section.analyze() self.spim_section.set_comment_offset(self.rom_start) - for func in self.spim_section.get_section().symbolList: + section = self.spim_section.get_section() + + assert section is not None + + for func in section.symbolList: assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction) self.process_insns(func) @@ -111,7 +134,7 @@ def scan_code(self, rom_bytes, is_hasm=False): def process_insns( self, func_spim: spimdisasm.mips.symbols.SymbolFunction, - ): + ) -> None: assert isinstance(self.parent, CommonSegCode) assert func_spim.vram is not None assert func_spim.vramEnd is not None @@ -124,7 +147,9 @@ def process_insns( # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in func_spim.referencedVrams: - context_sym = self.spim_section.get_section().getSymbol( + section = self.spim_section.get_section() + assert section is not None + context_sym = section.getSymbol( referenced_vram, tryPlusOffset=False ) if context_sym is not None: @@ -150,27 +175,32 @@ def process_insns( if instr_offset in func_spim.instrAnalyzer.symbolInstrOffset: sym_address = func_spim.instrAnalyzer.symbolInstrOffset[instr_offset] - context_sym = self.spim_section.get_section().getSymbol(sym_address) + section = self.spim_section.get_section() + assert section is not None + context_sym = section.getSymbol(sym_address) if context_sym is not None: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), context_sym, force_in_segment=False ) - def print_file_boundaries(self): + def print_file_boundaries(self) -> None: if not self.show_file_boundaries or not self.spim_section: return assert isinstance(self.rom_start, int) - for in_file_offset in self.spim_section.get_section().fileBoundaries: + section = self.spim_section.get_section() + assert section is not None + + for in_file_offset in section.fileBoundaries: if not self.parent.reported_file_split: self.parent.reported_file_split = True # Look up for the last symbol in this boundary sym_addr = 0 - for sym in self.spim_section.get_section().symbolList: + for sym in section.symbolList: symOffset = ( - sym.inFileOffset - self.spim_section.get_section().inFileOffset + sym.inFileOffset - section.inFileOffset ) if in_file_offset == symOffset: break @@ -199,7 +229,7 @@ def should_split(self) -> bool: def should_self_split(self) -> bool: return self.should_split() - def get_asm_file_header(self) -> List[str]: + def get_asm_file_header(self) -> list[str]: ret = [] ret.append('.include "macro.inc"') @@ -217,7 +247,7 @@ def get_asm_file_header(self) -> List[str]: return ret - def get_asm_file_extra_directives(self) -> List[str]: + def get_asm_file_extra_directives(self) -> list[str]: ret = [] ret.append(".set noat") # allow manual use of $at @@ -231,10 +261,10 @@ def get_asm_file_extra_directives(self) -> List[str]: def asm_out_path(self) -> Path: return options.opts.asm_path / self.dir / f"{self.name}.s" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return self.asm_out_path() - def split_as_asm_file(self, out_path: Optional[Path]): + def split_as_asm_file(self, out_path: Path | None) -> None: if self.spim_section is None: return @@ -252,7 +282,7 @@ def split_as_asm_file(self, out_path: Optional[Path]): f.write(self.spim_section.disassemble()) # Same as above but write all sections from siblings - def split_as_asmtu_file(self, out_path: Path): + def split_as_asmtu_file(self, out_path: Path) -> None: out_path.parent.mkdir(parents=True, exist_ok=True) self.print_file_boundaries() diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 94f02ff2..1af2e25e 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -1,22 +1,28 @@ -from typing import List, Optional +from __future__ import annotations -from ...util import log +from typing import TYPE_CHECKING, override -from .segment import CommonSegment -from ..segment import empty_statistics, Segment, SegmentStatistics +from splat.util import log + +from splat.segtypes.common.segment import CommonSegment +from splat.segtypes.segment import empty_statistics, Segment, SegmentStatistics + +if TYPE_CHECKING: + from splat.util.vram_classes import SerializedSegmentData + from splat.segtypes.linker_entry import LinkerEntry class CommonSegGroup(CommonSegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegmentData | list[str], + ) -> None: super().__init__( rom_start, rom_end, @@ -27,9 +33,10 @@ def __init__( yaml=yaml, ) - self.subsegments: List[Segment] = self.parse_subsegments(yaml) + # TODO: Fix + self.subsegments: list[Segment] = self.parse_subsegments(yaml) # type: ignore[arg-type] - def get_next_seg_start(self, i, subsegment_yamls) -> Optional[int]: + def get_next_seg_start(self, i: int, subsegment_yamls: list[SerializedSegmentData | list[str]]) -> int | None: j = i + 1 while j < len(subsegment_yamls): ret, is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) @@ -40,13 +47,13 @@ def get_next_seg_start(self, i, subsegment_yamls) -> Optional[int]: # Fallback return self.rom_end - def parse_subsegments(self, yaml) -> List[Segment]: - ret: List[Segment] = [] + def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[str]]]) -> list[Segment]: + ret: list[Segment] = [] if not yaml or "subsegments" not in yaml: return ret - prev_start: Optional[int] = -1 + prev_start: int | None = -1 last_rom_end = 0 for i, subsegment_yaml in enumerate(yaml["subsegments"]): @@ -71,7 +78,7 @@ def parse_subsegments(self, yaml) -> List[Segment]: # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address - end: Optional[int] = None + end: int | None = None if i < len(yaml["subsegments"]) - 1: end, end_is_auto_segment = Segment.parse_segment_start( yaml["subsegments"][i + 1] @@ -103,8 +110,12 @@ def parse_subsegments(self, yaml) -> List[Segment]: # and it has a rom size of zero end = last_rom_end - segment: Segment = Segment.from_yaml( - segment_class, subsegment_yaml, start, end, self, vram + segment: Segment = segment_class.from_yaml( + subsegment_yaml, + start, + end, + self, + vram, ) if segment.special_vram_segment: self.special_vram_segment = True @@ -137,15 +148,15 @@ def statistics(self) -> SegmentStatistics: stats[ty] = stats[ty].merge(info) return stats - def get_linker_entries(self): + def get_linker_entries(self) -> list[LinkerEntry]: return [entry for sub in self.subsegments for entry in sub.get_linker_entries()] - def scan(self, rom_bytes): + def scan(self, rom_bytes: bytes) -> None: for sub in self.subsegments: if sub.should_scan(): sub.scan(rom_bytes) - def split(self, rom_bytes): + def split(self, rom_bytes: bytes) -> None: for sub in self.subsegments: if sub.should_split(): sub.split(rom_bytes) @@ -156,7 +167,8 @@ def should_split(self) -> bool: def should_scan(self) -> bool: return self.extract - def cache(self): + @override + def cache(self) -> list[tuple[SerializedSegmentData | list[str], int | None]]: # type: ignore[override] c = [] for sub in self.subsegments: @@ -164,7 +176,7 @@ def cache(self): return c - def get_subsegment_for_ram(self, addr: int) -> Optional[Segment]: + def get_subsegment_for_ram(self, addr: int) -> Segment | None: for sub in self.subsegments: if sub.contains_vram(addr): return sub @@ -175,8 +187,8 @@ def get_subsegment_for_ram(self, addr: int) -> Optional[Segment]: return None def get_next_subsegment_for_ram( - self, addr: int, current_subseg_index: Optional[int] - ) -> Optional[Segment]: + self, addr: int, current_subseg_index: int | None + ) -> Segment | None: """ Returns the first subsegment which comes after the specified address, or None in case this address belongs to the last subsegment of this group @@ -195,7 +207,7 @@ def get_next_subsegment_for_ram( def pair_subsegments_to_other_segment( self, other_segment: "CommonSegGroup", - ): + ) -> None: # Pair cousins with the same name for segment in self.subsegments: for sibling in other_segment.subsegments: diff --git a/src/splat/segtypes/common/header.py b/src/splat/segtypes/common/header.py index f8c41c66..135f2e81 100644 --- a/src/splat/segtypes/common/header.py +++ b/src/splat/segtypes/common/header.py @@ -1,8 +1,10 @@ +from __future__ import annotations + from pathlib import Path -from ...util import options +from splat.util import options -from .segment import CommonSegment +from splat.segtypes.segment import CommonSegment class CommonSegHeader(CommonSegment): @@ -10,11 +12,11 @@ class CommonSegHeader(CommonSegment): def is_data() -> bool: return True - def should_split(self): + def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("code") @staticmethod - def get_line(typ, data, comment): + def get_line(typ: str, data: bytes, comment: str) -> str: if typ == "ascii": text = data.decode("ASCII").strip() text = text.replace("\x00", "\\0") # escape NUL chars @@ -29,18 +31,18 @@ def get_line(typ, data, comment): def out_path(self) -> Path: return options.opts.asm_path / self.dir / f"{self.name}.s" - def parse_header(self, rom_bytes): + def parse_header(self, rom_bytes: bytes) -> list[str]: return [] - def split(self, rom_bytes): + def split(self, rom_bytes: bytes) -> None: header_lines = self.parse_header(rom_bytes) src_path = self.out_path() src_path.parent.mkdir(parents=True, exist_ok=True) - with open(src_path, "w", newline="\n") as f: + with open(src_path, "w", newline="\n", encoding="utf-8") as f: f.write("\n".join(header_lines)) self.log(f"Wrote {self.name} to {src_path}") @staticmethod - def get_default_name(addr): + def get_default_name(addr: int) -> str: return "header" diff --git a/src/splat/segtypes/common/segment.py b/src/splat/segtypes/common/segment.py index 20c89a5e..fef55086 100644 --- a/src/splat/segtypes/common/segment.py +++ b/src/splat/segtypes/common/segment.py @@ -1,5 +1,5 @@ -from ...segtypes.segment import Segment +from splat.segtypes.segment import Segment class CommonSegment(Segment): - pass + __slots__ = () diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index 192a203d..e964597b 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -1,13 +1,15 @@ +from __future__ import annotations + import os import re from functools import lru_cache from pathlib import Path from typing import Dict, List, OrderedDict, Set, Tuple, Union, Optional -from ..util import options, log +from splat.util import options, log -from .segment import Segment -from ..util.symbols import to_cname +from splat.segtypes.segment import Segment +from splat.util.symbols import to_cname # clean 'foo/../bar' to 'bar' @@ -43,7 +45,7 @@ def path_to_object_path(path: Path) -> Path: return clean_up_path(path.with_suffix(full_suffix)) -def write_file_if_different(path: Path, new_content: str): +def write_file_if_different(path: Path, new_content: str) -> None: if path.exists(): old_content = path.read_text() else: @@ -149,7 +151,7 @@ def section_link_type(self) -> str: else: return self.section_link - def emit_symbol_for_data(self, linker_writer: "LinkerWriter"): + def emit_symbol_for_data(self, linker_writer: LinkerWriter) -> None: if not options.opts.ld_generate_symbol_per_data_segment: return @@ -161,7 +163,7 @@ def emit_symbol_for_data(self, linker_writer: "LinkerWriter"): ) linker_writer._write_symbol(path_cname, ".") - def emit_path(self, linker_writer: "LinkerWriter"): + def emit_path(self, linker_writer: LinkerWriter) -> None: assert self.object_path is not None, ( f"{self.segment.name}, {self.segment.rom_start}" ) @@ -177,7 +179,7 @@ def emit_path(self, linker_writer: "LinkerWriter"): self.object_path, f"{self.section_link}{wildcard}" ) - def emit_entry(self, linker_writer: "LinkerWriter"): + def emit_entry(self, linker_writer: LinkerWriter) -> None: self.emit_symbol_for_data(linker_writer) self.emit_path(linker_writer) @@ -210,7 +212,7 @@ def __init__(self, is_partial: bool = False): self._writeln(f"_gp = 0x{options.opts.gp:X};") # Write a series of statements which compute a symbol that represents the highest address among a list of segments' end addresses - def write_max_vram_end_sym(self, symbol: str, overlays: List[Segment]): + def write_max_vram_end_sym(self, symbol: str, overlays: list[Segment]) -> None: for segment in overlays: if segment == overlays[0]: self._writeln( @@ -222,7 +224,7 @@ def write_max_vram_end_sym(self, symbol: str, overlays: List[Segment]): ) # Adds all the entries of a segment to the linker script buffer - def add(self, segment: Segment, max_vram_syms: List[Tuple[str, List[Segment]]]): + def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) self.dependencies_entries.extend(entries) @@ -296,7 +298,7 @@ def add(self, segment: Segment, max_vram_syms: List[Tuple[str, List[Segment]]]): self._end_segment(segment, all_bss=not any_load) - def add_legacy(self, segment: Segment, entries: List[LinkerEntry]): + def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: seg_name = segment.get_cname() # To keep track which sections has been started @@ -360,8 +362,8 @@ def add_legacy(self, segment: Segment, entries: List[LinkerEntry]): self._end_segment(segment, all_bss=False) def add_referenced_partial_segment( - self, segment: Segment, max_vram_syms: List[Tuple[str, List[Segment]]] - ): + self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]] + ) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) @@ -436,7 +438,7 @@ def add_referenced_partial_segment( self._end_segment(segment, all_bss=not any_load) - def add_partial_segment(self, segment: Segment): + def add_partial_segment(self, segment: Segment) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) self.dependencies_entries.extend(entries) @@ -482,7 +484,7 @@ def add_partial_segment(self, segment: Segment): self._end_partial_segment(section_name) - def save_linker_script(self, output_path: Path): + def save_linker_script(self, output_path: Path) -> None: if len(self.sections_allowlist) > 0: address = " 0" if self.is_partial: @@ -510,7 +512,7 @@ def save_linker_script(self, output_path: Path): write_file_if_different(output_path, "\n".join(self.buffer) + "\n") - def save_symbol_header(self): + def save_symbol_header(self) -> None: path = options.opts.ld_symbol_header_path if path: @@ -528,7 +530,7 @@ def save_symbol_header(self): "#endif\n", ) - def save_dependencies_file(self, output_path: Path, target_elf_path: Path): + def save_dependencies_file(self, output_path: Path, target_elf_path: Path) -> None: output = f"{clean_up_path(target_elf_path).as_posix()}:" for entry in self.dependencies_entries: @@ -543,21 +545,21 @@ def save_dependencies_file(self, output_path: Path, target_elf_path: Path): output += f"{entry.object_path.as_posix()}:\n" write_file_if_different(output_path, output) - def _writeln(self, line: str): + def _writeln(self, line: str) -> None: if len(line) == 0: self.buffer.append(line) else: self.buffer.append(" " * self._indent_level + line) - def _begin_block(self): + def _begin_block(self) -> None: self._writeln("{") self._indent_level += 1 - def _end_block(self): + def _end_block(self) -> None: self._indent_level -= 1 self._writeln("}") - def _write_symbol(self, symbol: str, value: Union[str, int]): + def _write_symbol(self, symbol: str, value: Union[str, int]) -> None: symbol = to_cname(symbol) if isinstance(value, int): @@ -567,12 +569,12 @@ def _write_symbol(self, symbol: str, value: Union[str, int]): self.header_symbols.add(symbol) - def _write_object_path_section(self, object_path: Path, section: str): + def _write_object_path_section(self, object_path: Path, section: str) -> None: self._writeln(f"{object_path.as_posix()}({section});") def _begin_segment( self, segment: Segment, seg_name: str, noload: bool, is_first: bool - ): + ) -> None: if ( options.opts.ld_use_symbolic_vram_addresses and segment.vram_symbol is not None @@ -608,7 +610,7 @@ def _begin_segment( if segment.ld_fill_value is not None: self._writeln(f"FILL(0x{segment.ld_fill_value:08X});") - def _end_segment(self, segment: Segment, all_bss=False): + def _end_segment(self, segment: Segment, all_bss: bool = False) -> None: self._end_block() name = segment.get_cname() @@ -636,7 +638,7 @@ def _end_segment(self, segment: Segment, all_bss=False): self._writeln("") - def _begin_partial_segment(self, section_name: str, segment: Segment, noload: bool): + def _begin_partial_segment(self, section_name: str, segment: Segment, noload: bool) -> None: line = f"{section_name}" if noload: line += " (NOLOAD)" @@ -647,7 +649,7 @@ def _begin_partial_segment(self, section_name: str, segment: Segment, noload: bo self._writeln(line) self._begin_block() - def _end_partial_segment(self, section_name: str, all_bss=False): + def _end_partial_segment(self, section_name: str, all_bss: bool = False) -> None: self._end_block() self._writeln("") @@ -672,10 +674,10 @@ def _write_segment_sections( self, segment: Segment, seg_name: str, - section_entries: OrderedDict[str, List[LinkerEntry]], + section_entries: OrderedDict[str, list[LinkerEntry]], noload: bool, is_first: bool, - ): + ) -> None: if not is_first: self._end_block() diff --git a/src/splat/segtypes/psx/header.py b/src/splat/segtypes/psx/header.py index 6b0990cb..fc61059f 100644 --- a/src/splat/segtypes/psx/header.py +++ b/src/splat/segtypes/psx/header.py @@ -4,7 +4,7 @@ class PsxSegHeader(CommonSegHeader): # little endian so reverse words, TODO: use struct.unpack(" list[str]: header_lines = [] header_lines.append(".section .data\n") header_lines.append( diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index 784ca4a2..bdf99898 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -1,71 +1,76 @@ +from __future__ import annotations + import collections import dataclasses import importlib import importlib.util from pathlib import Path -from typing import Dict, List, Optional, Set, Type, TYPE_CHECKING, Union, Tuple +from typing import Optional, Type, TYPE_CHECKING, Union, Dict, TypeAlias, List from intervaltree import Interval, IntervalTree -from ..util import vram_classes +from splat.util import vram_classes -from ..util.vram_classes import VramClass -from ..util import log, options, symbols -from ..util.symbols import Symbol, to_cname +from splat.util.vram_classes import VramClass, SerializedSegmentData +from splat.util import log, options, symbols +from splat.util.symbols import Symbol, to_cname -from .. import __package_name__ +from splat import __package_name__ # circular import if TYPE_CHECKING: - from ..segtypes.linker_entry import LinkerEntry + from splat.segtypes.linker_entry import LinkerEntry + +SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] -def parse_segment_vram(segment: Union[dict, list]) -> Optional[int]: +def parse_segment_vram(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "vram" in segment: return int(segment["vram"]) else: return None -def parse_segment_vram_symbol(segment: Union[dict, list]) -> Optional[str]: +def parse_segment_vram_symbol(segment: SerializedSegment) -> str | None: if isinstance(segment, dict) and "vram_symbol" in segment: return str(segment["vram_symbol"]) else: return None -def parse_segment_vram_class(segment: Union[dict, list]) -> Optional[VramClass]: +def parse_segment_vram_class(segment: SerializedSegment) -> VramClass | None: if isinstance(segment, dict) and "vram_class" in segment: return vram_classes.resolve(segment["vram_class"]) return None -def parse_segment_follows_vram(segment: Union[dict, list]) -> Optional[str]: +def parse_segment_follows_vram(segment: SerializedSegment) -> str | None: if isinstance(segment, dict): return segment.get("follows_vram", None) return None -def parse_segment_align(segment: Union[dict, list]) -> Optional[int]: +def parse_segment_align(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "align" in segment: return int(segment["align"]) return None -def parse_segment_subalign(segment: Union[dict, list]) -> Optional[int]: +def parse_segment_subalign(segment: SerializedSegment) -> int | None: default = options.opts.subalign if isinstance(segment, dict): - subalign = segment.get("subalign", default) + subalign: int | str | None = segment.get("subalign", default) if subalign is not None: - subalign = int(subalign) - return subalign + return int(subalign) + return None return default -def parse_segment_section_order(segment: Union[dict, list]) -> List[str]: +def parse_segment_section_order(segment: SerializedSegment) -> list[str]: default = options.opts.section_order if isinstance(segment, dict): - return segment.get("section_order", default) + section_order: list[str] = segment.get("section_order", default) + return section_order return default @@ -94,7 +99,7 @@ class Segment: require_unique_name = True @staticmethod - def get_class_for_type(seg_type) -> Type["Segment"]: + def get_class_for_type(seg_type: str) -> type[Segment]: # so .data loads SegData, for example if seg_type.startswith("."): seg_type = seg_type[1:] @@ -117,7 +122,7 @@ def get_class_for_type(seg_type) -> Type["Segment"]: return segment_class @staticmethod - def get_base_segment_class(seg_type): + def get_base_segment_class(seg_type: str) -> type[Segment] | None: platform = options.opts.platform is_platform_seg = False @@ -136,10 +141,13 @@ def get_base_segment_class(seg_type): return None seg_prefix = platform.capitalize() if is_platform_seg else "Common" - return getattr(segmodule, f"{seg_prefix}Seg{seg_type.capitalize()}") + return getattr( # type: ignore[no-any-return] + segmodule, + f"{seg_prefix}Seg{seg_type.capitalize()}" + ) @staticmethod - def get_extension_segment_class(seg_type): + def get_extension_segment_class(seg_type: str) -> type[Segment] | None: platform = options.opts.platform ext_path = options.opts.extensions_path @@ -161,12 +169,12 @@ def get_extension_segment_class(seg_type): except Exception: return None - return getattr( + return getattr( # type: ignore[no-any-return] ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}" ) @staticmethod - def parse_segment_start(segment: Union[dict, list]) -> Tuple[Optional[int], bool]: + def parse_segment_start(segment: SerializedSegment) -> tuple[int | None, bool]: """ Parses the rom start address of a given segment. @@ -178,7 +186,7 @@ def parse_segment_start(segment: Union[dict, list]) -> Tuple[Optional[int], bool """ if isinstance(segment, dict): - s = segment.get("start", None) + s: str | None = segment.get("start", None) else: s = segment[0] @@ -190,81 +198,82 @@ def parse_segment_start(segment: Union[dict, list]) -> Tuple[Optional[int], bool return int(s), False @staticmethod - def parse_segment_type(segment: Union[dict, list]) -> str: + def parse_segment_type(segment: SerializedSegment) -> str: if isinstance(segment, dict): return str(segment["type"]) else: return str(segment[1]) - @staticmethod - def parse_segment_name(cls, rom_start, segment: Union[dict, list]) -> str: - if isinstance(segment, dict) and "name" in segment: - return str(segment["name"]) - elif isinstance(segment, dict) and "dir" in segment: - return str(segment["dir"]) + @classmethod + def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) -> str: + if isinstance(segment, dict): + if "name" in segment: + return str(segment["name"]) + elif "dir" in segment: + return str(segment["dir"]) elif isinstance(segment, list) and len(segment) >= 3: return str(segment[2]) - else: - return str(cls.get_default_name(rom_start)) + assert rom_start is not None + return str(cls.get_default_name(rom_start)) @staticmethod - def parse_segment_symbol_name_format(segment: Union[dict, list]) -> str: + def parse_segment_symbol_name_format(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format" in segment: return str(segment["symbol_name_format"]) else: return options.opts.symbol_name_format @staticmethod - def parse_segment_symbol_name_format_no_rom(segment: Union[dict, list]) -> str: + def parse_segment_symbol_name_format_no_rom(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format_no_rom" in segment: return str(segment["symbol_name_format_no_rom"]) else: return options.opts.symbol_name_format_no_rom @staticmethod - def parse_segment_file_path(segment: Union[dict, list]) -> Optional[Path]: + def parse_segment_file_path(segment: SerializedSegment) -> Path | None: if isinstance(segment, dict) and "path" in segment: return Path(segment["path"]) return None @staticmethod def parse_segment_bss_contains_common( - segment: Union[dict, list], default: bool + segment: SerializedSegment, default: bool ) -> bool: if isinstance(segment, dict) and "bss_contains_common" in segment: return bool(segment["bss_contains_common"]) return default @staticmethod - def parse_linker_section_order(yaml: Union[dict, list]) -> Optional[str]: + def parse_linker_section_order(yaml: SerializedSegment) -> str | None: if isinstance(yaml, dict) and "linker_section_order" in yaml: return str(yaml["linker_section_order"]) return None @staticmethod - def parse_linker_section(yaml: Union[dict, list]) -> Optional[str]: + def parse_linker_section(yaml: SerializedSegment) -> str | None: if isinstance(yaml, dict) and "linker_section" in yaml: return str(yaml["linker_section"]) return None @staticmethod def parse_ld_fill_value( - yaml: Union[dict, list], default: Optional[int] - ) -> Optional[int]: + yaml: SerializedSegment, default: int | None + ) -> int | None: if isinstance(yaml, dict) and "ld_fill_value" in yaml: return yaml["ld_fill_value"] return default @staticmethod - def parse_ld_align_segment_start(yaml: Union[dict, list]) -> Optional[int]: + def parse_ld_align_segment_start(yaml: SerializedSegment) -> int | None: if isinstance(yaml, dict) and "ld_align_segment_start" in yaml: return yaml["ld_align_segment_start"] return options.opts.ld_align_segment_start @staticmethod def parse_suggestion_rodata_section_start( - yaml: Union[dict, list], - ) -> Optional[bool]: + yaml: SerializedSegment, + ) -> bool | None: if isinstance(yaml, dict): suggestion_rodata_section_start = yaml.get( "suggestion_rodata_section_start" @@ -275,62 +284,62 @@ def parse_suggestion_rodata_section_start( return None @staticmethod - def parse_pair_segment(yaml: Union[dict, list]) -> Optional[str]: + def parse_pair_segment(yaml: SerializedSegment) -> str | None: if isinstance(yaml, dict) and "pair_segment" in yaml: return yaml["pair_segment"] return None def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegment, + ) -> None: self.rom_start = rom_start self.rom_end = rom_end self.type = type self.name = name - self.vram_start: Optional[int] = vram_start + self.vram_start: int | None = vram_start - self.align: Optional[int] = None - self.given_subalign: Optional[int] = options.opts.subalign - self.exclusive_ram_id: Optional[str] = None + self.align: int | None = None + self.given_subalign: int | None = options.opts.subalign + self.exclusive_ram_id: str | None = None self.given_dir: Path = Path() # Default to global options. - self.given_find_file_boundaries: Optional[bool] = None + self.given_find_file_boundaries: bool | None = None # Symbols known to be in this segment - self.given_seg_symbols: Dict[int, List[Symbol]] = {} + self.given_seg_symbols: dict[int, list[Symbol]] = {} # Ranges for faster symbol lookup self.symbol_ranges_ram: IntervalTree = IntervalTree() self.symbol_ranges_rom: IntervalTree = IntervalTree() - self.given_section_order: List[str] = options.opts.section_order + self.given_section_order: list[str] = options.opts.section_order - self.vram_class: Optional[VramClass] = None - self.given_follows_vram: Optional[str] = None - self.given_vram_symbol: Optional[str] = None + self.vram_class: VramClass | None = None + self.given_follows_vram: str | None = None + self.given_vram_symbol: str | None = None self.given_symbol_name_format: str = options.opts.symbol_name_format self.given_symbol_name_format_no_rom: str = ( options.opts.symbol_name_format_no_rom ) - self.parent: Optional[Segment] = None - self.sibling: Optional[Segment] = None - self.siblings: Dict[str, Segment] = {} - self.pair_segment_name: Optional[str] = self.parse_pair_segment(yaml) - self.paired_segment: Optional[Segment] = None + self.parent: Segment | None = None + self.sibling: Segment | None = None + self.siblings: dict[str, Segment] = {} + self.pair_segment_name: str | None = self.parse_pair_segment(yaml) + self.paired_segment: Segment | None = None - self.file_path: Optional[Path] = None + self.file_path: Path | None = None - self.args: List[str] = args + self.args: list[str] = args self.yaml = yaml self.extract: bool = True @@ -340,7 +349,7 @@ def __init__( elif self.type.startswith("."): self.extract = False - self.warnings: List[str] = [] + self.warnings: list[str] = [] self.did_run = False self.bss_contains_common = Segment.parse_segment_bss_contains_common( yaml, options.opts.ld_bss_contains_common @@ -349,29 +358,29 @@ def __init__( # For segments which are not in the usual VRAM segment space, like N64's IPL3 which lives in 0xA4... self.special_vram_segment: bool = False - self.linker_section_order: Optional[str] = self.parse_linker_section_order(yaml) - self.linker_section: Optional[str] = self.parse_linker_section(yaml) + self.linker_section_order: str | None = self.parse_linker_section_order(yaml) + self.linker_section: str | None = self.parse_linker_section(yaml) # If not defined on the segment then default to the global option - self.ld_fill_value: Optional[int] = self.parse_ld_fill_value( + self.ld_fill_value: int | None = self.parse_ld_fill_value( yaml, options.opts.ld_fill_value ) - self.ld_align_segment_start: Optional[int] = self.parse_ld_align_segment_start( + self.ld_align_segment_start: int | None = self.parse_ld_align_segment_start( yaml ) # True if this segment was generated based on auto_link_sections self.is_generated: bool = False - self.given_suggestion_rodata_section_start: Optional[bool] = ( + self.given_suggestion_rodata_section_start: bool | None = ( self.parse_suggestion_rodata_section_start(yaml) ) # Is an automatic segment, generated automatically or declared on the yaml by the user self.is_auto_segment: bool = False - self.index_within_group: Optional[int] = None + self.index_within_group: int | None = None if self.rom_start is not None and self.rom_end is not None: if self.rom_start > self.rom_end: @@ -379,20 +388,22 @@ def __init__( f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})" ) - @staticmethod + @classmethod def from_yaml( - cls: Type["Segment"], - yaml: Union[dict, list], - rom_start: Optional[int], - rom_end: Optional[int], - parent: Optional["Segment"], - vram=None, - ): - type = Segment.parse_segment_type(yaml) - name = Segment.parse_segment_name(cls, rom_start, yaml) + cls: type[Segment], + yaml: SerializedSegment, + rom_start: int | None, + rom_end: int | None, + parent: Segment | None, + vram: int | None = None, + ) -> Segment: + type = cls.parse_segment_type(yaml) + name = cls.parse_segment_name(rom_start, yaml) vram_class = parse_segment_vram_class(yaml) + vram_start: int | None + if vram is not None: vram_start = vram elif vram_class: @@ -400,7 +411,7 @@ def from_yaml( else: vram_start = parse_segment_vram(yaml) - args: List[str] = [] if isinstance(yaml, dict) else yaml[3:] + args: list[str] = [] if isinstance(yaml, dict) else yaml[3:] ret = cls( rom_start=rom_start, @@ -424,8 +435,8 @@ def from_yaml( ret.parent = parent # Import here to avoid circular imports - from .common.code import CommonSegCode - from .common.bss import CommonSegBss + from splat.segtypes.common.code import CommonSegCode + from splat.segtypes.common.bss import CommonSegBss if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss): # We need to know the bss space for the segment. @@ -502,7 +513,7 @@ def is_noload() -> bool: return False @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: + def estimate_size(yaml: SerializedSegment) -> int | None: return None @property @@ -537,14 +548,14 @@ def symbol_name_format_no_rom(self) -> str: return self.given_symbol_name_format_no_rom @property - def subalign(self) -> Optional[int]: + def subalign(self) -> int | None: assert self.parent is None, ( f"subalign is not valid for non-top-level segments. ({self})" ) return self.given_subalign @property - def vram_symbol(self) -> Optional[str]: + def vram_symbol(self) -> str | None: if self.vram_class and self.vram_class.vram_symbol: return self.vram_class.vram_symbol elif self.given_vram_symbol: @@ -552,12 +563,12 @@ def vram_symbol(self) -> Optional[str]: else: return None - def get_exclusive_ram_id(self) -> Optional[str]: + def get_exclusive_ram_id(self) -> str | None: if self.parent: return self.parent.get_exclusive_ram_id() return self.exclusive_ram_id - def add_symbol(self, symbol: Symbol): + def add_symbol(self, symbol: Symbol) -> None: if symbol.vram_start not in self.given_seg_symbols: self.given_seg_symbols[symbol.vram_start] = [] self.given_seg_symbols[symbol.vram_start].append(symbol) @@ -569,14 +580,14 @@ def add_symbol(self, symbol: Symbol): self.symbol_ranges_rom.addi(symbol.rom, symbol.rom_end, symbol) @property - def seg_symbols(self) -> Dict[int, List[Symbol]]: + def seg_symbols(self) -> dict[int, list[Symbol]]: if self.parent: return self.parent.seg_symbols else: return self.given_seg_symbols @property - def size(self) -> Optional[int]: + def size(self) -> int | None: if self.rom_start is not None and self.rom_end is not None: return self.rom_end - self.rom_start else: @@ -594,14 +605,14 @@ def statistics_type(self) -> SegmentType: return self.type @property - def vram_end(self) -> Optional[int]: + def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size else: return None @property - def section_order(self) -> List[str]: + def section_order(self) -> list[str]: return self.given_section_order @property @@ -639,13 +650,13 @@ def contains_rom(self, rom: int) -> bool: else: return False - def rom_to_ram(self, rom_addr: int) -> Optional[int]: + def rom_to_ram(self, rom_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.vram_start + rom_addr - self.rom_start else: return None - def ram_to_rom(self, ram_addr: int) -> Optional[int]: + def ram_to_rom(self, ram_addr: int) -> int | None: if not self.contains_vram(ram_addr) and ram_addr != self.vram_end: return None @@ -660,13 +671,13 @@ def should_scan(self) -> bool: def should_split(self) -> bool: return self.extract and options.opts.is_mode_active(self.type) - def scan(self, rom_bytes: bytes): + def scan(self, rom_bytes: bytes) -> None: pass - def split(self, rom_bytes: bytes): + def split(self, rom_bytes: bytes) -> None: pass - def cache(self): + def cache(self) -> tuple[SerializedSegment, int | None]: return (self.yaml, self.rom_end) def get_linker_section(self) -> str: @@ -690,7 +701,7 @@ def get_linker_section_linksection(self) -> str: return self.linker_section return self.get_linker_section() - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: """ Allows specifying flags for a section. @@ -701,7 +712,7 @@ def get_section_flags(self) -> Optional[str]: Example: ``` - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: # Tells the linker to allocate this section return "a" ``` @@ -715,10 +726,10 @@ def get_section_asm_line(self) -> str: line += f', "{section_flags}"' return line - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return None - def get_most_parent(self) -> "Segment": + def get_most_parent(self) -> Segment: seg = self while seg.parent: @@ -726,7 +737,7 @@ def get_most_parent(self) -> "Segment": return seg - def get_linker_entries(self) -> "List[LinkerEntry]": + def get_linker_entries(self) -> "list[LinkerEntry]": from ..segtypes.linker_entry import LinkerEntry if not self.has_linker_entry: @@ -748,21 +759,22 @@ def get_linker_entries(self) -> "List[LinkerEntry]": else: return [] - def log(self, msg): + def log(self, msg: str) -> None: if options.opts.verbose: log.write(f"{self.type} {self.name}: {msg}") - def warn(self, msg: str): + def warn(self, msg: str) -> None: self.warnings.append(msg) @staticmethod - def get_default_name(addr) -> str: + def get_default_name(addr: int) -> str: return f"{addr:X}" - def is_name_default(self): + def is_name_default(self) -> bool: + assert self.rom_start is not None return self.name == self.get_default_name(self.rom_start) - def unique_id(self): + def unique_id(self) -> str: if self.parent: s = self.parent.unique_id() + "_" else: @@ -771,7 +783,7 @@ def unique_id(self): return s + self.type + "_" + self.name @staticmethod - def visible_ram(seg1: "Segment", seg2: "Segment") -> bool: + def visible_ram(seg1: Segment, seg2: Segment) -> bool: if seg1.get_most_parent() == seg2.get_most_parent(): return True if seg1.get_exclusive_ram_id() is None or seg2.get_exclusive_ram_id() is None: @@ -779,8 +791,8 @@ def visible_ram(seg1: "Segment", seg2: "Segment") -> bool: return seg1.get_exclusive_ram_id() != seg2.get_exclusive_ram_id() def retrieve_symbol( - self, syms: Dict[int, List[Symbol]], addr: int - ) -> Optional[Symbol]: + self, syms: dict[int, list[Symbol]], addr: int + ) -> Symbol | None: if addr not in syms: return None @@ -801,8 +813,8 @@ def retrieve_symbol( return items[0] def retrieve_sym_type( - self, syms: Dict[int, List[Symbol]], addr: int, type: str - ) -> Optional[symbols.Symbol]: + self, syms: dict[int, list[Symbol]], addr: int, type: str + ) -> symbols.Symbol | None: if addr not in syms: return None @@ -824,15 +836,15 @@ def get_symbol( self, addr: int, in_segment: bool = False, - type: Optional[str] = None, + type: str | None = None, create: bool = False, define: bool = False, reference: bool = False, search_ranges: bool = False, local_only: bool = False, - ) -> Optional[Symbol]: - ret: Optional[Symbol] = None - rom: Optional[int] = None + ) -> Symbol | None: + ret: Symbol | None = None + rom: int | None = None most_parent = self.get_most_parent() @@ -844,7 +856,7 @@ def get_symbol( if not ret and search_ranges: # Search ranges first, starting with rom if rom is not None: - cands: Set[Interval] = most_parent.symbol_ranges_rom[rom] + cands: set[Interval] = most_parent.symbol_ranges_rom[rom] if cands: ret = cands.pop().data # and then vram if we can't find a rom match @@ -890,7 +902,7 @@ def create_symbol( self, addr: int, in_segment: bool, - type: Optional[str] = None, + type: str | None = None, define: bool = False, reference: bool = False, search_ranges: bool = False, diff --git a/src/splat/util/cache_handler.py b/src/splat/util/cache_handler.py index deb55780..da894518 100644 --- a/src/splat/util/cache_handler.py +++ b/src/splat/util/cache_handler.py @@ -1,14 +1,16 @@ +from __future__ import annotations + import pickle -from typing import Any, Dict +from typing import Any -from . import options, log -from ..segtypes.common.segment import Segment +from splat.util import options, log +from splat.segtypes.segment import Segment class Cache: - def __init__(self, config: Dict[str, Any], use_cache: bool, verbose: bool): + def __init__(self, config: dict[str, Any], use_cache: bool, verbose: bool) -> None: self.use_cache: bool = use_cache - self.cache: Dict[str, Any] = {} + self.cache: dict[str, Any] = {} # Load cache if use_cache and options.opts.cache_path.exists(): @@ -32,7 +34,7 @@ def __init__(self, config: Dict[str, Any], use_cache: bool, verbose: bool): "__options__": config.get("options"), } - def save(self, verbose: bool): + def save(self, verbose: bool) -> None: if self.cache != {} and self.use_cache: if verbose: log.write("Writing cache") @@ -42,8 +44,12 @@ def save(self, verbose: bool): def check_cache_hit(self, segment: Segment, update_on_miss: bool) -> bool: if self.use_cache: + # types: no-untyped-call error: Call to untyped function "cache" in typed context cached = segment.cache() + # types: ^^^^^^^^^^^^^^^ + # types: no-untyped-call error: Call to untyped function "unique_id" in typed context segment_id = segment.unique_id() +# types: ^^^^^^^^^^^^^^^^^^^ if cached == self.cache.get(segment_id): # Cache hit diff --git a/src/splat/util/color.py b/src/splat/util/color.py index f8250ae0..a361041e 100644 --- a/src/splat/util/color.py +++ b/src/splat/util/color.py @@ -1,10 +1,12 @@ +from __future__ import annotations + from math import ceil -from . import options +from splat.util import options # RRRRRGGG GGBBBBBA -def unpack_color(data): +def unpack_color(data: bytes) -> tuple[int, int, int, int]: s = int.from_bytes(data[0:2], byteorder=options.opts.endianness) r = (s >> 11) & 0x1F diff --git a/src/splat/util/log.py b/src/splat/util/log.py index 6dc8ff64..19c34701 100644 --- a/src/splat/util/log.py +++ b/src/splat/util/log.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import sys -from typing import NoReturn, Optional +from typing import NoReturn, Optional, TypeAlias, TextIO from pathlib import Path from colorama import Fore, init, Style @@ -8,10 +10,10 @@ newline = True -Status = Optional[str] +Status: TypeAlias = Optional[str] -def write(*args, status=None, **kwargs): +def write(*args: object, status: Status = None, sep: str | None = None, end: str | None = None, flush: bool = False) -> None: global newline if not newline: @@ -21,24 +23,26 @@ def write(*args, status=None, **kwargs): print( status_to_ansi(status) + str(args[0]), *args[1:], - **kwargs, + sep=sep, + end=end, file=output_file(status), + flush=flush, ) -def error(*args, **kwargs) -> NoReturn: - write(*args, **kwargs, status="error") +def error(*args: object, sep: str | None = None, end: str | None = None, flush: bool = False) -> NoReturn: + write(*args, status="error", sep=sep, end=end, flush=flush) sys.exit(2) # The line_num is expected to be zero-indexed -def parsing_error_preamble(path: Path, line_num: int, line: str): +def parsing_error_preamble(path: Path, line_num: int, line: str) -> None: write("") write(f"error reading {path}, line {line_num + 1}:", status="error") write(f"\t{line}") -def status_to_ansi(status: Status): +def status_to_ansi(status: Status) -> Fore | str: if status == "ok": return Fore.GREEN elif status == "warn": @@ -51,7 +55,7 @@ def status_to_ansi(status: Status): return "" -def output_file(status: Status): +def output_file(status: Status) -> TextIO: if status == "warn" or status == "error": return sys.stderr return sys.stdout diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 2a6337f0..0d2cdefd 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -1,10 +1,13 @@ +from __future__ import annotations + from dataclasses import dataclass import os from pathlib import Path -from typing import cast, Dict, List, Literal, Mapping, Optional, Set, Type, TypeVar +from typing import cast, Literal, Type, TypeVar +from collections.abc import Mapping -from . import compiler -from .compiler import Compiler +from splat.util import compiler +from splat.util.compiler import Compiler @dataclass @@ -12,7 +15,7 @@ class SplatOpts: # Debug / logging verbose: bool dump_symbols: bool - modes: List[str] + modes: list[str] # Project configuration @@ -21,7 +24,7 @@ class SplatOpts: # Determines the path to the target binary target_path: Path # Path to the final elf target - elf_path: Optional[Path] + elf_path: Path | None # Determines the platform of the target binary platform: str # Determines the compiler used to compile the target binary @@ -30,13 +33,13 @@ class SplatOpts: endianness: Literal["big", "little"] # Determines the default section order of the target binary # this can be overridden per-segment - section_order: List[str] + section_order: list[str] # Determines the code that is inserted by default in generated .c files generated_c_preamble: str # Determines the code that is inserted by default in generated .s files generated_s_preamble: str # Determines any extra content to be added in the generated macro.inc file - generated_macro_inc_content: Optional[str] + generated_macro_inc_content: str | None # Determines if files related to assembly macros should be regenerated by splat generate_asm_macros_files: bool # Changes the definition of the generated `INCLUDE_ASM`. @@ -48,7 +51,7 @@ class SplatOpts: # Determines whether to use .o as the suffix for all binary files?... TODO document use_o_as_suffix: bool # the value of the $gp register to correctly calculate offset to %gp_rel relocs - gp: Optional[int] + gp: int | None # Checks and errors if there are any non consecutive segment types check_consecutive_segment_types: bool # Disable checks on `platform` option. @@ -63,8 +66,8 @@ class SplatOpts: # as well as optional metadata such as rom address, type, and more # # It's possible to use more than one file by supplying a list instead of a string - symbol_addrs_paths: List[Path] - reloc_addrs_paths: List[Path] + symbol_addrs_paths: list[Path] + reloc_addrs_paths: list[Path] # Determines the path to the project build directory build_path: Path # Determines the path to the source code directory @@ -95,32 +98,32 @@ class SplatOpts: undefined_syms_auto_path: Path # Determines the path in which to search for custom splat extensions - extensions_path: Optional[Path] + extensions_path: Path | None # Determines the path to library files that are to be linked into the target binary lib_path: Path # TODO document - elf_section_list_path: Optional[Path] + elf_section_list_path: Path | None # Linker script # Determines the default subalign value to be specified in the generated linker script - subalign: Optional[int] + subalign: int | None # Determines whether to emit the subalign directive in the generated linker script emit_subalign: bool # The following option determines a list of sections for which automatic linker script entries should be added - auto_link_sections: List[str] + auto_link_sections: list[str] # Determines the desired path to the linker script that splat will generate ld_script_path: Path # Determines the desired path to the linker symbol header, # which exposes externed definitions for all segment ram/rom start/end locations - ld_symbol_header_path: Optional[Path] + ld_symbol_header_path: Path | None # Determines whether to add a discard section with a wildcard to the linker script ld_discard_section: bool # A list of sections to preserve during link time. It can be useful to preserve debugging sections - ld_sections_allowlist: List[str] + ld_sections_allowlist: list[str] # A list of sections to discard during link time. It can be useful to avoid using the wildcard discard. Note that this option does not turn off `ld_discard_section` - ld_sections_denylist: List[str] + ld_sections_denylist: list[str] # Determines whether to add wildcards for section linking in the linker script (.rodata* for example) ld_wildcard_sections: bool # Determines whether to use `follows_vram` (segment option) and @@ -132,9 +135,9 @@ class SplatOpts: # Change linker script generation to allow partially linking segments. Requires both `ld_partial_scripts_path` and `ld_partial_build_segments_path` to be set. ld_partial_linking: bool # Folder were each intermediary linker script will be written to. - ld_partial_scripts_path: Optional[Path] + ld_partial_scripts_path: Path | None # Folder where the built partially linked segments will be placed by the build system. - ld_partial_build_segments_path: Optional[Path] + ld_partial_build_segments_path: Path | None # Generate a dependency file for every linker script generated. Dependency files will have the same path and name as the corresponding linker script, but changing the extension to `.d`. Requires `elf_path` to be set. ld_dependencies: bool # Legacy linker script generation does not impose the section_order specified in the yaml options or per-segment options. @@ -146,11 +149,11 @@ class SplatOpts: # Specifies the starting offset for rom address symbols in the linker script. ld_rom_start: int # The value passed to the FILL statement on each segment. `None` disables using FILL statements on the linker script. Defaults to a fill value of 0. - ld_fill_value: Optional[int] + ld_fill_value: int | None # Allows to control if `bss` sections (and derivatived sections) will be put on a `NOLOAD` segment on the generated linker script or not. ld_bss_is_noload: bool # Aligns the start of the segment to the given value - ld_align_segment_start: Optional[int] + ld_align_segment_start: int | None # Allows to toggle aligning the `*_VRAM_END` linker symbol for each segment. ld_align_segment_vram_end: bool # Allows to toggle aligning the `*_END` linker symbol for each section of each section. @@ -160,7 +163,7 @@ class SplatOpts: # Sets the default option for the `bss_contains_common` attribute of all segments. ld_bss_contains_common: bool # Specify an expression to be used for the `_gp` symbol in the generated linker script instead of a hardcoded value. - ld_gp_expression: Optional[str] + ld_gp_expression: str | None ################################################################################ # C file options @@ -208,7 +211,7 @@ class SplatOpts: # Determines the macro used to declare the given symbol is a non matching one. asm_nonmatching_label_macro: str # Toggles the .size directive emitted by the disassembler - asm_emit_size_directive: Optional[bool] + asm_emit_size_directive: bool | None # Determines the number of characters to left align before the TODO finish documenting mnemonic_ljust: int # Determines whether to pad the rom address @@ -227,13 +230,13 @@ class SplatOpts: # Generate .asmproc.d dependency files for each C file which still reference functions in assembly files create_asm_dependencies: bool # Global option for rodata string encoding. This can be overriden per segment - string_encoding: Optional[str] + string_encoding: str | None # Global option for data string encoding. This can be overriden per segment - data_string_encoding: Optional[str] + data_string_encoding: str | None # Global option for the rodata string guesser. 0 disables the guesser completely. - rodata_string_guesser_level: Optional[int] + rodata_string_guesser_level: int | None # Global option for the data string guesser. 0 disables the guesser completely. - data_string_guesser_level: Optional[int] + data_string_guesser_level: int | None # Global option for allowing data symbols using addends on symbol references. It can be overriden per symbol allow_data_addends: bool # Tells the disassembler to try disassembling functions with unknown instructions instead of falling back to disassembling as raw data @@ -246,8 +249,8 @@ class SplatOpts: make_full_disasm_for_code: bool # Allow specifying that the global memory range may be larger than what was automatically detected. # Useful for projects where splat is used in multiple individual files, meaning the expected global segment may not be properly detected because each instance of splat can't see the info from other files. - global_vram_start: Optional[int] - global_vram_end: Optional[int] + global_vram_start: int | None + global_vram_end: int | None # For `c` segments (functions under the nonmatchings folder). # If True then use the `%gp_rel` explicit relocation parameter on instructions that use the $gp register, # otherwise strip the `%gp_rel` parameter entirely and convert those instructions into macro instructions that may not assemble to the original @@ -297,13 +300,13 @@ def is_mode_active(self, mode: str) -> bool: class OptParser: - _read_opts: Set[str] + _read_opts: set[str] def __init__(self, yaml: Mapping[str, object]) -> None: self._yaml = yaml self._read_opts = set() - def parse_opt(self, opt: str, t: Type[T], default: Optional[T] = None) -> T: + def parse_opt(self, opt: str, t: type[T], default: T | None = None) -> T: if opt not in self._yaml: if default is not None: return default @@ -316,14 +319,14 @@ def parse_opt(self, opt: str, t: Type[T], default: Optional[T] = None) -> T: return cast(T, float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") - def parse_optional_opt(self, opt: str, t: Type[T]) -> Optional[T]: + def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: if opt not in self._yaml: return None return self.parse_opt(opt, t) def parse_optional_opt_with_default( - self, opt: str, t: Type[T], default: Optional[T] - ) -> Optional[T]: + self, opt: str, t: type[T], default: T | None + ) -> T | None: if opt not in self._yaml: return default self._read_opts.add(opt) @@ -335,7 +338,7 @@ def parse_optional_opt_with_default( raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_opt_within( - self, opt: str, t: Type[T], within: List[T], default: Optional[T] = None + self, opt: str, t: type[T], within: list[T], default: T | None = None ) -> T: value = self.parse_opt(opt, t, default) if value not in within: @@ -343,16 +346,16 @@ def parse_opt_within( return value def parse_path( - self, base_path: Path, opt: str, default: Optional[str] = None + self, base_path: Path, opt: str, default: str | None = None ) -> Path: return Path(os.path.normpath(base_path / self.parse_opt(opt, str, default))) - def parse_optional_path(self, base_path: Path, opt: str) -> Optional[Path]: + def parse_optional_path(self, base_path: Path, opt: str) -> Path | None: if opt not in self._yaml: return None return self.parse_path(base_path, opt) - def parse_path_list(self, base_path: Path, opt: str, default: str) -> List[Path]: + def parse_path_list(self, base_path: Path, opt: str, default: str) -> list[Path]: paths = self.parse_opt(opt, object, default) if isinstance(paths, str): @@ -369,9 +372,9 @@ def check_no_unread_opts(self) -> None: def _parse_yaml( - yaml: Dict, - config_paths: List[Path], - modes: List[str], + yaml: Mapping[str, object], + config_paths: list[Path], + modes: list[str], verbose: bool = False, disasm_all: bool = False, make_full_disasm_for_code: bool = False, @@ -651,13 +654,13 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: def initialize( - config: Dict, - config_paths: List[Path], - modes: Optional[List[str]] = None, - verbose=False, - disasm_all=False, - make_full_disasm_for_code=False, -): + config: Mapping[str, Mapping[str, object]], + config_paths: list[Path], + modes: list[str] | None = None, + verbose: bool = False, + disasm_all: bool = False, + make_full_disasm_for_code: bool = False, +) -> None: global opts if not modes: diff --git a/src/splat/util/palettes.py b/src/splat/util/palettes.py index 03d33e6d..d3bdabd1 100644 --- a/src/splat/util/palettes.py +++ b/src/splat/util/palettes.py @@ -1,17 +1,22 @@ -from typing import Dict +from __future__ import annotations -from ..util import log +from typing import Dict, TYPE_CHECKING -from ..segtypes.common.group import CommonSegGroup -from ..segtypes.n64.ci import N64SegCi -from ..segtypes.n64.palette import N64SegPalette +from splat.util import log -global_ids: Dict[str, N64SegPalette] +from splat.segtypes.common.group import CommonSegGroup +from splat.segtypes.n64.ci import N64SegCi +from splat.segtypes.n64.palette import N64SegPalette + +if TYPE_CHECKING: + from splat.segtypes.segment import Segment + +global_ids: Dict[str, N64SegPalette] = {} # Resolve Raster#palette and Palette#raster links -def initialize(all_segments): - def collect_global_ids(segments): +def initialize(all_segments: list[Segment]) -> None: + def collect_global_ids(segments: list[Segment]) -> None: for segment in segments: if isinstance(segment, N64SegPalette): if segment.global_id is not None: @@ -20,7 +25,7 @@ def collect_global_ids(segments): if isinstance(segment, CommonSegGroup): collect_global_ids(segment.subsegments) - def process(segments): + def process(segments: list[Segment]) -> None: raster_map: Dict[str, N64SegCi] = {} palette_map: Dict[str, N64SegPalette] = {} @@ -73,7 +78,7 @@ def process(segments): log.error(f"Palette {pal.name} has no linked rasters") global global_ids - global_ids = {} + global_ids.clear() collect_global_ids(all_segments) diff --git a/src/splat/util/progress_bar.py b/src/splat/util/progress_bar.py index caec2cee..12b2ee3b 100644 --- a/src/splat/util/progress_bar.py +++ b/src/splat/util/progress_bar.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import tqdm import sys out_file = sys.stderr -def get_progress_bar(elements: list) -> tqdm.tqdm: +def get_progress_bar(elements: list[str]) -> tqdm.tqdm: return tqdm.tqdm(elements, total=len(elements), file=out_file) diff --git a/src/splat/util/relocs.py b/src/splat/util/relocs.py index 37a7a01a..00be694a 100644 --- a/src/splat/util/relocs.py +++ b/src/splat/util/relocs.py @@ -1,9 +1,11 @@ +from __future__ import annotations + from dataclasses import dataclass from typing import Dict import spimdisasm -from . import log, options, symbols, progress_bar +from splat.util import log, options, symbols, progress_bar @dataclass @@ -18,11 +20,11 @@ class Reloc: all_relocs: Dict[int, Reloc] = {} -def add_reloc(reloc: Reloc): +def add_reloc(reloc: Reloc) -> None: all_relocs[reloc.rom_address] = reloc -def initialize(): +def initialize() -> None: global all_relocs all_relocs = {} @@ -114,7 +116,7 @@ def initialize(): add_reloc(reloc) -def initialize_spim_context(): +def initialize_spim_context() -> None: for rom_address, reloc in all_relocs.items(): reloc_type = spimdisasm.common.RelocType.fromStr(reloc.reloc_type) diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index 78efd197..77cbbed0 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -1,40 +1,43 @@ +from __future__ import annotations + from colorama import Fore, Style -from typing import Dict, Optional -from . import log +from splat.util import log def fmt_size(size: int) -> str: if size > 1000000: - return str(size // 1000000) + " MB" + return f"{size // 1000000} MB" elif size > 1000: - return str(size // 1000) + " KB" + return f"{size // 1000} KB" else: - return str(size) + " B" + return f"{size} B" class Statistics: - def __init__(self): - self.seg_sizes: Dict[str, int] = {} - self.seg_split: Dict[str, int] = {} - self.seg_cached: Dict[str, int] = {} - - def add_size(self, typ: str, size: Optional[int]): + __slots__ = ("seg_sizes", "seg_split", "seg_cached") + + def __init__(self) -> None: + self.seg_sizes: dict[str, int] = {} + self.seg_split: dict[str, int] = {} + self.seg_cached: dict[str, int] = {} + + def add_size(self, typ: str, size: int | None) -> None: if typ not in self.seg_sizes: self.seg_sizes[typ] = 0 self.seg_sizes[typ] += 0 if size is None else size - def count_split(self, typ: str, count: int = 1): + def count_split(self, typ: str, count: int = 1) -> None: if typ not in self.seg_split: self.seg_split[typ] = 0 self.seg_split[typ] += count - def count_cached(self, typ: str, count: int = 1): + def count_cached(self, typ: str, count: int = 1) -> None: if typ not in self.seg_cached: self.seg_cached[typ] = 0 self.seg_cached[typ] += count - def print_statistics(self, total_size: int): + def print_statistics(self, total_size: int) -> None: unk_size = self.seg_sizes.get("unk", 0) rest_size = 0 diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index a5cc8a24..e8fcb210 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -1,24 +1,26 @@ +from __future__ import annotations + from dataclasses import dataclass import re -from typing import Dict, List, Optional, Set, TYPE_CHECKING +from typing import TYPE_CHECKING import spimdisasm from intervaltree import IntervalTree -from ..disassembler import disassembler_instance +from splat.disassembler import disassembler_instance from pathlib import Path # circular import if TYPE_CHECKING: - from ..segtypes.segment import Segment + from splat.segtypes.segment import Segment -from . import log, options, progress_bar +from splat.util import log, options, progress_bar -all_symbols: List["Symbol"] = [] -all_symbols_dict: Dict[int, List["Symbol"]] = {} +all_symbols: list[Symbol] = [] +all_symbols_dict: dict[int, list[Symbol]] = {} all_symbols_ranges = IntervalTree() -ignored_addresses: Set[int] = set() -to_mark_as_defined: Set[str] = set() +ignored_addresses: set[int] = set() +to_mark_as_defined: set[str] = set() # Initialize a spimdisasm context, used to store symbols and functions spim_context = spimdisasm.common.Context() @@ -52,7 +54,7 @@ def is_falsey(str: str) -> bool: return str.lower() in FALSEY_VALS -def add_symbol(sym: "Symbol"): +def add_symbol(sym: Symbol) -> None: all_symbols.append(sym) if sym.vram_start is not None: if sym.vram_start not in all_symbols_dict: @@ -74,21 +76,21 @@ def to_cname(symbol_name: str) -> str: def handle_sym_addrs( - path: Path, sym_addrs_lines: List[str], all_segments: "List[Segment]" -): - def get_seg_for_name(name: str) -> Optional["Segment"]: + path: Path, sym_addrs_lines: list[str], all_segments: list[Segment] +) -> None: + def get_seg_for_name(name: str) -> Segment | None: for segment in all_segments: if segment.name == name: return segment return None - def get_seg_for_rom(rom: int) -> Optional["Segment"]: + def get_seg_for_rom(rom: int) -> Segment | None: for segment in all_segments: if segment.contains_rom(rom): return segment return None - seen_symbols: Dict[str, "Symbol"] = dict() + seen_symbols: dict[str, Symbol] = dict() prog_bar = progress_bar.get_progress_bar(sym_addrs_lines) prog_bar.set_description(f"Loading symbols ({path.stem})") line: str @@ -330,7 +332,7 @@ def get_seg_for_rom(rom: int) -> Optional["Segment"]: add_symbol(sym) -def initialize(all_segments: "List[Segment]"): +def initialize(all_segments: list[Segment]) -> None: global all_symbols global all_symbols_dict global all_symbols_ranges @@ -342,23 +344,23 @@ def initialize(all_segments: "List[Segment]"): # Manual list of func name / addrs for path in options.opts.symbol_addrs_paths: if path.exists(): - with open(path) as f: + with open(path, encoding="utf-8") as f: sym_addrs_lines = f.readlines() handle_sym_addrs(path, sym_addrs_lines, all_segments) -def initialize_spim_context(all_segments: "List[Segment]") -> None: +def initialize_spim_context(all_segments: list[Segment]) -> None: global_vrom_start = None global_vrom_end = None global_vram_start = options.opts.global_vram_start global_vram_end = options.opts.global_vram_end - overlay_segments: Set[spimdisasm.common.SymbolsSegment] = set() + overlay_segments: set[spimdisasm.common.SymbolsSegment] = set() spim_context.bannedSymbols |= ignored_addresses from ..segtypes.common.code import CommonSegCode - global_segments_after_overlays: List[CommonSegCode] = [] + global_segments_after_overlays: list[CommonSegCode] = [] for segment in all_segments: if not isinstance(segment, CommonSegCode): @@ -493,7 +495,7 @@ def initialize_spim_context(all_segments: "List[Segment]") -> None: def add_symbol_to_spim_segment( - segment: spimdisasm.common.SymbolsSegment, sym: "Symbol" + segment: spimdisasm.common.SymbolsSegment, sym: Symbol ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = segment.addFunction( @@ -551,7 +553,7 @@ def add_symbol_to_spim_segment( def add_symbol_to_spim_section( - section: spimdisasm.mips.sections.SectionBase, sym: "Symbol" + section: spimdisasm.mips.sections.SectionBase, sym: Symbol ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = section.addFunction( @@ -603,11 +605,11 @@ def add_symbol_to_spim_section( # force_in_segment=True when the symbol belongs to this specific segment. # force_in_segment=False when this symbol is just a reference. def create_symbol_from_spim_symbol( - segment: "Segment", + segment: Segment, context_sym: spimdisasm.common.ContextSymbol, *, force_in_segment: bool, -) -> "Symbol": +) -> Symbol: in_segment = False sym_type = None @@ -657,7 +659,7 @@ def create_symbol_from_spim_symbol( return sym -def mark_c_funcs_as_defined(): +def mark_c_funcs_as_defined() -> None: for symbol in all_symbols: if len(to_mark_as_defined) == 0: return @@ -671,12 +673,12 @@ def mark_c_funcs_as_defined(): class Symbol: vram_start: int - given_name: Optional[str] = None - given_name_end: Optional[str] = None - rom: Optional[int] = None - type: Optional[str] = None - given_size: Optional[int] = None - segment: Optional["Segment"] = None + given_name: str | None = None + given_name_end: str | None = None + rom: int | None = None + type: str | None = None + given_size: int | None = None + segment: Segment | None = None defined: bool = False referenced: bool = False @@ -685,27 +687,27 @@ class Symbol: force_migration: bool = False force_not_migration: bool = False - function_owner: Optional[str] = None + function_owner: str | None = None allow_addend: bool = False dont_allow_addend: bool = False - can_reference: Optional[bool] = None - can_be_referenced: Optional[bool] = None + can_reference: bool | None = None + can_be_referenced: bool | None = None - linker_section: Optional[str] = None + linker_section: str | None = None allow_duplicated: bool = False - given_filename: Optional[str] = None - given_visibility: Optional[str] = None + given_filename: str | None = None + given_visibility: str | None = None - given_align: Optional[int] = None + given_align: int | None = None - _generated_default_name: Optional[str] = None - _last_type: Optional[str] = None + _generated_default_name: str | None = None + _last_type: str | None = None - def __str__(self): + def __str__(self) -> str: return self.name def __eq__(self, other: object) -> bool: @@ -714,7 +716,7 @@ def __eq__(self, other: object) -> bool: return self.vram_start == other.vram_start and self.segment == other.segment # https://stackoverflow.com/a/56915493/6292472 - def __hash__(self): + def __hash__(self) -> int: return hash((self.vram_start, self.segment)) def format_name(self, format: str) -> str: @@ -769,11 +771,11 @@ def default_name(self) -> str: return self._generated_default_name @property - def rom_end(self): + def rom_end(self) -> int | None: return None if not self.rom else self.rom + self.size @property - def vram_end(self): + def vram_end(self) -> int: return self.vram_start + self.size @property @@ -792,19 +794,21 @@ def filename(self) -> str: return self.given_filename return self.name - def contains_vram(self, offset): + def contains_vram(self, offset: int) -> bool: return offset >= self.vram_start and offset < self.vram_end - def contains_rom(self, offset): + def contains_rom(self, offset: int) -> bool: + if self.rom is None or self.rom_end is None: + return False return offset >= self.rom and offset < self.rom_end -def get_all_symbols(): +def get_all_symbols() -> list[Symbol]: global all_symbols return all_symbols -def reset_symbols(): +def reset_symbols() -> None: global all_symbols global all_symbols_dict global all_symbols_ranges diff --git a/src/splat/util/utils.py b/src/splat/util/utils.py index a311dd3b..ec0656c7 100644 --- a/src/splat/util/utils.py +++ b/src/splat/util/utils.py @@ -1,9 +1,11 @@ -from typing import List, Optional, TypeVar +from __future__ import annotations + +from typing import TypeVar T = TypeVar("T") -def list_index(the_list: List[T], value: T) -> Optional[int]: +def list_index(the_list: list[T], value: T) -> int | None: if value not in the_list: return None return the_list.index(value) diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index b28c6d7f..8c8e752a 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -1,18 +1,20 @@ +from __future__ import annotations + from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional +from typing import TypedDict, NotRequired -from . import log +from splat.util import log @dataclass(frozen=True) class VramClass: name: str vram: int - given_vram_symbol: Optional[str] = None - follows_classes: List[str] = field(default_factory=list, compare=False) + given_vram_symbol: str | None = None + follows_classes: list[str] = field(default_factory=list, compare=False) @property - def vram_symbol(self) -> Optional[str]: + def vram_symbol(self) -> str | None: if self.given_vram_symbol is not None: return self.given_vram_symbol elif self.follows_classes: @@ -21,10 +23,37 @@ def vram_symbol(self) -> Optional[str]: return None -_vram_classes: Dict[str, VramClass] = {} +_vram_classes: dict[str, VramClass] = {} -def initialize(yaml: Any): +class SerializedSegmentData(TypedDict): + name: NotRequired[str] + vram: int + vram_symbol: str | None + follows_classes: list[str] + vram_class: NotRequired[str] + follows_vram: NotRequired[str | None] + align: NotRequired[str] + subalign: NotRequired[str] + section_order: NotRequired[list[str]] + start: NotRequired[str] + type: NotRequired[str] + dir: NotRequired[str] + symbol_name_format: NotRequired[str] + symbol_name_format_no_rom: NotRequired[str] + path: NotRequired[str] + bss_contains_common: NotRequired[bool] + linker_section_order: NotRequired[str] + linker_section: NotRequired[str] + ld_fill_value: NotRequired[int] + ld_align_segment_start: NotRequired[int] + pair_segment: NotRequired[str] + exclusive_ram_id: NotRequired[str] + find_file_boundaries: NotRequired[bool] + + + +def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: global _vram_classes _vram_classes = {} @@ -47,8 +76,8 @@ def initialize(yaml: Any): for vram_class in yaml: name: str vram: int - vram_symbol: Optional[str] = None - follows_classes: List[str] = [] + vram_symbol: str | None = None + follows_classes: list[str] = [] if isinstance(vram_class, dict): if "name" not in vram_class: @@ -83,7 +112,7 @@ def initialize(yaml: Any): f"vram_class ({vram_class}) must have 2 elements, got {len(vram_class)}" ) name = vram_class[0] - vram = vram_class[1] + vram = int(vram_class[1]) else: log.error(f"vram_class must be a dict or list, got {type(vram_class)}") From 0df25a418fce5b8bf30cb2f44c870174acbc4bf1 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:32:30 -0600 Subject: [PATCH 02/19] Add ruff linting rules --- pyproject.toml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index afb0071d..63310af9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,3 +71,44 @@ no_implicit_optional = true strict = true warn_unreachable = true check_untyped_defs = true + +[tool.ruff.lint.isort] +combine-as-imports = true + +[tool.ruff] +line-length = 79 +fix = true + +include = ["*.py", "*.pyi", "**/pyproject.toml"] + +[tool.ruff.lint] +extend-select = [ + "A", # flake8-builtins + "COM", # flake8-commas + "E", # Error + "FA", # flake8-future-annotations + "I", # isort + "ICN", # flake8-import-conventions + "PYI", # flake8-pyi + "R", # Refactor + "RET", # flake8-return + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "TCH", # flake8-type-checking + "UP", # pyupgrade + "W", # Warning + "YTT", # flake8-2020 +] +extend-ignore = [ + "E501", # line-too-long + "PYI041", # redundant-numeric-union + "SIM117", # multiple-with-statements +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "D100", # undocumented-public-module + "D103", # undocumented-public-function + "D107", # undocumented-public-init +] From b0aade7e1e5611cfc9feebf07eebff002530b02d Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:42:48 -0600 Subject: [PATCH 03/19] Revert import changes --- src/splat/__init__.py | 10 +++++----- src/splat/disassembler/disassembler_section.py | 2 +- src/splat/segtypes/common/asm.py | 2 +- src/splat/segtypes/common/code.py | 4 ++-- src/splat/segtypes/common/codesubsegment.py | 8 ++++---- src/splat/segtypes/common/group.py | 10 +++++----- src/splat/segtypes/common/header.py | 4 ++-- src/splat/segtypes/common/segment.py | 2 +- src/splat/segtypes/linker_entry.py | 6 +++--- src/splat/segtypes/segment.py | 17 +++++++++-------- src/splat/util/cache_handler.py | 4 ++-- src/splat/util/color.py | 2 +- src/splat/util/options.py | 4 ++-- src/splat/util/palettes.py | 10 +++++----- src/splat/util/relocs.py | 2 +- src/splat/util/statistics.py | 2 +- src/splat/util/symbols.py | 6 +++--- src/splat/util/vram_classes.py | 2 +- 18 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/splat/__init__.py b/src/splat/__init__.py index dbe887fd..301b77a9 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -4,9 +4,9 @@ __version__ = "0.37.3" __author__ = "ethteck" -from splat import util as util -from splat import disassembler as disassembler -from splat import platforms as platforms -from splat import segtypes as segtypes +from . import util as util +from . import disassembler as disassembler +from . import platforms as platforms +from . import segtypes as segtypes -from splat import scripts as scripts +from . import scripts as scripts diff --git a/src/splat/disassembler/disassembler_section.py b/src/splat/disassembler/disassembler_section.py index da18944c..5c293946 100644 --- a/src/splat/disassembler/disassembler_section.py +++ b/src/splat/disassembler/disassembler_section.py @@ -4,7 +4,7 @@ import spimdisasm -from splat.util import options, symbols +from ..util import options, symbols class DisassemblerSection(ABC): diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index 81579390..c1e5f9cc 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,7 +1,7 @@ from typing import Optional -from splat.segtypes.common.codesubsegment import CommonSegCodeSubsegment +from .codesubsegment import CommonSegCodeSubsegment class CommonSegAsm(CommonSegCodeSubsegment): diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 98b93776..ca556055 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -3,9 +3,9 @@ from collections import OrderedDict from typing import List, Optional, Type, Tuple -from splat.util import log, options, utils +from ...util import log, options, utils -from splat.segtypes.common.group import CommonSegGroup +from .group import CommonSegGroup from ..segment import Segment, parse_segment_align diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 74d82cc9..97972381 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -5,13 +5,13 @@ import spimdisasm import rabbitizer -from splat.util import options, symbols, log +from ...util import options, symbols, log -from splat.segtypes.common.code import CommonSegCode +from .code import CommonSegCode -from splat.segtypes.segment import Segment, parse_segment_vram, SerializedSegment +from ..segment import Segment, parse_segment_vram, SerializedSegment -from splat.disassembler.disassembler_section import DisassemblerSection, make_text_section +from ...disassembler.disassembler_section import DisassemblerSection, make_text_section # abstract class for c, asm, data, etc diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 1af2e25e..7ceb1c7a 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -2,14 +2,14 @@ from typing import TYPE_CHECKING, override -from splat.util import log +from ...util import log -from splat.segtypes.common.segment import CommonSegment -from splat.segtypes.segment import empty_statistics, Segment, SegmentStatistics +from .segment import CommonSegment +from ..segment import empty_statistics, Segment, SegmentStatistics if TYPE_CHECKING: - from splat.util.vram_classes import SerializedSegmentData - from splat.segtypes.linker_entry import LinkerEntry + from ...util.vram_classes import SerializedSegmentData + from ..linker_entry import LinkerEntry class CommonSegGroup(CommonSegment): diff --git a/src/splat/segtypes/common/header.py b/src/splat/segtypes/common/header.py index 135f2e81..0bab56ad 100644 --- a/src/splat/segtypes/common/header.py +++ b/src/splat/segtypes/common/header.py @@ -2,9 +2,9 @@ from pathlib import Path -from splat.util import options +from ...util import options -from splat.segtypes.segment import CommonSegment +from .segment import CommonSegment class CommonSegHeader(CommonSegment): diff --git a/src/splat/segtypes/common/segment.py b/src/splat/segtypes/common/segment.py index fef55086..091d02b8 100644 --- a/src/splat/segtypes/common/segment.py +++ b/src/splat/segtypes/common/segment.py @@ -1,4 +1,4 @@ -from splat.segtypes.segment import Segment +from ..segment import Segment class CommonSegment(Segment): diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index e964597b..adf35c86 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import Dict, List, OrderedDict, Set, Tuple, Union, Optional -from splat.util import options, log +from ..util import options, log -from splat.segtypes.segment import Segment -from splat.util.symbols import to_cname +from .segment import Segment +from ..util.symbols import to_cname # clean 'foo/../bar' to 'bar' diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index bdf99898..dd55a2f6 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -9,17 +9,18 @@ from typing import Optional, Type, TYPE_CHECKING, Union, Dict, TypeAlias, List from intervaltree import Interval, IntervalTree -from splat.util import vram_classes +from ..util import vram_classes -from splat.util.vram_classes import VramClass, SerializedSegmentData -from splat.util import log, options, symbols -from splat.util.symbols import Symbol, to_cname -from splat import __package_name__ +from ..util.vram_classes import VramClass, SerializedSegmentData +from ..util import log, options, symbols +from ..util.symbols import Symbol, to_cname + +from .. import __package_name__ # circular import if TYPE_CHECKING: - from splat.segtypes.linker_entry import LinkerEntry + from ..segtypes.linker_entry import LinkerEntry SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] @@ -435,8 +436,8 @@ def from_yaml( ret.parent = parent # Import here to avoid circular imports - from splat.segtypes.common.code import CommonSegCode - from splat.segtypes.common.bss import CommonSegBss + from .common.code import CommonSegCode + from .common.bss import CommonSegBss if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss): # We need to know the bss space for the segment. diff --git a/src/splat/util/cache_handler.py b/src/splat/util/cache_handler.py index da894518..9d86e750 100644 --- a/src/splat/util/cache_handler.py +++ b/src/splat/util/cache_handler.py @@ -3,8 +3,8 @@ import pickle from typing import Any -from splat.util import options, log -from splat.segtypes.segment import Segment +from . import options, log +from ..segtypes.common.segment import Segment class Cache: diff --git a/src/splat/util/color.py b/src/splat/util/color.py index a361041e..22e01660 100644 --- a/src/splat/util/color.py +++ b/src/splat/util/color.py @@ -2,7 +2,7 @@ from math import ceil -from splat.util import options +from . import options # RRRRRGGG GGBBBBBA diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 0d2cdefd..40895e06 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -6,8 +6,8 @@ from typing import cast, Literal, Type, TypeVar from collections.abc import Mapping -from splat.util import compiler -from splat.util.compiler import Compiler +from . import compiler +from .compiler import Compiler @dataclass diff --git a/src/splat/util/palettes.py b/src/splat/util/palettes.py index d3bdabd1..d83d24c1 100644 --- a/src/splat/util/palettes.py +++ b/src/splat/util/palettes.py @@ -2,14 +2,14 @@ from typing import Dict, TYPE_CHECKING -from splat.util import log +from ..util import log -from splat.segtypes.common.group import CommonSegGroup -from splat.segtypes.n64.ci import N64SegCi -from splat.segtypes.n64.palette import N64SegPalette +from ..segtypes.common.group import CommonSegGroup +from ..segtypes.n64.ci import N64SegCi +from ..segtypes.n64.palette import N64SegPalette if TYPE_CHECKING: - from splat.segtypes.segment import Segment + from ..segtypes.segment import Segment global_ids: Dict[str, N64SegPalette] = {} diff --git a/src/splat/util/relocs.py b/src/splat/util/relocs.py index 00be694a..5c7c02a9 100644 --- a/src/splat/util/relocs.py +++ b/src/splat/util/relocs.py @@ -5,7 +5,7 @@ import spimdisasm -from splat.util import log, options, symbols, progress_bar +from . import log, options, symbols, progress_bar @dataclass diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index 77cbbed0..af755dea 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -2,7 +2,7 @@ from colorama import Fore, Style -from splat.util import log +from . import log def fmt_size(size: int) -> str: diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index e8fcb210..b90d3620 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -7,14 +7,14 @@ import spimdisasm from intervaltree import IntervalTree -from splat.disassembler import disassembler_instance +from ..disassembler import disassembler_instance from pathlib import Path # circular import if TYPE_CHECKING: - from splat.segtypes.segment import Segment + from ..segtypes.segment import Segment -from splat.util import log, options, progress_bar +from . import log, options, progress_bar all_symbols: list[Symbol] = [] all_symbols_dict: dict[int, list[Symbol]] = {} diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index 8c8e752a..ec4f2edd 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from typing import TypedDict, NotRequired -from splat.util import log +from . import log @dataclass(frozen=True) From 826594c668a58ad5843261eed17eae65182f9bc1 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:28:51 -0600 Subject: [PATCH 04/19] Run typing-related ruff linting --- .pre-commit-config.yaml | 33 ++++ docs/VramClasses.md | 2 +- src/splat/__init__.py | 13 +- src/splat/__main__.py | 4 +- src/splat/disassembler/__init__.py | 8 +- src/splat/disassembler/disassembler.py | 3 +- .../disassembler/disassembler_instance.py | 5 +- .../disassembler/disassembler_section.py | 2 +- src/splat/disassembler/null_disassembler.py | 4 +- .../disassembler/spimdisasm_disassembler.py | 21 +- src/splat/platforms/__init__.py | 5 +- src/splat/platforms/ps2.py | 2 +- src/splat/scripts/__init__.py | 4 +- src/splat/scripts/capy.py | 2 +- src/splat/scripts/create_config.py | 53 ++--- src/splat/scripts/split.py | 136 +++++++------ src/splat/segtypes/__init__.py | 17 +- src/splat/segtypes/common/__init__.py | 44 +++-- src/splat/segtypes/common/asm.py | 5 +- src/splat/segtypes/common/bin.py | 16 +- src/splat/segtypes/common/bss.py | 31 ++- src/splat/segtypes/common/c.py | 105 +++++----- src/splat/segtypes/common/code.py | 84 ++++---- src/splat/segtypes/common/codesubsegment.py | 40 ++-- src/splat/segtypes/common/data.py | 48 ++--- src/splat/segtypes/common/databin.py | 7 +- src/splat/segtypes/common/eh_frame.py | 12 +- src/splat/segtypes/common/gcc_except_table.py | 15 +- src/splat/segtypes/common/group.py | 32 ++-- src/splat/segtypes/common/hasm.py | 3 +- src/splat/segtypes/common/header.py | 6 +- src/splat/segtypes/common/lib.py | 23 ++- src/splat/segtypes/common/linker_offset.py | 3 +- src/splat/segtypes/common/pad.py | 5 +- src/splat/segtypes/common/rodata.py | 65 ++++--- src/splat/segtypes/common/rodatabin.py | 7 +- src/splat/segtypes/common/textbin.py | 25 +-- src/splat/segtypes/linker_entry.py | 104 +++++----- src/splat/segtypes/n64/__init__.py | 44 +++-- src/splat/segtypes/n64/ci.py | 9 +- src/splat/segtypes/n64/decompressor.py | 9 +- src/splat/segtypes/n64/gfx.py | 74 ++++--- src/splat/segtypes/n64/header.py | 15 +- src/splat/segtypes/n64/img.py | 46 ++--- src/splat/segtypes/n64/palette.py | 31 +-- src/splat/segtypes/n64/vtx.py | 26 +-- src/splat/segtypes/ps2/ctor.py | 12 +- src/splat/segtypes/ps2/lit4.py | 12 +- src/splat/segtypes/ps2/lit8.py | 12 +- src/splat/segtypes/ps2/vtables.py | 12 +- src/splat/segtypes/psx/header.py | 32 ++-- src/splat/segtypes/segment.py | 181 ++++++++---------- src/splat/util/__init__.py | 34 ++-- src/splat/util/cache_handler.py | 10 +- src/splat/util/compiler.py | 7 +- src/splat/util/conf.py | 17 +- src/splat/util/file_presets.py | 4 +- src/splat/util/log.py | 17 +- src/splat/util/n64/__init__.py | 3 +- src/splat/util/n64/find_code_length.py | 2 +- src/splat/util/n64/rominfo.py | 101 +++++----- src/splat/util/options.py | 108 +++++------ src/splat/util/palettes.py | 24 ++- src/splat/util/progress_bar.py | 3 +- src/splat/util/ps2/ps2elfinfo.py | 20 +- src/splat/util/psx/psxexeinfo.py | 17 +- src/splat/util/relocs.py | 15 +- src/splat/util/statistics.py | 15 +- src/splat/util/symbols.py | 124 ++++++------ src/splat/util/vram_classes.py | 17 +- test.py | 67 +++---- 71 files changed, 1065 insertions(+), 1049 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..eac5c88e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +ci: + autofix_prs: true + autoupdate_schedule: quarterly + submodules: false + skip: [badgie] + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-ast + - id: check-yaml + - id: check-toml + - id: check-merge-conflict + - id: mixed-line-ending + - id: check-case-conflict + - id: check-added-large-files + - id: sort-simple-yaml + files: .pre-commit-config.yaml + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.0 + hooks: +## - id: ruff-format + - id: ruff-check + types: [file] + types_or: [python, pyi, toml] + args: ["--show-fixes"] +## - repo: https://github.com/adhtruong/mirrors-typos +## rev: v1.43.4 +## hooks: +## - id: typos diff --git a/docs/VramClasses.md b/docs/VramClasses.md index c889919c..840766b4 100644 --- a/docs/VramClasses.md +++ b/docs/VramClasses.md @@ -56,4 +56,4 @@ The following properties are optional and only take effect if `ld_use_symbolic_v - `follows_classes`: A list of vram class names that this class must come after in memory. If we added `follows_classes: [apples, bananas]` to our above vram_class, this would make all `maps` segments start at the end of all `apples` and `bananas` segments. -The internal linker script symbol name that is chosen for `follows_classes` is the name of the class followed by `_CLASS_VRAM`. You can override this by also specifying `vram_symbol`. \ No newline at end of file +The internal linker script symbol name that is chosen for `follows_classes` is the name of the class followed by `_CLASS_VRAM`. You can override this by also specifying `vram_symbol`. diff --git a/src/splat/__init__.py b/src/splat/__init__.py index 301b77a9..b8e6abc3 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -4,9 +4,10 @@ __version__ = "0.37.3" __author__ = "ethteck" -from . import util as util -from . import disassembler as disassembler -from . import platforms as platforms -from . import segtypes as segtypes - -from . import scripts as scripts +from . import ( + disassembler as disassembler, + platforms as platforms, + scripts as scripts, + segtypes as segtypes, + util as util, +) diff --git a/src/splat/__main__.py b/src/splat/__main__.py index 724d5604..f5686cea 100644 --- a/src/splat/__main__.py +++ b/src/splat/__main__.py @@ -12,11 +12,11 @@ def splat_main() -> None: ) parser.add_argument( - "-V", "--version", action="version", version=f"%(prog)s {splat.__version__}" + "-V", "--version", action="version", version=f"%(prog)s {splat.__version__}", ) subparsers = parser.add_subparsers( - description="action", help="The CLI utility to run", required=True + description="action", help="The CLI utility to run", required=True, ) splat.scripts.split.add_subparser(subparsers) diff --git a/src/splat/disassembler/__init__.py b/src/splat/disassembler/__init__.py index 23dfca8d..ba24e78b 100644 --- a/src/splat/disassembler/__init__.py +++ b/src/splat/disassembler/__init__.py @@ -1,3 +1,5 @@ -from . import disassembler as disassembler -from . import spimdisasm_disassembler as spimdisasm_disassembler -from . import disassembler_section as disassembler_section +from . import ( + disassembler as disassembler, + disassembler_section as disassembler_section, + spimdisasm_disassembler as spimdisasm_disassembler, +) diff --git a/src/splat/disassembler/disassembler.py b/src/splat/disassembler/disassembler.py index e06cad15..4a1b291f 100644 --- a/src/splat/disassembler/disassembler.py +++ b/src/splat/disassembler/disassembler.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Set class Disassembler(ABC): @@ -12,5 +11,5 @@ def check_version(self, skip_version_check: bool, splat_version: str): raise NotImplementedError("check_version") @abstractmethod - def known_types(self) -> Set[str]: + def known_types(self) -> set[str]: raise NotImplementedError("known_types") diff --git a/src/splat/disassembler/disassembler_instance.py b/src/splat/disassembler/disassembler_instance.py index 1745a442..f5450ed6 100644 --- a/src/splat/disassembler/disassembler_instance.py +++ b/src/splat/disassembler/disassembler_instance.py @@ -1,8 +1,7 @@ +from ..util import options from .disassembler import Disassembler -from .spimdisasm_disassembler import SpimdisasmDisassembler from .null_disassembler import NullDisassembler - -from ..util import options +from .spimdisasm_disassembler import SpimdisasmDisassembler __instance: Disassembler = NullDisassembler() __initialized = False diff --git a/src/splat/disassembler/disassembler_section.py b/src/splat/disassembler/disassembler_section.py index 5c293946..4fc8b5af 100644 --- a/src/splat/disassembler/disassembler_section.py +++ b/src/splat/disassembler/disassembler_section.py @@ -9,7 +9,7 @@ class DisassemblerSection(ABC): __slots__ = () - + @abstractmethod def disassemble(self) -> str: raise NotImplementedError("disassemble") diff --git a/src/splat/disassembler/null_disassembler.py b/src/splat/disassembler/null_disassembler.py index 5436e3af..fea13406 100644 --- a/src/splat/disassembler/null_disassembler.py +++ b/src/splat/disassembler/null_disassembler.py @@ -1,5 +1,5 @@ + from . import disassembler -from typing import Set class NullDisassembler(disassembler.Disassembler): @@ -9,5 +9,5 @@ def configure(self): def check_version(self, skip_version_check: bool, splat_version: str): pass - def known_types(self) -> Set[str]: + def known_types(self) -> set[str]: return set() diff --git a/src/splat/disassembler/spimdisasm_disassembler.py b/src/splat/disassembler/spimdisasm_disassembler.py index a9c60592..43b033c2 100644 --- a/src/splat/disassembler/spimdisasm_disassembler.py +++ b/src/splat/disassembler/spimdisasm_disassembler.py @@ -1,8 +1,9 @@ -from . import disassembler -import spimdisasm + import rabbitizer -from ..util import log, compiler, options -from typing import Set +import spimdisasm + +from ..util import compiler, log, options +from . import disassembler class SpimdisasmDisassembler(disassembler.Disassembler): @@ -43,10 +44,10 @@ def configure(self): rabbitizer.config.misc_opcodeLJust = options.opts.mnemonic_ljust - 1 rabbitizer.config.regNames_gprAbiNames = rabbitizer.Abi.fromStr( - options.opts.mips_abi_gpr + options.opts.mips_abi_gpr, ) rabbitizer.config.regNames_fprAbiNames = rabbitizer.Abi.fromStr( - options.opts.mips_abi_float_regs + options.opts.mips_abi_float_regs, ) if options.opts.endianness == "big": @@ -64,7 +65,7 @@ def configure(self): status="error", ) log.error( - f"The following options are supported: {list(spimdisasm.common.compilerOptions.keys())}" + f"The following options are supported: {list(spimdisasm.common.compilerOptions.keys())}", ) spimdisasm.common.GlobalConfig.COMPILER = spimdisasm_compiler if selected_compiler == compiler.SN64: @@ -130,12 +131,12 @@ def configure(self): def check_version(self, skip_version_check: bool, splat_version: str): if not skip_version_check and spimdisasm.__version_info__ < self.SPIMDISASM_MIN: log.error( - f"splat {splat_version} requires as minimum spimdisasm {self.SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}" + f"splat {splat_version} requires as minimum spimdisasm {self.SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}", ) log.write( - f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})" + f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})", ) - def known_types(self) -> Set[str]: + def known_types(self) -> set[str]: return spimdisasm.common.gKnownTypes diff --git a/src/splat/platforms/__init__.py b/src/splat/platforms/__init__.py index 173970fc..51b6a244 100644 --- a/src/splat/platforms/__init__.py +++ b/src/splat/platforms/__init__.py @@ -1,4 +1 @@ -from . import n64 as n64 -from . import ps2 as ps2 -from . import psx as psx -from . import psp as psp +from . import n64 as n64, ps2 as ps2, psp as psp, psx as psx diff --git a/src/splat/platforms/ps2.py b/src/splat/platforms/ps2.py index adee3bb9..09273679 100644 --- a/src/splat/platforms/ps2.py +++ b/src/splat/platforms/ps2.py @@ -1,5 +1,5 @@ -import spimdisasm import rabbitizer +import spimdisasm def init(target_bytes: bytes): diff --git a/src/splat/scripts/__init__.py b/src/splat/scripts/__init__.py index 14382eff..1ef99def 100644 --- a/src/splat/scripts/__init__.py +++ b/src/splat/scripts/__init__.py @@ -1,3 +1 @@ -from . import capy as capy -from . import create_config as create_config -from . import split as split +from . import capy as capy, create_config as create_config, split as split diff --git a/src/splat/scripts/capy.py b/src/splat/scripts/capy.py index 8bd48a95..77d0c4a1 100644 --- a/src/splat/scripts/capy.py +++ b/src/splat/scripts/capy.py @@ -40,6 +40,6 @@ def process_arguments(args: argparse.Namespace): def add_subparser(subparser: argparse._SubParsersAction): parser = subparser.add_parser( - "capy", help=script_description, description=script_description + "capy", help=script_description, description=script_description, ) parser.set_defaults(func=process_arguments) diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index eed507cd..6b31b888 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -1,19 +1,19 @@ #! /usr/bin/env python3 +from __future__ import annotations import argparse import hashlib -from pathlib import Path import subprocess import sys -from typing import Optional +from pathlib import Path +from ..util import conf, file_presets, log from ..util.n64 import find_code_length, rominfo -from ..util.psx import psxexeinfo from ..util.ps2 import ps2elfinfo -from ..util import log, file_presets, conf +from ..util.psx import psxexeinfo -def main(file_path: Path, objcopy: Optional[str]): +def main(file_path: Path, objcopy: str | None): if not file_path.exists(): sys.exit(f"File {file_path} does not exist ({file_path.absolute()})") if file_path.is_dir(): @@ -103,7 +103,7 @@ def create_n64_config(rom_path: Path): # Start analysing after the entrypoint segment. first_section_end = find_code_length.run( - rom_bytes, 0x1000 + rom.entrypoint_info.segment_size(), rom.entry_point + rom_bytes, 0x1000 + rom.entrypoint_info.segment_size(), rom.entry_point, ) extra_message = "" @@ -195,7 +195,7 @@ def create_n64_config(rom_path: Path): # Write reloc_addrs.txt file reloc_addrs: list[str] = [] - addresses_info: list[tuple[Optional[rominfo.EntryAddressInfo], str]] = [ + addresses_info: list[tuple[rominfo.EntryAddressInfo | None, str]] = [ (rom.entrypoint_info.main_address, "main"), (rom.entrypoint_info.bss_start_address, "main_BSS_START"), (rom.entrypoint_info.bss_size, "main_BSS_SIZE"), @@ -213,10 +213,10 @@ def create_n64_config(rom_path: Path): continue reloc_addrs.append( - f"rom:0x{addr_info.rom_hi:06X} reloc:MIPS_HI16 symbol:{sym_name}" + f"rom:0x{addr_info.rom_hi:06X} reloc:MIPS_HI16 symbol:{sym_name}", ) reloc_addrs.append( - f"rom:0x{addr_info.rom_lo:06X} reloc:MIPS_LO16 symbol:{sym_name}" + f"rom:0x{addr_info.rom_lo:06X} reloc:MIPS_LO16 symbol:{sym_name}", ) reloc_addrs.append("") @@ -225,32 +225,32 @@ def create_n64_config(rom_path: Path): and not rom.entrypoint_info.stack_top.ori ): reloc_addrs.append( - '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.' + '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.', ) reloc_addrs.append( - "// It is commented out because it was not possible to infer what the start of the stack symbol is, so you'll have to figure it out by yourself." + "// It is commented out because it was not possible to infer what the start of the stack symbol is, so you'll have to figure it out by yourself.", ) reloc_addrs.append( - "// Once you have found it you can properly name it and specify the length of this stack as the addend value here." + "// Once you have found it you can properly name it and specify the length of this stack as the addend value here.", ) reloc_addrs.append( - f"// The address of the end of the stack is 0x{rom.entrypoint_info.stack_top.value:08X}." + f"// The address of the end of the stack is 0x{rom.entrypoint_info.stack_top.value:08X}.", ) reloc_addrs.append( - f"// A common size for this stack is 0x2000, so try checking for the address 0x{rom.entrypoint_info.stack_top.value - 0x2000:08X}. Note the stack may have a different size." + f"// A common size for this stack is 0x2000, so try checking for the address 0x{rom.entrypoint_info.stack_top.value - 0x2000:08X}. Note the stack may have a different size.", ) reloc_addrs.append( - f"// rom:0x{rom.entrypoint_info.stack_top.rom_hi:06X} reloc:MIPS_HI16 symbol:main_stack addend:0xXXXX" + f"// rom:0x{rom.entrypoint_info.stack_top.rom_hi:06X} reloc:MIPS_HI16 symbol:main_stack addend:0xXXXX", ) reloc_addrs.append( - f"// rom:0x{rom.entrypoint_info.stack_top.rom_lo:06X} reloc:MIPS_LO16 symbol:main_stack addend:0xXXXX" + f"// rom:0x{rom.entrypoint_info.stack_top.rom_lo:06X} reloc:MIPS_LO16 symbol:main_stack addend:0xXXXX", ) reloc_addrs.append("") if reloc_addrs: with Path("reloc_addrs.txt").open("w", newline="\n") as f: print("Writing reloc_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Advanced-Reloc for documentation about this file\n" + "// Visit https://github.com/ethteck/splat/wiki/Advanced-Reloc for documentation about this file\n", ) f.write("// entrypoint relocs\n") contents = "\n".join(reloc_addrs) @@ -261,14 +261,14 @@ def create_n64_config(rom_path: Path): symbol_addrs.append(f"entrypoint = 0x{rom.entry_point:08X}; // type:func") if rom.entrypoint_info.main_address is not None: symbol_addrs.append( - f"main = 0x{rom.entrypoint_info.main_address.value:08X}; // type:func" + f"main = 0x{rom.entrypoint_info.main_address.value:08X}; // type:func", ) if symbol_addrs: symbol_addrs.append("") with Path("symbol_addrs.txt").open("w", newline="\n") as f: print("Writing symbol_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n" + "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n", ) contents = "\n".join(symbol_addrs) f.write(contents) @@ -374,7 +374,7 @@ def create_psx_config(exe_path: Path, exe_bytes: bytes): file_presets.write_all_files() -def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]): +def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): elf = ps2elfinfo.Ps2Elf.get_info(elf_path, elf_bytes) if elf is None: log.error(f"Unsupported elf file '{elf_path}'") @@ -496,7 +496,7 @@ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]): with Path("symbol_addrs.txt").open("w", newline="\n") as f: print("Writing symbol_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n" + "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n", ) contents = "\n".join(symbol_addrs) f.write(contents) @@ -509,21 +509,21 @@ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]): with Path("linker_script_extra.ld").open("w", newline="\n") as f: print("Writing linker_script_extra.ld") f.write( - "/* Pass this file to the linker with the `-T linker_script_extra.ld` flag */\n" + "/* Pass this file to the linker with the `-T linker_script_extra.ld` flag */\n", ) contents = "\n".join(linker_script) f.write(contents) print() print( - "The generated yaml does not use the actual ELF file as input, but instead it" + "The generated yaml does not use the actual ELF file as input, but instead it", ) print( - 'uses a "rom" generated from said ELF, which contains the game code without any' + 'uses a "rom" generated from said ELF, which contains the game code without any', ) print("of the elf metadata.") print( - 'Use the following command to generate this "rom". It is recommended to include' + 'Use the following command to generate this "rom". It is recommended to include', ) print("this command into your setup/configure script.") print("```") @@ -550,6 +550,7 @@ def find_objcopy() -> str: msg += f" - {name}\n" msg += "\nTry to install one of those or use the `--objcopy` flag to pass the name to your own objcopy to me." log.error(msg) + return None def run_objcopy(objcopy_name: str, elf_path: str, rom: str) -> list[str]: @@ -583,7 +584,7 @@ def process_arguments(args: argparse.Namespace): def add_subparser(subparser: argparse._SubParsersAction): parser = subparser.add_parser( - "create_config", help=script_description, description=script_description + "create_config", help=script_description, description=script_description, ) add_arguments_to_parser(parser) parser.set_defaults(func=process_arguments) diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index d2a1af1c..fed888b4 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -1,45 +1,55 @@ #! /usr/bin/env python3 +from __future__ import annotations import argparse import hashlib import importlib -from typing import Any, Dict, List, Optional, Set, Tuple, Union -from pathlib import Path - +import sys from collections import defaultdict, deque - -from .. import __package_name__, __version__ -from ..disassembler import disassembler_instance -from ..util import cache_handler, progress_bar, vram_classes, statistics, file_presets +from pathlib import Path +from typing import Any from colorama import Style from intervaltree import Interval, IntervalTree -import sys +from .. import __package_name__, __version__ +from ..disassembler import disassembler_instance +from ..segtypes.common.group import CommonSegGroup from ..segtypes.linker_entry import ( LinkerWriter, get_segment_vram_end_symbol_name, ) from ..segtypes.segment import Segment -from ..segtypes.common.group import CommonSegGroup -from ..util import conf, log, options, palettes, symbols, relocs +from ..util import ( + cache_handler, + conf, + file_presets, + log, + options, + palettes, + progress_bar, + relocs, + statistics, + symbols, + vram_classes, +) linker_writer: LinkerWriter -config: Dict[str, Any] +config: dict[str, Any] segment_roms: IntervalTree = IntervalTree() segment_rams: IntervalTree = IntervalTree() -def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: +def initialize_segments(config_segments: dict | list) -> list[Segment]: global segment_roms global segment_rams segment_roms = IntervalTree() segment_rams = IntervalTree() - segments_by_name: Dict[str, Segment] = {} - ret: List[Segment] = [] + segments_by_name: dict[str, Segment] = {} + ret: list[Segment] = [] # Cross segment pairing can be quite expensive, so we try to avoid it if the user haven't requested it. do_cross_segment_pairing = False @@ -55,19 +65,19 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: segment_class = Segment.get_class_for_type(seg_type) - this_start, is_auto_segment = Segment.parse_segment_start(seg_yaml) + this_start, _is_auto_segment = Segment.parse_segment_start(seg_yaml) j = i + 1 while j < len(config_segments): - next_start, next_is_auto_segment = Segment.parse_segment_start( - config_segments[j] + next_start, _next_is_auto_segment = Segment.parse_segment_start( + config_segments[j], ) if next_start is not None: break j += 1 if next_start is None: log.error( - "Next segment address could not be found. Segments list must end with a rom end pos marker ([0x10000000])" + "Next segment address could not be found. Segments list must end with a rom end pos marker ([0x10000000])", ) if segment_class.is_noload(): @@ -77,7 +87,7 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: next_start = last_rom_end segment: Segment = Segment.from_yaml( - segment_class, seg_yaml, this_start, next_start, None + segment_class, seg_yaml, this_start, next_start, None, ) if segment.require_unique_name: @@ -110,15 +120,15 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: if segment.given_follows_vram: if segment.given_follows_vram not in segments_by_name: log.error( - f"segment '{segment.given_follows_vram}', the 'follows_vram' value for segment '{segment.name}', does not exist" + f"segment '{segment.given_follows_vram}', the 'follows_vram' value for segment '{segment.name}', does not exist", ) segment.given_vram_symbol = get_segment_vram_end_symbol_name( - segments_by_name[segment.given_follows_vram] + segments_by_name[segment.given_follows_vram], ) if ret[-1].type == "pad": log.error( - "Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad" + "Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad", ) if do_cross_segment_pairing: @@ -130,13 +140,13 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: if seg == other_seg: continue if seg.pair_segment_name == other_seg.name and isinstance( - other_seg, CommonSegGroup + other_seg, CommonSegGroup, ): # We found the other segment specified by `pair_segment` if other_seg.pair_segment_name is not None: log.error( - f"Both segments '{seg.name}' and '{other_seg.name}' have a `pair_segment` value, when only at most one of the cross-paired segments can have this attribute." + f"Both segments '{seg.name}' and '{other_seg.name}' have a `pair_segment` value, when only at most one of the cross-paired segments can have this attribute.", ) # Not user error, hopefully... @@ -153,7 +163,7 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: other_seg.paired_segment = seg if isinstance(seg, CommonSegGroup) and isinstance( - other_seg, CommonSegGroup + other_seg, CommonSegGroup, ): # Pair the subsegments seg.pair_subsegments_to_other_segment(other_seg) @@ -162,7 +172,7 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: if not found: log.error( - f"Segment '{seg.pair_segment_name}' not found.\n It is referenced by segment '{seg.name}', because of the `pair_segment` attribute." + f"Segment '{seg.pair_segment_name}' not found.\n It is referenced by segment '{seg.name}', because of the `pair_segment` attribute.", ) return ret @@ -174,7 +184,7 @@ def assign_symbols_to_segments() -> None: continue if symbol.rom: - cands: Set[Interval] = segment_roms[symbol.rom] + cands: set[Interval] = segment_roms[symbol.rom] if len(cands) > 1: log.error("multiple segments rom overlap symbol", symbol) elif len(cands) == 0: @@ -185,7 +195,7 @@ def assign_symbols_to_segments() -> None: seg.add_symbol(symbol) else: cands = segment_rams[symbol.vram_start] - segs: List[Segment] = [cand.data for cand in cands] + segs: list[Segment] = [cand.data for cand in cands] for seg in segs: if not seg.get_exclusive_ram_id(): seg.add_symbol(symbol) @@ -200,10 +210,10 @@ def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str: # Return a mapping of vram classes to segments that need to be part of their vram symbol's calculation def calc_segment_dependences( - all_segments: List[Segment], -) -> Dict[vram_classes.VramClass, List[Segment]]: + all_segments: list[Segment], +) -> dict[vram_classes.VramClass, list[Segment]]: # Map vram class names to segments that have that vram class - vram_class_to_segments: Dict[str, List[Segment]] = {} + vram_class_to_segments: dict[str, list[Segment]] = {} for seg in all_segments: if seg.vram_class is not None: if seg.vram_class.name not in vram_class_to_segments: @@ -211,7 +221,7 @@ def calc_segment_dependences( vram_class_to_segments[seg.vram_class.name].append(seg) # Map vram class names to segments that the vram class follows - vram_class_to_follows_segments: Dict[vram_classes.VramClass, List[Segment]] = {} + vram_class_to_follows_segments: dict[vram_classes.VramClass, list[Segment]] = {} for vram_class in vram_classes._vram_classes.values(): if vram_class.follows_classes: vram_class_to_follows_segments[vram_class] = [] @@ -225,16 +235,16 @@ def calc_segment_dependences( def sort_segments_by_vram_class_dependency( - all_segments: List[Segment], -) -> List[Segment]: + all_segments: list[Segment], +) -> list[Segment]: # map all "_VRAM_END" strings to segments - end_sym_to_seg: Dict[str, Segment] = {} + end_sym_to_seg: dict[str, Segment] = {} for seg in all_segments: end_sym_to_seg[get_segment_vram_end_symbol_name(seg)] = seg # build dependency graph: A -> B means "A must come before B" - graph: Dict[Segment, List[Segment]] = defaultdict(list) - indeg: Dict[Segment, int] = {seg: 0 for seg in all_segments} + graph: dict[Segment, list[Segment]] = defaultdict(list) + indeg: dict[Segment, int] = {seg: 0 for seg in all_segments} for seg in all_segments: sym = seg.vram_symbol @@ -248,7 +258,7 @@ def sort_segments_by_vram_class_dependency( # stable topo sort with queue seeded in original order q = deque([seg for seg in all_segments if indeg[seg] == 0]) - out: List[Segment] = [] + out: list[Segment] = [] while q: n = q.popleft() @@ -281,7 +291,7 @@ def read_target_binary() -> bytes: def initialize_platform(rom_bytes: bytes): platform_module = importlib.import_module( - f"{__package_name__}.platforms.{options.opts.platform}" + f"{__package_name__}.platforms.{options.opts.platform}", ) platform_init = getattr(platform_module, "init") platform_init(rom_bytes) @@ -289,7 +299,7 @@ def initialize_platform(rom_bytes: bytes): return platform_module -def initialize_all_symbols(all_segments: List[Segment]): +def initialize_all_symbols(all_segments: list[Segment]): # Load and process symbols symbols.initialize(all_segments) relocs.initialize() @@ -303,12 +313,12 @@ def initialize_all_symbols(all_segments: List[Segment]): def do_scan( - all_segments: List[Segment], + all_segments: list[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, ): - processed_segments: List[Segment] = [] + processed_segments: list[Segment] = [] scan_bar = progress_bar.get_progress_bar(all_segments) for segment in scan_bar: @@ -336,7 +346,7 @@ def do_scan( def do_split( - all_segments: List[Segment], + all_segments: list[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, @@ -359,14 +369,14 @@ def do_split( segment.split(segment_bytes) -def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: +def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: if options.opts.ld_sort_segments_by_vram_class_dependency: all_segments = sort_segments_by_vram_class_dependency(all_segments) vram_class_dependencies = calc_segment_dependences(all_segments) vram_classes_to_search = set(vram_class_dependencies.keys()) - max_vram_end_insertion_points: Dict[Segment, List[Tuple[str, List[Segment]]]] = {} + max_vram_end_insertion_points: dict[Segment, list[tuple[str, list[Segment]]]] = {} for seg in reversed(all_segments): if seg.vram_class in vram_classes_to_search: assert seg.vram_class.vram_symbol is not None @@ -376,7 +386,7 @@ def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: ( seg.vram_class.vram_symbol, vram_class_dependencies[seg.vram_class], - ) + ), ) vram_classes_to_search.remove(seg.vram_class) @@ -390,11 +400,11 @@ def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: if partial_linking: if partial_scripts_path is None: log.error( - "Partial linking is enabled but `ld_partial_scripts_path` has not been set" + "Partial linking is enabled but `ld_partial_scripts_path` has not been set", ) if options.opts.ld_partial_build_segments_path is None: log.error( - "Partial linking is enabled but `ld_partial_build_segments_path` has not been set" + "Partial linking is enabled but `ld_partial_build_segments_path` has not been set", ) for segment in linker_bar: @@ -415,7 +425,7 @@ def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: seg_name = segment.get_cname() sub_linker_writer.save_linker_script( - partial_scripts_path / f"{seg_name}.ld" + partial_scripts_path / f"{seg_name}.ld", ) if options.opts.ld_dependencies: sub_linker_writer.save_dependencies_file( @@ -432,10 +442,10 @@ def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: elf_path = options.opts.elf_path if elf_path is None: log.error( - "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options" + "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options", ) linker_writer.save_dependencies_file( - options.opts.ld_script_path.with_suffix(".d"), elf_path + options.opts.ld_script_path.with_suffix(".d"), elf_path, ) return linker_writer @@ -446,14 +456,14 @@ def write_ld_dependencies(linker_writer: LinkerWriter): elf_path = options.opts.elf_path if elf_path is None: log.error( - "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options" + "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options", ) linker_writer.save_dependencies_file( - options.opts.ld_script_path.with_suffix(".d"), elf_path + options.opts.ld_script_path.with_suffix(".d"), elf_path, ) -def write_elf_sections_file(all_segments: List[Segment]): +def write_elf_sections_file(all_segments: list[Segment]): # write elf_sections.txt - this only lists the generated sections in the elf, not subsections # that the elf combines into one section if options.opts.elf_section_list_path: @@ -465,7 +475,7 @@ def write_elf_sections_file(all_segments: List[Segment]): f.write(section_list) -def write_undefined_auto(to_write: List[symbols.Symbol], file_path: Path): +def write_undefined_auto(to_write: list[symbols.Symbol], file_path: Path): file_path.parent.mkdir(parents=True, exist_ok=True) with file_path.open("w", newline="\n") as f: for symbol in to_write: @@ -498,11 +508,11 @@ def write_undefined_syms_auto(): write_undefined_auto(to_write, options.opts.undefined_syms_auto_path) -def print_segment_warnings(all_segments: List[Segment]): +def print_segment_warnings(all_segments: list[Segment]): for segment in all_segments: if len(segment.warnings) > 0: log.write( - f"{Style.DIM}0x{segment.rom_start:06X}{Style.RESET_ALL} {segment.type} {Style.BRIGHT}{segment.name}{Style.RESET_ALL}:" + f"{Style.DIM}0x{segment.rom_start:06X}{Style.RESET_ALL} {segment.type} {Style.BRIGHT}{segment.name}{Style.RESET_ALL}:", ) for warn in segment.warnings: @@ -520,7 +530,7 @@ def dump_symbols() -> None: with open(splat_hidden_folder / "splat_symbols.csv", "w") as f: f.write( - "vram_start,given_name,name,type,given_size,size,rom,defined,user_declared,referenced,extract\n" + "vram_start,given_name,name,type,given_size,size,rom,defined,user_declared,referenced,extract\n", ) for s in sorted(symbols.all_symbols, key=lambda x: x.vram_start): f.write(f"{s.vram_start:X},{s.given_name},{s.name},{s.type},") @@ -539,8 +549,8 @@ def dump_symbols() -> None: def main( - config_path: List[Path], - modes: Optional[List[str]], + config_path: list[Path], + modes: list[str] | None, verbose: bool, use_cache: bool = True, skip_version_check: bool = False, @@ -592,7 +602,7 @@ def main( do_split(all_segments, rom_bytes, stats, cache) if options.opts.is_mode_active( - "ld" + "ld", ): # TODO move this to platform initialization when it gets implemented global linker_writer linker_writer = write_linker_script(all_segments) @@ -630,7 +640,7 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): parser.add_argument("--modes", nargs="+", default=["all"]) parser.add_argument("--verbose", action="store_true", help="Enable debug logging") parser.add_argument( - "--use-cache", action="store_true", help="Only split changed segments in config" + "--use-cache", action="store_true", help="Only split changed segments in config", ) parser.add_argument( "--skip-version-check", @@ -672,7 +682,7 @@ def process_arguments(args: argparse.Namespace): def add_subparser(subparser: argparse._SubParsersAction): parser = subparser.add_parser( - "split", help=script_description, description=script_description + "split", help=script_description, description=script_description, ) add_arguments_to_parser(parser) parser.set_defaults(func=process_arguments) diff --git a/src/splat/segtypes/__init__.py b/src/splat/segtypes/__init__.py index 8204998a..a366fd79 100644 --- a/src/splat/segtypes/__init__.py +++ b/src/splat/segtypes/__init__.py @@ -1,8 +1,9 @@ -from . import linker_entry as linker_entry -from . import segment as segment - -from . import common as common -from . import n64 as n64 -from . import ps2 as ps2 -from . import psx as psx -from . import psp as psp +from . import ( + common as common, + linker_entry as linker_entry, + n64 as n64, + ps2 as ps2, + psp as psp, + psx as psx, + segment as segment, +) diff --git a/src/splat/segtypes/common/__init__.py b/src/splat/segtypes/common/__init__.py index bf1ad25c..22c1541d 100644 --- a/src/splat/segtypes/common/__init__.py +++ b/src/splat/segtypes/common/__init__.py @@ -1,21 +1,23 @@ -from . import asm as asm -from . import bin as bin -from . import bss as bss -from . import code as code -from . import codesubsegment as codesubsegment -from . import cpp as cpp -from . import c as c -from . import databin as databin -from . import data as data -from . import group as group -from . import hasm as hasm -from . import header as header -from . import lib as lib -from . import linker_offset as linker_offset -from . import rdata as rdata -from . import rodatabin as rodatabin -from . import rodata as rodata -from . import sbss as sbss -from . import sdata as sdata -from . import segment as segment -from . import textbin as textbin +from . import ( + asm as asm, + bin as bin, # noqa: A004 # Import `bin` is shadowing a Python builtin + bss as bss, + c as c, + code as code, + codesubsegment as codesubsegment, + cpp as cpp, + data as data, + databin as databin, + group as group, + hasm as hasm, + header as header, + lib as lib, + linker_offset as linker_offset, + rdata as rdata, + rodata as rodata, + rodatabin as rodatabin, + sbss as sbss, + sdata as sdata, + segment as segment, + textbin as textbin, +) diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index c1e5f9cc..6f6cd018 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,5 +1,4 @@ -from typing import Optional - +from __future__ import annotations from .codesubsegment import CommonSegCodeSubsegment @@ -9,7 +8,7 @@ class CommonSegAsm(CommonSegCodeSubsegment): def is_text() -> bool: return True - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" def scan(self, rom_bytes: bytes): diff --git a/src/splat/segtypes/common/bin.py b/src/splat/segtypes/common/bin.py index 7c4b80d9..fa87ed68 100644 --- a/src/splat/segtypes/common/bin.py +++ b/src/splat/segtypes/common/bin.py @@ -1,10 +1,14 @@ -from pathlib import Path -from typing import Optional +from __future__ import annotations -from ...util import log, options +from typing import TYPE_CHECKING +from ...util import log, options from .segment import CommonSegment -from ..segment import SegmentType + +if TYPE_CHECKING: + from pathlib import Path + + from ..segment import SegmentType class CommonSegBin(CommonSegment): @@ -12,7 +16,7 @@ class CommonSegBin(CommonSegment): def is_data() -> bool: return True - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return options.opts.asset_path / self.dir / f"{self.name}.bin" def split(self, rom_bytes): @@ -22,7 +26,7 @@ def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) if self.size is None or self.size <= 0: diff --git a/src/splat/segtypes/common/bss.py b/src/splat/segtypes/common/bss.py index 5a9adbc9..6c053188 100644 --- a/src/splat/segtypes/common/bss.py +++ b/src/splat/segtypes/common/bss.py @@ -1,11 +1,12 @@ -from typing import Optional - -from ...util import options, symbols, log +from __future__ import annotations +from ...disassembler.disassembler_section import ( + DisassemblerSection, + make_bss_section, +) +from ...util import log, options, symbols from .data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection, make_bss_section - # If `options.opts.ld_bss_is_noload` is False, then this segment behaves like a `CommonSegData` @@ -13,23 +14,19 @@ class CommonSegBss(CommonSegData): def get_linker_section(self) -> str: return ".bss" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" @staticmethod def is_data() -> bool: - if not options.opts.ld_bss_is_noload: - return True - return False + return bool(not options.opts.ld_bss_is_noload) @staticmethod def is_noload() -> bool: - if not options.opts.ld_bss_is_noload: - return False - return True + return options.opts.ld_bss_is_noload def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" @@ -45,7 +42,7 @@ def disassemble_data(self, rom_bytes: bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", ) # Supposedly logic error, not user error @@ -57,11 +54,11 @@ def disassemble_data(self, rom_bytes: bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", ) next_subsegment = self.parent.get_next_subsegment_for_ram( - self.vram_start, self.index_within_group + self.vram_start, self.index_within_group, ) if next_subsegment is None: bss_end = self.get_most_parent().vram_end @@ -88,7 +85,7 @@ def disassemble_data(self, rom_bytes: bytes): for spim_sym in self.spim_section.get_section().symbolList: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), spim_sym.contextSym, force_in_segment=True + self.get_most_parent(), spim_sym.contextSym, force_in_segment=True, ) def should_scan(self) -> bool: diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index 77990eb4..dacee761 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -1,18 +1,20 @@ +from __future__ import annotations + import os import re from pathlib import Path -from typing import Optional, Set, List +from typing import TYPE_CHECKING import rabbitizer import spimdisasm from ...util import log, options, symbols from ...util.compiler import IDO -from ...util.symbols import Symbol - from .codesubsegment import CommonSegCodeSubsegment from .rodata import CommonSegRodata +if TYPE_CHECKING: + from ...util.symbols import Symbol STRIP_C_COMMENTS_RE = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', @@ -20,7 +22,7 @@ ) C_FUNC_RE = re.compile( - r"^(?:static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+?{", re.MULTILINE + r"^(?:static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+?{", re.MULTILINE, ) C_GLOBAL_ASM_IDO_RE = re.compile(r"GLOBAL_ASM\(\"(\w+\/)*(\w+)\.s\"\)", re.MULTILINE) @@ -30,9 +32,9 @@ class CommonSegC(CommonSegCodeSubsegment): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.defined_funcs: Set[str] = set() - self.global_asm_funcs: Set[str] = set() - self.global_asm_rodata_syms: Set[str] = set() + self.defined_funcs: set[str] = set() + self.global_asm_funcs: set[str] = set() + self.global_asm_rodata_syms: set[str] = set() self.file_extension = "c" @@ -44,14 +46,13 @@ def replacer(match): s = match.group(0) if s.startswith("/"): return " " - else: - return s + return s return re.sub(STRIP_C_COMMENTS_RE, replacer, text) @staticmethod - def get_funcs_defined_in_c(c_file: Path) -> Set[str]: - with open(c_file, "r", encoding="utf-8") as f: + def get_funcs_defined_in_c(c_file: Path) -> set[str]: + with open(c_file, encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) return set(m.group(1) for m in C_FUNC_RE.finditer(text)) @@ -76,15 +77,14 @@ def get_close_parenthesis(string: str, pos: int): elif cur_char == ")": if paren_count == 0: return pos + 1 - else: - paren_count -= 1 + paren_count -= 1 pos += 1 @staticmethod def find_include_macro(text: str, macro_name: str): for pos in CommonSegC.find_all_instances(text, f"{macro_name}("): close_paren_pos = CommonSegC.get_close_parenthesis( - text, pos + len(f"{macro_name}(") + text, pos + len(f"{macro_name}("), ) macro_contents = text[pos:close_paren_pos] macro_args = macro_contents.split(",") @@ -104,31 +104,29 @@ def find_include_rodata(text: str): return CommonSegC.find_include_macro(text, "INCLUDE_RODATA") @staticmethod - def get_global_asm_funcs(c_file: Path) -> Set[str]: + def get_global_asm_funcs(c_file: Path) -> set[str]: with c_file.open(encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - else: - return set(CommonSegC.find_include_asm(text)) + return set(CommonSegC.find_include_asm(text)) @staticmethod - def get_global_asm_rodata_syms(c_file: Path) -> Set[str]: + def get_global_asm_rodata_syms(c_file: Path) -> set[str]: with c_file.open(encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - else: - return set(CommonSegC.find_include_rodata(text)) + return set(CommonSegC.find_include_rodata(text)) @staticmethod def is_text() -> bool: return True - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return options.opts.src_path / self.dir / f"{self.name}.{self.file_extension}" def scan(self, rom_bytes: bytes): @@ -138,15 +136,14 @@ def scan(self, rom_bytes: bytes): and self.rom_start != self.rom_end ): path = self.out_path() - if path: - if options.opts.do_c_func_detection and path.exists(): - # TODO run cpp? - self.defined_funcs = self.get_funcs_defined_in_c(path) - self.global_asm_funcs = self.get_global_asm_funcs(path) - self.global_asm_rodata_syms = self.get_global_asm_rodata_syms(path) - symbols.to_mark_as_defined.update(self.defined_funcs) - symbols.to_mark_as_defined.update(self.global_asm_funcs) - symbols.to_mark_as_defined.update(self.global_asm_rodata_syms) + if path and options.opts.do_c_func_detection and path.exists(): + # TODO run cpp? + self.defined_funcs = self.get_funcs_defined_in_c(path) + self.global_asm_funcs = self.get_global_asm_funcs(path) + self.global_asm_rodata_syms = self.get_global_asm_rodata_syms(path) + symbols.to_mark_as_defined.update(self.defined_funcs) + symbols.to_mark_as_defined.update(self.global_asm_funcs) + symbols.to_mark_as_defined.update(self.global_asm_rodata_syms) self.scan_code(rom_bytes) @@ -163,12 +160,12 @@ def split(self, rom_bytes: bytes): self.print_file_boundaries() assert self.spim_section is not None and isinstance( - self.spim_section.get_section(), spimdisasm.mips.sections.SectionText + self.spim_section.get_section(), spimdisasm.mips.sections.SectionText, ), f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}" # We want to know if this C section has a corresponding rodata section so we can migrate its rodata rodata_section_type = "" - rodata_spim_segment: Optional[spimdisasm.mips.sections.SectionRodata] = None + rodata_spim_segment: spimdisasm.mips.sections.SectionRodata | None = None if options.opts.migrate_rodata_to_functions: # We don't know if the rodata section is .rodata or .rdata, so we need to check both for sect in [".rodata", ".rdata"]: @@ -193,10 +190,10 @@ def split(self, rom_bytes: bytes): status="warn", ) log.write( - f"\t The `{rodata_sibling.type}` section was not prefixed with a dot, which is required for the rodata migration feature to work properly and avoid build errors due to duplicated symbols at link-time." + f"\t The `{rodata_sibling.type}` section was not prefixed with a dot, which is required for the rodata migration feature to work properly and avoid build errors due to duplicated symbols at link-time.", ) log.error( - f"\t To fix this, please prefix the section type with a dot (like `.{rodata_sibling.type}`)." + f"\t To fix this, please prefix the section type with a dot (like `.{rodata_sibling.type}`).", ) rodata_section_type = ( @@ -216,7 +213,7 @@ def split(self, rom_bytes: bytes): # Precompute function-rodata pairings symbols_entries = ( spimdisasm.mips.FunctionRodataEntry.getAllEntriesFromSections( - self.spim_section.get_section(), rodata_spim_segment + self.spim_section.get_section(), rodata_spim_segment, ) ) @@ -230,7 +227,7 @@ def split(self, rom_bytes: bytes): is_new_c_file = True self.create_asm_dependencies_file( - c_path, asm_out_dir, is_new_c_file, symbols_entries + c_path, asm_out_dir, is_new_c_file, symbols_entries, ) # Produce the asm files for functions @@ -258,7 +255,7 @@ def split(self, rom_bytes: bytes): and not is_new_c_file ): self.create_c_asm_file( - entry, matching_asm_out_dir, func_sym + entry, matching_asm_out_dir, func_sym, ) else: self.create_c_asm_file(entry, asm_out_dir, func_sym) @@ -272,12 +269,12 @@ def split(self, rom_bytes: bytes): or options.opts.disassemble_all ): rodata_sym = self.get_symbol( - spim_rodata_sym.vram, in_segment=True, local_only=True + spim_rodata_sym.vram, in_segment=True, local_only=True, ) assert rodata_sym is not None self.create_unmigrated_rodata_file( - spim_rodata_sym, asm_out_dir, rodata_sym + spim_rodata_sym, asm_out_dir, rodata_sym, ) if options.opts.make_full_disasm_for_code: @@ -317,7 +314,7 @@ def get_c_preamble(self): def check_gaps_in_migrated_rodata( self, func: spimdisasm.mips.symbols.SymbolFunction, - rodata_list: List[spimdisasm.mips.symbols.SymbolBase], + rodata_list: list[spimdisasm.mips.symbols.SymbolBase], ): for index in range(len(rodata_list) - 1): rodata_sym = rodata_list[index] @@ -325,16 +322,16 @@ def check_gaps_in_migrated_rodata( if rodata_sym.vramEnd != next_rodata_sym.vram: log.write( - "\nA gap was detected in migrated rodata symbols!", status="warn" + "\nA gap was detected in migrated rodata symbols!", status="warn", ) log.write( - f"\t In function '{func.getNameUnquoted()}' (0x{func.vram:08X}), gap detected between '{rodata_sym.getNameUnquoted()}' (0x{rodata_sym.vram:08X}) and '{next_rodata_sym.getNameUnquoted()}' (0x{next_rodata_sym.vram:08X})" + f"\t In function '{func.getNameUnquoted()}' (0x{func.vram:08X}), gap detected between '{rodata_sym.getNameUnquoted()}' (0x{rodata_sym.vram:08X}) and '{next_rodata_sym.getNameUnquoted()}' (0x{next_rodata_sym.vram:08X})", ) log.write( - f"\t The address of the missing rodata symbol is 0x{rodata_sym.vramEnd:08X}" + f"\t The address of the missing rodata symbol is 0x{rodata_sym.vramEnd:08X}", ) log.write( - "\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force_not_migration:True`" + "\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force_not_migration:True`", ) def create_c_asm_file( @@ -354,7 +351,7 @@ def create_c_asm_file( with outpath.open("w", newline="\n") as f: if options.opts.asm_inc_header: f.write( - options.opts.c_newline.join(options.opts.asm_inc_header.split("\n")) + options.opts.c_newline.join(options.opts.asm_inc_header.split("\n")), ) named_registers_opt = rabbitizer.config.regNames_namedRegisters @@ -367,10 +364,10 @@ def create_c_asm_file( if func_rodata_entry.function is not None: self.check_gaps_in_migrated_rodata( - func_rodata_entry.function, func_rodata_entry.rodataSyms + func_rodata_entry.function, func_rodata_entry.rodataSyms, ) self.check_gaps_in_migrated_rodata( - func_rodata_entry.function, func_rodata_entry.lateRodataSyms + func_rodata_entry.function, func_rodata_entry.lateRodataSyms, ) self.log(f"Disassembled {func_sym.filename} to {outpath}") @@ -425,7 +422,7 @@ def get_c_lines_for_function( sym: Symbol, spim_sym: spimdisasm.mips.symbols.SymbolFunction, asm_out_dir: Path, - ) -> List[str]: + ) -> list[str]: c_lines = [] # Terrible hack to "auto-decompile" empty functions @@ -439,7 +436,7 @@ def get_c_lines_for_function( c_lines.append("}") else: c_lines.append( - self.get_c_line_include_macro(sym, asm_out_dir, "INCLUDE_ASM") + self.get_c_line_include_macro(sym, asm_out_dir, "INCLUDE_ASM"), ) c_lines.append("") return c_lines @@ -453,7 +450,7 @@ def create_c_file( self, asm_out_dir: Path, c_path: Path, - symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], + symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], ): c_lines = self.get_c_preamble() @@ -468,12 +465,12 @@ def create_c_file( assert func_sym is not None c_lines += self.get_c_lines_for_function( - func_sym, entry.function, asm_out_dir + func_sym, entry.function, asm_out_dir, ) else: for spim_rodata_sym in entry.rodataSyms: rodata_sym = self.get_symbol( - spim_rodata_sym.vram, in_segment=True, local_only=True + spim_rodata_sym.vram, in_segment=True, local_only=True, ) assert rodata_sym is not None @@ -489,7 +486,7 @@ def create_asm_dependencies_file( c_path: Path, asm_out_dir: Path, is_new_c_file: bool, - symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], + symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], ): if not options.opts.create_asm_dependencies: return diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index ca556055..d025c6ee 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -1,30 +1,28 @@ from __future__ import annotations from collections import OrderedDict -from typing import List, Optional, Type, Tuple from ...util import log, options, utils - -from .group import CommonSegGroup from ..segment import Segment, parse_segment_align +from .group import CommonSegGroup -def dotless_type(type: str) -> str: - return type[1:] if type[0] == "." else type +def dotless_type(type_: str) -> str: + return type_[1:] if type_[0] == "." else type_ # code group class CommonSegCode(CommonSegGroup): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], - args: list, + vram_start: int | None, + args: list[str], yaml, - ): + ) -> None: self.bss_size: int = yaml.get("bss_size", 0) if isinstance(yaml, dict) else 0 super().__init__( @@ -48,22 +46,21 @@ def needs_symbols(self) -> bool: return True @property - def vram_end(self) -> Optional[int]: + def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size + self.bss_size - else: - return None + return None # Generates a placeholder segment for the auto_link_sections option def _generate_segment_from_all( self, rep_type: str, - replace_class: Type[Segment], + replace_class: type[Segment], base_name: str, base_seg: Segment, - rom_start: Optional[int] = None, - rom_end: Optional[int] = None, - vram_start: Optional[int] = None, + rom_start: int | None = None, + rom_end: int | None = None, + vram_start: int | None = None, ) -> Segment: rep: Segment = replace_class( rom_start=rom_start, @@ -92,25 +89,21 @@ def _generate_segment_from_all( def _insert_all_auto_sections( self, - ret: List[Segment], + ret: list[Segment], base_segments: OrderedDict[str, Segment], readonly_before: bool, - ) -> List[Segment]: + ) -> list[Segment]: if len(options.opts.auto_link_sections) == 0: return ret - base_segments_list: List[Tuple[str, Segment]] = list(base_segments.items()) + base_segments_list: list[tuple[str, Segment]] = list(base_segments.items()) # Determine what will be the min insertion index last_inserted_index = len(base_segments_list) - 1 for i, seg in enumerate(ret): - if seg.is_text(): + if seg.is_text() or (readonly_before and seg.is_rodata()): last_inserted_index = i - elif readonly_before: - if seg.is_rodata(): - last_inserted_index = i - for i, sect in enumerate(options.opts.auto_link_sections): for name, seg in base_segments_list: link_section = seg.get_linker_section_order() @@ -124,7 +117,7 @@ def _insert_all_auto_sections( if sibling is None: replace_class = Segment.get_class_for_type(sect) sibling = self._generate_segment_from_all( - sect, replace_class, seg.name, seg + sect, replace_class, seg.name, seg, ) seg.siblings[sect] = sibling last_inserted_index += 1 @@ -147,18 +140,18 @@ def _insert_all_auto_sections( return ret - def parse_subsegments(self, segment_yaml) -> List[Segment]: + def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: if "subsegments" not in segment_yaml: if not self.parent: raise Exception( - f"No subsegments provided in top-level code segment {self.name}" + f"No subsegments provided in top-level code segment {self.name}", ) return [] base_segments: OrderedDict[str, Segment] = OrderedDict() - ret: List[Segment] = [] - prev_start: Optional[int] = -1 - prev_vram: Optional[int] = -1 + ret: list[Segment] = [] + prev_start: int | None = -1 + prev_vram: int | None = -1 last_rom_end = None @@ -186,20 +179,18 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: if start is None: # Attempt to infer the start address - if i == 0: - # The start address of this segment is the start address of the group - start = self.rom_start - else: - # The start address is the end address of the previous segment - start = last_rom_end + # The start address of this segment either + # - the start address of the group + # - end address of the previous segment + start = self.rom_start if i == 0 else last_rom_end # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address - end: Optional[int] = None + end: int | None = None if i < len(segment_yaml["subsegments"]) - 1: - end, end_is_auto_segment = Segment.parse_segment_start( - segment_yaml["subsegments"][i + 1] + end, _end_is_auto_segment = Segment.parse_segment_start( + segment_yaml["subsegments"][i + 1], ) if start is not None and end is None: est_size = segment_class.estimate_size(subsegment_yaml) @@ -210,7 +201,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: if start is not None and prev_start is not None and start < prev_start: log.error( - f"Error: Group segment '{self.name}' contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})" + f"Error: Group segment '{self.name}' contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})", ) vram = None @@ -225,7 +216,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: end = last_rom_end segment: Segment = Segment.from_yaml( - segment_class, subsegment_yaml, start, end, self, vram + segment_class, subsegment_yaml, start, end, self, vram, ) segment.is_auto_segment = is_auto_segment @@ -236,7 +227,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: ): log.error( f"Error: Group segment '{self.name}' contains subsegments which are out of ascending vram order (0x{prev_vram:X} followed by 0x{segment.vram_start:X}).\n" - + f"Detected when processing file '{segment.name}' of type '{segment.type}'" + + f"Detected when processing file '{segment.name}' of type '{segment.type}'", ) ret.append(segment) @@ -244,9 +235,8 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: if segment.is_text(): base_segments[segment.name] = segment - if readonly_before: - if segment.is_rodata() and segment.sibling is None: - base_segments[segment.name] = segment + if readonly_before and segment.is_rodata() and segment.sibling is None: + base_segments[segment.name] = segment if segment.special_vram_segment: self.special_vram_segment = True diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 97972381..50208ad0 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -1,17 +1,20 @@ from __future__ import annotations -from pathlib import Path +from typing import TYPE_CHECKING -import spimdisasm import rabbitizer +import spimdisasm -from ...util import options, symbols, log - +from ...disassembler.disassembler_section import ( + DisassemblerSection, + make_text_section, +) +from ...util import log, options, symbols +from ..segment import Segment, SerializedSegment, parse_segment_vram from .code import CommonSegCode -from ..segment import Segment, parse_segment_vram, SerializedSegment - -from ...disassembler.disassembler_section import DisassemblerSection, make_text_section +if TYPE_CHECKING: + from pathlib import Path # abstract class for c, asm, data, etc @@ -20,7 +23,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, vram_start: int | None, args: list[str], @@ -70,7 +73,7 @@ def get_linker_section(self) -> str: return ".text" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" @@ -90,7 +93,7 @@ def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", ) # Supposedly logic error, not user error @@ -102,7 +105,7 @@ def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", ) self.spim_section = make_text_section( @@ -142,7 +145,7 @@ def process_insns( self.parent: CommonSegCode = self.parent symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), func_spim.contextSym, force_in_segment=False + self.get_most_parent(), func_spim.contextSym, force_in_segment=False, ) # Gather symbols found by spimdisasm and create those symbols in splat's side @@ -150,19 +153,20 @@ def process_insns( section = self.spim_section.get_section() assert section is not None context_sym = section.getSymbol( - referenced_vram, tryPlusOffset=False + referenced_vram, tryPlusOffset=False, ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False + self.get_most_parent(), context_sym, force_in_segment=False, ) # Main loop for i, insn in enumerate(func_spim.instructions): if options.opts.platform == "ps2": - from .c import CommonSegC from rabbitizer import TrinaryValue + from .c import CommonSegC + if isinstance(self, CommonSegC): insn.flag_r5900UseDollar = TrinaryValue.FALSE else: @@ -180,7 +184,7 @@ def process_insns( context_sym = section.getSymbol(sym_address) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False + self.get_most_parent(), context_sym, force_in_segment=False, ) def print_file_boundaries(self) -> None: @@ -207,10 +211,10 @@ def print_file_boundaries(self) -> None: sym_addr = sym.vram print( - f"\nSegment {self.name}, symbol at vram {sym_addr:X} ends with extra nops, indicating a likely file split." + f"\nSegment {self.name}, symbol at vram {sym_addr:X} ends with extra nops, indicating a likely file split.", ) print( - "File split suggestions for this segment will follow in config yaml format:" + "File split suggestions for this segment will follow in config yaml format:", ) print(f" - [0x{self.rom_start + in_file_offset:X}, {self.type}]") diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index 19f7467f..c13e2ce5 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -1,11 +1,17 @@ -from pathlib import Path -from typing import Optional, List -from ...util import options, symbols, log +from __future__ import annotations +from typing import TYPE_CHECKING + +from ...disassembler.disassembler_section import ( + DisassemblerSection, + make_data_section, +) +from ...util import log, options, symbols from .codesubsegment import CommonSegCodeSubsegment from .group import CommonSegGroup -from ...disassembler.disassembler_section import DisassemblerSection, make_data_section +if TYPE_CHECKING: + from pathlib import Path class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup): @@ -20,17 +26,15 @@ def asm_out_path(self) -> Path: return options.opts.data_path / self.dir / f"{self.name}.{typ}.s" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: if self.type.startswith("."): if self.sibling: # C file return self.sibling.out_path() - else: - # Implied C file - return options.opts.src_path / self.dir / f"{self.name}.c" - else: - # ASM - return self.asm_out_path() + # Implied C file + return options.opts.src_path / self.dir / f"{self.name}.c" + # ASM + return self.asm_out_path() def scan(self, rom_bytes: bytes): CommonSegGroup.scan(self, rom_bytes) @@ -38,7 +42,7 @@ def scan(self, rom_bytes: bytes): if self.rom_start is not None and self.rom_end is not None: self.disassemble_data(rom_bytes) - def get_asm_file_extra_directives(self) -> List[str]: + def get_asm_file_extra_directives(self) -> list[str]: return [] def split(self, rom_bytes: bytes): @@ -66,14 +70,14 @@ def cache(self): def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def get_linker_entries(self): return CommonSegCodeSubsegment.get_linker_entries(self) def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" @@ -94,7 +98,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", ) # Supposedly logic error, not user error @@ -106,7 +110,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", ) self.spim_section = make_data_section( @@ -130,17 +134,17 @@ def disassemble_data(self, rom_bytes): for symbol in self.spim_section.get_section().symbolList: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), symbol.contextSym, force_in_segment=True + self.get_most_parent(), symbol.contextSym, force_in_segment=True, ) # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: context_sym = self.spim_section.get_section().getSymbol( - referenced_vram, tryPlusOffset=False + referenced_vram, tryPlusOffset=False, ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False + self.get_most_parent(), context_sym, force_in_segment=False, ) # Hint to the user that we are now in the .rodata section and no longer in the .data section (assuming rodata follows data) @@ -148,14 +152,14 @@ def disassemble_data(self, rom_bytes): self.suggestion_rodata_section_start and not rodata_encountered and self.get_most_parent().rodata_follows_data + and symbol.contextSym.isJumpTable() ): - if symbol.contextSym.isJumpTable(): rodata_encountered = True print( - f"\n\nData segment {self.name}, symbol at vram {symbol.contextSym.vram:X} is a jumptable, indicating the start of the rodata section _may_ be near here." + f"\n\nData segment {self.name}, symbol at vram {symbol.contextSym.vram:X} is a jumptable, indicating the start of the rodata section _may_ be near here.", ) print( - "Please note the real start of the rodata section may be way before this point." + "Please note the real start of the rodata section may be way before this point.", ) if symbol.contextSym.vromAddress is not None: print(f" - [0x{symbol.contextSym.vromAddress:X}, rodata]") diff --git a/src/splat/segtypes/common/databin.py b/src/splat/segtypes/common/databin.py index 25ffd257..102bc26a 100644 --- a/src/splat/segtypes/common/databin.py +++ b/src/splat/segtypes/common/databin.py @@ -1,7 +1,6 @@ -from typing import Optional +from __future__ import annotations from ...util import log, options - from .textbin import CommonSegTextbin @@ -17,13 +16,13 @@ def is_data() -> bool: def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/common/eh_frame.py b/src/splat/segtypes/common/eh_frame.py index ef1a72b9..3422c7a3 100644 --- a/src/splat/segtypes/common/eh_frame.py +++ b/src/splat/segtypes/common/eh_frame.py @@ -1,7 +1,11 @@ -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from .data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class CommonSegEh_frame(CommonSegData): @@ -10,11 +14,11 @@ class CommonSegEh_frame(CommonSegData): def get_linker_section(self) -> str: return ".eh_frame" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "aw" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/common/gcc_except_table.py b/src/splat/segtypes/common/gcc_except_table.py index 1ff7d4c6..39b6127e 100644 --- a/src/splat/segtypes/common/gcc_except_table.py +++ b/src/splat/segtypes/common/gcc_except_table.py @@ -1,12 +1,11 @@ -from typing import Optional - -from .data import CommonSegData -from ...util import log +from __future__ import annotations from ...disassembler.disassembler_section import ( DisassemblerSection, make_gcc_except_table_section, ) +from ...util import log +from .data import CommonSegData class CommonSegGcc_except_table(CommonSegData): @@ -15,11 +14,11 @@ class CommonSegGcc_except_table(CommonSegData): def get_linker_section(self) -> str: return ".gcc_except_table" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "aw" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" @@ -35,7 +34,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", ) # Supposedly logic error, not user error @@ -47,7 +46,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", ) self.spim_section = make_gcc_except_table_section( diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 7ceb1c7a..2fa353e9 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -3,9 +3,8 @@ from typing import TYPE_CHECKING, override from ...util import log - +from ..segment import Segment, SegmentStatistics, empty_statistics from .segment import CommonSegment -from ..segment import empty_statistics, Segment, SegmentStatistics if TYPE_CHECKING: from ...util.vram_classes import SerializedSegmentData @@ -17,7 +16,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, vram_start: int | None, args: list[str], @@ -39,7 +38,7 @@ def __init__( def get_next_seg_start(self, i: int, subsegment_yamls: list[SerializedSegmentData | list[str]]) -> int | None: j = i + 1 while j < len(subsegment_yamls): - ret, is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) + ret, _is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) if ret is not None: return ret j += 1 @@ -68,20 +67,18 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st if start is None: # Attempt to infer the start address - if i == 0: - # The start address of this segment is the start address of the group - start = self.rom_start - else: - # The start address is the end address of the previous segment - start = last_rom_end + # The start address of this segment is one of the following + # - the start address of the group + # - the end address of the previous segment + start = self.rom_start if i == 0 else last_rom_end # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address end: int | None = None if i < len(yaml["subsegments"]) - 1: - end, end_is_auto_segment = Segment.parse_segment_start( - yaml["subsegments"][i + 1] + end, _end_is_auto_segment = Segment.parse_segment_start( + yaml["subsegments"][i + 1], ) if start is not None and end is None: est_size = segment_class.estimate_size(subsegment_yaml) @@ -92,7 +89,7 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st if start is not None and prev_start is not None and start < prev_start: log.error( - f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})" + f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})", ) vram = None @@ -135,10 +132,7 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st @property def needs_symbols(self) -> bool: - for seg in self.subsegments: - if seg.needs_symbols: - return True - return False + return any(seg.needs_symbols for seg in self.subsegments) @property def statistics(self) -> SegmentStatistics: @@ -187,7 +181,7 @@ def get_subsegment_for_ram(self, addr: int) -> Segment | None: return None def get_next_subsegment_for_ram( - self, addr: int, current_subseg_index: int | None + self, addr: int, current_subseg_index: int | None, ) -> Segment | None: """ Returns the first subsegment which comes after the specified address, @@ -206,7 +200,7 @@ def get_next_subsegment_for_ram( def pair_subsegments_to_other_segment( self, - other_segment: "CommonSegGroup", + other_segment: CommonSegGroup, ) -> None: # Pair cousins with the same name for segment in self.subsegments: diff --git a/src/splat/segtypes/common/hasm.py b/src/splat/segtypes/common/hasm.py index 7966ca4a..777d466b 100644 --- a/src/splat/segtypes/common/hasm.py +++ b/src/splat/segtypes/common/hasm.py @@ -1,8 +1,7 @@ from pathlib import Path -from .asm import CommonSegAsm - from ...util import options +from .asm import CommonSegAsm class CommonSegHasm(CommonSegAsm): diff --git a/src/splat/segtypes/common/header.py b/src/splat/segtypes/common/header.py index 0bab56ad..d5df1830 100644 --- a/src/splat/segtypes/common/header.py +++ b/src/splat/segtypes/common/header.py @@ -1,11 +1,13 @@ from __future__ import annotations -from pathlib import Path +from typing import TYPE_CHECKING from ...util import options - from .segment import CommonSegment +if TYPE_CHECKING: + from pathlib import Path + class CommonSegHeader(CommonSegment): @staticmethod diff --git a/src/splat/segtypes/common/lib.py b/src/splat/segtypes/common/lib.py index 409fa282..c295af2b 100644 --- a/src/splat/segtypes/common/lib.py +++ b/src/splat/segtypes/common/lib.py @@ -1,26 +1,25 @@ +from __future__ import annotations + from pathlib import Path -from typing import Optional, List from ...util import log, options - from ..linker_entry import LinkerEntry, LinkerWriter -from .segment import CommonSegment - from ..segment import Segment, parse_segment_vram +from .segment import CommonSegment class LinkerEntryLib(LinkerEntry): def __init__( self, segment: Segment, - src_paths: List[Path], + src_paths: list[Path], object_path: Path, section_order: str, section_link: str, noload: bool, ): super().__init__( - segment, src_paths, object_path, section_order, section_link, noload + segment, src_paths, object_path, section_order, section_link, noload, ) self.object_path = object_path @@ -31,11 +30,11 @@ def emit_entry(self, linker_writer: LinkerWriter): class CommonSegLib(CommonSegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -73,7 +72,7 @@ def __init__( def get_linker_section(self) -> str: return self.section - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: path = options.opts.lib_path / self.name object_path = Path(f"{path}.a:{self.object}.o") @@ -86,5 +85,5 @@ def get_linker_entries(self) -> List[LinkerEntry]: self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ) + ), ] diff --git a/src/splat/segtypes/common/linker_offset.py b/src/splat/segtypes/common/linker_offset.py index e941715a..7fd3d768 100644 --- a/src/splat/segtypes/common/linker_offset.py +++ b/src/splat/segtypes/common/linker_offset.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List from ..linker_entry import LinkerEntry, LinkerWriter from ..segment import Segment @@ -24,5 +23,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: return [LinkerEntryOffset(self)] diff --git a/src/splat/segtypes/common/pad.py b/src/splat/segtypes/common/pad.py index c99362d9..d0715574 100644 --- a/src/splat/segtypes/common/pad.py +++ b/src/splat/segtypes/common/pad.py @@ -1,9 +1,8 @@ from pathlib import Path -from typing import List -from .segment import CommonSegment from ..linker_entry import LinkerEntry, LinkerWriter from ..segment import Segment +from .segment import CommonSegment class LinkerEntryPad(LinkerEntry): @@ -25,5 +24,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: return [LinkerEntryPad(self)] diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index 39baeba9..291a2b0e 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -1,21 +1,25 @@ -from typing import Optional, Set, Tuple, List -import spimdisasm -from ..segment import Segment -from ...util import log, options, symbols +from __future__ import annotations -from .data import CommonSegData +from typing import TYPE_CHECKING from ...disassembler.disassembler_section import ( DisassemblerSection, make_rodata_section, ) +from ...util import log, options, symbols +from .data import CommonSegData + +if TYPE_CHECKING: + import spimdisasm + + from ..segment import Segment class CommonSegRodata(CommonSegData): def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" @staticmethod @@ -27,8 +31,8 @@ def is_rodata() -> bool: return True def get_possible_text_subsegment_for_symbol( - self, rodata_sym: spimdisasm.mips.symbols.SymbolBase - ) -> Optional[Tuple[Segment, spimdisasm.common.ContextSymbol]]: + self, rodata_sym: spimdisasm.mips.symbols.SymbolBase, + ) -> tuple[Segment, spimdisasm.common.ContextSymbol] | None: # Check if this rodata segment does not have a corresponding code file, try to look for one if self.sibling is not None or not options.opts.pair_rodata_to_text: @@ -40,7 +44,7 @@ def get_possible_text_subsegment_for_symbol( if len(rodata_sym.contextSym.referenceFunctions) != 1: return None - func = list(rodata_sym.contextSym.referenceFunctions)[0] + func = next(iter(rodata_sym.contextSym.referenceFunctions)) text_segment = self.parent.get_subsegment_for_ram(func.vram) if text_segment is None or not text_segment.is_text(): @@ -48,7 +52,7 @@ def get_possible_text_subsegment_for_symbol( return text_segment, func def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" @@ -69,7 +73,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", ) # Supposedly logic error, not user error @@ -81,7 +85,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", ) self.spim_section = make_rodata_section( @@ -101,25 +105,25 @@ def disassemble_data(self, rom_bytes): self.spim_section.analyze() self.spim_section.set_comment_offset(self.rom_start) - possible_text_segments: Set[Segment] = set() + possible_text_segments: set[Segment] = set() last_jumptable_addr_remainder = 0 - misaligned_jumptable_offsets: List[int] = [] + misaligned_jumptable_offsets: list[int] = [] for symbol in self.spim_section.get_section().symbolList: generated_symbol = symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), symbol.contextSym, force_in_segment=True + self.get_most_parent(), symbol.contextSym, force_in_segment=True, ) generated_symbol.linker_section = self.get_linker_section_linksection() # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: context_sym = self.spim_section.get_section().getSymbol( - referenced_vram, tryPlusOffset=False + referenced_vram, tryPlusOffset=False, ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False + self.get_most_parent(), context_sym, force_in_segment=False, ) possible_text = self.get_possible_text_subsegment_for_symbol(symbol) @@ -127,31 +131,30 @@ def disassemble_data(self, rom_bytes): text_segment, refenceeFunction = possible_text if text_segment not in possible_text_segments: print( - f"\nRodata segment '{self.name}' may belong to the text segment '{text_segment.name}'" + f"\nRodata segment '{self.name}' may belong to the text segment '{text_segment.name}'", ) print( - f" Based on the usage from the function {refenceeFunction.getNameUnquoted()} to the symbol {symbol.getNameUnquoted()}" + f" Based on the usage from the function {refenceeFunction.getNameUnquoted()} to the symbol {symbol.getNameUnquoted()}", ) possible_text_segments.add(text_segment) - if options.opts.platform in ("psx", "ps2"): - if generated_symbol.type == "jtbl": - # GCC aligns jumptables to 8, but it doesn't impose alignment restrictions for sections themselves on PSX/PS2. - # This means a jumptable may be aligned file-wise, but it may not end up 8-aligned binary-wise. - # We can use this as a way to find file splits on PSX/PS2 - vram_diff = generated_symbol.vram_start - self.vram_start - if vram_diff % 8 != last_jumptable_addr_remainder: - # Each time the this remainder changes means there's a new file split - last_jumptable_addr_remainder = vram_diff % 8 + if options.opts.platform in ("psx", "ps2") and generated_symbol.type == "jtbl": + # GCC aligns jumptables to 8, but it doesn't impose alignment restrictions for sections themselves on PSX/PS2. + # This means a jumptable may be aligned file-wise, but it may not end up 8-aligned binary-wise. + # We can use this as a way to find file splits on PSX/PS2 + vram_diff = generated_symbol.vram_start - self.vram_start + if vram_diff % 8 != last_jumptable_addr_remainder: + # Each time the this remainder changes means there's a new file split + last_jumptable_addr_remainder = vram_diff % 8 - misaligned_jumptable_offsets.append(self.rom_start + vram_diff) + misaligned_jumptable_offsets.append(self.rom_start + vram_diff) if len(misaligned_jumptable_offsets) > 0: print( - f"\nThe rodata segment '{self.name}' has jumptables that are not aligned properly file-wise, indicating one or more likely file split." + f"\nThe rodata segment '{self.name}' has jumptables that are not aligned properly file-wise, indicating one or more likely file split.", ) print( - "File split suggestions for this segment will follow in config yaml format:" + "File split suggestions for this segment will follow in config yaml format:", ) for offset in misaligned_jumptable_offsets: print(f" - [0x{offset:X}, {self.type}]") diff --git a/src/splat/segtypes/common/rodatabin.py b/src/splat/segtypes/common/rodatabin.py index f3e8575a..58672120 100644 --- a/src/splat/segtypes/common/rodatabin.py +++ b/src/splat/segtypes/common/rodatabin.py @@ -1,7 +1,6 @@ -from typing import Optional +from __future__ import annotations from ...util import log, options - from .textbin import CommonSegTextbin @@ -17,13 +16,13 @@ def is_rodata() -> bool: def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/common/textbin.py b/src/splat/segtypes/common/textbin.py index e24eb6f7..dabbd2b1 100644 --- a/src/splat/segtypes/common/textbin.py +++ b/src/splat/segtypes/common/textbin.py @@ -1,20 +1,23 @@ -from pathlib import Path +from __future__ import annotations + import re -from typing import Optional, TextIO +from typing import TYPE_CHECKING, TextIO from ...util import log, options - from .segment import CommonSegment +if TYPE_CHECKING: + from pathlib import Path + class CommonSegTextbin(CommonSegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -28,7 +31,7 @@ def __init__( yaml=yaml, ) self.use_src_path: bool = isinstance(yaml, dict) and yaml.get( - "use_src_path", False + "use_src_path", False, ) @staticmethod @@ -38,10 +41,10 @@ def is_text() -> bool: def get_linker_section(self) -> str: return ".text" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: if self.use_src_path: return options.opts.src_path / self.dir / f"{self.name}.s" @@ -140,7 +143,7 @@ def write_asm_contents(self, rom_bytes, f: TextIO): def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index adf35c86..a3559749 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -2,18 +2,20 @@ import os import re -from functools import lru_cache +from collections import OrderedDict +from functools import cache from pathlib import Path -from typing import Dict, List, OrderedDict, Set, Tuple, Union, Optional +from typing import TYPE_CHECKING -from ..util import options, log - -from .segment import Segment +from ..util import log, options from ..util.symbols import to_cname +if TYPE_CHECKING: + from .segment import Segment + # clean 'foo/../bar' to 'bar' -@lru_cache(maxsize=None) +@cache def clean_up_path(path: Path) -> Path: path_resolved = path.resolve() base_resolved = options.opts.base_path.resolve() @@ -35,10 +37,7 @@ def clean_up_path(path: Path) -> Path: def path_to_object_path(path: Path) -> Path: path = clean_up_path(path) - if options.opts.use_o_as_suffix: - full_suffix = ".o" - else: - full_suffix = path.suffix + ".o" + full_suffix = ".o" if options.opts.use_o_as_suffix else path.suffix + ".o" if not str(path).startswith(str(options.opts.build_path)): path = options.opts.build_path / path @@ -46,10 +45,7 @@ def path_to_object_path(path: Path) -> Path: def write_file_if_different(path: Path, new_content: str) -> None: - if path.exists(): - old_content = path.read_text() - else: - old_content = "" + old_content = path.read_text() if path.exists() else "" if old_content != new_content: path.parent.mkdir(parents=True, exist_ok=True) @@ -123,7 +119,7 @@ class LinkerEntry: def __init__( self, segment: Segment, - src_paths: List[Path], + src_paths: list[Path], object_path: Path, section_order: str, section_link: str, @@ -135,21 +131,19 @@ def __init__( self.section_link = section_link self.noload = noload self.bss_contains_common = segment.bss_contains_common - self.object_path: Optional[Path] = path_to_object_path(object_path) + self.object_path: Path | None = path_to_object_path(object_path) @property def section_order_type(self) -> str: if self.section_order == ".rdata": return ".rodata" - else: - return self.section_order + return self.section_order @property def section_link_type(self) -> str: if self.section_link == ".rdata": return ".rodata" - else: - return self.section_link + return self.section_link def emit_symbol_for_data(self, linker_writer: LinkerWriter) -> None: if not options.opts.ld_generate_symbol_per_data_segment: @@ -170,13 +164,13 @@ def emit_path(self, linker_writer: LinkerWriter) -> None: if self.noload and self.bss_contains_common: linker_writer._write_object_path_section( - self.object_path, f"{self.section_link} COMMON .scommon" + self.object_path, f"{self.section_link} COMMON .scommon", ) else: wildcard = "*" if options.opts.ld_wildcard_sections else "" linker_writer._write_object_path_section( - self.object_path, f"{self.section_link}{wildcard}" + self.object_path, f"{self.section_link}{wildcard}", ) def emit_entry(self, linker_writer: LinkerWriter) -> None: @@ -187,14 +181,14 @@ def emit_entry(self, linker_writer: LinkerWriter) -> None: class LinkerWriter: def __init__(self, is_partial: bool = False): self.linker_discard_section: bool = options.opts.ld_discard_section - self.sections_allowlist: List[str] = options.opts.ld_sections_allowlist - self.sections_denylist: List[str] = options.opts.ld_sections_denylist + self.sections_allowlist: list[str] = options.opts.ld_sections_allowlist + self.sections_denylist: list[str] = options.opts.ld_sections_denylist # Used to store all the linker entries - build tools may want this information - self.entries: List[LinkerEntry] = [] - self.dependencies_entries: List[LinkerEntry] = [] + self.entries: list[LinkerEntry] = [] + self.dependencies_entries: list[LinkerEntry] = [] - self.buffer: List[str] = [] - self.header_symbols: Set[str] = set() + self.buffer: list[str] = [] + self.header_symbols: set[str] = set() self.is_partial: bool = is_partial @@ -216,11 +210,11 @@ def write_max_vram_end_sym(self, symbol: str, overlays: list[Segment]) -> None: for segment in overlays: if segment == overlays[0]: self._writeln( - f"{symbol} = {get_segment_vram_end_symbol_name(segment)};" + f"{symbol} = {get_segment_vram_end_symbol_name(segment)};", ) else: self._writeln( - f"{symbol} = MAX({symbol}, {get_segment_vram_end_symbol_name(segment)});" + f"{symbol} = MAX({symbol}, {get_segment_vram_end_symbol_name(segment)});", ) # Adds all the entries of a segment to the linker script buffer @@ -238,7 +232,7 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) self.add_legacy(segment, entries) return - section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -258,14 +252,14 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if entry.section_order_type in section_entries: if entry.section_order_type not in section_entries: log.error( - f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options." + f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options.", ) section_entries[entry.section_order_type].append(entry) elif prev_entry is not None: # If this section is not present in section_order or section_order then pretend it is part of the last seen section, mainly for handling linker_offset if prev_entry not in section_entries: log.error( - f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options." + f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options.", ) section_entries[prev_entry].append(entry) any_load = any_load or not entry.noload @@ -274,7 +268,7 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -285,14 +279,14 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if any_load: # Only emit normal segment if there's at least one normal entry self._write_segment_sections( - segment, seg_name, section_entries, noload=False, is_first=is_first + segment, seg_name, section_entries, noload=False, is_first=is_first, ) is_first = False if any_noload: # Only emit NOLOAD segment if there is at least one noload entry self._write_segment_sections( - segment, seg_name, section_entries, noload=True, is_first=is_first + segment, seg_name, section_entries, noload=True, is_first=is_first, ) is_first = False @@ -302,12 +296,12 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: seg_name = segment.get_cname() # To keep track which sections has been started - started_sections: Dict[str, bool] = { + started_sections: dict[str, bool] = { section_name: False for section_name in options.opts.section_order } # Find where sections are last seen - last_seen_sections: Dict[LinkerEntry, str] = {} + last_seen_sections: dict[LinkerEntry, str] = {} for entry in reversed(entries): if ( entry.section_order_type in options.opts.section_order @@ -317,7 +311,7 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -362,7 +356,7 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: self._end_segment(segment, all_bss=False) def add_referenced_partial_segment( - self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]] + self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]], ) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) @@ -377,7 +371,7 @@ def add_referenced_partial_segment( if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -445,7 +439,7 @@ def add_partial_segment(self, segment: Segment) -> None: seg_name = segment.get_cname() - section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -456,14 +450,14 @@ def add_partial_segment(self, segment: Segment) -> None: if entry.section_order_type in section_entries: if entry.section_order_type not in section_entries: log.error( - f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options." + f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options.", ) section_entries[entry.section_order_type].append(entry) elif prev_entry is not None: # If this section is not present in section_order or section_order then pretend it is part of the last seen section, mainly for handling linker_offset if prev_entry not in section_entries: log.error( - f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options." + f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options.", ) section_entries[prev_entry].append(entry) prev_entry = entry.section_order_type @@ -559,7 +553,7 @@ def _end_block(self) -> None: self._indent_level -= 1 self._writeln("}") - def _write_symbol(self, symbol: str, value: Union[str, int]) -> None: + def _write_symbol(self, symbol: str, value: str | int) -> None: symbol = to_cname(symbol) if isinstance(value, int): @@ -573,7 +567,7 @@ def _write_object_path_section(self, object_path: Path, section: str) -> None: self._writeln(f"{object_path.as_posix()}({section});") def _begin_segment( - self, segment: Segment, seg_name: str, noload: bool, is_first: bool + self, segment: Segment, seg_name: str, noload: bool, is_first: bool, ) -> None: if ( options.opts.ld_use_symbolic_vram_addresses @@ -619,22 +613,20 @@ def _end_segment(self, segment: Segment, all_bss: bool = False) -> None: self._writeln(f"__romPos += SIZEOF(.{name});") # Align directive - if not options.opts.segment_end_before_align: - if segment.align: - self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") - if options.opts.ld_align_segment_vram_end: - self._writeln(f". = ALIGN(., {segment.align});") + if not options.opts.segment_end_before_align and segment.align: + self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") + if options.opts.ld_align_segment_vram_end: + self._writeln(f". = ALIGN(., {segment.align});") seg_rom_end = get_segment_rom_end(name) self._write_symbol(seg_rom_end, "__romPos") self._write_symbol(get_segment_vram_end_symbol_name(segment), ".") # Align directive - if options.opts.segment_end_before_align: - if segment.align: - self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") - if options.opts.ld_align_segment_vram_end: - self._writeln(f". = ALIGN(., {segment.align});") + if options.opts.segment_end_before_align and segment.align: + self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") + if options.opts.ld_align_segment_vram_end: + self._writeln(f". = ALIGN(., {segment.align});") self._writeln("") diff --git a/src/splat/segtypes/n64/__init__.py b/src/splat/segtypes/n64/__init__.py index 70eb16e2..1f19298b 100644 --- a/src/splat/segtypes/n64/__init__.py +++ b/src/splat/segtypes/n64/__init__.py @@ -1,21 +1,23 @@ -from . import ci as ci -from . import ci4 as ci4 -from . import ci8 as ci8 -from . import decompressor as decompressor -from . import gfx as gfx -from . import header as header -from . import i1 as i1 -from . import i4 as i4 -from . import i8 as i8 -from . import ia16 as ia16 -from . import ia4 as ia4 -from . import ia8 as ia8 -from . import img as img -from . import ipl3 as ipl3 -from . import mio0 as mio0 -from . import palette as palette -from . import rgba16 as rgba16 -from . import rgba32 as rgba32 -from . import rsp as rsp -from . import vtx as vtx -from . import yay0 as yay0 +from . import ( + ci as ci, + ci4 as ci4, + ci8 as ci8, + decompressor as decompressor, + gfx as gfx, + header as header, + i1 as i1, + i4 as i4, + i8 as i8, + ia4 as ia4, + ia8 as ia8, + ia16 as ia16, + img as img, + ipl3 as ipl3, + mio0 as mio0, + palette as palette, + rgba16 as rgba16, + rgba32 as rgba32, + rsp as rsp, + vtx as vtx, + yay0 as yay0, +) diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index 68e6e4da..a2c90edb 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -1,8 +1,7 @@ from pathlib import Path -from typing import List, TYPE_CHECKING +from typing import TYPE_CHECKING from ...util import log, options - from .img import N64SegImg if TYPE_CHECKING: @@ -11,7 +10,7 @@ # Base class for CI4/CI8 class N64SegCi(N64SegImg): - def parse_palette_names(self, yaml, args) -> List[str]: + def parse_palette_names(self, yaml, args) -> list[str]: ret = [self.name] if isinstance(yaml, dict): if "palettes" in yaml: @@ -26,7 +25,7 @@ def parse_palette_names(self, yaml, args) -> List[str]: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.palettes: "List[N64SegPalette]" = [] + self.palettes: list[N64SegPalette] = [] self.palette_names = self.parse_palette_names(self.yaml, self.args) def scan(self, rom_bytes: bytes) -> None: @@ -54,7 +53,7 @@ def split(self, rom_bytes): if len(self.palettes) == 0: # TODO: output with blank palette log.error( - f"no palettes have been mapped to ci segment `{self.name}`\n(hint: add a palette segment with the same name or use the `palettes:` field of this segment to specify palettes by name')" + f"no palettes have been mapped to ci segment `{self.name}`\n(hint: add a palette segment with the same name or use the `palettes:` field of this segment to specify palettes by name')", ) assert isinstance(self.rom_start, int) diff --git a/src/splat/segtypes/n64/decompressor.py b/src/splat/segtypes/n64/decompressor.py index 1d992212..2723a575 100644 --- a/src/splat/segtypes/n64/decompressor.py +++ b/src/splat/segtypes/n64/decompressor.py @@ -1,5 +1,4 @@ from ...util import log, options - from ..segment import Segment @@ -10,7 +9,7 @@ def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) out_path = out_dir / f"{self.name}.bin" @@ -37,16 +36,16 @@ def get_linker_entries(self): self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ) + ), ] @property def compression_type(self) -> str: log.error( - f"Segment {self.__class__.__name__} needs to define a compression type" + f"Segment {self.__class__.__name__} needs to define a compression type", ) def decompress(self, compressed_bytes: bytes) -> bytes: log.error( - f"Segment {self.__class__.__name__} needs to define a decompression method" + f"Segment {self.__class__.__name__} needs to define a decompression method", ) diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index dbf0afd3..f8829b13 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -2,18 +2,23 @@ N64 f3dex display list splitter Dumps out Gfx[] as a .inc.c file. """ +from __future__ import annotations import re -from typing import Dict, List, Optional, Union - -from pathlib import Path +from typing import TYPE_CHECKING from pygfxd import ( + GfxdEndian, gfxd_buffer_to_string, gfxd_cimg_callback, gfxd_dl_callback, gfxd_endian, gfxd_execute, + gfxd_f3d, + gfxd_f3db, + gfxd_f3dex, + gfxd_f3dex2, + gfxd_f3dexb, gfxd_input_buffer, gfxd_light_callback, gfxd_lookat_callback, @@ -29,20 +34,14 @@ gfxd_vp_callback, gfxd_vtx_callback, gfxd_zimg_callback, - GfxdEndian, - gfxd_f3d, - gfxd_f3db, - gfxd_f3dex, - gfxd_f3dexb, - gfxd_f3dex2, ) -from ...util import log, options +from ...util import log, options, symbols from ...util.log import error - from ..common.codesubsegment import CommonSegCodeSubsegment -from ...util import symbols +if TYPE_CHECKING: + from pathlib import Path LIGHTS_RE = re.compile(r"\*\(Lightsn \*\)0x[0-9A-F]{8}") @@ -50,11 +49,11 @@ class N64SegGfx(CommonSegCodeSubsegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -88,41 +87,41 @@ def get_gfxd_target(self): if opt == "f3d": return gfxd_f3d - elif opt == "f3db": + if opt == "f3db": return gfxd_f3db - elif opt == "f3dex": + if opt == "f3dex": return gfxd_f3dex - elif opt == "f3dexb": + if opt == "f3dexb": return gfxd_f3dexb - elif opt == "f3dex2": + if opt == "f3dex2": return gfxd_f3dex2 - else: - log.error(f"Unknown target {opt}") + log.error(f"Unknown target {opt}") + return None def tlut_handler(self, addr, idx, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 def timg_handler(self, addr, fmt, size, width, height, pal): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 def cimg_handler(self, addr, fmt, size, width): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 def zimg_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 @@ -133,28 +132,28 @@ def dl_handler(self, addr): if not sym: sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 def mtx_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 def lookat_handler(self, addr, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 def light_handler(self, addr, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(self.format_sym_name(sym)) return 1 @@ -178,7 +177,7 @@ def vtx_handler(self, addr, count): def vp_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 @@ -198,13 +197,13 @@ def disassemble_data(self, rom_bytes): segment_length = len(gfx_data) if (segment_length) % 8 != 0: error( - f"Error: gfx segment {self.name} length ({segment_length}) is not a multiple of 8!" + f"Error: gfx segment {self.name} length ({segment_length}) is not a multiple of 8!", ) out_str = "" if self.data_only else options.opts.generated_c_preamble + "\n\n" sym = self.create_symbol( - addr=self.vram_start, in_segment=True, type="data", define=True + addr=self.vram_start, in_segment=True, type="data", define=True, ) gfxd_input_buffer(gfx_data) @@ -215,7 +214,7 @@ def disassemble_data(self, rom_bytes): gfxd_target(self.get_gfxd_target()) gfxd_endian( - GfxdEndian.big if options.opts.endianness == "big" else GfxdEndian.little, 4 + GfxdEndian.big if options.opts.endianness == "big" else GfxdEndian.little, 4, ) # Callbacks @@ -250,13 +249,12 @@ def light_sub_func(match): light = match.group(0) addr = int(light[12:], 0) sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True + addr=addr, in_segment=self.in_segment, type="data", reference=True, ) return self.format_sym_name(sym) - out_str = re.sub(LIGHTS_RE, light_sub_func, out_str) + return re.sub(LIGHTS_RE, light_sub_func, out_str) - return out_str def split(self, rom_bytes: bytes): if self.file_text and self.out_path(): @@ -276,7 +274,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("gfx") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: + def estimate_size(yaml: dict | list) -> int | None: if isinstance(yaml, dict) and "length" in yaml: return yaml["length"] * 0x10 return None diff --git a/src/splat/segtypes/n64/header.py b/src/splat/segtypes/n64/header.py index 93b77adb..0e252045 100644 --- a/src/splat/segtypes/n64/header.py +++ b/src/splat/segtypes/n64/header.py @@ -1,5 +1,4 @@ from ...util import options - from ..common.header import CommonSegHeader @@ -10,13 +9,13 @@ def parse_header(self, rom_bytes): header_lines = [] header_lines.append(".section .data\n") header_lines.append( - self.get_line("word", rom_bytes[0x00:0x04], "PI BSB Domain 1 register") + self.get_line("word", rom_bytes[0x00:0x04], "PI BSB Domain 1 register"), ) header_lines.append( - self.get_line("word", rom_bytes[0x04:0x08], "Clockrate setting") + self.get_line("word", rom_bytes[0x04:0x08], "Clockrate setting"), ) header_lines.append( - self.get_line("word", rom_bytes[0x08:0x0C], "Entrypoint address") + self.get_line("word", rom_bytes[0x08:0x0C], "Entrypoint address"), ) header_lines.append(self.get_line("word", rom_bytes[0x0C:0x10], "Revision")) header_lines.append(self.get_line("word", rom_bytes[0x10:0x14], "Checksum 1")) @@ -28,21 +27,21 @@ def parse_header(self, rom_bytes): header_lines.append( '.ascii "' + rom_bytes[0x20:0x34].decode(encoding).strip().ljust(20) - + '" /* Internal name */' + + '" /* Internal name */', ) else: for i in range(0x20, 0x34, 4): header_lines.append( - self.get_line("word", rom_bytes[i : i + 4], "Internal name") + self.get_line("word", rom_bytes[i : i + 4], "Internal name"), ) header_lines.append(self.get_line("word", rom_bytes[0x34:0x38], "Unknown 3")) header_lines.append(self.get_line("word", rom_bytes[0x38:0x3C], "Cartridge")) header_lines.append( - self.get_line("ascii", rom_bytes[0x3C:0x3E], "Cartridge ID") + self.get_line("ascii", rom_bytes[0x3C:0x3E], "Cartridge ID"), ) header_lines.append( - self.get_line("ascii", rom_bytes[0x3E:0x3F], "Country code") + self.get_line("ascii", rom_bytes[0x3E:0x3F], "Country code"), ) header_lines.append(self.get_line("byte", rom_bytes[0x3F:0x40], "Version")) header_lines.append("") diff --git a/src/splat/segtypes/n64/img.py b/src/splat/segtypes/n64/img.py index 6bb09a68..3ed28751 100644 --- a/src/splat/segtypes/n64/img.py +++ b/src/splat/segtypes/n64/img.py @@ -1,32 +1,35 @@ -from pathlib import Path -from typing import Dict, List, Tuple, Type, Optional, Union +from __future__ import annotations -from n64img.image import Image -from ...util import log, options +from typing import TYPE_CHECKING +from ...util import log, options from ..segment import Segment +if TYPE_CHECKING: + from pathlib import Path + + from n64img.image import Image + class N64SegImg(Segment): @staticmethod - def parse_dimensions(yaml: Union[Dict, List]) -> Tuple[int, int]: + def parse_dimensions(yaml: dict | list) -> tuple[int, int]: if isinstance(yaml, dict): return yaml["width"], yaml["height"] - else: - if len(yaml) < 5: - log.error(f"Error: {yaml} is missing width and height parameters") - return yaml[3], yaml[4] + if len(yaml) < 5: + log.error(f"Error: {yaml} is missing width and height parameters") + return yaml[3], yaml[4] def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], - args: list, + vram_start: int | None, + args: list[str], yaml, - img_cls: Type[Image], + img_cls: type[Image], ): super().__init__( rom_start, @@ -62,11 +65,11 @@ def check_len(self) -> None: actual_len = self.rom_end - self.rom_start if actual_len > expected_len: 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)" + 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)", ) elif actual_len < expected_len: 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}" + f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}", ) def out_path(self) -> Path: @@ -90,15 +93,14 @@ def split(self, rom_bytes): self.log(f"Wrote {self.name} to {path}") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> int: + def estimate_size(yaml: dict | list) -> int: width, height = N64SegImg.parse_dimensions(yaml) typ = Segment.parse_segment_type(yaml) if typ == "ci4" or typ == "i4" or typ == "ia4": return width * height // 2 - elif typ in ("ia16", "rgba16"): + if typ in ("ia16", "rgba16"): return width * height * 2 - elif typ == "rgba32": + if typ == "rgba32": return width * height * 4 - else: - return width * height + return width * height diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index de0d830b..5f3c8011 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -1,12 +1,14 @@ +from __future__ import annotations + from itertools import zip_longest -from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING from ...util import log, options from ...util.color import unpack_color - from ..segment import Segment +if TYPE_CHECKING: + from pathlib import Path VALID_SIZES = [0x20, 0x40, 0x80, 0x100, 0x200] @@ -20,7 +22,7 @@ def __init__(self, *args, **kwargs): if self.extract: if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", ) if isinstance(self.yaml, dict) and "size" in self.yaml: @@ -29,7 +31,7 @@ def __init__(self, *args, **kwargs): rom_len = self.rom_end - self.rom_start if rom_len < yaml_size: log.error( - f"Error: {self.name} has a `size` value of 0x{yaml_size:X}, but this is smaller than the actual rom size of the palette (0x{rom_len:X}, start: 0x{self.rom_start:X}, end: 0x{self.rom_end:X})" + f"Error: {self.name} has a `size` value of 0x{yaml_size:X}, but this is smaller than the actual rom size of the palette (0x{rom_len:X}, start: 0x{self.rom_start:X}, end: 0x{self.rom_end:X})", ) elif rom_len > yaml_size: log.error( @@ -46,19 +48,19 @@ def __init__(self, *args, **kwargs): if actual_len > VALID_SIZES[-1]: log.error( - f"Error: {self.name} (0x{actual_len:X} bytes) is too long, max 0x{VALID_SIZES[-1]:X})\n{hint_msg}" + f"Error: {self.name} (0x{actual_len:X} bytes) is too long, max 0x{VALID_SIZES[-1]:X})\n{hint_msg}", ) if actual_len not in VALID_SIZES: log.error( - f"Error: {self.name} (0x{actual_len:X} bytes) is not a valid palette size ({', '.join(hex(s) for s in VALID_SIZES)})\n{hint_msg}" + f"Error: {self.name} (0x{actual_len:X} bytes) is not a valid palette size ({', '.join(hex(s) for s in VALID_SIZES)})\n{hint_msg}", ) size = actual_len else: size = 0 self.palette_size: int = size - self.global_id: Optional[str] = ( + self.global_id: str | None = ( self.yaml.get("global_id") if isinstance(self.yaml, dict) else None ) @@ -66,7 +68,7 @@ def get_cname(self) -> str: return super().get_cname() + "_pal" @staticmethod - def parse_palette_bytes(data) -> List[Tuple[int, int, int, int]]: + def parse_palette_bytes(data) -> list[tuple[int, int, int, int]]: def iter_in_groups(iterable, n, fillvalue=None): args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) @@ -78,7 +80,7 @@ def iter_in_groups(iterable, n, fillvalue=None): return palette - def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]: + def parse_palette(self, rom_bytes) -> list[tuple[int, int, int, int]]: assert self.rom_start is not None data = rom_bytes[self.rom_start : self.rom_start + self.palette_size] @@ -102,12 +104,11 @@ def get_linker_entries(self): self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ) + ), ] @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> int: - if isinstance(yaml, dict): - if "size" in yaml: - return int(yaml["size"]) + def estimate_size(yaml: dict | list) -> int: + if isinstance(yaml, dict) and "size" in yaml: + return int(yaml["size"]) return 0x20 diff --git a/src/splat/segtypes/n64/vtx.py b/src/splat/segtypes/n64/vtx.py index acf4bcbb..e60472f8 100644 --- a/src/splat/segtypes/n64/vtx.py +++ b/src/splat/segtypes/n64/vtx.py @@ -4,24 +4,26 @@ Originally written by Mark Street (https://github.com/mkst) """ +from __future__ import annotations import struct -from pathlib import Path -from typing import Dict, List, Optional, Union - -from ...util import options, log +from typing import TYPE_CHECKING +from ...util import log, options from ..common.codesubsegment import CommonSegCodeSubsegment +if TYPE_CHECKING: + from pathlib import Path + class N64SegVtx(CommonSegCodeSubsegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], - type: str, + rom_start: int | None, + rom_end: int | None, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -34,7 +36,7 @@ def __init__( args=args, yaml=yaml, ) - self.file_text: Optional[str] = None + self.file_text: str | None = None self.data_only = isinstance(yaml, dict) and yaml.get("data_only", False) def format_sym_name(self, sym) -> str: @@ -58,7 +60,7 @@ def disassemble_data(self, rom_bytes) -> str: segment_length = len(vertex_data) if (segment_length) % 16 != 0: log.error( - f"Error: Vtx segment {self.name} length ({segment_length}) is not a multiple of 16!" + f"Error: Vtx segment {self.name} length ({segment_length}) is not a multiple of 16!", ) lines = [] @@ -68,7 +70,7 @@ def disassemble_data(self, rom_bytes) -> str: vertex_count = segment_length // 16 sym = self.create_symbol( - addr=self.vram_start, in_segment=True, type="data", define=True + addr=self.vram_start, in_segment=True, type="data", define=True, ) if not self.data_only: @@ -102,7 +104,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("vtx") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: + def estimate_size(yaml: dict | list) -> int | None: if isinstance(yaml, dict) and "length" in yaml: return yaml["length"] * 0x10 return None diff --git a/src/splat/segtypes/ps2/ctor.py b/src/splat/segtypes/ps2/ctor.py index 003059ac..a1b2c647 100644 --- a/src/splat/segtypes/ps2/ctor.py +++ b/src/splat/segtypes/ps2/ctor.py @@ -1,7 +1,11 @@ -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegCtor(CommonSegData): @@ -10,11 +14,11 @@ class Ps2SegCtor(CommonSegData): def get_linker_section(self) -> str: return ".ctor" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/lit4.py b/src/splat/segtypes/ps2/lit4.py index d2f1d581..046d1912 100644 --- a/src/splat/segtypes/ps2/lit4.py +++ b/src/splat/segtypes/ps2/lit4.py @@ -1,7 +1,11 @@ -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit4(CommonSegData): @@ -10,11 +14,11 @@ class Ps2SegLit4(CommonSegData): def get_linker_section(self) -> str: return ".lit4" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/lit8.py b/src/splat/segtypes/ps2/lit8.py index 81b5cf99..74128960 100644 --- a/src/splat/segtypes/ps2/lit8.py +++ b/src/splat/segtypes/ps2/lit8.py @@ -1,7 +1,11 @@ -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit8(CommonSegData): @@ -10,11 +14,11 @@ class Ps2SegLit8(CommonSegData): def get_linker_section(self) -> str: return ".lit8" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/vtables.py b/src/splat/segtypes/ps2/vtables.py index afbdbd17..3d567dc2 100644 --- a/src/splat/segtypes/ps2/vtables.py +++ b/src/splat/segtypes/ps2/vtables.py @@ -1,7 +1,11 @@ -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegVtables(CommonSegData): @@ -10,11 +14,11 @@ class Ps2SegVtables(CommonSegData): def get_linker_section(self) -> str: return ".vtables" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection + self, disassembler_section: DisassemblerSection, ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/psx/header.py b/src/splat/segtypes/psx/header.py index fc61059f..5c6d8170 100644 --- a/src/splat/segtypes/psx/header.py +++ b/src/splat/segtypes/psx/header.py @@ -8,47 +8,47 @@ def parse_header(self, rom_bytes: bytes) -> list[str]: header_lines = [] header_lines.append(".section .data\n") header_lines.append( - self.get_line("ascii", rom_bytes[0x00:0x08], "Magic number") + self.get_line("ascii", rom_bytes[0x00:0x08], "Magic number"), ) header_lines.append( - self.get_line("word", rom_bytes[0x08:0x0C][::-1], ".text vram address") + self.get_line("word", rom_bytes[0x08:0x0C][::-1], ".text vram address"), ) header_lines.append( - self.get_line("word", rom_bytes[0x0C:0x10][::-1], ".data vram address") + self.get_line("word", rom_bytes[0x0C:0x10][::-1], ".data vram address"), ) header_lines.append( - self.get_line("word", rom_bytes[0x10:0x14][::-1], "Initial PC") + self.get_line("word", rom_bytes[0x10:0x14][::-1], "Initial PC"), ) header_lines.append( - self.get_line("word", rom_bytes[0x14:0x18][::-1], "Initial $gp/r28") + self.get_line("word", rom_bytes[0x14:0x18][::-1], "Initial $gp/r28"), ) header_lines.append( - self.get_line("word", rom_bytes[0x18:0x1C][::-1], ".text start") + self.get_line("word", rom_bytes[0x18:0x1C][::-1], ".text start"), ) header_lines.append( - self.get_line("word", rom_bytes[0x1C:0x20][::-1], ".text size") + self.get_line("word", rom_bytes[0x1C:0x20][::-1], ".text size"), ) header_lines.append( - self.get_line("word", rom_bytes[0x20:0x24][::-1], ".data start") + self.get_line("word", rom_bytes[0x20:0x24][::-1], ".data start"), ) header_lines.append( - self.get_line("word", rom_bytes[0x24:0x28][::-1], ".data size") + self.get_line("word", rom_bytes[0x24:0x28][::-1], ".data size"), ) header_lines.append( - self.get_line("word", rom_bytes[0x28:0x2C][::-1], ".bss start") + self.get_line("word", rom_bytes[0x28:0x2C][::-1], ".bss start"), ) header_lines.append( - self.get_line("word", rom_bytes[0x2C:0x30][::-1], ".bss size") + self.get_line("word", rom_bytes[0x2C:0x30][::-1], ".bss size"), ) header_lines.append( self.get_line( - "word", rom_bytes[0x30:0x34][::-1], "Initial $sp/r29 & $fp/r30 base" - ) + "word", rom_bytes[0x30:0x34][::-1], "Initial $sp/r29 & $fp/r30 base", + ), ) header_lines.append( self.get_line( - "word", rom_bytes[0x34:0x38][::-1], "Initial $sp/r29 & $fp/r30 offset" - ) + "word", rom_bytes[0x34:0x38][::-1], "Initial $sp/r29 & $fp/r30 offset", + ), ) header_lines.append(self.get_line("word", rom_bytes[0x38:0x3C], "Reserved")) header_lines.append(self.get_line("word", rom_bytes[0x3C:0x40], "Reserved")) @@ -57,7 +57,7 @@ def parse_header(self, rom_bytes: bytes) -> list[str]: header_lines.append(self.get_line("word", rom_bytes[0x48:0x4C], "Reserved")) assert isinstance(self.rom_end, int) header_lines.append( - self.get_line("ascii", rom_bytes[0x4C : self.rom_end], "Sony Inc") + self.get_line("ascii", rom_bytes[0x4C : self.rom_end], "Sony Inc"), ) header_lines.append("") diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index dd55a2f6..6b168f11 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -5,38 +5,32 @@ import importlib import importlib.util from pathlib import Path - -from typing import Optional, Type, TYPE_CHECKING, Union, Dict, TypeAlias, List +from typing import TYPE_CHECKING, TypeAlias, Union from intervaltree import Interval, IntervalTree -from ..util import vram_classes - - -from ..util.vram_classes import VramClass, SerializedSegmentData -from ..util import log, options, symbols -from ..util.symbols import Symbol, to_cname from .. import __package_name__ +from ..util import log, options, symbols, vram_classes +from ..util.symbols import Symbol, to_cname +from ..util.vram_classes import SerializedSegmentData, VramClass # circular import if TYPE_CHECKING: from ..segtypes.linker_entry import LinkerEntry -SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] +SerializedSegment: TypeAlias = Union[SerializedSegmentData, list[str]] def parse_segment_vram(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "vram" in segment: return int(segment["vram"]) - else: - return None + return None def parse_segment_vram_symbol(segment: SerializedSegment) -> str | None: if isinstance(segment, dict) and "vram_symbol" in segment: return str(segment["vram_symbol"]) - else: - return None + return None def parse_segment_vram_class(segment: SerializedSegment) -> VramClass | None: @@ -83,9 +77,9 @@ class SegmentStatisticsInfo: size: int count: int - def merge(self, other: "SegmentStatisticsInfo") -> "SegmentStatisticsInfo": + def merge(self, other: SegmentStatisticsInfo) -> SegmentStatisticsInfo: return SegmentStatisticsInfo( - size=self.size + other.size, count=self.count + other.count + size=self.size + other.size, count=self.count + other.count, ) @@ -117,7 +111,7 @@ def get_class_for_type(seg_type: str) -> type[Segment]: if segment_class is None: log.error( - f"could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)" + f"could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)", ) return segment_class @@ -130,13 +124,13 @@ def get_base_segment_class(seg_type: str) -> type[Segment] | None: # heirarchy is platform -> common -> fail try: segmodule = importlib.import_module( - f".segtypes.{platform}.{seg_type}", package=__package_name__ + f".segtypes.{platform}.{seg_type}", package=__package_name__, ) is_platform_seg = True except ModuleNotFoundError: try: segmodule = importlib.import_module( - f".segtypes.common.{seg_type}", package=__package_name__ + f".segtypes.common.{seg_type}", package=__package_name__, ) except ModuleNotFoundError: return None @@ -144,7 +138,7 @@ def get_base_segment_class(seg_type: str) -> type[Segment] | None: seg_prefix = platform.capitalize() if is_platform_seg else "Common" return getattr( # type: ignore[no-any-return] segmodule, - f"{seg_prefix}Seg{seg_type.capitalize()}" + f"{seg_prefix}Seg{seg_type.capitalize()}", ) @staticmethod @@ -154,7 +148,7 @@ def get_extension_segment_class(seg_type: str) -> type[Segment] | None: ext_path = options.opts.extensions_path if not ext_path: log.error( - f"could not load presumed extended segment type '{seg_type}' because no extensions path is configured" + f"could not load presumed extended segment type '{seg_type}' because no extensions path is configured", ) assert ext_path is not None @@ -171,7 +165,7 @@ def get_extension_segment_class(seg_type: str) -> type[Segment] | None: return None return getattr( # type: ignore[no-any-return] - ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}" + ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}", ) @staticmethod @@ -195,22 +189,20 @@ def parse_segment_start(segment: SerializedSegment) -> tuple[int | None, bool]: return None, False if s == "auto": return None, True - else: - return int(s), False + return int(s), False @staticmethod def parse_segment_type(segment: SerializedSegment) -> str: if isinstance(segment, dict): return str(segment["type"]) - else: - return str(segment[1]) + return str(segment[1]) @classmethod def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) -> str: if isinstance(segment, dict): if "name" in segment: return str(segment["name"]) - elif "dir" in segment: + if "dir" in segment: return str(segment["dir"]) elif isinstance(segment, list) and len(segment) >= 3: return str(segment[2]) @@ -221,15 +213,13 @@ def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) - def parse_segment_symbol_name_format(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format" in segment: return str(segment["symbol_name_format"]) - else: - return options.opts.symbol_name_format + return options.opts.symbol_name_format @staticmethod def parse_segment_symbol_name_format_no_rom(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format_no_rom" in segment: return str(segment["symbol_name_format_no_rom"]) - else: - return options.opts.symbol_name_format_no_rom + return options.opts.symbol_name_format_no_rom @staticmethod def parse_segment_file_path(segment: SerializedSegment) -> Path | None: @@ -239,7 +229,7 @@ def parse_segment_file_path(segment: SerializedSegment) -> Path | None: @staticmethod def parse_segment_bss_contains_common( - segment: SerializedSegment, default: bool + segment: SerializedSegment, default: bool, ) -> bool: if isinstance(segment, dict) and "bss_contains_common" in segment: return bool(segment["bss_contains_common"]) @@ -259,7 +249,7 @@ def parse_linker_section(yaml: SerializedSegment) -> str | None: @staticmethod def parse_ld_fill_value( - yaml: SerializedSegment, default: int | None + yaml: SerializedSegment, default: int | None, ) -> int | None: if isinstance(yaml, dict) and "ld_fill_value" in yaml: return yaml["ld_fill_value"] @@ -277,7 +267,7 @@ def parse_suggestion_rodata_section_start( ) -> bool | None: if isinstance(yaml, dict): suggestion_rodata_section_start = yaml.get( - "suggestion_rodata_section_start" + "suggestion_rodata_section_start", ) if suggestion_rodata_section_start is not None: assert isinstance(suggestion_rodata_section_start, bool) @@ -294,7 +284,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, + type: str, # noqa: A002 # `type` is shadowing a builtin name: str, vram_start: int | None, args: list[str], @@ -345,15 +335,13 @@ def __init__( self.extract: bool = True self.has_linker_entry: bool = True - if self.rom_start is None: - self.extract = False - elif self.type.startswith("."): + if self.rom_start is None or self.type.startswith("."): self.extract = False self.warnings: list[str] = [] self.did_run = False self.bss_contains_common = Segment.parse_segment_bss_contains_common( - yaml, options.opts.ld_bss_contains_common + yaml, options.opts.ld_bss_contains_common, ) # For segments which are not in the usual VRAM segment space, like N64's IPL3 which lives in 0xA4... @@ -364,11 +352,11 @@ def __init__( # If not defined on the segment then default to the global option self.ld_fill_value: int | None = self.parse_ld_fill_value( - yaml, options.opts.ld_fill_value + yaml, options.opts.ld_fill_value, ) self.ld_align_segment_start: int | None = self.parse_ld_align_segment_start( - yaml + yaml, ) # True if this segment was generated based on auto_link_sections @@ -383,11 +371,14 @@ def __init__( self.index_within_group: int | None = None - if self.rom_start is not None and self.rom_end is not None: - if self.rom_start > self.rom_end: - log.error( - f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})" - ) + if ( + self.rom_start is not None + and self.rom_end is not None + and self.rom_start > self.rom_end + ): + log.error( + f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})", + ) @classmethod def from_yaml( @@ -398,7 +389,7 @@ def from_yaml( parent: Segment | None, vram: int | None = None, ) -> Segment: - type = cls.parse_segment_type(yaml) + type_ = cls.parse_segment_type(yaml) name = cls.parse_segment_name(rom_start, yaml) vram_class = parse_segment_vram_class(yaml) @@ -417,7 +408,7 @@ def from_yaml( ret = cls( rom_start=rom_start, rom_end=rom_end, - type=type, + type=type_, name=name, vram_start=vram_start, args=args, @@ -426,34 +417,33 @@ def from_yaml( if parent is not None: if "subalign" in yaml: log.error( - f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `subalign`. `subalign` is valid only for top-level segments" + f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `subalign`. `subalign` is valid only for top-level segments", ) if "ld_fill_value" in yaml: log.error( - f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `ld_fill_value`. `ld_fill_value` is valid only for top-level segments" + f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `ld_fill_value`. `ld_fill_value` is valid only for top-level segments", ) ret.parent = parent # Import here to avoid circular imports - from .common.code import CommonSegCode from .common.bss import CommonSegBss + from .common.code import CommonSegCode - if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss): + if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss) and isinstance(parent, CommonSegCode): # We need to know the bss space for the segment. - if isinstance(parent, CommonSegCode): - if parent.bss_size <= 0: - log.error( - f"Top-level segment '{parent.name}' is missing a `bss_size` value.\n A non-zero `bss_size` value must be defined on the top-level segments that contain '{ret.type}' sections (produced by the '{ret.name}' section)." - ) - if ( - isinstance(ret.vram_start, int) - and isinstance(parent.vram_end, int) - and ret.vram_start >= parent.vram_end - ): - log.error( - f"The section '{ret.name}' (vram 0x{ret.vram_start:08X}) is outside its parent's address range '{parent.name}' (0x{parent.vram_start:08X} ~ 0x{parent.vram_end:08X}).\n This may happen when the specified `bss_size` value is too small." - ) + if parent.bss_size <= 0: + log.error( + f"Top-level segment '{parent.name}' is missing a `bss_size` value.\n A non-zero `bss_size` value must be defined on the top-level segments that contain '{ret.type}' sections (produced by the '{ret.name}' section).", + ) + if ( + isinstance(ret.vram_start, int) + and isinstance(parent.vram_end, int) + and ret.vram_start >= parent.vram_end + ): + log.error( + f"The section '{ret.name}' (vram 0x{ret.vram_start:08X}) is outside its parent's address range '{parent.name}' (0x{parent.vram_start:08X} ~ 0x{parent.vram_end:08X}).\n This may happen when the specified `bss_size` value is too small.", + ) ret.given_section_order = parse_segment_section_order(yaml) ret.given_subalign = parse_segment_subalign(yaml) @@ -472,7 +462,7 @@ def from_yaml( ret.file_path = Segment.parse_segment_file_path(yaml) ret.bss_contains_common = Segment.parse_segment_bss_contains_common( - yaml, options.opts.ld_bss_contains_common + yaml, options.opts.ld_bss_contains_common, ) ret.given_follows_vram = parse_segment_follows_vram(yaml) @@ -482,11 +472,11 @@ def from_yaml( ret.vram_class = vram_class if ret.given_follows_vram: log.error( - f"Error: segment {ret.name} has both a vram class and a follows_vram property" + f"Error: segment {ret.name} has both a vram class and a follows_vram property", ) if ret.given_vram_symbol: log.error( - f"Error: segment {ret.name} has both a vram class and a vram_symbol property" + f"Error: segment {ret.name} has both a vram class and a vram_symbol property", ) if not ret.align: @@ -525,8 +515,7 @@ def needs_symbols(self) -> bool: def dir(self) -> Path: if self.parent: return self.parent.dir / self.given_dir - else: - return self.given_dir + return self.given_dir @property def show_file_boundaries(self) -> bool: @@ -559,10 +548,9 @@ def subalign(self) -> int | None: def vram_symbol(self) -> str | None: if self.vram_class and self.vram_class.vram_symbol: return self.vram_class.vram_symbol - elif self.given_vram_symbol: + if self.given_vram_symbol: return self.given_vram_symbol - else: - return None + return None def get_exclusive_ram_id(self) -> str | None: if self.parent: @@ -584,15 +572,13 @@ def add_symbol(self, symbol: Symbol) -> None: def seg_symbols(self) -> dict[int, list[Symbol]]: if self.parent: return self.parent.seg_symbols - else: - return self.given_seg_symbols + return self.given_seg_symbols @property def size(self) -> int | None: if self.rom_start is not None and self.rom_end is not None: return self.rom_end - self.rom_start - else: - return None + return None @property def statistics(self) -> SegmentStatistics: @@ -609,8 +595,7 @@ def statistics_type(self) -> SegmentType: def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size - else: - return None + return None @property def section_order(self) -> list[str]: @@ -642,20 +627,17 @@ def get_cname(self) -> str: def contains_vram(self, vram: int) -> bool: if self.vram_start is not None and self.vram_end is not None: return vram >= self.vram_start and vram < self.vram_end - else: - return False + return False def contains_rom(self, rom: int) -> bool: if self.rom_start is not None and self.rom_end is not None: return rom >= self.rom_start and rom < self.rom_end - else: - return False + return False def rom_to_ram(self, rom_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.vram_start + rom_addr - self.rom_start - else: - return None + return None def ram_to_rom(self, ram_addr: int) -> int | None: if not self.contains_vram(ram_addr) and ram_addr != self.vram_end: @@ -663,8 +645,7 @@ def ram_to_rom(self, ram_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.rom_start + ram_addr - self.vram_start - else: - return None + return None def should_scan(self) -> bool: return self.should_split() @@ -738,7 +719,7 @@ def get_most_parent(self) -> Segment: return seg - def get_linker_entries(self) -> "list[LinkerEntry]": + def get_linker_entries(self) -> list[LinkerEntry]: from ..segtypes.linker_entry import LinkerEntry if not self.has_linker_entry: @@ -755,10 +736,9 @@ def get_linker_entries(self) -> "list[LinkerEntry]": self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ) + ), ] - else: - return [] + return [] def log(self, msg: str) -> None: if options.opts.verbose: @@ -776,10 +756,7 @@ def is_name_default(self) -> bool: return self.name == self.get_default_name(self.rom_start) def unique_id(self) -> str: - if self.parent: - s = self.parent.unique_id() + "_" - else: - s = "" + s = self.parent.unique_id() + "_" if self.parent else "" return s + self.type + "_" + self.name @@ -792,7 +769,7 @@ def visible_ram(seg1: Segment, seg2: Segment) -> bool: return seg1.get_exclusive_ram_id() != seg2.get_exclusive_ram_id() def retrieve_symbol( - self, syms: dict[int, list[Symbol]], addr: int + self, syms: dict[int, list[Symbol]], addr: int, ) -> Symbol | None: if addr not in syms: return None @@ -814,7 +791,10 @@ def retrieve_symbol( return items[0] def retrieve_sym_type( - self, syms: dict[int, list[Symbol]], addr: int, type: str + self, + syms: dict[int, list[Symbol]], + addr: int, + type: str, # noqa: A002 # `type` is shadowing a builtin ) -> symbols.Symbol | None: if addr not in syms: return None @@ -837,7 +817,7 @@ def get_symbol( self, addr: int, in_segment: bool = False, - type: str | None = None, + type: str | None = None, # noqa: A002 # `type` is shadowing a builtin create: bool = False, define: bool = False, reference: bool = False, @@ -893,9 +873,8 @@ def get_symbol( ret.type = type if ret.rom is None: ret.rom = rom - if in_segment: - if ret.segment is None: - ret.segment = most_parent + if in_segment and ret.segment is None: + ret.segment = most_parent return ret @@ -903,7 +882,7 @@ def create_symbol( self, addr: int, in_segment: bool, - type: str | None = None, + type: str | None = None, # noqa: A002 # `type` is shadowing a builtin define: bool = False, reference: bool = False, search_ranges: bool = False, diff --git a/src/splat/util/__init__.py b/src/splat/util/__init__.py index d08c7839..c17ab7a7 100644 --- a/src/splat/util/__init__.py +++ b/src/splat/util/__init__.py @@ -1,16 +1,18 @@ -from . import cache_handler as cache_handler -from . import color as color -from . import compiler as compiler -from . import file_presets as file_presets -from . import log as log -from . import n64 as n64 -from . import options as options -from . import palettes as palettes -from . import progress_bar as progress_bar -from . import ps2 as ps2 -from . import psx as psx -from . import relocs as relocs -from . import statistics as statistics -from . import symbols as symbols -from . import utils as utils -from . import vram_classes as vram_classes +from . import ( + cache_handler as cache_handler, + color as color, + compiler as compiler, + file_presets as file_presets, + log as log, + n64 as n64, + options as options, + palettes as palettes, + progress_bar as progress_bar, + ps2 as ps2, + psx as psx, + relocs as relocs, + statistics as statistics, + symbols as symbols, + utils as utils, + vram_classes as vram_classes, +) diff --git a/src/splat/util/cache_handler.py b/src/splat/util/cache_handler.py index 9d86e750..e4260eef 100644 --- a/src/splat/util/cache_handler.py +++ b/src/splat/util/cache_handler.py @@ -1,10 +1,12 @@ from __future__ import annotations import pickle -from typing import Any +from typing import TYPE_CHECKING, Any -from . import options, log -from ..segtypes.common.segment import Segment +from . import log, options + +if TYPE_CHECKING: + from ..segtypes.common.segment import Segment class Cache: @@ -22,7 +24,7 @@ def __init__(self, config: dict[str, Any], use_cache: bool, verbose: bool) -> No log.write(f"Loaded cache ({len(self.cache.keys())} items)") except Exception: log.write( - "Not able to load cache file. Discarding old cache", status="warn" + "Not able to load cache file. Discarding old cache", status="warn", ) # invalidate entire cache if options change diff --git a/src/splat/util/compiler.py b/src/splat/util/compiler.py index eeea374a..40399019 100644 --- a/src/splat/util/compiler.py +++ b/src/splat/util/compiler.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional, Dict @dataclass @@ -15,7 +16,7 @@ class Compiler: asm_nonmatching_label_macro: str = "nonmatching" c_newline: str = "\n" asm_inc_header: str = "" - asm_emit_size_directive: Optional[bool] = None + asm_emit_size_directive: bool | None = None j_as_branch: bool = False uses_include_asm: bool = True align_on_branch_labels: bool = False @@ -64,7 +65,7 @@ class Compiler: MWCCPS2 = Compiler("MWCCPS2", uses_include_asm=False) EEGCC = Compiler("EEGCC", align_on_branch_labels=True) -compiler_for_name: Dict[str, Compiler] = { +compiler_for_name: dict[str, Compiler] = { x.name: x for x in [ GCC, diff --git a/src/splat/util/conf.py b/src/splat/util/conf.py index ea45069b..cf61e80e 100644 --- a/src/splat/util/conf.py +++ b/src/splat/util/conf.py @@ -5,9 +5,9 @@ config = conf.load("path/to/splat.yaml") """ +from __future__ import annotations -from typing import Any, Dict, List, Optional -from pathlib import Path +from typing import TYPE_CHECKING, Any # This unused import makes the yaml library faster. don't remove import pylibyaml # noqa: F401 @@ -15,6 +15,9 @@ from . import options, vram_classes +if TYPE_CHECKING: + from pathlib import Path + def _merge_configs(main_config, additional_config, additional_config_path): # Merge rules are simple @@ -28,7 +31,7 @@ def _merge_configs(main_config, additional_config, additional_config_path): main_config[curkey] = additional_config[curkey] elif type(main_config[curkey]) is not type(additional_config[curkey]): raise TypeError( - f"Could not merge {str(additional_config_path)}: type for key '{curkey}' in configs does not match" + f"Could not merge {additional_config_path!s}: type for key '{curkey}' in configs does not match", ) else: # keys exist and match, see if a list to append @@ -49,12 +52,12 @@ def _merge_configs(main_config, additional_config, additional_config_path): def load( - config_path: List[Path], - modes: Optional[List[str]] = None, + config_path: list[Path], + modes: list[str] | None = None, verbose: bool = False, disassemble_all: bool = False, make_full_disasm_for_code=False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Returns a `dict` with resolved splat config. @@ -76,7 +79,7 @@ def load( Config with invalid options may raise an error. """ - config: Dict[str, Any] = {} + config: dict[str, Any] = {} for entry in config_path: with entry.open() as f: additional_config = yaml.load(f.read(), Loader=yaml.SafeLoader) diff --git a/src/splat/util/file_presets.py b/src/splat/util/file_presets.py index 7965f68b..ce74cd5e 100644 --- a/src/splat/util/file_presets.py +++ b/src/splat/util/file_presets.py @@ -12,7 +12,7 @@ from pathlib import Path -from . import options, log +from . import log, options def write_all_files(): @@ -250,7 +250,7 @@ def write_assembly_inc_files(): # names when using modern gas to avoid build errors. # This means we can't reuse the labels.inc file. gas = macros_inc.replace("\\label", '"\\label"').replace( - '"\\label"\\().NON_MATCHING', '"\\label\\().NON_MATCHING"' + '"\\label"\\().NON_MATCHING', '"\\label\\().NON_MATCHING"', ) elif not options.opts.is_unsupported_platform: log.error(f"Unknown platform '{options.opts.platform}'") diff --git a/src/splat/util/log.py b/src/splat/util/log.py index 19c34701..fb053299 100644 --- a/src/splat/util/log.py +++ b/src/splat/util/log.py @@ -1,10 +1,12 @@ from __future__ import annotations import sys -from typing import NoReturn, Optional, TypeAlias, TextIO -from pathlib import Path +from typing import TYPE_CHECKING, NoReturn, Optional, TextIO, TypeAlias -from colorama import Fore, init, Style +from colorama import Fore, Style, init + +if TYPE_CHECKING: + from pathlib import Path init(autoreset=True) @@ -45,14 +47,13 @@ def parsing_error_preamble(path: Path, line_num: int, line: str) -> None: def status_to_ansi(status: Status) -> Fore | str: if status == "ok": return Fore.GREEN - elif status == "warn": + if status == "warn": return Fore.YELLOW + Style.BRIGHT - elif status == "error": + if status == "error": return Fore.RED + Style.BRIGHT - elif status == "skip": + if status == "skip": return Style.DIM - else: - return "" + return "" def output_file(status: Status) -> TextIO: diff --git a/src/splat/util/n64/__init__.py b/src/splat/util/n64/__init__.py index 22561555..787f41f6 100644 --- a/src/splat/util/n64/__init__.py +++ b/src/splat/util/n64/__init__.py @@ -1,2 +1 @@ -from . import find_code_length as find_code_length -from . import rominfo as rominfo +from . import find_code_length as find_code_length, rominfo as rominfo diff --git a/src/splat/util/n64/find_code_length.py b/src/splat/util/n64/find_code_length.py index 1f9053ca..d15a8dda 100755 --- a/src/splat/util/n64/find_code_length.py +++ b/src/splat/util/n64/find_code_length.py @@ -11,7 +11,7 @@ def int_any_base(x): parser = argparse.ArgumentParser( - description="Given a rom and start offset, find where the code ends" + description="Given a rom and start offset, find where the code ends", ) parser.add_argument("rom", help="path to a .z64 rom") parser.add_argument("start", help="start offset", type=int_any_base) diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index 772c273d..9c174faa 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -1,17 +1,14 @@ #! /usr/bin/env python3 +from __future__ import annotations import argparse - import hashlib import itertools import struct - import sys import zlib from dataclasses import dataclass - from pathlib import Path -from typing import Optional, List import rabbitizer import spimdisasm @@ -78,8 +75,8 @@ class EntryAddressInfo: @staticmethod def new( - value: Optional[int], hi: Optional[int], lo: Optional[int], ori: Optional[int] - ) -> Optional["EntryAddressInfo"]: + value: int | None, hi: int | None, lo: int | None, ori: int | None, + ) -> EntryAddressInfo | None: if value is not None and hi is not None and lo is not None: return EntryAddressInfo(value, hi, lo, ori == lo) return None @@ -88,12 +85,12 @@ def new( @dataclass class N64EntrypointInfo: entry_size: int - data_size: Optional[int] - bss_start_address: Optional[EntryAddressInfo] - bss_size: Optional[EntryAddressInfo] - bss_end_address: Optional[EntryAddressInfo] - main_address: Optional[EntryAddressInfo] - stack_top: Optional[EntryAddressInfo] + data_size: int | None + bss_start_address: EntryAddressInfo | None + bss_size: EntryAddressInfo | None + bss_end_address: EntryAddressInfo | None + main_address: EntryAddressInfo | None + stack_top: EntryAddressInfo | None traditional_entrypoint: bool ori_entrypoint: bool @@ -102,7 +99,7 @@ def segment_size(self) -> int: return self.entry_size + self.data_size return self.entry_size - def get_bss_size(self) -> Optional[int]: + def get_bss_size(self) -> int | None: if self.bss_size is not None: return self.bss_size.value if self.bss_start_address is not None and self.bss_end_address is not None: @@ -111,35 +108,35 @@ def get_bss_size(self) -> Optional[int]: @staticmethod def parse_rom_bytes( - rom_bytes, vram: int, offset: int = 0x1000, size: int = 0x60 - ) -> "N64EntrypointInfo": + rom_bytes, vram: int, offset: int = 0x1000, size: int = 0x60, + ) -> N64EntrypointInfo: word_list = spimdisasm.common.Utils.bytesToWords( - rom_bytes, offset, offset + size + rom_bytes, offset, offset + size, ) nops_count = 0 register_values = [0 for _ in range(32)] completed_pair = [False for _ in range(32)] - hi_assignments: List[Optional[int]] = [None for _ in range(32)] - lo_assignments: List[Optional[int]] = [None for _ in range(32)] + hi_assignments: list[int | None] = [None for _ in range(32)] + lo_assignments: list[int | None] = [None for _ in range(32)] # We need to track if something was paired using an ori instead of an # addiu or similar, because if that's the case we can't emit normal # relocations in the generated symbol_addrs file for it. - ori_assignments: List[Optional[int]] = [None for _ in range(32)] + ori_assignments: list[int | None] = [None for _ in range(32)] - register_bss_address: Optional[int] = None - register_bss_size: Optional[int] = None - register_main_address: Optional[int] = None + register_bss_address: int | None = None + register_bss_size: int | None = None + register_main_address: int | None = None - bss_address: Optional[EntryAddressInfo] = None - bss_size: Optional[EntryAddressInfo] = None - bss_end_address: Optional[EntryAddressInfo] = None + bss_address: EntryAddressInfo | None = None + bss_size: EntryAddressInfo | None = None + bss_end_address: EntryAddressInfo | None = None traditional_entrypoint = True ori_entrypoint = False decrementing_bss_routine = True - data_size: Optional[int] = None - func_call_target: Optional[EntryAddressInfo] = None + data_size: int | None = None + func_call_target: EntryAddressInfo | None = None size = 0 i = 0 @@ -173,11 +170,10 @@ def parse_rom_bytes( if insn.isUnsigned(): ori_assignments[insn.rt.value] = current_rom ori_entrypoint = True - elif insn.doesStore(): - if insn.rt == rabbitizer.RegGprO32.zero: - # Try to detect the zero-ing bss algorithm - # sw $zero, 0x0($t0) - register_bss_address = insn.rs.value + elif insn.doesStore() and insn.rt == rabbitizer.RegGprO32.zero: + # Try to detect the zero-ing bss algorithm + # sw $zero, 0x0($t0) + register_bss_address = insn.rs.value elif insn.isBranch(): if insn.uniqueId == rabbitizer.InstrId.cpu_beq: traditional_entrypoint = False @@ -233,7 +229,7 @@ def parse_rom_bytes( # entrypoint to actual code. traditional_entrypoint = False func_call_target = EntryAddressInfo( - insn.getInstrIndexAsVram(), current_rom, current_rom, False + insn.getInstrIndexAsVram(), current_rom, current_rom, False, ) elif insn.uniqueId == rabbitizer.InstrId.cpu_break: @@ -293,18 +289,17 @@ def parse_rom_bytes( ori_assignments[rabbitizer.RegGprO32.sp.value], ) - if not traditional_entrypoint: - if func_call_target is not None: - main_address = func_call_target - if func_call_target.value > vram: - # Some weird-entrypoint games have non-code between the - # entrypoint and the actual user code. - # We try to find where actual code may begin, and tag - # everything in between as "entrypoint data". + if not traditional_entrypoint and func_call_target is not None: + main_address = func_call_target + if func_call_target.value > vram: + # Some weird-entrypoint games have non-code between the + # entrypoint and the actual user code. + # We try to find where actual code may begin, and tag + # everything in between as "entrypoint data". - code_start = find_code_after_data(rom_bytes, offset + i * 4, vram) - if code_start is not None and code_start > offset + size: - data_size = code_start - (offset + size) + code_start = find_code_after_data(rom_bytes, offset + i * 4, vram) + if code_start is not None and code_start > offset + size: + data_size = code_start - (offset + size) return N64EntrypointInfo( size, @@ -320,9 +315,9 @@ def parse_rom_bytes( def find_code_after_data( - rom_bytes: bytes, offset: int, vram: int, threshold: int = 0x18000 -) -> Optional[int]: - code_offset: Optional[int] = None + rom_bytes: bytes, offset: int, vram: int, threshold: int = 0x18000, +) -> int | None: + code_offset: int | None = None # We loop through every word until we find a valid `jr $ra` instruction and # hope for it to be part of valid code. @@ -337,7 +332,7 @@ def find_code_after_data( if insn.isValid() and insn.isReturn(): # Check the instruction on the delay slot of the `jr $ra` is valid too. next_word = spimdisasm.common.Utils.bytesToWords( - rom_bytes, offset + 4, offset + 4 + 4 + rom_bytes, offset + 4, offset + 4 + 4, )[0] if rabbitizer.Instruction(next_word, vram + 4).isValid(): jr_ra_found = True @@ -393,7 +388,7 @@ def swap_bytes(data): return bytes( itertools.chain.from_iterable( struct.pack(">H", x) for (x,) in struct.iter_unpack(" N64Rom: if rom_bytes is None: rom_bytes = read_rom(rom_path) @@ -457,7 +452,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sys.exit( "splat could not decode the game name;" " try using a different encoding by passing the --header-encoding argument" - " (see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings)" + " (see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings)", ) country_code = rom_bytes[0x3E] @@ -470,7 +465,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sha1 = hashlib.sha1(rom_bytes).hexdigest() entrypoint_info = N64EntrypointInfo.parse_rom_bytes( - rom_bytes, entry_point, size=0x100 + rom_bytes, entry_point, size=0x100, ) return N64Rom( @@ -507,7 +502,7 @@ def get_compiler_info(rom_bytes, entry_point, print_result=True): if print_result: print( f"{branches} branches and {jumps} jumps detected in the first code segment." - f" Compiler is most likely {compiler}" + f" Compiler is most likely {compiler}", ) return compiler diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 40895e06..9aa780b1 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -1,13 +1,16 @@ from __future__ import annotations -from dataclasses import dataclass import os +from dataclasses import dataclass from pathlib import Path -from typing import cast, Literal, Type, TypeVar -from collections.abc import Mapping +from typing import TYPE_CHECKING, Literal, TypeVar, cast from . import compiler -from .compiler import Compiler + +if TYPE_CHECKING: + from collections.abc import Mapping + + from .compiler import Compiler @dataclass @@ -316,7 +319,7 @@ def parse_opt(self, opt: str, t: type[T], default: T | None = None) -> T: if isinstance(value, t): return value if t is float and isinstance(value, int): - return cast(T, float(value)) + return cast("T", float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: @@ -325,7 +328,7 @@ def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: return self.parse_opt(opt, t) def parse_optional_opt_with_default( - self, opt: str, t: type[T], default: T | None + self, opt: str, t: type[T], default: T | None, ) -> T | None: if opt not in self._yaml: return default @@ -334,11 +337,11 @@ def parse_optional_opt_with_default( if value is None or isinstance(value, t): return value if t is float and isinstance(value, int): - return cast(T, float(value)) + return cast("T", float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_opt_within( - self, opt: str, t: type[T], within: list[T], default: T | None = None + self, opt: str, t: type[T], within: list[T], default: T | None = None, ) -> T: value = self.parse_opt(opt, t, default) if value not in within: @@ -346,7 +349,7 @@ def parse_opt_within( return value def parse_path( - self, base_path: Path, opt: str, default: str | None = None + self, base_path: Path, opt: str, default: str | None = None, ) -> Path: return Path(os.path.normpath(base_path / self.parse_opt(opt, str, default))) @@ -360,10 +363,9 @@ def parse_path_list(self, base_path: Path, opt: str, default: str) -> list[Path] if isinstance(paths, str): return [base_path / paths] - elif isinstance(paths, list): + if isinstance(paths, list): return [base_path / path for path in paths] - else: - raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") + raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") def check_no_unread_opts(self) -> None: opts = [opt for opt in self._yaml if opt not in self._read_opts] @@ -392,7 +394,7 @@ def _parse_yaml( comp = compiler.for_name(p.parse_opt("compiler", str, "IDO")) base_path = Path( - os.path.normpath(config_paths[0].parent / p.parse_opt("base_path", str)) + os.path.normpath(config_paths[0].parent / p.parse_opt("base_path", str)), ) asm_path: Path = p.parse_path(base_path, "asm_path", "asm") @@ -416,10 +418,9 @@ def parse_endianness() -> Literal["big", "little"]: if endianness == "big": return "big" - elif endianness == "little": + if endianness == "little": return "little" - else: - raise ValueError(f"Invalid endianness: {endianness}") + raise ValueError(f"Invalid endianness: {endianness}") def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: include_asm_macro_style = p.parse_opt_within( @@ -431,10 +432,9 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: if include_asm_macro_style == "default": return "default" - elif include_asm_macro_style == "maspsx_hack": + if include_asm_macro_style == "maspsx_hack": return "maspsx_hack" - else: - raise ValueError(f"Invalid endianness: {include_asm_macro_style}") + raise ValueError(f"Invalid endianness: {include_asm_macro_style}") default_ld_bss_is_noload = True if platform == "psx": @@ -457,31 +457,31 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: compiler=comp, endianness=parse_endianness(), section_order=p.parse_opt( - "section_order", list, [".text", ".data", ".rodata", ".bss"] + "section_order", list, [".text", ".data", ".rodata", ".bss"], ), generated_c_preamble=p.parse_opt( - "generated_c_preamble", str, '#include "common.h"' + "generated_c_preamble", str, '#include "common.h"', ), generated_s_preamble=p.parse_opt("generated_s_preamble", str, ""), generated_macro_inc_content=p.parse_optional_opt( - "generated_macro_inc_content", str + "generated_macro_inc_content", str, ), generate_asm_macros_files=p.parse_opt("generate_asm_macros_files", bool, True), include_asm_macro_style=parse_include_asm_macro_style(), generated_asm_macros_directory=p.parse_path( - base_path, "generated_asm_macros_directory", "include" + base_path, "generated_asm_macros_directory", "include", ), use_o_as_suffix=p.parse_opt("o_as_suffix", bool, False), gp=p.parse_optional_opt("gp_value", int), check_consecutive_segment_types=p.parse_opt( - "check_consecutive_segment_types", bool, True + "check_consecutive_segment_types", bool, True, ), asset_path=p.parse_path(base_path, "asset_path", "assets"), symbol_addrs_paths=p.parse_path_list( - base_path, "symbol_addrs_path", "symbol_addrs.txt" + base_path, "symbol_addrs_path", "symbol_addrs.txt", ), reloc_addrs_paths=p.parse_path_list( - base_path, "reloc_addrs_path", "reloc_addrs.txt" + base_path, "reloc_addrs_path", "reloc_addrs.txt", ), build_path=p.parse_path(base_path, "build_path", "build"), src_path=p.parse_path(base_path, "src_path", "src"), @@ -492,16 +492,16 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: cache_path=p.parse_path(base_path, "cache_path", ".splache"), hasm_in_src_path=p.parse_opt("hasm_in_src_path", bool, False), create_undefined_funcs_auto=p.parse_opt( - "create_undefined_funcs_auto", bool, True + "create_undefined_funcs_auto", bool, True, ), undefined_funcs_auto_path=p.parse_path( - base_path, "undefined_funcs_auto_path", "undefined_funcs_auto.txt" + base_path, "undefined_funcs_auto_path", "undefined_funcs_auto.txt", ), create_undefined_syms_auto=p.parse_opt( - "create_undefined_syms_auto", bool, True + "create_undefined_syms_auto", bool, True, ), undefined_syms_auto_path=p.parse_path( - base_path, "undefined_syms_auto_path", "undefined_syms_auto.txt" + base_path, "undefined_syms_auto_path", "undefined_syms_auto.txt", ), extensions_path=p.parse_optional_path(base_path, "extensions_path"), lib_path=p.parse_path(base_path, "lib_path", "lib"), @@ -509,7 +509,7 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: subalign=p.parse_optional_opt_with_default("subalign", int, 16), emit_subalign=p.parse_opt("emit_subalign", bool, True), auto_link_sections=p.parse_opt( - "auto_link_sections", list, [".data", ".rodata", ".bss"] + "auto_link_sections", list, [".data", ".rodata", ".bss"], ), ld_script_path=p.parse_path(base_path, "ld_script_path", f"{basename}.ld"), ld_symbol_header_path=p.parse_optional_path(base_path, "ld_symbol_header_path"), @@ -518,76 +518,76 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: ld_sections_denylist=p.parse_opt("ld_sections_denylist", list, []), ld_wildcard_sections=p.parse_opt("ld_wildcard_sections", bool, False), ld_sort_segments_by_vram_class_dependency=p.parse_opt( - "ld_sort_segments_by_vram_class_dependency", bool, False + "ld_sort_segments_by_vram_class_dependency", bool, False, ), ld_use_symbolic_vram_addresses=p.parse_opt( - "ld_use_symbolic_vram_addresses", bool, True + "ld_use_symbolic_vram_addresses", bool, True, ), ld_partial_linking=p.parse_opt("ld_partial_linking", bool, False), ld_partial_scripts_path=p.parse_optional_path( - base_path, "ld_partial_scripts_path" + base_path, "ld_partial_scripts_path", ), ld_partial_build_segments_path=p.parse_optional_path( - base_path, "ld_partial_build_segments_path" + base_path, "ld_partial_build_segments_path", ), ld_dependencies=p.parse_opt("ld_dependencies", bool, False), ld_legacy_generation=p.parse_opt("ld_legacy_generation", bool, False), segment_end_before_align=p.parse_opt("segment_end_before_align", bool, False), segment_symbols_style=p.parse_opt_within( - "segment_symbols_style", str, ["splat", "makerom"], "splat" + "segment_symbols_style", str, ["splat", "makerom"], "splat", ), ld_rom_start=p.parse_opt("ld_rom_start", int, 0), ld_fill_value=p.parse_optional_opt_with_default("ld_fill_value", int, 0), ld_bss_is_noload=p.parse_opt( - "ld_bss_is_noload", bool, default_ld_bss_is_noload + "ld_bss_is_noload", bool, default_ld_bss_is_noload, ), ld_align_segment_start=p.parse_optional_opt_with_default( - "ld_align_segment_start", int, None + "ld_align_segment_start", int, None, ), ld_align_segment_vram_end=p.parse_opt("ld_align_segment_vram_end", bool, True), ld_align_section_vram_end=p.parse_opt("ld_align_section_vram_end", bool, True), ld_generate_symbol_per_data_segment=p.parse_opt( - "ld_generate_symbol_per_data_segment", bool, False + "ld_generate_symbol_per_data_segment", bool, False, ), ld_bss_contains_common=p.parse_opt("ld_bss_contains_common", bool, False), ld_gp_expression=p.parse_optional_opt_with_default( - "ld_gp_expression", str, None + "ld_gp_expression", str, None, ), create_c_files=p.parse_opt("create_c_files", bool, True), auto_decompile_empty_functions=p.parse_opt( - "auto_decompile_empty_functions", bool, True + "auto_decompile_empty_functions", bool, True, ), do_c_func_detection=p.parse_opt("do_c_func_detection", bool, True), c_newline=p.parse_opt("c_newline", str, comp.c_newline), symbol_name_format=p.parse_opt("symbol_name_format", str, "$VRAM"), symbol_name_format_no_rom=p.parse_opt( - "symbol_name_format_no_rom", str, "$VRAM_$SEG" + "symbol_name_format_no_rom", str, "$VRAM_$SEG", ), find_file_boundaries=p.parse_opt("find_file_boundaries", bool, True), pair_rodata_to_text=p.parse_opt("pair_rodata_to_text", bool, True), migrate_rodata_to_functions=p.parse_opt( - "migrate_rodata_to_functions", bool, True + "migrate_rodata_to_functions", bool, True, ), asm_inc_header=p.parse_opt("asm_inc_header", str, comp.asm_inc_header), asm_function_macro=p.parse_opt( - "asm_function_macro", str, comp.asm_function_macro + "asm_function_macro", str, comp.asm_function_macro, ), asm_function_alt_macro=p.parse_opt( - "asm_function_alt_macro", str, comp.asm_function_alt_macro + "asm_function_alt_macro", str, comp.asm_function_alt_macro, ), asm_jtbl_label_macro=p.parse_opt( - "asm_jtbl_label_macro", str, comp.asm_jtbl_label_macro + "asm_jtbl_label_macro", str, comp.asm_jtbl_label_macro, ), asm_data_macro=p.parse_opt("asm_data_macro", str, comp.asm_data_macro), asm_end_label=p.parse_opt("asm_end_label", str, comp.asm_end_label), asm_data_end_label=p.parse_opt( - "asm_data_end_label", str, comp.asm_data_end_label + "asm_data_end_label", str, comp.asm_data_end_label, ), asm_ehtable_label_macro=p.parse_opt( - "asm_ehtable_label_macro", str, comp.asm_ehtable_label_macro + "asm_ehtable_label_macro", str, comp.asm_ehtable_label_macro, ), asm_nonmatching_label_macro=p.parse_opt( - "asm_nonmatching_label_macro", str, comp.asm_nonmatching_label_macro + "asm_nonmatching_label_macro", str, comp.asm_nonmatching_label_macro, ), asm_emit_size_directive=asm_emit_size_directive, mnemonic_ljust=p.parse_opt("mnemonic_ljust", int, 11), @@ -610,10 +610,10 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: string_encoding=p.parse_optional_opt("string_encoding", str), data_string_encoding=p.parse_optional_opt("data_string_encoding", str), rodata_string_guesser_level=p.parse_optional_opt( - "rodata_string_guesser_level", int + "rodata_string_guesser_level", int, ), data_string_guesser_level=p.parse_optional_opt( - "data_string_guesser_level", int + "data_string_guesser_level", int, ), allow_data_addends=p.parse_opt("allow_data_addends", bool, True), header_encoding=p.parse_opt("header_encoding", str, "ASCII"), @@ -631,7 +631,7 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: align_on_branch_labels=align_on_branch_labels, disasm_unknown=p.parse_opt("disasm_unknown", bool, False), detect_redundant_function_end=p.parse_opt( - "detect_redundant_function_end", bool, True + "detect_redundant_function_end", bool, True, ), # Setting either option will produce a full disassembly, # but we still have to check the yaml option first to avoid leaving option unparsed, @@ -642,11 +642,11 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: global_vram_start=p.parse_optional_opt("global_vram_start", int), global_vram_end=p.parse_optional_opt("global_vram_end", int), use_gp_rel_macro_nonmatching=p.parse_opt( - "use_gp_rel_macro_nonmatching", bool, True + "use_gp_rel_macro_nonmatching", bool, True, ), use_gp_rel_macro=p.parse_opt("use_gp_rel_macro", bool, True), suggestion_rodata_section_start=p.parse_opt( - "suggestion_rodata_section_start", bool, True + "suggestion_rodata_section_start", bool, True, ), ) p.check_no_unread_opts() diff --git a/src/splat/util/palettes.py b/src/splat/util/palettes.py index d83d24c1..a277cdf4 100644 --- a/src/splat/util/palettes.py +++ b/src/splat/util/palettes.py @@ -1,33 +1,31 @@ from __future__ import annotations -from typing import Dict, TYPE_CHECKING - -from ..util import log +from typing import TYPE_CHECKING from ..segtypes.common.group import CommonSegGroup from ..segtypes.n64.ci import N64SegCi from ..segtypes.n64.palette import N64SegPalette +from ..util import log if TYPE_CHECKING: from ..segtypes.segment import Segment -global_ids: Dict[str, N64SegPalette] = {} +global_ids: dict[str, N64SegPalette] = {} # Resolve Raster#palette and Palette#raster links def initialize(all_segments: list[Segment]) -> None: def collect_global_ids(segments: list[Segment]) -> None: for segment in segments: - if isinstance(segment, N64SegPalette): - if segment.global_id is not None: - global_ids[segment.global_id] = segment + if isinstance(segment, N64SegPalette) and segment.global_id is not None: + global_ids[segment.global_id] = segment if isinstance(segment, CommonSegGroup): collect_global_ids(segment.subsegments) def process(segments: list[Segment]) -> None: - raster_map: Dict[str, N64SegCi] = {} - palette_map: Dict[str, N64SegPalette] = {} + raster_map: dict[str, N64SegCi] = {} + palette_map: dict[str, N64SegPalette] = {} for segment in segments: if isinstance(segment, N64SegPalette): @@ -43,13 +41,13 @@ def process(segments: list[Segment]) -> None: for raster in raster_map.values(): for pal_name in raster.palette_names: - pal = global_ids.get(pal_name, None) + pal = global_ids.get(pal_name) if pal is not None: global_ids_not_seen.discard(pal_name) palettes_seen.discard(pal_name) raster.palettes.append(pal) else: - pal = palette_map.get(pal_name, None) + pal = palette_map.get(pal_name) if pal is not None: palettes_seen.discard(pal_name) @@ -59,7 +57,7 @@ def process(segments: list[Segment]) -> None: "Could not find palette: " + pal_name + ", referenced by raster: " - + raster.name + + raster.name, ) # Resolve "." palette links @@ -88,5 +86,5 @@ def process(segments: list[Segment]) -> None: if len(global_ids_not_seen) > 0: log.error( - f"Found no ci links to palettes with global_ids: {', '.join(global_ids_not_seen)}" + f"Found no ci links to palettes with global_ids: {', '.join(global_ids_not_seen)}", ) diff --git a/src/splat/util/progress_bar.py b/src/splat/util/progress_bar.py index 12b2ee3b..3ce7696e 100644 --- a/src/splat/util/progress_bar.py +++ b/src/splat/util/progress_bar.py @@ -1,8 +1,9 @@ from __future__ import annotations -import tqdm import sys +import tqdm + out_file = sys.stderr diff --git a/src/splat/util/ps2/ps2elfinfo.py b/src/splat/util/ps2/ps2elfinfo.py index 2b9419fe..36aa0743 100644 --- a/src/splat/util/ps2/ps2elfinfo.py +++ b/src/splat/util/ps2/ps2elfinfo.py @@ -3,18 +3,20 @@ from __future__ import annotations import dataclasses -from pathlib import Path +from typing import TYPE_CHECKING + import spimdisasm from spimdisasm.elf32 import ( - Elf32File, Elf32Constants, - Elf32SectionHeaderFlag, + Elf32File, Elf32ObjectFileType, + Elf32SectionHeaderFlag, ) -from typing import Optional from .. import log +if TYPE_CHECKING: + from pathlib import Path ELF_SECTION_MAPPING: dict[str, str] = { ".text": "asm", @@ -56,11 +58,11 @@ class Ps2Elf: size: int compiler: str elf_section_names: list[tuple[str, bool]] - gp: Optional[int] - ld_gp_expression: Optional[str] + gp: int | None + ld_gp_expression: str | None @staticmethod - def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: + def get_info(elf_path: Path, elf_bytes: bytes) -> Ps2Elf | None: # Avoid spimdisasm from complaining about unknown sections. spimdisasm.common.GlobalConfig.QUIET = True @@ -81,7 +83,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: gp = elf.reginfo.gpValue else: gp = None - first_small_section_info: Optional[tuple[str, int]] = None + first_small_section_info: tuple[str, int] | None = None first_segment_name = "cod" segs = [FakeSegment(first_segment_name, 0, 0, [])] @@ -91,7 +93,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: elf_section_names: list[tuple[str, bool]] = [] - first_offset: Optional[int] = None + first_offset: int | None = None rom_size = 0 previous_type = Elf32Constants.Elf32SectionHeaderType.PROGBITS diff --git a/src/splat/util/psx/psxexeinfo.py b/src/splat/util/psx/psxexeinfo.py index f6b13451..eb880380 100755 --- a/src/splat/util/psx/psxexeinfo.py +++ b/src/splat/util/psx/psxexeinfo.py @@ -3,12 +3,9 @@ from __future__ import annotations import argparse - +import dataclasses import hashlib import struct - -import dataclasses - from pathlib import Path import rabbitizer @@ -74,20 +71,14 @@ def is_valid(insn) -> bool: if not insn.isValid(): - if insn.instrIdType.name in ("CPU_SPECIAL", "CPU_COP2"): - return True - else: - return False + return insn.instrIdType.name in ("CPU_SPECIAL", "CPU_COP2") opcode = insn.getOpcodeName() - if opcode in UNSUPPORTED_OPS: - return False - - return True + return opcode not in UNSUPPORTED_OPS def try_find_text( - rom_bytes, start_offset=PAYLOAD_OFFSET, valid_threshold=32 + rom_bytes, start_offset=PAYLOAD_OFFSET, valid_threshold=32, ) -> tuple[int, int]: start = end = 0 good_count = valid_count = 0 diff --git a/src/splat/util/relocs.py b/src/splat/util/relocs.py index 5c7c02a9..964aefce 100644 --- a/src/splat/util/relocs.py +++ b/src/splat/util/relocs.py @@ -1,11 +1,10 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict import spimdisasm -from . import log, options, symbols, progress_bar +from . import log, options, progress_bar, symbols @dataclass @@ -17,7 +16,7 @@ class Reloc: addend: int = 0 -all_relocs: Dict[int, Reloc] = {} +all_relocs: dict[int, Reloc] = {} def add_reloc(reloc: Reloc) -> None: @@ -64,13 +63,13 @@ def initialize() -> None: if attr_name == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute name in '{info}', is there extra whitespace?" + f"Missing attribute name in '{info}', is there extra whitespace?", ) log.error("") if attr_val == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute value in '{info}', is there extra whitespace?" + f"Missing attribute value in '{info}', is there extra whitespace?", ) log.error("") @@ -111,7 +110,7 @@ def initialize() -> None: if reloc.rom_address in all_relocs: log.parsing_error_preamble(path, line_num, line) log.error( - f"Duplicated 'rom' address for reloc: 0x{reloc.rom_address:X}" + f"Duplicated 'rom' address for reloc: 0x{reloc.rom_address:X}", ) add_reloc(reloc) @@ -122,9 +121,9 @@ def initialize_spim_context() -> None: if reloc_type is None: log.error( - f"Reloc type '{reloc.reloc_type}' is not valid. Rom address: 0x{rom_address:X}" + f"Reloc type '{reloc.reloc_type}' is not valid. Rom address: 0x{rom_address:X}", ) symbols.spim_context.addGlobalReloc( - rom_address, reloc_type, reloc.symbol_name, addend=reloc.addend + rom_address, reloc_type, reloc.symbol_name, addend=reloc.addend, ) diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index af755dea..059aff74 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -8,15 +8,14 @@ def fmt_size(size: int) -> str: if size > 1000000: return f"{size // 1000000} MB" - elif size > 1000: + if size > 1000: return f"{size // 1000} KB" - else: - return f"{size} B" + return f"{size} B" class Statistics: - __slots__ = ("seg_sizes", "seg_split", "seg_cached") - + __slots__ = ("seg_cached", "seg_sizes", "seg_split") + def __init__(self) -> None: self.seg_sizes: dict[str, int] = {} self.seg_split: dict[str, int] = {} @@ -49,14 +48,14 @@ def print_statistics(self, total_size: int) -> None: unk_ratio = unk_size / total_size log.write( - f"Split {fmt_size(rest_size)} ({known_ratio:.2%}) in defined segments" + f"Split {fmt_size(rest_size)} ({known_ratio:.2%}) in defined segments", ) for typ, size in self.seg_sizes.items(): if typ != "unk": tmp_ratio = size / total_size log.write( - f"{typ:>20}: {fmt_size(size):>8} ({tmp_ratio:.2%}) {Fore.GREEN}{self.seg_split.get(typ, 0)} split{Style.RESET_ALL}, {Style.DIM}{self.seg_cached.get(typ, 0)} cached" + f"{typ:>20}: {fmt_size(size):>8} ({tmp_ratio:.2%}) {Fore.GREEN}{self.seg_split.get(typ, 0)} split{Style.RESET_ALL}, {Style.DIM}{self.seg_cached.get(typ, 0)} cached", ) log.write( - f"{'unknown':>20}: {fmt_size(unk_size):>8} ({unk_ratio:.2%}) from unknown bin files" + f"{'unknown':>20}: {fmt_size(unk_size):>8} ({unk_ratio:.2%}) from unknown bin files", ) diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index b90d3620..09d83786 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -1,17 +1,18 @@ from __future__ import annotations -from dataclasses import dataclass import re +from dataclasses import dataclass from typing import TYPE_CHECKING import spimdisasm - from intervaltree import IntervalTree + from ..disassembler import disassembler_instance -from pathlib import Path # circular import if TYPE_CHECKING: + from pathlib import Path + from ..segtypes.segment import Segment from . import log, options, progress_bar @@ -40,18 +41,15 @@ def check_valid_type(typename: str) -> bool: if typename in splat_sym_types: return True - if typename in disassembler_instance.get_instance().known_types(): - return True - - return False + return typename in disassembler_instance.get_instance().known_types() -def is_truey(str: str) -> bool: - return str.lower() in TRUEY_VALS +def is_truey(string: str) -> bool: + return string.lower() in TRUEY_VALS -def is_falsey(str: str) -> bool: - return str.lower() in FALSEY_VALS +def is_falsey(string: str) -> bool: + return string.lower() in FALSEY_VALS def add_symbol(sym: Symbol) -> None: @@ -76,7 +74,7 @@ def to_cname(symbol_name: str) -> str: def handle_sym_addrs( - path: Path, sym_addrs_lines: list[str], all_segments: list[Segment] + path: Path, sym_addrs_lines: list[str], all_segments: list[Segment], ) -> None: def get_seg_for_name(name: str) -> Segment | None: for segment in all_segments: @@ -96,7 +94,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: line: str for line_num, line in enumerate(prog_bar): line = line.strip() - if not line == "" and not line.startswith("//"): + if line != "" and not line.startswith("//"): comment_loc = line.find("//") line_main = line line_ext = "" @@ -133,13 +131,13 @@ def get_seg_for_rom(rom: int) -> Segment | None: if attr_name == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute name in '{info}', is there extra whitespace?" + f"Missing attribute name in '{info}', is there extra whitespace?", ) log.error("") if attr_val == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute value in '{info}', is there extra whitespace?" + f"Missing attribute value in '{info}', is there extra whitespace?", ) log.error("") @@ -149,20 +147,20 @@ def get_seg_for_rom(rom: int) -> Segment | None: if not check_valid_type(attr_val): log.parsing_error_preamble(path, line_num, line) log.write( - f"Unrecognized symbol type in '{info}', it should be one of" + f"Unrecognized symbol type in '{info}', it should be one of", ) log.write( [ *splat_sym_types, *spimdisasm.common.gKnownTypes, - ] + ], ) log.write( - "You may use a custom type that starts with a capital letter" + "You may use a custom type that starts with a capital letter", ) log.error("") - type = attr_val - sym.type = type + type_ = attr_val + sym.type = type_ continue if attr_name == "size": size = int(attr_val, 0) @@ -200,18 +198,18 @@ def get_seg_for_rom(rom: int) -> Segment | None: if align < 0: log.parsing_error_preamble(path, line_num, line) log.error( - f"The given alignment for '{sym.name}' (0x{sym.vram_start:08X}) is negative." + f"The given alignment for '{sym.name}' (0x{sym.vram_start:08X}) is negative.", ) align_shift = (align & (-align)).bit_length() - 1 if (1 << align_shift) != align: log.parsing_error_preamble(path, line_num, line) log.error( - f"The given alignment '0x{align:X}' for symbol '{sym.name}' (0x{sym.vram_start:08X}) is not a power of two." + f"The given alignment '0x{align:X}' for symbol '{sym.name}' (0x{sym.vram_start:08X}) is not a power of two.", ) if sym.vram_start % align != 0: log.parsing_error_preamble(path, line_num, line) log.error( - f"The symbol '{sym.name}' (0x{sym.vram_start:08X}) is not aligned already to the given alignment '0x{align:X}'." + f"The symbol '{sym.name}' (0x{sym.vram_start:08X}) is not aligned already to the given alignment '0x{align:X}'.", ) sym.given_align = align @@ -219,7 +217,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: except: log.parsing_error_preamble(path, line_num, line) log.write( - f"value of attribute '{attr_name}' could not be read:" + f"value of attribute '{attr_name}' could not be read:", ) log.write("") raise @@ -235,7 +233,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: if tf_val is None: log.parsing_error_preamble(path, line_num, line) log.write( - f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of" + f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of", ) log.write([*TRUEY_VALS, *FALSEY_VALS]) log.error("") @@ -276,7 +274,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: ignored_addresses.add(sym.vram_start) else: spim_context.addBannedSymbolRangeBySize( - sym.vram_start, sym.given_size + sym.vram_start, sym.given_size, ) continue @@ -294,7 +292,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: if not sym.allow_duplicated or not item.allow_duplicated: log.parsing_error_preamble(path, line_num, line) log.error( - f"Duplicate symbol detected! {sym.name} has already been defined at vram 0x{item.vram_start:08X}" + f"Duplicate symbol detected! {sym.name} has already been defined at vram 0x{item.vram_start:08X}", ) if addr in all_symbols_dict: @@ -303,12 +301,14 @@ def get_seg_for_rom(rom: int) -> Segment | None: have_same_rom_addresses = sym.rom == item.rom same_segment = sym.segment == item.segment - if have_same_rom_addresses and same_segment: - if not sym.allow_duplicated or not item.allow_duplicated: - log.parsing_error_preamble(path, line_num, line) - log.error( - f"Duplicate symbol detected! {sym.name} clashes with {item.name} defined at vram 0x{addr:08X}.\n If this is intended, specify either a segment or a rom address for this symbol" - ) + if ( + have_same_rom_addresses and same_segment + and (not sym.allow_duplicated or not item.allow_duplicated) + ): + log.parsing_error_preamble(path, line_num, line) + log.error( + f"Duplicate symbol detected! {sym.name} clashes with {item.name} defined at vram 0x{addr:08X}.\n If this is intended, specify either a segment or a rom address for this symbol", + ) if len(sym.filename) > 253 or any( c in ILLEGAL_FILENAME_CHARS for c in sym.filename @@ -324,7 +324,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: f" which will be a problem when writing the symbol to its own file.\n" f" To fix this specify a `filename` for this symbol, like `filename:func_{sym.vram_start:08X}`.\n" f" Make sure the filename does not exceed 253 bytes nor it contains any of the following characters:\n" - f" {ILLEGAL_FILENAME_CHARS}" + f" {ILLEGAL_FILENAME_CHARS}", ) seen_symbols[sym.name] = sym @@ -382,9 +382,7 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: ram_id = segment.get_exclusive_ram_id() if ram_id is None: - if global_vram_start is None: - global_vram_start = segment.vram_start - elif segment.vram_start < global_vram_start: + if global_vram_start is None or segment.vram_start < global_vram_start: global_vram_start = segment.vram_start if global_vram_end is None: @@ -396,14 +394,10 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: # Global segment *after* overlay segments? global_segments_after_overlays.append(segment) - if global_vrom_start is None: - global_vrom_start = segment.rom_start - elif segment.rom_start < global_vrom_start: + if global_vrom_start is None or segment.rom_start < global_vrom_start: global_vrom_start = segment.rom_start - if global_vrom_end is None: - global_vrom_end = segment.rom_end - elif global_vrom_end < segment.rom_end: + if global_vrom_end is None or global_vrom_end < segment.rom_end: global_vrom_end = segment.rom_end elif segment.vram_start != segment.vram_end: @@ -430,7 +424,7 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: and global_vrom_end is not None ): spim_context.changeGlobalSegmentRanges( - global_vrom_start, global_vrom_end, global_vram_start, global_vram_end + global_vrom_start, global_vrom_end, global_vram_start, global_vram_end, ) overlaps_found = False @@ -453,7 +447,7 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: "Many overlaps between non-global and global segments were found.", ) log.write( - "This is usually caused by missing `exclusive_ram_id` tags on segments that have a higher vram address than other `exclusive_ram_id`-tagged segments" + "This is usually caused by missing `exclusive_ram_id` tags on segments that have a higher vram address than other `exclusive_ram_id`-tagged segments", ) if len(global_segments_after_overlays) > 0: log.write( @@ -495,27 +489,27 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: def add_symbol_to_spim_segment( - segment: spimdisasm.common.SymbolsSegment, sym: Symbol + segment: spimdisasm.common.SymbolsSegment, sym: Symbol, ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = segment.addFunction( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, ) elif sym.type == "jtbl": context_sym = segment.addJumpTable( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, ) elif sym.type == "jtbl_label": context_sym = segment.addJumpTableLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, ) elif sym.type == "label": context_sym = segment.addBranchLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, ) else: context_sym = segment.addSymbol( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, ) if sym.type is not None: context_sym.type = sym.type @@ -553,27 +547,27 @@ def add_symbol_to_spim_segment( def add_symbol_to_spim_section( - section: spimdisasm.mips.sections.SectionBase, sym: Symbol + section: spimdisasm.mips.sections.SectionBase, sym: Symbol, ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = section.addFunction( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, ) elif sym.type == "jtbl": context_sym = section.addJumpTable( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, ) elif sym.type == "jtbl_label": context_sym = section.addJumpTableLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, ) elif sym.type == "label": context_sym = section.addBranchLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, ) else: context_sym = section.addSymbol( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, ) if sym.type is not None: context_sym.type = sym.type @@ -638,7 +632,7 @@ def create_symbol_from_spim_symbol( in_segment = segment.contains_vram(context_sym.vram) sym = segment.create_symbol( - context_sym.vram, force_in_segment or in_segment, type=sym_type, reference=True + context_sym.vram, force_in_segment or in_segment, type=sym_type, reference=True, ) if sym.given_name is None and context_sym.name is not None: @@ -719,15 +713,15 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash((self.vram_start, self.segment)) - def format_name(self, format: str) -> str: - ret = format + def format_name(self, format_: str) -> str: + ret = format_ ret = ret.replace("$VRAM", f"{self.vram_start:08X}") if "$ROM" in ret: if not isinstance(self.rom, int): log.error( - f"Attempting to rom-name a symbol with no ROM address: {self.vram_start:08X} typed {self.type}" + f"Attempting to rom-name a symbol with no ROM address: {self.vram_start:08X} typed {self.type}", ) ret = ret.replace("$ROM", f"{self.rom:X}") @@ -742,9 +736,11 @@ def format_name(self, format: str) -> str: @property def default_name(self) -> str: - if self._generated_default_name is not None: - if self.type == self._last_type: - return self._generated_default_name + if ( + self._generated_default_name is not None + and self.type == self._last_type + ): + return self._generated_default_name if self.segment: if isinstance(self.rom, int): diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index ec4f2edd..268efe56 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import TypedDict, NotRequired +from typing import NotRequired, TypedDict from . import log @@ -17,10 +17,9 @@ class VramClass: def vram_symbol(self) -> str | None: if self.given_vram_symbol is not None: return self.given_vram_symbol - elif self.follows_classes: + if self.follows_classes: return self.name + "_CLASS_VRAM" - else: - return None + return None _vram_classes: dict[str, VramClass] = {} @@ -50,7 +49,7 @@ class SerializedSegmentData(TypedDict): pair_segment: NotRequired[str] exclusive_ram_id: NotRequired[str] find_file_boundaries: NotRequired[bool] - + def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: @@ -92,24 +91,24 @@ def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: vram_symbol = vram_class["vram_symbol"] if not isinstance(vram_symbol, str): log.error( - f"vram_symbol ({vram_symbol})must be a string, got {type(vram_symbol)}" + f"vram_symbol ({vram_symbol})must be a string, got {type(vram_symbol)}", ) if "follows_classes" in vram_class: follows_classes = vram_class["follows_classes"] if not isinstance(follows_classes, list): log.error( - f"vram_symbol ({follows_classes})must be a list, got {type(follows_classes)}" + f"vram_symbol ({follows_classes})must be a list, got {type(follows_classes)}", ) for follows_class in follows_classes: if follows_class not in class_names: log.error( - f"follows_class ({follows_class}) not found in vram_classes" + f"follows_class ({follows_class}) not found in vram_classes", ) elif isinstance(vram_class, list): if len(vram_class) != 2: log.error( - f"vram_class ({vram_class}) must have 2 elements, got {len(vram_class)}" + f"vram_class ({vram_class}) must have 2 elements, got {len(vram_class)}", ) name = vram_class[0] vram = int(vram_class[1]) diff --git a/test.py b/test.py index d8dfc9b0..cc63a543 100755 --- a/test.py +++ b/test.py @@ -1,37 +1,38 @@ #!/usr/bin/env python3 +from __future__ import annotations + import difflib import filecmp -import io +import unittest from pathlib import Path + import spimdisasm -import unittest -from typing import List, Tuple from src.splat import __version__ from src.splat.disassembler import disassembler_instance from src.splat.scripts.split import main -from src.splat.util import symbols, options -from src.splat.segtypes.common.rodata import CommonSegRodata -from src.splat.segtypes.common.code import CommonSegCode -from src.splat.segtypes.common.c import CommonSegC from src.splat.segtypes.common.bss import CommonSegBss +from src.splat.segtypes.common.c import CommonSegC +from src.splat.segtypes.common.code import CommonSegCode +from src.splat.segtypes.common.rodata import CommonSegRodata from src.splat.segtypes.segment import Segment +from src.splat.util import options, symbols class Testing(unittest.TestCase): def compare_files(self, test_path, ref_path): - with io.open(test_path) as test_f, io.open(ref_path) as ref_f: + with open(test_path) as test_f, open(ref_path) as ref_f: self.assertListEqual(list(test_f), list(ref_f)) - def get_same_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): + def get_same_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): for name in dcmp.same_files: out.append((name, dcmp.left, dcmp.right)) for sub_dcmp in dcmp.subdirs.values(): self.get_same_files(sub_dcmp, out) - def get_diff_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): + def get_diff_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): for name in dcmp.diff_files: out.append((name, dcmp.left, dcmp.right)) @@ -39,7 +40,7 @@ def get_diff_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): self.get_diff_files(sub_dcmp, out) def get_left_only_files( - self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] + self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]], ): for name in dcmp.left_only: out.append((name, dcmp.left, dcmp.right)) @@ -48,7 +49,7 @@ def get_left_only_files( self.get_left_only_files(sub_dcmp, out) def get_right_only_files( - self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] + self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]], ): for name in dcmp.right_only: out.append((name, dcmp.left, dcmp.right)) @@ -61,19 +62,19 @@ def test_basic_app(self): main([Path("test/basic_app/splat.yaml")], None, False) comparison = filecmp.dircmp( - "test/basic_app/split", "test/basic_app/expected", [".gitkeep"] + "test/basic_app/split", "test/basic_app/expected", [".gitkeep"], ) - diff_files: List[Tuple[str, str, str]] = [] + diff_files: list[tuple[str, str, str]] = [] self.get_diff_files(comparison, diff_files) - same_files: List[Tuple[str, str, str]] = [] + same_files: list[tuple[str, str, str]] = [] self.get_same_files(comparison, same_files) - left_only_files: List[Tuple[str, str, str]] = [] + left_only_files: list[tuple[str, str, str]] = [] self.get_left_only_files(comparison, left_only_files) - right_only_files: List[Tuple[str, str, str]] = [] + right_only_files: list[tuple[str, str, str]] = [] self.get_right_only_files(comparison, right_only_files) print("same_files", same_files) @@ -108,7 +109,7 @@ def test_basic_app(self): file2_lines = file2.readlines() for line in difflib.unified_diff( - file1_lines, file2_lines, fromfile="file1", tofile="file2", lineterm="" + file1_lines, file2_lines, fromfile="file1", tofile="file2", lineterm="", ): print(line) @@ -162,8 +163,8 @@ def test_check_valid_type(self): splat_sym_types = {"func", "jtbl", "jtbl_label", "label"} - for type in splat_sym_types: - assert symbols.check_valid_type(type) + for type_ in splat_sym_types: + assert symbols.check_valid_type(type_) spim_types = [ "char*", @@ -182,8 +183,8 @@ def test_check_valid_type(self): "s32", ] - for type in spim_types: - assert symbols.check_valid_type(type) + for type_ in spim_types: + assert symbols.check_valid_type(type_) def test_add_symbol_to_spim_segment(self): segment = spimdisasm.common.SymbolsSegment( @@ -240,7 +241,7 @@ def test_create_symbol_from_spim_symbol(self): ) context_sym = spimdisasm.common.ContextSymbol(address=0) result = symbols.create_symbol_from_spim_symbol( - segment, context_sym, force_in_segment=False + segment, context_sym, force_in_segment=False, ) assert result.referenced assert result.extract @@ -362,7 +363,7 @@ def test_disassemble_data(self): assert bss.spim_section is not None assert isinstance( - bss.spim_section.get_section(), spimdisasm.mips.sections.SectionBss + bss.spim_section.get_section(), spimdisasm.mips.sections.SectionBss, ) assert bss.spim_section.get_section().bssVramStart == 0x40000000 assert bss.spim_section.get_section().bssVramEnd == 0x300 @@ -374,7 +375,7 @@ def test_attrs(self): test_init() sym_addrs_lines = [ - "func_1 = 0x100; // type:func size:10 rom:100 segment:test_segment name_end:the_name_end " + "func_1 = 0x100; // type:func size:10 rom:100 segment:test_segment name_end:the_name_end ", ] all_segments = [ @@ -386,7 +387,7 @@ def test_attrs(self): vram_start=0x300, args=[], yaml={}, - ) + ), ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -403,7 +404,7 @@ def test_boolean_attrs(self): sym_addrs_lines = [ "func_1 = 0x100; // defined:True extract:True force_migration:True force_not_migration:True " - "allow_addend:True dont_allow_addend:True" + "allow_addend:True dont_allow_addend:True", ] all_segments = [ @@ -415,7 +416,7 @@ def test_boolean_attrs(self): vram_start=0x300, args=[], yaml={}, - ) + ), ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -441,7 +442,7 @@ def test_ignore(self): vram_start=0x300, args=[], yaml={}, - ) + ), ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -470,7 +471,7 @@ def test_overlay(self): ], } - all_segments: List["Segment"] = [ + all_segments: list[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -479,7 +480,7 @@ def test_overlay(self): vram_start=0x80000400, args=[], yaml=yaml, - ) + ), ] # force this since it's hard to set up @@ -512,7 +513,7 @@ def test_global(self): ], } - all_segments: List["Segment"] = [ + all_segments: list[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -521,7 +522,7 @@ def test_global(self): vram_start=0x100, args=[], yaml=yaml, - ) + ), ] assert symbols.spim_context.globalSegment.vramStart == 0x80000000 From 1f41416122568079f7439f1e01cdf26e8004bc2a Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:33:07 -0600 Subject: [PATCH 05/19] Don't use `typing.override` --- src/splat/segtypes/common/group.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 2fa353e9..7ca4f97e 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING from ...util import log from ..segment import Segment, SegmentStatistics, empty_statistics @@ -161,7 +161,6 @@ def should_split(self) -> bool: def should_scan(self) -> bool: return self.extract - @override def cache(self) -> list[tuple[SerializedSegmentData | list[str], int | None]]: # type: ignore[override] c = [] From 565a48dafb1c967b24abb18c2075697896d89758 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:35:41 -0600 Subject: [PATCH 06/19] Workaround for `typing.NotRequired` for older python --- src/splat/util/vram_classes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index 268efe56..e9234c13 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -1,10 +1,13 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import NotRequired, TypedDict +from typing import TYPE_CHECKING, TypedDict from . import log +if TYPE_CHECKING: + from typing_extensions import NotRequired + @dataclass(frozen=True) class VramClass: From 62e5232fab7d75b08ec857ff38ef74223d3de741 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:48:55 -0600 Subject: [PATCH 07/19] Add more missing annotations --- src/splat/disassembler/disassembler.py | 6 +++++- src/splat/disassembler/null_disassembler.py | 4 +++- .../disassembler/spimdisasm_disassembler.py | 2 +- src/splat/scripts/capy.py | 6 +++--- src/splat/util/cache_handler.py | 6 +----- src/splat/util/file_presets.py | 8 ++++---- src/splat/util/n64/find_code_length.py | 7 ++++--- src/splat/util/n64/rominfo.py | 18 +++++++++--------- src/splat/util/psx/psxexeinfo.py | 10 +++++----- 9 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/splat/disassembler/disassembler.py b/src/splat/disassembler/disassembler.py index 4a1b291f..7d5a1531 100644 --- a/src/splat/disassembler/disassembler.py +++ b/src/splat/disassembler/disassembler.py @@ -1,9 +1,13 @@ +from __future__ import annotations + from abc import ABC, abstractmethod class Disassembler(ABC): + __slots__ = () + @abstractmethod - def configure(self): + def configure(self) -> None: raise NotImplementedError("configure") @abstractmethod diff --git a/src/splat/disassembler/null_disassembler.py b/src/splat/disassembler/null_disassembler.py index fea13406..dbb3c6c4 100644 --- a/src/splat/disassembler/null_disassembler.py +++ b/src/splat/disassembler/null_disassembler.py @@ -1,9 +1,11 @@ +from __future__ import annotations from . import disassembler class NullDisassembler(disassembler.Disassembler): - def configure(self): + __slots__ = () + def configure(self) -> None: pass def check_version(self, skip_version_check: bool, splat_version: str): diff --git a/src/splat/disassembler/spimdisasm_disassembler.py b/src/splat/disassembler/spimdisasm_disassembler.py index 43b033c2..39447b48 100644 --- a/src/splat/disassembler/spimdisasm_disassembler.py +++ b/src/splat/disassembler/spimdisasm_disassembler.py @@ -10,7 +10,7 @@ class SpimdisasmDisassembler(disassembler.Disassembler): # This value should be kept in sync with the version listed on requirements.txt and pyproject.toml SPIMDISASM_MIN = (1, 39, 0) - def configure(self): + def configure(self) -> None: # Configure spimdisasm spimdisasm.common.GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET = True spimdisasm.common.GlobalConfig.TRUST_USER_FUNCTIONS = True diff --git a/src/splat/scripts/capy.py b/src/splat/scripts/capy.py index 77d0c4a1..f3627b47 100644 --- a/src/splat/scripts/capy.py +++ b/src/splat/scripts/capy.py @@ -27,18 +27,18 @@ """ -def print_capybara(): +def print_capybara() -> None: print(capybara) -def process_arguments(args: argparse.Namespace): +def process_arguments(args: argparse.Namespace) -> None: print_capybara() script_description = "Capybara" -def add_subparser(subparser: argparse._SubParsersAction): +def add_subparser(subparser: argparse._SubParsersAction) -> None: parser = subparser.add_parser( "capy", help=script_description, description=script_description, ) diff --git a/src/splat/util/cache_handler.py b/src/splat/util/cache_handler.py index e4260eef..4116d139 100644 --- a/src/splat/util/cache_handler.py +++ b/src/splat/util/cache_handler.py @@ -6,7 +6,7 @@ from . import log, options if TYPE_CHECKING: - from ..segtypes.common.segment import Segment + from ..segtypes.segment import Segment class Cache: @@ -46,12 +46,8 @@ def save(self, verbose: bool) -> None: def check_cache_hit(self, segment: Segment, update_on_miss: bool) -> bool: if self.use_cache: - # types: no-untyped-call error: Call to untyped function "cache" in typed context cached = segment.cache() - # types: ^^^^^^^^^^^^^^^ - # types: no-untyped-call error: Call to untyped function "unique_id" in typed context segment_id = segment.unique_id() -# types: ^^^^^^^^^^^^^^^^^^^ if cached == self.cache.get(segment_id): # Cache hit diff --git a/src/splat/util/file_presets.py b/src/splat/util/file_presets.py index ce74cd5e..b915fdea 100644 --- a/src/splat/util/file_presets.py +++ b/src/splat/util/file_presets.py @@ -15,7 +15,7 @@ from . import log, options -def write_all_files(): +def write_all_files() -> None: if not options.opts.generate_asm_macros_files: return @@ -36,7 +36,7 @@ def _write(filepath: str, contents: str): f.write(contents) -def write_include_asm_h(): +def write_include_asm_h() -> None: if not options.opts.compiler.uses_include_asm: # These compilers do not use the `INCLUDE_ASM` macro. return @@ -115,7 +115,7 @@ def write_include_asm_h(): _write(f"{directory}/include_asm.h", file_data) -def write_assembly_inc_files(): +def write_assembly_inc_files() -> None: directory = options.opts.generated_asm_macros_directory.as_posix() func_macros = f"""\ @@ -352,7 +352,7 @@ def write_assembly_inc_files(): _write(f"{directory}/macro.inc", f"{preamble}\n{gas}") -def write_gte_macros(): +def write_gte_macros() -> None: # Taken directly from https://github.com/Decompollaborate/rabbitizer/blob/-/docs/r3000gte/gte_macros.s # Please try to upstream any fix/update done here. gte_macros = """\ diff --git a/src/splat/util/n64/find_code_length.py b/src/splat/util/n64/find_code_length.py index d15a8dda..7bc00111 100755 --- a/src/splat/util/n64/find_code_length.py +++ b/src/splat/util/n64/find_code_length.py @@ -1,4 +1,5 @@ #! /usr/bin/env python3 +from __future__ import annotations import argparse @@ -6,7 +7,7 @@ import spimdisasm -def int_any_base(x): +def int_any_base(x: str) -> int: return int(x, 0) @@ -24,7 +25,7 @@ def int_any_base(x): ) -def run(rom_bytes, start_offset, vram, end_offset=None): +def run(rom_bytes: bytes, start_offset: int, vram: int, end_offset: int | None = None) -> int: rom_addr = start_offset last_return = rom_addr @@ -46,7 +47,7 @@ def run(rom_bytes, start_offset, vram, end_offset=None): return end -def main(): +def main() -> None: args = parser.parse_args() with open(args.rom, "rb") as f: diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index 9c174faa..c54e3843 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -108,7 +108,7 @@ def get_bss_size(self) -> int | None: @staticmethod def parse_rom_bytes( - rom_bytes, vram: int, offset: int = 0x1000, size: int = 0x60, + rom_bytes: bytes, vram: int, offset: int = 0x1000, size: int = 0x60, ) -> N64EntrypointInfo: word_list = spimdisasm.common.Utils.bytesToWords( rom_bytes, offset, offset + size, @@ -384,7 +384,7 @@ def get_country_name(self) -> str: return country_codes[self.country_code] -def swap_bytes(data): +def swap_bytes(data: bytes) -> bytes: return bytes( itertools.chain.from_iterable( struct.pack(">H", x) for (x,) in struct.iter_unpack(" bytes: rom_bytes = rom_path.read_bytes() if rom_path.suffix.lower() == ".n64": @@ -405,17 +405,17 @@ def read_rom(rom_path: Path): return rom_bytes -def get_cic(rom_bytes: bytes): +def get_cic(rom_bytes: bytes) -> CIC: ipl3_crc = zlib.crc32(rom_bytes[0x40:0x1000]) return crc_to_cic.get(ipl3_crc, unknown_cic) -def get_entry_point(program_counter: int, cic: CIC): +def get_entry_point(program_counter: int, cic: CIC) -> int: return program_counter - cic.offset -def guess_header_encoding(rom_bytes: bytes): +def guess_header_encoding(rom_bytes: bytes) -> str: header = rom_bytes[0x20:0x34] encodings = ["ASCII", "shift_jis", "euc-jp"] for encoding in encodings: @@ -430,7 +430,7 @@ def guess_header_encoding(rom_bytes: bytes): def get_info( - rom_path: Path, rom_bytes: bytes | None = None, header_encoding=None, + rom_path: Path, rom_bytes: bytes | None = None, header_encoding: str | None = None, ) -> N64Rom: if rom_bytes is None: rom_bytes = read_rom(rom_path) @@ -483,7 +483,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: ) -def get_compiler_info(rom_bytes, entry_point, print_result=True): +def get_compiler_info(rom_bytes: bytes, entry_point: int, print_result: bool = True) -> str: jumps = 0 branches = 0 @@ -507,7 +507,7 @@ def get_compiler_info(rom_bytes, entry_point, print_result=True): return compiler -def main(): +def main() -> None: rabbitizer.config.pseudos_pseudoB = True args = parser.parse_args() diff --git a/src/splat/util/psx/psxexeinfo.py b/src/splat/util/psx/psxexeinfo.py index eb880380..63c12819 100755 --- a/src/splat/util/psx/psxexeinfo.py +++ b/src/splat/util/psx/psxexeinfo.py @@ -69,7 +69,7 @@ } -def is_valid(insn) -> bool: +def is_valid(insn: rabbitizer.Instruction) -> bool: if not insn.isValid(): return insn.instrIdType.name in ("CPU_SPECIAL", "CPU_COP2") @@ -78,7 +78,7 @@ def is_valid(insn) -> bool: def try_find_text( - rom_bytes, start_offset=PAYLOAD_OFFSET, valid_threshold=32, + rom_bytes: bytes, start_offset: int = PAYLOAD_OFFSET, valid_threshold: int = 32, ) -> tuple[int, int]: start = end = 0 good_count = valid_count = 0 @@ -114,7 +114,7 @@ def try_find_text( return (start, end) -def try_get_gp(rom_bytes, start_offset, max_instructions=50) -> int: +def try_get_gp(rom_bytes: bytes, start_offset: int, max_instructions: int = 50) -> int: # $gp is set like this: # /* A7738 800B7138 0E801C3C */ lui $gp, (0x800E0000 >> 16) # /* A773C 800B713C 90409C27 */ addiu $gp, $gp, 0x4090 @@ -133,7 +133,7 @@ def try_get_gp(rom_bytes, start_offset, max_instructions=50) -> int: return gp -def read_word(exe_bytes, offset) -> int: +def read_word(exe_bytes: bytes, offset: int) -> int: return struct.unpack(" PsxExe: ) -def main(): +def main() -> None: parser = argparse.ArgumentParser(description="Gives information on PSX EXEs") parser.add_argument("exe", help="Path to an PSX EXE") From e4583f9c22e07cb02f7ed62fcbcb4f990b8add4b Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:02:29 -0600 Subject: [PATCH 08/19] Support older python without TypeAlias --- src/splat/util/log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/splat/util/log.py b/src/splat/util/log.py index fb053299..7bd0cbb9 100644 --- a/src/splat/util/log.py +++ b/src/splat/util/log.py @@ -1,13 +1,15 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, NoReturn, Optional, TextIO, TypeAlias +from typing import TYPE_CHECKING, NoReturn, Optional, TextIO from colorama import Fore, Style, init if TYPE_CHECKING: from pathlib import Path + from typing_extensions import TypeAlias + init(autoreset=True) newline = True From a1a9eb614aebac01a3ebe947d6d374478e10c5df Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:39:22 -0600 Subject: [PATCH 09/19] Revert "Run typing-related ruff linting" This reverts commit 826594c668a58ad5843261eed17eae65182f9bc1. --- .pre-commit-config.yaml | 33 ---- docs/VramClasses.md | 2 +- src/splat/__init__.py | 13 +- src/splat/__main__.py | 4 +- src/splat/disassembler/__init__.py | 8 +- src/splat/disassembler/disassembler.py | 3 +- .../disassembler/disassembler_instance.py | 5 +- .../disassembler/disassembler_section.py | 2 +- .../disassembler/spimdisasm_disassembler.py | 21 +- src/splat/platforms/__init__.py | 5 +- src/splat/platforms/ps2.py | 2 +- src/splat/scripts/__init__.py | 4 +- src/splat/scripts/capy.py | 2 +- src/splat/scripts/create_config.py | 53 +++-- src/splat/scripts/split.py | 136 ++++++------- src/splat/segtypes/__init__.py | 17 +- src/splat/segtypes/common/__init__.py | 44 ++--- src/splat/segtypes/common/asm.py | 5 +- src/splat/segtypes/common/bin.py | 16 +- src/splat/segtypes/common/bss.py | 31 +-- src/splat/segtypes/common/c.py | 105 +++++----- src/splat/segtypes/common/code.py | 84 ++++---- src/splat/segtypes/common/codesubsegment.py | 40 ++-- src/splat/segtypes/common/data.py | 48 +++-- src/splat/segtypes/common/databin.py | 7 +- src/splat/segtypes/common/eh_frame.py | 12 +- src/splat/segtypes/common/gcc_except_table.py | 15 +- src/splat/segtypes/common/group.py | 32 ++-- src/splat/segtypes/common/hasm.py | 3 +- src/splat/segtypes/common/header.py | 6 +- src/splat/segtypes/common/lib.py | 23 +-- src/splat/segtypes/common/linker_offset.py | 3 +- src/splat/segtypes/common/pad.py | 5 +- src/splat/segtypes/common/rodata.py | 65 +++---- src/splat/segtypes/common/rodatabin.py | 7 +- src/splat/segtypes/common/textbin.py | 25 ++- src/splat/segtypes/linker_entry.py | 104 +++++----- src/splat/segtypes/n64/__init__.py | 44 ++--- src/splat/segtypes/n64/ci.py | 9 +- src/splat/segtypes/n64/decompressor.py | 9 +- src/splat/segtypes/n64/gfx.py | 74 +++---- src/splat/segtypes/n64/header.py | 15 +- src/splat/segtypes/n64/img.py | 46 +++-- src/splat/segtypes/n64/palette.py | 31 ++- src/splat/segtypes/n64/vtx.py | 26 ++- src/splat/segtypes/ps2/ctor.py | 12 +- src/splat/segtypes/ps2/lit4.py | 12 +- src/splat/segtypes/ps2/lit8.py | 12 +- src/splat/segtypes/ps2/vtables.py | 12 +- src/splat/segtypes/psx/header.py | 32 ++-- src/splat/segtypes/segment.py | 181 ++++++++++-------- src/splat/util/__init__.py | 34 ++-- src/splat/util/cache_handler.py | 2 +- src/splat/util/compiler.py | 7 +- src/splat/util/conf.py | 17 +- src/splat/util/file_presets.py | 4 +- src/splat/util/n64/__init__.py | 3 +- src/splat/util/n64/find_code_length.py | 2 +- src/splat/util/n64/rominfo.py | 5 - src/splat/util/options.py | 108 +++++------ src/splat/util/palettes.py | 24 +-- src/splat/util/progress_bar.py | 3 +- src/splat/util/ps2/ps2elfinfo.py | 20 +- src/splat/util/relocs.py | 15 +- src/splat/util/statistics.py | 15 +- src/splat/util/symbols.py | 124 ++++++------ src/splat/util/vram_classes.py | 5 - test.py | 67 ++++--- 68 files changed, 961 insertions(+), 999 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index eac5c88e..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -ci: - autofix_prs: true - autoupdate_schedule: quarterly - submodules: false - skip: [badgie] - -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-ast - - id: check-yaml - - id: check-toml - - id: check-merge-conflict - - id: mixed-line-ending - - id: check-case-conflict - - id: check-added-large-files - - id: sort-simple-yaml - files: .pre-commit-config.yaml - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.0 - hooks: -## - id: ruff-format - - id: ruff-check - types: [file] - types_or: [python, pyi, toml] - args: ["--show-fixes"] -## - repo: https://github.com/adhtruong/mirrors-typos -## rev: v1.43.4 -## hooks: -## - id: typos diff --git a/docs/VramClasses.md b/docs/VramClasses.md index 840766b4..c889919c 100644 --- a/docs/VramClasses.md +++ b/docs/VramClasses.md @@ -56,4 +56,4 @@ The following properties are optional and only take effect if `ld_use_symbolic_v - `follows_classes`: A list of vram class names that this class must come after in memory. If we added `follows_classes: [apples, bananas]` to our above vram_class, this would make all `maps` segments start at the end of all `apples` and `bananas` segments. -The internal linker script symbol name that is chosen for `follows_classes` is the name of the class followed by `_CLASS_VRAM`. You can override this by also specifying `vram_symbol`. +The internal linker script symbol name that is chosen for `follows_classes` is the name of the class followed by `_CLASS_VRAM`. You can override this by also specifying `vram_symbol`. \ No newline at end of file diff --git a/src/splat/__init__.py b/src/splat/__init__.py index b8e6abc3..301b77a9 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -4,10 +4,9 @@ __version__ = "0.37.3" __author__ = "ethteck" -from . import ( - disassembler as disassembler, - platforms as platforms, - scripts as scripts, - segtypes as segtypes, - util as util, -) +from . import util as util +from . import disassembler as disassembler +from . import platforms as platforms +from . import segtypes as segtypes + +from . import scripts as scripts diff --git a/src/splat/__main__.py b/src/splat/__main__.py index f5686cea..724d5604 100644 --- a/src/splat/__main__.py +++ b/src/splat/__main__.py @@ -12,11 +12,11 @@ def splat_main() -> None: ) parser.add_argument( - "-V", "--version", action="version", version=f"%(prog)s {splat.__version__}", + "-V", "--version", action="version", version=f"%(prog)s {splat.__version__}" ) subparsers = parser.add_subparsers( - description="action", help="The CLI utility to run", required=True, + description="action", help="The CLI utility to run", required=True ) splat.scripts.split.add_subparser(subparsers) diff --git a/src/splat/disassembler/__init__.py b/src/splat/disassembler/__init__.py index ba24e78b..23dfca8d 100644 --- a/src/splat/disassembler/__init__.py +++ b/src/splat/disassembler/__init__.py @@ -1,5 +1,3 @@ -from . import ( - disassembler as disassembler, - disassembler_section as disassembler_section, - spimdisasm_disassembler as spimdisasm_disassembler, -) +from . import disassembler as disassembler +from . import spimdisasm_disassembler as spimdisasm_disassembler +from . import disassembler_section as disassembler_section diff --git a/src/splat/disassembler/disassembler.py b/src/splat/disassembler/disassembler.py index 7d5a1531..0a2a8baa 100644 --- a/src/splat/disassembler/disassembler.py +++ b/src/splat/disassembler/disassembler.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import Set class Disassembler(ABC): @@ -15,5 +16,5 @@ def check_version(self, skip_version_check: bool, splat_version: str): raise NotImplementedError("check_version") @abstractmethod - def known_types(self) -> set[str]: + def known_types(self) -> Set[str]: raise NotImplementedError("known_types") diff --git a/src/splat/disassembler/disassembler_instance.py b/src/splat/disassembler/disassembler_instance.py index f5450ed6..1745a442 100644 --- a/src/splat/disassembler/disassembler_instance.py +++ b/src/splat/disassembler/disassembler_instance.py @@ -1,7 +1,8 @@ -from ..util import options from .disassembler import Disassembler -from .null_disassembler import NullDisassembler from .spimdisasm_disassembler import SpimdisasmDisassembler +from .null_disassembler import NullDisassembler + +from ..util import options __instance: Disassembler = NullDisassembler() __initialized = False diff --git a/src/splat/disassembler/disassembler_section.py b/src/splat/disassembler/disassembler_section.py index 4fc8b5af..5c293946 100644 --- a/src/splat/disassembler/disassembler_section.py +++ b/src/splat/disassembler/disassembler_section.py @@ -9,7 +9,7 @@ class DisassemblerSection(ABC): __slots__ = () - + @abstractmethod def disassemble(self) -> str: raise NotImplementedError("disassemble") diff --git a/src/splat/disassembler/spimdisasm_disassembler.py b/src/splat/disassembler/spimdisasm_disassembler.py index 39447b48..8b6cebd1 100644 --- a/src/splat/disassembler/spimdisasm_disassembler.py +++ b/src/splat/disassembler/spimdisasm_disassembler.py @@ -1,9 +1,8 @@ - -import rabbitizer -import spimdisasm - -from ..util import compiler, log, options from . import disassembler +import spimdisasm +import rabbitizer +from ..util import log, compiler, options +from typing import Set class SpimdisasmDisassembler(disassembler.Disassembler): @@ -44,10 +43,10 @@ def configure(self) -> None: rabbitizer.config.misc_opcodeLJust = options.opts.mnemonic_ljust - 1 rabbitizer.config.regNames_gprAbiNames = rabbitizer.Abi.fromStr( - options.opts.mips_abi_gpr, + options.opts.mips_abi_gpr ) rabbitizer.config.regNames_fprAbiNames = rabbitizer.Abi.fromStr( - options.opts.mips_abi_float_regs, + options.opts.mips_abi_float_regs ) if options.opts.endianness == "big": @@ -65,7 +64,7 @@ def configure(self) -> None: status="error", ) log.error( - f"The following options are supported: {list(spimdisasm.common.compilerOptions.keys())}", + f"The following options are supported: {list(spimdisasm.common.compilerOptions.keys())}" ) spimdisasm.common.GlobalConfig.COMPILER = spimdisasm_compiler if selected_compiler == compiler.SN64: @@ -131,12 +130,12 @@ def configure(self) -> None: def check_version(self, skip_version_check: bool, splat_version: str): if not skip_version_check and spimdisasm.__version_info__ < self.SPIMDISASM_MIN: log.error( - f"splat {splat_version} requires as minimum spimdisasm {self.SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}", + f"splat {splat_version} requires as minimum spimdisasm {self.SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}" ) log.write( - f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})", + f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})" ) - def known_types(self) -> set[str]: + def known_types(self) -> Set[str]: return spimdisasm.common.gKnownTypes diff --git a/src/splat/platforms/__init__.py b/src/splat/platforms/__init__.py index 51b6a244..173970fc 100644 --- a/src/splat/platforms/__init__.py +++ b/src/splat/platforms/__init__.py @@ -1 +1,4 @@ -from . import n64 as n64, ps2 as ps2, psp as psp, psx as psx +from . import n64 as n64 +from . import ps2 as ps2 +from . import psx as psx +from . import psp as psp diff --git a/src/splat/platforms/ps2.py b/src/splat/platforms/ps2.py index 09273679..adee3bb9 100644 --- a/src/splat/platforms/ps2.py +++ b/src/splat/platforms/ps2.py @@ -1,5 +1,5 @@ -import rabbitizer import spimdisasm +import rabbitizer def init(target_bytes: bytes): diff --git a/src/splat/scripts/__init__.py b/src/splat/scripts/__init__.py index 1ef99def..14382eff 100644 --- a/src/splat/scripts/__init__.py +++ b/src/splat/scripts/__init__.py @@ -1 +1,3 @@ -from . import capy as capy, create_config as create_config, split as split +from . import capy as capy +from . import create_config as create_config +from . import split as split diff --git a/src/splat/scripts/capy.py b/src/splat/scripts/capy.py index f3627b47..a821bfd6 100644 --- a/src/splat/scripts/capy.py +++ b/src/splat/scripts/capy.py @@ -40,6 +40,6 @@ def process_arguments(args: argparse.Namespace) -> None: def add_subparser(subparser: argparse._SubParsersAction) -> None: parser = subparser.add_parser( - "capy", help=script_description, description=script_description, + "capy", help=script_description, description=script_description ) parser.set_defaults(func=process_arguments) diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index 6b31b888..eed507cd 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -1,19 +1,19 @@ #! /usr/bin/env python3 -from __future__ import annotations import argparse import hashlib +from pathlib import Path import subprocess import sys -from pathlib import Path +from typing import Optional -from ..util import conf, file_presets, log from ..util.n64 import find_code_length, rominfo -from ..util.ps2 import ps2elfinfo from ..util.psx import psxexeinfo +from ..util.ps2 import ps2elfinfo +from ..util import log, file_presets, conf -def main(file_path: Path, objcopy: str | None): +def main(file_path: Path, objcopy: Optional[str]): if not file_path.exists(): sys.exit(f"File {file_path} does not exist ({file_path.absolute()})") if file_path.is_dir(): @@ -103,7 +103,7 @@ def create_n64_config(rom_path: Path): # Start analysing after the entrypoint segment. first_section_end = find_code_length.run( - rom_bytes, 0x1000 + rom.entrypoint_info.segment_size(), rom.entry_point, + rom_bytes, 0x1000 + rom.entrypoint_info.segment_size(), rom.entry_point ) extra_message = "" @@ -195,7 +195,7 @@ def create_n64_config(rom_path: Path): # Write reloc_addrs.txt file reloc_addrs: list[str] = [] - addresses_info: list[tuple[rominfo.EntryAddressInfo | None, str]] = [ + addresses_info: list[tuple[Optional[rominfo.EntryAddressInfo], str]] = [ (rom.entrypoint_info.main_address, "main"), (rom.entrypoint_info.bss_start_address, "main_BSS_START"), (rom.entrypoint_info.bss_size, "main_BSS_SIZE"), @@ -213,10 +213,10 @@ def create_n64_config(rom_path: Path): continue reloc_addrs.append( - f"rom:0x{addr_info.rom_hi:06X} reloc:MIPS_HI16 symbol:{sym_name}", + f"rom:0x{addr_info.rom_hi:06X} reloc:MIPS_HI16 symbol:{sym_name}" ) reloc_addrs.append( - f"rom:0x{addr_info.rom_lo:06X} reloc:MIPS_LO16 symbol:{sym_name}", + f"rom:0x{addr_info.rom_lo:06X} reloc:MIPS_LO16 symbol:{sym_name}" ) reloc_addrs.append("") @@ -225,32 +225,32 @@ def create_n64_config(rom_path: Path): and not rom.entrypoint_info.stack_top.ori ): reloc_addrs.append( - '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.', + '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.' ) reloc_addrs.append( - "// It is commented out because it was not possible to infer what the start of the stack symbol is, so you'll have to figure it out by yourself.", + "// It is commented out because it was not possible to infer what the start of the stack symbol is, so you'll have to figure it out by yourself." ) reloc_addrs.append( - "// Once you have found it you can properly name it and specify the length of this stack as the addend value here.", + "// Once you have found it you can properly name it and specify the length of this stack as the addend value here." ) reloc_addrs.append( - f"// The address of the end of the stack is 0x{rom.entrypoint_info.stack_top.value:08X}.", + f"// The address of the end of the stack is 0x{rom.entrypoint_info.stack_top.value:08X}." ) reloc_addrs.append( - f"// A common size for this stack is 0x2000, so try checking for the address 0x{rom.entrypoint_info.stack_top.value - 0x2000:08X}. Note the stack may have a different size.", + f"// A common size for this stack is 0x2000, so try checking for the address 0x{rom.entrypoint_info.stack_top.value - 0x2000:08X}. Note the stack may have a different size." ) reloc_addrs.append( - f"// rom:0x{rom.entrypoint_info.stack_top.rom_hi:06X} reloc:MIPS_HI16 symbol:main_stack addend:0xXXXX", + f"// rom:0x{rom.entrypoint_info.stack_top.rom_hi:06X} reloc:MIPS_HI16 symbol:main_stack addend:0xXXXX" ) reloc_addrs.append( - f"// rom:0x{rom.entrypoint_info.stack_top.rom_lo:06X} reloc:MIPS_LO16 symbol:main_stack addend:0xXXXX", + f"// rom:0x{rom.entrypoint_info.stack_top.rom_lo:06X} reloc:MIPS_LO16 symbol:main_stack addend:0xXXXX" ) reloc_addrs.append("") if reloc_addrs: with Path("reloc_addrs.txt").open("w", newline="\n") as f: print("Writing reloc_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Advanced-Reloc for documentation about this file\n", + "// Visit https://github.com/ethteck/splat/wiki/Advanced-Reloc for documentation about this file\n" ) f.write("// entrypoint relocs\n") contents = "\n".join(reloc_addrs) @@ -261,14 +261,14 @@ def create_n64_config(rom_path: Path): symbol_addrs.append(f"entrypoint = 0x{rom.entry_point:08X}; // type:func") if rom.entrypoint_info.main_address is not None: symbol_addrs.append( - f"main = 0x{rom.entrypoint_info.main_address.value:08X}; // type:func", + f"main = 0x{rom.entrypoint_info.main_address.value:08X}; // type:func" ) if symbol_addrs: symbol_addrs.append("") with Path("symbol_addrs.txt").open("w", newline="\n") as f: print("Writing symbol_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n", + "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n" ) contents = "\n".join(symbol_addrs) f.write(contents) @@ -374,7 +374,7 @@ def create_psx_config(exe_path: Path, exe_bytes: bytes): file_presets.write_all_files() -def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): +def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]): elf = ps2elfinfo.Ps2Elf.get_info(elf_path, elf_bytes) if elf is None: log.error(f"Unsupported elf file '{elf_path}'") @@ -496,7 +496,7 @@ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): with Path("symbol_addrs.txt").open("w", newline="\n") as f: print("Writing symbol_addrs.txt") f.write( - "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n", + "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n" ) contents = "\n".join(symbol_addrs) f.write(contents) @@ -509,21 +509,21 @@ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): with Path("linker_script_extra.ld").open("w", newline="\n") as f: print("Writing linker_script_extra.ld") f.write( - "/* Pass this file to the linker with the `-T linker_script_extra.ld` flag */\n", + "/* Pass this file to the linker with the `-T linker_script_extra.ld` flag */\n" ) contents = "\n".join(linker_script) f.write(contents) print() print( - "The generated yaml does not use the actual ELF file as input, but instead it", + "The generated yaml does not use the actual ELF file as input, but instead it" ) print( - 'uses a "rom" generated from said ELF, which contains the game code without any', + 'uses a "rom" generated from said ELF, which contains the game code without any' ) print("of the elf metadata.") print( - 'Use the following command to generate this "rom". It is recommended to include', + 'Use the following command to generate this "rom". It is recommended to include' ) print("this command into your setup/configure script.") print("```") @@ -550,7 +550,6 @@ def find_objcopy() -> str: msg += f" - {name}\n" msg += "\nTry to install one of those or use the `--objcopy` flag to pass the name to your own objcopy to me." log.error(msg) - return None def run_objcopy(objcopy_name: str, elf_path: str, rom: str) -> list[str]: @@ -584,7 +583,7 @@ def process_arguments(args: argparse.Namespace): def add_subparser(subparser: argparse._SubParsersAction): parser = subparser.add_parser( - "create_config", help=script_description, description=script_description, + "create_config", help=script_description, description=script_description ) add_arguments_to_parser(parser) parser.set_defaults(func=process_arguments) diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index fed888b4..d2a1af1c 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -1,55 +1,45 @@ #! /usr/bin/env python3 -from __future__ import annotations import argparse import hashlib import importlib -import sys -from collections import defaultdict, deque +from typing import Any, Dict, List, Optional, Set, Tuple, Union from pathlib import Path -from typing import Any -from colorama import Style -from intervaltree import Interval, IntervalTree +from collections import defaultdict, deque from .. import __package_name__, __version__ from ..disassembler import disassembler_instance -from ..segtypes.common.group import CommonSegGroup +from ..util import cache_handler, progress_bar, vram_classes, statistics, file_presets + +from colorama import Style +from intervaltree import Interval, IntervalTree +import sys + from ..segtypes.linker_entry import ( LinkerWriter, get_segment_vram_end_symbol_name, ) from ..segtypes.segment import Segment -from ..util import ( - cache_handler, - conf, - file_presets, - log, - options, - palettes, - progress_bar, - relocs, - statistics, - symbols, - vram_classes, -) +from ..segtypes.common.group import CommonSegGroup +from ..util import conf, log, options, palettes, symbols, relocs linker_writer: LinkerWriter -config: dict[str, Any] +config: Dict[str, Any] segment_roms: IntervalTree = IntervalTree() segment_rams: IntervalTree = IntervalTree() -def initialize_segments(config_segments: dict | list) -> list[Segment]: +def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: global segment_roms global segment_rams segment_roms = IntervalTree() segment_rams = IntervalTree() - segments_by_name: dict[str, Segment] = {} - ret: list[Segment] = [] + segments_by_name: Dict[str, Segment] = {} + ret: List[Segment] = [] # Cross segment pairing can be quite expensive, so we try to avoid it if the user haven't requested it. do_cross_segment_pairing = False @@ -65,19 +55,19 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: segment_class = Segment.get_class_for_type(seg_type) - this_start, _is_auto_segment = Segment.parse_segment_start(seg_yaml) + this_start, is_auto_segment = Segment.parse_segment_start(seg_yaml) j = i + 1 while j < len(config_segments): - next_start, _next_is_auto_segment = Segment.parse_segment_start( - config_segments[j], + next_start, next_is_auto_segment = Segment.parse_segment_start( + config_segments[j] ) if next_start is not None: break j += 1 if next_start is None: log.error( - "Next segment address could not be found. Segments list must end with a rom end pos marker ([0x10000000])", + "Next segment address could not be found. Segments list must end with a rom end pos marker ([0x10000000])" ) if segment_class.is_noload(): @@ -87,7 +77,7 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: next_start = last_rom_end segment: Segment = Segment.from_yaml( - segment_class, seg_yaml, this_start, next_start, None, + segment_class, seg_yaml, this_start, next_start, None ) if segment.require_unique_name: @@ -120,15 +110,15 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: if segment.given_follows_vram: if segment.given_follows_vram not in segments_by_name: log.error( - f"segment '{segment.given_follows_vram}', the 'follows_vram' value for segment '{segment.name}', does not exist", + f"segment '{segment.given_follows_vram}', the 'follows_vram' value for segment '{segment.name}', does not exist" ) segment.given_vram_symbol = get_segment_vram_end_symbol_name( - segments_by_name[segment.given_follows_vram], + segments_by_name[segment.given_follows_vram] ) if ret[-1].type == "pad": log.error( - "Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad", + "Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad" ) if do_cross_segment_pairing: @@ -140,13 +130,13 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: if seg == other_seg: continue if seg.pair_segment_name == other_seg.name and isinstance( - other_seg, CommonSegGroup, + other_seg, CommonSegGroup ): # We found the other segment specified by `pair_segment` if other_seg.pair_segment_name is not None: log.error( - f"Both segments '{seg.name}' and '{other_seg.name}' have a `pair_segment` value, when only at most one of the cross-paired segments can have this attribute.", + f"Both segments '{seg.name}' and '{other_seg.name}' have a `pair_segment` value, when only at most one of the cross-paired segments can have this attribute." ) # Not user error, hopefully... @@ -163,7 +153,7 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: other_seg.paired_segment = seg if isinstance(seg, CommonSegGroup) and isinstance( - other_seg, CommonSegGroup, + other_seg, CommonSegGroup ): # Pair the subsegments seg.pair_subsegments_to_other_segment(other_seg) @@ -172,7 +162,7 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: if not found: log.error( - f"Segment '{seg.pair_segment_name}' not found.\n It is referenced by segment '{seg.name}', because of the `pair_segment` attribute.", + f"Segment '{seg.pair_segment_name}' not found.\n It is referenced by segment '{seg.name}', because of the `pair_segment` attribute." ) return ret @@ -184,7 +174,7 @@ def assign_symbols_to_segments() -> None: continue if symbol.rom: - cands: set[Interval] = segment_roms[symbol.rom] + cands: Set[Interval] = segment_roms[symbol.rom] if len(cands) > 1: log.error("multiple segments rom overlap symbol", symbol) elif len(cands) == 0: @@ -195,7 +185,7 @@ def assign_symbols_to_segments() -> None: seg.add_symbol(symbol) else: cands = segment_rams[symbol.vram_start] - segs: list[Segment] = [cand.data for cand in cands] + segs: List[Segment] = [cand.data for cand in cands] for seg in segs: if not seg.get_exclusive_ram_id(): seg.add_symbol(symbol) @@ -210,10 +200,10 @@ def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str: # Return a mapping of vram classes to segments that need to be part of their vram symbol's calculation def calc_segment_dependences( - all_segments: list[Segment], -) -> dict[vram_classes.VramClass, list[Segment]]: + all_segments: List[Segment], +) -> Dict[vram_classes.VramClass, List[Segment]]: # Map vram class names to segments that have that vram class - vram_class_to_segments: dict[str, list[Segment]] = {} + vram_class_to_segments: Dict[str, List[Segment]] = {} for seg in all_segments: if seg.vram_class is not None: if seg.vram_class.name not in vram_class_to_segments: @@ -221,7 +211,7 @@ def calc_segment_dependences( vram_class_to_segments[seg.vram_class.name].append(seg) # Map vram class names to segments that the vram class follows - vram_class_to_follows_segments: dict[vram_classes.VramClass, list[Segment]] = {} + vram_class_to_follows_segments: Dict[vram_classes.VramClass, List[Segment]] = {} for vram_class in vram_classes._vram_classes.values(): if vram_class.follows_classes: vram_class_to_follows_segments[vram_class] = [] @@ -235,16 +225,16 @@ def calc_segment_dependences( def sort_segments_by_vram_class_dependency( - all_segments: list[Segment], -) -> list[Segment]: + all_segments: List[Segment], +) -> List[Segment]: # map all "_VRAM_END" strings to segments - end_sym_to_seg: dict[str, Segment] = {} + end_sym_to_seg: Dict[str, Segment] = {} for seg in all_segments: end_sym_to_seg[get_segment_vram_end_symbol_name(seg)] = seg # build dependency graph: A -> B means "A must come before B" - graph: dict[Segment, list[Segment]] = defaultdict(list) - indeg: dict[Segment, int] = {seg: 0 for seg in all_segments} + graph: Dict[Segment, List[Segment]] = defaultdict(list) + indeg: Dict[Segment, int] = {seg: 0 for seg in all_segments} for seg in all_segments: sym = seg.vram_symbol @@ -258,7 +248,7 @@ def sort_segments_by_vram_class_dependency( # stable topo sort with queue seeded in original order q = deque([seg for seg in all_segments if indeg[seg] == 0]) - out: list[Segment] = [] + out: List[Segment] = [] while q: n = q.popleft() @@ -291,7 +281,7 @@ def read_target_binary() -> bytes: def initialize_platform(rom_bytes: bytes): platform_module = importlib.import_module( - f"{__package_name__}.platforms.{options.opts.platform}", + f"{__package_name__}.platforms.{options.opts.platform}" ) platform_init = getattr(platform_module, "init") platform_init(rom_bytes) @@ -299,7 +289,7 @@ def initialize_platform(rom_bytes: bytes): return platform_module -def initialize_all_symbols(all_segments: list[Segment]): +def initialize_all_symbols(all_segments: List[Segment]): # Load and process symbols symbols.initialize(all_segments) relocs.initialize() @@ -313,12 +303,12 @@ def initialize_all_symbols(all_segments: list[Segment]): def do_scan( - all_segments: list[Segment], + all_segments: List[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, ): - processed_segments: list[Segment] = [] + processed_segments: List[Segment] = [] scan_bar = progress_bar.get_progress_bar(all_segments) for segment in scan_bar: @@ -346,7 +336,7 @@ def do_scan( def do_split( - all_segments: list[Segment], + all_segments: List[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, @@ -369,14 +359,14 @@ def do_split( segment.split(segment_bytes) -def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: +def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: if options.opts.ld_sort_segments_by_vram_class_dependency: all_segments = sort_segments_by_vram_class_dependency(all_segments) vram_class_dependencies = calc_segment_dependences(all_segments) vram_classes_to_search = set(vram_class_dependencies.keys()) - max_vram_end_insertion_points: dict[Segment, list[tuple[str, list[Segment]]]] = {} + max_vram_end_insertion_points: Dict[Segment, List[Tuple[str, List[Segment]]]] = {} for seg in reversed(all_segments): if seg.vram_class in vram_classes_to_search: assert seg.vram_class.vram_symbol is not None @@ -386,7 +376,7 @@ def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: ( seg.vram_class.vram_symbol, vram_class_dependencies[seg.vram_class], - ), + ) ) vram_classes_to_search.remove(seg.vram_class) @@ -400,11 +390,11 @@ def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: if partial_linking: if partial_scripts_path is None: log.error( - "Partial linking is enabled but `ld_partial_scripts_path` has not been set", + "Partial linking is enabled but `ld_partial_scripts_path` has not been set" ) if options.opts.ld_partial_build_segments_path is None: log.error( - "Partial linking is enabled but `ld_partial_build_segments_path` has not been set", + "Partial linking is enabled but `ld_partial_build_segments_path` has not been set" ) for segment in linker_bar: @@ -425,7 +415,7 @@ def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: seg_name = segment.get_cname() sub_linker_writer.save_linker_script( - partial_scripts_path / f"{seg_name}.ld", + partial_scripts_path / f"{seg_name}.ld" ) if options.opts.ld_dependencies: sub_linker_writer.save_dependencies_file( @@ -442,10 +432,10 @@ def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: elf_path = options.opts.elf_path if elf_path is None: log.error( - "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options", + "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options" ) linker_writer.save_dependencies_file( - options.opts.ld_script_path.with_suffix(".d"), elf_path, + options.opts.ld_script_path.with_suffix(".d"), elf_path ) return linker_writer @@ -456,14 +446,14 @@ def write_ld_dependencies(linker_writer: LinkerWriter): elf_path = options.opts.elf_path if elf_path is None: log.error( - "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options", + "Generation of dependency file for linker script requested but `elf_path` was not provided in the yaml options" ) linker_writer.save_dependencies_file( - options.opts.ld_script_path.with_suffix(".d"), elf_path, + options.opts.ld_script_path.with_suffix(".d"), elf_path ) -def write_elf_sections_file(all_segments: list[Segment]): +def write_elf_sections_file(all_segments: List[Segment]): # write elf_sections.txt - this only lists the generated sections in the elf, not subsections # that the elf combines into one section if options.opts.elf_section_list_path: @@ -475,7 +465,7 @@ def write_elf_sections_file(all_segments: list[Segment]): f.write(section_list) -def write_undefined_auto(to_write: list[symbols.Symbol], file_path: Path): +def write_undefined_auto(to_write: List[symbols.Symbol], file_path: Path): file_path.parent.mkdir(parents=True, exist_ok=True) with file_path.open("w", newline="\n") as f: for symbol in to_write: @@ -508,11 +498,11 @@ def write_undefined_syms_auto(): write_undefined_auto(to_write, options.opts.undefined_syms_auto_path) -def print_segment_warnings(all_segments: list[Segment]): +def print_segment_warnings(all_segments: List[Segment]): for segment in all_segments: if len(segment.warnings) > 0: log.write( - f"{Style.DIM}0x{segment.rom_start:06X}{Style.RESET_ALL} {segment.type} {Style.BRIGHT}{segment.name}{Style.RESET_ALL}:", + f"{Style.DIM}0x{segment.rom_start:06X}{Style.RESET_ALL} {segment.type} {Style.BRIGHT}{segment.name}{Style.RESET_ALL}:" ) for warn in segment.warnings: @@ -530,7 +520,7 @@ def dump_symbols() -> None: with open(splat_hidden_folder / "splat_symbols.csv", "w") as f: f.write( - "vram_start,given_name,name,type,given_size,size,rom,defined,user_declared,referenced,extract\n", + "vram_start,given_name,name,type,given_size,size,rom,defined,user_declared,referenced,extract\n" ) for s in sorted(symbols.all_symbols, key=lambda x: x.vram_start): f.write(f"{s.vram_start:X},{s.given_name},{s.name},{s.type},") @@ -549,8 +539,8 @@ def dump_symbols() -> None: def main( - config_path: list[Path], - modes: list[str] | None, + config_path: List[Path], + modes: Optional[List[str]], verbose: bool, use_cache: bool = True, skip_version_check: bool = False, @@ -602,7 +592,7 @@ def main( do_split(all_segments, rom_bytes, stats, cache) if options.opts.is_mode_active( - "ld", + "ld" ): # TODO move this to platform initialization when it gets implemented global linker_writer linker_writer = write_linker_script(all_segments) @@ -640,7 +630,7 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): parser.add_argument("--modes", nargs="+", default=["all"]) parser.add_argument("--verbose", action="store_true", help="Enable debug logging") parser.add_argument( - "--use-cache", action="store_true", help="Only split changed segments in config", + "--use-cache", action="store_true", help="Only split changed segments in config" ) parser.add_argument( "--skip-version-check", @@ -682,7 +672,7 @@ def process_arguments(args: argparse.Namespace): def add_subparser(subparser: argparse._SubParsersAction): parser = subparser.add_parser( - "split", help=script_description, description=script_description, + "split", help=script_description, description=script_description ) add_arguments_to_parser(parser) parser.set_defaults(func=process_arguments) diff --git a/src/splat/segtypes/__init__.py b/src/splat/segtypes/__init__.py index a366fd79..8204998a 100644 --- a/src/splat/segtypes/__init__.py +++ b/src/splat/segtypes/__init__.py @@ -1,9 +1,8 @@ -from . import ( - common as common, - linker_entry as linker_entry, - n64 as n64, - ps2 as ps2, - psp as psp, - psx as psx, - segment as segment, -) +from . import linker_entry as linker_entry +from . import segment as segment + +from . import common as common +from . import n64 as n64 +from . import ps2 as ps2 +from . import psx as psx +from . import psp as psp diff --git a/src/splat/segtypes/common/__init__.py b/src/splat/segtypes/common/__init__.py index 22c1541d..bf1ad25c 100644 --- a/src/splat/segtypes/common/__init__.py +++ b/src/splat/segtypes/common/__init__.py @@ -1,23 +1,21 @@ -from . import ( - asm as asm, - bin as bin, # noqa: A004 # Import `bin` is shadowing a Python builtin - bss as bss, - c as c, - code as code, - codesubsegment as codesubsegment, - cpp as cpp, - data as data, - databin as databin, - group as group, - hasm as hasm, - header as header, - lib as lib, - linker_offset as linker_offset, - rdata as rdata, - rodata as rodata, - rodatabin as rodatabin, - sbss as sbss, - sdata as sdata, - segment as segment, - textbin as textbin, -) +from . import asm as asm +from . import bin as bin +from . import bss as bss +from . import code as code +from . import codesubsegment as codesubsegment +from . import cpp as cpp +from . import c as c +from . import databin as databin +from . import data as data +from . import group as group +from . import hasm as hasm +from . import header as header +from . import lib as lib +from . import linker_offset as linker_offset +from . import rdata as rdata +from . import rodatabin as rodatabin +from . import rodata as rodata +from . import sbss as sbss +from . import sdata as sdata +from . import segment as segment +from . import textbin as textbin diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index 6f6cd018..c1e5f9cc 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,4 +1,5 @@ -from __future__ import annotations +from typing import Optional + from .codesubsegment import CommonSegCodeSubsegment @@ -8,7 +9,7 @@ class CommonSegAsm(CommonSegCodeSubsegment): def is_text() -> bool: return True - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "ax" def scan(self, rom_bytes: bytes): diff --git a/src/splat/segtypes/common/bin.py b/src/splat/segtypes/common/bin.py index fa87ed68..7c4b80d9 100644 --- a/src/splat/segtypes/common/bin.py +++ b/src/splat/segtypes/common/bin.py @@ -1,14 +1,10 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from pathlib import Path +from typing import Optional from ...util import log, options -from .segment import CommonSegment -if TYPE_CHECKING: - from pathlib import Path - - from ..segment import SegmentType +from .segment import CommonSegment +from ..segment import SegmentType class CommonSegBin(CommonSegment): @@ -16,7 +12,7 @@ class CommonSegBin(CommonSegment): def is_data() -> bool: return True - def out_path(self) -> Path | None: + def out_path(self) -> Optional[Path]: return options.opts.asset_path / self.dir / f"{self.name}.bin" def split(self, rom_bytes): @@ -26,7 +22,7 @@ def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) if self.size is None or self.size <= 0: diff --git a/src/splat/segtypes/common/bss.py b/src/splat/segtypes/common/bss.py index 6c053188..5a9adbc9 100644 --- a/src/splat/segtypes/common/bss.py +++ b/src/splat/segtypes/common/bss.py @@ -1,12 +1,11 @@ -from __future__ import annotations +from typing import Optional + +from ...util import options, symbols, log -from ...disassembler.disassembler_section import ( - DisassemblerSection, - make_bss_section, -) -from ...util import log, options, symbols from .data import CommonSegData +from ...disassembler.disassembler_section import DisassemblerSection, make_bss_section + # If `options.opts.ld_bss_is_noload` is False, then this segment behaves like a `CommonSegData` @@ -14,19 +13,23 @@ class CommonSegBss(CommonSegData): def get_linker_section(self) -> str: return ".bss" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "wa" @staticmethod def is_data() -> bool: - return bool(not options.opts.ld_bss_is_noload) + if not options.opts.ld_bss_is_noload: + return True + return False @staticmethod def is_noload() -> bool: - return options.opts.ld_bss_is_noload + if not options.opts.ld_bss_is_noload: + return False + return True def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" @@ -42,7 +45,7 @@ def disassemble_data(self, rom_bytes: bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" ) # Supposedly logic error, not user error @@ -54,11 +57,11 @@ def disassemble_data(self, rom_bytes: bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) next_subsegment = self.parent.get_next_subsegment_for_ram( - self.vram_start, self.index_within_group, + self.vram_start, self.index_within_group ) if next_subsegment is None: bss_end = self.get_most_parent().vram_end @@ -85,7 +88,7 @@ def disassemble_data(self, rom_bytes: bytes): for spim_sym in self.spim_section.get_section().symbolList: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), spim_sym.contextSym, force_in_segment=True, + self.get_most_parent(), spim_sym.contextSym, force_in_segment=True ) def should_scan(self) -> bool: diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index dacee761..77990eb4 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -1,20 +1,18 @@ -from __future__ import annotations - import os import re from pathlib import Path -from typing import TYPE_CHECKING +from typing import Optional, Set, List import rabbitizer import spimdisasm from ...util import log, options, symbols from ...util.compiler import IDO +from ...util.symbols import Symbol + from .codesubsegment import CommonSegCodeSubsegment from .rodata import CommonSegRodata -if TYPE_CHECKING: - from ...util.symbols import Symbol STRIP_C_COMMENTS_RE = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', @@ -22,7 +20,7 @@ ) C_FUNC_RE = re.compile( - r"^(?:static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+?{", re.MULTILINE, + r"^(?:static\s+)?[^\s]+\s+([^\s(]+)\(([^;)]*)\)[^;]+?{", re.MULTILINE ) C_GLOBAL_ASM_IDO_RE = re.compile(r"GLOBAL_ASM\(\"(\w+\/)*(\w+)\.s\"\)", re.MULTILINE) @@ -32,9 +30,9 @@ class CommonSegC(CommonSegCodeSubsegment): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.defined_funcs: set[str] = set() - self.global_asm_funcs: set[str] = set() - self.global_asm_rodata_syms: set[str] = set() + self.defined_funcs: Set[str] = set() + self.global_asm_funcs: Set[str] = set() + self.global_asm_rodata_syms: Set[str] = set() self.file_extension = "c" @@ -46,13 +44,14 @@ def replacer(match): s = match.group(0) if s.startswith("/"): return " " - return s + else: + return s return re.sub(STRIP_C_COMMENTS_RE, replacer, text) @staticmethod - def get_funcs_defined_in_c(c_file: Path) -> set[str]: - with open(c_file, encoding="utf-8") as f: + def get_funcs_defined_in_c(c_file: Path) -> Set[str]: + with open(c_file, "r", encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) return set(m.group(1) for m in C_FUNC_RE.finditer(text)) @@ -77,14 +76,15 @@ def get_close_parenthesis(string: str, pos: int): elif cur_char == ")": if paren_count == 0: return pos + 1 - paren_count -= 1 + else: + paren_count -= 1 pos += 1 @staticmethod def find_include_macro(text: str, macro_name: str): for pos in CommonSegC.find_all_instances(text, f"{macro_name}("): close_paren_pos = CommonSegC.get_close_parenthesis( - text, pos + len(f"{macro_name}("), + text, pos + len(f"{macro_name}(") ) macro_contents = text[pos:close_paren_pos] macro_args = macro_contents.split(",") @@ -104,29 +104,31 @@ def find_include_rodata(text: str): return CommonSegC.find_include_macro(text, "INCLUDE_RODATA") @staticmethod - def get_global_asm_funcs(c_file: Path) -> set[str]: + def get_global_asm_funcs(c_file: Path) -> Set[str]: with c_file.open(encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - return set(CommonSegC.find_include_asm(text)) + else: + return set(CommonSegC.find_include_asm(text)) @staticmethod - def get_global_asm_rodata_syms(c_file: Path) -> set[str]: + def get_global_asm_rodata_syms(c_file: Path) -> Set[str]: with c_file.open(encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - return set(CommonSegC.find_include_rodata(text)) + else: + return set(CommonSegC.find_include_rodata(text)) @staticmethod def is_text() -> bool: return True - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "ax" - def out_path(self) -> Path | None: + def out_path(self) -> Optional[Path]: return options.opts.src_path / self.dir / f"{self.name}.{self.file_extension}" def scan(self, rom_bytes: bytes): @@ -136,14 +138,15 @@ def scan(self, rom_bytes: bytes): and self.rom_start != self.rom_end ): path = self.out_path() - if path and options.opts.do_c_func_detection and path.exists(): - # TODO run cpp? - self.defined_funcs = self.get_funcs_defined_in_c(path) - self.global_asm_funcs = self.get_global_asm_funcs(path) - self.global_asm_rodata_syms = self.get_global_asm_rodata_syms(path) - symbols.to_mark_as_defined.update(self.defined_funcs) - symbols.to_mark_as_defined.update(self.global_asm_funcs) - symbols.to_mark_as_defined.update(self.global_asm_rodata_syms) + if path: + if options.opts.do_c_func_detection and path.exists(): + # TODO run cpp? + self.defined_funcs = self.get_funcs_defined_in_c(path) + self.global_asm_funcs = self.get_global_asm_funcs(path) + self.global_asm_rodata_syms = self.get_global_asm_rodata_syms(path) + symbols.to_mark_as_defined.update(self.defined_funcs) + symbols.to_mark_as_defined.update(self.global_asm_funcs) + symbols.to_mark_as_defined.update(self.global_asm_rodata_syms) self.scan_code(rom_bytes) @@ -160,12 +163,12 @@ def split(self, rom_bytes: bytes): self.print_file_boundaries() assert self.spim_section is not None and isinstance( - self.spim_section.get_section(), spimdisasm.mips.sections.SectionText, + self.spim_section.get_section(), spimdisasm.mips.sections.SectionText ), f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}" # We want to know if this C section has a corresponding rodata section so we can migrate its rodata rodata_section_type = "" - rodata_spim_segment: spimdisasm.mips.sections.SectionRodata | None = None + rodata_spim_segment: Optional[spimdisasm.mips.sections.SectionRodata] = None if options.opts.migrate_rodata_to_functions: # We don't know if the rodata section is .rodata or .rdata, so we need to check both for sect in [".rodata", ".rdata"]: @@ -190,10 +193,10 @@ def split(self, rom_bytes: bytes): status="warn", ) log.write( - f"\t The `{rodata_sibling.type}` section was not prefixed with a dot, which is required for the rodata migration feature to work properly and avoid build errors due to duplicated symbols at link-time.", + f"\t The `{rodata_sibling.type}` section was not prefixed with a dot, which is required for the rodata migration feature to work properly and avoid build errors due to duplicated symbols at link-time." ) log.error( - f"\t To fix this, please prefix the section type with a dot (like `.{rodata_sibling.type}`).", + f"\t To fix this, please prefix the section type with a dot (like `.{rodata_sibling.type}`)." ) rodata_section_type = ( @@ -213,7 +216,7 @@ def split(self, rom_bytes: bytes): # Precompute function-rodata pairings symbols_entries = ( spimdisasm.mips.FunctionRodataEntry.getAllEntriesFromSections( - self.spim_section.get_section(), rodata_spim_segment, + self.spim_section.get_section(), rodata_spim_segment ) ) @@ -227,7 +230,7 @@ def split(self, rom_bytes: bytes): is_new_c_file = True self.create_asm_dependencies_file( - c_path, asm_out_dir, is_new_c_file, symbols_entries, + c_path, asm_out_dir, is_new_c_file, symbols_entries ) # Produce the asm files for functions @@ -255,7 +258,7 @@ def split(self, rom_bytes: bytes): and not is_new_c_file ): self.create_c_asm_file( - entry, matching_asm_out_dir, func_sym, + entry, matching_asm_out_dir, func_sym ) else: self.create_c_asm_file(entry, asm_out_dir, func_sym) @@ -269,12 +272,12 @@ def split(self, rom_bytes: bytes): or options.opts.disassemble_all ): rodata_sym = self.get_symbol( - spim_rodata_sym.vram, in_segment=True, local_only=True, + spim_rodata_sym.vram, in_segment=True, local_only=True ) assert rodata_sym is not None self.create_unmigrated_rodata_file( - spim_rodata_sym, asm_out_dir, rodata_sym, + spim_rodata_sym, asm_out_dir, rodata_sym ) if options.opts.make_full_disasm_for_code: @@ -314,7 +317,7 @@ def get_c_preamble(self): def check_gaps_in_migrated_rodata( self, func: spimdisasm.mips.symbols.SymbolFunction, - rodata_list: list[spimdisasm.mips.symbols.SymbolBase], + rodata_list: List[spimdisasm.mips.symbols.SymbolBase], ): for index in range(len(rodata_list) - 1): rodata_sym = rodata_list[index] @@ -322,16 +325,16 @@ def check_gaps_in_migrated_rodata( if rodata_sym.vramEnd != next_rodata_sym.vram: log.write( - "\nA gap was detected in migrated rodata symbols!", status="warn", + "\nA gap was detected in migrated rodata symbols!", status="warn" ) log.write( - f"\t In function '{func.getNameUnquoted()}' (0x{func.vram:08X}), gap detected between '{rodata_sym.getNameUnquoted()}' (0x{rodata_sym.vram:08X}) and '{next_rodata_sym.getNameUnquoted()}' (0x{next_rodata_sym.vram:08X})", + f"\t In function '{func.getNameUnquoted()}' (0x{func.vram:08X}), gap detected between '{rodata_sym.getNameUnquoted()}' (0x{rodata_sym.vram:08X}) and '{next_rodata_sym.getNameUnquoted()}' (0x{next_rodata_sym.vram:08X})" ) log.write( - f"\t The address of the missing rodata symbol is 0x{rodata_sym.vramEnd:08X}", + f"\t The address of the missing rodata symbol is 0x{rodata_sym.vramEnd:08X}" ) log.write( - "\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force_not_migration:True`", + "\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force_not_migration:True`" ) def create_c_asm_file( @@ -351,7 +354,7 @@ def create_c_asm_file( with outpath.open("w", newline="\n") as f: if options.opts.asm_inc_header: f.write( - options.opts.c_newline.join(options.opts.asm_inc_header.split("\n")), + options.opts.c_newline.join(options.opts.asm_inc_header.split("\n")) ) named_registers_opt = rabbitizer.config.regNames_namedRegisters @@ -364,10 +367,10 @@ def create_c_asm_file( if func_rodata_entry.function is not None: self.check_gaps_in_migrated_rodata( - func_rodata_entry.function, func_rodata_entry.rodataSyms, + func_rodata_entry.function, func_rodata_entry.rodataSyms ) self.check_gaps_in_migrated_rodata( - func_rodata_entry.function, func_rodata_entry.lateRodataSyms, + func_rodata_entry.function, func_rodata_entry.lateRodataSyms ) self.log(f"Disassembled {func_sym.filename} to {outpath}") @@ -422,7 +425,7 @@ def get_c_lines_for_function( sym: Symbol, spim_sym: spimdisasm.mips.symbols.SymbolFunction, asm_out_dir: Path, - ) -> list[str]: + ) -> List[str]: c_lines = [] # Terrible hack to "auto-decompile" empty functions @@ -436,7 +439,7 @@ def get_c_lines_for_function( c_lines.append("}") else: c_lines.append( - self.get_c_line_include_macro(sym, asm_out_dir, "INCLUDE_ASM"), + self.get_c_line_include_macro(sym, asm_out_dir, "INCLUDE_ASM") ) c_lines.append("") return c_lines @@ -450,7 +453,7 @@ def create_c_file( self, asm_out_dir: Path, c_path: Path, - symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], + symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], ): c_lines = self.get_c_preamble() @@ -465,12 +468,12 @@ def create_c_file( assert func_sym is not None c_lines += self.get_c_lines_for_function( - func_sym, entry.function, asm_out_dir, + func_sym, entry.function, asm_out_dir ) else: for spim_rodata_sym in entry.rodataSyms: rodata_sym = self.get_symbol( - spim_rodata_sym.vram, in_segment=True, local_only=True, + spim_rodata_sym.vram, in_segment=True, local_only=True ) assert rodata_sym is not None @@ -486,7 +489,7 @@ def create_asm_dependencies_file( c_path: Path, asm_out_dir: Path, is_new_c_file: bool, - symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], + symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], ): if not options.opts.create_asm_dependencies: return diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index d025c6ee..ca556055 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -1,28 +1,30 @@ from __future__ import annotations from collections import OrderedDict +from typing import List, Optional, Type, Tuple from ...util import log, options, utils -from ..segment import Segment, parse_segment_align + from .group import CommonSegGroup +from ..segment import Segment, parse_segment_align -def dotless_type(type_: str) -> str: - return type_[1:] if type_[0] == "." else type_ +def dotless_type(type: str) -> str: + return type[1:] if type[0] == "." else type # code group class CommonSegCode(CommonSegGroup): def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, - args: list[str], + vram_start: Optional[int], + args: list, yaml, - ) -> None: + ): self.bss_size: int = yaml.get("bss_size", 0) if isinstance(yaml, dict) else 0 super().__init__( @@ -46,21 +48,22 @@ def needs_symbols(self) -> bool: return True @property - def vram_end(self) -> int | None: + def vram_end(self) -> Optional[int]: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size + self.bss_size - return None + else: + return None # Generates a placeholder segment for the auto_link_sections option def _generate_segment_from_all( self, rep_type: str, - replace_class: type[Segment], + replace_class: Type[Segment], base_name: str, base_seg: Segment, - rom_start: int | None = None, - rom_end: int | None = None, - vram_start: int | None = None, + rom_start: Optional[int] = None, + rom_end: Optional[int] = None, + vram_start: Optional[int] = None, ) -> Segment: rep: Segment = replace_class( rom_start=rom_start, @@ -89,21 +92,25 @@ def _generate_segment_from_all( def _insert_all_auto_sections( self, - ret: list[Segment], + ret: List[Segment], base_segments: OrderedDict[str, Segment], readonly_before: bool, - ) -> list[Segment]: + ) -> List[Segment]: if len(options.opts.auto_link_sections) == 0: return ret - base_segments_list: list[tuple[str, Segment]] = list(base_segments.items()) + base_segments_list: List[Tuple[str, Segment]] = list(base_segments.items()) # Determine what will be the min insertion index last_inserted_index = len(base_segments_list) - 1 for i, seg in enumerate(ret): - if seg.is_text() or (readonly_before and seg.is_rodata()): + if seg.is_text(): last_inserted_index = i + elif readonly_before: + if seg.is_rodata(): + last_inserted_index = i + for i, sect in enumerate(options.opts.auto_link_sections): for name, seg in base_segments_list: link_section = seg.get_linker_section_order() @@ -117,7 +124,7 @@ def _insert_all_auto_sections( if sibling is None: replace_class = Segment.get_class_for_type(sect) sibling = self._generate_segment_from_all( - sect, replace_class, seg.name, seg, + sect, replace_class, seg.name, seg ) seg.siblings[sect] = sibling last_inserted_index += 1 @@ -140,18 +147,18 @@ def _insert_all_auto_sections( return ret - def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: + def parse_subsegments(self, segment_yaml) -> List[Segment]: if "subsegments" not in segment_yaml: if not self.parent: raise Exception( - f"No subsegments provided in top-level code segment {self.name}", + f"No subsegments provided in top-level code segment {self.name}" ) return [] base_segments: OrderedDict[str, Segment] = OrderedDict() - ret: list[Segment] = [] - prev_start: int | None = -1 - prev_vram: int | None = -1 + ret: List[Segment] = [] + prev_start: Optional[int] = -1 + prev_vram: Optional[int] = -1 last_rom_end = None @@ -179,18 +186,20 @@ def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: if start is None: # Attempt to infer the start address - # The start address of this segment either - # - the start address of the group - # - end address of the previous segment - start = self.rom_start if i == 0 else last_rom_end + if i == 0: + # The start address of this segment is the start address of the group + start = self.rom_start + else: + # The start address is the end address of the previous segment + start = last_rom_end # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address - end: int | None = None + end: Optional[int] = None if i < len(segment_yaml["subsegments"]) - 1: - end, _end_is_auto_segment = Segment.parse_segment_start( - segment_yaml["subsegments"][i + 1], + end, end_is_auto_segment = Segment.parse_segment_start( + segment_yaml["subsegments"][i + 1] ) if start is not None and end is None: est_size = segment_class.estimate_size(subsegment_yaml) @@ -201,7 +210,7 @@ def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: if start is not None and prev_start is not None and start < prev_start: log.error( - f"Error: Group segment '{self.name}' contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})", + f"Error: Group segment '{self.name}' contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})" ) vram = None @@ -216,7 +225,7 @@ def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: end = last_rom_end segment: Segment = Segment.from_yaml( - segment_class, subsegment_yaml, start, end, self, vram, + segment_class, subsegment_yaml, start, end, self, vram ) segment.is_auto_segment = is_auto_segment @@ -227,7 +236,7 @@ def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: ): log.error( f"Error: Group segment '{self.name}' contains subsegments which are out of ascending vram order (0x{prev_vram:X} followed by 0x{segment.vram_start:X}).\n" - + f"Detected when processing file '{segment.name}' of type '{segment.type}'", + + f"Detected when processing file '{segment.name}' of type '{segment.type}'" ) ret.append(segment) @@ -235,8 +244,9 @@ def parse_subsegments(self, segment_yaml: dict) -> list[Segment]: if segment.is_text(): base_segments[segment.name] = segment - if readonly_before and segment.is_rodata() and segment.sibling is None: - base_segments[segment.name] = segment + if readonly_before: + if segment.is_rodata() and segment.sibling is None: + base_segments[segment.name] = segment if segment.special_vram_segment: self.special_vram_segment = True diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 50208ad0..97972381 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -1,20 +1,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from pathlib import Path -import rabbitizer import spimdisasm +import rabbitizer + +from ...util import options, symbols, log -from ...disassembler.disassembler_section import ( - DisassemblerSection, - make_text_section, -) -from ...util import log, options, symbols -from ..segment import Segment, SerializedSegment, parse_segment_vram from .code import CommonSegCode -if TYPE_CHECKING: - from pathlib import Path +from ..segment import Segment, parse_segment_vram, SerializedSegment + +from ...disassembler.disassembler_section import DisassemblerSection, make_text_section # abstract class for c, asm, data, etc @@ -23,7 +20,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + type: str, name: str, vram_start: int | None, args: list[str], @@ -73,7 +70,7 @@ def get_linker_section(self) -> str: return ".text" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" @@ -93,7 +90,7 @@ def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" ) # Supposedly logic error, not user error @@ -105,7 +102,7 @@ def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) self.spim_section = make_text_section( @@ -145,7 +142,7 @@ def process_insns( self.parent: CommonSegCode = self.parent symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), func_spim.contextSym, force_in_segment=False, + self.get_most_parent(), func_spim.contextSym, force_in_segment=False ) # Gather symbols found by spimdisasm and create those symbols in splat's side @@ -153,19 +150,18 @@ def process_insns( section = self.spim_section.get_section() assert section is not None context_sym = section.getSymbol( - referenced_vram, tryPlusOffset=False, + referenced_vram, tryPlusOffset=False ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False, + self.get_most_parent(), context_sym, force_in_segment=False ) # Main loop for i, insn in enumerate(func_spim.instructions): if options.opts.platform == "ps2": - from rabbitizer import TrinaryValue - from .c import CommonSegC + from rabbitizer import TrinaryValue if isinstance(self, CommonSegC): insn.flag_r5900UseDollar = TrinaryValue.FALSE @@ -184,7 +180,7 @@ def process_insns( context_sym = section.getSymbol(sym_address) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False, + self.get_most_parent(), context_sym, force_in_segment=False ) def print_file_boundaries(self) -> None: @@ -211,10 +207,10 @@ def print_file_boundaries(self) -> None: sym_addr = sym.vram print( - f"\nSegment {self.name}, symbol at vram {sym_addr:X} ends with extra nops, indicating a likely file split.", + f"\nSegment {self.name}, symbol at vram {sym_addr:X} ends with extra nops, indicating a likely file split." ) print( - "File split suggestions for this segment will follow in config yaml format:", + "File split suggestions for this segment will follow in config yaml format:" ) print(f" - [0x{self.rom_start + in_file_offset:X}, {self.type}]") diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index c13e2ce5..19f7467f 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -1,17 +1,11 @@ -from __future__ import annotations +from pathlib import Path +from typing import Optional, List +from ...util import options, symbols, log -from typing import TYPE_CHECKING - -from ...disassembler.disassembler_section import ( - DisassemblerSection, - make_data_section, -) -from ...util import log, options, symbols from .codesubsegment import CommonSegCodeSubsegment from .group import CommonSegGroup -if TYPE_CHECKING: - from pathlib import Path +from ...disassembler.disassembler_section import DisassemblerSection, make_data_section class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup): @@ -26,15 +20,17 @@ def asm_out_path(self) -> Path: return options.opts.data_path / self.dir / f"{self.name}.{typ}.s" - def out_path(self) -> Path | None: + def out_path(self) -> Optional[Path]: if self.type.startswith("."): if self.sibling: # C file return self.sibling.out_path() - # Implied C file - return options.opts.src_path / self.dir / f"{self.name}.c" - # ASM - return self.asm_out_path() + else: + # Implied C file + return options.opts.src_path / self.dir / f"{self.name}.c" + else: + # ASM + return self.asm_out_path() def scan(self, rom_bytes: bytes): CommonSegGroup.scan(self, rom_bytes) @@ -42,7 +38,7 @@ def scan(self, rom_bytes: bytes): if self.rom_start is not None and self.rom_end is not None: self.disassemble_data(rom_bytes) - def get_asm_file_extra_directives(self) -> list[str]: + def get_asm_file_extra_directives(self) -> List[str]: return [] def split(self, rom_bytes: bytes): @@ -70,14 +66,14 @@ def cache(self): def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "wa" def get_linker_entries(self): return CommonSegCodeSubsegment.get_linker_entries(self) def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" @@ -98,7 +94,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" ) # Supposedly logic error, not user error @@ -110,7 +106,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) self.spim_section = make_data_section( @@ -134,17 +130,17 @@ def disassemble_data(self, rom_bytes): for symbol in self.spim_section.get_section().symbolList: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), symbol.contextSym, force_in_segment=True, + self.get_most_parent(), symbol.contextSym, force_in_segment=True ) # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: context_sym = self.spim_section.get_section().getSymbol( - referenced_vram, tryPlusOffset=False, + referenced_vram, tryPlusOffset=False ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False, + self.get_most_parent(), context_sym, force_in_segment=False ) # Hint to the user that we are now in the .rodata section and no longer in the .data section (assuming rodata follows data) @@ -152,14 +148,14 @@ def disassemble_data(self, rom_bytes): self.suggestion_rodata_section_start and not rodata_encountered and self.get_most_parent().rodata_follows_data - and symbol.contextSym.isJumpTable() ): + if symbol.contextSym.isJumpTable(): rodata_encountered = True print( - f"\n\nData segment {self.name}, symbol at vram {symbol.contextSym.vram:X} is a jumptable, indicating the start of the rodata section _may_ be near here.", + f"\n\nData segment {self.name}, symbol at vram {symbol.contextSym.vram:X} is a jumptable, indicating the start of the rodata section _may_ be near here." ) print( - "Please note the real start of the rodata section may be way before this point.", + "Please note the real start of the rodata section may be way before this point." ) if symbol.contextSym.vromAddress is not None: print(f" - [0x{symbol.contextSym.vromAddress:X}, rodata]") diff --git a/src/splat/segtypes/common/databin.py b/src/splat/segtypes/common/databin.py index 102bc26a..25ffd257 100644 --- a/src/splat/segtypes/common/databin.py +++ b/src/splat/segtypes/common/databin.py @@ -1,6 +1,7 @@ -from __future__ import annotations +from typing import Optional from ...util import log, options + from .textbin import CommonSegTextbin @@ -16,13 +17,13 @@ def is_data() -> bool: def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "wa" def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/common/eh_frame.py b/src/splat/segtypes/common/eh_frame.py index 3422c7a3..ef1a72b9 100644 --- a/src/splat/segtypes/common/eh_frame.py +++ b/src/splat/segtypes/common/eh_frame.py @@ -1,11 +1,7 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Optional from .data import CommonSegData - -if TYPE_CHECKING: - from ...disassembler.disassembler_section import DisassemblerSection +from ...disassembler.disassembler_section import DisassemblerSection class CommonSegEh_frame(CommonSegData): @@ -14,11 +10,11 @@ class CommonSegEh_frame(CommonSegData): def get_linker_section(self) -> str: return ".eh_frame" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "aw" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/common/gcc_except_table.py b/src/splat/segtypes/common/gcc_except_table.py index 39b6127e..1ff7d4c6 100644 --- a/src/splat/segtypes/common/gcc_except_table.py +++ b/src/splat/segtypes/common/gcc_except_table.py @@ -1,11 +1,12 @@ -from __future__ import annotations +from typing import Optional + +from .data import CommonSegData +from ...util import log from ...disassembler.disassembler_section import ( DisassemblerSection, make_gcc_except_table_section, ) -from ...util import log -from .data import CommonSegData class CommonSegGcc_except_table(CommonSegData): @@ -14,11 +15,11 @@ class CommonSegGcc_except_table(CommonSegData): def get_linker_section(self) -> str: return ".gcc_except_table" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "aw" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" @@ -34,7 +35,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" ) # Supposedly logic error, not user error @@ -46,7 +47,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) self.spim_section = make_gcc_except_table_section( diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 7ca4f97e..f0b6cf9f 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -3,8 +3,9 @@ from typing import TYPE_CHECKING from ...util import log -from ..segment import Segment, SegmentStatistics, empty_statistics + from .segment import CommonSegment +from ..segment import empty_statistics, Segment, SegmentStatistics if TYPE_CHECKING: from ...util.vram_classes import SerializedSegmentData @@ -16,7 +17,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + type: str, name: str, vram_start: int | None, args: list[str], @@ -38,7 +39,7 @@ def __init__( def get_next_seg_start(self, i: int, subsegment_yamls: list[SerializedSegmentData | list[str]]) -> int | None: j = i + 1 while j < len(subsegment_yamls): - ret, _is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) + ret, is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) if ret is not None: return ret j += 1 @@ -67,18 +68,20 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st if start is None: # Attempt to infer the start address - # The start address of this segment is one of the following - # - the start address of the group - # - the end address of the previous segment - start = self.rom_start if i == 0 else last_rom_end + if i == 0: + # The start address of this segment is the start address of the group + start = self.rom_start + else: + # The start address is the end address of the previous segment + start = last_rom_end # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address end: int | None = None if i < len(yaml["subsegments"]) - 1: - end, _end_is_auto_segment = Segment.parse_segment_start( - yaml["subsegments"][i + 1], + end, end_is_auto_segment = Segment.parse_segment_start( + yaml["subsegments"][i + 1] ) if start is not None and end is None: est_size = segment_class.estimate_size(subsegment_yaml) @@ -89,7 +92,7 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st if start is not None and prev_start is not None and start < prev_start: log.error( - f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})", + f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})" ) vram = None @@ -132,7 +135,10 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st @property def needs_symbols(self) -> bool: - return any(seg.needs_symbols for seg in self.subsegments) + for seg in self.subsegments: + if seg.needs_symbols: + return True + return False @property def statistics(self) -> SegmentStatistics: @@ -180,7 +186,7 @@ def get_subsegment_for_ram(self, addr: int) -> Segment | None: return None def get_next_subsegment_for_ram( - self, addr: int, current_subseg_index: int | None, + self, addr: int, current_subseg_index: int | None ) -> Segment | None: """ Returns the first subsegment which comes after the specified address, @@ -199,7 +205,7 @@ def get_next_subsegment_for_ram( def pair_subsegments_to_other_segment( self, - other_segment: CommonSegGroup, + other_segment: "CommonSegGroup", ) -> None: # Pair cousins with the same name for segment in self.subsegments: diff --git a/src/splat/segtypes/common/hasm.py b/src/splat/segtypes/common/hasm.py index 777d466b..7966ca4a 100644 --- a/src/splat/segtypes/common/hasm.py +++ b/src/splat/segtypes/common/hasm.py @@ -1,8 +1,9 @@ from pathlib import Path -from ...util import options from .asm import CommonSegAsm +from ...util import options + class CommonSegHasm(CommonSegAsm): def asm_out_path(self) -> Path: diff --git a/src/splat/segtypes/common/header.py b/src/splat/segtypes/common/header.py index d5df1830..0bab56ad 100644 --- a/src/splat/segtypes/common/header.py +++ b/src/splat/segtypes/common/header.py @@ -1,12 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from pathlib import Path from ...util import options -from .segment import CommonSegment -if TYPE_CHECKING: - from pathlib import Path +from .segment import CommonSegment class CommonSegHeader(CommonSegment): diff --git a/src/splat/segtypes/common/lib.py b/src/splat/segtypes/common/lib.py index c295af2b..409fa282 100644 --- a/src/splat/segtypes/common/lib.py +++ b/src/splat/segtypes/common/lib.py @@ -1,25 +1,26 @@ -from __future__ import annotations - from pathlib import Path +from typing import Optional, List from ...util import log, options + from ..linker_entry import LinkerEntry, LinkerWriter -from ..segment import Segment, parse_segment_vram from .segment import CommonSegment +from ..segment import Segment, parse_segment_vram + class LinkerEntryLib(LinkerEntry): def __init__( self, segment: Segment, - src_paths: list[Path], + src_paths: List[Path], object_path: Path, section_order: str, section_link: str, noload: bool, ): super().__init__( - segment, src_paths, object_path, section_order, section_link, noload, + segment, src_paths, object_path, section_order, section_link, noload ) self.object_path = object_path @@ -30,11 +31,11 @@ def emit_entry(self, linker_writer: LinkerWriter): class CommonSegLib(CommonSegment): def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, + vram_start: Optional[int], args: list, yaml, ): @@ -72,7 +73,7 @@ def __init__( def get_linker_section(self) -> str: return self.section - def get_linker_entries(self) -> list[LinkerEntry]: + def get_linker_entries(self) -> List[LinkerEntry]: path = options.opts.lib_path / self.name object_path = Path(f"{path}.a:{self.object}.o") @@ -85,5 +86,5 @@ def get_linker_entries(self) -> list[LinkerEntry]: self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ), + ) ] diff --git a/src/splat/segtypes/common/linker_offset.py b/src/splat/segtypes/common/linker_offset.py index 7fd3d768..e941715a 100644 --- a/src/splat/segtypes/common/linker_offset.py +++ b/src/splat/segtypes/common/linker_offset.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import List from ..linker_entry import LinkerEntry, LinkerWriter from ..segment import Segment @@ -23,5 +24,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> list[LinkerEntry]: + def get_linker_entries(self) -> List[LinkerEntry]: return [LinkerEntryOffset(self)] diff --git a/src/splat/segtypes/common/pad.py b/src/splat/segtypes/common/pad.py index d0715574..c99362d9 100644 --- a/src/splat/segtypes/common/pad.py +++ b/src/splat/segtypes/common/pad.py @@ -1,8 +1,9 @@ from pathlib import Path +from typing import List +from .segment import CommonSegment from ..linker_entry import LinkerEntry, LinkerWriter from ..segment import Segment -from .segment import CommonSegment class LinkerEntryPad(LinkerEntry): @@ -24,5 +25,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> list[LinkerEntry]: + def get_linker_entries(self) -> List[LinkerEntry]: return [LinkerEntryPad(self)] diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index 291a2b0e..39baeba9 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -1,25 +1,21 @@ -from __future__ import annotations +from typing import Optional, Set, Tuple, List +import spimdisasm +from ..segment import Segment +from ...util import log, options, symbols -from typing import TYPE_CHECKING +from .data import CommonSegData from ...disassembler.disassembler_section import ( DisassemblerSection, make_rodata_section, ) -from ...util import log, options, symbols -from .data import CommonSegData - -if TYPE_CHECKING: - import spimdisasm - - from ..segment import Segment class CommonSegRodata(CommonSegData): def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "a" @staticmethod @@ -31,8 +27,8 @@ def is_rodata() -> bool: return True def get_possible_text_subsegment_for_symbol( - self, rodata_sym: spimdisasm.mips.symbols.SymbolBase, - ) -> tuple[Segment, spimdisasm.common.ContextSymbol] | None: + self, rodata_sym: spimdisasm.mips.symbols.SymbolBase + ) -> Optional[Tuple[Segment, spimdisasm.common.ContextSymbol]]: # Check if this rodata segment does not have a corresponding code file, try to look for one if self.sibling is not None or not options.opts.pair_rodata_to_text: @@ -44,7 +40,7 @@ def get_possible_text_subsegment_for_symbol( if len(rodata_sym.contextSym.referenceFunctions) != 1: return None - func = next(iter(rodata_sym.contextSym.referenceFunctions)) + func = list(rodata_sym.contextSym.referenceFunctions)[0] text_segment = self.parent.get_subsegment_for_ram(func.vram) if text_segment is None or not text_segment.is_text(): @@ -52,7 +48,7 @@ def get_possible_text_subsegment_for_symbol( return text_segment, func def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" @@ -73,7 +69,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.rom_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a rom_start. Got '{self.rom_start}'" ) # Supposedly logic error, not user error @@ -85,7 +81,7 @@ def disassemble_data(self, rom_bytes): if not isinstance(self.vram_start, int): log.error( - f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'", + f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) self.spim_section = make_rodata_section( @@ -105,25 +101,25 @@ def disassemble_data(self, rom_bytes): self.spim_section.analyze() self.spim_section.set_comment_offset(self.rom_start) - possible_text_segments: set[Segment] = set() + possible_text_segments: Set[Segment] = set() last_jumptable_addr_remainder = 0 - misaligned_jumptable_offsets: list[int] = [] + misaligned_jumptable_offsets: List[int] = [] for symbol in self.spim_section.get_section().symbolList: generated_symbol = symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), symbol.contextSym, force_in_segment=True, + self.get_most_parent(), symbol.contextSym, force_in_segment=True ) generated_symbol.linker_section = self.get_linker_section_linksection() # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: context_sym = self.spim_section.get_section().getSymbol( - referenced_vram, tryPlusOffset=False, + referenced_vram, tryPlusOffset=False ) if context_sym is not None: symbols.create_symbol_from_spim_symbol( - self.get_most_parent(), context_sym, force_in_segment=False, + self.get_most_parent(), context_sym, force_in_segment=False ) possible_text = self.get_possible_text_subsegment_for_symbol(symbol) @@ -131,30 +127,31 @@ def disassemble_data(self, rom_bytes): text_segment, refenceeFunction = possible_text if text_segment not in possible_text_segments: print( - f"\nRodata segment '{self.name}' may belong to the text segment '{text_segment.name}'", + f"\nRodata segment '{self.name}' may belong to the text segment '{text_segment.name}'" ) print( - f" Based on the usage from the function {refenceeFunction.getNameUnquoted()} to the symbol {symbol.getNameUnquoted()}", + f" Based on the usage from the function {refenceeFunction.getNameUnquoted()} to the symbol {symbol.getNameUnquoted()}" ) possible_text_segments.add(text_segment) - if options.opts.platform in ("psx", "ps2") and generated_symbol.type == "jtbl": - # GCC aligns jumptables to 8, but it doesn't impose alignment restrictions for sections themselves on PSX/PS2. - # This means a jumptable may be aligned file-wise, but it may not end up 8-aligned binary-wise. - # We can use this as a way to find file splits on PSX/PS2 - vram_diff = generated_symbol.vram_start - self.vram_start - if vram_diff % 8 != last_jumptable_addr_remainder: - # Each time the this remainder changes means there's a new file split - last_jumptable_addr_remainder = vram_diff % 8 + if options.opts.platform in ("psx", "ps2"): + if generated_symbol.type == "jtbl": + # GCC aligns jumptables to 8, but it doesn't impose alignment restrictions for sections themselves on PSX/PS2. + # This means a jumptable may be aligned file-wise, but it may not end up 8-aligned binary-wise. + # We can use this as a way to find file splits on PSX/PS2 + vram_diff = generated_symbol.vram_start - self.vram_start + if vram_diff % 8 != last_jumptable_addr_remainder: + # Each time the this remainder changes means there's a new file split + last_jumptable_addr_remainder = vram_diff % 8 - misaligned_jumptable_offsets.append(self.rom_start + vram_diff) + misaligned_jumptable_offsets.append(self.rom_start + vram_diff) if len(misaligned_jumptable_offsets) > 0: print( - f"\nThe rodata segment '{self.name}' has jumptables that are not aligned properly file-wise, indicating one or more likely file split.", + f"\nThe rodata segment '{self.name}' has jumptables that are not aligned properly file-wise, indicating one or more likely file split." ) print( - "File split suggestions for this segment will follow in config yaml format:", + "File split suggestions for this segment will follow in config yaml format:" ) for offset in misaligned_jumptable_offsets: print(f" - [0x{offset:X}, {self.type}]") diff --git a/src/splat/segtypes/common/rodatabin.py b/src/splat/segtypes/common/rodatabin.py index 58672120..f3e8575a 100644 --- a/src/splat/segtypes/common/rodatabin.py +++ b/src/splat/segtypes/common/rodatabin.py @@ -1,6 +1,7 @@ -from __future__ import annotations +from typing import Optional from ...util import log, options + from .textbin import CommonSegTextbin @@ -16,13 +17,13 @@ def is_rodata() -> bool: def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "a" def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/common/textbin.py b/src/splat/segtypes/common/textbin.py index dabbd2b1..e24eb6f7 100644 --- a/src/splat/segtypes/common/textbin.py +++ b/src/splat/segtypes/common/textbin.py @@ -1,23 +1,20 @@ -from __future__ import annotations - +from pathlib import Path import re -from typing import TYPE_CHECKING, TextIO +from typing import Optional, TextIO from ...util import log, options -from .segment import CommonSegment -if TYPE_CHECKING: - from pathlib import Path +from .segment import CommonSegment class CommonSegTextbin(CommonSegment): def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, + vram_start: Optional[int], args: list, yaml, ): @@ -31,7 +28,7 @@ def __init__( yaml=yaml, ) self.use_src_path: bool = isinstance(yaml, dict) and yaml.get( - "use_src_path", False, + "use_src_path", False ) @staticmethod @@ -41,10 +38,10 @@ def is_text() -> bool: def get_linker_section(self) -> str: return ".text" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "ax" - def out_path(self) -> Path | None: + def out_path(self) -> Optional[Path]: if self.use_src_path: return options.opts.src_path / self.dir / f"{self.name}.s" @@ -143,7 +140,7 @@ def write_asm_contents(self, rom_bytes, f: TextIO): def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) self.write_bin(rom_bytes) diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index a3559749..adf35c86 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -2,20 +2,18 @@ import os import re -from collections import OrderedDict -from functools import cache +from functools import lru_cache from pathlib import Path -from typing import TYPE_CHECKING +from typing import Dict, List, OrderedDict, Set, Tuple, Union, Optional -from ..util import log, options -from ..util.symbols import to_cname +from ..util import options, log -if TYPE_CHECKING: - from .segment import Segment +from .segment import Segment +from ..util.symbols import to_cname # clean 'foo/../bar' to 'bar' -@cache +@lru_cache(maxsize=None) def clean_up_path(path: Path) -> Path: path_resolved = path.resolve() base_resolved = options.opts.base_path.resolve() @@ -37,7 +35,10 @@ def clean_up_path(path: Path) -> Path: def path_to_object_path(path: Path) -> Path: path = clean_up_path(path) - full_suffix = ".o" if options.opts.use_o_as_suffix else path.suffix + ".o" + if options.opts.use_o_as_suffix: + full_suffix = ".o" + else: + full_suffix = path.suffix + ".o" if not str(path).startswith(str(options.opts.build_path)): path = options.opts.build_path / path @@ -45,7 +46,10 @@ def path_to_object_path(path: Path) -> Path: def write_file_if_different(path: Path, new_content: str) -> None: - old_content = path.read_text() if path.exists() else "" + if path.exists(): + old_content = path.read_text() + else: + old_content = "" if old_content != new_content: path.parent.mkdir(parents=True, exist_ok=True) @@ -119,7 +123,7 @@ class LinkerEntry: def __init__( self, segment: Segment, - src_paths: list[Path], + src_paths: List[Path], object_path: Path, section_order: str, section_link: str, @@ -131,19 +135,21 @@ def __init__( self.section_link = section_link self.noload = noload self.bss_contains_common = segment.bss_contains_common - self.object_path: Path | None = path_to_object_path(object_path) + self.object_path: Optional[Path] = path_to_object_path(object_path) @property def section_order_type(self) -> str: if self.section_order == ".rdata": return ".rodata" - return self.section_order + else: + return self.section_order @property def section_link_type(self) -> str: if self.section_link == ".rdata": return ".rodata" - return self.section_link + else: + return self.section_link def emit_symbol_for_data(self, linker_writer: LinkerWriter) -> None: if not options.opts.ld_generate_symbol_per_data_segment: @@ -164,13 +170,13 @@ def emit_path(self, linker_writer: LinkerWriter) -> None: if self.noload and self.bss_contains_common: linker_writer._write_object_path_section( - self.object_path, f"{self.section_link} COMMON .scommon", + self.object_path, f"{self.section_link} COMMON .scommon" ) else: wildcard = "*" if options.opts.ld_wildcard_sections else "" linker_writer._write_object_path_section( - self.object_path, f"{self.section_link}{wildcard}", + self.object_path, f"{self.section_link}{wildcard}" ) def emit_entry(self, linker_writer: LinkerWriter) -> None: @@ -181,14 +187,14 @@ def emit_entry(self, linker_writer: LinkerWriter) -> None: class LinkerWriter: def __init__(self, is_partial: bool = False): self.linker_discard_section: bool = options.opts.ld_discard_section - self.sections_allowlist: list[str] = options.opts.ld_sections_allowlist - self.sections_denylist: list[str] = options.opts.ld_sections_denylist + self.sections_allowlist: List[str] = options.opts.ld_sections_allowlist + self.sections_denylist: List[str] = options.opts.ld_sections_denylist # Used to store all the linker entries - build tools may want this information - self.entries: list[LinkerEntry] = [] - self.dependencies_entries: list[LinkerEntry] = [] + self.entries: List[LinkerEntry] = [] + self.dependencies_entries: List[LinkerEntry] = [] - self.buffer: list[str] = [] - self.header_symbols: set[str] = set() + self.buffer: List[str] = [] + self.header_symbols: Set[str] = set() self.is_partial: bool = is_partial @@ -210,11 +216,11 @@ def write_max_vram_end_sym(self, symbol: str, overlays: list[Segment]) -> None: for segment in overlays: if segment == overlays[0]: self._writeln( - f"{symbol} = {get_segment_vram_end_symbol_name(segment)};", + f"{symbol} = {get_segment_vram_end_symbol_name(segment)};" ) else: self._writeln( - f"{symbol} = MAX({symbol}, {get_segment_vram_end_symbol_name(segment)});", + f"{symbol} = MAX({symbol}, {get_segment_vram_end_symbol_name(segment)});" ) # Adds all the entries of a segment to the linker script buffer @@ -232,7 +238,7 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) self.add_legacy(segment, entries) return - section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -252,14 +258,14 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if entry.section_order_type in section_entries: if entry.section_order_type not in section_entries: log.error( - f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options.", + f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options." ) section_entries[entry.section_order_type].append(entry) elif prev_entry is not None: # If this section is not present in section_order or section_order then pretend it is part of the last seen section, mainly for handling linker_offset if prev_entry not in section_entries: log.error( - f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options.", + f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options." ) section_entries[prev_entry].append(entry) any_load = any_load or not entry.noload @@ -268,7 +274,7 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -279,14 +285,14 @@ def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) if any_load: # Only emit normal segment if there's at least one normal entry self._write_segment_sections( - segment, seg_name, section_entries, noload=False, is_first=is_first, + segment, seg_name, section_entries, noload=False, is_first=is_first ) is_first = False if any_noload: # Only emit NOLOAD segment if there is at least one noload entry self._write_segment_sections( - segment, seg_name, section_entries, noload=True, is_first=is_first, + segment, seg_name, section_entries, noload=True, is_first=is_first ) is_first = False @@ -296,12 +302,12 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: seg_name = segment.get_cname() # To keep track which sections has been started - started_sections: dict[str, bool] = { + started_sections: Dict[str, bool] = { section_name: False for section_name in options.opts.section_order } # Find where sections are last seen - last_seen_sections: dict[LinkerEntry, str] = {} + last_seen_sections: Dict[LinkerEntry, str] = {} for entry in reversed(entries): if ( entry.section_order_type in options.opts.section_order @@ -311,7 +317,7 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -356,7 +362,7 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: self._end_segment(segment, all_bss=False) def add_referenced_partial_segment( - self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]], + self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]] ) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) @@ -371,7 +377,7 @@ def add_referenced_partial_segment( if segment.ld_align_segment_start: self._write_symbol( - "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})", + "__romPos", f"ALIGN(__romPos, 0x{segment.ld_align_segment_start:X})" ) self._write_symbol(".", f"ALIGN(., 0x{segment.ld_align_segment_start:X})") @@ -439,7 +445,7 @@ def add_partial_segment(self, segment: Segment) -> None: seg_name = segment.get_cname() - section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -450,14 +456,14 @@ def add_partial_segment(self, segment: Segment) -> None: if entry.section_order_type in section_entries: if entry.section_order_type not in section_entries: log.error( - f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options.", + f"\nError on linker script generation: section '{entry.section_order_type}' not found.\n Make sure the section '{entry.section_order_type}' is listed on 'section_order' of the yaml options." ) section_entries[entry.section_order_type].append(entry) elif prev_entry is not None: # If this section is not present in section_order or section_order then pretend it is part of the last seen section, mainly for handling linker_offset if prev_entry not in section_entries: log.error( - f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options.", + f"\nError on linker script generation: section '{prev_entry}' not found.\n Make sure the section '{prev_entry}' is listed on 'section_order' of the yaml options." ) section_entries[prev_entry].append(entry) prev_entry = entry.section_order_type @@ -553,7 +559,7 @@ def _end_block(self) -> None: self._indent_level -= 1 self._writeln("}") - def _write_symbol(self, symbol: str, value: str | int) -> None: + def _write_symbol(self, symbol: str, value: Union[str, int]) -> None: symbol = to_cname(symbol) if isinstance(value, int): @@ -567,7 +573,7 @@ def _write_object_path_section(self, object_path: Path, section: str) -> None: self._writeln(f"{object_path.as_posix()}({section});") def _begin_segment( - self, segment: Segment, seg_name: str, noload: bool, is_first: bool, + self, segment: Segment, seg_name: str, noload: bool, is_first: bool ) -> None: if ( options.opts.ld_use_symbolic_vram_addresses @@ -613,20 +619,22 @@ def _end_segment(self, segment: Segment, all_bss: bool = False) -> None: self._writeln(f"__romPos += SIZEOF(.{name});") # Align directive - if not options.opts.segment_end_before_align and segment.align: - self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") - if options.opts.ld_align_segment_vram_end: - self._writeln(f". = ALIGN(., {segment.align});") + if not options.opts.segment_end_before_align: + if segment.align: + self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") + if options.opts.ld_align_segment_vram_end: + self._writeln(f". = ALIGN(., {segment.align});") seg_rom_end = get_segment_rom_end(name) self._write_symbol(seg_rom_end, "__romPos") self._write_symbol(get_segment_vram_end_symbol_name(segment), ".") # Align directive - if options.opts.segment_end_before_align and segment.align: - self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") - if options.opts.ld_align_segment_vram_end: - self._writeln(f". = ALIGN(., {segment.align});") + if options.opts.segment_end_before_align: + if segment.align: + self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});") + if options.opts.ld_align_segment_vram_end: + self._writeln(f". = ALIGN(., {segment.align});") self._writeln("") diff --git a/src/splat/segtypes/n64/__init__.py b/src/splat/segtypes/n64/__init__.py index 1f19298b..70eb16e2 100644 --- a/src/splat/segtypes/n64/__init__.py +++ b/src/splat/segtypes/n64/__init__.py @@ -1,23 +1,21 @@ -from . import ( - ci as ci, - ci4 as ci4, - ci8 as ci8, - decompressor as decompressor, - gfx as gfx, - header as header, - i1 as i1, - i4 as i4, - i8 as i8, - ia4 as ia4, - ia8 as ia8, - ia16 as ia16, - img as img, - ipl3 as ipl3, - mio0 as mio0, - palette as palette, - rgba16 as rgba16, - rgba32 as rgba32, - rsp as rsp, - vtx as vtx, - yay0 as yay0, -) +from . import ci as ci +from . import ci4 as ci4 +from . import ci8 as ci8 +from . import decompressor as decompressor +from . import gfx as gfx +from . import header as header +from . import i1 as i1 +from . import i4 as i4 +from . import i8 as i8 +from . import ia16 as ia16 +from . import ia4 as ia4 +from . import ia8 as ia8 +from . import img as img +from . import ipl3 as ipl3 +from . import mio0 as mio0 +from . import palette as palette +from . import rgba16 as rgba16 +from . import rgba32 as rgba32 +from . import rsp as rsp +from . import vtx as vtx +from . import yay0 as yay0 diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index a2c90edb..68e6e4da 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -1,7 +1,8 @@ from pathlib import Path -from typing import TYPE_CHECKING +from typing import List, TYPE_CHECKING from ...util import log, options + from .img import N64SegImg if TYPE_CHECKING: @@ -10,7 +11,7 @@ # Base class for CI4/CI8 class N64SegCi(N64SegImg): - def parse_palette_names(self, yaml, args) -> list[str]: + def parse_palette_names(self, yaml, args) -> List[str]: ret = [self.name] if isinstance(yaml, dict): if "palettes" in yaml: @@ -25,7 +26,7 @@ def parse_palette_names(self, yaml, args) -> list[str]: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.palettes: list[N64SegPalette] = [] + self.palettes: "List[N64SegPalette]" = [] self.palette_names = self.parse_palette_names(self.yaml, self.args) def scan(self, rom_bytes: bytes) -> None: @@ -53,7 +54,7 @@ def split(self, rom_bytes): if len(self.palettes) == 0: # TODO: output with blank palette log.error( - f"no palettes have been mapped to ci segment `{self.name}`\n(hint: add a palette segment with the same name or use the `palettes:` field of this segment to specify palettes by name')", + f"no palettes have been mapped to ci segment `{self.name}`\n(hint: add a palette segment with the same name or use the `palettes:` field of this segment to specify palettes by name')" ) assert isinstance(self.rom_start, int) diff --git a/src/splat/segtypes/n64/decompressor.py b/src/splat/segtypes/n64/decompressor.py index 2723a575..1d992212 100644 --- a/src/splat/segtypes/n64/decompressor.py +++ b/src/splat/segtypes/n64/decompressor.py @@ -1,4 +1,5 @@ from ...util import log, options + from ..segment import Segment @@ -9,7 +10,7 @@ def split(self, rom_bytes): if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) out_path = out_dir / f"{self.name}.bin" @@ -36,16 +37,16 @@ def get_linker_entries(self): self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ), + ) ] @property def compression_type(self) -> str: log.error( - f"Segment {self.__class__.__name__} needs to define a compression type", + f"Segment {self.__class__.__name__} needs to define a compression type" ) def decompress(self, compressed_bytes: bytes) -> bytes: log.error( - f"Segment {self.__class__.__name__} needs to define a decompression method", + f"Segment {self.__class__.__name__} needs to define a decompression method" ) diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index f8829b13..dbf0afd3 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -2,23 +2,18 @@ N64 f3dex display list splitter Dumps out Gfx[] as a .inc.c file. """ -from __future__ import annotations import re -from typing import TYPE_CHECKING +from typing import Dict, List, Optional, Union + +from pathlib import Path from pygfxd import ( - GfxdEndian, gfxd_buffer_to_string, gfxd_cimg_callback, gfxd_dl_callback, gfxd_endian, gfxd_execute, - gfxd_f3d, - gfxd_f3db, - gfxd_f3dex, - gfxd_f3dex2, - gfxd_f3dexb, gfxd_input_buffer, gfxd_light_callback, gfxd_lookat_callback, @@ -34,14 +29,20 @@ gfxd_vp_callback, gfxd_vtx_callback, gfxd_zimg_callback, + GfxdEndian, + gfxd_f3d, + gfxd_f3db, + gfxd_f3dex, + gfxd_f3dexb, + gfxd_f3dex2, ) -from ...util import log, options, symbols +from ...util import log, options from ...util.log import error + from ..common.codesubsegment import CommonSegCodeSubsegment -if TYPE_CHECKING: - from pathlib import Path +from ...util import symbols LIGHTS_RE = re.compile(r"\*\(Lightsn \*\)0x[0-9A-F]{8}") @@ -49,11 +50,11 @@ class N64SegGfx(CommonSegCodeSubsegment): def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, + vram_start: Optional[int], args: list, yaml, ): @@ -87,41 +88,41 @@ def get_gfxd_target(self): if opt == "f3d": return gfxd_f3d - if opt == "f3db": + elif opt == "f3db": return gfxd_f3db - if opt == "f3dex": + elif opt == "f3dex": return gfxd_f3dex - if opt == "f3dexb": + elif opt == "f3dexb": return gfxd_f3dexb - if opt == "f3dex2": + elif opt == "f3dex2": return gfxd_f3dex2 - log.error(f"Unknown target {opt}") - return None + else: + log.error(f"Unknown target {opt}") def tlut_handler(self, addr, idx, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 def timg_handler(self, addr, fmt, size, width, height, pal): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 def cimg_handler(self, addr, fmt, size, width): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 def zimg_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 @@ -132,28 +133,28 @@ def dl_handler(self, addr): if not sym: sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 def mtx_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 def lookat_handler(self, addr, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 def light_handler(self, addr, count): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 @@ -177,7 +178,7 @@ def vtx_handler(self, addr, count): def vp_handler(self, addr): sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 @@ -197,13 +198,13 @@ def disassemble_data(self, rom_bytes): segment_length = len(gfx_data) if (segment_length) % 8 != 0: error( - f"Error: gfx segment {self.name} length ({segment_length}) is not a multiple of 8!", + f"Error: gfx segment {self.name} length ({segment_length}) is not a multiple of 8!" ) out_str = "" if self.data_only else options.opts.generated_c_preamble + "\n\n" sym = self.create_symbol( - addr=self.vram_start, in_segment=True, type="data", define=True, + addr=self.vram_start, in_segment=True, type="data", define=True ) gfxd_input_buffer(gfx_data) @@ -214,7 +215,7 @@ def disassemble_data(self, rom_bytes): gfxd_target(self.get_gfxd_target()) gfxd_endian( - GfxdEndian.big if options.opts.endianness == "big" else GfxdEndian.little, 4, + GfxdEndian.big if options.opts.endianness == "big" else GfxdEndian.little, 4 ) # Callbacks @@ -249,12 +250,13 @@ def light_sub_func(match): light = match.group(0) addr = int(light[12:], 0) sym = self.create_symbol( - addr=addr, in_segment=self.in_segment, type="data", reference=True, + addr=addr, in_segment=self.in_segment, type="data", reference=True ) return self.format_sym_name(sym) - return re.sub(LIGHTS_RE, light_sub_func, out_str) + out_str = re.sub(LIGHTS_RE, light_sub_func, out_str) + return out_str def split(self, rom_bytes: bytes): if self.file_text and self.out_path(): @@ -274,7 +276,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("gfx") @staticmethod - def estimate_size(yaml: dict | list) -> int | None: + def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: if isinstance(yaml, dict) and "length" in yaml: return yaml["length"] * 0x10 return None diff --git a/src/splat/segtypes/n64/header.py b/src/splat/segtypes/n64/header.py index 0e252045..93b77adb 100644 --- a/src/splat/segtypes/n64/header.py +++ b/src/splat/segtypes/n64/header.py @@ -1,4 +1,5 @@ from ...util import options + from ..common.header import CommonSegHeader @@ -9,13 +10,13 @@ def parse_header(self, rom_bytes): header_lines = [] header_lines.append(".section .data\n") header_lines.append( - self.get_line("word", rom_bytes[0x00:0x04], "PI BSB Domain 1 register"), + self.get_line("word", rom_bytes[0x00:0x04], "PI BSB Domain 1 register") ) header_lines.append( - self.get_line("word", rom_bytes[0x04:0x08], "Clockrate setting"), + self.get_line("word", rom_bytes[0x04:0x08], "Clockrate setting") ) header_lines.append( - self.get_line("word", rom_bytes[0x08:0x0C], "Entrypoint address"), + self.get_line("word", rom_bytes[0x08:0x0C], "Entrypoint address") ) header_lines.append(self.get_line("word", rom_bytes[0x0C:0x10], "Revision")) header_lines.append(self.get_line("word", rom_bytes[0x10:0x14], "Checksum 1")) @@ -27,21 +28,21 @@ def parse_header(self, rom_bytes): header_lines.append( '.ascii "' + rom_bytes[0x20:0x34].decode(encoding).strip().ljust(20) - + '" /* Internal name */', + + '" /* Internal name */' ) else: for i in range(0x20, 0x34, 4): header_lines.append( - self.get_line("word", rom_bytes[i : i + 4], "Internal name"), + self.get_line("word", rom_bytes[i : i + 4], "Internal name") ) header_lines.append(self.get_line("word", rom_bytes[0x34:0x38], "Unknown 3")) header_lines.append(self.get_line("word", rom_bytes[0x38:0x3C], "Cartridge")) header_lines.append( - self.get_line("ascii", rom_bytes[0x3C:0x3E], "Cartridge ID"), + self.get_line("ascii", rom_bytes[0x3C:0x3E], "Cartridge ID") ) header_lines.append( - self.get_line("ascii", rom_bytes[0x3E:0x3F], "Country code"), + self.get_line("ascii", rom_bytes[0x3E:0x3F], "Country code") ) header_lines.append(self.get_line("byte", rom_bytes[0x3F:0x40], "Version")) header_lines.append("") diff --git a/src/splat/segtypes/n64/img.py b/src/splat/segtypes/n64/img.py index 3ed28751..6bb09a68 100644 --- a/src/splat/segtypes/n64/img.py +++ b/src/splat/segtypes/n64/img.py @@ -1,35 +1,32 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from pathlib import Path +from typing import Dict, List, Tuple, Type, Optional, Union +from n64img.image import Image from ...util import log, options -from ..segment import Segment -if TYPE_CHECKING: - from pathlib import Path - - from n64img.image import Image +from ..segment import Segment class N64SegImg(Segment): @staticmethod - def parse_dimensions(yaml: dict | list) -> tuple[int, int]: + def parse_dimensions(yaml: Union[Dict, List]) -> Tuple[int, int]: if isinstance(yaml, dict): return yaml["width"], yaml["height"] - if len(yaml) < 5: - log.error(f"Error: {yaml} is missing width and height parameters") - return yaml[3], yaml[4] + else: + if len(yaml) < 5: + log.error(f"Error: {yaml} is missing width and height parameters") + return yaml[3], yaml[4] def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, - args: list[str], + vram_start: Optional[int], + args: list, yaml, - img_cls: type[Image], + img_cls: Type[Image], ): super().__init__( rom_start, @@ -65,11 +62,11 @@ def check_len(self) -> None: actual_len = self.rom_end - self.rom_start if actual_len > expected_len: 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)", + 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)" ) elif actual_len < expected_len: 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}", + f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}" ) def out_path(self) -> Path: @@ -93,14 +90,15 @@ def split(self, rom_bytes): self.log(f"Wrote {self.name} to {path}") @staticmethod - def estimate_size(yaml: dict | list) -> int: + def estimate_size(yaml: Union[Dict, List]) -> int: width, height = N64SegImg.parse_dimensions(yaml) typ = Segment.parse_segment_type(yaml) if typ == "ci4" or typ == "i4" or typ == "ia4": return width * height // 2 - if typ in ("ia16", "rgba16"): + elif typ in ("ia16", "rgba16"): return width * height * 2 - if typ == "rgba32": + elif typ == "rgba32": return width * height * 4 - return width * height + else: + return width * height diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index 5f3c8011..de0d830b 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -1,14 +1,12 @@ -from __future__ import annotations - from itertools import zip_longest -from typing import TYPE_CHECKING +from pathlib import Path +from typing import Dict, List, Optional, Tuple, Union from ...util import log, options from ...util.color import unpack_color + from ..segment import Segment -if TYPE_CHECKING: - from pathlib import Path VALID_SIZES = [0x20, 0x40, 0x80, 0x100, 0x200] @@ -22,7 +20,7 @@ def __init__(self, *args, **kwargs): if self.extract: if self.rom_end is None: log.error( - f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it", + f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it" ) if isinstance(self.yaml, dict) and "size" in self.yaml: @@ -31,7 +29,7 @@ def __init__(self, *args, **kwargs): rom_len = self.rom_end - self.rom_start if rom_len < yaml_size: log.error( - f"Error: {self.name} has a `size` value of 0x{yaml_size:X}, but this is smaller than the actual rom size of the palette (0x{rom_len:X}, start: 0x{self.rom_start:X}, end: 0x{self.rom_end:X})", + f"Error: {self.name} has a `size` value of 0x{yaml_size:X}, but this is smaller than the actual rom size of the palette (0x{rom_len:X}, start: 0x{self.rom_start:X}, end: 0x{self.rom_end:X})" ) elif rom_len > yaml_size: log.error( @@ -48,19 +46,19 @@ def __init__(self, *args, **kwargs): if actual_len > VALID_SIZES[-1]: log.error( - f"Error: {self.name} (0x{actual_len:X} bytes) is too long, max 0x{VALID_SIZES[-1]:X})\n{hint_msg}", + f"Error: {self.name} (0x{actual_len:X} bytes) is too long, max 0x{VALID_SIZES[-1]:X})\n{hint_msg}" ) if actual_len not in VALID_SIZES: log.error( - f"Error: {self.name} (0x{actual_len:X} bytes) is not a valid palette size ({', '.join(hex(s) for s in VALID_SIZES)})\n{hint_msg}", + f"Error: {self.name} (0x{actual_len:X} bytes) is not a valid palette size ({', '.join(hex(s) for s in VALID_SIZES)})\n{hint_msg}" ) size = actual_len else: size = 0 self.palette_size: int = size - self.global_id: str | None = ( + self.global_id: Optional[str] = ( self.yaml.get("global_id") if isinstance(self.yaml, dict) else None ) @@ -68,7 +66,7 @@ def get_cname(self) -> str: return super().get_cname() + "_pal" @staticmethod - def parse_palette_bytes(data) -> list[tuple[int, int, int, int]]: + def parse_palette_bytes(data) -> List[Tuple[int, int, int, int]]: def iter_in_groups(iterable, n, fillvalue=None): args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) @@ -80,7 +78,7 @@ def iter_in_groups(iterable, n, fillvalue=None): return palette - def parse_palette(self, rom_bytes) -> list[tuple[int, int, int, int]]: + def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]: assert self.rom_start is not None data = rom_bytes[self.rom_start : self.rom_start + self.palette_size] @@ -104,11 +102,12 @@ def get_linker_entries(self): self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ), + ) ] @staticmethod - def estimate_size(yaml: dict | list) -> int: - if isinstance(yaml, dict) and "size" in yaml: - return int(yaml["size"]) + def estimate_size(yaml: Union[Dict, List]) -> int: + if isinstance(yaml, dict): + if "size" in yaml: + return int(yaml["size"]) return 0x20 diff --git a/src/splat/segtypes/n64/vtx.py b/src/splat/segtypes/n64/vtx.py index e60472f8..acf4bcbb 100644 --- a/src/splat/segtypes/n64/vtx.py +++ b/src/splat/segtypes/n64/vtx.py @@ -4,26 +4,24 @@ Originally written by Mark Street (https://github.com/mkst) """ -from __future__ import annotations import struct -from typing import TYPE_CHECKING +from pathlib import Path +from typing import Dict, List, Optional, Union -from ...util import log, options -from ..common.codesubsegment import CommonSegCodeSubsegment +from ...util import options, log -if TYPE_CHECKING: - from pathlib import Path +from ..common.codesubsegment import CommonSegCodeSubsegment class N64SegVtx(CommonSegCodeSubsegment): def __init__( self, - rom_start: int | None, - rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + rom_start: Optional[int], + rom_end: Optional[int], + type: str, name: str, - vram_start: int | None, + vram_start: Optional[int], args: list, yaml, ): @@ -36,7 +34,7 @@ def __init__( args=args, yaml=yaml, ) - self.file_text: str | None = None + self.file_text: Optional[str] = None self.data_only = isinstance(yaml, dict) and yaml.get("data_only", False) def format_sym_name(self, sym) -> str: @@ -60,7 +58,7 @@ def disassemble_data(self, rom_bytes) -> str: segment_length = len(vertex_data) if (segment_length) % 16 != 0: log.error( - f"Error: Vtx segment {self.name} length ({segment_length}) is not a multiple of 16!", + f"Error: Vtx segment {self.name} length ({segment_length}) is not a multiple of 16!" ) lines = [] @@ -70,7 +68,7 @@ def disassemble_data(self, rom_bytes) -> str: vertex_count = segment_length // 16 sym = self.create_symbol( - addr=self.vram_start, in_segment=True, type="data", define=True, + addr=self.vram_start, in_segment=True, type="data", define=True ) if not self.data_only: @@ -104,7 +102,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("vtx") @staticmethod - def estimate_size(yaml: dict | list) -> int | None: + def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: if isinstance(yaml, dict) and "length" in yaml: return yaml["length"] * 0x10 return None diff --git a/src/splat/segtypes/ps2/ctor.py b/src/splat/segtypes/ps2/ctor.py index a1b2c647..003059ac 100644 --- a/src/splat/segtypes/ps2/ctor.py +++ b/src/splat/segtypes/ps2/ctor.py @@ -1,11 +1,7 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Optional from ..common.data import CommonSegData - -if TYPE_CHECKING: - from ...disassembler.disassembler_section import DisassemblerSection +from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegCtor(CommonSegData): @@ -14,11 +10,11 @@ class Ps2SegCtor(CommonSegData): def get_linker_section(self) -> str: return ".ctor" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "a" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/lit4.py b/src/splat/segtypes/ps2/lit4.py index 046d1912..d2f1d581 100644 --- a/src/splat/segtypes/ps2/lit4.py +++ b/src/splat/segtypes/ps2/lit4.py @@ -1,11 +1,7 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Optional from ..common.data import CommonSegData - -if TYPE_CHECKING: - from ...disassembler.disassembler_section import DisassemblerSection +from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit4(CommonSegData): @@ -14,11 +10,11 @@ class Ps2SegLit4(CommonSegData): def get_linker_section(self) -> str: return ".lit4" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "wa" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/lit8.py b/src/splat/segtypes/ps2/lit8.py index 74128960..81b5cf99 100644 --- a/src/splat/segtypes/ps2/lit8.py +++ b/src/splat/segtypes/ps2/lit8.py @@ -1,11 +1,7 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Optional from ..common.data import CommonSegData - -if TYPE_CHECKING: - from ...disassembler.disassembler_section import DisassemblerSection +from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit8(CommonSegData): @@ -14,11 +10,11 @@ class Ps2SegLit8(CommonSegData): def get_linker_section(self) -> str: return ".lit8" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "wa" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/ps2/vtables.py b/src/splat/segtypes/ps2/vtables.py index 3d567dc2..afbdbd17 100644 --- a/src/splat/segtypes/ps2/vtables.py +++ b/src/splat/segtypes/ps2/vtables.py @@ -1,11 +1,7 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Optional from ..common.data import CommonSegData - -if TYPE_CHECKING: - from ...disassembler.disassembler_section import DisassemblerSection +from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegVtables(CommonSegData): @@ -14,11 +10,11 @@ class Ps2SegVtables(CommonSegData): def get_linker_section(self) -> str: return ".vtables" - def get_section_flags(self) -> str | None: + def get_section_flags(self) -> Optional[str]: return "a" def configure_disassembler_section( - self, disassembler_section: DisassemblerSection, + self, disassembler_section: DisassemblerSection ) -> None: "Allows to configure the section before running the analysis on it" diff --git a/src/splat/segtypes/psx/header.py b/src/splat/segtypes/psx/header.py index 5c6d8170..fc61059f 100644 --- a/src/splat/segtypes/psx/header.py +++ b/src/splat/segtypes/psx/header.py @@ -8,47 +8,47 @@ def parse_header(self, rom_bytes: bytes) -> list[str]: header_lines = [] header_lines.append(".section .data\n") header_lines.append( - self.get_line("ascii", rom_bytes[0x00:0x08], "Magic number"), + self.get_line("ascii", rom_bytes[0x00:0x08], "Magic number") ) header_lines.append( - self.get_line("word", rom_bytes[0x08:0x0C][::-1], ".text vram address"), + self.get_line("word", rom_bytes[0x08:0x0C][::-1], ".text vram address") ) header_lines.append( - self.get_line("word", rom_bytes[0x0C:0x10][::-1], ".data vram address"), + self.get_line("word", rom_bytes[0x0C:0x10][::-1], ".data vram address") ) header_lines.append( - self.get_line("word", rom_bytes[0x10:0x14][::-1], "Initial PC"), + self.get_line("word", rom_bytes[0x10:0x14][::-1], "Initial PC") ) header_lines.append( - self.get_line("word", rom_bytes[0x14:0x18][::-1], "Initial $gp/r28"), + self.get_line("word", rom_bytes[0x14:0x18][::-1], "Initial $gp/r28") ) header_lines.append( - self.get_line("word", rom_bytes[0x18:0x1C][::-1], ".text start"), + self.get_line("word", rom_bytes[0x18:0x1C][::-1], ".text start") ) header_lines.append( - self.get_line("word", rom_bytes[0x1C:0x20][::-1], ".text size"), + self.get_line("word", rom_bytes[0x1C:0x20][::-1], ".text size") ) header_lines.append( - self.get_line("word", rom_bytes[0x20:0x24][::-1], ".data start"), + self.get_line("word", rom_bytes[0x20:0x24][::-1], ".data start") ) header_lines.append( - self.get_line("word", rom_bytes[0x24:0x28][::-1], ".data size"), + self.get_line("word", rom_bytes[0x24:0x28][::-1], ".data size") ) header_lines.append( - self.get_line("word", rom_bytes[0x28:0x2C][::-1], ".bss start"), + self.get_line("word", rom_bytes[0x28:0x2C][::-1], ".bss start") ) header_lines.append( - self.get_line("word", rom_bytes[0x2C:0x30][::-1], ".bss size"), + self.get_line("word", rom_bytes[0x2C:0x30][::-1], ".bss size") ) header_lines.append( self.get_line( - "word", rom_bytes[0x30:0x34][::-1], "Initial $sp/r29 & $fp/r30 base", - ), + "word", rom_bytes[0x30:0x34][::-1], "Initial $sp/r29 & $fp/r30 base" + ) ) header_lines.append( self.get_line( - "word", rom_bytes[0x34:0x38][::-1], "Initial $sp/r29 & $fp/r30 offset", - ), + "word", rom_bytes[0x34:0x38][::-1], "Initial $sp/r29 & $fp/r30 offset" + ) ) header_lines.append(self.get_line("word", rom_bytes[0x38:0x3C], "Reserved")) header_lines.append(self.get_line("word", rom_bytes[0x3C:0x40], "Reserved")) @@ -57,7 +57,7 @@ def parse_header(self, rom_bytes: bytes) -> list[str]: header_lines.append(self.get_line("word", rom_bytes[0x48:0x4C], "Reserved")) assert isinstance(self.rom_end, int) header_lines.append( - self.get_line("ascii", rom_bytes[0x4C : self.rom_end], "Sony Inc"), + self.get_line("ascii", rom_bytes[0x4C : self.rom_end], "Sony Inc") ) header_lines.append("") diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index 6b168f11..dd55a2f6 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -5,32 +5,38 @@ import importlib import importlib.util from pathlib import Path -from typing import TYPE_CHECKING, TypeAlias, Union + +from typing import Optional, Type, TYPE_CHECKING, Union, Dict, TypeAlias, List from intervaltree import Interval, IntervalTree +from ..util import vram_classes -from .. import __package_name__ -from ..util import log, options, symbols, vram_classes + +from ..util.vram_classes import VramClass, SerializedSegmentData +from ..util import log, options, symbols from ..util.symbols import Symbol, to_cname -from ..util.vram_classes import SerializedSegmentData, VramClass + +from .. import __package_name__ # circular import if TYPE_CHECKING: from ..segtypes.linker_entry import LinkerEntry -SerializedSegment: TypeAlias = Union[SerializedSegmentData, list[str]] +SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] def parse_segment_vram(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "vram" in segment: return int(segment["vram"]) - return None + else: + return None def parse_segment_vram_symbol(segment: SerializedSegment) -> str | None: if isinstance(segment, dict) and "vram_symbol" in segment: return str(segment["vram_symbol"]) - return None + else: + return None def parse_segment_vram_class(segment: SerializedSegment) -> VramClass | None: @@ -77,9 +83,9 @@ class SegmentStatisticsInfo: size: int count: int - def merge(self, other: SegmentStatisticsInfo) -> SegmentStatisticsInfo: + def merge(self, other: "SegmentStatisticsInfo") -> "SegmentStatisticsInfo": return SegmentStatisticsInfo( - size=self.size + other.size, count=self.count + other.count, + size=self.size + other.size, count=self.count + other.count ) @@ -111,7 +117,7 @@ def get_class_for_type(seg_type: str) -> type[Segment]: if segment_class is None: log.error( - f"could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)", + f"could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)" ) return segment_class @@ -124,13 +130,13 @@ def get_base_segment_class(seg_type: str) -> type[Segment] | None: # heirarchy is platform -> common -> fail try: segmodule = importlib.import_module( - f".segtypes.{platform}.{seg_type}", package=__package_name__, + f".segtypes.{platform}.{seg_type}", package=__package_name__ ) is_platform_seg = True except ModuleNotFoundError: try: segmodule = importlib.import_module( - f".segtypes.common.{seg_type}", package=__package_name__, + f".segtypes.common.{seg_type}", package=__package_name__ ) except ModuleNotFoundError: return None @@ -138,7 +144,7 @@ def get_base_segment_class(seg_type: str) -> type[Segment] | None: seg_prefix = platform.capitalize() if is_platform_seg else "Common" return getattr( # type: ignore[no-any-return] segmodule, - f"{seg_prefix}Seg{seg_type.capitalize()}", + f"{seg_prefix}Seg{seg_type.capitalize()}" ) @staticmethod @@ -148,7 +154,7 @@ def get_extension_segment_class(seg_type: str) -> type[Segment] | None: ext_path = options.opts.extensions_path if not ext_path: log.error( - f"could not load presumed extended segment type '{seg_type}' because no extensions path is configured", + f"could not load presumed extended segment type '{seg_type}' because no extensions path is configured" ) assert ext_path is not None @@ -165,7 +171,7 @@ def get_extension_segment_class(seg_type: str) -> type[Segment] | None: return None return getattr( # type: ignore[no-any-return] - ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}", + ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}" ) @staticmethod @@ -189,20 +195,22 @@ def parse_segment_start(segment: SerializedSegment) -> tuple[int | None, bool]: return None, False if s == "auto": return None, True - return int(s), False + else: + return int(s), False @staticmethod def parse_segment_type(segment: SerializedSegment) -> str: if isinstance(segment, dict): return str(segment["type"]) - return str(segment[1]) + else: + return str(segment[1]) @classmethod def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) -> str: if isinstance(segment, dict): if "name" in segment: return str(segment["name"]) - if "dir" in segment: + elif "dir" in segment: return str(segment["dir"]) elif isinstance(segment, list) and len(segment) >= 3: return str(segment[2]) @@ -213,13 +221,15 @@ def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) - def parse_segment_symbol_name_format(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format" in segment: return str(segment["symbol_name_format"]) - return options.opts.symbol_name_format + else: + return options.opts.symbol_name_format @staticmethod def parse_segment_symbol_name_format_no_rom(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format_no_rom" in segment: return str(segment["symbol_name_format_no_rom"]) - return options.opts.symbol_name_format_no_rom + else: + return options.opts.symbol_name_format_no_rom @staticmethod def parse_segment_file_path(segment: SerializedSegment) -> Path | None: @@ -229,7 +239,7 @@ def parse_segment_file_path(segment: SerializedSegment) -> Path | None: @staticmethod def parse_segment_bss_contains_common( - segment: SerializedSegment, default: bool, + segment: SerializedSegment, default: bool ) -> bool: if isinstance(segment, dict) and "bss_contains_common" in segment: return bool(segment["bss_contains_common"]) @@ -249,7 +259,7 @@ def parse_linker_section(yaml: SerializedSegment) -> str | None: @staticmethod def parse_ld_fill_value( - yaml: SerializedSegment, default: int | None, + yaml: SerializedSegment, default: int | None ) -> int | None: if isinstance(yaml, dict) and "ld_fill_value" in yaml: return yaml["ld_fill_value"] @@ -267,7 +277,7 @@ def parse_suggestion_rodata_section_start( ) -> bool | None: if isinstance(yaml, dict): suggestion_rodata_section_start = yaml.get( - "suggestion_rodata_section_start", + "suggestion_rodata_section_start" ) if suggestion_rodata_section_start is not None: assert isinstance(suggestion_rodata_section_start, bool) @@ -284,7 +294,7 @@ def __init__( self, rom_start: int | None, rom_end: int | None, - type: str, # noqa: A002 # `type` is shadowing a builtin + type: str, name: str, vram_start: int | None, args: list[str], @@ -335,13 +345,15 @@ def __init__( self.extract: bool = True self.has_linker_entry: bool = True - if self.rom_start is None or self.type.startswith("."): + if self.rom_start is None: + self.extract = False + elif self.type.startswith("."): self.extract = False self.warnings: list[str] = [] self.did_run = False self.bss_contains_common = Segment.parse_segment_bss_contains_common( - yaml, options.opts.ld_bss_contains_common, + yaml, options.opts.ld_bss_contains_common ) # For segments which are not in the usual VRAM segment space, like N64's IPL3 which lives in 0xA4... @@ -352,11 +364,11 @@ def __init__( # If not defined on the segment then default to the global option self.ld_fill_value: int | None = self.parse_ld_fill_value( - yaml, options.opts.ld_fill_value, + yaml, options.opts.ld_fill_value ) self.ld_align_segment_start: int | None = self.parse_ld_align_segment_start( - yaml, + yaml ) # True if this segment was generated based on auto_link_sections @@ -371,14 +383,11 @@ def __init__( self.index_within_group: int | None = None - if ( - self.rom_start is not None - and self.rom_end is not None - and self.rom_start > self.rom_end - ): - log.error( - f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})", - ) + if self.rom_start is not None and self.rom_end is not None: + if self.rom_start > self.rom_end: + log.error( + f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})" + ) @classmethod def from_yaml( @@ -389,7 +398,7 @@ def from_yaml( parent: Segment | None, vram: int | None = None, ) -> Segment: - type_ = cls.parse_segment_type(yaml) + type = cls.parse_segment_type(yaml) name = cls.parse_segment_name(rom_start, yaml) vram_class = parse_segment_vram_class(yaml) @@ -408,7 +417,7 @@ def from_yaml( ret = cls( rom_start=rom_start, rom_end=rom_end, - type=type_, + type=type, name=name, vram_start=vram_start, args=args, @@ -417,33 +426,34 @@ def from_yaml( if parent is not None: if "subalign" in yaml: log.error( - f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `subalign`. `subalign` is valid only for top-level segments", + f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `subalign`. `subalign` is valid only for top-level segments" ) if "ld_fill_value" in yaml: log.error( - f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `ld_fill_value`. `ld_fill_value` is valid only for top-level segments", + f"Non top-level segment '{name}' (rom address 0x{rom_start:X}) specified a `ld_fill_value`. `ld_fill_value` is valid only for top-level segments" ) ret.parent = parent # Import here to avoid circular imports - from .common.bss import CommonSegBss from .common.code import CommonSegCode + from .common.bss import CommonSegBss - if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss) and isinstance(parent, CommonSegCode): + if options.opts.ld_bss_is_noload and isinstance(ret, CommonSegBss): # We need to know the bss space for the segment. - if parent.bss_size <= 0: - log.error( - f"Top-level segment '{parent.name}' is missing a `bss_size` value.\n A non-zero `bss_size` value must be defined on the top-level segments that contain '{ret.type}' sections (produced by the '{ret.name}' section).", - ) - if ( - isinstance(ret.vram_start, int) - and isinstance(parent.vram_end, int) - and ret.vram_start >= parent.vram_end - ): - log.error( - f"The section '{ret.name}' (vram 0x{ret.vram_start:08X}) is outside its parent's address range '{parent.name}' (0x{parent.vram_start:08X} ~ 0x{parent.vram_end:08X}).\n This may happen when the specified `bss_size` value is too small.", - ) + if isinstance(parent, CommonSegCode): + if parent.bss_size <= 0: + log.error( + f"Top-level segment '{parent.name}' is missing a `bss_size` value.\n A non-zero `bss_size` value must be defined on the top-level segments that contain '{ret.type}' sections (produced by the '{ret.name}' section)." + ) + if ( + isinstance(ret.vram_start, int) + and isinstance(parent.vram_end, int) + and ret.vram_start >= parent.vram_end + ): + log.error( + f"The section '{ret.name}' (vram 0x{ret.vram_start:08X}) is outside its parent's address range '{parent.name}' (0x{parent.vram_start:08X} ~ 0x{parent.vram_end:08X}).\n This may happen when the specified `bss_size` value is too small." + ) ret.given_section_order = parse_segment_section_order(yaml) ret.given_subalign = parse_segment_subalign(yaml) @@ -462,7 +472,7 @@ def from_yaml( ret.file_path = Segment.parse_segment_file_path(yaml) ret.bss_contains_common = Segment.parse_segment_bss_contains_common( - yaml, options.opts.ld_bss_contains_common, + yaml, options.opts.ld_bss_contains_common ) ret.given_follows_vram = parse_segment_follows_vram(yaml) @@ -472,11 +482,11 @@ def from_yaml( ret.vram_class = vram_class if ret.given_follows_vram: log.error( - f"Error: segment {ret.name} has both a vram class and a follows_vram property", + f"Error: segment {ret.name} has both a vram class and a follows_vram property" ) if ret.given_vram_symbol: log.error( - f"Error: segment {ret.name} has both a vram class and a vram_symbol property", + f"Error: segment {ret.name} has both a vram class and a vram_symbol property" ) if not ret.align: @@ -515,7 +525,8 @@ def needs_symbols(self) -> bool: def dir(self) -> Path: if self.parent: return self.parent.dir / self.given_dir - return self.given_dir + else: + return self.given_dir @property def show_file_boundaries(self) -> bool: @@ -548,9 +559,10 @@ def subalign(self) -> int | None: def vram_symbol(self) -> str | None: if self.vram_class and self.vram_class.vram_symbol: return self.vram_class.vram_symbol - if self.given_vram_symbol: + elif self.given_vram_symbol: return self.given_vram_symbol - return None + else: + return None def get_exclusive_ram_id(self) -> str | None: if self.parent: @@ -572,13 +584,15 @@ def add_symbol(self, symbol: Symbol) -> None: def seg_symbols(self) -> dict[int, list[Symbol]]: if self.parent: return self.parent.seg_symbols - return self.given_seg_symbols + else: + return self.given_seg_symbols @property def size(self) -> int | None: if self.rom_start is not None and self.rom_end is not None: return self.rom_end - self.rom_start - return None + else: + return None @property def statistics(self) -> SegmentStatistics: @@ -595,7 +609,8 @@ def statistics_type(self) -> SegmentType: def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size - return None + else: + return None @property def section_order(self) -> list[str]: @@ -627,17 +642,20 @@ def get_cname(self) -> str: def contains_vram(self, vram: int) -> bool: if self.vram_start is not None and self.vram_end is not None: return vram >= self.vram_start and vram < self.vram_end - return False + else: + return False def contains_rom(self, rom: int) -> bool: if self.rom_start is not None and self.rom_end is not None: return rom >= self.rom_start and rom < self.rom_end - return False + else: + return False def rom_to_ram(self, rom_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.vram_start + rom_addr - self.rom_start - return None + else: + return None def ram_to_rom(self, ram_addr: int) -> int | None: if not self.contains_vram(ram_addr) and ram_addr != self.vram_end: @@ -645,7 +663,8 @@ def ram_to_rom(self, ram_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.rom_start + ram_addr - self.vram_start - return None + else: + return None def should_scan(self) -> bool: return self.should_split() @@ -719,7 +738,7 @@ def get_most_parent(self) -> Segment: return seg - def get_linker_entries(self) -> list[LinkerEntry]: + def get_linker_entries(self) -> "list[LinkerEntry]": from ..segtypes.linker_entry import LinkerEntry if not self.has_linker_entry: @@ -736,9 +755,10 @@ def get_linker_entries(self) -> list[LinkerEntry]: self.get_linker_section_order(), self.get_linker_section_linksection(), self.is_noload(), - ), + ) ] - return [] + else: + return [] def log(self, msg: str) -> None: if options.opts.verbose: @@ -756,7 +776,10 @@ def is_name_default(self) -> bool: return self.name == self.get_default_name(self.rom_start) def unique_id(self) -> str: - s = self.parent.unique_id() + "_" if self.parent else "" + if self.parent: + s = self.parent.unique_id() + "_" + else: + s = "" return s + self.type + "_" + self.name @@ -769,7 +792,7 @@ def visible_ram(seg1: Segment, seg2: Segment) -> bool: return seg1.get_exclusive_ram_id() != seg2.get_exclusive_ram_id() def retrieve_symbol( - self, syms: dict[int, list[Symbol]], addr: int, + self, syms: dict[int, list[Symbol]], addr: int ) -> Symbol | None: if addr not in syms: return None @@ -791,10 +814,7 @@ def retrieve_symbol( return items[0] def retrieve_sym_type( - self, - syms: dict[int, list[Symbol]], - addr: int, - type: str, # noqa: A002 # `type` is shadowing a builtin + self, syms: dict[int, list[Symbol]], addr: int, type: str ) -> symbols.Symbol | None: if addr not in syms: return None @@ -817,7 +837,7 @@ def get_symbol( self, addr: int, in_segment: bool = False, - type: str | None = None, # noqa: A002 # `type` is shadowing a builtin + type: str | None = None, create: bool = False, define: bool = False, reference: bool = False, @@ -873,8 +893,9 @@ def get_symbol( ret.type = type if ret.rom is None: ret.rom = rom - if in_segment and ret.segment is None: - ret.segment = most_parent + if in_segment: + if ret.segment is None: + ret.segment = most_parent return ret @@ -882,7 +903,7 @@ def create_symbol( self, addr: int, in_segment: bool, - type: str | None = None, # noqa: A002 # `type` is shadowing a builtin + type: str | None = None, define: bool = False, reference: bool = False, search_ranges: bool = False, diff --git a/src/splat/util/__init__.py b/src/splat/util/__init__.py index c17ab7a7..d08c7839 100644 --- a/src/splat/util/__init__.py +++ b/src/splat/util/__init__.py @@ -1,18 +1,16 @@ -from . import ( - cache_handler as cache_handler, - color as color, - compiler as compiler, - file_presets as file_presets, - log as log, - n64 as n64, - options as options, - palettes as palettes, - progress_bar as progress_bar, - ps2 as ps2, - psx as psx, - relocs as relocs, - statistics as statistics, - symbols as symbols, - utils as utils, - vram_classes as vram_classes, -) +from . import cache_handler as cache_handler +from . import color as color +from . import compiler as compiler +from . import file_presets as file_presets +from . import log as log +from . import n64 as n64 +from . import options as options +from . import palettes as palettes +from . import progress_bar as progress_bar +from . import ps2 as ps2 +from . import psx as psx +from . import relocs as relocs +from . import statistics as statistics +from . import symbols as symbols +from . import utils as utils +from . import vram_classes as vram_classes diff --git a/src/splat/util/cache_handler.py b/src/splat/util/cache_handler.py index 4116d139..0e42821a 100644 --- a/src/splat/util/cache_handler.py +++ b/src/splat/util/cache_handler.py @@ -24,7 +24,7 @@ def __init__(self, config: dict[str, Any], use_cache: bool, verbose: bool) -> No log.write(f"Loaded cache ({len(self.cache.keys())} items)") except Exception: log.write( - "Not able to load cache file. Discarding old cache", status="warn", + "Not able to load cache file. Discarding old cache", status="warn" ) # invalidate entire cache if options change diff --git a/src/splat/util/compiler.py b/src/splat/util/compiler.py index 40399019..eeea374a 100644 --- a/src/splat/util/compiler.py +++ b/src/splat/util/compiler.py @@ -1,6 +1,5 @@ -from __future__ import annotations - from dataclasses import dataclass +from typing import Optional, Dict @dataclass @@ -16,7 +15,7 @@ class Compiler: asm_nonmatching_label_macro: str = "nonmatching" c_newline: str = "\n" asm_inc_header: str = "" - asm_emit_size_directive: bool | None = None + asm_emit_size_directive: Optional[bool] = None j_as_branch: bool = False uses_include_asm: bool = True align_on_branch_labels: bool = False @@ -65,7 +64,7 @@ class Compiler: MWCCPS2 = Compiler("MWCCPS2", uses_include_asm=False) EEGCC = Compiler("EEGCC", align_on_branch_labels=True) -compiler_for_name: dict[str, Compiler] = { +compiler_for_name: Dict[str, Compiler] = { x.name: x for x in [ GCC, diff --git a/src/splat/util/conf.py b/src/splat/util/conf.py index cf61e80e..ea45069b 100644 --- a/src/splat/util/conf.py +++ b/src/splat/util/conf.py @@ -5,9 +5,9 @@ config = conf.load("path/to/splat.yaml") """ -from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import Any, Dict, List, Optional +from pathlib import Path # This unused import makes the yaml library faster. don't remove import pylibyaml # noqa: F401 @@ -15,9 +15,6 @@ from . import options, vram_classes -if TYPE_CHECKING: - from pathlib import Path - def _merge_configs(main_config, additional_config, additional_config_path): # Merge rules are simple @@ -31,7 +28,7 @@ def _merge_configs(main_config, additional_config, additional_config_path): main_config[curkey] = additional_config[curkey] elif type(main_config[curkey]) is not type(additional_config[curkey]): raise TypeError( - f"Could not merge {additional_config_path!s}: type for key '{curkey}' in configs does not match", + f"Could not merge {str(additional_config_path)}: type for key '{curkey}' in configs does not match" ) else: # keys exist and match, see if a list to append @@ -52,12 +49,12 @@ def _merge_configs(main_config, additional_config, additional_config_path): def load( - config_path: list[Path], - modes: list[str] | None = None, + config_path: List[Path], + modes: Optional[List[str]] = None, verbose: bool = False, disassemble_all: bool = False, make_full_disasm_for_code=False, -) -> dict[str, Any]: +) -> Dict[str, Any]: """ Returns a `dict` with resolved splat config. @@ -79,7 +76,7 @@ def load( Config with invalid options may raise an error. """ - config: dict[str, Any] = {} + config: Dict[str, Any] = {} for entry in config_path: with entry.open() as f: additional_config = yaml.load(f.read(), Loader=yaml.SafeLoader) diff --git a/src/splat/util/file_presets.py b/src/splat/util/file_presets.py index b915fdea..c94ad8d2 100644 --- a/src/splat/util/file_presets.py +++ b/src/splat/util/file_presets.py @@ -12,7 +12,7 @@ from pathlib import Path -from . import log, options +from . import options, log def write_all_files() -> None: @@ -250,7 +250,7 @@ def write_assembly_inc_files() -> None: # names when using modern gas to avoid build errors. # This means we can't reuse the labels.inc file. gas = macros_inc.replace("\\label", '"\\label"').replace( - '"\\label"\\().NON_MATCHING', '"\\label\\().NON_MATCHING"', + '"\\label"\\().NON_MATCHING', '"\\label\\().NON_MATCHING"' ) elif not options.opts.is_unsupported_platform: log.error(f"Unknown platform '{options.opts.platform}'") diff --git a/src/splat/util/n64/__init__.py b/src/splat/util/n64/__init__.py index 787f41f6..22561555 100644 --- a/src/splat/util/n64/__init__.py +++ b/src/splat/util/n64/__init__.py @@ -1 +1,2 @@ -from . import find_code_length as find_code_length, rominfo as rominfo +from . import find_code_length as find_code_length +from . import rominfo as rominfo diff --git a/src/splat/util/n64/find_code_length.py b/src/splat/util/n64/find_code_length.py index 7bc00111..c525a88d 100755 --- a/src/splat/util/n64/find_code_length.py +++ b/src/splat/util/n64/find_code_length.py @@ -12,7 +12,7 @@ def int_any_base(x: str) -> int: parser = argparse.ArgumentParser( - description="Given a rom and start offset, find where the code ends", + description="Given a rom and start offset, find where the code ends" ) parser.add_argument("rom", help="path to a .z64 rom") parser.add_argument("start", help="start offset", type=int_any_base) diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index c54e3843..c11f4c23 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -332,7 +332,6 @@ def find_code_after_data( if insn.isValid() and insn.isReturn(): # Check the instruction on the delay slot of the `jr $ra` is valid too. next_word = spimdisasm.common.Utils.bytesToWords( - rom_bytes, offset + 4, offset + 4 + 4, )[0] if rabbitizer.Instruction(next_word, vram + 4).isValid(): jr_ra_found = True @@ -388,7 +387,6 @@ def swap_bytes(data: bytes) -> bytes: return bytes( itertools.chain.from_iterable( struct.pack(">H", x) for (x,) in struct.iter_unpack(" N64Rom: sys.exit( "splat could not decode the game name;" " try using a different encoding by passing the --header-encoding argument" - " (see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings)", ) country_code = rom_bytes[0x3E] @@ -465,7 +462,6 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sha1 = hashlib.sha1(rom_bytes).hexdigest() entrypoint_info = N64EntrypointInfo.parse_rom_bytes( - rom_bytes, entry_point, size=0x100, ) return N64Rom( @@ -502,7 +498,6 @@ def get_compiler_info(rom_bytes: bytes, entry_point: int, print_result: bool = T if print_result: print( f"{branches} branches and {jumps} jumps detected in the first code segment." - f" Compiler is most likely {compiler}", ) return compiler diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 9aa780b1..40895e06 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -1,16 +1,13 @@ from __future__ import annotations -import os from dataclasses import dataclass +import os from pathlib import Path -from typing import TYPE_CHECKING, Literal, TypeVar, cast +from typing import cast, Literal, Type, TypeVar +from collections.abc import Mapping from . import compiler - -if TYPE_CHECKING: - from collections.abc import Mapping - - from .compiler import Compiler +from .compiler import Compiler @dataclass @@ -319,7 +316,7 @@ def parse_opt(self, opt: str, t: type[T], default: T | None = None) -> T: if isinstance(value, t): return value if t is float and isinstance(value, int): - return cast("T", float(value)) + return cast(T, float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: @@ -328,7 +325,7 @@ def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: return self.parse_opt(opt, t) def parse_optional_opt_with_default( - self, opt: str, t: type[T], default: T | None, + self, opt: str, t: type[T], default: T | None ) -> T | None: if opt not in self._yaml: return default @@ -337,11 +334,11 @@ def parse_optional_opt_with_default( if value is None or isinstance(value, t): return value if t is float and isinstance(value, int): - return cast("T", float(value)) + return cast(T, float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_opt_within( - self, opt: str, t: type[T], within: list[T], default: T | None = None, + self, opt: str, t: type[T], within: list[T], default: T | None = None ) -> T: value = self.parse_opt(opt, t, default) if value not in within: @@ -349,7 +346,7 @@ def parse_opt_within( return value def parse_path( - self, base_path: Path, opt: str, default: str | None = None, + self, base_path: Path, opt: str, default: str | None = None ) -> Path: return Path(os.path.normpath(base_path / self.parse_opt(opt, str, default))) @@ -363,9 +360,10 @@ def parse_path_list(self, base_path: Path, opt: str, default: str) -> list[Path] if isinstance(paths, str): return [base_path / paths] - if isinstance(paths, list): + elif isinstance(paths, list): return [base_path / path for path in paths] - raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") + else: + raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") def check_no_unread_opts(self) -> None: opts = [opt for opt in self._yaml if opt not in self._read_opts] @@ -394,7 +392,7 @@ def _parse_yaml( comp = compiler.for_name(p.parse_opt("compiler", str, "IDO")) base_path = Path( - os.path.normpath(config_paths[0].parent / p.parse_opt("base_path", str)), + os.path.normpath(config_paths[0].parent / p.parse_opt("base_path", str)) ) asm_path: Path = p.parse_path(base_path, "asm_path", "asm") @@ -418,9 +416,10 @@ def parse_endianness() -> Literal["big", "little"]: if endianness == "big": return "big" - if endianness == "little": + elif endianness == "little": return "little" - raise ValueError(f"Invalid endianness: {endianness}") + else: + raise ValueError(f"Invalid endianness: {endianness}") def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: include_asm_macro_style = p.parse_opt_within( @@ -432,9 +431,10 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: if include_asm_macro_style == "default": return "default" - if include_asm_macro_style == "maspsx_hack": + elif include_asm_macro_style == "maspsx_hack": return "maspsx_hack" - raise ValueError(f"Invalid endianness: {include_asm_macro_style}") + else: + raise ValueError(f"Invalid endianness: {include_asm_macro_style}") default_ld_bss_is_noload = True if platform == "psx": @@ -457,31 +457,31 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: compiler=comp, endianness=parse_endianness(), section_order=p.parse_opt( - "section_order", list, [".text", ".data", ".rodata", ".bss"], + "section_order", list, [".text", ".data", ".rodata", ".bss"] ), generated_c_preamble=p.parse_opt( - "generated_c_preamble", str, '#include "common.h"', + "generated_c_preamble", str, '#include "common.h"' ), generated_s_preamble=p.parse_opt("generated_s_preamble", str, ""), generated_macro_inc_content=p.parse_optional_opt( - "generated_macro_inc_content", str, + "generated_macro_inc_content", str ), generate_asm_macros_files=p.parse_opt("generate_asm_macros_files", bool, True), include_asm_macro_style=parse_include_asm_macro_style(), generated_asm_macros_directory=p.parse_path( - base_path, "generated_asm_macros_directory", "include", + base_path, "generated_asm_macros_directory", "include" ), use_o_as_suffix=p.parse_opt("o_as_suffix", bool, False), gp=p.parse_optional_opt("gp_value", int), check_consecutive_segment_types=p.parse_opt( - "check_consecutive_segment_types", bool, True, + "check_consecutive_segment_types", bool, True ), asset_path=p.parse_path(base_path, "asset_path", "assets"), symbol_addrs_paths=p.parse_path_list( - base_path, "symbol_addrs_path", "symbol_addrs.txt", + base_path, "symbol_addrs_path", "symbol_addrs.txt" ), reloc_addrs_paths=p.parse_path_list( - base_path, "reloc_addrs_path", "reloc_addrs.txt", + base_path, "reloc_addrs_path", "reloc_addrs.txt" ), build_path=p.parse_path(base_path, "build_path", "build"), src_path=p.parse_path(base_path, "src_path", "src"), @@ -492,16 +492,16 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: cache_path=p.parse_path(base_path, "cache_path", ".splache"), hasm_in_src_path=p.parse_opt("hasm_in_src_path", bool, False), create_undefined_funcs_auto=p.parse_opt( - "create_undefined_funcs_auto", bool, True, + "create_undefined_funcs_auto", bool, True ), undefined_funcs_auto_path=p.parse_path( - base_path, "undefined_funcs_auto_path", "undefined_funcs_auto.txt", + base_path, "undefined_funcs_auto_path", "undefined_funcs_auto.txt" ), create_undefined_syms_auto=p.parse_opt( - "create_undefined_syms_auto", bool, True, + "create_undefined_syms_auto", bool, True ), undefined_syms_auto_path=p.parse_path( - base_path, "undefined_syms_auto_path", "undefined_syms_auto.txt", + base_path, "undefined_syms_auto_path", "undefined_syms_auto.txt" ), extensions_path=p.parse_optional_path(base_path, "extensions_path"), lib_path=p.parse_path(base_path, "lib_path", "lib"), @@ -509,7 +509,7 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: subalign=p.parse_optional_opt_with_default("subalign", int, 16), emit_subalign=p.parse_opt("emit_subalign", bool, True), auto_link_sections=p.parse_opt( - "auto_link_sections", list, [".data", ".rodata", ".bss"], + "auto_link_sections", list, [".data", ".rodata", ".bss"] ), ld_script_path=p.parse_path(base_path, "ld_script_path", f"{basename}.ld"), ld_symbol_header_path=p.parse_optional_path(base_path, "ld_symbol_header_path"), @@ -518,76 +518,76 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: ld_sections_denylist=p.parse_opt("ld_sections_denylist", list, []), ld_wildcard_sections=p.parse_opt("ld_wildcard_sections", bool, False), ld_sort_segments_by_vram_class_dependency=p.parse_opt( - "ld_sort_segments_by_vram_class_dependency", bool, False, + "ld_sort_segments_by_vram_class_dependency", bool, False ), ld_use_symbolic_vram_addresses=p.parse_opt( - "ld_use_symbolic_vram_addresses", bool, True, + "ld_use_symbolic_vram_addresses", bool, True ), ld_partial_linking=p.parse_opt("ld_partial_linking", bool, False), ld_partial_scripts_path=p.parse_optional_path( - base_path, "ld_partial_scripts_path", + base_path, "ld_partial_scripts_path" ), ld_partial_build_segments_path=p.parse_optional_path( - base_path, "ld_partial_build_segments_path", + base_path, "ld_partial_build_segments_path" ), ld_dependencies=p.parse_opt("ld_dependencies", bool, False), ld_legacy_generation=p.parse_opt("ld_legacy_generation", bool, False), segment_end_before_align=p.parse_opt("segment_end_before_align", bool, False), segment_symbols_style=p.parse_opt_within( - "segment_symbols_style", str, ["splat", "makerom"], "splat", + "segment_symbols_style", str, ["splat", "makerom"], "splat" ), ld_rom_start=p.parse_opt("ld_rom_start", int, 0), ld_fill_value=p.parse_optional_opt_with_default("ld_fill_value", int, 0), ld_bss_is_noload=p.parse_opt( - "ld_bss_is_noload", bool, default_ld_bss_is_noload, + "ld_bss_is_noload", bool, default_ld_bss_is_noload ), ld_align_segment_start=p.parse_optional_opt_with_default( - "ld_align_segment_start", int, None, + "ld_align_segment_start", int, None ), ld_align_segment_vram_end=p.parse_opt("ld_align_segment_vram_end", bool, True), ld_align_section_vram_end=p.parse_opt("ld_align_section_vram_end", bool, True), ld_generate_symbol_per_data_segment=p.parse_opt( - "ld_generate_symbol_per_data_segment", bool, False, + "ld_generate_symbol_per_data_segment", bool, False ), ld_bss_contains_common=p.parse_opt("ld_bss_contains_common", bool, False), ld_gp_expression=p.parse_optional_opt_with_default( - "ld_gp_expression", str, None, + "ld_gp_expression", str, None ), create_c_files=p.parse_opt("create_c_files", bool, True), auto_decompile_empty_functions=p.parse_opt( - "auto_decompile_empty_functions", bool, True, + "auto_decompile_empty_functions", bool, True ), do_c_func_detection=p.parse_opt("do_c_func_detection", bool, True), c_newline=p.parse_opt("c_newline", str, comp.c_newline), symbol_name_format=p.parse_opt("symbol_name_format", str, "$VRAM"), symbol_name_format_no_rom=p.parse_opt( - "symbol_name_format_no_rom", str, "$VRAM_$SEG", + "symbol_name_format_no_rom", str, "$VRAM_$SEG" ), find_file_boundaries=p.parse_opt("find_file_boundaries", bool, True), pair_rodata_to_text=p.parse_opt("pair_rodata_to_text", bool, True), migrate_rodata_to_functions=p.parse_opt( - "migrate_rodata_to_functions", bool, True, + "migrate_rodata_to_functions", bool, True ), asm_inc_header=p.parse_opt("asm_inc_header", str, comp.asm_inc_header), asm_function_macro=p.parse_opt( - "asm_function_macro", str, comp.asm_function_macro, + "asm_function_macro", str, comp.asm_function_macro ), asm_function_alt_macro=p.parse_opt( - "asm_function_alt_macro", str, comp.asm_function_alt_macro, + "asm_function_alt_macro", str, comp.asm_function_alt_macro ), asm_jtbl_label_macro=p.parse_opt( - "asm_jtbl_label_macro", str, comp.asm_jtbl_label_macro, + "asm_jtbl_label_macro", str, comp.asm_jtbl_label_macro ), asm_data_macro=p.parse_opt("asm_data_macro", str, comp.asm_data_macro), asm_end_label=p.parse_opt("asm_end_label", str, comp.asm_end_label), asm_data_end_label=p.parse_opt( - "asm_data_end_label", str, comp.asm_data_end_label, + "asm_data_end_label", str, comp.asm_data_end_label ), asm_ehtable_label_macro=p.parse_opt( - "asm_ehtable_label_macro", str, comp.asm_ehtable_label_macro, + "asm_ehtable_label_macro", str, comp.asm_ehtable_label_macro ), asm_nonmatching_label_macro=p.parse_opt( - "asm_nonmatching_label_macro", str, comp.asm_nonmatching_label_macro, + "asm_nonmatching_label_macro", str, comp.asm_nonmatching_label_macro ), asm_emit_size_directive=asm_emit_size_directive, mnemonic_ljust=p.parse_opt("mnemonic_ljust", int, 11), @@ -610,10 +610,10 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: string_encoding=p.parse_optional_opt("string_encoding", str), data_string_encoding=p.parse_optional_opt("data_string_encoding", str), rodata_string_guesser_level=p.parse_optional_opt( - "rodata_string_guesser_level", int, + "rodata_string_guesser_level", int ), data_string_guesser_level=p.parse_optional_opt( - "data_string_guesser_level", int, + "data_string_guesser_level", int ), allow_data_addends=p.parse_opt("allow_data_addends", bool, True), header_encoding=p.parse_opt("header_encoding", str, "ASCII"), @@ -631,7 +631,7 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: align_on_branch_labels=align_on_branch_labels, disasm_unknown=p.parse_opt("disasm_unknown", bool, False), detect_redundant_function_end=p.parse_opt( - "detect_redundant_function_end", bool, True, + "detect_redundant_function_end", bool, True ), # Setting either option will produce a full disassembly, # but we still have to check the yaml option first to avoid leaving option unparsed, @@ -642,11 +642,11 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: global_vram_start=p.parse_optional_opt("global_vram_start", int), global_vram_end=p.parse_optional_opt("global_vram_end", int), use_gp_rel_macro_nonmatching=p.parse_opt( - "use_gp_rel_macro_nonmatching", bool, True, + "use_gp_rel_macro_nonmatching", bool, True ), use_gp_rel_macro=p.parse_opt("use_gp_rel_macro", bool, True), suggestion_rodata_section_start=p.parse_opt( - "suggestion_rodata_section_start", bool, True, + "suggestion_rodata_section_start", bool, True ), ) p.check_no_unread_opts() diff --git a/src/splat/util/palettes.py b/src/splat/util/palettes.py index a277cdf4..d83d24c1 100644 --- a/src/splat/util/palettes.py +++ b/src/splat/util/palettes.py @@ -1,31 +1,33 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import Dict, TYPE_CHECKING + +from ..util import log from ..segtypes.common.group import CommonSegGroup from ..segtypes.n64.ci import N64SegCi from ..segtypes.n64.palette import N64SegPalette -from ..util import log if TYPE_CHECKING: from ..segtypes.segment import Segment -global_ids: dict[str, N64SegPalette] = {} +global_ids: Dict[str, N64SegPalette] = {} # Resolve Raster#palette and Palette#raster links def initialize(all_segments: list[Segment]) -> None: def collect_global_ids(segments: list[Segment]) -> None: for segment in segments: - if isinstance(segment, N64SegPalette) and segment.global_id is not None: - global_ids[segment.global_id] = segment + if isinstance(segment, N64SegPalette): + if segment.global_id is not None: + global_ids[segment.global_id] = segment if isinstance(segment, CommonSegGroup): collect_global_ids(segment.subsegments) def process(segments: list[Segment]) -> None: - raster_map: dict[str, N64SegCi] = {} - palette_map: dict[str, N64SegPalette] = {} + raster_map: Dict[str, N64SegCi] = {} + palette_map: Dict[str, N64SegPalette] = {} for segment in segments: if isinstance(segment, N64SegPalette): @@ -41,13 +43,13 @@ def process(segments: list[Segment]) -> None: for raster in raster_map.values(): for pal_name in raster.palette_names: - pal = global_ids.get(pal_name) + pal = global_ids.get(pal_name, None) if pal is not None: global_ids_not_seen.discard(pal_name) palettes_seen.discard(pal_name) raster.palettes.append(pal) else: - pal = palette_map.get(pal_name) + pal = palette_map.get(pal_name, None) if pal is not None: palettes_seen.discard(pal_name) @@ -57,7 +59,7 @@ def process(segments: list[Segment]) -> None: "Could not find palette: " + pal_name + ", referenced by raster: " - + raster.name, + + raster.name ) # Resolve "." palette links @@ -86,5 +88,5 @@ def process(segments: list[Segment]) -> None: if len(global_ids_not_seen) > 0: log.error( - f"Found no ci links to palettes with global_ids: {', '.join(global_ids_not_seen)}", + f"Found no ci links to palettes with global_ids: {', '.join(global_ids_not_seen)}" ) diff --git a/src/splat/util/progress_bar.py b/src/splat/util/progress_bar.py index 3ce7696e..12b2ee3b 100644 --- a/src/splat/util/progress_bar.py +++ b/src/splat/util/progress_bar.py @@ -1,8 +1,7 @@ from __future__ import annotations -import sys - import tqdm +import sys out_file = sys.stderr diff --git a/src/splat/util/ps2/ps2elfinfo.py b/src/splat/util/ps2/ps2elfinfo.py index 36aa0743..2b9419fe 100644 --- a/src/splat/util/ps2/ps2elfinfo.py +++ b/src/splat/util/ps2/ps2elfinfo.py @@ -3,20 +3,18 @@ from __future__ import annotations import dataclasses -from typing import TYPE_CHECKING - +from pathlib import Path import spimdisasm from spimdisasm.elf32 import ( - Elf32Constants, Elf32File, - Elf32ObjectFileType, + Elf32Constants, Elf32SectionHeaderFlag, + Elf32ObjectFileType, ) +from typing import Optional from .. import log -if TYPE_CHECKING: - from pathlib import Path ELF_SECTION_MAPPING: dict[str, str] = { ".text": "asm", @@ -58,11 +56,11 @@ class Ps2Elf: size: int compiler: str elf_section_names: list[tuple[str, bool]] - gp: int | None - ld_gp_expression: str | None + gp: Optional[int] + ld_gp_expression: Optional[str] @staticmethod - def get_info(elf_path: Path, elf_bytes: bytes) -> Ps2Elf | None: + def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: # Avoid spimdisasm from complaining about unknown sections. spimdisasm.common.GlobalConfig.QUIET = True @@ -83,7 +81,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Ps2Elf | None: gp = elf.reginfo.gpValue else: gp = None - first_small_section_info: tuple[str, int] | None = None + first_small_section_info: Optional[tuple[str, int]] = None first_segment_name = "cod" segs = [FakeSegment(first_segment_name, 0, 0, [])] @@ -93,7 +91,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Ps2Elf | None: elf_section_names: list[tuple[str, bool]] = [] - first_offset: int | None = None + first_offset: Optional[int] = None rom_size = 0 previous_type = Elf32Constants.Elf32SectionHeaderType.PROGBITS diff --git a/src/splat/util/relocs.py b/src/splat/util/relocs.py index 964aefce..5c7c02a9 100644 --- a/src/splat/util/relocs.py +++ b/src/splat/util/relocs.py @@ -1,10 +1,11 @@ from __future__ import annotations from dataclasses import dataclass +from typing import Dict import spimdisasm -from . import log, options, progress_bar, symbols +from . import log, options, symbols, progress_bar @dataclass @@ -16,7 +17,7 @@ class Reloc: addend: int = 0 -all_relocs: dict[int, Reloc] = {} +all_relocs: Dict[int, Reloc] = {} def add_reloc(reloc: Reloc) -> None: @@ -63,13 +64,13 @@ def initialize() -> None: if attr_name == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute name in '{info}', is there extra whitespace?", + f"Missing attribute name in '{info}', is there extra whitespace?" ) log.error("") if attr_val == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute value in '{info}', is there extra whitespace?", + f"Missing attribute value in '{info}', is there extra whitespace?" ) log.error("") @@ -110,7 +111,7 @@ def initialize() -> None: if reloc.rom_address in all_relocs: log.parsing_error_preamble(path, line_num, line) log.error( - f"Duplicated 'rom' address for reloc: 0x{reloc.rom_address:X}", + f"Duplicated 'rom' address for reloc: 0x{reloc.rom_address:X}" ) add_reloc(reloc) @@ -121,9 +122,9 @@ def initialize_spim_context() -> None: if reloc_type is None: log.error( - f"Reloc type '{reloc.reloc_type}' is not valid. Rom address: 0x{rom_address:X}", + f"Reloc type '{reloc.reloc_type}' is not valid. Rom address: 0x{rom_address:X}" ) symbols.spim_context.addGlobalReloc( - rom_address, reloc_type, reloc.symbol_name, addend=reloc.addend, + rom_address, reloc_type, reloc.symbol_name, addend=reloc.addend ) diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index 059aff74..af755dea 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -8,14 +8,15 @@ def fmt_size(size: int) -> str: if size > 1000000: return f"{size // 1000000} MB" - if size > 1000: + elif size > 1000: return f"{size // 1000} KB" - return f"{size} B" + else: + return f"{size} B" class Statistics: - __slots__ = ("seg_cached", "seg_sizes", "seg_split") - + __slots__ = ("seg_sizes", "seg_split", "seg_cached") + def __init__(self) -> None: self.seg_sizes: dict[str, int] = {} self.seg_split: dict[str, int] = {} @@ -48,14 +49,14 @@ def print_statistics(self, total_size: int) -> None: unk_ratio = unk_size / total_size log.write( - f"Split {fmt_size(rest_size)} ({known_ratio:.2%}) in defined segments", + f"Split {fmt_size(rest_size)} ({known_ratio:.2%}) in defined segments" ) for typ, size in self.seg_sizes.items(): if typ != "unk": tmp_ratio = size / total_size log.write( - f"{typ:>20}: {fmt_size(size):>8} ({tmp_ratio:.2%}) {Fore.GREEN}{self.seg_split.get(typ, 0)} split{Style.RESET_ALL}, {Style.DIM}{self.seg_cached.get(typ, 0)} cached", + f"{typ:>20}: {fmt_size(size):>8} ({tmp_ratio:.2%}) {Fore.GREEN}{self.seg_split.get(typ, 0)} split{Style.RESET_ALL}, {Style.DIM}{self.seg_cached.get(typ, 0)} cached" ) log.write( - f"{'unknown':>20}: {fmt_size(unk_size):>8} ({unk_ratio:.2%}) from unknown bin files", + f"{'unknown':>20}: {fmt_size(unk_size):>8} ({unk_ratio:.2%}) from unknown bin files" ) diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index 09d83786..b90d3620 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -1,18 +1,17 @@ from __future__ import annotations -import re from dataclasses import dataclass +import re from typing import TYPE_CHECKING import spimdisasm -from intervaltree import IntervalTree +from intervaltree import IntervalTree from ..disassembler import disassembler_instance +from pathlib import Path # circular import if TYPE_CHECKING: - from pathlib import Path - from ..segtypes.segment import Segment from . import log, options, progress_bar @@ -41,15 +40,18 @@ def check_valid_type(typename: str) -> bool: if typename in splat_sym_types: return True - return typename in disassembler_instance.get_instance().known_types() + if typename in disassembler_instance.get_instance().known_types(): + return True + + return False -def is_truey(string: str) -> bool: - return string.lower() in TRUEY_VALS +def is_truey(str: str) -> bool: + return str.lower() in TRUEY_VALS -def is_falsey(string: str) -> bool: - return string.lower() in FALSEY_VALS +def is_falsey(str: str) -> bool: + return str.lower() in FALSEY_VALS def add_symbol(sym: Symbol) -> None: @@ -74,7 +76,7 @@ def to_cname(symbol_name: str) -> str: def handle_sym_addrs( - path: Path, sym_addrs_lines: list[str], all_segments: list[Segment], + path: Path, sym_addrs_lines: list[str], all_segments: list[Segment] ) -> None: def get_seg_for_name(name: str) -> Segment | None: for segment in all_segments: @@ -94,7 +96,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: line: str for line_num, line in enumerate(prog_bar): line = line.strip() - if line != "" and not line.startswith("//"): + if not line == "" and not line.startswith("//"): comment_loc = line.find("//") line_main = line line_ext = "" @@ -131,13 +133,13 @@ def get_seg_for_rom(rom: int) -> Segment | None: if attr_name == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute name in '{info}', is there extra whitespace?", + f"Missing attribute name in '{info}', is there extra whitespace?" ) log.error("") if attr_val == "": log.parsing_error_preamble(path, line_num, line) log.write( - f"Missing attribute value in '{info}', is there extra whitespace?", + f"Missing attribute value in '{info}', is there extra whitespace?" ) log.error("") @@ -147,20 +149,20 @@ def get_seg_for_rom(rom: int) -> Segment | None: if not check_valid_type(attr_val): log.parsing_error_preamble(path, line_num, line) log.write( - f"Unrecognized symbol type in '{info}', it should be one of", + f"Unrecognized symbol type in '{info}', it should be one of" ) log.write( [ *splat_sym_types, *spimdisasm.common.gKnownTypes, - ], + ] ) log.write( - "You may use a custom type that starts with a capital letter", + "You may use a custom type that starts with a capital letter" ) log.error("") - type_ = attr_val - sym.type = type_ + type = attr_val + sym.type = type continue if attr_name == "size": size = int(attr_val, 0) @@ -198,18 +200,18 @@ def get_seg_for_rom(rom: int) -> Segment | None: if align < 0: log.parsing_error_preamble(path, line_num, line) log.error( - f"The given alignment for '{sym.name}' (0x{sym.vram_start:08X}) is negative.", + f"The given alignment for '{sym.name}' (0x{sym.vram_start:08X}) is negative." ) align_shift = (align & (-align)).bit_length() - 1 if (1 << align_shift) != align: log.parsing_error_preamble(path, line_num, line) log.error( - f"The given alignment '0x{align:X}' for symbol '{sym.name}' (0x{sym.vram_start:08X}) is not a power of two.", + f"The given alignment '0x{align:X}' for symbol '{sym.name}' (0x{sym.vram_start:08X}) is not a power of two." ) if sym.vram_start % align != 0: log.parsing_error_preamble(path, line_num, line) log.error( - f"The symbol '{sym.name}' (0x{sym.vram_start:08X}) is not aligned already to the given alignment '0x{align:X}'.", + f"The symbol '{sym.name}' (0x{sym.vram_start:08X}) is not aligned already to the given alignment '0x{align:X}'." ) sym.given_align = align @@ -217,7 +219,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: except: log.parsing_error_preamble(path, line_num, line) log.write( - f"value of attribute '{attr_name}' could not be read:", + f"value of attribute '{attr_name}' could not be read:" ) log.write("") raise @@ -233,7 +235,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: if tf_val is None: log.parsing_error_preamble(path, line_num, line) log.write( - f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of", + f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of" ) log.write([*TRUEY_VALS, *FALSEY_VALS]) log.error("") @@ -274,7 +276,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: ignored_addresses.add(sym.vram_start) else: spim_context.addBannedSymbolRangeBySize( - sym.vram_start, sym.given_size, + sym.vram_start, sym.given_size ) continue @@ -292,7 +294,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: if not sym.allow_duplicated or not item.allow_duplicated: log.parsing_error_preamble(path, line_num, line) log.error( - f"Duplicate symbol detected! {sym.name} has already been defined at vram 0x{item.vram_start:08X}", + f"Duplicate symbol detected! {sym.name} has already been defined at vram 0x{item.vram_start:08X}" ) if addr in all_symbols_dict: @@ -301,14 +303,12 @@ def get_seg_for_rom(rom: int) -> Segment | None: have_same_rom_addresses = sym.rom == item.rom same_segment = sym.segment == item.segment - if ( - have_same_rom_addresses and same_segment - and (not sym.allow_duplicated or not item.allow_duplicated) - ): - log.parsing_error_preamble(path, line_num, line) - log.error( - f"Duplicate symbol detected! {sym.name} clashes with {item.name} defined at vram 0x{addr:08X}.\n If this is intended, specify either a segment or a rom address for this symbol", - ) + if have_same_rom_addresses and same_segment: + if not sym.allow_duplicated or not item.allow_duplicated: + log.parsing_error_preamble(path, line_num, line) + log.error( + f"Duplicate symbol detected! {sym.name} clashes with {item.name} defined at vram 0x{addr:08X}.\n If this is intended, specify either a segment or a rom address for this symbol" + ) if len(sym.filename) > 253 or any( c in ILLEGAL_FILENAME_CHARS for c in sym.filename @@ -324,7 +324,7 @@ def get_seg_for_rom(rom: int) -> Segment | None: f" which will be a problem when writing the symbol to its own file.\n" f" To fix this specify a `filename` for this symbol, like `filename:func_{sym.vram_start:08X}`.\n" f" Make sure the filename does not exceed 253 bytes nor it contains any of the following characters:\n" - f" {ILLEGAL_FILENAME_CHARS}", + f" {ILLEGAL_FILENAME_CHARS}" ) seen_symbols[sym.name] = sym @@ -382,7 +382,9 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: ram_id = segment.get_exclusive_ram_id() if ram_id is None: - if global_vram_start is None or segment.vram_start < global_vram_start: + if global_vram_start is None: + global_vram_start = segment.vram_start + elif segment.vram_start < global_vram_start: global_vram_start = segment.vram_start if global_vram_end is None: @@ -394,10 +396,14 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: # Global segment *after* overlay segments? global_segments_after_overlays.append(segment) - if global_vrom_start is None or segment.rom_start < global_vrom_start: + if global_vrom_start is None: + global_vrom_start = segment.rom_start + elif segment.rom_start < global_vrom_start: global_vrom_start = segment.rom_start - if global_vrom_end is None or global_vrom_end < segment.rom_end: + if global_vrom_end is None: + global_vrom_end = segment.rom_end + elif global_vrom_end < segment.rom_end: global_vrom_end = segment.rom_end elif segment.vram_start != segment.vram_end: @@ -424,7 +430,7 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: and global_vrom_end is not None ): spim_context.changeGlobalSegmentRanges( - global_vrom_start, global_vrom_end, global_vram_start, global_vram_end, + global_vrom_start, global_vrom_end, global_vram_start, global_vram_end ) overlaps_found = False @@ -447,7 +453,7 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: "Many overlaps between non-global and global segments were found.", ) log.write( - "This is usually caused by missing `exclusive_ram_id` tags on segments that have a higher vram address than other `exclusive_ram_id`-tagged segments", + "This is usually caused by missing `exclusive_ram_id` tags on segments that have a higher vram address than other `exclusive_ram_id`-tagged segments" ) if len(global_segments_after_overlays) > 0: log.write( @@ -489,27 +495,27 @@ def initialize_spim_context(all_segments: list[Segment]) -> None: def add_symbol_to_spim_segment( - segment: spimdisasm.common.SymbolsSegment, sym: Symbol, + segment: spimdisasm.common.SymbolsSegment, sym: Symbol ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = segment.addFunction( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom ) elif sym.type == "jtbl": context_sym = segment.addJumpTable( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom ) elif sym.type == "jtbl_label": context_sym = segment.addJumpTableLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom ) elif sym.type == "label": context_sym = segment.addBranchLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom ) else: context_sym = segment.addSymbol( - sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, vromAddress=sym.rom ) if sym.type is not None: context_sym.type = sym.type @@ -547,27 +553,27 @@ def add_symbol_to_spim_segment( def add_symbol_to_spim_section( - section: spimdisasm.mips.sections.SectionBase, sym: Symbol, + section: spimdisasm.mips.sections.SectionBase, sym: Symbol ) -> spimdisasm.common.ContextSymbol: if sym.type == "func": context_sym = section.addFunction( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom ) elif sym.type == "jtbl": context_sym = section.addJumpTable( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom ) elif sym.type == "jtbl_label": context_sym = section.addJumpTableLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom ) elif sym.type == "label": context_sym = section.addBranchLabel( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom ) else: context_sym = section.addSymbol( - sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom, + sym.vram_start, isAutogenerated=not sym.user_declared, symbolVrom=sym.rom ) if sym.type is not None: context_sym.type = sym.type @@ -632,7 +638,7 @@ def create_symbol_from_spim_symbol( in_segment = segment.contains_vram(context_sym.vram) sym = segment.create_symbol( - context_sym.vram, force_in_segment or in_segment, type=sym_type, reference=True, + context_sym.vram, force_in_segment or in_segment, type=sym_type, reference=True ) if sym.given_name is None and context_sym.name is not None: @@ -713,15 +719,15 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash((self.vram_start, self.segment)) - def format_name(self, format_: str) -> str: - ret = format_ + def format_name(self, format: str) -> str: + ret = format ret = ret.replace("$VRAM", f"{self.vram_start:08X}") if "$ROM" in ret: if not isinstance(self.rom, int): log.error( - f"Attempting to rom-name a symbol with no ROM address: {self.vram_start:08X} typed {self.type}", + f"Attempting to rom-name a symbol with no ROM address: {self.vram_start:08X} typed {self.type}" ) ret = ret.replace("$ROM", f"{self.rom:X}") @@ -736,11 +742,9 @@ def format_name(self, format_: str) -> str: @property def default_name(self) -> str: - if ( - self._generated_default_name is not None - and self.type == self._last_type - ): - return self._generated_default_name + if self._generated_default_name is not None: + if self.type == self._last_type: + return self._generated_default_name if self.segment: if isinstance(self.rom, int): diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index e9234c13..7994d063 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -54,7 +54,6 @@ class SerializedSegmentData(TypedDict): find_file_boundaries: NotRequired[bool] - def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: global _vram_classes @@ -94,24 +93,20 @@ def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: vram_symbol = vram_class["vram_symbol"] if not isinstance(vram_symbol, str): log.error( - f"vram_symbol ({vram_symbol})must be a string, got {type(vram_symbol)}", ) if "follows_classes" in vram_class: follows_classes = vram_class["follows_classes"] if not isinstance(follows_classes, list): log.error( - f"vram_symbol ({follows_classes})must be a list, got {type(follows_classes)}", ) for follows_class in follows_classes: if follows_class not in class_names: log.error( - f"follows_class ({follows_class}) not found in vram_classes", ) elif isinstance(vram_class, list): if len(vram_class) != 2: log.error( - f"vram_class ({vram_class}) must have 2 elements, got {len(vram_class)}", ) name = vram_class[0] vram = int(vram_class[1]) diff --git a/test.py b/test.py index cc63a543..d8dfc9b0 100755 --- a/test.py +++ b/test.py @@ -1,38 +1,37 @@ #!/usr/bin/env python3 -from __future__ import annotations - import difflib import filecmp -import unittest +import io from pathlib import Path - import spimdisasm +import unittest +from typing import List, Tuple from src.splat import __version__ from src.splat.disassembler import disassembler_instance from src.splat.scripts.split import main -from src.splat.segtypes.common.bss import CommonSegBss -from src.splat.segtypes.common.c import CommonSegC -from src.splat.segtypes.common.code import CommonSegCode +from src.splat.util import symbols, options from src.splat.segtypes.common.rodata import CommonSegRodata +from src.splat.segtypes.common.code import CommonSegCode +from src.splat.segtypes.common.c import CommonSegC +from src.splat.segtypes.common.bss import CommonSegBss from src.splat.segtypes.segment import Segment -from src.splat.util import options, symbols class Testing(unittest.TestCase): def compare_files(self, test_path, ref_path): - with open(test_path) as test_f, open(ref_path) as ref_f: + with io.open(test_path) as test_f, io.open(ref_path) as ref_f: self.assertListEqual(list(test_f), list(ref_f)) - def get_same_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): + def get_same_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): for name in dcmp.same_files: out.append((name, dcmp.left, dcmp.right)) for sub_dcmp in dcmp.subdirs.values(): self.get_same_files(sub_dcmp, out) - def get_diff_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): + def get_diff_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): for name in dcmp.diff_files: out.append((name, dcmp.left, dcmp.right)) @@ -40,7 +39,7 @@ def get_diff_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): self.get_diff_files(sub_dcmp, out) def get_left_only_files( - self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]], + self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] ): for name in dcmp.left_only: out.append((name, dcmp.left, dcmp.right)) @@ -49,7 +48,7 @@ def get_left_only_files( self.get_left_only_files(sub_dcmp, out) def get_right_only_files( - self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]], + self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] ): for name in dcmp.right_only: out.append((name, dcmp.left, dcmp.right)) @@ -62,19 +61,19 @@ def test_basic_app(self): main([Path("test/basic_app/splat.yaml")], None, False) comparison = filecmp.dircmp( - "test/basic_app/split", "test/basic_app/expected", [".gitkeep"], + "test/basic_app/split", "test/basic_app/expected", [".gitkeep"] ) - diff_files: list[tuple[str, str, str]] = [] + diff_files: List[Tuple[str, str, str]] = [] self.get_diff_files(comparison, diff_files) - same_files: list[tuple[str, str, str]] = [] + same_files: List[Tuple[str, str, str]] = [] self.get_same_files(comparison, same_files) - left_only_files: list[tuple[str, str, str]] = [] + left_only_files: List[Tuple[str, str, str]] = [] self.get_left_only_files(comparison, left_only_files) - right_only_files: list[tuple[str, str, str]] = [] + right_only_files: List[Tuple[str, str, str]] = [] self.get_right_only_files(comparison, right_only_files) print("same_files", same_files) @@ -109,7 +108,7 @@ def test_basic_app(self): file2_lines = file2.readlines() for line in difflib.unified_diff( - file1_lines, file2_lines, fromfile="file1", tofile="file2", lineterm="", + file1_lines, file2_lines, fromfile="file1", tofile="file2", lineterm="" ): print(line) @@ -163,8 +162,8 @@ def test_check_valid_type(self): splat_sym_types = {"func", "jtbl", "jtbl_label", "label"} - for type_ in splat_sym_types: - assert symbols.check_valid_type(type_) + for type in splat_sym_types: + assert symbols.check_valid_type(type) spim_types = [ "char*", @@ -183,8 +182,8 @@ def test_check_valid_type(self): "s32", ] - for type_ in spim_types: - assert symbols.check_valid_type(type_) + for type in spim_types: + assert symbols.check_valid_type(type) def test_add_symbol_to_spim_segment(self): segment = spimdisasm.common.SymbolsSegment( @@ -241,7 +240,7 @@ def test_create_symbol_from_spim_symbol(self): ) context_sym = spimdisasm.common.ContextSymbol(address=0) result = symbols.create_symbol_from_spim_symbol( - segment, context_sym, force_in_segment=False, + segment, context_sym, force_in_segment=False ) assert result.referenced assert result.extract @@ -363,7 +362,7 @@ def test_disassemble_data(self): assert bss.spim_section is not None assert isinstance( - bss.spim_section.get_section(), spimdisasm.mips.sections.SectionBss, + bss.spim_section.get_section(), spimdisasm.mips.sections.SectionBss ) assert bss.spim_section.get_section().bssVramStart == 0x40000000 assert bss.spim_section.get_section().bssVramEnd == 0x300 @@ -375,7 +374,7 @@ def test_attrs(self): test_init() sym_addrs_lines = [ - "func_1 = 0x100; // type:func size:10 rom:100 segment:test_segment name_end:the_name_end ", + "func_1 = 0x100; // type:func size:10 rom:100 segment:test_segment name_end:the_name_end " ] all_segments = [ @@ -387,7 +386,7 @@ def test_attrs(self): vram_start=0x300, args=[], yaml={}, - ), + ) ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -404,7 +403,7 @@ def test_boolean_attrs(self): sym_addrs_lines = [ "func_1 = 0x100; // defined:True extract:True force_migration:True force_not_migration:True " - "allow_addend:True dont_allow_addend:True", + "allow_addend:True dont_allow_addend:True" ] all_segments = [ @@ -416,7 +415,7 @@ def test_boolean_attrs(self): vram_start=0x300, args=[], yaml={}, - ), + ) ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -442,7 +441,7 @@ def test_ignore(self): vram_start=0x300, args=[], yaml={}, - ), + ) ] symbols.handle_sym_addrs(Path("/tmp/thing"), sym_addrs_lines, all_segments) @@ -471,7 +470,7 @@ def test_overlay(self): ], } - all_segments: list[Segment] = [ + all_segments: List["Segment"] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -480,7 +479,7 @@ def test_overlay(self): vram_start=0x80000400, args=[], yaml=yaml, - ), + ) ] # force this since it's hard to set up @@ -513,7 +512,7 @@ def test_global(self): ], } - all_segments: list[Segment] = [ + all_segments: List["Segment"] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -522,7 +521,7 @@ def test_global(self): vram_start=0x100, args=[], yaml=yaml, - ), + ) ] assert symbols.spim_context.globalSegment.vramStart == 0x80000000 From 20f8c72c1c82f2c6079dd5bb0f38b8ee6f167042 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:44:55 -0600 Subject: [PATCH 10/19] Fix syntax error --- src/splat/util/n64/rominfo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index c11f4c23..00785442 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -387,6 +387,7 @@ def swap_bytes(data: bytes) -> bytes: return bytes( itertools.chain.from_iterable( struct.pack(">H", x) for (x,) in struct.iter_unpack(" Date: Fri, 20 Feb 2026 20:50:25 -0600 Subject: [PATCH 11/19] Disable strict mode for now --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63310af9..ce3bd825 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,9 +68,9 @@ disallow_any_unimported = true ignore_missing_imports = true local_partial_types = true no_implicit_optional = true -strict = true +#strict = true warn_unreachable = true -check_untyped_defs = true +check_untyped_defs = false # TODO: change true [tool.ruff.lint.isort] combine-as-imports = true From bb1a107ac0006485cdd9d28a7d14d7937a12d8e5 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:54:09 -0600 Subject: [PATCH 12/19] Fix a ton of mypy issues --- pyproject.toml | 2 +- .../disassembler/disassembler_instance.py | 1 - src/splat/scripts/split.py | 113 ++++++++++-------- src/splat/segtypes/common/bss.py | 11 +- src/splat/segtypes/common/c.py | 100 ++++++++-------- src/splat/segtypes/common/code.py | 51 ++++---- src/splat/segtypes/common/data.py | 29 +++-- src/splat/segtypes/common/eh_frame.py | 5 +- src/splat/segtypes/common/gcc_except_table.py | 7 +- src/splat/segtypes/common/group.py | 3 +- src/splat/segtypes/common/rodata.py | 21 ++-- src/splat/segtypes/n64/ci.py | 19 +-- src/splat/segtypes/n64/gfx.py | 66 +++++----- src/splat/segtypes/n64/img.py | 29 +++-- src/splat/segtypes/n64/palette.py | 29 +++-- src/splat/segtypes/n64/vtx.py | 39 +++--- src/splat/segtypes/ps2/ctor.py | 5 +- src/splat/segtypes/ps2/lit4.py | 5 +- src/splat/segtypes/ps2/lit8.py | 5 +- src/splat/segtypes/ps2/vtables.py | 5 +- src/splat/segtypes/segment.py | 2 +- src/splat/util/color.py | 6 +- src/splat/util/n64/rominfo.py | 4 + src/splat/util/progress_bar.py | 8 +- src/splat/util/vram_classes.py | 10 ++ 25 files changed, 337 insertions(+), 238 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ce3bd825..778deabb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ show_column_numbers = true show_error_codes = true show_traceback = true disallow_any_decorated = true -disallow_any_unimported = true +disallow_any_unimported = false # TODO: change true ignore_missing_imports = true local_partial_types = true no_implicit_optional = true diff --git a/src/splat/disassembler/disassembler_instance.py b/src/splat/disassembler/disassembler_instance.py index 1745a442..cb1f2009 100644 --- a/src/splat/disassembler/disassembler_instance.py +++ b/src/splat/disassembler/disassembler_instance.py @@ -27,5 +27,4 @@ def get_instance() -> Disassembler: global __initialized if not __initialized: raise Exception("Disassembler instance not initialized") - return None return __instance diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index d2a1af1c..9a909af8 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -1,9 +1,11 @@ #! /usr/bin/env python3 +from __future__ import annotations + import argparse import hashlib import importlib -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import Any, Dict, List, Optional, Set, Tuple, Union, TYPE_CHECKING from pathlib import Path from collections import defaultdict, deque @@ -24,22 +26,25 @@ from ..segtypes.common.group import CommonSegGroup from ..util import conf, log, options, palettes, symbols, relocs +if TYPE_CHECKING: + from types import ModuleType + linker_writer: LinkerWriter -config: Dict[str, Any] +config: dict[str, Any] segment_roms: IntervalTree = IntervalTree() segment_rams: IntervalTree = IntervalTree() -def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: +def initialize_segments(config_segments: dict | list) -> list[Segment]: global segment_roms global segment_rams segment_roms = IntervalTree() segment_rams = IntervalTree() - segments_by_name: Dict[str, Segment] = {} - ret: List[Segment] = [] + segments_by_name: dict[str, Segment] = {} + ret: list[Segment] = [] # Cross segment pairing can be quite expensive, so we try to avoid it if the user haven't requested it. do_cross_segment_pairing = False @@ -77,7 +82,11 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]: next_start = last_rom_end segment: Segment = Segment.from_yaml( - segment_class, seg_yaml, this_start, next_start, None + segment_class, + seg_yaml, + this_start, + next_start, + None, ) if segment.require_unique_name: @@ -174,7 +183,7 @@ def assign_symbols_to_segments() -> None: continue if symbol.rom: - cands: Set[Interval] = segment_roms[symbol.rom] + cands: set[Interval] = segment_roms[symbol.rom] if len(cands) > 1: log.error("multiple segments rom overlap symbol", symbol) elif len(cands) == 0: @@ -191,7 +200,7 @@ def assign_symbols_to_segments() -> None: seg.add_symbol(symbol) -def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str: +def brief_seg_name(seg: Segment, limit: int, ellipsis: str = "…") -> str: s = seg.name.strip() if len(s) > limit: return s[:limit].strip() + ellipsis @@ -200,8 +209,8 @@ def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str: # Return a mapping of vram classes to segments that need to be part of their vram symbol's calculation def calc_segment_dependences( - all_segments: List[Segment], -) -> Dict[vram_classes.VramClass, List[Segment]]: + all_segments: list[Segment], +) -> dict[vram_classes.VramClass, list[Segment]]: # Map vram class names to segments that have that vram class vram_class_to_segments: Dict[str, List[Segment]] = {} for seg in all_segments: @@ -225,8 +234,8 @@ def calc_segment_dependences( def sort_segments_by_vram_class_dependency( - all_segments: List[Segment], -) -> List[Segment]: + all_segments: list[Segment], +) -> list[Segment]: # map all "_VRAM_END" strings to segments end_sym_to_seg: Dict[str, Segment] = {} for seg in all_segments: @@ -279,7 +288,7 @@ def read_target_binary() -> bytes: return rom_bytes -def initialize_platform(rom_bytes: bytes): +def initialize_platform(rom_bytes: bytes) -> ModuleType: platform_module = importlib.import_module( f"{__package_name__}.platforms.{options.opts.platform}" ) @@ -289,7 +298,7 @@ def initialize_platform(rom_bytes: bytes): return platform_module -def initialize_all_symbols(all_segments: List[Segment]): +def initialize_all_symbols(all_segments: list[Segment]) -> None: # Load and process symbols symbols.initialize(all_segments) relocs.initialize() @@ -303,12 +312,12 @@ def initialize_all_symbols(all_segments: List[Segment]): def do_scan( - all_segments: List[Segment], + all_segments: list[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, -): - processed_segments: List[Segment] = [] +) -> list[Segment]: + processed_segments: list[Segment] = [] scan_bar = progress_bar.get_progress_bar(all_segments) for segment in scan_bar: @@ -336,11 +345,11 @@ def do_scan( def do_split( - all_segments: List[Segment], + all_segments: list[Segment], rom_bytes: bytes, stats: statistics.Statistics, cache: cache_handler.Cache, -): +) -> None: split_bar = progress_bar.get_progress_bar(all_segments) for segment in split_bar: assert isinstance(segment, Segment) @@ -441,7 +450,7 @@ def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: return linker_writer -def write_ld_dependencies(linker_writer: LinkerWriter): +def write_ld_dependencies(linker_writer: LinkerWriter) -> None: if options.opts.ld_dependencies: elf_path = options.opts.elf_path if elf_path is None: @@ -453,7 +462,7 @@ def write_ld_dependencies(linker_writer: LinkerWriter): ) -def write_elf_sections_file(all_segments: List[Segment]): +def write_elf_sections_file(all_segments: list[Segment]) -> None: # write elf_sections.txt - this only lists the generated sections in the elf, not subsections # that the elf combines into one section if options.opts.elf_section_list_path: @@ -465,40 +474,44 @@ def write_elf_sections_file(all_segments: List[Segment]): f.write(section_list) -def write_undefined_auto(to_write: List[symbols.Symbol], file_path: Path): +def write_undefined_auto(to_write: list[symbols.Symbol], file_path: Path) -> None: file_path.parent.mkdir(parents=True, exist_ok=True) with file_path.open("w", newline="\n") as f: for symbol in to_write: f.write(f"{symbol.name} = 0x{symbol.vram_start:X};\n") -def write_undefined_funcs_auto(): - if options.opts.create_undefined_funcs_auto: - to_write = [ - s - for s in symbols.all_symbols - if s.referenced and not s.defined and s.type == "func" - ] - to_write.sort(key=lambda x: x.vram_start) +def write_undefined_funcs_auto() -> None: + if not options.opts.create_undefined_funcs_auto: + return + + to_write = [ + s + for s in symbols.all_symbols + if s.referenced and not s.defined and s.type == "func" + ] + to_write.sort(key=lambda x: x.vram_start) + + write_undefined_auto(to_write, options.opts.undefined_funcs_auto_path) - write_undefined_auto(to_write, options.opts.undefined_funcs_auto_path) +def write_undefined_syms_auto() -> None: + if not options.opts.create_undefined_syms_auto: + return -def write_undefined_syms_auto(): - if options.opts.create_undefined_syms_auto: - to_write = [ - s - for s in symbols.all_symbols - if s.referenced - and not s.defined - and s.type not in {"func", "label", "jtbl_label"} - ] - to_write.sort(key=lambda x: x.vram_start) + to_write = [ + s + for s in symbols.all_symbols + if s.referenced + and not s.defined + and s.type not in {"func", "label", "jtbl_label"} + ] + to_write.sort(key=lambda x: x.vram_start) - write_undefined_auto(to_write, options.opts.undefined_syms_auto_path) + write_undefined_auto(to_write, options.opts.undefined_syms_auto_path) -def print_segment_warnings(all_segments: List[Segment]): +def print_segment_warnings(all_segments: list[Segment]) -> None: for segment in all_segments: if len(segment.warnings) > 0: log.write( @@ -539,15 +552,15 @@ def dump_symbols() -> None: def main( - config_path: List[Path], - modes: Optional[List[str]], + config_path: list[Path], + modes: list[str] | None, verbose: bool, use_cache: bool = True, skip_version_check: bool = False, stdout_only: bool = False, disassemble_all: bool = False, - make_full_disasm_for_code=False, -): + make_full_disasm_for_code: Any = False, +) -> None: if stdout_only: log.write("--stdout-only flag is deprecated", status="warn") progress_bar.out_file = sys.stdout @@ -620,7 +633,7 @@ def main( file_presets.write_all_files() -def add_arguments_to_parser(parser: argparse.ArgumentParser): +def add_arguments_to_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "config", help="path to a compatible config .yaml file", @@ -654,7 +667,7 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser): ) -def process_arguments(args: argparse.Namespace): +def process_arguments(args: argparse.Namespace) -> None: main( args.config, args.modes, @@ -670,7 +683,7 @@ def process_arguments(args: argparse.Namespace): script_description = "Split a rom given a rom, a config, and output directory" -def add_subparser(subparser: argparse._SubParsersAction): +def add_subparser(subparser: argparse._SubParsersAction) -> None: parser = subparser.add_parser( "split", help=script_description, description=script_description ) diff --git a/src/splat/segtypes/common/bss.py b/src/splat/segtypes/common/bss.py index 5a9adbc9..c749797e 100644 --- a/src/splat/segtypes/common/bss.py +++ b/src/splat/segtypes/common/bss.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ...util import options, symbols, log @@ -13,7 +13,7 @@ class CommonSegBss(CommonSegData): def get_linker_section(self) -> str: return ".bss" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" @staticmethod @@ -35,7 +35,7 @@ def configure_disassembler_section( pass - def disassemble_data(self, rom_bytes: bytes): + def disassemble_data(self, rom_bytes: bytes) -> None: if not options.opts.ld_bss_is_noload: super().disassemble_data(rom_bytes) return @@ -86,7 +86,10 @@ def disassemble_data(self, rom_bytes: bytes): self.spim_section.analyze() self.spim_section.set_comment_offset(self.rom_start) - for spim_sym in self.spim_section.get_section().symbolList: + section = self.spim_section.get_section() + assert section is not None + + for spim_sym in section.symbolList: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), spim_sym.contextSym, force_in_segment=True ) diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index 77990eb4..d4ec94b6 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import os import re from pathlib import Path -from typing import Optional, Set, List +from typing import TYPE_CHECKING import rabbitizer import spimdisasm @@ -13,6 +15,9 @@ from .codesubsegment import CommonSegCodeSubsegment from .rodata import CommonSegRodata +if TYPE_CHECKING: + from collections.abc import Generator + STRIP_C_COMMENTS_RE = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', @@ -27,37 +32,36 @@ class CommonSegC(CommonSegCodeSubsegment): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.defined_funcs: Set[str] = set() - self.global_asm_funcs: Set[str] = set() - self.global_asm_rodata_syms: Set[str] = set() + self.defined_funcs: set[str] = set() + self.global_asm_funcs: set[str] = set() + self.global_asm_rodata_syms: set[str] = set() self.file_extension = "c" self.use_gp_rel_macro = options.opts.use_gp_rel_macro_nonmatching @staticmethod - def strip_c_comments(text): - def replacer(match): + def strip_c_comments(text: str) -> str: + def replacer(match: re.Match[str]) -> str: s = match.group(0) if s.startswith("/"): return " " - else: - return s + return s return re.sub(STRIP_C_COMMENTS_RE, replacer, text) @staticmethod - def get_funcs_defined_in_c(c_file: Path) -> Set[str]: + def get_funcs_defined_in_c(c_file: Path) -> set[str]: with open(c_file, "r", encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) return set(m.group(1) for m in C_FUNC_RE.finditer(text)) @staticmethod - def find_all_instances(string: str, sub: str): + def find_all_instances(string: str, sub: str) -> Generator[int, None, None]: start = 0 while True: start = string.find(sub, start) @@ -67,7 +71,7 @@ def find_all_instances(string: str, sub: str): start += len(sub) @staticmethod - def get_close_parenthesis(string: str, pos: int): + def get_close_parenthesis(string: str, pos: int) -> int: paren_count = 0 while True: cur_char = string[pos] @@ -80,10 +84,10 @@ def get_close_parenthesis(string: str, pos: int): paren_count -= 1 pos += 1 - @staticmethod - def find_include_macro(text: str, macro_name: str): - for pos in CommonSegC.find_all_instances(text, f"{macro_name}("): - close_paren_pos = CommonSegC.get_close_parenthesis( + @classmethod + def find_include_macro(cls, text: str, macro_name: str) -> Generator[str, None, None]: + for pos in cls.find_all_instances(text, f"{macro_name}("): + close_paren_pos = cls.get_close_parenthesis( text, pos + len(f"{macro_name}(") ) macro_contents = text[pos:close_paren_pos] @@ -95,43 +99,43 @@ def find_include_macro(text: str, macro_name: str): if len(macro_args) >= 2: yield macro_args[1].strip(" )") - @staticmethod - def find_include_asm(text: str): - return CommonSegC.find_include_macro(text, "INCLUDE_ASM") + @classmethod + def find_include_asm(cls, text: str) -> Generator[str, None, None]: + return cls.find_include_macro(text, "INCLUDE_ASM") - @staticmethod - def find_include_rodata(text: str): - return CommonSegC.find_include_macro(text, "INCLUDE_RODATA") + @classmethod + def find_include_rodata(cls, text: str) -> Generator[str, None, None]: + return cls.find_include_macro(text, "INCLUDE_RODATA") - @staticmethod - def get_global_asm_funcs(c_file: Path) -> Set[str]: + @classmethod + def get_global_asm_funcs(cls, c_file: Path) -> set[str]: with c_file.open(encoding="utf-8") as f: - text = CommonSegC.strip_c_comments(f.read()) + text = cls.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) else: - return set(CommonSegC.find_include_asm(text)) + return set(cls.find_include_asm(text)) - @staticmethod - def get_global_asm_rodata_syms(c_file: Path) -> Set[str]: + @classmethod + def get_global_asm_rodata_syms(cls, c_file: Path) -> set[str]: with c_file.open(encoding="utf-8") as f: - text = CommonSegC.strip_c_comments(f.read()) + text = cls.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) else: - return set(CommonSegC.find_include_rodata(text)) + return set(cls.find_include_rodata(text)) @staticmethod def is_text() -> bool: return True - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return options.opts.src_path / self.dir / f"{self.name}.{self.file_extension}" - def scan(self, rom_bytes: bytes): + def scan(self, rom_bytes: bytes) -> None: if ( self.rom_start is not None and self.rom_end is not None @@ -150,7 +154,7 @@ def scan(self, rom_bytes: bytes): self.scan_code(rom_bytes) - def split(self, rom_bytes: bytes): + def split(self, rom_bytes: bytes) -> None: if self.is_auto_segment: if options.opts.make_full_disasm_for_code: self.split_as_asmtu_file(self.asm_out_path()) @@ -168,7 +172,7 @@ def split(self, rom_bytes: bytes): # We want to know if this C section has a corresponding rodata section so we can migrate its rodata rodata_section_type = "" - rodata_spim_segment: Optional[spimdisasm.mips.sections.SectionRodata] = None + rodata_spim_segment: spimdisasm.mips.sections.SectionRodata | spimdisasm.mips.sections.SectionBase | None = None if options.opts.migrate_rodata_to_functions: # We don't know if the rodata section is .rodata or .rdata, so we need to check both for sect in [".rodata", ".rdata"]: @@ -283,6 +287,7 @@ def split(self, rom_bytes: bytes): if options.opts.make_full_disasm_for_code: # Disable gpRelHack since this file is expected to be built with modern gas section = self.spim_section.get_section() + assert section is not None old_value = section.getGpRelHack() section.setGpRelHack(False) @@ -305,7 +310,7 @@ def split(self, rom_bytes: bytes): section.setGpRelHack(old_value) - def get_c_preamble(self): + def get_c_preamble(self) -> list[str]: ret = [] preamble = options.opts.generated_c_preamble @@ -317,8 +322,8 @@ def get_c_preamble(self): def check_gaps_in_migrated_rodata( self, func: spimdisasm.mips.symbols.SymbolFunction, - rodata_list: List[spimdisasm.mips.symbols.SymbolBase], - ): + rodata_list: list[spimdisasm.mips.symbols.SymbolBase], + ) -> None: for index in range(len(rodata_list) - 1): rodata_sym = rodata_list[index] next_rodata_sym = rodata_list[index + 1] @@ -342,7 +347,7 @@ def create_c_asm_file( func_rodata_entry: spimdisasm.mips.FunctionRodataEntry, out_dir: Path, func_sym: Symbol, - ): + ) -> None: outpath = out_dir / self.name / f"{func_sym.filename}.s" # Skip extraction if the file exists and the symbol is marked as extract=false @@ -380,7 +385,7 @@ def create_unmigrated_rodata_file( spim_rodata_sym: spimdisasm.mips.symbols.SymbolBase, out_dir: Path, rodata_sym: Symbol, - ): + ) -> None: outpath = out_dir / self.name / f"{rodata_sym.filename}.s" # Skip extraction if the file exists and the symbol is marked as extract=false @@ -389,7 +394,7 @@ def create_unmigrated_rodata_file( outpath.parent.mkdir(parents=True, exist_ok=True) - with outpath.open("w", newline="\n") as f: + with outpath.open("w", encoding="utf-8", newline="\n") as f: preamble = options.opts.generated_s_preamble if preamble: f.write(preamble + "\n") @@ -425,7 +430,7 @@ def get_c_lines_for_function( sym: Symbol, spim_sym: spimdisasm.mips.symbols.SymbolFunction, asm_out_dir: Path, - ) -> List[str]: + ) -> list[str]: c_lines = [] # Terrible hack to "auto-decompile" empty functions @@ -444,7 +449,7 @@ def get_c_lines_for_function( c_lines.append("") return c_lines - def get_c_lines_for_rodata_sym(self, sym: Symbol, asm_out_dir: Path): + def get_c_lines_for_rodata_sym(self, sym: Symbol, asm_out_dir: Path) -> list[str]: c_lines = [self.get_c_line_include_macro(sym, asm_out_dir, "INCLUDE_RODATA")] c_lines.append("") return c_lines @@ -453,8 +458,8 @@ def create_c_file( self, asm_out_dir: Path, c_path: Path, - symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], - ): + symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], + ) -> None: c_lines = self.get_c_preamble() for entry in symbols_entries: @@ -489,8 +494,8 @@ def create_asm_dependencies_file( c_path: Path, asm_out_dir: Path, is_new_c_file: bool, - symbols_entries: List[spimdisasm.mips.FunctionRodataEntry], - ): + symbols_entries: list[spimdisasm.mips.FunctionRodataEntry], + ) -> None: if not options.opts.create_asm_dependencies: return if ( @@ -536,3 +541,4 @@ def create_asm_dependencies_file( for depend_file in depend_list: f.write(f"{depend_file.as_posix()}:\n") + diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index ca556055..1429239f 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -1,30 +1,33 @@ from __future__ import annotations from collections import OrderedDict -from typing import List, Optional, Type, Tuple +from typing import TYPE_CHECKING, cast from ...util import log, options, utils from .group import CommonSegGroup from ..segment import Segment, parse_segment_align +if TYPE_CHECKING: + from ...util.vram_classes import SerializedSegmentData -def dotless_type(type: str) -> str: - return type[1:] if type[0] == "." else type + +def dotless_type(type_: str) -> str: + return type_[1:] if type_[0] == "." else type_ # code group class CommonSegCode(CommonSegGroup): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegmentData | list[str], + ) -> None: self.bss_size: int = yaml.get("bss_size", 0) if isinstance(yaml, dict) else 0 super().__init__( @@ -48,7 +51,7 @@ def needs_symbols(self) -> bool: return True @property - def vram_end(self) -> Optional[int]: + def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size + self.bss_size else: @@ -58,12 +61,12 @@ def vram_end(self) -> Optional[int]: def _generate_segment_from_all( self, rep_type: str, - replace_class: Type[Segment], + replace_class: type[Segment], base_name: str, base_seg: Segment, - rom_start: Optional[int] = None, - rom_end: Optional[int] = None, - vram_start: Optional[int] = None, + rom_start: int | None = None, + rom_end: int | None = None, + vram_start: int | None = None, ) -> Segment: rep: Segment = replace_class( rom_start=rom_start, @@ -72,7 +75,7 @@ def _generate_segment_from_all( name=base_name, vram_start=vram_start, args=[], - yaml={}, + yaml=cast(SerializedSegmentData, {}), ) rep.extract = False rep.given_subalign = self.given_subalign @@ -92,14 +95,14 @@ def _generate_segment_from_all( def _insert_all_auto_sections( self, - ret: List[Segment], + ret: list[Segment], base_segments: OrderedDict[str, Segment], readonly_before: bool, - ) -> List[Segment]: + ) -> list[Segment]: if len(options.opts.auto_link_sections) == 0: return ret - base_segments_list: List[Tuple[str, Segment]] = list(base_segments.items()) + base_segments_list: list[tuple[str, Segment]] = list(base_segments.items()) # Determine what will be the min insertion index last_inserted_index = len(base_segments_list) - 1 @@ -147,7 +150,7 @@ def _insert_all_auto_sections( return ret - def parse_subsegments(self, segment_yaml) -> List[Segment]: + def parse_subsegments(self, segment_yaml: dict[str, list[SerializedSegmentData | list[str]]]) -> list[Segment]: if "subsegments" not in segment_yaml: if not self.parent: raise Exception( @@ -156,9 +159,9 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: return [] base_segments: OrderedDict[str, Segment] = OrderedDict() - ret: List[Segment] = [] - prev_start: Optional[int] = -1 - prev_vram: Optional[int] = -1 + ret: list[Segment] = [] + prev_start: int | None = -1 + prev_vram: int | None = -1 last_rom_end = None @@ -196,7 +199,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: # First, try to get the end address from the next segment's start address # Second, try to get the end address from the estimated size of this segment # Third, try to get the end address from the next segment with a start address - end: Optional[int] = None + end: int | None = None if i < len(segment_yaml["subsegments"]) - 1: end, end_is_auto_segment = Segment.parse_segment_start( segment_yaml["subsegments"][i + 1] @@ -277,7 +280,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]: return ret - def scan(self, rom_bytes): + def scan(self, rom_bytes: bytes) -> None: # Always scan code first for sub in self.subsegments: if sub.is_text() and sub.should_scan(): diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index 19f7467f..78dca217 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -1,5 +1,7 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING from pathlib import Path -from typing import Optional, List from ...util import options, symbols, log from .codesubsegment import CommonSegCodeSubsegment @@ -7,6 +9,9 @@ from ...disassembler.disassembler_section import DisassemblerSection, make_data_section +if TYPE_CHECKING: + from ...segtypes.linker_entry import LinkerEntry + class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup): @staticmethod @@ -20,7 +25,7 @@ def asm_out_path(self) -> Path: return options.opts.data_path / self.dir / f"{self.name}.{typ}.s" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: if self.type.startswith("."): if self.sibling: # C file @@ -32,16 +37,16 @@ def out_path(self) -> Optional[Path]: # ASM return self.asm_out_path() - def scan(self, rom_bytes: bytes): + def scan(self, rom_bytes: bytes) -> None: CommonSegGroup.scan(self, rom_bytes) if self.rom_start is not None and self.rom_end is not None: self.disassemble_data(rom_bytes) - def get_asm_file_extra_directives(self) -> List[str]: + def get_asm_file_extra_directives(self) -> list[str]: return [] - def split(self, rom_bytes: bytes): + def split(self, rom_bytes: bytes) -> None: super().split(rom_bytes) if self.spim_section is None or not self.should_self_split(): @@ -66,10 +71,10 @@ def cache(self): def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" - def get_linker_entries(self): + def get_linker_entries(self) -> list[LinkerEntry]: return CommonSegCodeSubsegment.get_linker_entries(self) def configure_disassembler_section( @@ -78,6 +83,7 @@ def configure_disassembler_section( "Allows to configure the section before running the analysis on it" section = disassembler_section.get_section() + assert section is not None # Set data string encoding # First check the global configuration @@ -88,7 +94,7 @@ def configure_disassembler_section( if self.str_encoding is not None: section.stringEncoding = self.str_encoding - def disassemble_data(self, rom_bytes): + def disassemble_data(self, rom_bytes: bytes) -> None: if self.is_auto_segment: return @@ -128,14 +134,17 @@ def disassemble_data(self, rom_bytes): rodata_encountered = False - for symbol in self.spim_section.get_section().symbolList: + section = self.spim_section.get_section() + assert section is not None + + for symbol in section.symbolList: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), symbol.contextSym, force_in_segment=True ) # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: - context_sym = self.spim_section.get_section().getSymbol( + context_sym = section.getSymbol( referenced_vram, tryPlusOffset=False ) if context_sym is not None: diff --git a/src/splat/segtypes/common/eh_frame.py b/src/splat/segtypes/common/eh_frame.py index ef1a72b9..67e94702 100644 --- a/src/splat/segtypes/common/eh_frame.py +++ b/src/splat/segtypes/common/eh_frame.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from .data import CommonSegData from ...disassembler.disassembler_section import DisassemblerSection @@ -10,7 +10,7 @@ class CommonSegEh_frame(CommonSegData): def get_linker_section(self) -> str: return ".eh_frame" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "aw" def configure_disassembler_section( @@ -21,6 +21,7 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None # We use s32 to make sure spimdisasm disassembles the data from this section as words/references to other symbols section.enableStringGuessing = False diff --git a/src/splat/segtypes/common/gcc_except_table.py b/src/splat/segtypes/common/gcc_except_table.py index 1ff7d4c6..a0cf5529 100644 --- a/src/splat/segtypes/common/gcc_except_table.py +++ b/src/splat/segtypes/common/gcc_except_table.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from .data import CommonSegData from ...util import log @@ -15,7 +15,7 @@ class CommonSegGcc_except_table(CommonSegData): def get_linker_section(self) -> str: return ".gcc_except_table" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "aw" def configure_disassembler_section( @@ -26,10 +26,11 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None section.enableStringGuessing = False - def disassemble_data(self, rom_bytes): + def disassemble_data(self, rom_bytes: bytes) -> None: if self.is_auto_segment: return diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index f0b6cf9f..68a4ef0d 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -110,7 +110,8 @@ def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[st # and it has a rom size of zero end = last_rom_end - segment: Segment = segment_class.from_yaml( + segment: Segment = Segment.from_yaml( + segment_class, subsegment_yaml, start, end, diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index 39baeba9..e4a62092 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -1,4 +1,5 @@ -from typing import Optional, Set, Tuple, List +from __future__ import annotations + import spimdisasm from ..segment import Segment from ...util import log, options, symbols @@ -15,7 +16,7 @@ class CommonSegRodata(CommonSegData): def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" @staticmethod @@ -28,7 +29,7 @@ def is_rodata() -> bool: def get_possible_text_subsegment_for_symbol( self, rodata_sym: spimdisasm.mips.symbols.SymbolBase - ) -> Optional[Tuple[Segment, spimdisasm.common.ContextSymbol]]: + ) -> tuple[Segment, spimdisasm.common.ContextSymbol] | None: # Check if this rodata segment does not have a corresponding code file, try to look for one if self.sibling is not None or not options.opts.pair_rodata_to_text: @@ -53,6 +54,7 @@ def configure_disassembler_section( "Allows to configure the section before running the analysis on it" section = disassembler_section.get_section() + assert section is not None # Set rodata string encoding # First check the global configuration @@ -63,7 +65,7 @@ def configure_disassembler_section( if self.str_encoding is not None: section.stringEncoding = self.str_encoding - def disassemble_data(self, rom_bytes): + def disassemble_data(self, rom_bytes: bytes) -> None: if self.is_auto_segment: return @@ -101,12 +103,15 @@ def disassemble_data(self, rom_bytes): self.spim_section.analyze() self.spim_section.set_comment_offset(self.rom_start) - possible_text_segments: Set[Segment] = set() + possible_text_segments: set[Segment] = set() last_jumptable_addr_remainder = 0 - misaligned_jumptable_offsets: List[int] = [] + misaligned_jumptable_offsets: list[int] = [] + + section = self.spim_section.get_section() + assert section is not None - for symbol in self.spim_section.get_section().symbolList: + for symbol in section.symbolList: generated_symbol = symbols.create_symbol_from_spim_symbol( self.get_most_parent(), symbol.contextSym, force_in_segment=True ) @@ -114,7 +119,7 @@ def disassemble_data(self, rom_bytes): # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: - context_sym = self.spim_section.get_section().getSymbol( + context_sym = section.getSymbol( referenced_vram, tryPlusOffset=False ) if context_sym is not None: diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index 68e6e4da..7996f263 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from pathlib import Path -from typing import List, TYPE_CHECKING +from typing import TYPE_CHECKING from ...util import log, options @@ -7,12 +9,13 @@ if TYPE_CHECKING: from .palette import N64SegPalette + from ...utils.vram_classes import SerializedSegmentData # Base class for CI4/CI8 class N64SegCi(N64SegImg): - def parse_palette_names(self, yaml, args) -> List[str]: - ret = [self.name] + def parse_palette_names(self, yaml: SerializedSegmentData | list[str], args: list[str]) -> list[str]: + ret: list[str] | str = [self.name] if isinstance(yaml, dict): if "palettes" in yaml: ret = yaml["palettes"] @@ -20,19 +23,19 @@ def parse_palette_names(self, yaml, args) -> List[str]: ret = args[2] if isinstance(ret, str): - ret = [ret] + return [ret] return ret - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.palettes: "List[N64SegPalette]" = [] + self.palettes: "list[N64SegPalette]" = [] self.palette_names = self.parse_palette_names(self.yaml, self.args) def scan(self, rom_bytes: bytes) -> None: self.n64img.data = rom_bytes[self.rom_start : self.rom_end] - def out_path_pal(self, pal_name) -> Path: + def out_path_pal(self, pal_name: str) -> Path: type_extension = f".{self.type}" if options.opts.image_type_in_extension else "" if len(self.palettes) == 1: @@ -47,7 +50,7 @@ def out_path_pal(self, pal_name) -> Path: return options.opts.asset_path / self.dir / f"{out_name}{type_extension}.png" - def split(self, rom_bytes): + def split(self, rom_bytes: bytes) -> None: self.check_len() assert self.palettes is not None diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index dbf0afd3..fe62fde3 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -3,8 +3,10 @@ Dumps out Gfx[] as a .inc.c file. """ +from __future__ import annotations + import re -from typing import Dict, List, Optional, Union +from typing import TYPE_CHECKING from pathlib import Path @@ -44,20 +46,23 @@ from ...util import symbols +if TYPE_CHECKING: + from ...util.vram_classes import SerializedSegmentData + LIGHTS_RE = re.compile(r"\*\(Lightsn \*\)0x[0-9A-F]{8}") class N64SegGfx(CommonSegCodeSubsegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegmentData | list[str], + ) -> None: super().__init__( rom_start, rom_end, @@ -67,11 +72,11 @@ def __init__( args=args, yaml=yaml, ) - self.file_text = None + self.file_text: str | None = None self.data_only = isinstance(yaml, dict) and yaml.get("data_only", False) self.in_segment = not isinstance(yaml, dict) or yaml.get("in_segment", True) - def format_sym_name(self, sym) -> str: + def format_sym_name(self, sym: symbols.Symbol) -> str: return sym.name def get_linker_section(self) -> str: @@ -80,10 +85,10 @@ def get_linker_section(self) -> str: def out_path(self) -> Path: return options.opts.asset_path / self.dir / f"{self.name}.gfx.inc.c" - def scan(self, rom_bytes: bytes): + def scan(self, rom_bytes: bytes) -> None: self.file_text = self.disassemble_data(rom_bytes) - def get_gfxd_target(self): + def get_gfxd_target(self) -> gfxd_f3d: opt = options.opts.gfx_ucode if opt == "f3d": @@ -99,35 +104,35 @@ def get_gfxd_target(self): else: log.error(f"Unknown target {opt}") - def tlut_handler(self, addr, idx, count): + def tlut_handler(self, addr: int, idx: int, count: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def timg_handler(self, addr, fmt, size, width, height, pal): + def timg_handler(self, addr: int, fmt, size: int, width: int, height: int, pal) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def cimg_handler(self, addr, fmt, size, width): + def cimg_handler(self, addr: int, fmt, size: int, width: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def zimg_handler(self, addr): + def zimg_handler(self, addr: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def dl_handler(self, addr): + def dl_handler(self, addr: int) -> int: # Look for 'Gfx'-typed symbols first sym = self.retrieve_sym_type(symbols.all_symbols_dict, addr, "Gfx") @@ -138,28 +143,28 @@ def dl_handler(self, addr): gfxd_printf(self.format_sym_name(sym)) return 1 - def mtx_handler(self, addr): + def mtx_handler(self, addr: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 - def lookat_handler(self, addr, count): + def lookat_handler(self, addr: int, count: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def light_handler(self, addr, count): + def light_handler(self, addr: int, count: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(self.format_sym_name(sym)) return 1 - def vtx_handler(self, addr, count): + def vtx_handler(self, addr: int, count: int) -> int: # Look for 'Vtx'-typed symbols first sym = self.retrieve_sym_type(symbols.all_symbols_dict, addr, "Vtx") @@ -176,20 +181,20 @@ def vtx_handler(self, addr, count): gfxd_printf(f"&{self.format_sym_name(sym)}[{index}]") return 1 - def vp_handler(self, addr): + def vp_handler(self, addr: int) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) gfxd_printf(f"&{self.format_sym_name(sym)}") return 1 - def macro_fn(self): + def macro_fn(self) -> int: gfxd_puts(" ") gfxd_macro_dflt() gfxd_puts(",\n") return 0 - def disassemble_data(self, rom_bytes): + def disassemble_data(self, rom_bytes: bytes) -> str: assert isinstance(self.rom_start, int) assert isinstance(self.rom_end, int) assert isinstance(self.vram_start, int) @@ -246,7 +251,7 @@ def disassemble_data(self, rom_bytes): out_str += "};\n" # Poor man's light fix until we get my libgfxd PR merged - def light_sub_func(match): + def light_sub_func(match: re.Match[str]) -> str: light = match.group(0) addr = int(light[12:], 0) sym = self.create_symbol( @@ -258,11 +263,12 @@ def light_sub_func(match): return out_str - def split(self, rom_bytes: bytes): - if self.file_text and self.out_path(): - self.out_path().parent.mkdir(parents=True, exist_ok=True) + def split(self, rom_bytes: bytes) -> None: + out_path = self.out_path() + if self.file_text and out_path is not None: + out_path.parent.mkdir(parents=True, exist_ok=True) - with open(self.out_path(), "w", newline="\n") as f: + with open(self.out_path(), "w", encoding="utf-8", newline="\n") as f: f.write(self.file_text) def should_scan(self) -> bool: @@ -276,7 +282,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("gfx") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: + def estimate_size(yaml: SerializedSegmentData | list[str]) -> int | None: if isinstance(yaml, dict) and "length" in yaml: - return yaml["length"] * 0x10 + return int(yaml["length"]) * 0x10 return None diff --git a/src/splat/segtypes/n64/img.py b/src/splat/segtypes/n64/img.py index 6bb09a68..3c6c9074 100644 --- a/src/splat/segtypes/n64/img.py +++ b/src/splat/segtypes/n64/img.py @@ -1,33 +1,38 @@ +from __future__ import annotations + from pathlib import Path -from typing import Dict, List, Tuple, Type, Optional, Union +from typing import TYPE_CHECKING from n64img.image import Image from ...util import log, options from ..segment import Segment +if TYPE_CHECKING: + from ...util.vram_classes import SerializedSegmentData + class N64SegImg(Segment): @staticmethod - def parse_dimensions(yaml: Union[Dict, List]) -> Tuple[int, int]: + def parse_dimensions(yaml: SerializedSegmentData | list[str]) -> tuple[int, int]: if isinstance(yaml, dict): return yaml["width"], yaml["height"] else: if len(yaml) < 5: log.error(f"Error: {yaml} is missing width and height parameters") - return yaml[3], yaml[4] + return int(yaml[3]), int(yaml[4]) def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - img_cls: Type[Image], - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegmentData | list[str], + img_cls: type[Image], + ) -> None: super().__init__( rom_start, rom_end, @@ -77,7 +82,7 @@ def out_path(self) -> Path: def should_split(self) -> bool: return options.opts.is_mode_active("img") - def split(self, rom_bytes): + def split(self, rom_bytes: bytes) -> None: self.check_len() path = self.out_path() @@ -90,7 +95,7 @@ def split(self, rom_bytes): self.log(f"Wrote {self.name} to {path}") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> int: + def estimate_size(yaml: SerializedSegmentData | list[str]) -> int: width, height = N64SegImg.parse_dimensions(yaml) typ = Segment.parse_segment_type(yaml) diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index de0d830b..e107f37c 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -1,20 +1,31 @@ +from __future__ import annotations + from itertools import zip_longest from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, TypeVar from ...util import log, options from ...util.color import unpack_color from ..segment import Segment +if TYPE_CHECKING: + from collections.abc import Iterable + from typing_extensions import Final + + from ..linker_entry import LinkerEntry + from ...util.vram_classes import SerializedSegmentData + +T = TypeVar("T") + -VALID_SIZES = [0x20, 0x40, 0x80, 0x100, 0x200] +VALID_SIZES: Final = (0x20, 0x40, 0x80, 0x100, 0x200) class N64SegPalette(Segment): require_unique_name = False - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) if self.extract: @@ -58,7 +69,7 @@ def __init__(self, *args, **kwargs): size = 0 self.palette_size: int = size - self.global_id: Optional[str] = ( + self.global_id: str | None = ( self.yaml.get("global_id") if isinstance(self.yaml, dict) else None ) @@ -66,8 +77,8 @@ def get_cname(self) -> str: return super().get_cname() + "_pal" @staticmethod - def parse_palette_bytes(data) -> List[Tuple[int, int, int, int]]: - def iter_in_groups(iterable, n, fillvalue=None): + def parse_palette_bytes(data: bytes) -> list[tuple[int, int, int, int]]: + def iter_in_groups(iterable: Iterable[T], n: int, fillvalue: object=None) -> zip_longest[tuple[T, ...]]: args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) @@ -78,7 +89,7 @@ def iter_in_groups(iterable, n, fillvalue=None): return palette - def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]: + def parse_palette(self, rom_bytes: bytes) -> list[tuple[int, int, int, int]]: assert self.rom_start is not None data = rom_bytes[self.rom_start : self.rom_start + self.palette_size] @@ -91,7 +102,7 @@ def out_path(self) -> Path: return options.opts.asset_path / self.dir / f"{self.name}.png" # TODO NEED NAMES... - def get_linker_entries(self): + def get_linker_entries(self) -> list[LinkerEntry]: from ..linker_entry import LinkerEntry return [ @@ -106,7 +117,7 @@ def get_linker_entries(self): ] @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> int: + def estimate_size(yaml: SerializedSegmentData | list[str]) -> int: if isinstance(yaml, dict): if "size" in yaml: return int(yaml["size"]) diff --git a/src/splat/segtypes/n64/vtx.py b/src/splat/segtypes/n64/vtx.py index acf4bcbb..622dcc29 100644 --- a/src/splat/segtypes/n64/vtx.py +++ b/src/splat/segtypes/n64/vtx.py @@ -5,26 +5,32 @@ Originally written by Mark Street (https://github.com/mkst) """ +from __future__ import annotations + import struct from pathlib import Path -from typing import Dict, List, Optional, Union +from typing import TYPE_CHECKING from ...util import options, log from ..common.codesubsegment import CommonSegCodeSubsegment +if TYPE_CHECKING: + from ...util.vram_classes import SerializedSegmentData + from ...util.symbols import Symbol + class N64SegVtx(CommonSegCodeSubsegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], - args: list, - yaml, - ): + vram_start: int | None, + args: list[str], + yaml: SerializedSegmentData | list[str], + ) -> None: super().__init__( rom_start, rom_end, @@ -34,10 +40,10 @@ def __init__( args=args, yaml=yaml, ) - self.file_text: Optional[str] = None + self.file_text: str | None = None self.data_only = isinstance(yaml, dict) and yaml.get("data_only", False) - def format_sym_name(self, sym) -> str: + def format_sym_name(self, sym: Symbol) -> str: return sym.name def get_linker_section(self) -> str: @@ -46,10 +52,10 @@ def get_linker_section(self) -> str: def out_path(self) -> Path: return options.opts.asset_path / self.dir / f"{self.name}.vtx.inc.c" - def scan(self, rom_bytes: bytes): + def scan(self, rom_bytes: bytes) -> None: self.file_text = self.disassemble_data(rom_bytes) - def disassemble_data(self, rom_bytes) -> str: + def disassemble_data(self, rom_bytes: bytes) -> str: assert isinstance(self.rom_start, int) assert isinstance(self.rom_end, int) assert isinstance(self.vram_start, int) @@ -88,11 +94,12 @@ def disassemble_data(self, rom_bytes) -> str: lines.append("") return "\n".join(lines) - def split(self, rom_bytes: bytes): - if self.file_text and self.out_path(): - self.out_path().parent.mkdir(parents=True, exist_ok=True) + def split(self, rom_bytes: bytes) -> None: + out_path = self.out_path() + if self.file_text and out_path is not None: + out_path.parent.mkdir(parents=True, exist_ok=True) - with open(self.out_path(), "w", newline="\n") as f: + with open(out_path, "w", encoding="utf-8", newline="\n") as f: f.write(self.file_text) def should_scan(self) -> bool: @@ -102,7 +109,7 @@ def should_split(self) -> bool: return self.extract and options.opts.is_mode_active("vtx") @staticmethod - def estimate_size(yaml: Union[Dict, List]) -> Optional[int]: + def estimate_size(yaml: SerializedSegmentData | list[str]) -> int | None: if isinstance(yaml, dict) and "length" in yaml: return yaml["length"] * 0x10 return None diff --git a/src/splat/segtypes/ps2/ctor.py b/src/splat/segtypes/ps2/ctor.py index 003059ac..57ed18b2 100644 --- a/src/splat/segtypes/ps2/ctor.py +++ b/src/splat/segtypes/ps2/ctor.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ..common.data import CommonSegData from ...disassembler.disassembler_section import DisassemblerSection @@ -10,7 +10,7 @@ class Ps2SegCtor(CommonSegData): def get_linker_section(self) -> str: return ".ctor" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def configure_disassembler_section( @@ -21,6 +21,7 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None # We use s32 to make sure spimdisasm disassembles the data from this section as words/references to other symbols section.enableStringGuessing = False diff --git a/src/splat/segtypes/ps2/lit4.py b/src/splat/segtypes/ps2/lit4.py index d2f1d581..fbd845e5 100644 --- a/src/splat/segtypes/ps2/lit4.py +++ b/src/splat/segtypes/ps2/lit4.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ..common.data import CommonSegData from ...disassembler.disassembler_section import DisassemblerSection @@ -10,7 +10,7 @@ class Ps2SegLit4(CommonSegData): def get_linker_section(self) -> str: return ".lit4" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def configure_disassembler_section( @@ -21,6 +21,7 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None # Tell spimdisasm this section only contains floats section.enableStringGuessing = False diff --git a/src/splat/segtypes/ps2/lit8.py b/src/splat/segtypes/ps2/lit8.py index 81b5cf99..4757d4b1 100644 --- a/src/splat/segtypes/ps2/lit8.py +++ b/src/splat/segtypes/ps2/lit8.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ..common.data import CommonSegData from ...disassembler.disassembler_section import DisassemblerSection @@ -10,7 +10,7 @@ class Ps2SegLit8(CommonSegData): def get_linker_section(self) -> str: return ".lit8" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def configure_disassembler_section( @@ -21,6 +21,7 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None # Tell spimdisasm this section only contains doubles section.enableStringGuessing = False diff --git a/src/splat/segtypes/ps2/vtables.py b/src/splat/segtypes/ps2/vtables.py index afbdbd17..a8620b3b 100644 --- a/src/splat/segtypes/ps2/vtables.py +++ b/src/splat/segtypes/ps2/vtables.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ..common.data import CommonSegData from ...disassembler.disassembler_section import DisassemblerSection @@ -10,7 +10,7 @@ class Ps2SegVtables(CommonSegData): def get_linker_section(self) -> str: return ".vtables" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def configure_disassembler_section( @@ -21,6 +21,7 @@ def configure_disassembler_section( super().configure_disassembler_section(disassembler_section) section = disassembler_section.get_section() + assert section is not None # We use s32 to make sure spimdisasm disassembles the data from this section as words/references to other symbols section.enableStringGuessing = False diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index dd55a2f6..61d1f07f 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -389,7 +389,7 @@ def __init__( f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})" ) - @classmethod + @staticmethod def from_yaml( cls: type[Segment], yaml: SerializedSegment, diff --git a/src/splat/util/color.py b/src/splat/util/color.py index 22e01660..0d143d67 100644 --- a/src/splat/util/color.py +++ b/src/splat/util/color.py @@ -1,12 +1,16 @@ from __future__ import annotations +from typing import TYPE_CHECKING from math import ceil from . import options +if TYPE_CHECKING: + from collections.abc import Sequence + # RRRRRGGG GGBBBBBA -def unpack_color(data: bytes) -> tuple[int, int, int, int]: +def unpack_color(data: Sequence[int]) -> tuple[int, int, int, int]: s = int.from_bytes(data[0:2], byteorder=options.opts.endianness) r = (s >> 11) & 0x1F diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index 00785442..b86b36ff 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -332,6 +332,7 @@ def find_code_after_data( if insn.isValid() and insn.isReturn(): # Check the instruction on the delay slot of the `jr $ra` is valid too. next_word = spimdisasm.common.Utils.bytesToWords( + rom_bytes, offset + 4, offset + 4 + 4, )[0] if rabbitizer.Instruction(next_word, vram + 4).isValid(): jr_ra_found = True @@ -451,6 +452,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sys.exit( "splat could not decode the game name;" " try using a different encoding by passing the --header-encoding argument" + " (see docs.python.org/3/library/codecs.html#standard-encodings for valid encodings)", ) country_code = rom_bytes[0x3E] @@ -463,6 +465,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sha1 = hashlib.sha1(rom_bytes).hexdigest() entrypoint_info = N64EntrypointInfo.parse_rom_bytes( + rom_bytes, entry_point, size=0x100, ) return N64Rom( @@ -499,6 +502,7 @@ def get_compiler_info(rom_bytes: bytes, entry_point: int, print_result: bool = T if print_result: print( f"{branches} branches and {jumps} jumps detected in the first code segment." + f" Compiler is most likely {compiler}", ) return compiler diff --git a/src/splat/util/progress_bar.py b/src/splat/util/progress_bar.py index 12b2ee3b..a809df0c 100644 --- a/src/splat/util/progress_bar.py +++ b/src/splat/util/progress_bar.py @@ -2,9 +2,13 @@ import tqdm import sys +from typing import TYPE_CHECKING, TextIO -out_file = sys.stderr +if TYPE_CHECKING: + from collections.abc import Sequence +out_file: TextIO = sys.stderr -def get_progress_bar(elements: list[str]) -> tqdm.tqdm: + +def get_progress_bar(elements: Sequence[object]) -> tqdm.tqdm: return tqdm.tqdm(elements, total=len(elements), file=out_file) diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index 7994d063..0379f625 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -52,6 +52,16 @@ class SerializedSegmentData(TypedDict): pair_segment: NotRequired[str] exclusive_ram_id: NotRequired[str] find_file_boundaries: NotRequired[bool] + size: NotRequired[int] + global_id: NotRequired[str] + length: NotRequired[int] + in_segment: NotRequired[bool] + data_only: NotRequired[bool] + bss_size: NotRequired[int] + str_encoding: NotRequired[str] + detect_redundant_function_end: NotRequired[bool] + width: NotRequired[int] + height: NotRequired[int] def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: From dcc29d405dc595b60add2b2bd7dcddab082f8d7a Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:59:59 -0600 Subject: [PATCH 13/19] Fix runtime issue --- src/splat/segtypes/common/code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 1429239f..6fd2b8eb 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -75,7 +75,7 @@ def _generate_segment_from_all( name=base_name, vram_start=vram_start, args=[], - yaml=cast(SerializedSegmentData, {}), + yaml=cast("SerializedSegmentData", {}), ) rep.extract = False rep.given_subalign = self.given_subalign From bb1304c148b7a7a2287a8de76f49d05eea03f77f Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:55:59 -0600 Subject: [PATCH 14/19] Apply ruff formatting --- pyproject.toml | 8 ++-- .../disassembler/disassembler_section.py | 2 +- src/splat/disassembler/null_disassembler.py | 1 + src/splat/segtypes/common/c.py | 11 +++-- src/splat/segtypes/common/code.py | 4 +- src/splat/segtypes/common/codesubsegment.py | 8 +--- src/splat/segtypes/common/data.py | 4 +- src/splat/segtypes/common/group.py | 8 +++- src/splat/segtypes/common/rodata.py | 4 +- src/splat/segtypes/linker_entry.py | 8 +++- src/splat/segtypes/n64/ci.py | 4 +- src/splat/segtypes/n64/gfx.py | 4 +- src/splat/segtypes/n64/palette.py | 4 +- src/splat/segtypes/segment.py | 12 +++--- src/splat/util/log.py | 12 +++++- src/splat/util/n64/find_code_length.py | 4 +- src/splat/util/n64/rominfo.py | 40 ++++++++++++++----- src/splat/util/options.py | 4 +- src/splat/util/psx/psxexeinfo.py | 4 +- src/splat/util/statistics.py | 2 +- src/splat/util/vram_classes.py | 4 ++ 21 files changed, 101 insertions(+), 51 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 778deabb..5fc94be4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,15 +76,15 @@ check_untyped_defs = false # TODO: change true combine-as-imports = true [tool.ruff] -line-length = 79 +#line-length = 79 fix = true include = ["*.py", "*.pyi", "**/pyproject.toml"] [tool.ruff.lint] extend-select = [ - "A", # flake8-builtins - "COM", # flake8-commas + #"A", # flake8-builtins + #"COM", # flake8-commas "E", # Error "FA", # flake8-future-annotations "I", # isort @@ -93,7 +93,7 @@ extend-select = [ "R", # Refactor "RET", # flake8-return "RUF", # Ruff-specific rules - "SIM", # flake8-simplify + #"SIM", # flake8-simplify "SLOT", # flake8-slots "TCH", # flake8-type-checking "UP", # pyupgrade diff --git a/src/splat/disassembler/disassembler_section.py b/src/splat/disassembler/disassembler_section.py index 5c293946..4fc8b5af 100644 --- a/src/splat/disassembler/disassembler_section.py +++ b/src/splat/disassembler/disassembler_section.py @@ -9,7 +9,7 @@ class DisassemblerSection(ABC): __slots__ = () - + @abstractmethod def disassemble(self) -> str: raise NotImplementedError("disassemble") diff --git a/src/splat/disassembler/null_disassembler.py b/src/splat/disassembler/null_disassembler.py index dbb3c6c4..7fdbb7f5 100644 --- a/src/splat/disassembler/null_disassembler.py +++ b/src/splat/disassembler/null_disassembler.py @@ -5,6 +5,7 @@ class NullDisassembler(disassembler.Disassembler): __slots__ = () + def configure(self) -> None: pass diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index d4ec94b6..8aad01f0 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -85,7 +85,9 @@ def get_close_parenthesis(string: str, pos: int) -> int: pos += 1 @classmethod - def find_include_macro(cls, text: str, macro_name: str) -> Generator[str, None, None]: + def find_include_macro( + cls, text: str, macro_name: str + ) -> Generator[str, None, None]: for pos in cls.find_all_instances(text, f"{macro_name}("): close_paren_pos = cls.get_close_parenthesis( text, pos + len(f"{macro_name}(") @@ -172,7 +174,11 @@ def split(self, rom_bytes: bytes) -> None: # We want to know if this C section has a corresponding rodata section so we can migrate its rodata rodata_section_type = "" - rodata_spim_segment: spimdisasm.mips.sections.SectionRodata | spimdisasm.mips.sections.SectionBase | None = None + rodata_spim_segment: ( + spimdisasm.mips.sections.SectionRodata + | spimdisasm.mips.sections.SectionBase + | None + ) = None if options.opts.migrate_rodata_to_functions: # We don't know if the rodata section is .rodata or .rdata, so we need to check both for sect in [".rodata", ".rdata"]: @@ -541,4 +547,3 @@ def create_asm_dependencies_file( for depend_file in depend_list: f.write(f"{depend_file.as_posix()}:\n") - diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 6fd2b8eb..648f60f1 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -150,7 +150,9 @@ def _insert_all_auto_sections( return ret - def parse_subsegments(self, segment_yaml: dict[str, list[SerializedSegmentData | list[str]]]) -> list[Segment]: + def parse_subsegments( + self, segment_yaml: dict[str, list[SerializedSegmentData | list[str]]] + ) -> list[Segment]: if "subsegments" not in segment_yaml: if not self.parent: raise Exception( diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 97972381..34452fae 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -149,9 +149,7 @@ def process_insns( for referenced_vram in func_spim.referencedVrams: section = self.spim_section.get_section() assert section is not None - context_sym = section.getSymbol( - referenced_vram, tryPlusOffset=False - ) + context_sym = section.getSymbol(referenced_vram, tryPlusOffset=False) if context_sym is not None: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), context_sym, force_in_segment=False @@ -199,9 +197,7 @@ def print_file_boundaries(self) -> None: # Look up for the last symbol in this boundary sym_addr = 0 for sym in section.symbolList: - symOffset = ( - sym.inFileOffset - section.inFileOffset - ) + symOffset = sym.inFileOffset - section.inFileOffset if in_file_offset == symOffset: break sym_addr = sym.vram diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index 78dca217..90266ac7 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -144,9 +144,7 @@ def disassemble_data(self, rom_bytes: bytes) -> None: # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: - context_sym = section.getSymbol( - referenced_vram, tryPlusOffset=False - ) + context_sym = section.getSymbol(referenced_vram, tryPlusOffset=False) if context_sym is not None: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), context_sym, force_in_segment=False diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 68a4ef0d..fabea15d 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -36,7 +36,9 @@ def __init__( # TODO: Fix self.subsegments: list[Segment] = self.parse_subsegments(yaml) # type: ignore[arg-type] - def get_next_seg_start(self, i: int, subsegment_yamls: list[SerializedSegmentData | list[str]]) -> int | None: + def get_next_seg_start( + self, i: int, subsegment_yamls: list[SerializedSegmentData | list[str]] + ) -> int | None: j = i + 1 while j < len(subsegment_yamls): ret, is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) @@ -47,7 +49,9 @@ def get_next_seg_start(self, i: int, subsegment_yamls: list[SerializedSegmentDat # Fallback return self.rom_end - def parse_subsegments(self, yaml: dict[str, list[SerializedSegmentData | list[str]]]) -> list[Segment]: + def parse_subsegments( + self, yaml: dict[str, list[SerializedSegmentData | list[str]]] + ) -> list[Segment]: ret: list[Segment] = [] if not yaml or "subsegments" not in yaml: diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index e4a62092..c3731d82 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -119,9 +119,7 @@ def disassemble_data(self, rom_bytes: bytes) -> None: # Gather symbols found by spimdisasm and create those symbols in splat's side for referenced_vram in symbol.referencedVrams: - context_sym = section.getSymbol( - referenced_vram, tryPlusOffset=False - ) + context_sym = section.getSymbol(referenced_vram, tryPlusOffset=False) if context_sym is not None: symbols.create_symbol_from_spim_symbol( self.get_most_parent(), context_sym, force_in_segment=False diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index adf35c86..411c74a6 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -224,7 +224,9 @@ def write_max_vram_end_sym(self, symbol: str, overlays: list[Segment]) -> None: ) # Adds all the entries of a segment to the linker script buffer - def add(self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]]) -> None: + def add( + self, segment: Segment, max_vram_syms: list[tuple[str, list[Segment]]] + ) -> None: entries = segment.get_linker_entries() self.entries.extend(entries) self.dependencies_entries.extend(entries) @@ -638,7 +640,9 @@ def _end_segment(self, segment: Segment, all_bss: bool = False) -> None: self._writeln("") - def _begin_partial_segment(self, section_name: str, segment: Segment, noload: bool) -> None: + def _begin_partial_segment( + self, section_name: str, segment: Segment, noload: bool + ) -> None: line = f"{section_name}" if noload: line += " (NOLOAD)" diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index 7996f263..a95370e3 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -14,7 +14,9 @@ # Base class for CI4/CI8 class N64SegCi(N64SegImg): - def parse_palette_names(self, yaml: SerializedSegmentData | list[str], args: list[str]) -> list[str]: + def parse_palette_names( + self, yaml: SerializedSegmentData | list[str], args: list[str] + ) -> list[str]: ret: list[str] | str = [self.name] if isinstance(yaml, dict): if "palettes" in yaml: diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index fe62fde3..b4c602e4 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -111,7 +111,9 @@ def tlut_handler(self, addr: int, idx: int, count: int) -> int: gfxd_printf(self.format_sym_name(sym)) return 1 - def timg_handler(self, addr: int, fmt, size: int, width: int, height: int, pal) -> int: + def timg_handler( + self, addr: int, fmt, size: int, width: int, height: int, pal + ) -> int: sym = self.create_symbol( addr=addr, in_segment=self.in_segment, type="data", reference=True ) diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index e107f37c..d643355f 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -78,7 +78,9 @@ def get_cname(self) -> str: @staticmethod def parse_palette_bytes(data: bytes) -> list[tuple[int, int, int, int]]: - def iter_in_groups(iterable: Iterable[T], n: int, fillvalue: object=None) -> zip_longest[tuple[T, ...]]: + def iter_in_groups( + iterable: Iterable[T], n: int, fillvalue: object = None + ) -> zip_longest[tuple[T, ...]]: args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index 61d1f07f..d21ddee8 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -25,6 +25,7 @@ SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] + def parse_segment_vram(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "vram" in segment: return int(segment["vram"]) @@ -143,8 +144,7 @@ def get_base_segment_class(seg_type: str) -> type[Segment] | None: seg_prefix = platform.capitalize() if is_platform_seg else "Common" return getattr( # type: ignore[no-any-return] - segmodule, - f"{seg_prefix}Seg{seg_type.capitalize()}" + segmodule, f"{seg_prefix}Seg{seg_type.capitalize()}" ) @staticmethod @@ -206,7 +206,9 @@ def parse_segment_type(segment: SerializedSegment) -> str: return str(segment[1]) @classmethod - def parse_segment_name(cls, rom_start: int | None, segment: SerializedSegment) -> str: + def parse_segment_name( + cls, rom_start: int | None, segment: SerializedSegment + ) -> str: if isinstance(segment, dict): if "name" in segment: return str(segment["name"]) @@ -258,9 +260,7 @@ def parse_linker_section(yaml: SerializedSegment) -> str | None: return None @staticmethod - def parse_ld_fill_value( - yaml: SerializedSegment, default: int | None - ) -> int | None: + def parse_ld_fill_value(yaml: SerializedSegment, default: int | None) -> int | None: if isinstance(yaml, dict) and "ld_fill_value" in yaml: return yaml["ld_fill_value"] return default diff --git a/src/splat/util/log.py b/src/splat/util/log.py index 7bd0cbb9..66cd7735 100644 --- a/src/splat/util/log.py +++ b/src/splat/util/log.py @@ -17,7 +17,13 @@ Status: TypeAlias = Optional[str] -def write(*args: object, status: Status = None, sep: str | None = None, end: str | None = None, flush: bool = False) -> None: +def write( + *args: object, + status: Status = None, + sep: str | None = None, + end: str | None = None, + flush: bool = False, +) -> None: global newline if not newline: @@ -34,7 +40,9 @@ def write(*args: object, status: Status = None, sep: str | None = None, end: str ) -def error(*args: object, sep: str | None = None, end: str | None = None, flush: bool = False) -> NoReturn: +def error( + *args: object, sep: str | None = None, end: str | None = None, flush: bool = False +) -> NoReturn: write(*args, status="error", sep=sep, end=end, flush=flush) sys.exit(2) diff --git a/src/splat/util/n64/find_code_length.py b/src/splat/util/n64/find_code_length.py index c525a88d..84b76026 100755 --- a/src/splat/util/n64/find_code_length.py +++ b/src/splat/util/n64/find_code_length.py @@ -25,7 +25,9 @@ def int_any_base(x: str) -> int: ) -def run(rom_bytes: bytes, start_offset: int, vram: int, end_offset: int | None = None) -> int: +def run( + rom_bytes: bytes, start_offset: int, vram: int, end_offset: int | None = None +) -> int: rom_addr = start_offset last_return = rom_addr diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index b86b36ff..f65eb3b9 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -75,7 +75,10 @@ class EntryAddressInfo: @staticmethod def new( - value: int | None, hi: int | None, lo: int | None, ori: int | None, + value: int | None, + hi: int | None, + lo: int | None, + ori: int | None, ) -> EntryAddressInfo | None: if value is not None and hi is not None and lo is not None: return EntryAddressInfo(value, hi, lo, ori == lo) @@ -108,10 +111,15 @@ def get_bss_size(self) -> int | None: @staticmethod def parse_rom_bytes( - rom_bytes: bytes, vram: int, offset: int = 0x1000, size: int = 0x60, + rom_bytes: bytes, + vram: int, + offset: int = 0x1000, + size: int = 0x60, ) -> N64EntrypointInfo: word_list = spimdisasm.common.Utils.bytesToWords( - rom_bytes, offset, offset + size, + rom_bytes, + offset, + offset + size, ) nops_count = 0 @@ -229,7 +237,10 @@ def parse_rom_bytes( # entrypoint to actual code. traditional_entrypoint = False func_call_target = EntryAddressInfo( - insn.getInstrIndexAsVram(), current_rom, current_rom, False, + insn.getInstrIndexAsVram(), + current_rom, + current_rom, + False, ) elif insn.uniqueId == rabbitizer.InstrId.cpu_break: @@ -315,7 +326,10 @@ def parse_rom_bytes( def find_code_after_data( - rom_bytes: bytes, offset: int, vram: int, threshold: int = 0x18000, + rom_bytes: bytes, + offset: int, + vram: int, + threshold: int = 0x18000, ) -> int | None: code_offset: int | None = None @@ -332,7 +346,9 @@ def find_code_after_data( if insn.isValid() and insn.isReturn(): # Check the instruction on the delay slot of the `jr $ra` is valid too. next_word = spimdisasm.common.Utils.bytesToWords( - rom_bytes, offset + 4, offset + 4 + 4, + rom_bytes, + offset + 4, + offset + 4 + 4, )[0] if rabbitizer.Instruction(next_word, vram + 4).isValid(): jr_ra_found = True @@ -430,7 +446,9 @@ def guess_header_encoding(rom_bytes: bytes) -> str: def get_info( - rom_path: Path, rom_bytes: bytes | None = None, header_encoding: str | None = None, + rom_path: Path, + rom_bytes: bytes | None = None, + header_encoding: str | None = None, ) -> N64Rom: if rom_bytes is None: rom_bytes = read_rom(rom_path) @@ -465,7 +483,9 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: sha1 = hashlib.sha1(rom_bytes).hexdigest() entrypoint_info = N64EntrypointInfo.parse_rom_bytes( - rom_bytes, entry_point, size=0x100, + rom_bytes, + entry_point, + size=0x100, ) return N64Rom( @@ -483,7 +503,9 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom: ) -def get_compiler_info(rom_bytes: bytes, entry_point: int, print_result: bool = True) -> str: +def get_compiler_info( + rom_bytes: bytes, entry_point: int, print_result: bool = True +) -> str: jumps = 0 branches = 0 diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 40895e06..c4fee66a 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -345,9 +345,7 @@ def parse_opt_within( raise ValueError(f"Invalid value for {opt}: {value}") return value - def parse_path( - self, base_path: Path, opt: str, default: str | None = None - ) -> Path: + def parse_path(self, base_path: Path, opt: str, default: str | None = None) -> Path: return Path(os.path.normpath(base_path / self.parse_opt(opt, str, default))) def parse_optional_path(self, base_path: Path, opt: str) -> Path | None: diff --git a/src/splat/util/psx/psxexeinfo.py b/src/splat/util/psx/psxexeinfo.py index 63c12819..924da41a 100755 --- a/src/splat/util/psx/psxexeinfo.py +++ b/src/splat/util/psx/psxexeinfo.py @@ -78,7 +78,9 @@ def is_valid(insn: rabbitizer.Instruction) -> bool: def try_find_text( - rom_bytes: bytes, start_offset: int = PAYLOAD_OFFSET, valid_threshold: int = 32, + rom_bytes: bytes, + start_offset: int = PAYLOAD_OFFSET, + valid_threshold: int = 32, ) -> tuple[int, int]: start = end = 0 good_count = valid_count = 0 diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index af755dea..ad639a45 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -16,7 +16,7 @@ def fmt_size(size: int) -> str: class Statistics: __slots__ = ("seg_sizes", "seg_split", "seg_cached") - + def __init__(self) -> None: self.seg_sizes: dict[str, int] = {} self.seg_split: dict[str, int] = {} diff --git a/src/splat/util/vram_classes.py b/src/splat/util/vram_classes.py index 0379f625..b6ddb5f5 100644 --- a/src/splat/util/vram_classes.py +++ b/src/splat/util/vram_classes.py @@ -103,20 +103,24 @@ def initialize(yaml: list[SerializedSegmentData | list[str]] | None) -> None: vram_symbol = vram_class["vram_symbol"] if not isinstance(vram_symbol, str): log.error( + f"vram_symbol ({vram_symbol})must be a string, got {type(vram_symbol)}" ) if "follows_classes" in vram_class: follows_classes = vram_class["follows_classes"] if not isinstance(follows_classes, list): log.error( + f"vram_symbol ({follows_classes})must be a list, got {type(follows_classes)}" ) for follows_class in follows_classes: if follows_class not in class_names: log.error( + f"follows_class ({follows_class}) not found in vram_classes" ) elif isinstance(vram_class, list): if len(vram_class) != 2: log.error( + f"vram_class ({vram_class}) must have 2 elements, got {len(vram_class)}" ) name = vram_class[0] vram = int(vram_class[1]) From 3e340be9808fc6b87e3f1efb1f55196fe7a05311 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:59:45 -0600 Subject: [PATCH 15/19] Ruff autofixes --- pyproject.toml | 5 +-- src/splat/scripts/split.py | 2 +- src/splat/segtypes/common/c.py | 11 +++--- src/splat/segtypes/common/code.py | 3 +- src/splat/segtypes/common/data.py | 10 +++--- src/splat/segtypes/common/group.py | 2 +- src/splat/segtypes/linker_entry.py | 13 ++++--- src/splat/segtypes/n64/ci.py | 2 +- src/splat/segtypes/n64/gfx.py | 11 +++--- src/splat/segtypes/n64/img.py | 14 ++++---- src/splat/segtypes/n64/palette.py | 2 +- src/splat/segtypes/segment.py | 58 +++++++++++------------------- src/splat/util/conf.py | 2 +- src/splat/util/options.py | 21 +++++------ src/splat/util/statistics.py | 7 ++-- test.py | 7 ++-- 16 files changed, 68 insertions(+), 102 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5fc94be4..0597c4fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,9 +72,6 @@ no_implicit_optional = true warn_unreachable = true check_untyped_defs = false # TODO: change true -[tool.ruff.lint.isort] -combine-as-imports = true - [tool.ruff] #line-length = 79 fix = true @@ -87,7 +84,7 @@ extend-select = [ #"COM", # flake8-commas "E", # Error "FA", # flake8-future-annotations - "I", # isort + #"I", # isort "ICN", # flake8-import-conventions "PYI", # flake8-pyi "R", # Refactor diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index 9a909af8..777101f3 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -5,7 +5,7 @@ import argparse import hashlib import importlib -from typing import Any, Dict, List, Optional, Set, Tuple, Union, TYPE_CHECKING +from typing import Any, Dict, List, Tuple, TYPE_CHECKING from pathlib import Path from collections import defaultdict, deque diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index 8aad01f0..914a759f 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -55,7 +55,7 @@ def replacer(match: re.Match[str]) -> str: @staticmethod def get_funcs_defined_in_c(c_file: Path) -> set[str]: - with open(c_file, "r", encoding="utf-8") as f: + with open(c_file, encoding="utf-8") as f: text = CommonSegC.strip_c_comments(f.read()) return set(m.group(1) for m in C_FUNC_RE.finditer(text)) @@ -80,8 +80,7 @@ def get_close_parenthesis(string: str, pos: int) -> int: elif cur_char == ")": if paren_count == 0: return pos + 1 - else: - paren_count -= 1 + paren_count -= 1 pos += 1 @classmethod @@ -115,8 +114,7 @@ def get_global_asm_funcs(cls, c_file: Path) -> set[str]: text = cls.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - else: - return set(cls.find_include_asm(text)) + return set(cls.find_include_asm(text)) @classmethod def get_global_asm_rodata_syms(cls, c_file: Path) -> set[str]: @@ -124,8 +122,7 @@ def get_global_asm_rodata_syms(cls, c_file: Path) -> set[str]: text = cls.strip_c_comments(f.read()) if options.opts.compiler == IDO: return set(m.group(2) for m in C_GLOBAL_ASM_IDO_RE.finditer(text)) - else: - return set(cls.find_include_rodata(text)) + return set(cls.find_include_rodata(text)) @staticmethod def is_text() -> bool: diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 648f60f1..17dd3509 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -54,8 +54,7 @@ def needs_symbols(self) -> bool: def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size + self.bss_size - else: - return None + return None # Generates a placeholder segment for the auto_link_sections option def _generate_segment_from_all( diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index 90266ac7..a335074c 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -30,12 +30,10 @@ def out_path(self) -> Path | None: if self.sibling: # C file return self.sibling.out_path() - else: - # Implied C file - return options.opts.src_path / self.dir / f"{self.name}.c" - else: - # ASM - return self.asm_out_path() + # Implied C file + return options.opts.src_path / self.dir / f"{self.name}.c" + # ASM + return self.asm_out_path() def scan(self, rom_bytes: bytes) -> None: CommonSegGroup.scan(self, rom_bytes) diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index fabea15d..2c3c97d2 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -210,7 +210,7 @@ def get_next_subsegment_for_ram( def pair_subsegments_to_other_segment( self, - other_segment: "CommonSegGroup", + other_segment: CommonSegGroup, ) -> None: # Pair cousins with the same name for segment in self.subsegments: diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index 411c74a6..3f5ff148 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -2,9 +2,10 @@ import os import re -from functools import lru_cache +from functools import cache from pathlib import Path -from typing import Dict, List, OrderedDict, Set, Tuple, Union, Optional +from typing import Dict, List, Set, Union, Optional +from collections import OrderedDict from ..util import options, log @@ -13,7 +14,7 @@ # clean 'foo/../bar' to 'bar' -@lru_cache(maxsize=None) +@cache def clean_up_path(path: Path) -> Path: path_resolved = path.resolve() base_resolved = options.opts.base_path.resolve() @@ -141,15 +142,13 @@ def __init__( def section_order_type(self) -> str: if self.section_order == ".rdata": return ".rodata" - else: - return self.section_order + return self.section_order @property def section_link_type(self) -> str: if self.section_link == ".rdata": return ".rodata" - else: - return self.section_link + return self.section_link def emit_symbol_for_data(self, linker_writer: LinkerWriter) -> None: if not options.opts.ld_generate_symbol_per_data_segment: diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index a95370e3..70877619 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -31,7 +31,7 @@ def parse_palette_names( def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.palettes: "list[N64SegPalette]" = [] + self.palettes: list[N64SegPalette] = [] self.palette_names = self.parse_palette_names(self.yaml, self.args) def scan(self, rom_bytes: bytes) -> None: diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index b4c602e4..3f1295b5 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -93,16 +93,15 @@ def get_gfxd_target(self) -> gfxd_f3d: if opt == "f3d": return gfxd_f3d - elif opt == "f3db": + if opt == "f3db": return gfxd_f3db - elif opt == "f3dex": + if opt == "f3dex": return gfxd_f3dex - elif opt == "f3dexb": + if opt == "f3dexb": return gfxd_f3dexb - elif opt == "f3dex2": + if opt == "f3dex2": return gfxd_f3dex2 - else: - log.error(f"Unknown target {opt}") + log.error(f"Unknown target {opt}") def tlut_handler(self, addr: int, idx: int, count: int) -> int: sym = self.create_symbol( diff --git a/src/splat/segtypes/n64/img.py b/src/splat/segtypes/n64/img.py index 3c6c9074..df760739 100644 --- a/src/splat/segtypes/n64/img.py +++ b/src/splat/segtypes/n64/img.py @@ -17,10 +17,9 @@ class N64SegImg(Segment): def parse_dimensions(yaml: SerializedSegmentData | list[str]) -> tuple[int, int]: if isinstance(yaml, dict): return yaml["width"], yaml["height"] - else: - if len(yaml) < 5: - log.error(f"Error: {yaml} is missing width and height parameters") - return int(yaml[3]), int(yaml[4]) + if len(yaml) < 5: + log.error(f"Error: {yaml} is missing width and height parameters") + return int(yaml[3]), int(yaml[4]) def __init__( self, @@ -101,9 +100,8 @@ def estimate_size(yaml: SerializedSegmentData | list[str]) -> int: if typ == "ci4" or typ == "i4" or typ == "ia4": return width * height // 2 - elif typ in ("ia16", "rgba16"): + if typ in ("ia16", "rgba16"): return width * height * 2 - elif typ == "rgba32": + if typ == "rgba32": return width * height * 4 - else: - return width * height + return width * height diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index d643355f..10d4d99f 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from collections.abc import Iterable - from typing_extensions import Final + from typing import Final from ..linker_entry import LinkerEntry from ...util.vram_classes import SerializedSegmentData diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index d21ddee8..0ced64df 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -6,7 +6,7 @@ import importlib.util from pathlib import Path -from typing import Optional, Type, TYPE_CHECKING, Union, Dict, TypeAlias, List +from typing import TYPE_CHECKING, Union, TypeAlias, List from intervaltree import Interval, IntervalTree from ..util import vram_classes @@ -29,15 +29,13 @@ def parse_segment_vram(segment: SerializedSegment) -> int | None: if isinstance(segment, dict) and "vram" in segment: return int(segment["vram"]) - else: - return None + return None def parse_segment_vram_symbol(segment: SerializedSegment) -> str | None: if isinstance(segment, dict) and "vram_symbol" in segment: return str(segment["vram_symbol"]) - else: - return None + return None def parse_segment_vram_class(segment: SerializedSegment) -> VramClass | None: @@ -84,7 +82,7 @@ class SegmentStatisticsInfo: size: int count: int - def merge(self, other: "SegmentStatisticsInfo") -> "SegmentStatisticsInfo": + def merge(self, other: SegmentStatisticsInfo) -> SegmentStatisticsInfo: return SegmentStatisticsInfo( size=self.size + other.size, count=self.count + other.count ) @@ -195,15 +193,13 @@ def parse_segment_start(segment: SerializedSegment) -> tuple[int | None, bool]: return None, False if s == "auto": return None, True - else: - return int(s), False + return int(s), False @staticmethod def parse_segment_type(segment: SerializedSegment) -> str: if isinstance(segment, dict): return str(segment["type"]) - else: - return str(segment[1]) + return str(segment[1]) @classmethod def parse_segment_name( @@ -212,7 +208,7 @@ def parse_segment_name( if isinstance(segment, dict): if "name" in segment: return str(segment["name"]) - elif "dir" in segment: + if "dir" in segment: return str(segment["dir"]) elif isinstance(segment, list) and len(segment) >= 3: return str(segment[2]) @@ -223,15 +219,13 @@ def parse_segment_name( def parse_segment_symbol_name_format(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format" in segment: return str(segment["symbol_name_format"]) - else: - return options.opts.symbol_name_format + return options.opts.symbol_name_format @staticmethod def parse_segment_symbol_name_format_no_rom(segment: SerializedSegment) -> str: if isinstance(segment, dict) and "symbol_name_format_no_rom" in segment: return str(segment["symbol_name_format_no_rom"]) - else: - return options.opts.symbol_name_format_no_rom + return options.opts.symbol_name_format_no_rom @staticmethod def parse_segment_file_path(segment: SerializedSegment) -> Path | None: @@ -525,8 +519,7 @@ def needs_symbols(self) -> bool: def dir(self) -> Path: if self.parent: return self.parent.dir / self.given_dir - else: - return self.given_dir + return self.given_dir @property def show_file_boundaries(self) -> bool: @@ -559,10 +552,9 @@ def subalign(self) -> int | None: def vram_symbol(self) -> str | None: if self.vram_class and self.vram_class.vram_symbol: return self.vram_class.vram_symbol - elif self.given_vram_symbol: + if self.given_vram_symbol: return self.given_vram_symbol - else: - return None + return None def get_exclusive_ram_id(self) -> str | None: if self.parent: @@ -584,15 +576,13 @@ def add_symbol(self, symbol: Symbol) -> None: def seg_symbols(self) -> dict[int, list[Symbol]]: if self.parent: return self.parent.seg_symbols - else: - return self.given_seg_symbols + return self.given_seg_symbols @property def size(self) -> int | None: if self.rom_start is not None and self.rom_end is not None: return self.rom_end - self.rom_start - else: - return None + return None @property def statistics(self) -> SegmentStatistics: @@ -609,8 +599,7 @@ def statistics_type(self) -> SegmentType: def vram_end(self) -> int | None: if self.vram_start is not None and self.size is not None: return self.vram_start + self.size - else: - return None + return None @property def section_order(self) -> list[str]: @@ -642,20 +631,17 @@ def get_cname(self) -> str: def contains_vram(self, vram: int) -> bool: if self.vram_start is not None and self.vram_end is not None: return vram >= self.vram_start and vram < self.vram_end - else: - return False + return False def contains_rom(self, rom: int) -> bool: if self.rom_start is not None and self.rom_end is not None: return rom >= self.rom_start and rom < self.rom_end - else: - return False + return False def rom_to_ram(self, rom_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.vram_start + rom_addr - self.rom_start - else: - return None + return None def ram_to_rom(self, ram_addr: int) -> int | None: if not self.contains_vram(ram_addr) and ram_addr != self.vram_end: @@ -663,8 +649,7 @@ def ram_to_rom(self, ram_addr: int) -> int | None: if self.vram_start is not None and self.rom_start is not None: return self.rom_start + ram_addr - self.vram_start - else: - return None + return None def should_scan(self) -> bool: return self.should_split() @@ -738,7 +723,7 @@ def get_most_parent(self) -> Segment: return seg - def get_linker_entries(self) -> "list[LinkerEntry]": + def get_linker_entries(self) -> list[LinkerEntry]: from ..segtypes.linker_entry import LinkerEntry if not self.has_linker_entry: @@ -757,8 +742,7 @@ def get_linker_entries(self) -> "list[LinkerEntry]": self.is_noload(), ) ] - else: - return [] + return [] def log(self, msg: str) -> None: if options.opts.verbose: diff --git a/src/splat/util/conf.py b/src/splat/util/conf.py index ea45069b..eb85a9ef 100644 --- a/src/splat/util/conf.py +++ b/src/splat/util/conf.py @@ -28,7 +28,7 @@ def _merge_configs(main_config, additional_config, additional_config_path): main_config[curkey] = additional_config[curkey] elif type(main_config[curkey]) is not type(additional_config[curkey]): raise TypeError( - f"Could not merge {str(additional_config_path)}: type for key '{curkey}' in configs does not match" + f"Could not merge {additional_config_path!s}: type for key '{curkey}' in configs does not match" ) else: # keys exist and match, see if a list to append diff --git a/src/splat/util/options.py b/src/splat/util/options.py index c4fee66a..1eea0606 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import os from pathlib import Path -from typing import cast, Literal, Type, TypeVar +from typing import cast, Literal, TypeVar from collections.abc import Mapping from . import compiler @@ -316,7 +316,7 @@ def parse_opt(self, opt: str, t: type[T], default: T | None = None) -> T: if isinstance(value, t): return value if t is float and isinstance(value, int): - return cast(T, float(value)) + return cast("T", float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_optional_opt(self, opt: str, t: type[T]) -> T | None: @@ -334,7 +334,7 @@ def parse_optional_opt_with_default( if value is None or isinstance(value, t): return value if t is float and isinstance(value, int): - return cast(T, float(value)) + return cast("T", float(value)) raise ValueError(f"Expected {opt} to have type {t}, got {type(value)}") def parse_opt_within( @@ -358,10 +358,9 @@ def parse_path_list(self, base_path: Path, opt: str, default: str) -> list[Path] if isinstance(paths, str): return [base_path / paths] - elif isinstance(paths, list): + if isinstance(paths, list): return [base_path / path for path in paths] - else: - raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") + raise ValueError(f"Expected str or list for '{opt}', got {type(paths)}") def check_no_unread_opts(self) -> None: opts = [opt for opt in self._yaml if opt not in self._read_opts] @@ -414,10 +413,9 @@ def parse_endianness() -> Literal["big", "little"]: if endianness == "big": return "big" - elif endianness == "little": + if endianness == "little": return "little" - else: - raise ValueError(f"Invalid endianness: {endianness}") + raise ValueError(f"Invalid endianness: {endianness}") def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: include_asm_macro_style = p.parse_opt_within( @@ -429,10 +427,9 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: if include_asm_macro_style == "default": return "default" - elif include_asm_macro_style == "maspsx_hack": + if include_asm_macro_style == "maspsx_hack": return "maspsx_hack" - else: - raise ValueError(f"Invalid endianness: {include_asm_macro_style}") + raise ValueError(f"Invalid endianness: {include_asm_macro_style}") default_ld_bss_is_noload = True if platform == "psx": diff --git a/src/splat/util/statistics.py b/src/splat/util/statistics.py index ad639a45..74ea8917 100644 --- a/src/splat/util/statistics.py +++ b/src/splat/util/statistics.py @@ -8,14 +8,13 @@ def fmt_size(size: int) -> str: if size > 1000000: return f"{size // 1000000} MB" - elif size > 1000: + if size > 1000: return f"{size // 1000} KB" - else: - return f"{size} B" + return f"{size} B" class Statistics: - __slots__ = ("seg_sizes", "seg_split", "seg_cached") + __slots__ = ("seg_cached", "seg_sizes", "seg_split") def __init__(self) -> None: self.seg_sizes: dict[str, int] = {} diff --git a/test.py b/test.py index d8dfc9b0..8b668159 100755 --- a/test.py +++ b/test.py @@ -2,7 +2,6 @@ import difflib import filecmp -import io from pathlib import Path import spimdisasm import unittest @@ -21,7 +20,7 @@ class Testing(unittest.TestCase): def compare_files(self, test_path, ref_path): - with io.open(test_path) as test_f, io.open(ref_path) as ref_f: + with open(test_path) as test_f, open(ref_path) as ref_f: self.assertListEqual(list(test_f), list(ref_f)) def get_same_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): @@ -470,7 +469,7 @@ def test_overlay(self): ], } - all_segments: List["Segment"] = [ + all_segments: List[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -512,7 +511,7 @@ def test_global(self): ], } - all_segments: List["Segment"] = [ + all_segments: List[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, From 801581c476cfd6fef78c9e5a7ce72af0ad2dbc6c Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:00:37 -0600 Subject: [PATCH 16/19] Ruff unsafe autofixes --- src/splat/disassembler/disassembler.py | 3 +- .../disassembler/spimdisasm_disassembler.py | 3 +- src/splat/scripts/create_config.py | 9 +++--- src/splat/scripts/split.py | 24 +++++++------- src/splat/segtypes/common/asm.py | 4 +-- src/splat/segtypes/common/bin.py | 11 ++++--- src/splat/segtypes/common/c.py | 2 +- src/splat/segtypes/common/code.py | 2 +- src/splat/segtypes/common/codesubsegment.py | 5 ++- src/splat/segtypes/common/data.py | 2 +- src/splat/segtypes/common/databin.py | 4 +-- src/splat/segtypes/common/eh_frame.py | 5 ++- src/splat/segtypes/common/group.py | 4 +-- src/splat/segtypes/common/header.py | 5 ++- src/splat/segtypes/common/lib.py | 12 +++---- src/splat/segtypes/common/linker_offset.py | 3 +- src/splat/segtypes/common/pad.py | 3 +- src/splat/segtypes/common/rodata.py | 9 ++++-- src/splat/segtypes/common/rodatabin.py | 4 +-- src/splat/segtypes/common/textbin.py | 17 ++++++---- src/splat/segtypes/linker_entry.py | 32 ++++++++++--------- src/splat/segtypes/n64/ci.py | 2 +- src/splat/segtypes/n64/gfx.py | 7 ++-- src/splat/segtypes/n64/img.py | 4 +-- src/splat/segtypes/n64/palette.py | 2 +- src/splat/segtypes/n64/vtx.py | 2 +- src/splat/segtypes/ps2/ctor.py | 5 ++- src/splat/segtypes/ps2/lit4.py | 5 ++- src/splat/segtypes/ps2/lit8.py | 5 ++- src/splat/segtypes/ps2/vtables.py | 5 ++- src/splat/segtypes/segment.py | 4 +-- src/splat/util/compiler.py | 6 ++-- src/splat/util/conf.py | 16 ++++++---- src/splat/util/options.py | 8 +++-- src/splat/util/palettes.py | 8 ++--- src/splat/util/ps2/ps2elfinfo.py | 16 ++++++---- src/splat/util/relocs.py | 3 +- src/splat/util/symbols.py | 2 +- test.py | 21 ++++++------ 39 files changed, 159 insertions(+), 125 deletions(-) diff --git a/src/splat/disassembler/disassembler.py b/src/splat/disassembler/disassembler.py index 0a2a8baa..7d5a1531 100644 --- a/src/splat/disassembler/disassembler.py +++ b/src/splat/disassembler/disassembler.py @@ -1,7 +1,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Set class Disassembler(ABC): @@ -16,5 +15,5 @@ def check_version(self, skip_version_check: bool, splat_version: str): raise NotImplementedError("check_version") @abstractmethod - def known_types(self) -> Set[str]: + def known_types(self) -> set[str]: raise NotImplementedError("known_types") diff --git a/src/splat/disassembler/spimdisasm_disassembler.py b/src/splat/disassembler/spimdisasm_disassembler.py index 8b6cebd1..8e65695a 100644 --- a/src/splat/disassembler/spimdisasm_disassembler.py +++ b/src/splat/disassembler/spimdisasm_disassembler.py @@ -2,7 +2,6 @@ import spimdisasm import rabbitizer from ..util import log, compiler, options -from typing import Set class SpimdisasmDisassembler(disassembler.Disassembler): @@ -137,5 +136,5 @@ def check_version(self, skip_version_check: bool, splat_version: str): f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})" ) - def known_types(self) -> Set[str]: + def known_types(self) -> set[str]: return spimdisasm.common.gKnownTypes diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index eed507cd..6add9557 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -1,11 +1,11 @@ #! /usr/bin/env python3 +from __future__ import annotations import argparse import hashlib from pathlib import Path import subprocess import sys -from typing import Optional from ..util.n64 import find_code_length, rominfo from ..util.psx import psxexeinfo @@ -13,7 +13,7 @@ from ..util import log, file_presets, conf -def main(file_path: Path, objcopy: Optional[str]): +def main(file_path: Path, objcopy: str | None): if not file_path.exists(): sys.exit(f"File {file_path} does not exist ({file_path.absolute()})") if file_path.is_dir(): @@ -195,7 +195,7 @@ def create_n64_config(rom_path: Path): # Write reloc_addrs.txt file reloc_addrs: list[str] = [] - addresses_info: list[tuple[Optional[rominfo.EntryAddressInfo], str]] = [ + addresses_info: list[tuple[rominfo.EntryAddressInfo | None, str]] = [ (rom.entrypoint_info.main_address, "main"), (rom.entrypoint_info.bss_start_address, "main_BSS_START"), (rom.entrypoint_info.bss_size, "main_BSS_SIZE"), @@ -374,7 +374,7 @@ def create_psx_config(exe_path: Path, exe_bytes: bytes): file_presets.write_all_files() -def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]): +def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): elf = ps2elfinfo.Ps2Elf.get_info(elf_path, elf_bytes) if elf is None: log.error(f"Unsupported elf file '{elf_path}'") @@ -550,6 +550,7 @@ def find_objcopy() -> str: msg += f" - {name}\n" msg += "\nTry to install one of those or use the `--objcopy` flag to pass the name to your own objcopy to me." log.error(msg) + return None def run_objcopy(objcopy_name: str, elf_path: str, rom: str) -> list[str]: diff --git a/src/splat/scripts/split.py b/src/splat/scripts/split.py index 777101f3..4e63d556 100644 --- a/src/splat/scripts/split.py +++ b/src/splat/scripts/split.py @@ -5,7 +5,7 @@ import argparse import hashlib import importlib -from typing import Any, Dict, List, Tuple, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from pathlib import Path from collections import defaultdict, deque @@ -60,11 +60,11 @@ def initialize_segments(config_segments: dict | list) -> list[Segment]: segment_class = Segment.get_class_for_type(seg_type) - this_start, is_auto_segment = Segment.parse_segment_start(seg_yaml) + this_start, _is_auto_segment = Segment.parse_segment_start(seg_yaml) j = i + 1 while j < len(config_segments): - next_start, next_is_auto_segment = Segment.parse_segment_start( + next_start, _next_is_auto_segment = Segment.parse_segment_start( config_segments[j] ) if next_start is not None: @@ -194,7 +194,7 @@ def assign_symbols_to_segments() -> None: seg.add_symbol(symbol) else: cands = segment_rams[symbol.vram_start] - segs: List[Segment] = [cand.data for cand in cands] + segs: list[Segment] = [cand.data for cand in cands] for seg in segs: if not seg.get_exclusive_ram_id(): seg.add_symbol(symbol) @@ -212,7 +212,7 @@ def calc_segment_dependences( all_segments: list[Segment], ) -> dict[vram_classes.VramClass, list[Segment]]: # Map vram class names to segments that have that vram class - vram_class_to_segments: Dict[str, List[Segment]] = {} + vram_class_to_segments: dict[str, list[Segment]] = {} for seg in all_segments: if seg.vram_class is not None: if seg.vram_class.name not in vram_class_to_segments: @@ -220,7 +220,7 @@ def calc_segment_dependences( vram_class_to_segments[seg.vram_class.name].append(seg) # Map vram class names to segments that the vram class follows - vram_class_to_follows_segments: Dict[vram_classes.VramClass, List[Segment]] = {} + vram_class_to_follows_segments: dict[vram_classes.VramClass, list[Segment]] = {} for vram_class in vram_classes._vram_classes.values(): if vram_class.follows_classes: vram_class_to_follows_segments[vram_class] = [] @@ -237,13 +237,13 @@ def sort_segments_by_vram_class_dependency( all_segments: list[Segment], ) -> list[Segment]: # map all "_VRAM_END" strings to segments - end_sym_to_seg: Dict[str, Segment] = {} + end_sym_to_seg: dict[str, Segment] = {} for seg in all_segments: end_sym_to_seg[get_segment_vram_end_symbol_name(seg)] = seg # build dependency graph: A -> B means "A must come before B" - graph: Dict[Segment, List[Segment]] = defaultdict(list) - indeg: Dict[Segment, int] = {seg: 0 for seg in all_segments} + graph: dict[Segment, list[Segment]] = defaultdict(list) + indeg: dict[Segment, int] = {seg: 0 for seg in all_segments} for seg in all_segments: sym = seg.vram_symbol @@ -257,7 +257,7 @@ def sort_segments_by_vram_class_dependency( # stable topo sort with queue seeded in original order q = deque([seg for seg in all_segments if indeg[seg] == 0]) - out: List[Segment] = [] + out: list[Segment] = [] while q: n = q.popleft() @@ -368,14 +368,14 @@ def do_split( segment.split(segment_bytes) -def write_linker_script(all_segments: List[Segment]) -> LinkerWriter: +def write_linker_script(all_segments: list[Segment]) -> LinkerWriter: if options.opts.ld_sort_segments_by_vram_class_dependency: all_segments = sort_segments_by_vram_class_dependency(all_segments) vram_class_dependencies = calc_segment_dependences(all_segments) vram_classes_to_search = set(vram_class_dependencies.keys()) - max_vram_end_insertion_points: Dict[Segment, List[Tuple[str, List[Segment]]]] = {} + max_vram_end_insertion_points: dict[Segment, list[tuple[str, list[Segment]]]] = {} for seg in reversed(all_segments): if seg.vram_class in vram_classes_to_search: assert seg.vram_class.vram_symbol is not None diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index c1e5f9cc..3d784797 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from .codesubsegment import CommonSegCodeSubsegment @@ -9,7 +9,7 @@ class CommonSegAsm(CommonSegCodeSubsegment): def is_text() -> bool: return True - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" def scan(self, rom_bytes: bytes): diff --git a/src/splat/segtypes/common/bin.py b/src/splat/segtypes/common/bin.py index 7c4b80d9..e363e577 100644 --- a/src/splat/segtypes/common/bin.py +++ b/src/splat/segtypes/common/bin.py @@ -1,10 +1,13 @@ -from pathlib import Path -from typing import Optional +from __future__ import annotations +from typing import TYPE_CHECKING from ...util import log, options from .segment import CommonSegment -from ..segment import SegmentType + +if TYPE_CHECKING: + from ..segment import SegmentType + from pathlib import Path class CommonSegBin(CommonSegment): @@ -12,7 +15,7 @@ class CommonSegBin(CommonSegment): def is_data() -> bool: return True - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: return options.opts.asset_path / self.dir / f"{self.name}.bin" def split(self, rom_bytes): diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index 914a759f..752819b1 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -10,12 +10,12 @@ from ...util import log, options, symbols from ...util.compiler import IDO -from ...util.symbols import Symbol from .codesubsegment import CommonSegCodeSubsegment from .rodata import CommonSegRodata if TYPE_CHECKING: + from ...util.symbols import Symbol from collections.abc import Generator diff --git a/src/splat/segtypes/common/code.py b/src/splat/segtypes/common/code.py index 17dd3509..08525db4 100644 --- a/src/splat/segtypes/common/code.py +++ b/src/splat/segtypes/common/code.py @@ -202,7 +202,7 @@ def parse_subsegments( # Third, try to get the end address from the next segment with a start address end: int | None = None if i < len(segment_yaml["subsegments"]) - 1: - end, end_is_auto_segment = Segment.parse_segment_start( + end, _end_is_auto_segment = Segment.parse_segment_start( segment_yaml["subsegments"][i + 1] ) if start is not None and end is None: diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index 34452fae..fdb6e7b2 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path import spimdisasm import rabbitizer @@ -12,6 +11,10 @@ from ..segment import Segment, parse_segment_vram, SerializedSegment from ...disassembler.disassembler_section import DisassemblerSection, make_text_section +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path # abstract class for c, asm, data, etc diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index a335074c..0c58519d 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -1,7 +1,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -from pathlib import Path from ...util import options, symbols, log from .codesubsegment import CommonSegCodeSubsegment @@ -10,6 +9,7 @@ from ...disassembler.disassembler_section import DisassemblerSection, make_data_section if TYPE_CHECKING: + from pathlib import Path from ...segtypes.linker_entry import LinkerEntry diff --git a/src/splat/segtypes/common/databin.py b/src/splat/segtypes/common/databin.py index 25ffd257..6d207653 100644 --- a/src/splat/segtypes/common/databin.py +++ b/src/splat/segtypes/common/databin.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ...util import log, options @@ -17,7 +17,7 @@ def is_data() -> bool: def get_linker_section(self) -> str: return ".data" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "wa" def split(self, rom_bytes): diff --git a/src/splat/segtypes/common/eh_frame.py b/src/splat/segtypes/common/eh_frame.py index 67e94702..18a445b7 100644 --- a/src/splat/segtypes/common/eh_frame.py +++ b/src/splat/segtypes/common/eh_frame.py @@ -1,7 +1,10 @@ from __future__ import annotations from .data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class CommonSegEh_frame(CommonSegData): diff --git a/src/splat/segtypes/common/group.py b/src/splat/segtypes/common/group.py index 2c3c97d2..11429ac4 100644 --- a/src/splat/segtypes/common/group.py +++ b/src/splat/segtypes/common/group.py @@ -41,7 +41,7 @@ def get_next_seg_start( ) -> int | None: j = i + 1 while j < len(subsegment_yamls): - ret, is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) + ret, _is_auto_segment = Segment.parse_segment_start(subsegment_yamls[j]) if ret is not None: return ret j += 1 @@ -84,7 +84,7 @@ def parse_subsegments( # Third, try to get the end address from the next segment with a start address end: int | None = None if i < len(yaml["subsegments"]) - 1: - end, end_is_auto_segment = Segment.parse_segment_start( + end, _end_is_auto_segment = Segment.parse_segment_start( yaml["subsegments"][i + 1] ) if start is not None and end is None: diff --git a/src/splat/segtypes/common/header.py b/src/splat/segtypes/common/header.py index 0bab56ad..3514d46c 100644 --- a/src/splat/segtypes/common/header.py +++ b/src/splat/segtypes/common/header.py @@ -1,10 +1,13 @@ from __future__ import annotations -from pathlib import Path from ...util import options from .segment import CommonSegment +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path class CommonSegHeader(CommonSegment): diff --git a/src/splat/segtypes/common/lib.py b/src/splat/segtypes/common/lib.py index 409fa282..49228ab4 100644 --- a/src/splat/segtypes/common/lib.py +++ b/src/splat/segtypes/common/lib.py @@ -1,5 +1,5 @@ +from __future__ import annotations from pathlib import Path -from typing import Optional, List from ...util import log, options @@ -13,7 +13,7 @@ class LinkerEntryLib(LinkerEntry): def __init__( self, segment: Segment, - src_paths: List[Path], + src_paths: list[Path], object_path: Path, section_order: str, section_link: str, @@ -31,11 +31,11 @@ def emit_entry(self, linker_writer: LinkerWriter): class CommonSegLib(CommonSegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -73,7 +73,7 @@ def __init__( def get_linker_section(self) -> str: return self.section - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: path = options.opts.lib_path / self.name object_path = Path(f"{path}.a:{self.object}.o") diff --git a/src/splat/segtypes/common/linker_offset.py b/src/splat/segtypes/common/linker_offset.py index e941715a..7fd3d768 100644 --- a/src/splat/segtypes/common/linker_offset.py +++ b/src/splat/segtypes/common/linker_offset.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List from ..linker_entry import LinkerEntry, LinkerWriter from ..segment import Segment @@ -24,5 +23,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: return [LinkerEntryOffset(self)] diff --git a/src/splat/segtypes/common/pad.py b/src/splat/segtypes/common/pad.py index c99362d9..0471b577 100644 --- a/src/splat/segtypes/common/pad.py +++ b/src/splat/segtypes/common/pad.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List from .segment import CommonSegment from ..linker_entry import LinkerEntry, LinkerWriter @@ -25,5 +24,5 @@ def get_linker_section_order(self) -> str: def get_linker_section_linksection(self) -> str: return "" - def get_linker_entries(self) -> List[LinkerEntry]: + def get_linker_entries(self) -> list[LinkerEntry]: return [LinkerEntryPad(self)] diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index c3731d82..eee1efd0 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -1,7 +1,5 @@ from __future__ import annotations -import spimdisasm -from ..segment import Segment from ...util import log, options, symbols from .data import CommonSegData @@ -10,6 +8,11 @@ DisassemblerSection, make_rodata_section, ) +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ..segment import Segment + import spimdisasm class CommonSegRodata(CommonSegData): @@ -41,7 +44,7 @@ def get_possible_text_subsegment_for_symbol( if len(rodata_sym.contextSym.referenceFunctions) != 1: return None - func = list(rodata_sym.contextSym.referenceFunctions)[0] + func = next(iter(rodata_sym.contextSym.referenceFunctions)) text_segment = self.parent.get_subsegment_for_ram(func.vram) if text_segment is None or not text_segment.is_text(): diff --git a/src/splat/segtypes/common/rodatabin.py b/src/splat/segtypes/common/rodatabin.py index f3e8575a..4e77072f 100644 --- a/src/splat/segtypes/common/rodatabin.py +++ b/src/splat/segtypes/common/rodatabin.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from ...util import log, options @@ -17,7 +17,7 @@ def is_rodata() -> bool: def get_linker_section(self) -> str: return ".rodata" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "a" def split(self, rom_bytes): diff --git a/src/splat/segtypes/common/textbin.py b/src/splat/segtypes/common/textbin.py index e24eb6f7..d7895d5d 100644 --- a/src/splat/segtypes/common/textbin.py +++ b/src/splat/segtypes/common/textbin.py @@ -1,20 +1,23 @@ -from pathlib import Path +from __future__ import annotations import re -from typing import Optional, TextIO +from typing import TextIO, TYPE_CHECKING from ...util import log, options from .segment import CommonSegment +if TYPE_CHECKING: + from pathlib import Path + class CommonSegTextbin(CommonSegment): def __init__( self, - rom_start: Optional[int], - rom_end: Optional[int], + rom_start: int | None, + rom_end: int | None, type: str, name: str, - vram_start: Optional[int], + vram_start: int | None, args: list, yaml, ): @@ -38,10 +41,10 @@ def is_text() -> bool: def get_linker_section(self) -> str: return ".text" - def get_section_flags(self) -> Optional[str]: + def get_section_flags(self) -> str | None: return "ax" - def out_path(self) -> Optional[Path]: + def out_path(self) -> Path | None: if self.use_src_path: return options.opts.src_path / self.dir / f"{self.name}.s" diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index 3f5ff148..76358ba8 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -4,14 +4,16 @@ import re from functools import cache from pathlib import Path -from typing import Dict, List, Set, Union, Optional +from typing import TYPE_CHECKING from collections import OrderedDict from ..util import options, log -from .segment import Segment from ..util.symbols import to_cname +if TYPE_CHECKING: + from .segment import Segment + # clean 'foo/../bar' to 'bar' @cache @@ -124,7 +126,7 @@ class LinkerEntry: def __init__( self, segment: Segment, - src_paths: List[Path], + src_paths: list[Path], object_path: Path, section_order: str, section_link: str, @@ -136,7 +138,7 @@ def __init__( self.section_link = section_link self.noload = noload self.bss_contains_common = segment.bss_contains_common - self.object_path: Optional[Path] = path_to_object_path(object_path) + self.object_path: Path | None = path_to_object_path(object_path) @property def section_order_type(self) -> str: @@ -186,14 +188,14 @@ def emit_entry(self, linker_writer: LinkerWriter) -> None: class LinkerWriter: def __init__(self, is_partial: bool = False): self.linker_discard_section: bool = options.opts.ld_discard_section - self.sections_allowlist: List[str] = options.opts.ld_sections_allowlist - self.sections_denylist: List[str] = options.opts.ld_sections_denylist + self.sections_allowlist: list[str] = options.opts.ld_sections_allowlist + self.sections_denylist: list[str] = options.opts.ld_sections_denylist # Used to store all the linker entries - build tools may want this information - self.entries: List[LinkerEntry] = [] - self.dependencies_entries: List[LinkerEntry] = [] + self.entries: list[LinkerEntry] = [] + self.dependencies_entries: list[LinkerEntry] = [] - self.buffer: List[str] = [] - self.header_symbols: Set[str] = set() + self.buffer: list[str] = [] + self.header_symbols: set[str] = set() self.is_partial: bool = is_partial @@ -239,7 +241,7 @@ def add( self.add_legacy(segment, entries) return - section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -303,12 +305,12 @@ def add_legacy(self, segment: Segment, entries: list[LinkerEntry]) -> None: seg_name = segment.get_cname() # To keep track which sections has been started - started_sections: Dict[str, bool] = { + started_sections: dict[str, bool] = { section_name: False for section_name in options.opts.section_order } # Find where sections are last seen - last_seen_sections: Dict[LinkerEntry, str] = {} + last_seen_sections: dict[LinkerEntry, str] = {} for entry in reversed(entries): if ( entry.section_order_type in options.opts.section_order @@ -446,7 +448,7 @@ def add_partial_segment(self, segment: Segment) -> None: seg_name = segment.get_cname() - section_entries: OrderedDict[str, List[LinkerEntry]] = OrderedDict() + section_entries: OrderedDict[str, list[LinkerEntry]] = OrderedDict() for section_name in segment.section_order: if section_name in options.opts.section_order: section_entries[section_name] = [] @@ -560,7 +562,7 @@ def _end_block(self) -> None: self._indent_level -= 1 self._writeln("}") - def _write_symbol(self, symbol: str, value: Union[str, int]) -> None: + def _write_symbol(self, symbol: str, value: str | int) -> None: symbol = to_cname(symbol) if isinstance(value, int): diff --git a/src/splat/segtypes/n64/ci.py b/src/splat/segtypes/n64/ci.py index 70877619..85b9136e 100644 --- a/src/splat/segtypes/n64/ci.py +++ b/src/splat/segtypes/n64/ci.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING from ...util import log, options @@ -8,6 +7,7 @@ from .img import N64SegImg if TYPE_CHECKING: + from pathlib import Path from .palette import N64SegPalette from ...utils.vram_classes import SerializedSegmentData diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index 3f1295b5..8ef3f7a7 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -8,7 +8,6 @@ import re from typing import TYPE_CHECKING -from pathlib import Path from pygfxd import ( gfxd_buffer_to_string, @@ -47,6 +46,7 @@ from ...util import symbols if TYPE_CHECKING: + from pathlib import Path from ...util.vram_classes import SerializedSegmentData LIGHTS_RE = re.compile(r"\*\(Lightsn \*\)0x[0-9A-F]{8}") @@ -102,6 +102,7 @@ def get_gfxd_target(self) -> gfxd_f3d: if opt == "f3dex2": return gfxd_f3dex2 log.error(f"Unknown target {opt}") + return None def tlut_handler(self, addr: int, idx: int, count: int) -> int: sym = self.create_symbol( @@ -260,9 +261,7 @@ def light_sub_func(match: re.Match[str]) -> str: ) return self.format_sym_name(sym) - out_str = re.sub(LIGHTS_RE, light_sub_func, out_str) - - return out_str + return re.sub(LIGHTS_RE, light_sub_func, out_str) def split(self, rom_bytes: bytes) -> None: out_path = self.out_path() diff --git a/src/splat/segtypes/n64/img.py b/src/splat/segtypes/n64/img.py index df760739..1bb1b0bd 100644 --- a/src/splat/segtypes/n64/img.py +++ b/src/splat/segtypes/n64/img.py @@ -1,14 +1,14 @@ from __future__ import annotations -from pathlib import Path from typing import TYPE_CHECKING -from n64img.image import Image from ...util import log, options from ..segment import Segment if TYPE_CHECKING: + from n64img.image import Image + from pathlib import Path from ...util.vram_classes import SerializedSegmentData diff --git a/src/splat/segtypes/n64/palette.py b/src/splat/segtypes/n64/palette.py index 10d4d99f..4c58dab1 100644 --- a/src/splat/segtypes/n64/palette.py +++ b/src/splat/segtypes/n64/palette.py @@ -1,7 +1,6 @@ from __future__ import annotations from itertools import zip_longest -from pathlib import Path from typing import TYPE_CHECKING, TypeVar from ...util import log, options @@ -10,6 +9,7 @@ from ..segment import Segment if TYPE_CHECKING: + from pathlib import Path from collections.abc import Iterable from typing import Final diff --git a/src/splat/segtypes/n64/vtx.py b/src/splat/segtypes/n64/vtx.py index 622dcc29..5911fc39 100644 --- a/src/splat/segtypes/n64/vtx.py +++ b/src/splat/segtypes/n64/vtx.py @@ -8,7 +8,6 @@ from __future__ import annotations import struct -from pathlib import Path from typing import TYPE_CHECKING from ...util import options, log @@ -16,6 +15,7 @@ from ..common.codesubsegment import CommonSegCodeSubsegment if TYPE_CHECKING: + from pathlib import Path from ...util.vram_classes import SerializedSegmentData from ...util.symbols import Symbol diff --git a/src/splat/segtypes/ps2/ctor.py b/src/splat/segtypes/ps2/ctor.py index 57ed18b2..192b6ea6 100644 --- a/src/splat/segtypes/ps2/ctor.py +++ b/src/splat/segtypes/ps2/ctor.py @@ -1,7 +1,10 @@ from __future__ import annotations from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegCtor(CommonSegData): diff --git a/src/splat/segtypes/ps2/lit4.py b/src/splat/segtypes/ps2/lit4.py index fbd845e5..064deab3 100644 --- a/src/splat/segtypes/ps2/lit4.py +++ b/src/splat/segtypes/ps2/lit4.py @@ -1,7 +1,10 @@ from __future__ import annotations from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit4(CommonSegData): diff --git a/src/splat/segtypes/ps2/lit8.py b/src/splat/segtypes/ps2/lit8.py index 4757d4b1..462d34dc 100644 --- a/src/splat/segtypes/ps2/lit8.py +++ b/src/splat/segtypes/ps2/lit8.py @@ -1,7 +1,10 @@ from __future__ import annotations from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegLit8(CommonSegData): diff --git a/src/splat/segtypes/ps2/vtables.py b/src/splat/segtypes/ps2/vtables.py index a8620b3b..25a29dcd 100644 --- a/src/splat/segtypes/ps2/vtables.py +++ b/src/splat/segtypes/ps2/vtables.py @@ -1,7 +1,10 @@ from __future__ import annotations from ..common.data import CommonSegData -from ...disassembler.disassembler_section import DisassemblerSection +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...disassembler.disassembler_section import DisassemblerSection class Ps2SegVtables(CommonSegData): diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index 0ced64df..e173c3ed 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -6,7 +6,7 @@ import importlib.util from pathlib import Path -from typing import TYPE_CHECKING, Union, TypeAlias, List +from typing import TYPE_CHECKING, Union, TypeAlias from intervaltree import Interval, IntervalTree from ..util import vram_classes @@ -23,7 +23,7 @@ from ..segtypes.linker_entry import LinkerEntry -SerializedSegment: TypeAlias = Union[SerializedSegmentData, List[str]] +SerializedSegment: TypeAlias = Union[SerializedSegmentData, list[str]] def parse_segment_vram(segment: SerializedSegment) -> int | None: diff --git a/src/splat/util/compiler.py b/src/splat/util/compiler.py index eeea374a..0b30719e 100644 --- a/src/splat/util/compiler.py +++ b/src/splat/util/compiler.py @@ -1,5 +1,5 @@ +from __future__ import annotations from dataclasses import dataclass -from typing import Optional, Dict @dataclass @@ -15,7 +15,7 @@ class Compiler: asm_nonmatching_label_macro: str = "nonmatching" c_newline: str = "\n" asm_inc_header: str = "" - asm_emit_size_directive: Optional[bool] = None + asm_emit_size_directive: bool | None = None j_as_branch: bool = False uses_include_asm: bool = True align_on_branch_labels: bool = False @@ -64,7 +64,7 @@ class Compiler: MWCCPS2 = Compiler("MWCCPS2", uses_include_asm=False) EEGCC = Compiler("EEGCC", align_on_branch_labels=True) -compiler_for_name: Dict[str, Compiler] = { +compiler_for_name: dict[str, Compiler] = { x.name: x for x in [ GCC, diff --git a/src/splat/util/conf.py b/src/splat/util/conf.py index eb85a9ef..76241096 100644 --- a/src/splat/util/conf.py +++ b/src/splat/util/conf.py @@ -6,8 +6,9 @@ config = conf.load("path/to/splat.yaml") """ -from typing import Any, Dict, List, Optional -from pathlib import Path +from __future__ import annotations + +from typing import Any, TYPE_CHECKING # This unused import makes the yaml library faster. don't remove import pylibyaml # noqa: F401 @@ -15,6 +16,9 @@ from . import options, vram_classes +if TYPE_CHECKING: + from pathlib import Path + def _merge_configs(main_config, additional_config, additional_config_path): # Merge rules are simple @@ -49,12 +53,12 @@ def _merge_configs(main_config, additional_config, additional_config_path): def load( - config_path: List[Path], - modes: Optional[List[str]] = None, + config_path: list[Path], + modes: list[str] | None = None, verbose: bool = False, disassemble_all: bool = False, make_full_disasm_for_code=False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Returns a `dict` with resolved splat config. @@ -76,7 +80,7 @@ def load( Config with invalid options may raise an error. """ - config: Dict[str, Any] = {} + config: dict[str, Any] = {} for entry in config_path: with entry.open() as f: additional_config = yaml.load(f.read(), Loader=yaml.SafeLoader) diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 1eea0606..c0b8b65b 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -3,11 +3,13 @@ from dataclasses import dataclass import os from pathlib import Path -from typing import cast, Literal, TypeVar -from collections.abc import Mapping +from typing import cast, Literal, TypeVar, TYPE_CHECKING from . import compiler -from .compiler import Compiler + +if TYPE_CHECKING: + from collections.abc import Mapping + from .compiler import Compiler @dataclass diff --git a/src/splat/util/palettes.py b/src/splat/util/palettes.py index d83d24c1..76e29cd9 100644 --- a/src/splat/util/palettes.py +++ b/src/splat/util/palettes.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING from ..util import log @@ -11,7 +11,7 @@ if TYPE_CHECKING: from ..segtypes.segment import Segment -global_ids: Dict[str, N64SegPalette] = {} +global_ids: dict[str, N64SegPalette] = {} # Resolve Raster#palette and Palette#raster links @@ -26,8 +26,8 @@ def collect_global_ids(segments: list[Segment]) -> None: collect_global_ids(segment.subsegments) def process(segments: list[Segment]) -> None: - raster_map: Dict[str, N64SegCi] = {} - palette_map: Dict[str, N64SegPalette] = {} + raster_map: dict[str, N64SegCi] = {} + palette_map: dict[str, N64SegPalette] = {} for segment in segments: if isinstance(segment, N64SegPalette): diff --git a/src/splat/util/ps2/ps2elfinfo.py b/src/splat/util/ps2/ps2elfinfo.py index 2b9419fe..4dd98301 100644 --- a/src/splat/util/ps2/ps2elfinfo.py +++ b/src/splat/util/ps2/ps2elfinfo.py @@ -3,7 +3,6 @@ from __future__ import annotations import dataclasses -from pathlib import Path import spimdisasm from spimdisasm.elf32 import ( Elf32File, @@ -11,10 +10,13 @@ Elf32SectionHeaderFlag, Elf32ObjectFileType, ) -from typing import Optional +from typing import TYPE_CHECKING from .. import log +if TYPE_CHECKING: + from pathlib import Path + ELF_SECTION_MAPPING: dict[str, str] = { ".text": "asm", @@ -56,11 +58,11 @@ class Ps2Elf: size: int compiler: str elf_section_names: list[tuple[str, bool]] - gp: Optional[int] - ld_gp_expression: Optional[str] + gp: int | None + ld_gp_expression: str | None @staticmethod - def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: + def get_info(elf_path: Path, elf_bytes: bytes) -> Ps2Elf | None: # Avoid spimdisasm from complaining about unknown sections. spimdisasm.common.GlobalConfig.QUIET = True @@ -81,7 +83,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: gp = elf.reginfo.gpValue else: gp = None - first_small_section_info: Optional[tuple[str, int]] = None + first_small_section_info: tuple[str, int] | None = None first_segment_name = "cod" segs = [FakeSegment(first_segment_name, 0, 0, [])] @@ -91,7 +93,7 @@ def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]: elf_section_names: list[tuple[str, bool]] = [] - first_offset: Optional[int] = None + first_offset: int | None = None rom_size = 0 previous_type = Elf32Constants.Elf32SectionHeaderType.PROGBITS diff --git a/src/splat/util/relocs.py b/src/splat/util/relocs.py index 5c7c02a9..99c72310 100644 --- a/src/splat/util/relocs.py +++ b/src/splat/util/relocs.py @@ -1,7 +1,6 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict import spimdisasm @@ -17,7 +16,7 @@ class Reloc: addend: int = 0 -all_relocs: Dict[int, Reloc] = {} +all_relocs: dict[int, Reloc] = {} def add_reloc(reloc: Reloc) -> None: diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index b90d3620..03d0c2b0 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -8,10 +8,10 @@ from intervaltree import IntervalTree from ..disassembler import disassembler_instance -from pathlib import Path # circular import if TYPE_CHECKING: + from pathlib import Path from ..segtypes.segment import Segment from . import log, options, progress_bar diff --git a/test.py b/test.py index 8b668159..a54d5464 100755 --- a/test.py +++ b/test.py @@ -5,7 +5,6 @@ from pathlib import Path import spimdisasm import unittest -from typing import List, Tuple from src.splat import __version__ from src.splat.disassembler import disassembler_instance @@ -23,14 +22,14 @@ def compare_files(self, test_path, ref_path): with open(test_path) as test_f, open(ref_path) as ref_f: self.assertListEqual(list(test_f), list(ref_f)) - def get_same_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): + def get_same_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): for name in dcmp.same_files: out.append((name, dcmp.left, dcmp.right)) for sub_dcmp in dcmp.subdirs.values(): self.get_same_files(sub_dcmp, out) - def get_diff_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): + def get_diff_files(self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]]): for name in dcmp.diff_files: out.append((name, dcmp.left, dcmp.right)) @@ -38,7 +37,7 @@ def get_diff_files(self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]]): self.get_diff_files(sub_dcmp, out) def get_left_only_files( - self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] + self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]] ): for name in dcmp.left_only: out.append((name, dcmp.left, dcmp.right)) @@ -47,7 +46,7 @@ def get_left_only_files( self.get_left_only_files(sub_dcmp, out) def get_right_only_files( - self, dcmp: filecmp.dircmp, out: List[Tuple[str, str, str]] + self, dcmp: filecmp.dircmp, out: list[tuple[str, str, str]] ): for name in dcmp.right_only: out.append((name, dcmp.left, dcmp.right)) @@ -63,16 +62,16 @@ def test_basic_app(self): "test/basic_app/split", "test/basic_app/expected", [".gitkeep"] ) - diff_files: List[Tuple[str, str, str]] = [] + diff_files: list[tuple[str, str, str]] = [] self.get_diff_files(comparison, diff_files) - same_files: List[Tuple[str, str, str]] = [] + same_files: list[tuple[str, str, str]] = [] self.get_same_files(comparison, same_files) - left_only_files: List[Tuple[str, str, str]] = [] + left_only_files: list[tuple[str, str, str]] = [] self.get_left_only_files(comparison, left_only_files) - right_only_files: List[Tuple[str, str, str]] = [] + right_only_files: list[tuple[str, str, str]] = [] self.get_right_only_files(comparison, right_only_files) print("same_files", same_files) @@ -469,7 +468,7 @@ def test_overlay(self): ], } - all_segments: List[Segment] = [ + all_segments: list[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, @@ -511,7 +510,7 @@ def test_global(self): ], } - all_segments: List[Segment] = [ + all_segments: list[Segment] = [ CommonSegCode( rom_start=0x1000, rom_end=0x1140, From 00781df9fe6d9f333935480c4245f870b8b4fd1b Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:21:36 -0600 Subject: [PATCH 17/19] Fix/ignore remaining mypy issues --- src/splat/scripts/create_config.py | 3 +- src/splat/segtypes/common/bss.py | 2 ++ src/splat/segtypes/common/c.py | 35 +++++++++++++-------- src/splat/segtypes/common/codesubsegment.py | 10 +++--- src/splat/segtypes/common/rodata.py | 2 ++ src/splat/segtypes/n64/gfx.py | 3 +- 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index 6add9557..809b51ad 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -531,7 +531,7 @@ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: str | None): print("```") -def find_objcopy() -> str: +def find_objcopy() -> str: # noqa: RET503 # First we try to figure out if the user has objcopy on their pc, and under # which name. # We just try a bunch and hope for the best @@ -550,7 +550,6 @@ def find_objcopy() -> str: msg += f" - {name}\n" msg += "\nTry to install one of those or use the `--objcopy` flag to pass the name to your own objcopy to me." log.error(msg) - return None def run_objcopy(objcopy_name: str, elf_path: str, rom: str) -> list[str]: diff --git a/src/splat/segtypes/common/bss.py b/src/splat/segtypes/common/bss.py index c749797e..9aae73b3 100644 --- a/src/splat/segtypes/common/bss.py +++ b/src/splat/segtypes/common/bss.py @@ -3,6 +3,7 @@ from ...util import options, symbols, log from .data import CommonSegData +from .group import CommonSegGroup from ...disassembler.disassembler_section import DisassemblerSection, make_bss_section @@ -60,6 +61,7 @@ def disassemble_data(self, rom_bytes: bytes) -> None: f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'" ) + assert isinstance(self.parent, CommonSegGroup) next_subsegment = self.parent.get_next_subsegment_for_ram( self.vram_start, self.index_within_group ) diff --git a/src/splat/segtypes/common/c.py b/src/splat/segtypes/common/c.py index 752819b1..bbc9a806 100644 --- a/src/splat/segtypes/common/c.py +++ b/src/splat/segtypes/common/c.py @@ -165,9 +165,14 @@ def split(self, rom_bytes: bytes) -> None: self.print_file_boundaries() - assert self.spim_section is not None and isinstance( - self.spim_section.get_section(), spimdisasm.mips.sections.SectionText - ), f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}" + assert self.spim_section is not None, ( + f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}" + ) + spim_section = self.spim_section.get_section() + + assert isinstance(spim_section, spimdisasm.mips.sections.SectionText), ( + f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}" + ) # We want to know if this C section has a corresponding rodata section so we can migrate its rodata rodata_section_type = "" @@ -211,19 +216,23 @@ def split(self, rom_bytes: bytes) -> None: ) assert rodata_sibling.spim_section is not None, f"{rodata_sibling}" + rodata_spim_segment = rodata_sibling.spim_section.get_section() assert isinstance( - rodata_sibling.spim_section.get_section(), + rodata_spim_segment, spimdisasm.mips.sections.SectionRodata, ) - rodata_spim_segment = rodata_sibling.spim_section.get_section() # Stop searching break + assert rodata_spim_segment is None or isinstance( + rodata_spim_segment, spimdisasm.mips.sections.SectionRodata + ), rodata_spim_segment + # Precompute function-rodata pairings symbols_entries = ( spimdisasm.mips.FunctionRodataEntry.getAllEntriesFromSections( - self.spim_section.get_section(), rodata_spim_segment + spim_section, rodata_spim_segment ) ) @@ -288,17 +297,16 @@ def split(self, rom_bytes: bytes) -> None: ) if options.opts.make_full_disasm_for_code: + # TODO: Figure out why mypy thinks these attributes don't exist # Disable gpRelHack since this file is expected to be built with modern gas - section = self.spim_section.get_section() - assert section is not None - old_value = section.getGpRelHack() - section.setGpRelHack(False) + old_value = spim_section.getGpRelHack() # type: ignore[attr-defined] + spim_section.setGpRelHack(False) # type: ignore[attr-defined] if options.opts.platform == "ps2": # Modern gas requires `$` on the special r5900 registers. from rabbitizer import TrinaryValue - for func in section.symbolList: + for func in spim_section.symbolList: assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction) for inst in func.instructions: inst.flag_r5900UseDollar = TrinaryValue.TRUE @@ -306,12 +314,13 @@ def split(self, rom_bytes: bytes) -> None: self.split_as_asmtu_file(self.asm_out_path()) if options.opts.platform == "ps2": - for func in section.symbolList: + for func in spim_section.symbolList: assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction) for inst in func.instructions: inst.flag_r5900UseDollar = TrinaryValue.FALSE - section.setGpRelHack(old_value) + # See comment above + spim_section.setGpRelHack(old_value) # type: ignore[attr-defined] def get_c_preamble(self) -> list[str]: ret = [] diff --git a/src/splat/segtypes/common/codesubsegment.py b/src/splat/segtypes/common/codesubsegment.py index fdb6e7b2..7d578143 100644 --- a/src/splat/segtypes/common/codesubsegment.py +++ b/src/splat/segtypes/common/codesubsegment.py @@ -64,6 +64,7 @@ def __init__( self.is_hasm = False self.use_gp_rel_macro = options.opts.use_gp_rel_macro + # self.parent: CommonSegCode @property def needs_symbols(self) -> bool: @@ -80,10 +81,11 @@ def configure_disassembler_section( section = disassembler_section.get_section() assert section is not None + # TODO: Figure out why mypy thinks these attributes don't exist section.isHandwritten = self.is_hasm - section.instrCat = self.instr_category - section.detectRedundantFunctionEnd = self.detect_redundant_function_end - section.setGpRelHack(not self.use_gp_rel_macro) + section.instrCat = self.instr_category # type: ignore[attr-defined] + section.detectRedundantFunctionEnd = self.detect_redundant_function_end # type: ignore[attr-defined] + section.setGpRelHack(not self.use_gp_rel_macro) # type: ignore[attr-defined] def scan_code(self, rom_bytes: bytes, is_hasm: bool = False) -> None: self.is_hasm = is_hasm @@ -142,7 +144,6 @@ def process_insns( assert func_spim.vram is not None assert func_spim.vramEnd is not None assert self.spim_section is not None - self.parent: CommonSegCode = self.parent symbols.create_symbol_from_spim_symbol( self.get_most_parent(), func_spim.contextSym, force_in_segment=False @@ -188,6 +189,7 @@ def print_file_boundaries(self) -> None: if not self.show_file_boundaries or not self.spim_section: return + assert isinstance(self.parent, CommonSegCode) assert isinstance(self.rom_start, int) section = self.spim_section.get_section() diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index eee1efd0..217543bd 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -2,6 +2,7 @@ from ...util import log, options, symbols +from .code import CommonSegCode from .data import CommonSegData from ...disassembler.disassembler_section import ( @@ -45,6 +46,7 @@ def get_possible_text_subsegment_for_symbol( return None func = next(iter(rodata_sym.contextSym.referenceFunctions)) + assert isinstance(self.parent, CommonSegCode) text_segment = self.parent.get_subsegment_for_ram(func.vram) if text_segment is None or not text_segment.is_text(): diff --git a/src/splat/segtypes/n64/gfx.py b/src/splat/segtypes/n64/gfx.py index 8ef3f7a7..4cb7761b 100644 --- a/src/splat/segtypes/n64/gfx.py +++ b/src/splat/segtypes/n64/gfx.py @@ -88,7 +88,7 @@ def out_path(self) -> Path: def scan(self, rom_bytes: bytes) -> None: self.file_text = self.disassemble_data(rom_bytes) - def get_gfxd_target(self) -> gfxd_f3d: + def get_gfxd_target(self) -> gfxd_f3d: # noqa: RET503 opt = options.opts.gfx_ucode if opt == "f3d": @@ -102,7 +102,6 @@ def get_gfxd_target(self) -> gfxd_f3d: if opt == "f3dex2": return gfxd_f3dex2 log.error(f"Unknown target {opt}") - return None def tlut_handler(self, addr: int, idx: int, count: int) -> int: sym = self.create_symbol( From e475e74a480c6a22902960481ca7e64d5b39bc37 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:25:42 -0600 Subject: [PATCH 18/19] Fix 3.9 support --- src/splat/segtypes/segment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index e173c3ed..9d744df7 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -6,7 +6,7 @@ import importlib.util from pathlib import Path -from typing import TYPE_CHECKING, Union, TypeAlias +from typing import TYPE_CHECKING, Union from intervaltree import Interval, IntervalTree from ..util import vram_classes @@ -21,6 +21,7 @@ # circular import if TYPE_CHECKING: from ..segtypes.linker_entry import LinkerEntry + from typing_extensions import TypeAlias SerializedSegment: TypeAlias = Union[SerializedSegmentData, list[str]] From 60b823831d75c6000f9fde74ed7b570291cc0286 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:06:27 -0600 Subject: [PATCH 19/19] Run ruff formatting --- src/splat/util/symbols.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/splat/util/symbols.py b/src/splat/util/symbols.py index 93debe31..0e047f25 100644 --- a/src/splat/util/symbols.py +++ b/src/splat/util/symbols.py @@ -715,7 +715,6 @@ class Symbol: _generated_default_name: str | None = None _last_type: str | None = None - def __str__(self) -> str: return self.name