splat64 0.36.3__py3-none-any.whl → 0.36.4__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.36.3"
4
+ __version__ = "0.36.4"
5
5
  __author__ = "ethteck"
6
6
 
7
7
  from . import util as util
@@ -1,15 +1,19 @@
1
1
  #! /usr/bin/env python3
2
2
 
3
3
  import argparse
4
- import sys
4
+ import hashlib
5
5
  from pathlib import Path
6
+ import subprocess
7
+ import sys
8
+ from typing import Optional
6
9
 
7
10
  from ..util.n64 import find_code_length, rominfo
8
11
  from ..util.psx import psxexeinfo
12
+ from ..util.ps2 import ps2elfinfo
9
13
  from ..util import log, file_presets, conf
10
14
 
11
15
 
12
- def main(file_path: Path):
16
+ def main(file_path: Path, objcopy: Optional[str]):
13
17
  if not file_path.exists():
14
18
  sys.exit(f"File {file_path} does not exist ({file_path.absolute()})")
15
19
  if file_path.is_dir():
@@ -27,6 +31,11 @@ def main(file_path: Path):
27
31
  create_psx_config(file_path, file_bytes)
28
32
  return
29
33
 
34
+ # Check for ELFs
35
+ if file_bytes[0:4] == b"\x7fELF":
36
+ do_elf(file_path, file_bytes, objcopy)
37
+ return
38
+
30
39
  log.error(f"create_config does not support the file format of '{file_path}'")
31
40
 
32
41
 
@@ -360,15 +369,208 @@ segments:
360
369
  file_presets.write_all_files()
361
370
 
362
371
 
372
+ def do_elf(elf_path: Path, elf_bytes: bytes, objcopy: Optional[str]):
373
+ elf = ps2elfinfo.Ps2Elf.get_info(elf_path, elf_bytes)
374
+ if elf is None:
375
+ log.error(f"Unsupported elf file '{elf_path}'")
376
+
377
+ basename = elf_path.name.replace(" ", "")
378
+ cleaned_basename = remove_invalid_path_characters(basename)
379
+
380
+ rom_name = Path(f"{cleaned_basename}.rom")
381
+ # Prefer the user objcopy
382
+ if objcopy is None:
383
+ objcopy = find_objcopy()
384
+ objcopy_cmd = run_objcopy(objcopy, str(elf_path), str(rom_name))
385
+
386
+ sha1 = hashlib.sha1(rom_name.read_bytes()).hexdigest()
387
+
388
+ header = f"""\
389
+ # name: Your game name here!
390
+ sha1: {sha1}
391
+ options:
392
+ basename: {basename}
393
+ target_path: {rom_name}
394
+ elf_path: build/{cleaned_basename}.elf
395
+ base_path: .
396
+ platform: ps2
397
+ compiler: {elf.compiler}
398
+ """
399
+
400
+ if elf.gp is not None:
401
+ header += f"""
402
+ gp_value: 0x{elf.gp:08X}
403
+ """
404
+ if elf.ld_gp_expression is not None:
405
+ header += f" ld_gp_expression: {elf.ld_gp_expression}\n"
406
+ else:
407
+ header += " # ld_gp_expression:\n"
408
+
409
+ header += f"""
410
+ # asm_path: asm
411
+ # src_path: src
412
+ # build_path: build
413
+
414
+ ld_script_path: {cleaned_basename}.ld
415
+ ld_dependencies: True
416
+ ld_wildcard_sections: True
417
+ ld_bss_contains_common: True
418
+
419
+ create_asm_dependencies: True
420
+
421
+ find_file_boundaries: False
422
+
423
+ o_as_suffix: True
424
+
425
+ symbol_addrs_path:
426
+ - symbol_addrs.txt
427
+ reloc_addrs_path:
428
+ - reloc_addrs.txt
429
+
430
+ # undefined_funcs_auto_path: undefined_funcs_auto.txt
431
+ # undefined_syms_auto_path: undefined_syms_auto.txt
432
+
433
+ extensions_path: tools/splat_ext
434
+
435
+ string_encoding: ASCII
436
+ data_string_encoding: ASCII
437
+ rodata_string_guesser_level: 2
438
+ data_string_guesser_level: 2
439
+
440
+ named_regs_for_c_funcs: False
441
+ """
442
+
443
+ header += "\n section_order:\n"
444
+ for sect, is_valid in elf.elf_section_names:
445
+ comment = "" if is_valid else "# "
446
+ header += f" {comment}- {sect}\n"
447
+
448
+ header += "\n auto_link_sections:\n"
449
+ for sect, is_valid in elf.elf_section_names:
450
+ comment = "" if is_valid else "# "
451
+ if sect != ".text" and sect != ".vutext":
452
+ header += f" {comment}- {sect}\n"
453
+
454
+ segments = "\nsegments:"
455
+ for seg in elf.segs:
456
+ segments += f"""
457
+ - name: {seg.name}
458
+ type: code
459
+ start: 0x{seg.start:06X}
460
+ vram: 0x{seg.vram:08X}
461
+ bss_size: 0x{seg.bss_size:X}
462
+ subalign: null
463
+ subsegments:
464
+ """
465
+ for section in seg.sections:
466
+ if section.is_nobits:
467
+ segments += f" - {{ type: {section.splat_segment_type}, vram: 0x{section.vram:08X}, name: {seg.name}/{section.vram:08X} }} # {section.name}\n"
468
+ else:
469
+ segments += f" - [0x{section.start:06X}, {section.splat_segment_type}, {seg.name}/{section.start:06X}] # {section.name}\n"
470
+
471
+ segments += f"""\
472
+ - [0x{elf.size:X}]
473
+ """
474
+
475
+ out_file = Path(f"{cleaned_basename}.yaml")
476
+ with out_file.open("w", newline="\n") as f:
477
+ print(f"Writing config to {out_file}")
478
+ f.write(header)
479
+ f.write(segments)
480
+
481
+ # `file_presets` requires an initialized `opts`.
482
+ # A simple way to do that is to simply load the yaml we just generated.
483
+ conf.load([out_file])
484
+ file_presets.write_all_files()
485
+
486
+ # Write symbol_addrs.txt file
487
+ symbol_addrs = []
488
+ symbol_addrs.append(f"_start = 0x{elf.entrypoint:08X}; // type:func")
489
+ if symbol_addrs:
490
+ symbol_addrs.append("")
491
+ with Path("symbol_addrs.txt").open("w", newline="\n") as f:
492
+ print("Writing symbol_addrs.txt")
493
+ f.write(
494
+ "// Visit https://github.com/ethteck/splat/wiki/Adding-Symbols for documentation about this file\n"
495
+ )
496
+ contents = "\n".join(symbol_addrs)
497
+ f.write(contents)
498
+
499
+ # Write other linker script
500
+ linker_script = []
501
+ linker_script.append("ENTRY(_start);")
502
+ if linker_script:
503
+ linker_script.append("")
504
+ with Path("linker_script_extra.ld").open("w", newline="\n") as f:
505
+ print("Writing linker_script_extra.ld")
506
+ f.write(
507
+ "/* Pass this file to the linker with the `-T linker_script_extra.ld` flag */\n"
508
+ )
509
+ contents = "\n".join(linker_script)
510
+ f.write(contents)
511
+
512
+ print()
513
+ print(
514
+ "The generated yaml does not use the actual ELF file as input, but instead it"
515
+ )
516
+ print(
517
+ 'uses a "rom" generated from said ELF, which contains the game code without any'
518
+ )
519
+ print("of the elf metadata.")
520
+ print(
521
+ 'Use the following command to generate this "rom". It is recommended to include'
522
+ )
523
+ print("this command into your setup/configure script.")
524
+ print("```")
525
+ print(" ".join(objcopy_cmd))
526
+ print("```")
527
+
528
+
529
+ def find_objcopy() -> str:
530
+ # First we try to figure out if the user has objcopy on their pc, and under
531
+ # which name.
532
+ # We just try a bunch and hope for the best
533
+ options = [
534
+ "mips-linux-gnu-objcopy",
535
+ "mipsel-linux-gnu-objcopy",
536
+ ]
537
+
538
+ for name in options:
539
+ sub = subprocess.run([name, "--version"], capture_output=True)
540
+ if sub.returncode == 0:
541
+ return name
542
+
543
+ msg = "Unable to find objcopy.\nI tried the following list of names:\n"
544
+ for name in options:
545
+ msg += f" - {name}\n"
546
+ msg += "\nTry to install one of those or use the `--objcopy` flag to pass the name to your own objcopy to me."
547
+ log.error(msg)
548
+
549
+
550
+ def run_objcopy(objcopy_name: str, elf_path: str, rom: str) -> list[str]:
551
+ cmd = [objcopy_name, "-O", "binary", "--gap-fill=0x00", elf_path, rom]
552
+ print("Running:", " ".join(cmd))
553
+ sub = subprocess.run(cmd)
554
+ if sub.returncode != 0:
555
+ log.error("Failed to run objcopy")
556
+ return cmd
557
+
558
+
363
559
  def add_arguments_to_parser(parser: argparse.ArgumentParser):
