splat64 0.32.2__py3-none-any.whl → 0.33.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.32.2"
4
+ __version__ = "0.33.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, 32, 0)
10
+ SPIMDISASM_MIN = (1, 33, 0)
11
11
 
12
12
  def configure(self):
13
13
  # Configure spimdisasm
splat/scripts/split.py CHANGED
@@ -10,10 +10,6 @@ from .. import __package_name__, __version__
10
10
  from ..disassembler import disassembler_instance
11
11
  from ..util import cache_handler, progress_bar, vram_classes, statistics
12
12
 
13
- # This unused import makes the yaml library faster. don't remove
14
- import pylibyaml # pyright: ignore
15
- import yaml
16
-
17
13
  from colorama import Fore, Style
18
14
  from intervaltree import Interval, IntervalTree
19
15
  import sys
@@ -23,7 +19,7 @@ from ..segtypes.linker_entry import (
23
19
  get_segment_vram_end_symbol_name,
24
20
  )
25
21
  from ..segtypes.segment import Segment
26
- from ..util import log, options, palettes, symbols, relocs
22
+ from ..util import conf, log, options, palettes, symbols, relocs
27
23
 
28
24
  linker_writer: LinkerWriter
29
25
  config: Dict[str, Any]
@@ -142,34 +138,6 @@ def assign_symbols_to_segments():
142
138
  seg.add_symbol(symbol)
143
139
 
144
140
 
145
- def merge_configs(main_config, additional_config):
146
- # Merge rules are simple
147
- # For each key in the dictionary
148
- # - If list then append to list
149
- # - If a dictionary then repeat merge on sub dictionary entries
150
- # - Else assume string or number and replace entry
151
-
152
- for curkey in additional_config:
153
- if curkey not in main_config:
154
- main_config[curkey] = additional_config[curkey]
155
- elif type(main_config[curkey]) != type(additional_config[curkey]):
156
- log.error(f"Type for key {curkey} in configs does not match")
157
- else:
158
- # keys exist and match, see if a list to append
159
- if type(main_config[curkey]) == list:
160
- main_config[curkey] += additional_config[curkey]
161
- elif type(main_config[curkey]) == dict:
162
- # need to merge sub areas
163
- main_config[curkey] = merge_configs(
164
- main_config[curkey], additional_config[curkey]
165
- )
166
- else:
167
- # not a list or dictionary, must be a number or string, overwrite
168
- main_config[curkey] = additional_config[curkey]
169
-
170
- return main_config
171
-
172
-
173
141
  def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str:
174
142
  s = seg.name.strip()
175
143
  if len(s) > limit:
@@ -203,25 +171,6 @@ def calc_segment_dependences(
203
171
  return vram_class_to_follows_segments
204
172
 
205
173
 
206
- def initialize_config(
207
- config_path: List[str],
208
- modes: Optional[List[str]],
209
- verbose: bool,
210
- disassemble_all: bool = False,
211
- ) -> Dict[str, Any]:
212
- config: Dict[str, Any] = {}
213
- for entry in config_path:
214
- with open(entry) as f:
215
- additional_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
216
- config = merge_configs(config, additional_config)
217
-
218
- vram_classes.initialize(config.get("vram_classes"))
219
-
220
- options.initialize(config, config_path, modes, verbose, disassemble_all)
221
-
222
- return config
223
-
224
-
225
174
  def read_target_binary() -> bytes:
226
175
  rom_bytes = options.opts.target_path.read_bytes()
227
176
 
@@ -493,20 +442,27 @@ def dump_symbols() -> None:
493
442
 
494
443
 
495
444
  def main(
496
- config_path: List[str],
445
+ config_path: List[Path],
497
446
  modes: Optional[List[str]],
498
447
  verbose: bool,
499
448
  use_cache: bool = True,
500
449
  skip_version_check: bool = False,
501
450
  stdout_only: bool = False,
502
451
  disassemble_all: bool = False,
452
+ make_full_disasm_for_code=False,
503
453
  ):
504
454
  if stdout_only:
505
455
  progress_bar.out_file = sys.stdout
506
456
 
507
457
  # Load config
508
458
  global config
509
- config = initialize_config(config_path, modes, verbose, disassemble_all)
459
+ config = conf.load(
460
+ config_path,
461
+ modes,
462
+ verbose,
463
+ disassemble_all,
464
+ make_full_disasm_for_code,
465
+ )
510
466
 
511
467
  disassembler_instance.create_disassembler_instance(skip_version_check, __version__)
512
468
 
@@ -565,7 +521,10 @@ def main(
565
521
 
566
522
  def add_arguments_to_parser(parser: argparse.ArgumentParser):
567
523
  parser.add_argument(
568
- "config", help="path to a compatible config .yaml file", nargs="+"
524
+ "config",
525
+ help="path to a compatible config .yaml file",
526
+ nargs="+",
527
+ type=Path,
569
528
  )
570
529
  parser.add_argument("--modes", nargs="+", default="all")
571
530
  parser.add_argument("--verbose", action="store_true", help="Enable debug logging")
@@ -585,6 +544,11 @@ def add_arguments_to_parser(parser: argparse.ArgumentParser):
585
544
  help="Disasemble matched functions and migrated data",
586
545
  action="store_true",
587
546
  )
547
+ parser.add_argument(
548
+ "--make-full-disasm-for-code",
549
+ help="Emit a full `.s` file for each `c`/`cpp` segment besides the generated `nonmatchings` individual functions",
550
+ action="store_true",
551
+ )
588
552
 
589
553
 
590
554
  def process_arguments(args: argparse.Namespace):
@@ -596,6 +560,7 @@ def process_arguments(args: argparse.Namespace):
596
560
  args.skip_version_check,
597
561
  args.stdout_only,
598
562
  args.disassemble_all,
563
+ args.make_full_disasm_for_code,
599
564
  )
600
565
 
601
566
 
@@ -14,9 +14,6 @@ class CommonSegAsm(CommonSegCodeSubsegment):
14
14
  def get_section_flags(self) -> Optional[str]:
15
15
  return "ax"
16
16
 
17
- def out_path(self) -> Optional[Path]:
18
- return options.opts.asm_path / self.dir / f"{self.name}.s"
19
-
20
17
  def scan(self, rom_bytes: bytes):
21
18
  if (
22
19
  self.rom_start is not None
@@ -25,35 +22,8 @@ class CommonSegAsm(CommonSegCodeSubsegment):
25
22
  ):
26
23
  self.scan_code(rom_bytes)
27
24
 
28
- def get_file_header(self) -> List[str]:
29
- ret = []
30
-
31
- ret.append('.include "macro.inc"')
32
- ret.append("")
33
- ret.append(".set noat") # allow manual use of $at
34
- ret.append(".set noreorder") # don't insert nops after branches
35
- if options.opts.add_set_gp_64:
36
- ret.append(".set gp=64") # allow use of 64-bit general purpose registers
37
- ret.append("")
38
- preamble = options.opts.generated_s_preamble
39
- if preamble:
40
- ret.append(preamble)
41
- ret.append("")
42
-
43
- ret.append(self.get_section_asm_line())
44
- ret.append("")
45
-
46
- return ret
47
-
48
25
  def split(self, rom_bytes: bytes):
49
- if not self.rom_start == self.rom_end and self.spim_section is not None:
50
- out_path = self.out_path()
51
- if out_path:
52
- out_path.parent.mkdir(parents=True, exist_ok=True)
53
-
54
- self.print_file_boundaries()
26
+ if self.rom_start == self.rom_end:
27
+ return
55
28
 
56
- with open(out_path, "w", newline="\n") as f:
57
- for line in self.get_file_header():
58
- f.write(line + "\n")
59
- f.write(self.spim_section.disassemble())
29
+ self.split_as_asm_file(self.out_path())
@@ -24,7 +24,7 @@ class CommonSegAsmtu(CommonSegAsm):
24
24
 
25
25
  with open(out_path, "w", newline="\n") as f:
26
26
  # Write `.text` contents
27
- for line in self.get_file_header():
27
+ for line in self.get_asm_file_header():
28
28
  f.write(line + "\n")
29
29
  f.write(self.spim_section.disassemble())
30
30
 
@@ -40,7 +40,7 @@ class CommonSegAsmtu(CommonSegAsm):
40
40
  if (
41
41
  isinstance(sibling, CommonSegCodeSubsegment)
42
42
  and sibling.spim_section is not None
43
- and not sibling.should_split()
43
+ and not sibling.should_self_split()
44
44
  ):
45
45
  f.write("\n")
46
46
  f.write(f"{sibling.get_section_asm_line()}\n\n")
@@ -58,7 +58,7 @@ class CommonSegAsmtu(CommonSegAsm):
58
58
  if (
59
59
  isinstance(sibling, CommonSegCodeSubsegment)
60
60
  and sibling.spim_section is not None
61
- and not sibling.should_split()
61
+ and not sibling.should_self_split()
62
62
  ):
63
63
  f.write("\n")
64
64
  f.write(f"{sibling.get_section_asm_line()}\n\n")
@@ -60,7 +60,9 @@ class CommonSegBss(CommonSegData):
60
60
  f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'"
61
61
  )
62
62
 
63
- next_subsegment = self.parent.get_next_subsegment_for_ram(self.vram_start)
63
+ next_subsegment = self.parent.get_next_subsegment_for_ram(
64
+ self.vram_start, self.index_within_group
65
+ )
64
66
  if next_subsegment is None:
65
67
  bss_end = self.get_most_parent().vram_end
66
68
  else:
@@ -276,6 +276,14 @@ class CommonSegC(CommonSegCodeSubsegment):
276
276
  spim_rodata_sym, asm_out_dir, rodata_sym
277
277
  )
278
278
 
279
+ if options.opts.make_full_disasm_for_code:
280
+ # Disable gpRelHack since this file is expected to be built with modern gas
281
+ section = self.spim_section.get_section()
282
+ old_value = section.getGpRelHack()
283
+ section.setGpRelHack(False)
284
+ self.split_as_asm_file(self.asm_out_path())
285
+ section.setGpRelHack(old_value)
286
+
279
287
  def get_c_preamble(self):
280
288
  ret = []
281
289
 
@@ -380,13 +388,16 @@ class CommonSegC(CommonSegCodeSubsegment):
380
388
  # IDO uses the asm processor to embeed assembly, and it doesn't require a special directive to include symbols
381
389
  asm_outpath = asm_out_dir / self.name / f"{sym.filename}.s"
382
390
  rel_asm_outpath = os.path.relpath(asm_outpath, options.opts.base_path)
383
- return f'#pragma GLOBAL_ASM("{rel_asm_outpath}")'
391
+ final_path = Path(rel_asm_outpath).as_posix()
392
+ return f'#pragma GLOBAL_ASM("{final_path}")'
384
393
 
385
394
  if options.opts.use_legacy_include_asm:
386
395
  rel_asm_out_dir = asm_out_dir.relative_to(options.opts.nonmatchings_path)
387
- return f'{macro_name}(const s32, "{rel_asm_out_dir / self.name}", {sym.filename});'
396
+ final_path = (rel_asm_out_dir / self.name).as_posix()
397
+ return f'{macro_name}(const s32, "{final_path}", {sym.filename});'
388
398
 
389
- return f'{macro_name}("{asm_out_dir / self.name}", {sym.filename});'
399
+ final_path = (asm_out_dir / self.name).as_posix()
400
+ return f'{macro_name}("{final_path}", {sym.filename});'
390
401
 
