Skip to content

Commit a6a7542

Browse files
authored
Option to allow migrating rodata between segments (#463)
* start working on it * fixes * docs and such * Fix pairing recommendations * missed this
1 parent 53fcfad commit a6a7542

File tree

8 files changed

+125
-3
lines changed

8 files changed

+125
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# splat Release Notes
22

3+
### 0.35.0
4+
5+
* Add `pair_segment` option to segments:
6+
* Allows pairing the sections of two different segments together, making cross-segment rodata migration possible.
7+
38
### 0.34.3
9+
410
* `asm_emit_size_directive` now also affects generated assembly from `textbin`, `databin`, and `rodatabin` segments.
511

612
### 0.34.2

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The brackets corresponds to the optional dependencies to install while installin
2121
If you use a `requirements.txt` file in your repository, then you can add this library with the following line:
2222

2323
```txt
24-
splat64[mips]>=0.34.3,<1.0.0
24+
splat64[mips]>=0.35.0,<1.0.0
2525
```
2626

2727
### Optional dependencies

docs/Segments.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,36 @@ Defaults to the global option.
583583
vram: 0x80000460
584584
suggestion_rodata_section_start: False
585585
```
586+
587+
### `pair_segment`
588+
589+
Allows pairing sections of two different segments together.
590+
591+
The main purpose of this is to make the automatic rodata-to-function migration possible, since the default behavior only allows pairing different sections of the same name under the same segment only. This kind of ROM layout can be seen on some TLB games from N64 projects.
592+
593+
This value expects the name of the other segment that should be paired to the current one. Only one of the two to-be-paired segments should have this attribute.
594+
595+
**Example:**
596+
597+
```yaml
598+
- name: init
599+
type: code
600+
start: 0x00001000
601+
vram: 0x10001000
602+
pair_segment: init_data # This is the name of the following segment.
603+
subsegments:
604+
# -- snip --
605+
- [0x15550, c, libultra/audio/init_15550]
606+
# -- snip --
607+
608+
- name: init_data
609+
type: code
610+
start: 0x000290D0
611+
vram: 0x800290D0
612+
bss_size: 0x16690
613+
# Note there's no `pair_segment: init` on this segment.
614+
subsegments:
615+
# -- snip --
616+
- [0x2C6B0, .rodata, libultra/audio/init_15550]
617+
# -- snip --
618+
```

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "splat64"
33
# Should be synced with src/splat/__init__.py
4-
version = "0.34.3"
4+
version = "0.35.0"
55
description = "A binary splitting tool to assist with decompilation and modding projects"
66
readme = "README.md"
77
license = {file = "LICENSE"}

src/splat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__package_name__ = __name__
22

33
# Should be synced with pyproject.toml
4-
__version__ = "0.34.3"
4+
__version__ = "0.35.0"
55
__author__ = "ethteck"
66

77
from . import util as util

src/splat/scripts/split.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
get_segment_vram_end_symbol_name,
2020
)
2121
from ..segtypes.segment import Segment
22+
from ..segtypes.common.group import CommonSegGroup
2223
from ..util import conf, log, options, palettes, symbols, relocs
2324

2425
linker_writer: LinkerWriter
@@ -38,6 +39,9 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
3839
segments_by_name: Dict[str, Segment] = {}
3940
ret: List[Segment] = []
4041

42+
# Cross segment pairing can be quite expensive, so we try to avoid it if the user haven't requested it.
43+
do_cross_segment_pairing = False
44+
4145
last_rom_end = 0
4246

4347
for i, seg_yaml in enumerate(config_segments):
@@ -80,6 +84,9 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
8084

8185
segments_by_name[segment.name] = segment
8286

87+
if segment.pair_segment_name is not None:
88+
do_cross_segment_pairing = True
89+
8390
ret.append(segment)
8491
if (
8592
isinstance(segment.rom_start, int)
@@ -112,6 +119,50 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
112119
"Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad"
113120
)
114121

122+
if do_cross_segment_pairing:
123+
# Do the cross segment pairing
124+
for seg in ret:
125+
if seg.pair_segment_name is not None:
126+
found = False
127+
for other_seg in ret:
128+
if seg == other_seg:
129+
continue
130+
if seg.pair_segment_name == other_seg.name and isinstance(
131+
other_seg, CommonSegGroup
132+
):
133+
# We found the other segment specified by `pair_segment`
134+
135+
if other_seg.pair_segment_name is not None:
136+
log.error(
137+
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."
138+
)
139+
140+
# Not user error, hopefully...
141+
assert (
142+
seg.paired_segment is None
143+
), f"Somehow '{seg.name}' was already paired so something else? It is paired to '{seg.paired_segment.name}' instead of {other_seg.name}"
144+
assert (
145+
other_seg.paired_segment is None
146+
), f"Somehow '{other_seg.name}' was already paired so something else? It is paired to '{other_seg.paired_segment.name}' instead of {seg.name}"
147+
148+
found = True
149+
# Pair them
150+
seg.paired_segment = other_seg
151+
other_seg.paired_segment = seg
152+
153+
if isinstance(seg, CommonSegGroup) and isinstance(
154+
other_seg, CommonSegGroup
155+
):
156+
# Pair the subsegments
157+
seg.pair_subsegments_to_other_segment(other_seg)
158+
159+
break
160+
161+
if not found:
162+
log.error(
163+
f"Segment '{seg.pair_segment_name}' not found.\n It is referenced by segment '{seg.name}', because of the `pair_segment` attribute."
164+
)
165+
115166
return ret
116167

117168

src/splat/segtypes/common/group.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ def get_subsegment_for_ram(self, addr: int) -> Optional[Segment]:
168168
for sub in self.subsegments:
169169
if sub.contains_vram(addr):
170170
return sub
171+
if isinstance(self.paired_segment, CommonSegGroup):
172+
for sub in self.paired_segment.subsegments:
173+
if sub.contains_vram(addr):
174+
return sub
171175
return None
172176

173177
def get_next_subsegment_for_ram(
@@ -187,3 +191,22 @@ def get_next_subsegment_for_ram(
187191
if sub.vram_start > addr:
188192
return sub
189193
return None
194+
195+
def pair_subsegments_to_other_segment(
196+
self,
197+
other_segment: "CommonSegGroup",
198+
):
199+
# Pair cousins with the same name
200+
for segment in self.subsegments:
201+
for sibling in other_segment.subsegments:
202+
if segment.name == sibling.name:
203+
# Make them reference each other
204+
segment.siblings[sibling.get_linker_section_linksection()] = sibling
205+
sibling.siblings[segment.get_linker_section_linksection()] = segment
206+
207+
if segment.is_text():
208+
sibling.sibling = segment
209+
elif sibling.is_text():
210+
segment.sibling = sibling
211+
212+
break

src/splat/segtypes/segment.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ def parse_suggestion_rodata_section_start(
274274
return suggestion_rodata_section_start
275275
return None
276276

277+
@staticmethod
278+
def parse_pair_segment(yaml: Union[dict, list]) -> Optional[str]:
279+
if isinstance(yaml, dict) and "pair_segment" in yaml:
280+
return yaml["pair_segment"]
281+
return None
282+
277283
def __init__(
278284
self,
279285
rom_start: Optional[int],
@@ -319,6 +325,9 @@ def __init__(
319325
self.parent: Optional[Segment] = None
320326
self.sibling: Optional[Segment] = None
321327
self.siblings: Dict[str, Segment] = {}
328+
self.pair_segment_name: Optional[str] = self.parse_pair_segment(yaml)
329+
self.paired_segment: Optional[Segment] = None
330+
322331
self.file_path: Optional[Path] = None
323332

324333
self.args: List[str] = args

0 commit comments

Comments
 (0)