splat64 0.34.3__py3-none-any.whl → 0.35.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
splat/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  __package_name__ = __name__
2
2
 
3
3
  # Should be synced with pyproject.toml
4
- __version__ = "0.34.3"
4
+ __version__ = "0.35.0"
5
5
  __author__ = "ethteck"
6
6
 
7
7
  from . import util as util
@@ -7,7 +7,7 @@ from typing import Set
7
7
 
8
8
  class SpimdisasmDisassembler(disassembler.Disassembler):
9
9
  # This value should be kept in sync with the version listed on requirements.txt and pyproject.toml
10
- SPIMDISASM_MIN = (1, 33, 0)
10
+ SPIMDISASM_MIN = (1, 36, 0)
11
11
 
12
12
  def configure(self):
13
13
  # Configure spimdisasm
@@ -93,9 +93,15 @@ class SpimdisasmDisassembler(disassembler.Disassembler):
93
93
  )
94
94
  spimdisasm.common.GlobalConfig.ASM_DATA_LABEL = options.opts.asm_data_macro
95
95
  spimdisasm.common.GlobalConfig.ASM_TEXT_END_LABEL = options.opts.asm_end_label
96
+ spimdisasm.common.GlobalConfig.ASM_DATA_END_LABEL = (
97
+ options.opts.asm_data_end_label
98
+ )
96
99
  spimdisasm.common.GlobalConfig.ASM_EHTBL_LABEL = (
97
100
  options.opts.asm_ehtable_label_macro
98
101
  )
102
+ spimdisasm.common.GlobalConfig.ASM_NM_LABEL = (
103
+ options.opts.asm_nonmatching_label_macro
104
+ )
99
105
 
100
106
  if options.opts.asm_emit_size_directive is not None:
101
107
  spimdisasm.common.GlobalConfig.ASM_EMIT_SIZE_DIRECTIVE = (
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from ..util.n64 import find_code_length, rominfo
8
8
  from ..util.psx import psxexeinfo
9
- from ..util import log
9
+ from ..util import log, file_presets, conf
10
10
 
11
11
 
12
12
  def main(file_path: Path):
@@ -70,10 +70,6 @@ options:
70
70
  use_legacy_include_asm: False
71
71
  mips_abi_float_regs: o32
72
72
 
73
- asm_function_macro: glabel
74
- asm_jtbl_label_macro: jlabel
75
- asm_data_macro: dlabel
76
-
77
73
  # section_order: [".text", ".data", ".rodata", ".bss"]
78
74
  # auto_link_sections: [".data", ".rodata", ".bss"]
79
75
 
@@ -111,7 +107,7 @@ segments:
111
107
  type: header
112
108
  start: 0x0
113
109
 
114
- - name: boot
110
+ - name: ipl3
115
111
  type: bin
116
112
  start: 0x40
117
113
 
@@ -122,7 +118,7 @@ segments:
122
118
  subsegments:
123
119
  - [0x1000, hasm]
124
120
  """
125
- if rom.entrypoint_info.data_size > 0:
121
+ if rom.entrypoint_info.data_size is not None:
126
122
  segments += f"""\
127
123
  - [0x{0x1000 + rom.entrypoint_info.entry_size:X}, data]
128
124
  """
@@ -139,7 +135,7 @@ segments:
139
135
 
140
136
  if rom.entrypoint_info.bss_size is not None:
141
137
  segments += f"""\
142
- bss_size: 0x{rom.entrypoint_info.bss_size:X}
138
+ bss_size: 0x{rom.entrypoint_info.bss_size.value:X}
143
139
  """
144
140
 
145
141
  segments += f"""\
@@ -152,11 +148,13 @@ segments:
152
148
  and rom.entrypoint_info.bss_start_address is not None
153
149
  and first_section_end > main_rom_start
154
150
  ):
155
- bss_start = rom.entrypoint_info.bss_start_address - rom.entry_point + 0x1000
151
+ bss_start = (
152
+ rom.entrypoint_info.bss_start_address.value - rom.entry_point + 0x1000
153
+ )
156
154
  # first_section_end points to the start of data
157
155
  segments += f"""\
158
156
  - [0x{first_section_end:X}, data]
159
- - {{ type: bss, vram: 0x{rom.entrypoint_info.bss_start_address:08X} }}
157
+ - {{ type: bss, vram: 0x{rom.entrypoint_info.bss_start_address.value:08X} }}
160
158
  """
161
159
  # Point next segment to the detected end of the main one
162
160
  first_section_end = bss_start