364
560
  parser.add_argument(
365
561
  "file",
366
- help="Path to a .z64/.n64 ROM or PSX executable",
562
+ help="Path to a .z64/.n64 ROM, PSX executable or PS2 ELF",
563
+ type=Path,
564
+ )
565
+ parser.add_argument(
566
+ "--objcopy",
567
+ help="Path to an user-provided objcopy program. Only used when processing ELF files",
568
+ type=str,
367
569
  )
368
570
 
369
571
 
370
572
  def process_arguments(args: argparse.Namespace):
371
- main(Path(args.file))
573
+ main(args.file, args.objcopy)
372
574
 
373
575
 
374
576
  script_description = "Create a splat config from an N64 ROM or PSX executable."
@@ -88,7 +88,7 @@ class CommonSegBss(CommonSegData):
88
88
 
89
89
  for spim_sym in self.spim_section.get_section().symbolList:
90
90
  symbols.create_symbol_from_spim_symbol(
91
- self.get_most_parent(), spim_sym.contextSym
91
+ self.get_most_parent(), spim_sym.contextSym, force_in_segment=True
92
92
  )
93
93
 
94
94
  def should_scan(self) -> bool:
@@ -283,7 +283,24 @@ class CommonSegC(CommonSegCodeSubsegment):
283
283
  section = self.spim_section.get_section()
284
284
  old_value = section.getGpRelHack()
285
285
  section.setGpRelHack(False)
286
+
287
+ if options.opts.platform == "ps2":
288
+ # Modern gas requires `$` on the special r5900 registers.
289
+ from rabbitizer import TrinaryValue
290
+
291
+ for func in section.symbolList:
292
+ assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction)
293
+ for inst in func.instructions:
294
+ inst.flag_r5900UseDollar = TrinaryValue.TRUE
295
+
286
296
  self.split_as_asmtu_file(self.asm_out_path())
297
+
298
+ if options.opts.platform == "ps2":
299
+ for func in section.symbolList:
300
+ assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction)
301
+ for inst in func.instructions:
302
+ inst.flag_r5900UseDollar = TrinaryValue.FALSE
303
+
287
304
  section.setGpRelHack(old_value)
288
305
 
289
306
  def get_c_preamble(self):
@@ -119,7 +119,7 @@ class CommonSegCodeSubsegment(Segment):
119
119
  self.parent: CommonSegCode = self.parent