391
402
  def get_c_lines_for_function(
392
403
  self,
@@ -448,7 +459,7 @@ class CommonSegC(CommonSegCodeSubsegment):
448
459
  c_lines += self.get_c_lines_for_rodata_sym(rodata_sym, asm_out_dir)
449
460
 
450
461
  c_path.parent.mkdir(parents=True, exist_ok=True)
451
- with c_path.open("w") as f:
462
+ with c_path.open("w", newline=options.opts.c_newline) as f:
452
463
  f.write("\n".join(c_lines))
453
464
  log.write(f"Wrote {self.name} to {c_path}")
454
465
 
@@ -472,12 +483,12 @@ class CommonSegC(CommonSegCodeSubsegment):
472
483
 
473
484
  dep_path = build_path / c_path.with_suffix(".asmproc.d")
474
485
  dep_path.parent.mkdir(parents=True, exist_ok=True)
475
- with dep_path.open("w") as f:
486
+ with dep_path.open("w", newline=options.opts.c_newline) as f:
476
487
  if options.opts.use_o_as_suffix:
477
488
  o_path = build_path / c_path.with_suffix(".o")
478
489
  else:
479
490
  o_path = build_path / c_path.with_suffix(c_path.suffix + ".o")
480
- f.write(f"{o_path}:")
491
+ f.write(f"{o_path.as_posix()}:")
481
492
  depend_list = []
482
493
  for entry in symbols_entries:
483
494
  if entry.function is not None:
@@ -488,7 +499,7 @@ class CommonSegC(CommonSegCodeSubsegment):
488
499
  outpath.parent.mkdir(parents=True, exist_ok=True)
489
500
 
490
501
  depend_list.append(outpath)
491
- f.write(f" \\\n {outpath}")
502
+ f.write(f" \\\n {outpath.as_posix()}")
492
503
  else:
493
504
  for rodata_sym in entry.rodataSyms:
494
505
  rodata_name = rodata_sym.getName()
@@ -498,9 +509,9 @@ class CommonSegC(CommonSegCodeSubsegment):
498
509
  outpath.parent.mkdir(parents=True, exist_ok=True)
499
510
 
500
511
  depend_list.append(outpath)
501
- f.write(f" \\\n {outpath}")
512
+ f.write(f" \\\n {outpath.as_posix()}")
502
513
 
503
514
  f.write("\n")
504
515
 
505
516
  for depend_file in depend_list:
506
- f.write(f"{depend_file}:\n")
517
+ f.write(f"{depend_file.as_posix()}:\n")
@@ -270,6 +270,8 @@ class CommonSegCode(CommonSegGroup):
270
270
  sibling.siblings[segment.get_linker_section_linksection()] = segment
271
271
 
272
272
  ret = self._insert_all_auto_sections(ret, base_segments, readonly_before)
273
+ for i, seg in enumerate(ret):
274
+ seg.index_within_group = i
273
275
 
274
276
  return ret
275
277
 
@@ -1,4 +1,5 @@
1
- from typing import Optional
1
+ from pathlib import Path
2
+ from typing import Optional, List
2
3
 
3
4
  import spimdisasm
4
5
  import rabbitizer
@@ -60,7 +61,7 @@ class CommonSegCodeSubsegment(Segment):
60
61
  section.isHandwritten = self.is_hasm
61
62
  section.instrCat = self.instr_category
62
63
  section.detectRedundantFunctionEnd = self.detect_redundant_function_end
63
- section.gpRelHack = not self.use_gp_rel_macro
64
+ section.setGpRelHack(not self.use_gp_rel_macro)
64
65
 
65
66
  def scan_code(self, rom_bytes, is_hasm=False):
66
67
  self.is_hasm = is_hasm
@@ -194,3 +195,55 @@ class CommonSegCodeSubsegment(Segment):
194
195
  return (
195
196
  self.extract and options.opts.is_mode_active("code") and self.should_scan()
196
197
  ) # only split if the segment was scanned first
198
+
199
+ def should_self_split(self) -> bool:
200
+ return self.should_split()
201
+
202
+ def get_asm_file_header(self) -> List[str]:
203
+ ret = []
204
+
205
+ ret.append('.include "macro.inc"')
206
+ ret.append("")
207
+
208
+ ret.extend(self.get_asm_file_extra_directives())
209
+
210
+ preamble = options.opts.generated_s_preamble
211
+ if preamble:
212
+ ret.append(preamble)
213
+ ret.append("")
214
+
215
+ ret.append(self.get_section_asm_line())
216
+ ret.append("")
217
+
218
+ return ret
219
+
220
+ def get_asm_file_extra_directives(self) -> List[str]:
221
+ ret = []
222
+
223
+ ret.append(".set noat") # allow manual use of $at
224
+ ret.append(".set noreorder") # don't insert nops after branches
225
+ if options.opts.add_set_gp_64:
226
+ ret.append(".set gp=64") # allow use of 64-bit general purpose registers
227
+ ret.append("")
228
+
229
+ return ret
230
+
231
+ def asm_out_path(self) -> Path:
232
+ return options.opts.asm_path / self.dir / f"{self.name}.s"
233
+
234
+ def out_path(self) -> Optional[Path]:
235
+ return self.asm_out_path()
236
+
237
+ def split_as_asm_file(self, out_path: Optional[Path]):
238
+ if self.spim_section is None:
239
+ return
240
+
241
+ if out_path:
242
+ out_path.parent.mkdir(parents=True, exist_ok=True)
243
+
244
+ self.print_file_boundaries()
245
+
246
+ with open(out_path, "w", newline="\n") as f:
247
+ for line in self.get_asm_file_header():
248
+ f.write(line + "\n")
249
+ f.write(self.spim_section.disassemble())
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Optional
2
+ from typing import Optional, List
3
3
  from ...util import options, symbols, log
4
4
 
5
5
  from .codesubsegment import CommonSegCodeSubsegment
@@ -38,33 +38,21 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
38
38
  if self.rom_start is not None and self.rom_end is not None:
39
39
  self.disassemble_data(rom_bytes)
40
40
 
41
+ def get_asm_file_extra_directives(self) -> List[str]:
42
+ return []
43
+
41
44
  def split(self, rom_bytes: bytes):
42
45
  super().split(rom_bytes)
43
46
 
44
- if self.type.startswith(".") and not options.opts.disassemble_all:
45
- return
46
-
47
47
  if self.spim_section is None or not self.should_self_split():
48
48
  return
49
49
 
50
- path = self.asm_out_path()
51
-
52
- path.parent.mkdir(parents=True, exist_ok=True)
53
-
54
- self.print_file_boundaries()
55
-
56
- with path.open("w", newline="\n") as f:
57
- f.write('.include "macro.inc"\n\n')
58
- preamble = options.opts.generated_s_preamble
59
- if preamble:
60
- f.write(preamble + "\n")
61
-
62
- f.write(f"{self.get_section_asm_line()}\n\n")
63
-
64
- f.write(self.spim_section.disassemble())
50
+ self.split_as_asm_file(self.asm_out_path())
65
51
 
66
52
  def should_self_split(self) -> bool:
67
- return options.opts.is_mode_active("data")
53
+ return options.opts.is_mode_active("data") and (
54
+ not self.type.startswith(".") or options.opts.disassemble_all
55
+ )
68
56
 
69
57
  def should_scan(self) -> bool:
70
58
  return True
@@ -110,11 +110,16 @@ class CommonSegGroup(CommonSegment):
110
110
  self.special_vram_segment = True
111
111
  segment.is_auto_segment = is_auto_segment
112
112
 
113
+ segment.index_within_group = len(ret)
114
+
113
115
  ret.append(segment)
114
116
  prev_start = start
115
117
  if end is not None:
116
118
  last_rom_end = end
117
119
 
120
+ for i, seg in enumerate(ret):
121
+ seg.index_within_group = i
122
+
118
123
  return ret
119
124
 
120
125
  @property
@@ -165,13 +170,17 @@ class CommonSegGroup(CommonSegment):
165
170
  return sub
166
171
  return None
167
172
 
168
- def get_next_subsegment_for_ram(self, addr: int) -> Optional[Segment]:
173
+ def get_next_subsegment_for_ram(
174
+ self, addr: int, current_subseg_index: Optional[int]
175
+ ) -> Optional[Segment]:
169
176
  """
170
177
  Returns the first subsegment which comes after the specified address,
171
178
  or None in case this address belongs to the last subsegment of this group
172
179
  """
173
180
 
174
- for sub in self.subsegments:
181
+ start = current_subseg_index if current_subseg_index is not None else 0
182
+
183
+ for sub in self.subsegments[start:]:
175
184
  if sub.vram_start is None:
176
185
  continue
177
186
  assert isinstance(sub.vram_start, int)
@@ -1,5 +1,4 @@
1
1
  from pathlib import Path
2
- from typing import Optional
3
2
 
4
3
  from .asm import CommonSegAsm
5
4
 
@@ -7,11 +6,11 @@ from ...util import options
7
6
 
8
7
 
9
8
  class CommonSegHasm(CommonSegAsm):
10
- def out_path(self) -> Optional[Path]:
9
+ def asm_out_path(self) -> Path:
11
10
  if options.opts.hasm_in_src_path:
12
11
  return options.opts.src_path / self.dir / f"{self.name}.s"
13
12
 
14
- return super().out_path()
13
+ return super().asm_out_path()
15
14
 
16
15
  def scan(self, rom_bytes: bytes):
17
16
  if (
@@ -22,14 +21,7 @@ class CommonSegHasm(CommonSegAsm):
22
21
  self.scan_code(rom_bytes, is_hasm=True)
23
22
 
24
23
  def split(self, rom_bytes: bytes):
25
- if not self.rom_start == self.rom_end and self.spim_section is not None:
26
- out_path = self.out_path()
27
- if out_path and not out_path.exists():
28
- out_path.parent.mkdir(parents=True, exist_ok=True)
24
+ if self.rom_start == self.rom_end:
25
+ return
29
26
 
30
- self.print_file_boundaries()
31
-
32
- with open(out_path, "w", newline="\n") as f:
33
- for line in self.get_file_header():
34
- f.write(line + "\n")
35
- f.write(self.spim_section.disassemble())
27
+ self.split_as_asm_file(self.out_path())
@@ -83,9 +83,13 @@ class CommonSegTextbin(CommonSegment):
83
83
 
84
84
  if sym is not None:
85
85
  f.write(f"{asm_label} {sym.name}\n")
86
+ if asm_label == ".globl":
87
+ if self.is_text():
88
+ f.write(f".ent {sym.name}\n")
89
+ f.write(f"{sym.name}:\n")
86
90
  sym.defined = True
87
91
 
88
- f.write(f'.incbin "{binpath}"\n')
92
+ f.write(f'.incbin "{binpath.as_posix()}"\n')
89
93
 
90
94
  if sym is not None:
91
95
  if self.is_text() and options.opts.asm_end_label != "":
@@ -97,6 +101,8 @@ class CommonSegTextbin(CommonSegment):
97
101
  or sym.given_size == self.rom_end - self.rom_start
98
102
  ):
99
103
  f.write(f"{asm_label} {sym.given_name_end}\n")
104
+ if asm_label == ".globl":
105
+ f.write(f"{sym.given_name_end}:\n")
100
106
 
101
107
  def split(self, rom_bytes):
102
108
  if self.rom_end is None:
@@ -51,7 +51,7 @@ def write_file_if_different(path: Path, new_content: str):
51
51
 
52
52
  if old_content != new_content:
53
53
  path.parent.mkdir(parents=True, exist_ok=True)
54
- with path.open("w") as f:
54
+ with path.open("w", newline=options.opts.c_newline) as f:
55
55
  f.write(new_content)
56
56
 
57
57
 
@@ -522,18 +522,18 @@ 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}:"
525
+ output = f"{target_elf_path.as_posix()}:"
526
526
 