@@ -174,12 +172,93 @@ segments:
174
172
  - [0x{rom.size:X}]
175
173
  """
176
174
 
177
- out_file = f"{cleaned_basename}.yaml"
178
- with open(out_file, "w", newline="\n") as f:
175
+ out_file = Path(f"{cleaned_basename}.yaml")
176
+ with out_file.open("w", newline="\n") as f:
179
177
  print(f"Writing config to {out_file}")
180
178
  f.write(header)
181
179
  f.write(segments)
182
180
 
181
+ # `file_presets` requires an initialized `opts`.
182
+ # A simple way to do that is to simply load the yaml we just generated.
183
+ conf.load([out_file])
184
+ file_presets.write_all_files()
185
+
186
+ # Write reloc_addrs.txt file
187
+ reloc_addrs = []
188
+ if rom.entrypoint_info.bss_start_address is not None:
189
+ reloc_addrs.append(
190
+ f"rom:0x{rom.entrypoint_info.bss_start_address.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_START"
191
+ )
192
+ reloc_addrs.append(
193
+ f"rom:0x{rom.entrypoint_info.bss_start_address.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_START"
194
+ )
195
+ reloc_addrs.append("")
196
+ if rom.entrypoint_info.bss_size is not None:
197
+ reloc_addrs.append(
198
+ f"rom:0x{rom.entrypoint_info.bss_size.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_SIZE"
199
+ )
200
+ reloc_addrs.append(
201
+ f"rom:0x{rom.entrypoint_info.bss_size.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_SIZE"
202
+ )
203
+ reloc_addrs.append("")
204
+ if rom.entrypoint_info.bss_end_address is not None:
205
+ reloc_addrs.append(
206
+ f"rom:0x{rom.entrypoint_info.bss_end_address.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_END"
207
+ )
208
+ reloc_addrs.append(
209
+ f"rom:0x{rom.entrypoint_info.bss_end_address.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_END"
210
+ )
211
+ reloc_addrs.append("")
212
+ if rom.entrypoint_info.stack_top is not None:
213
+ reloc_addrs.append(
214
+ '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.'
215
+ )
216
+ reloc_addrs.append(
217
+ "// 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."
218
+ )
219
+ reloc_addrs.append(
220
+ "// Once you have found it you can properly name it and specify the length of this stack as the addend value here."
221
+ )
222
+ reloc_addrs.append(
223
+ f"// The address of the end of the stack is 0x{rom.entrypoint_info.stack_top.value:08X}."
224
+ )
225
+ reloc_addrs.append(
226
+ 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."
227
+ )
228
+ reloc_addrs.append(
229
+ f"// rom:0x{rom.entrypoint_info.stack_top.rom_hi:06X} reloc:MIPS_HI16 symbol:main_stack addend:0xXXXX"
230
+ )
231
+ reloc_addrs.append(
232
+ f"// rom:0x{rom.entrypoint_info.stack_top.rom_lo:06X} reloc:MIPS_LO16 symbol:main_stack addend:0xXXXX"
233
+ )
234
+ reloc_addrs.append("")
235
+ if reloc_addrs:
236
+ with Path("reloc_addrs.txt").open("w", newline="\n") as f:
237
+ print("Writing reloc_addrs.txt")
238
+ f.write(
239
+ "// Visit https://github.com/ethteck/splat/wiki/Advanced-Reloc for documentation about this file\n"
240
+ )
241
+ f.write("// entrypoint relocs\n")
242
+ contents = "\n".join(reloc_addrs)
243
+ f.write(contents)
244
+
245
+ # Write symbol_addrs.txt file
246
+ symbol_addrs = []
247
+ symbol_addrs.append(f"entrypoint = 0x{rom.entry_point:08X}; // type:func")
248
+ if rom.entrypoint_info.main_address is not None:
249
+ symbol_addrs.append(
250
+ f"main = 0x{rom.entrypoint_info.main_address.value:08X}; // type:func"
251
+ )
252
+ if symbol_addrs:
253
+ symbol_addrs.append("")
254
+ with Path("symbol_addrs.txt").open("w", newline="\n") as f:
255
+ print("Writing symbol_addrs.txt")
256
+ f.write(
257
+ "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n"
258
+ )
259
+ contents = "\n".join(symbol_addrs)
260
+ f.write(contents)
261
+
183
262
 
184
263
  def create_psx_config(exe_path: Path, exe_bytes: bytes):
185
264
  exe = psxexeinfo.PsxExe.get_info(exe_path, exe_bytes)
@@ -211,10 +290,6 @@ options:
211
290
  o_as_suffix: True
212
291
  use_legacy_include_asm: False
213
292
 
214
- asm_function_macro: glabel
215
- asm_jtbl_label_macro: jlabel
216
- asm_data_macro: dlabel
217
-
218
293
  section_order: [".rodata", ".text", ".data", ".bss"]
219
294
  # auto_link_sections: [".data", ".rodata", ".bss"]
220
295
 
@@ -251,7 +326,7 @@ segments:
251
326
  """
