From 49759d7495e2e0147382aa4417cfd21523e84e21 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Thu, 26 Feb 2026 19:35:02 +0000 Subject: [PATCH 1/4] Add `o` segment type --- docs/Segments.md | 18 ++++++++++++ src/splat/segtypes/common/o.py | 50 ++++++++++++++++++++++++++++++++++ test.py | 39 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 src/splat/segtypes/common/o.py diff --git a/docs/Segments.md b/docs/Segments.md index a82ae965..5b6f446e 100644 --- a/docs/Segments.md +++ b/docs/Segments.md @@ -290,6 +290,24 @@ It looks for libraries in the [`lib_path`](https://github.com/ethteck/splat/wiki - { type: lib, name: a_lib, object: b_obj, section: .text } ``` +## `o` + +Similar to the `lib` segment but used to reference an object file (for example extracted from a shared library file). Does not extract anything from the input binary. + +```yaml +# link to .text of myobject.o +- [auto, o, myobject] +``` + +```yaml +# link to .rodata of myobject.o +- [auto, o, myobject, .rodata] +``` + +```yaml +# link to .text of myobject.o (dict representation) +- { type: o, name: myobject, section: .text } +``` ## `pad` diff --git a/src/splat/segtypes/common/o.py b/src/splat/segtypes/common/o.py new file mode 100644 index 00000000..3b375098 --- /dev/null +++ b/src/splat/segtypes/common/o.py @@ -0,0 +1,50 @@ +from typing import Optional + +from pathlib import Path + +from ...util import options +from ..common.lib import CommonSegment +from ..segment import parse_segment_vram + + +class CommonSegO(CommonSegment): + def __init__( + self, + rom_start: Optional[int], + rom_end: Optional[int], + type: str, + name: str, + vram_start: Optional[int], + args: list, + yaml, + ): + super().__init__( + rom_start, + rom_end, + type, + name, + vram_start, + args=args, + yaml=yaml, + ) + + vram = parse_segment_vram(self.yaml) + if vram is not None: + self.vram_start = vram + + if isinstance(yaml, dict): + self.section = yaml.get("section", ".text") + else: + if len(args) > 0: + self.section = args[0] + else: + self.section = ".text" + + self.extract = False + + def get_linker_section(self) -> str: + return self.section + + def out_path(self) -> Optional[Path]: + out_path = options.opts.build_path / f"{self.name}" + return out_path diff --git a/test.py b/test.py index d8dfc9b0..564cddd3 100755 --- a/test.py +++ b/test.py @@ -16,6 +16,7 @@ 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.o import CommonSegO from src.splat.segtypes.segment import Segment @@ -368,6 +369,44 @@ def test_disassemble_data(self): assert bss.spim_section.get_section().bssVramEnd == 0x300 +class O(unittest.TestCase): + def test_o_text(self): + # Segment __init__ requires opts to be initialized + test_init() + + o = CommonSegO( + rom_start=0x100, + rom_end=0x200, + vram_start=0x0, + type="o", + name="myobject", + args=[], + yaml=None, + ) + expected = Path("test/basic_app/build/myobject") + self.assertEqual(expected, o.out_path()) + self.assertEqual(".text", o.get_linker_section()) + + def test_o_rodata(self): + # Segment __init__ requires opts to be initialized + test_init() + + section = ".rodata" + o = CommonSegO( + rom_start=0x100, + rom_end=0x200, + vram_start=0x0, + type="o", + name="myobject", + args=[section], + yaml=None, + ) + out_path = Path("test/basic_app/build/myobject") + + self.assertEqual(out_path, o.out_path()) + self.assertEqual(section, o.get_linker_section()) + + class SymbolsInitialize(unittest.TestCase): def test_attrs(self): symbols.reset_symbols() From 0e45f51ae5b3acabd9eea679ada0eae74093acc7 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Thu, 26 Feb 2026 19:38:00 +0000 Subject: [PATCH 2/4] ruff --- test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.py b/test.py index 564cddd3..d38eac4d 100755 --- a/test.py +++ b/test.py @@ -369,7 +369,7 @@ def test_disassemble_data(self): assert bss.spim_section.get_section().bssVramEnd == 0x300 -class O(unittest.TestCase): +class Object(unittest.TestCase): def test_o_text(self): # Segment __init__ requires opts to be initialized test_init() From 6f676b3a23c860c4fc8c25d0529db557a7d65986 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Tue, 3 Mar 2026 20:59:06 +0000 Subject: [PATCH 3/4] Version bump --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4deaa7ae..86491eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # splat Release Notes +### 0.39.0 + +* New `o` segment allowing for a more granular way to include library objects. ### 0.38.1 From cc31e3792d1e993cfda3b4247b7e6081a6c19f4b Mon Sep 17 00:00:00 2001 From: Mark Street Date: Tue, 10 Mar 2026 20:57:05 +0000 Subject: [PATCH 4/4] PR feedback --- README.md | 2 +- docs/Configuration.md | 11 +++++++++++ docs/Segments.md | 10 ++++++---- pyproject.toml | 2 +- src/splat/__init__.py | 2 +- src/splat/segtypes/common/o.py | 2 +- src/splat/util/options.py | 3 +++ 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5bcb3137..87974a9d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The brackets corresponds to the optional dependencies to install while installin If you use a `requirements.txt` file in your repository, then you can add this library with the following line: ```txt -splat64[mips]>=0.38.1,<1.0.0 +splat64[mips]>=0.39.0,<1.0.0 ``` ### Optional dependencies diff --git a/docs/Configuration.md b/docs/Configuration.md index 656ced40..ae0fbd74 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -384,6 +384,17 @@ extensions_path: path/to/extensions/folder Determines the path to library files that are to be linked into the target binary when the [`lib`](https://github.com/ethteck/splat/wiki/Segments#lib) segment type is used. +#### Default +`lib` + + +### o_path + +Determines the path to object files that are to be linked into the target binary when the [`o`](https://github.com/ethteck/splat/wiki/Segments#o) segment type is used. + +#### Default +`build` + ### elf_section_list_path Path to file containing elf section list. diff --git a/docs/Segments.md b/docs/Segments.md index 5b6f446e..ca9e70c3 100644 --- a/docs/Segments.md +++ b/docs/Segments.md @@ -292,21 +292,23 @@ It looks for libraries in the [`lib_path`](https://github.com/ethteck/splat/wiki ## `o` -Similar to the `lib` segment but used to reference an object file (for example extracted from a shared library file). Does not extract anything from the input binary. +Similar to the `lib` segment but this subsegment is used to reference an object file (for example extracted from a shared library file). Does not extract anything from the input binary. + +It looks for libraries in the [`o_path`](https://github.com/ethteck/splat/wiki/Configuration#o_path) global option. ```yaml # link to .text of myobject.o -- [auto, o, myobject] +- [0x14040, o, myobject] ``` ```yaml # link to .rodata of myobject.o -- [auto, o, myobject, .rodata] +- [0x14240, o, myobject, .rodata] ``` ```yaml # link to .text of myobject.o (dict representation) -- { type: o, name: myobject, section: .text } +- { start: 0x14040, type: o, name: myobject, section: .text } ``` ## `pad` diff --git a/pyproject.toml b/pyproject.toml index eda5470b..a135d7eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "splat64" # Should be synced with src/splat/__init__.py -version = "0.38.1" +version = "0.39.0" description = "A binary splitting tool to assist with decompilation and modding projects" readme = "README.md" license = {file = "LICENSE"} diff --git a/src/splat/__init__.py b/src/splat/__init__.py index a8063a85..8a5f6a51 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -1,7 +1,7 @@ __package_name__ = __name__ # Should be synced with pyproject.toml -__version__ = "0.38.1" +__version__ = "0.39.0" __author__ = "ethteck" from . import util as util diff --git a/src/splat/segtypes/common/o.py b/src/splat/segtypes/common/o.py index 3b375098..e722c2ba 100644 --- a/src/splat/segtypes/common/o.py +++ b/src/splat/segtypes/common/o.py @@ -46,5 +46,5 @@ def get_linker_section(self) -> str: return self.section def out_path(self) -> Optional[Path]: - out_path = options.opts.build_path / f"{self.name}" + out_path = options.opts.o_path / f"{self.name}" return out_path diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 2a6337f0..05d312bf 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -99,6 +99,8 @@ class SplatOpts: # Determines the path to library files that are to be linked into the target binary lib_path: Path + # Determines the path to object files that are to be linked into the target binary + o_path: Path # TODO document elf_section_list_path: Optional[Path] @@ -502,6 +504,7 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]: ), extensions_path=p.parse_optional_path(base_path, "extensions_path"), lib_path=p.parse_path(base_path, "lib_path", "lib"), + o_path=p.parse_path(base_path, "o_path", "build"), elf_section_list_path=p.parse_optional_path(base_path, "elf_section_list_path"), subalign=p.parse_optional_opt_with_default("subalign", int, 16), emit_subalign=p.parse_opt("emit_subalign", bool, True),