527
527
  for entry in self.dependencies_entries:
528
528
  if entry.object_path is None:
529
529
  continue
530
- output += f" \\\n {entry.object_path}"
530
+ output += f" \\\n {entry.object_path.as_posix()}"
531
531
 
532
532
  output += "\n"
533
533
  for entry in self.dependencies_entries:
534
534
  if entry.object_path is None:
535
535
  continue
536
- output += f"{entry.object_path}:\n"
536
+ output += f"{entry.object_path.as_posix()}:\n"
537
537
  write_file_if_different(output_path, output)
538
538
 
539
539
  def _writeln(self, line: str):
@@ -561,7 +561,7 @@ class LinkerWriter:
561
561
  self.header_symbols.add(symbol)
562
562
 
563
563
  def _write_object_path_section(self, object_path: Path, section: str):
564
- self._writeln(f"{object_path}({section});")
564
+ self._writeln(f"{object_path.as_posix()}({section});")
565
565
 
566
566
  def _begin_segment(
567
567
  self, segment: Segment, seg_name: str, noload: bool, is_first: bool
splat/segtypes/segment.py CHANGED
@@ -255,7 +255,7 @@ class Segment:
255
255
 
256
256
  @staticmethod
257
257
  def parse_suggestion_rodata_section_start(
258
- yaml: Union[dict, list]
258
+ yaml: Union[dict, list],
259
259
  ) -> Optional[bool]:
260
260
  if isinstance(yaml, dict):
261
261
  suggestion_rodata_section_start = yaml.get(
@@ -354,6 +354,8 @@ class Segment:
354
354
  # Is an automatic segment, generated automatically or declared on the yaml by the user
355
355
  self.is_auto_segment: bool = False
356
356
 
357
+ self.index_within_group: Optional[int] = None
358
+
357
359
  if self.rom_start is not None and self.rom_end is not None:
358
360
  if self.rom_start > self.rom_end:
359
361
  log.error(
splat/util/compiler.py CHANGED
@@ -26,6 +26,7 @@ GCC = Compiler(
26
26
  SN64 = Compiler(
27
27
  "SN64",
28
28
  asm_function_macro=".globl",
29
+ asm_function_alt_macro=".globl",
29
30
  asm_jtbl_label_macro=".globl",
30
31
  asm_data_macro=".globl",
31
32
  asm_end_label=".end",
splat/util/conf.py ADDED
@@ -0,0 +1,96 @@
1
+ """
2
+ This module is used to load splat configuration from a YAML file.
3
+
4
+ A config dict can be loaded using `load`.
5
+
6
+ config = conf.load("path/to/splat.yaml")
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional
10
+ from pathlib import Path
11
+
12
+ # This unused import makes the yaml library faster. don't remove
13
+ import pylibyaml # pyright: ignore
14
+ import yaml
15
+
16
+ from . import log, options, vram_classes
17
+
18
+
19
+ def _merge_configs(main_config, additional_config, additional_config_path):
20
+ # Merge rules are simple
21
+ # For each key in the dictionary
22
+ # - If list then append to list
23
+ # - If a dictionary then repeat merge on sub dictionary entries
24
+ # - Else assume string or number and replace entry
25
+
26
+ for curkey in additional_config:
27
+ if curkey not in main_config:
28
+ main_config[curkey] = additional_config[curkey]
29
+ elif type(main_config[curkey]) != type(additional_config[curkey]):
30
+ raise TypeError(
31
+ f"Could not merge {str(additional_config_path)}: type for key '{curkey}' in configs does not match"
32
+ )
33
+ else:
34
+ # keys exist and match, see if a list to append
35
+ if type(main_config[curkey]) == list:
36
+ main_config[curkey] += additional_config[curkey]
37
+ elif type(main_config[curkey]) == dict:
38
+ # need to merge sub areas
39
+ main_config[curkey] = _merge_configs(
40
+ main_config[curkey],
41
+ additional_config[curkey],
42
+ additional_config_path,
43
+ )
44
+ else:
45
+ # not a list or dictionary, must be a number or string, overwrite
46
+ main_config[curkey] = additional_config[curkey]
47
+
48
+ return main_config
49
+
50
+
51
+ def load(
52
+ config_path: List[Path],
53
+ modes: Optional[List[str]] = None,
54
+ verbose: bool = False,
55
+ disassemble_all: bool = False,
56
+ make_full_disasm_for_code=False,
57
+ ) -> Dict[str, Any]:
58
+ """
59
+ Returns a `dict` with resolved splat config.
60
+
61
+ Multiple configuration files can be passed in ``config_path`` with each subsequent file merged into the previous.
62
+
63
+ `modes` specifies which modes are active (all, code, img, gfx, vtx, etc.). The default is all.
64
+
65
+ `verbose` may be used to determine whether or not to display additional output.
66
+
67
+ `disassemble_all` determines whether functions which are already compiled will be disassembled. This is OR-ed with
68
+ the `disassemble_all` key in a config file, if present.
69
+
70
+ After all files are merged, static validation is done on the configuration.
71
+
72
+ The returned `dict` represents the merged and validated YAML config.
73
+
74
+ As a side effect, the global `splat.util.options.opts` is set.
75
+
76
+ Config with invalid options may raise an error.
77
+ """
78
+
79
+ config: Dict[str, Any] = {}
80
+ for entry in config_path:
81
+ with entry.open() as f:
82
+ additional_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
83
+ config = _merge_configs(config, additional_config, entry)
84
+
85
+ vram_classes.initialize(config.get("vram_classes"))
86
+
87
+ options.initialize(
88
+ config,
89
+ config_path,
90
+ modes,
91
+ verbose,
92
+ disassemble_all,
93
+ make_full_disasm_for_code,
94
+ )
95
+
96
+ return config
splat/util/options.py CHANGED
@@ -220,6 +220,8 @@ class SplatOpts:
220
220
  detect_redundant_function_end: bool
221
221
  # Don't skip disassembling already matched functions and migrated sections
222
222
  disassemble_all: bool
223
+ # Emit a full `.s` file for each `c`/`cpp` segment besides the generated `nonmatchings` individual functions
224
+ make_full_disasm_for_code: bool
223
225
  # Allow specifying that the global memory range may be larger than what was automatically detected.
224
226
  # 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.
225
227
  global_vram_start: Optional[int]
@@ -344,10 +346,11 @@ class OptParser:
344
346
 
345
347
  def _parse_yaml(
346
348
  yaml: Dict,
347
- config_paths: List[str],
349
+ config_paths: List[Path],
348
350
  modes: List[str],
349
351
  verbose: bool = False,
350
352
  disasm_all: bool = False,
353
+ make_full_disasm_for_code: bool = False,
351
354
  ) -> SplatOpts:
352
355
  p = OptParser(yaml)
353
356
 
@@ -356,7 +359,7 @@ def _parse_yaml(
356
359
  comp = compiler.for_name(p.parse_opt("compiler", str, "IDO"))
357
360
 
358
361
  base_path = Path(
359
- os.path.normpath(Path(config_paths[0]).parent / p.parse_opt("base_path", str))
362
+ os.path.normpath(config_paths[0].parent / p.parse_opt("base_path", str))
360
363
  )
361
364
  asm_path: Path = p.parse_path(base_path, "asm_path", "asm")
362
365
 
@@ -554,10 +557,12 @@ def _parse_yaml(
554
557
  detect_redundant_function_end=p.parse_opt(
555
558
  "detect_redundant_function_end", bool, True
556
559
  ),
557
- # Command line argument takes precedence over yaml option
558
- disassemble_all=(
559
- disasm_all if disasm_all else p.parse_opt("disassemble_all", bool, False)
560
- ),
560
+ # Setting either option will produce a full disassembly,
561
+ # but we still have to check the yaml option first to avoid leaving option unparsed,
562
+ # because splat would complain about an unrecognized yaml option otherwise.
563
+ disassemble_all=p.parse_opt("disassemble_all", bool, False) or disasm_all,
564
+ make_full_disasm_for_code=p.parse_opt("make_full_disasm_for_code", bool, False)
565
+ or make_full_disasm_for_code,
561
566
  global_vram_start=p.parse_optional_opt("global_vram_start", int),
562
567
  global_vram_end=p.parse_optional_opt("global_vram_end", int),
563
568
  use_gp_rel_macro_nonmatching=p.parse_opt(
@@ -574,14 +579,22 @@ def _parse_yaml(
574
579
 
575
580
  def initialize(
576
581
  config: Dict,
577
- config_paths: List[str],
582
+ config_paths: List[Path],
578
583
  modes: Optional[List[str]] = None,
579
584
  verbose=False,
580
585
  disasm_all=False,
586
+ make_full_disasm_for_code=False,
581
587
  ):
582
588
  global opts
583
589
 
584
590
  if not modes:
585
591
  modes = ["all"]
586
592
 
587
- opts = _parse_yaml(config["options"], config_paths, modes, verbose, disasm_all)
593
+ opts = _parse_yaml(
594
+ config["options"],
595
+ config_paths,
596
+ modes,
597
+ verbose,
598
+ disasm_all,
599
+ make_full_disasm_for_code,
600
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splat64
3
- Version: 0.32.2
3
+ Version: 0.33.0
4
4
  Summary: A binary splitting tool to assist with decompilation and modding projects
5
5
  Project-URL: Repository, https://github.com/ethteck/splat
6
6
  Project-URL: Issues, https://github.com/ethteck/splat/issues
@@ -76,7 +76,7 @@ The brackets corresponds to the optional dependencies to install while installin
76
76
  If you use a `requirements.txt` file in your repository, then you can add this library with the following line:
77
77
 
78
78
  ```txt
79
- splat64[mips]>=0.32.2,<1.0.0
79
+ splat64[mips]>=0.33.0,<1.0.0
80
80
  ```
81
81
 
82
82
  ### Optional dependencies
@@ -1,4 +1,4 @@
1
- splat/__init__.py,sha256=a-EuQ-1jQ9RLrnfrNmIp7R4dCc2ShUTZv0j4car-jHI,291
1
+ splat/__init__.py,sha256=BC3Ztd-mFegTEbtIJhxdClCWjIA8qw5qhS8jZNWQ-ec,291
2
2
  splat/__main__.py,sha256=T333dHDgr-2HYYhtARnYMEjdECnYiNIKfcXDD99o22A,732
3
3
  splat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  splat/disassembler/__init__.py,sha256=IubLMnm_F5cZ7WUPBfk1VJ7vdj6i1if5GG6RBvEoBEA,226
@@ -6,7 +6,7 @@ splat/disassembler/disassembler.py,sha256=2ynehZRz1P6UnaBk6DWRy4c3ynRnnWApMhf0K9
6
6
  splat/disassembler/disassembler_instance.py,sha256=09GW7QYoNolgE2wnO7ALngw_CxgF-mfyLiXBsyv22jA,916
7
7
  splat/disassembler/disassembler_section.py,sha256=J9jtplQVDVeGBmyEOcMpC3Hv3DyecqaNjlYc7zqEqDI,7459
8
8
  splat/disassembler/null_disassembler.py,sha256=jYuDMtfPiBifwz0H-ZHLMWtpGa19X_iLgy4K-dQhPYY,328
9
- splat/disassembler/spimdisasm_disassembler.py,sha256=Z3eLbmO1Q7uY9zdbFzGJH1zE1LgHSLRQWRwyYpNFuzQ,5685
9
+ splat/disassembler/spimdisasm_disassembler.py,sha256=Lj6zp2g7Jo4sRlZ7CavLb7qM4alzEG1n7AhFYLiskqU,5685
10
10
  splat/platforms/__init__.py,sha256=qjqKi63k5N3DUdILNfuk6qpJJkVeAWpjAs36L97vvP4,100
11
11
  splat/platforms/n64.py,sha256=kgWr6nesGC0X-qVydmu8tSq48NbqVf9mF6EyqvUuoUM,421
12
12
  splat/platforms/ps2.py,sha256=QI_8Ve43LZeNqpuk8_CFxIqsNJpMTacRHXdZVh3iY6c,336
@@ -15,25 +15,25 @@ splat/platforms/psx.py,sha256=YxQERdOBr4p3ab9Wk80FNhVYi-uvmh7p_jeykSFp23M,40
15
15
  splat/scripts/__init__.py,sha256=OY0nHg6a7JB437Sb96qLbZ_7ByVsal1gStj35wJAI_Y,101
16
16
  splat/scripts/capy.py,sha256=svbOfLO34-QN3xLiBy9vk2RGs_To8TWMWEKBw6yx2xQ,3674
17
17
  splat/scripts/create_config.py,sha256=nFwIt1UWzlWE9C3i-2dGsVyGX2cuXzIqkp2kur2-pdE,7670
18
- splat/scripts/split.py,sha256=INZ8gwyv9ulSfrNYJuPykkg5BWWsYLnto6JVXXIiHG0,20704
18
+ splat/scripts/split.py,sha256=EJLpvwGurQZEe-FE3hfNcMFIbxLToB4SX95OXdL_qoE,19285
19
19
  splat/segtypes/__init__.py,sha256=-upUw_4JGQtvyp6IfTMzOq_CK3xvVaT_0K0_EipHyOo,208
20
- splat/segtypes/linker_entry.py,sha256=m2qrZEZaRqsurUVlgo5rQt-4OZwDywK8hTqY1SoF2D8,24689
21
- splat/segtypes/segment.py,sha256=Ey4d4hxGWL4e8ZsxVRGXafP9GCFXB-3F6loaBFVDU-k,28764
20
+ splat/segtypes/linker_entry.py,sha256=e2IzjAWC1B_JCx5pxBdKJrzOCse4SYUBrLHM8l3AR3o,24765
21
+ splat/segtypes/segment.py,sha256=Y8OaNle09VeQ8pghzMQtxu8I2sWKy8fWPdfRH1zkUXU,28820
22
22
  splat/segtypes/common/__init__.py,sha256=mnq0acScilSCCo6q2PvkDk0Or3V8qitA7I8QMVw8haI,631
23
- splat/segtypes/common/asm.py,sha256=_EfIbDOlQdmvAVqPq_Jxgp335zIxEPLnuQc96x9EyOY,1819
24
- splat/segtypes/common/asmtu.py,sha256=hrflvDGZ4BcVEA8CRkkIOlqt0tUCC4iNb4oclht2o6s,2274
23
+ splat/segtypes/common/asm.py,sha256=k3p4vgbQJP40iyTgQkIci1j3CpKkWksqoWBx2Pb2oh8,703
24
+ splat/segtypes/common/asmtu.py,sha256=5FOVAXjqhM-kynm-nOuS1k7E9048WAW2eNHdKYtR5m8,2288
25
25
  splat/segtypes/common/bin.py,sha256=6rxYRXTdAVEs3AFMgmhelOwOmKMg3bbkl-j4k4wqql4,1229
26
- splat/segtypes/common/bss.py,sha256=EIN8E7CfqwcWRMWb_JIbGBicu6VKREdIbvQZNBM-6sM,3037
27
- splat/segtypes/common/c.py,sha256=NWLc1ArOQ3t2xfIJj8I2AGzm8xNR9DPbb5Wgg9L1VNY,19755
28
- splat/segtypes/common/code.py,sha256=23VZJXNFi3PYq3xH269DlxjCiqasSQnMuxwQkh-1qXU,10629
29
- splat/segtypes/common/codesubsegment.py,sha256=fRDFUu9RzBSMeHo_vsT0Ko1qoeLDq8kLaIX4asJGMa4,7015
26
+ splat/segtypes/common/bss.py,sha256=nktRbKrDivweyLdsUQkL5guSayqpwn7NK5j3gq3arKc,3084
27
+ splat/segtypes/common/c.py,sha256=FBcEQ6GB5-F5wZQCahh2FjkqLkWFHBTehql8SuLVQyY,20419
28
+ splat/segtypes/common/code.py,sha256=CFDbn2YCoRIpUT07vTW0rUQtG58X0sxTtp17VPbrZsc,10706
29
+ splat/segtypes/common/codesubsegment.py,sha256=_fpQF7O5pq4CBclhqcR4_KbiY8Oo2p7gaMEnkICBfQ4,8558
30
30
  splat/segtypes/common/cpp.py,sha256=p-duowCt4czrmaWgj1LuFw5LGfdyB_uaeLTKVmH80Cg,180
31
- splat/segtypes/common/data.py,sha256=ZR6ZVgAOWuKJOMpKAe4nK2ORH9oAjQ8uSszcTJaZ1j8,5997
31
+ splat/segtypes/common/data.py,sha256=r9G_vYieNlRze-5wGOKFazvFjpuodfSLad6iCNsBOAU,5665
32
32
  splat/segtypes/common/databin.py,sha256=ucbbsc_M1332KifjExuDsf1wqm4-GZAiLqOwwrUwUpQ,1194
33
33
  splat/segtypes/common/eh_frame.py,sha256=MzL373ej2E38leM47py3T5beLq2IolsYszLZM8PYS2Y,913
34
34
  splat/segtypes/common/gcc_except_table.py,sha256=-ZrQL5dCcRu7EPFFPgL8lIJwWL5FsEeeQoSUVfTkyIg,2112
35
- splat/segtypes/common/group.py,sha256=DIDLeiOlpguIPatw8a9vHmvsQ2LXI-QWbFy_D7PhVgQ,5928
36
- splat/segtypes/common/hasm.py,sha256=HW6OADo-2D3Jln0q5KAoE8zUGOUiJAUEHqHOG4dRmw8,1130
35
+ splat/segtypes/common/group.py,sha256=4g8jrpUjlsvxFuFRauS4rdxTvAAePebc941fQj96qEA,6197
36
+ splat/segtypes/common/hasm.py,sha256=0sU00X0Ax-DK2y8-lKt1ZowjaVKX-5bdiKnjnX_cKGA,703
37
37
  splat/segtypes/common/header.py,sha256=NspQdk-L69j9dOBzBtDKb7bc11Gwa0WDWIVCd2P4mfE,1257
38
38
  splat/segtypes/common/lib.py,sha256=WaJH0DH0B8iJvhYQWYBjThbNeEXz3Yh7E8ZyONe5JtQ,2349
39
39
  splat/segtypes/common/linker_offset.py,sha256=IaEXMXq62BS2g95I4lS0wax5Ehjfi3O3C-uSDUbMjvI,767
@@ -44,7 +44,7 @@ splat/segtypes/common/rodatabin.py,sha256=uqp60QuwHwvlMwHq0nZrU3y3yYcHbv2twT0PID
44
44
  splat/segtypes/common/sbss.py,sha256=blIEuVYie4qNEOYMmlSdltn5f4MvgJu3AV8vqVD8Nh4,131
45
45
  splat/segtypes/common/sdata.py,sha256=dnLzNSNtSyclLZiNUmFTv0e0BWN8glxB1km1MSRq6xY,136
46
46
  splat/segtypes/common/segment.py,sha256=vVFyFjs-gS5qIWO8Pd0pMJYxboP-iBRKvxcDV8YxdYM,94
47
- splat/segtypes/common/textbin.py,sha256=6WXGaAPJhqFYamqoUPeSr2nq2M6Go3BxG1_s7UozEUA,4296
47
+ splat/segtypes/common/textbin.py,sha256=Xj-PxEDTN8f0DLgdZ2uA26SYCcXD3rqqr2r6-pEiA4Y,4578
48
48
  splat/segtypes/n64/__init__.py,sha256=tf2yWlijeKmOp1OhEEL-aW88mU-mWQRC2lSjQ5Ww1eI,569
49
49
  splat/segtypes/n64/ci.py,sha256=An7wIHcoZ89KiGCKpCRbM7IrmDNYBidXT-6kjKn2vH0,2400
50
50
  splat/segtypes/n64/ci4.py,sha256=ZP8e12CpV8U7r8oLnb9uHc-MkmBbxjXBbJxROFvOjiM,184
@@ -78,9 +78,10 @@ splat/segtypes/psx/header.py,sha256=2S8XG_6zfLGzmEA0XpFqs0-4nf52YD9Erk6SbMUlVzo,
78
78
  splat/util/__init__.py,sha256=3-jgYUssL84IgaR_JF1hSb-lN4ru0gJQTrjP_UGEWu8,470
79
79
  splat/util/cache_handler.py,sha256=N0SggmvYwh0k-0fngHXoHG1CosC2rCsnlCTDsG8z5aE,1852
80
80
  splat/util/color.py,sha256=FSmy0dAQJ9FzRBc99Yt4kBEyB62MC_YiVkqoWgPMsRU,371
81
- splat/util/compiler.py,sha256=g9MjVe6E1kSGRxgDRgWGEd4qdDf2LzGLK1QRtp4znMc,1493
81
+ splat/util/compiler.py,sha256=xDDNdnutmkB7T21j-BccBMdkA2leU3GzXuTYEWgVgNw,1530
82
+ splat/util/conf.py,sha256=aM6O2QikosjL95pCxI2FcCxrwDsLP8T8sRf2Uev_Pac,3236
82
83
  splat/util/log.py,sha256=GguW9AolH-EXGBmh-8aZXgUeBFJthqAOb3qKtRYUSj8,999
83
- splat/util/options.py,sha256=XrOq83KPdNU-RjrIMx-G6jZ8-y0wtsnazuuqcSaSWeY,28061
84
+ splat/util/options.py,sha256=JHyZnBuB86tbZN_HvhAvOH8oZGsppIc9KmgRVRkBWvg,28632
84
85
  splat/util/palettes.py,sha256=d3KoZnwt-zunI9eNwb3txysXg4DY3xnF0O5aQRxM4so,2920
85
86
  splat/util/progress_bar.py,sha256=41VehpIFK1cphENaXV_Aq6_3mFx25eQ8V7Z51SKFPeM,166
86
87
  splat/util/relocs.py,sha256=cgQYSaAtNpLlUZQdhEfa7ZpI8i0HqoDhwB88QtFqdJs,4212
@@ -93,8 +94,8 @@ splat/util/n64/find_code_length.py,sha256=uUoPoUORAjsAvH8oGqwnGvw6j8I_NnSrZtA-x9
93
94
  splat/util/n64/rominfo.py,sha256=U6TieblUAmxhZsn7u8nbjOKkbC6ygsC_7IiLLaOWwUE,14646
94
95
  splat/util/psx/__init__.py,sha256=kCCaR-KB1mNlIcXB4OuuSQ2zVLbWg_SnIZIUeyjeBBI,39
95
96
  splat/util/psx/psxexeinfo.py,sha256=MrxY28nes0uzpFmCz0o9JFbF8s1eQRQNOpC_82wsMVI,5725
96
- splat64-0.32.2.dist-info/METADATA,sha256=yd7HFS__8mQ3lqO6kA14ZYdwImHBVTcQc0zdtOaGDYI,3830
97
- splat64-0.32.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
- splat64-0.32.2.dist-info/entry_points.txt,sha256=O7Xy-qNOHcI87-OQrWJ-OhRDws74SuwVb_4rtnp0eLo,52
99
- splat64-0.32.2.dist-info/licenses/LICENSE,sha256=97VMVzjG8yQvsf8NG2M9IFSbh7R8cifJnc6QK1cZqj8,1070
100
- splat64-0.32.2.dist-info/RECORD,,
97
+ splat64-0.33.0.dist-info/METADATA,sha256=5nOsSDJcmC7QQQzzzQGbGFjTCIT_xlq9q2AAgt04PhI,3830
98
+ splat64-0.33.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
99
+ splat64-0.33.0.dist-info/entry_points.txt,sha256=O7Xy-qNOHcI87-OQrWJ-OhRDws74SuwVb_4rtnp0eLo,52
100
+ splat64-0.33.0.dist-info/licenses/LICENSE,sha256=97VMVzjG8yQvsf8NG2M9IFSbh7R8cifJnc6QK1cZqj8,1070
101
+ splat64-0.33.0.dist-info/RECORD,,