120
120
 
121
121
  symbols.create_symbol_from_spim_symbol(
122
- self.get_most_parent(), func_spim.contextSym
122
+ self.get_most_parent(), func_spim.contextSym, force_in_segment=False
123
123
  )
124
124
 
125
125
  # Gather symbols found by spimdisasm and create those symbols in splat's side
@@ -129,7 +129,7 @@ class CommonSegCodeSubsegment(Segment):
129
129
  )
130
130
  if context_sym is not None:
131
131
  symbols.create_symbol_from_spim_symbol(
132
- self.get_most_parent(), context_sym
132
+ self.get_most_parent(), context_sym, force_in_segment=False
133
133
  )
134
134
 
135
135
  # Main loop
@@ -153,7 +153,7 @@ class CommonSegCodeSubsegment(Segment):
153
153
  context_sym = self.spim_section.get_section().getSymbol(sym_address)
154
154
  if context_sym is not None:
155
155
  symbols.create_symbol_from_spim_symbol(
156
- self.get_most_parent(), context_sym
156
+ self.get_most_parent(), context_sym, force_in_segment=False
157
157
  )
158
158
 
159
159
  def print_file_boundaries(self):
@@ -130,7 +130,7 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
130
130
 
131
131
  for symbol in self.spim_section.get_section().symbolList:
132
132
  symbols.create_symbol_from_spim_symbol(
133
- self.get_most_parent(), symbol.contextSym
133
+ self.get_most_parent(), symbol.contextSym, force_in_segment=True
134
134
  )
135
135
 
136
136
  # Gather symbols found by spimdisasm and create those symbols in splat's side
@@ -140,7 +140,7 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
140
140
  )
141
141
  if context_sym is not None:
142
142
  symbols.create_symbol_from_spim_symbol(
143
- self.get_most_parent(), context_sym
143
+ self.get_most_parent(), context_sym, force_in_segment=False
144
144
  )
145
145
 
146
146
  # Hint to the user that we are now in the .rodata section and no longer in the .data section (assuming rodata follows data)
@@ -108,7 +108,7 @@ class CommonSegRodata(CommonSegData):
108
108
 
109
109
  for symbol in self.spim_section.get_section().symbolList:
110
110
  generated_symbol = symbols.create_symbol_from_spim_symbol(
111
- self.get_most_parent(), symbol.contextSym
111
+ self.get_most_parent(), symbol.contextSym, force_in_segment=True
112
112
  )
113
113
  generated_symbol.linker_section = self.get_linker_section_linksection()
114
114
 
@@ -119,7 +119,7 @@ class CommonSegRodata(CommonSegData):
119
119
  )
120
120
  if context_sym is not None:
121
121
  symbols.create_symbol_from_spim_symbol(
122
- self.get_most_parent(), context_sym
122
+ self.get_most_parent(), context_sym, force_in_segment=False
123
123
  )
124
124
 
125
125
  possible_text = self.get_possible_text_subsegment_for_symbol(symbol)
splat/segtypes/n64/ci.py CHANGED
@@ -48,6 +48,8 @@ class N64SegCi(N64SegImg):
48
48
  return options.opts.asset_path / self.dir / f"{out_name}{type_extension}.png"
49
49
 
50
50
  def split(self, rom_bytes):
51
+ self.check_len()
52
+
51
53
  assert self.palettes is not None
52
54
  if len(self.palettes) == 0:
53
55
  # TODO: output with blank palette
splat/segtypes/n64/img.py CHANGED
@@ -64,6 +64,10 @@ class N64SegImg(Segment):
64
64
  log.error(
65
65
  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)"
66
66
  )
67
+ elif actual_len < expected_len:
68
+ log.error(
69
+ f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}"
70
+ )
67
71
 
68
72
  def out_path(self) -> Path:
69
73
  type_extension = f".{self.type}" if self.image_type_in_extension else ""
@@ -23,7 +23,21 @@ class N64SegPalette(Segment):
23
23
  f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it"
24
24
  )
25
25
 
26
- if not isinstance(self.yaml, dict) or "size" not in self.yaml:
26
+ if isinstance(self.yaml, dict) and "size" in self.yaml:
27
+ yaml_size: int = self.yaml["size"]
28
+ if isinstance(self.rom_start, int) and isinstance(self.rom_end, int):
29
+ rom_len = self.rom_end - self.rom_start
30
+ if rom_len < yaml_size:
31
+ log.error(
32
+ 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})"
33
+ )
34
+ elif rom_len > yaml_size:
35
+ log.error(
36
+ f"Warning: {self.name} has a `size` value of 0x{yaml_size:X}, but this is larger than the end of the palette (0x{rom_len:X}, start: 0x{self.rom_start:X}, end: 0x{self.rom_end:X})\n(hint add a 'bin' segment after this palette with address 0x{self.rom_end:X})",
37
+ )
38
+
39
+ size = yaml_size
40
+ else:
27
41
  assert self.rom_end is not None
28
42
  assert self.rom_start is not None
29
43
  actual_len = self.rom_end - self.rom_start
@@ -39,7 +53,11 @@ class N64SegPalette(Segment):
39
53
  log.error(
40
54
  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}"
41
55
  )
56
+ size = actual_len
57
+ else:
58
+ size = 0
42
59
 
60
+ self.palette_size: int = size
43
61
  self.global_id: Optional[str] = (
44
62
  self.yaml.get("global_id") if isinstance(self.yaml, dict) else None
45
63
  )
@@ -61,7 +79,8 @@ class N64SegPalette(Segment):
61
79
  return palette
62
80
 
63
81
  def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]:
64
- data = rom_bytes[self.rom_start : self.rom_end]
82
+ assert self.rom_start is not None
83
+ data = rom_bytes[self.rom_start : self.rom_start + self.palette_size]
65
84
 
66
85
  return N64SegPalette.parse_palette_bytes(data)
67
86
 
splat/segtypes/segment.py CHANGED
@@ -811,8 +811,7 @@ class Segment:
811
811
  items = [
812
812
  i
813
813
  for i in items
814
- if i.segment is None
815
- or Segment.visible_ram(self, i.segment)
814
+ if (i.segment is None or Segment.visible_ram(self, i.segment))
816
815
  and (type == i.type)
817
816
  ]
818
817
 
splat/util/__init__.py CHANGED
@@ -7,6 +7,7 @@ from . import n64 as n64
7
7
  from . import options as options
8
8
  from . import palettes as palettes
9
9
  from . import progress_bar as progress_bar
10
+ from . import ps2 as ps2
10
11
  from . import psx as psx
11
12
  from . import relocs as relocs
12
13
  from . import statistics as statistics
@@ -0,0 +1 @@
1
+ from . import ps2elfinfo as ps2elfinfo
@@ -0,0 +1,249 @@
1
+ #! /usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+ from pathlib import Path
7
+ import spimdisasm
8
+ from spimdisasm.elf32 import (
9
+ Elf32File,
10
+ Elf32Constants,
11
+ Elf32SectionHeaderFlag,
12
+ Elf32ObjectFileType,
13
+ )
14
+ from typing import Optional
15
+
16
+ from .. import log
17
+
18
+
19
+ ELF_SECTION_MAPPING: dict[str, str] = {
20
+ ".text": "asm",
21
+ ".data": "data",
22
+ ".rodata": "rodata",
23
+ ".bss": "bss",
24
+ ".sdata": "sdata",
25
+ ".sbss": "sbss",
26
+ ".gcc_except_table": "gcc_except_table",
27
+ ".lit4": "lit4",
28
+ ".lit8": "lit8",
29
+ ".ctor": "ctor",
30
+ ".vtables": "vtables",
31
+ ".vutext": "textbin", # No "proper" support yet
32
+ ".vudata": "databin", # No "proper" support yet
33
+ }
34
+
35
+ # Section to not put into the elf_section_names list, because splat doesn't
36
+ # know have support for them yet.
37
+ ELF_SECTIONS_IGNORE: set[str] = {
38
+ ".vutext",
39
+ ".vudata",
40
+ ".vubss",
41
+ }
42
+
43
+ ELF_SMALL_SECTIONS: set[str] = {
44
+ ".lit4",
45
+ ".lit8",
46
+ ".sdata",
47
+ ".srdata",
48
+ ".sbss",
49
+ }
50
+
51
+
52
+ @dataclasses.dataclass
53
+ class Ps2Elf:
54
+ entrypoint: int
55
+ segs: list[FakeSegment]
56
+ size: int
57
+ compiler: str
58
+ elf_section_names: list[tuple[str, bool]]
59
+ gp: Optional[int]
60
+ ld_gp_expression: Optional[str]
61
+
62
+ @staticmethod
63
+ def get_info(elf_path: Path, elf_bytes: bytes) -> Optional[Ps2Elf]:
64
+ # Avoid spimdisasm from complaining about unknown sections.
65
+ spimdisasm.common.GlobalConfig.QUIET = True
66
+
67
+ elf = Elf32File(elf_bytes)
68
+ if elf.header.type != Elf32ObjectFileType.EXEC.value:
69
+ log.write("Elf file is not an EXEC type.", status="warn")
70
+ return None
71
+ if elf.header.machine != 8:
72
+ # 8 corresponds to EM_MIPS
73
+ # We only care about mips binaries.
74
+ log.write("Elf file is not a MIPS binary.", status="warn")
75
+ return None
76
+ if Elf32Constants.Elf32HeaderFlag._5900 not in elf.elfFlags:
77
+ log.write("Missing 5900 flag", status="warn")
78
+ return None
79
+
80
+ if elf.reginfo is not None and elf.reginfo.gpValue != 0:
81
+ gp = elf.reginfo.gpValue
82
+ else:
83
+ gp = None
84
+ first_small_section_info: Optional[tuple[str, int]] = None
85
+
86
+ first_segment_name = "cod"
87
+ segs = [FakeSegment(first_segment_name, 0, 0, [])]
88
+
89
+ # TODO: check `.comment` section for any compiler info
90
+ compiler = "EEGCC"
91
+
92
+ elf_section_names: list[tuple[str, bool]] = []
93
+
94
+ first_offset: Optional[int] = None
95
+ rom_size = 0
96
+
97
+ previous_type = Elf32Constants.Elf32SectionHeaderType.PROGBITS
98
+ do_new_segs = False
99
+
100
+ # Loop over all the sections.
101
+ # Treat every normal elf section as part as the "main" segment.
102
+ # If we see a PROGBITS after a NOBITS then we shift the behavior into
103
+ # putting every new elf section into their own segment, this can happen
104
+ # when the elf contains a few special sections like `.mfifo`.
105
+ section_headers = sorted(elf.sectionHeaders, key=lambda x: x.offset)
106
+ for section in section_headers:
107
+ if section.size == 0:
108
+ # Skip over empty sections
109
+ continue
110
+
111
+ name = elf.shstrtab[section.name]
112
+ if name == ".mwcats":
113
+ compiler = "MWCCPS2"
114
+ continue
115
+
116
+ flags, _unknown_flags = Elf32SectionHeaderFlag.parseFlags(section.flags)
117
+ # if _unknown_flags != 0:
118
+ # print(name, f"0x{_unknown_flags:08X}")
119
+ if Elf32SectionHeaderFlag.ALLOC not in flags:
120
+ # We don't care about non-alloc sections
121
+ continue
122
+
123
+ # We want PROGBITS (actual data) and NOBITS (bss and similar)
124
+ typ = Elf32Constants.Elf32SectionHeaderType.fromValue(section.type)
125
+ is_nobits = typ == Elf32Constants.Elf32SectionHeaderType.NOBITS
126
+ if typ == Elf32Constants.Elf32SectionHeaderType.PROGBITS:
127
+ if previous_type == Elf32Constants.Elf32SectionHeaderType.NOBITS:
128
+ do_new_segs = True
129
+ pass
130
+ elif typ == Elf32Constants.Elf32SectionHeaderType.NOBITS:
131
+ pass
132
+ elif typ == Elf32Constants.Elf32SectionHeaderType.MIPS_REGINFO:
133
+ continue
134
+ else:
135
+ log.write(
136
+ f"Unknown section type '{typ}' ({name}) found in the elf",
137
+ status="warn",
138
+ )
139
+ return None
140
+
141
+ if first_offset is None:
142
+ first_offset = section.offset
143
+
144
+ if first_small_section_info is None and name in ELF_SMALL_SECTIONS:
145
+ first_small_section_info = (name, section.addr)
146
+
147
+ start = section.offset - first_offset
148
+ size = section.size
149
+
150
+ if do_new_segs:
151
+ segs.append(FakeSegment(name.lstrip("."), 0, start, []))
152
+
153
+ # Try to map this section to something splat can understand.
154
+ splat_segment_type = ELF_SECTION_MAPPING.get(name)
155
+ if splat_segment_type is None:
156
+ # Let's infer based on the section's flags
157
+ if is_nobits:
158
+ splat_segment_type = "bss"
159
+ elif Elf32SectionHeaderFlag.EXECINSTR in flags:
160
+ splat_segment_type = "asm"
161
+ elif Elf32SectionHeaderFlag.WRITE in flags:
162
+ splat_segment_type = "data"
163
+ else:
164
+ # Whatever...
165
+ splat_segment_type = "rodata"
166
+
167
+ if name.startswith("."):
168
+ valid_for_splat = (
169
+ name in ELF_SECTION_MAPPING
170
+ and name not in ELF_SECTIONS_IGNORE
171
+ and not do_new_segs
172
+ )
173
+ elf_section_names.append((name, valid_for_splat))
174
+
175
+ new_section = ElfSection(
176
+ name,
177
+ splat_segment_type,
178
+ section.addr,
179
+ start,
180
+ size,
181
+ is_nobits,
182
+ )
183
+ segs[-1].sections.append(new_section)
184
+ if is_nobits:
185
+ segs[-1].bss_size += size
186
+ else:
187
+ rom_size = start + size
188
+
189
+ previous_type = typ
190
+
191
+ # There are some games where they just squashed most sections into a
192
+ # single one, making the elf_section_names list pretty useless.
193
+ # We try to detect this and provide a default list if that's the case,
194
+ # hoping for the best.
195
+ if len(elf_section_names) < 4:
196
+ elf_section_names = [
197
+ (".text", True),
198
+ (".vutext", False),
199
+ (".data", True),
200
+ (".vudata", False),
201
+ (".rodata", True),
202
+ (".gcc_except_table", True),
203
+ (".lit8", True),
204
+ (".lit4", True),
205
+ (".sdata", True),
206
+ (".sbss", True),
207
+ (".bss", True),
208
+ (".vubss", False),
209
+ ]
210
+
211
+ # Fixup vram address of segments
212
+ for seg in segs:
213
+ seg.vram = seg.sections[0].vram
214
+
215
+ ld_gp_expression = None
216
+ if gp is not None and first_small_section_info is not None:
217
+ section_name, section_address = first_small_section_info
218
+ if gp > section_address:
219
+ diff = gp - section_address
220
+ ld_gp_expression = f"{first_segment_name}_{section_name.strip('.').upper()}_START + 0x{diff:04X}"
221
+
222
+ return Ps2Elf(
223
+ elf.header.entry,
224
+ segs,
225
+ rom_size,
226
+ compiler,
227
+ elf_section_names,
228
+ gp,
229
+ ld_gp_expression,
230
+ )
231
+
232
+
233
+ @dataclasses.dataclass
234
+ class FakeSegment:
235
+ name: str
236
+ vram: int
237
+ start: int
238
+ sections: list[ElfSection]
239
+ bss_size: int = 0
240
+
241
+
242
+ @dataclasses.dataclass
243
+ class ElfSection:
244
+ name: str
245
+ splat_segment_type: str
246
+ vram: int
247
+ start: int
248
+ size: int
249
+ is_nobits: bool
splat/util/symbols.py CHANGED
@@ -600,8 +600,13 @@ def add_symbol_to_spim_section(
600
600
  return context_sym
601
601
 
602
602
 
603
+ # force_in_segment=True when the symbol belongs to this specific segment.
604
+ # force_in_segment=False when this symbol is just a reference.
603
605
  def create_symbol_from_spim_symbol(
604
- segment: "Segment", context_sym: spimdisasm.common.ContextSymbol
606
+ segment: "Segment",
607
+ context_sym: spimdisasm.common.ContextSymbol,
608
+ *,
609
+ force_in_segment: bool,
605
610
  ) -> "Symbol":
606
611
  in_segment = False
607
612
 
@@ -631,7 +636,7 @@ def create_symbol_from_spim_symbol(
631
636
  in_segment = segment.contains_vram(context_sym.vram)
632
637
 
633
638
  sym = segment.create_symbol(
634
- context_sym.vram, in_segment, type=sym_type, reference=True
639
+ context_sym.vram, force_in_segment or in_segment, type=sym_type, reference=True
635
640
  )
636
641
 
637
642
  if sym.given_name is None and context_sym.name is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splat64
3
- Version: 0.36.3
3
+ Version: 0.36.4
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
@@ -30,16 +30,16 @@ License-File: LICENSE
30
30
  Classifier: License :: OSI Approved :: MIT License
31
31
  Classifier: Programming Language :: Python :: 3
32
32
  Requires-Python: >=3.9
33
- Requires-Dist: colorama
34
- Requires-Dist: intervaltree
35
- Requires-Dist: pylibyaml
36
- Requires-Dist: pyyaml
37
- Requires-Dist: tqdm
33
+ Requires-Dist: colorama==0.4.6
34
+ Requires-Dist: intervaltree==3.1.0
35
+ Requires-Dist: pylibyaml==0.1.0
36
+ Requires-Dist: pyyaml==6.0.3
37
+ Requires-Dist: tqdm==4.67.1
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: crunch64<1.0.0,>=0.5.1; extra == 'dev'
40
40
  Requires-Dist: mypy; extra == 'dev'
41
41
  Requires-Dist: n64img>=0.3.3; extra == 'dev'
42
- Requires-Dist: pygfxd; extra == 'dev'
42
+ Requires-Dist: pygfxd>=1.0.5; extra == 'dev'
43
43
  Requires-Dist: rabbitizer<2.0.0,>=1.12.0; extra == 'dev'
44
44
  Requires-Dist: ruff; extra == 'dev'
45
45
  Requires-Dist: spimdisasm<2.0.0,>=1.38.0; extra == 'dev'
@@ -48,7 +48,7 @@ Requires-Dist: types-pyyaml; extra == 'dev'
48
48
  Provides-Extra: mips
49
49
  Requires-Dist: crunch64<1.0.0,>=0.5.1; extra == 'mips'
50
50
  Requires-Dist: n64img>=0.3.3; extra == 'mips'
51
- Requires-Dist: pygfxd; extra == 'mips'
51
+ Requires-Dist: pygfxd>=1.0.5; extra == 'mips'
52
52
  Requires-Dist: rabbitizer<2.0.0,>=1.12.0; extra == 'mips'
53
53
  Requires-Dist: spimdisasm<2.0.0,>=1.38.0; extra == 'mips'
54
54
  Description-Content-Type: text/markdown
@@ -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.36.3,<1.0.0
79
+ splat64[mips]>=0.36.4,<1.0.0
80
80
  ```
81
81
 
82
82
  ### Optional dependencies
@@ -1,4 +1,4 @@
1
- splat/__init__.py,sha256=Jd0jequ-9rFyGBhXTci1H5cne5ZAVFvapdlzAM-y-4o,291
1
+ splat/__init__.py,sha256=K7V_4INbs5mtgBs_XECCUNEhSPHURCOviws3SKjutbc,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=LXuCElHxOvbb9xuSSeswT8W_v78chwtqjKq_9nxFVzQ,167
@@ -14,21 +14,21 @@ splat/platforms/psp.py,sha256=rCr_uf1SuHNaMUXjIC0GyoA_463OQZv0se2KMarWry0,125
14
14
  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
- splat/scripts/create_config.py,sha256=WU_4jZQZ1Y9uLITWARgsYMl1J1YaKuPaX-bsPhqDsa4,11785
17
+ splat/scripts/create_config.py,sha256=Xt0BgS9ql5GfLrNKWQRMuKWea0sXUygRV1_wCYGrrPE,17949
18
18
  splat/scripts/split.py,sha256=wWib2lDdedzjcNLH-Ay48gM2XGZ9JCUsrriamuHzkOU,21935
19
19
  splat/segtypes/__init__.py,sha256=-upUw_4JGQtvyp6IfTMzOq_CK3xvVaT_0K0_EipHyOo,208
20
20
  splat/segtypes/linker_entry.py,sha256=WHFdVFC_NW6_ub-LJbfwze-FZGCTRtjEZHz_bhIkX-M,25160
21
- splat/segtypes/segment.py,sha256=wo4QCdDAmR1TZRgk0DgTZ0RJGuhdZGuJy20L_Fgz_Ss,30629
21
+ splat/segtypes/segment.py,sha256=IcmF7FxDrJ1ZP5AKFO-skNrGfYhGIYwawyepKzeTF3A,30619
22
22
  splat/segtypes/common/__init__.py,sha256=mnq0acScilSCCo6q2PvkDk0Or3V8qitA7I8QMVw8haI,631
23
23
  splat/segtypes/common/asm.py,sha256=gpR4uZ57l4Q9G0XTOeO1JIk9P5d8xXBxRfkrPIN-M1A,644
24
24
  splat/segtypes/common/asmtu.py,sha256=C52kKh-8YeDHu0EucEfQ-tQMtDgfKfwAJ6wwiW6nOBU,354
25
25
  splat/segtypes/common/bin.py,sha256=qZeuvHNSrRuxdk-8V0xW0tcFhtzPk6doyf43E_t7D9Q,1250
26
- splat/segtypes/common/bss.py,sha256=nktRbKrDivweyLdsUQkL5guSayqpwn7NK5j3gq3arKc,3084
27
- splat/segtypes/common/c.py,sha256=nIcnBOraSxSLcg6kn2fwkV3WMS-Tx7zfzbi7YXMdr7A,20549
26
+ splat/segtypes/common/bss.py,sha256=cX0RO7LRcBRO6t463AUSSV8sGdlxZTcd5_McZjjqfFo,3107
27
+ splat/segtypes/common/c.py,sha256=RkevmqKXK9RKQ_yPClFFur1Uq4jGGdVtL1BnScbHhok,21327
28
28
  splat/segtypes/common/code.py,sha256=FfBuy6x8fQXoJYX2Ul80uOs-CzlVslUaveD-hBmvKXM,10693
29
- splat/segtypes/common/codesubsegment.py,sha256=n6mtHMpwjxNoTSh3aMT5Y19xvriAaaBP_r8AhVW52uU,10648
29
+ splat/segtypes/common/codesubsegment.py,sha256=XpPzNwGSNL4HE7Qgi7-tSVXlYtfdXug9PrzQIJu3yDI,10720
30
30
  splat/segtypes/common/cpp.py,sha256=p-duowCt4czrmaWgj1LuFw5LGfdyB_uaeLTKVmH80Cg,180
31
- splat/segtypes/common/data.py,sha256=r9G_vYieNlRze-5wGOKFazvFjpuodfSLad6iCNsBOAU,5665
31
+ splat/segtypes/common/data.py,sha256=l3CCa95vHH8XwiksamaxuZuSF_rOGqcozGlZj0bt9Yw,5712
32
32
  splat/segtypes/common/databin.py,sha256=nDI1ciAv0QLt2xDYjEtGJHSoONZzJZKyST-BuSKML2Q,1169
33
33
  splat/segtypes/common/eh_frame.py,sha256=MzL373ej2E38leM47py3T5beLq2IolsYszLZM8PYS2Y,913
34
34
  splat/segtypes/common/gcc_except_table.py,sha256=-ZrQL5dCcRu7EPFFPgL8lIJwWL5FsEeeQoSUVfTkyIg,2112
@@ -39,14 +39,14 @@ splat/segtypes/common/lib.py,sha256=WaJH0DH0B8iJvhYQWYBjThbNeEXz3Yh7E8ZyONe5JtQ,
39
39
  splat/segtypes/common/linker_offset.py,sha256=IaEXMXq62BS2g95I4lS0wax5Ehjfi3O3C-uSDUbMjvI,767
40
40
  splat/segtypes/common/pad.py,sha256=-7SaRgXrmGeOINKVV9v1c3e9jn8aVQknD8EVaEPcPrw,758
41
41
  splat/segtypes/common/rdata.py,sha256=ab2MFZVjlnD2p5nDVNroo3W9blaVJND2jN2PJPy6a_Y,142
42
- splat/segtypes/common/rodata.py,sha256=Y8ugWuHDfdU6vQSXdfLF9YKdfvuaoVcRcyk3cFeo_iA,6191
42
+ splat/segtypes/common/rodata.py,sha256=ZUqXPw119EewNr9_pUL9QeoVML5XwPTo1THz9OjB86k,6238
43
43
  splat/segtypes/common/rodatabin.py,sha256=RsvhGxY9iX1QHSyMfuWLaFrqdBvDEtL4j13-jPpvyvk,1174
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=gFPzEi0Jq0NGtL4xlblIlNZus7dHkxebLKkr6jhOkxs,81
47
47
  splat/segtypes/common/textbin.py,sha256=PXEb1JQB2NEqVDLg5KW2vI0NLSqrROJvCOifxB0NUms,5994
48
48
  splat/segtypes/n64/__init__.py,sha256=tf2yWlijeKmOp1OhEEL-aW88mU-mWQRC2lSjQ5Ww1eI,569
49
- splat/segtypes/n64/ci.py,sha256=An7wIHcoZ89KiGCKpCRbM7IrmDNYBidXT-6kjKn2vH0,2400
49
+ splat/segtypes/n64/ci.py,sha256=i2axvnNtteezMg18gLcgggaI14Ez0I5DuGuNah9z4DU,2426
50
50
  splat/segtypes/n64/ci4.py,sha256=ZP8e12CpV8U7r8oLnb9uHc-MkmBbxjXBbJxROFvOjiM,184
51
51
  splat/segtypes/n64/ci8.py,sha256=tS4USmp0LuJnP-UlgaOYoxvYxBKgE67wYCz-KeRLlFk,184
52
52
  splat/segtypes/n64/decompressor.py,sha256=YuKo-PS_S2-Ow1Zw8QTO70NWlSYTY79JZN0kUQB7T0U,1779
@@ -58,10 +58,10 @@ splat/segtypes/n64/i8.py,sha256=5zqYmpmuXprEzwidVQTknSKqMuu0C1Izk4t5ILoTPf4,204
58
58
  splat/segtypes/n64/ia16.py,sha256=y1uLVuh72HTmyAEK90R67vftp-X4LDgRxgJyU8EAnH0,208
59
59
  splat/segtypes/n64/ia4.py,sha256=YdBtqx1LUHFXCPOUJUqutSHnD3YTIov6JKSZjXNKuVY,206
60
60
  splat/segtypes/n64/ia8.py,sha256=TxTqD-4mx7eF81IxThkPn54aJkEtGALGvGudmejBM0w,206
61
- splat/segtypes/n64/img.py,sha256=MtxMPSbMIDDnRhIjOte9-Kk3smzP5XybqKkifv6EhTE,3083
61
+ splat/segtypes/n64/img.py,sha256=xbpgJlg8tiok4CaFvkWTXRcxvNZGK_i3GAnN5jtY8kA,3283
62
62
  splat/segtypes/n64/ipl3.py,sha256=HKPXIaKr6XAQGwm3YKQedmijGqXNebzIS9LjiPxl2Q0,200
63
63
  splat/segtypes/n64/mio0.py,sha256=jYTq0Iz1NkWSFLDnFPdLYu3HG2adyRZEVlzevFNdn0U,379
64
- splat/segtypes/n64/palette.py,sha256=IwHVwByoRahTGOL3F1c_6hmgvGK63p3Da086dxSqBok,3132
64
+ splat/segtypes/n64/palette.py,sha256=y--D6EfBPvzahfcLA_ahFR9MHslcPpu2JLNBuNpK-B4,4264
65
65
  splat/segtypes/n64/rgba16.py,sha256=ddWJARv5SKFFuCUIFeI-MQFqa52fa3d0srZMVbCXOSw,212
66
66
  splat/segtypes/n64/rgba32.py,sha256=LbYDZ4b95axIYKc06HDyAYMUm-cTJLOsRCKGlE_Hlmw,227
67
67
  splat/segtypes/n64/rsp.py,sha256=8t9ZnhuF2KrxG1FTRC7R0zgciu-4C9SMcFcFzBP4rfI,236
@@ -75,7 +75,7 @@ splat/segtypes/ps2/vtables.py,sha256=c8weQcbQ0-KkjkLHUXViTWkEaJ6LNR8jBUMV-0t0m4o
75
75
  splat/segtypes/psp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  splat/segtypes/psx/__init__.py,sha256=tn0oDYUPtF0PfC8I4G23nPvH_JAcuLGxOCvYN1SE3Dc,31
77
77
  splat/segtypes/psx/header.py,sha256=2S8XG_6zfLGzmEA0XpFqs0-4nf52YD9Erk6SbMUlVzo,2639
78
- splat/util/__init__.py,sha256=rsnoPNjNVbu9j_yxoubyq8Pf-clIbOHk2vQyuKSmxyI,513
78
+ splat/util/__init__.py,sha256=vejj8R_nldFOlIOEoG492Lycg1zUCTQewoPF9c3P6fw,538
79
79
  splat/util/cache_handler.py,sha256=BrTWo8U4bj8TBcZfIRwiAYmogc3YUlBz9P-34Y0aIrg,1851
80
80
  splat/util/color.py,sha256=FSmy0dAQJ9FzRBc99Yt4kBEyB62MC_YiVkqoWgPMsRU,371
81
81
  splat/util/compiler.py,sha256=uXShMm49380ENecSFlsi75LWI45yakWkExZX8NT5pOU,1778
@@ -87,16 +87,18 @@ splat/util/palettes.py,sha256=d3KoZnwt-zunI9eNwb3txysXg4DY3xnF0O5aQRxM4so,2920
87
87
  splat/util/progress_bar.py,sha256=41VehpIFK1cphENaXV_Aq6_3mFx25eQ8V7Z51SKFPeM,166
88
88
  splat/util/relocs.py,sha256=j8fzM9u0ZQwZa1b1Dd26ho9GwIooBXt8mE0jAN7wqV0,4153
89
89
  splat/util/statistics.py,sha256=8C88vLW8q4Xd4i1Crz8X24NLoraLyKd0lw3ebbeonSo,1906
90
- splat/util/symbols.py,sha256=xYPbLTSlPrxsDvRw3aOTbN0wRZG6Ob4ZXD35cqC7YkE,31322
90
+ splat/util/symbols.py,sha256=wqznzi0-ZQ0pID1qWLShSPHL3eD7M5jjMwmO4LyWxIc,31519
91
91
  splat/util/utils.py,sha256=kAC0DAjaGa0Kq2k86RfQtFkheV87XuyeOgZIVIk3CHE,208
92
92
  splat/util/vram_classes.py,sha256=UH4rkugEwoec_-alJ4smNOcnU51G-V5OG7Pfsj47eaM,3491
93
93
  splat/util/n64/__init__.py,sha256=hsBkPh6NUz-bW7HVspcNZ0mCxBhdfcPC07_7gbga95o,84
94
94
  splat/util/n64/find_code_length.py,sha256=uUoPoUORAjsAvH8oGqwnGvw6j8I_NnSrZtA-x9h9h7E,1414
95
95
  splat/util/n64/rominfo.py,sha256=Ms9P-MuuOSyEF4QNB3wBuC-U9OdrfByqR0VPAHqTBQI,16976
96
+ splat/util/ps2/__init__.py,sha256=hAU12HtQLOc_sxc8_rzxOERBT5wjg3DhQ8XRN9hBXfA,39
97
+ splat/util/ps2/ps2elfinfo.py,sha256=v6nTPabzXpG1N-MhhkbVeLoGjgsjBEpHL1DoPv26-Hs,7982
96
98
  splat/util/psx/__init__.py,sha256=kCCaR-KB1mNlIcXB4OuuSQ2zVLbWg_SnIZIUeyjeBBI,39
97
99
  splat/util/psx/psxexeinfo.py,sha256=Oxd5Lt8HTNHuUzw3cVujdyJRqEG-yo0XT78wEISUXms,5705
98
- splat64-0.36.3.dist-info/METADATA,sha256=Ugzp2ibo0F22MBBZwQKte5jm-Kv6-9sJFda1lTcugUw,3829
99
- splat64-0.36.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
100
- splat64-0.36.3.dist-info/entry_points.txt,sha256=O7Xy-qNOHcI87-OQrWJ-OhRDws74SuwVb_4rtnp0eLo,52
101
- splat64-0.36.3.dist-info/licenses/LICENSE,sha256=97VMVzjG8yQvsf8NG2M9IFSbh7R8cifJnc6QK1cZqj8,1070
102
- splat64-0.36.3.dist-info/RECORD,,
100
+ splat64-0.36.4.dist-info/METADATA,sha256=sQtM6Sc89JJYxes3KqZRDfIKyDRLqzFuXUbiD82IXR4,3879
101
+ splat64-0.36.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
102
+ splat64-0.36.4.dist-info/entry_points.txt,sha256=O7Xy-qNOHcI87-OQrWJ-OhRDws74SuwVb_4rtnp0eLo,52
103
+ splat64-0.36.4.dist-info/licenses/LICENSE,sha256=97VMVzjG8yQvsf8NG2M9IFSbh7R8cifJnc6QK1cZqj8,1070
104
+ splat64-0.36.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any