252
327
  text_offset = exe.text_offset
253
328
  if text_offset != 0x800:
254
- segments += f"""\
329
+ segments += """\
255
330
  - [0x800, rodata, 800]
256
331
  """
257
332
  segments += f"""\
@@ -268,12 +343,17 @@ segments:
268
343
  - [0x{exe.size:X}]
269
344
  """
270
345
 
271
- out_file = f"{cleaned_basename}.yaml"
272
- with open(out_file, "w", newline="\n") as f:
346
+ out_file = Path(f"{cleaned_basename}.yaml")
347
+ with out_file.open("w", newline="\n") as f:
273
348
  print(f"Writing config to {out_file}")
274
349
  f.write(header)
275
350
  f.write(segments)
276
351
 
352
+ # `file_presets` requires an initialized `opts`.
353
+ # A simple way to do that is to simply load the yaml we just generated.
354
+ conf.load([out_file])
355
+ file_presets.write_all_files()
356
+
277
357
 
278
358
  def add_arguments_to_parser(parser: argparse.ArgumentParser):
279
359
  parser.add_argument(
splat/scripts/split.py CHANGED
@@ -8,9 +8,9 @@ from pathlib import Path
8
8
 
9
9
  from .. import __package_name__, __version__
10
10
  from ..disassembler import disassembler_instance
11
- from ..util import cache_handler, progress_bar, vram_classes, statistics
11
+ from ..util import cache_handler, progress_bar, vram_classes, statistics, file_presets
12
12
 
13
- from colorama import Fore, Style
13
+ from colorama import Style
14
14
  from intervaltree import Interval, IntervalTree
15
15
  import sys
16
16
 
@@ -19,6 +19,7 @@ from ..segtypes.linker_entry import (
19
19
  get_segment_vram_end_symbol_name,
20
20
  )
21
21
  from ..segtypes.segment import Segment
22
+ from ..segtypes.common.group import CommonSegGroup
22
23
  from ..util import conf, log, options, palettes, symbols, relocs
23
24
 
24
25
  linker_writer: LinkerWriter
@@ -38,6 +39,9 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
38
39
  segments_by_name: Dict[str, Segment] = {}
39
40
  ret: List[Segment] = []
40
41
 
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
+
41
45
  last_rom_end = 0
42
46
 
43
47
  for i, seg_yaml in enumerate(config_segments):
@@ -80,6 +84,9 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
80
84
 
81
85
  segments_by_name[segment.name] = segment
82
86
 
87
+ if segment.pair_segment_name is not None:
88
+ do_cross_segment_pairing = True
89
+
83
90
  ret.append(segment)
84
91
  if (
85
92
  isinstance(segment.rom_start, int)
@@ -112,6 +119,50 @@ def initialize_segments(config_segments: Union[dict, list]) -> List[Segment]:
112
119
  "Last segment in config cannot be a pad segment; see https://github.com/ethteck/splat/wiki/Segments#pad"
113
120
  )
114
121
 
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
+
115
166
  return ret
116
167
 
117
168
 
@@ -520,6 +571,8 @@ def main(
520
571
  if options.opts.is_mode_active("code"):
521
572
  dump_symbols()
522
573
 
574
+ file_presets.write_all_files()
575
+
523
576
 
524
577
  def add_arguments_to_parser(parser: argparse.ArgumentParser):
525
578
  parser.add_argument(
@@ -168,6 +168,10 @@ class CommonSegGroup(CommonSegment):
168
168
  for sub in self.subsegments:
169
169
  if sub.contains_vram(addr):
170
170
  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
171
175
  return None
172
176
 
173
177
  def get_next_subsegment_for_ram(
@@ -187,3 +191,22 @@ class CommonSegGroup(CommonSegment):
187
191
  if sub.vram_start > addr:
188
192
  return sub
189
193
  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
@@ -97,6 +97,8 @@ class CommonSegTextbin(CommonSegment):
97
97
 
98
98
  if self.is_text() and options.opts.asm_end_label != "":
99
99
  f.write(f"{options.opts.asm_end_label} {sym.name}\n")
100
+ elif self.is_data() and options.opts.asm_data_end_label != "":
101
+ f.write(f"{options.opts.asm_data_end_label} {sym.name}\n")
100
102
 
101
103
  if sym.given_name_end is not None:
102
104
  if (
@@ -106,6 +108,12 @@ class CommonSegTextbin(CommonSegment):
106
108
  f.write(f"{asm_label} {sym.given_name_end}\n")
107
109
  if asm_label == ".globl":
108
110
  f.write(f"{sym.given_name_end}:\n")
111
+ if self.is_text() and options.opts.asm_end_label != "":
112
+ f.write(f"{options.opts.asm_end_label} {sym.given_name_end}\n")
113
+ elif self.is_data() and options.opts.asm_data_end_label != "":
114
+ f.write(
115
+ f"{options.opts.asm_data_end_label} {sym.given_name_end}\n"
116
+ )
109
117
 
110
118
  def split(self, rom_bytes):
111
119
  if self.rom_end is None:
@@ -522,7 +522,7 @@ class LinkerWriter:
522
522
  )
523
523
 
524
524
  def save_dependencies_file(self, output_path: Path, target_elf_path: Path):
525
- output = f"{target_elf_path.as_posix()}:"
525
+ output = f"{clean_up_path(target_elf_path).as_posix()}:"
526
526
 
527
527
  for entry in self.dependencies_entries:
528
528
  if entry.object_path is None:
splat/segtypes/segment.py CHANGED
@@ -274,6 +274,12 @@ class Segment:
274
274
  return suggestion_rodata_section_start
275
275
  return None
276
276
 
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
+
277
283
  def __init__(
278
284
  self,
279
285
  rom_start: Optional[int],
@@ -319,6 +325,9 @@ class Segment:
319
325
  self.parent: Optional[Segment] = None
320
326
  self.sibling: Optional[Segment] = None
321
327
  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
+
322
331
  self.file_path: Optional[Path] = None
323
332
 
324
333
  self.args: List[str] = args
splat/util/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from . import cache_handler as cache_handler
2
2
  from . import color as color
3
3
  from . import compiler as compiler
4
+ from . import file_presets as file_presets
4
5
  from . import log as log
5
6
  from . import n64 as n64
6
7
  from . import options as options
splat/util/compiler.py CHANGED
@@ -6,15 +6,18 @@ from typing import Optional, Dict
6
6
  class Compiler:
7
7
  name: str
8
8
  asm_function_macro: str = "glabel"
9
- asm_function_alt_macro: str = "glabel"
10
- asm_jtbl_label_macro: str = "glabel"
11
- asm_data_macro: str = "glabel"
12
- asm_end_label: str = ""
9
+ asm_function_alt_macro: str = "alabel"
10
+ asm_jtbl_label_macro: str = "jlabel"
11
+ asm_data_macro: str = "dlabel"
12
+ asm_end_label: str = "endlabel"
13
+ asm_data_end_label: str = "enddlabel"
13
14
  asm_ehtable_label_macro: str = "ehlabel"
15
+ asm_nonmatching_label_macro: str = "nonmatching"
14
16
  c_newline: str = "\n"
15
17
  asm_inc_header: str = ""
16
18
  asm_emit_size_directive: Optional[bool] = None
17
19
  j_as_branch: bool = False
20
+ uses_include_asm: bool = True
18
21
 
19
22
 
20
23
  GCC = Compiler(
@@ -30,12 +33,14 @@ SN64 = Compiler(
30
33
  asm_jtbl_label_macro=".globl",
31
34
  asm_data_macro=".globl",
32
35
  asm_end_label=".end",
36
+ asm_data_end_label="",
37
+ asm_nonmatching_label_macro="",
33
38
  c_newline="\r\n",
34
39
  asm_emit_size_directive=False,
35
40
  j_as_branch=True,
36
41
  )
37
42
 
38
- IDO = Compiler("IDO", asm_emit_size_directive=False)
43
+ IDO = Compiler("IDO", asm_emit_size_directive=False, uses_include_asm=False)
39
44
 
40
45
  KMC = Compiler(
41
46
  "KMC",
@@ -55,7 +60,7 @@ PSYQ = Compiler(
55
60
  )
56
61
 
57
62
  # PS2
58
- MWCCPS2 = Compiler("MWCCPS2")
63
+ MWCCPS2 = Compiler("MWCCPS2", uses_include_asm=False)
59
64
  EEGCC = Compiler("EEGCC")
60
65
 
61
66
  compiler_for_name: Dict[str, Compiler] = {