summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorAnghelo Carvajal <angheloalf95@gmail.com>2023-07-14 14:44:11 -0400
committerGitHub <noreply@github.com>2023-07-14 14:44:11 -0400
commitce93cd026259b5724a8de4a2d3bb2e87297ce5e4 (patch)
tree33797b411d2451d9aeddd3b43d7fe2d7b244fa56 /tools
parent39a2e453bb882b2498c7c4aa9073a9f85d048f0c (diff)
Update subrepos (#19)
* git subrepo pull --force tools/asm-processor subrepo: subdir: "tools/asm-processor" merged: "6dd4d3a" upstream: origin: "git@github.com:simonlindholm/asm-processor.git" branch: "main" commit: "6dd4d3a" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * git subrepo pull lib/ultralib --force subrepo: subdir: "lib/ultralib" merged: "0417857" upstream: origin: "git@github.com:decompals/ultralib.git" branch: "main" commit: "0417857" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * git subrepo pull tools/asm-differ --force subrepo: subdir: "tools/asm-differ" merged: "6d3e4c4" upstream: origin: "git@github.com:simonlindholm/asm-differ.git" branch: "main" commit: "6d3e4c4" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * git subrepo pull tools/splat --force subrepo: subdir: "tools/splat" merged: "406b646" upstream: origin: "git@github.com:ethteck/splat.git" branch: "master" commit: "406b646" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Revert "git subrepo pull lib/ultralib --force" This reverts commit da9fca47be7d8f957b786c02d7bf4b4a48830f4a. * git subrepo pull tools/splat subrepo: subdir: "tools/splat" merged: "7869ef2" upstream: origin: "git@github.com:ethteck/splat.git" branch: "master" commit: "7869ef2" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Disable asm size directive
Diffstat (limited to 'tools')
-rw-r--r--tools/asm-differ/.github/workflows/check-poetry-lock.yml20
-rw-r--r--tools/asm-differ/.github/workflows/unit-tests.yml15
-rw-r--r--tools/asm-differ/.gitignore1
-rw-r--r--tools/asm-differ/.gitrepo4
-rw-r--r--tools/asm-differ/README.md2
-rwxr-xr-xtools/asm-differ/diff.py82
-rw-r--r--tools/asm-differ/poetry.lock321
-rw-r--r--tools/asm-differ/test.py185
-rw-r--r--tools/asm-processor/.gitrepo4
-rw-r--r--tools/asm-processor/asm_processor.py13
-rw-r--r--tools/asm-processor/prelude.inc10
-rw-r--r--tools/splat/.github/workflows/mypy.yml13
-rw-r--r--tools/splat/.gitrepo4
-rw-r--r--tools/splat/CHANGELOG.md62
-rw-r--r--tools/splat/Dockerfile8
-rw-r--r--tools/splat/disassembler/__init__.py2
-rw-r--r--tools/splat/disassembler/disassembler.py17
-rw-r--r--tools/splat/disassembler/disassembler_instance.py26
-rw-r--r--tools/splat/disassembler/null_disassembler.py14
-rw-r--r--tools/splat/disassembler/spimdisasm_disassembler.py118
-rw-r--r--tools/splat/disassembler_section.py283
-rw-r--r--tools/splat/requirements.txt6
-rw-r--r--tools/splat/run_tests.sh9
-rw-r--r--tools/splat/segtypes/common/bss.py12
-rw-r--r--tools/splat/segtypes/common/c.py14
-rw-r--r--tools/splat/segtypes/common/code.py17
-rw-r--r--tools/splat/segtypes/common/codesubsegment.py33
-rw-r--r--tools/splat/segtypes/common/data.py29
-rw-r--r--tools/splat/segtypes/common/group.py8
-rw-r--r--tools/splat/segtypes/common/rodata.py17
-rw-r--r--tools/splat/segtypes/gc/rarc.py2
-rw-r--r--tools/splat/segtypes/linker_entry.py23
-rw-r--r--tools/splat/segtypes/n64/ci.py3
-rw-r--r--tools/splat/segtypes/n64/gfx.py2
-rw-r--r--tools/splat/segtypes/n64/img.py3
-rw-r--r--tools/splat/segtypes/n64/vtx.py10
-rw-r--r--tools/splat/segtypes/segment.py36
-rwxr-xr-xtools/splat/split.py98
-rw-r--r--tools/splat/test.py515
-rw-r--r--tools/splat/test/basic_app/basic_app.ld78
-rwxr-xr-xtools/splat/test/basic_app/build.sh12
-rw-r--r--tools/splat/test/basic_app/clean.sh2
-rw-r--r--tools/splat/test/basic_app/dummy_ipl3.s3
-rw-r--r--tools/splat/test/basic_app/expected/.splachebin0 -> 805 bytes
-rw-r--r--tools/splat/test/basic_app/expected/asm/data/main.bss.s6
-rw-r--r--tools/splat/test/basic_app/expected/asm/data/main.data.s13
-rw-r--r--tools/splat/test/basic_app/expected/asm/handwritten.s16
-rw-r--r--tools/splat/test/basic_app/expected/asm/header.s16
-rw-r--r--tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_80000400.s55
-rw-r--r--tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_800004A0.s26
-rw-r--r--tools/splat/test/basic_app/expected/assets/dummy_ipl3.binbin0 -> 4032 bytes
-rw-r--r--tools/splat/test/basic_app/expected/basic_app.ld70
-rw-r--r--tools/splat/test/basic_app/expected/src/main.c5
-rw-r--r--tools/splat/test/basic_app/expected/undefined_funcs_auto.txt0
-rw-r--r--tools/splat/test/basic_app/expected/undefined_syms_auto.txt1
-rw-r--r--tools/splat/test/basic_app/handwritten.s12
-rw-r--r--tools/splat/test/basic_app/header.s16
-rw-r--r--tools/splat/test/basic_app/main.c45
-rw-r--r--tools/splat/test/basic_app/splat.yaml38
-rw-r--r--tools/splat/test_gen_expected.sh3
-rw-r--r--tools/splat/util/n64/Yay0decompress.py16
-rw-r--r--tools/splat/util/n64/decompressor.py5
-rwxr-xr-xtools/splat/util/n64/rominfo.py10
-rw-r--r--tools/splat/util/options.py36
-rw-r--r--tools/splat/util/symbols.py369
65 files changed, 2496 insertions, 398 deletions
diff --git a/tools/asm-differ/.github/workflows/check-poetry-lock.yml b/tools/asm-differ/.github/workflows/check-poetry-lock.yml
new file mode 100644
index 0000000..6104770
--- /dev/null
+++ b/tools/asm-differ/.github/workflows/check-poetry-lock.yml
@@ -0,0 +1,20 @@
+name: flake check
+
+on:
+ pull_request:
+ push:
+
+permissions: read-all
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ # Install `nix` which is just a dead-simple way to get a stable `poetry`
+ # in scope.
+ - uses: cachix/install-nix-action@v20
+ with:
+ github_access_token: ${{ secrets.GITHUB_TOKEN }}
+ # Check that poetry.lock is in sync with pyproject.toml
+ - run: nix run github:NixOS/nixpkgs/22.11#poetry -- lock --check
diff --git a/tools/asm-differ/.github/workflows/unit-tests.yml b/tools/asm-differ/.github/workflows/unit-tests.yml
new file mode 100644
index 0000000..a77498a
--- /dev/null
+++ b/tools/asm-differ/.github/workflows/unit-tests.yml
@@ -0,0 +1,15 @@
+name: unit tests
+
+on:
+ pull_request:
+ push:
+
+permissions: read-all
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - run: python3 -m pip install --user colorama watchdog levenshtein cxxfilt
+ - run: python3 test.py
diff --git a/tools/asm-differ/.gitignore b/tools/asm-differ/.gitignore
index a2b216f..90df93b 100644
--- a/tools/asm-differ/.gitignore
+++ b/tools/asm-differ/.gitignore
@@ -1,4 +1,3 @@
.mypy_cache/
__pycache__/
.vscode/
-poetry.lock
diff --git a/tools/asm-differ/.gitrepo b/tools/asm-differ/.gitrepo
index a98d53e..b40f427 100644
--- a/tools/asm-differ/.gitrepo
+++ b/tools/asm-differ/.gitrepo
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:simonlindholm/asm-differ.git
branch = main
- commit = 857b39804a6f764634483cff7adc168a2f3e5c87
- parent = 6e36168761e33e6c33e6f15c2678733ce61ddffb
+ commit = 6d3e4c44d1ad8ad59bcae3abecb35f0d10e135e1
+ parent = da9fca47be7d8f957b786c02d7bf4b4a48830f4a
method = merge
cmdver = 0.4.3
diff --git a/tools/asm-differ/README.md b/tools/asm-differ/README.md
index 1d0932c..63b5253 100644
--- a/tools/asm-differ/README.md
+++ b/tools/asm-differ/README.md
@@ -1,6 +1,6 @@
# asm-differ
-Nice differ for assembly code. Currently supports MIPS, PPC, AArch64, and ARM32; should be easy to hack to support other instruction sets.
+Nice differ for assembly code. Currently supports MIPS, PPC, AArch64, ARM32, and SH2; should be easy to hack to support other instruction sets.
![](screenshot.png)
diff --git a/tools/asm-differ/diff.py b/tools/asm-differ/diff.py
index 1b12f9a..543e8fe 100755
--- a/tools/asm-differ/diff.py
+++ b/tools/asm-differ/diff.py
@@ -54,7 +54,7 @@ if __name__ == "__main__":
argcomplete = None
parser = argparse.ArgumentParser(
- description="Diff MIPS, PPC, AArch64, or ARM32 assembly."
+ description="Diff MIPS, PPC, AArch64, ARM32, or SH2 assembly."
)
start_argument = parser.add_argument(
@@ -407,7 +407,7 @@ class ProjectSettings:
build_command: List[str]
map_format: str
build_dir: str
- ms_map_address_offset: int
+ map_address_offset: int
baseimg: Optional[str]
myimg: Optional[str]
mapfile: Optional[str]
@@ -481,7 +481,9 @@ def create_project_settings(settings: Dict[str, Any]) -> ProjectSettings:
objdump_flags=settings.get("objdump_flags", []),
expected_dir=settings.get("expected_dir", "expected/"),
map_format=settings.get("map_format", "gnu"),
- ms_map_address_offset=settings.get("ms_map_address_offset", 0),
+ map_address_offset=settings.get(
+ "map_address_offset", settings.get("ms_map_address_offset", 0)
+ ),
build_dir=settings.get("build_dir", settings.get("mw_build_dir", "build/")),
show_line_numbers_default=settings.get("show_line_numbers_default", True),
disassemble_all=settings.get("disassemble_all", False),
@@ -557,6 +559,7 @@ def get_objdump_executable(objdump_executable: Optional[str]) -> str:
"mips-linux-gnu-objdump",
"mips64-elf-objdump",
"mips-elf-objdump",
+ "sh-elf-objdump",
]
for objdump_cand in objdump_candidates:
try:
@@ -590,7 +593,7 @@ BUFFER_CMD: List[str] = ["tail", "-c", str(10**9)]
# -i ignores case when searching
# -c something about how the screen gets redrawn; I don't remember the purpose
# -#6 makes left/right arrow keys scroll by 6 characters
-LESS_CMD: List[str] = ["less", "-SRic", "-#6"]
+LESS_CMD: List[str] = ["less", "-SRic", "-+F", "-+X", "-#6"]
DEBOUNCE_DELAY: float = 0.1
@@ -1270,11 +1273,7 @@ def search_map_file(
if len(find) == 1:
names_find = re.search(r"(\S+) ... (\S+)", find[0])
assert names_find is not None
- fileofs = (
- int(names_find.group(1), 16)
- - load_address
- + project.ms_map_address_offset
- )
+ fileofs = int(names_find.group(1), 16) - load_address
if for_binary:
return None, fileofs
@@ -1503,6 +1502,7 @@ def dump_binary(
_, start_addr = search_map_file(start, project, config, for_binary=True)
if start_addr is None:
fail("Not able to find function in map file.")
+ start_addr += project.map_address_offset
if end is not None:
end_addr = eval_int(end, "End address must be an integer expression.")
else:
@@ -1568,7 +1568,8 @@ class AsmProcessorMIPS(AsmProcessor):
# integer.
return prev, None
before, imm, after = parse_relocated_line(prev)
- repl = row.split()[-1] + reloc_addend_from_imm(imm, before, self.config.arch)
+ addend = reloc_addend_from_imm(imm, before, self.config.arch)
+ repl = row.split()[-1] + addend
if "R_MIPS_LO16" in row:
repl = f"%lo({repl})"
elif "R_MIPS_HI16" in row:
@@ -1589,6 +1590,8 @@ class AsmProcessorMIPS(AsmProcessor):
repl = f"%got({repl})"
elif "R_MIPS_CALL16" in row:
repl = f"%call16({repl})"
+ elif "R_MIPS_LITERAL" in row:
+ repl = repl[:-len(addend)]
else:
assert False, f"unknown relocation type '{row}' for line '{prev}'"
return before + repl + after, repl
@@ -1811,6 +1814,17 @@ class AsmProcessorI686(AsmProcessor):
return mnemonic == "ret"
+class AsmProcessorSH2(AsmProcessor):
+ def __init__(self, config: Config) -> None:
+ super().__init__(config)
+
+ def process_reloc(self, row: str, prev: str) -> Tuple[str, Optional[str]]:
+ return prev, None
+
+ def is_end_of_function(self, mnemonic: str, args: str) -> bool:
+ return mnemonic == "rts"
+
+
@dataclass
class ArchSettings:
name: str
@@ -2005,6 +2019,15 @@ I686_BRANCH_INSTRUCTIONS = {
"jz",
}
+SH2_BRANCH_INSTRUCTIONS = {
+ "bf",
+ "bf.s",
+ "bt",
+ "bt.s",
+ "bra",
+ "bsr",
+}
+
MIPS_SETTINGS = ArchSettings(
name="mips",
re_int=re.compile(r"[0-9]+"),
@@ -2126,6 +2149,34 @@ I686_SETTINGS = ArchSettings(
proc=AsmProcessorI686,
)
+SH2_SETTINGS = ArchSettings(
+ name="sh2",
+ # match -128-127 preceded by a '#' with a ',' after (8 bit immediates)
+ re_int=re.compile(r"(?<=#)(-?(?:1[01][0-9]|12[0-8]|[1-9][0-9]?|0))(?=,)"),
+ # match <text>, match ! and after
+ re_comment=re.compile(r"<.*?>|!.*"),
+ # - r0-r15 general purpose registers, r15 is stack pointer during exceptions
+ # - sr, gbr, vbr - control registers
+ # - mach, macl, pr, pc - system registers
+ re_reg=re.compile(r"r1[0-5]|r[0-9]"),
+ # sh2 has pc-relative and gbr-relative but not stack-pointer-relative
+ re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)"),
+ # max immediate size is 8-bit
+ re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"),
+ re_imm=re.compile(r"\b0[xX][0-9a-fA-F]+\b"),
+ # https://github.com/bminor/binutils-gdb/blob/master/bfd/elf32-sh-relocs.h#L21
+ re_reloc=re.compile(r"R_SH_"),
+ arch_flags=["-m", "sh2"],
+ branch_instructions=SH2_BRANCH_INSTRUCTIONS,
+ instructions_with_address_immediates=SH2_BRANCH_INSTRUCTIONS.union(
+ {"bf", "bf.s", "bt", "bt.s", "bra", "bsr"}
+ ),
+ delay_slot_instructions=SH2_BRANCH_INSTRUCTIONS.union(
+ {"bf.s", "bt.s", "bra", "braf", "bsr", "bsrf", "jmp", "jsr", "rts"}
+ ),
+ proc=AsmProcessorSH2,
+)
+
ARCH_SETTINGS = [
MIPS_SETTINGS,
MIPSEL_SETTINGS,
@@ -2135,11 +2186,18 @@ ARCH_SETTINGS = [
AARCH64_SETTINGS,
PPC_SETTINGS,
I686_SETTINGS,
+ SH2_SETTINGS,
]
def hexify_int(row: str, pat: Match[str], arch: ArchSettings) -> str:
full = pat.group(0)
+
+ # sh2 only has 8-bit immediates, just convert them uniformly without
+ # any -hex stuff
+ if arch.name == "sh2":
+ return hex(int(full) & 0xFF)
+
if len(full) <= 1:
# leave one-digit ints alone
return full
@@ -2497,7 +2555,7 @@ def diff_sequences(
try:
rem1 = remap(seq1)
rem2 = remap(seq2)
- except ValueError as e:
+ except ValueError:
if len(seq1) + len(seq2) < 0x110000:
raise
# If there are too many unique elements, chr() doesn't work.
@@ -3112,7 +3170,7 @@ def align_diffs(old_diff: Diff, new_diff: Diff, config: Config) -> TableData:
def diff_line_to_table_line(line: Tuple[OutputLine, ...]) -> TableLine:
cells = [
- (line[0].base or Text(), line[0].line1)
+ (line[0].base or Text(), line[0].line1),
]
for ol in line[1:]:
cells.append((ol.fmt2, ol.line2))
diff --git a/tools/asm-differ/poetry.lock b/tools/asm-differ/poetry.lock
new file mode 100644
index 0000000..2826d78
--- /dev/null
+++ b/tools/asm-differ/poetry.lock
@@ -0,0 +1,321 @@
+# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
+
+[[package]]
+name = "ansiwrap"
+version = "0.8.4"
+description = "textwrap, but savvy to ANSI colors and styles"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"},
+ {file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"},
+]
+
+[package.dependencies]
+textwrap3 = ">=0.9.2"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "cxxfilt"
+version = "0.3.0"
+description = "Python interface to c++filt / abi::__cxa_demangle"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "cxxfilt-0.3.0-py2.py3-none-any.whl", hash = "sha256:774e85a8d0157775ed43276d89397d924b104135762d86b3a95f81f203094e07"},
+ {file = "cxxfilt-0.3.0.tar.gz", hash = "sha256:7df6464ba5e8efbf0d8974c0b2c78b32546676f06059a83515dbdfa559b34214"},
+]
+
+[package.extras]
+test = ["pytest (>=3.0.0)"]
+
+[[package]]
+name = "levenshtein"
+version = "0.20.9"
+description = "Python extension for computing string edit distances and similarities."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "Levenshtein-0.20.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:105c239ec786750cd5136991c58196b440cc39b6acf3ec8227f6562c9a94e4b9"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f7728bea7fe6dc55ceecde0dcda4287e74fe3b6733ad42530f46aaa8d2f81d0"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc7eca755c13c92814c8cce8175524cf764ce38f39228b602f59eac58cfdc51a"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8a552e79d053dc1324fb90d342447fd4e15736f4cbc5363b6fbd5577f53dce9"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5474b2681ee0b7944fb1e7fe281cd44e2dfe75b03ba4558dca49c96fa0861b62"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56e132c203b0dd8fc72a33e791c39ad0d5a25bcf24b130a1e202abbf489a3e75"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3badc94708ac05b405e795fde58a53272b90a9ee6099ecd54a345658b7b812e1"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48b9b3ae095b14dad7bc4bd219c7cd9113a7aa123a033337c85b00fe2ed565d3"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0d3a1f7328c91caeb1f857ddd2787e3f19d60cc2c688339d249ca8841da61454"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ef67c50428c99caf67d31bd209da21d9378da5f0cc3ad4f7bafb6caa78aee6f2"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:47f6d1592c0891f7355e38a302becd233336ca2f55f9a8be3a8635f946a6784f"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2891019740e874f05e0349e9f27b6af8ad837b1612f42e9c90c296d54d1404fd"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c554704eec4f4ba742febdcc79a85491f8f9a1d493cb103bb2af18536d6cf122"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-win32.whl", hash = "sha256:7628e356b3f9c78ad7272c3b9137f0641a1368849e749ff6f2c8fe372795806b"},
+ {file = "Levenshtein-0.20.9-cp310-cp310-win_amd64.whl", hash = "sha256:ba2bafe3511194a37044cae4e7d328cca70657933052691c37eba2ca428a379d"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7605a94145198d19fdaaa7e29c0f8a56ad719b12386f3ae8cd8ed4cb9fa6c2e4"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29db4dabfad2ddf33c7986eb6fd525c7587cca4c4d9e187365cff0a5281f5a35"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:965336c1772a4fc5fb2686a2a0bfaf3455dced96f19f50f278da8bc139076d31"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67235753035ac898d6475c0b29540521018db2e0027a3c1deb9aa0af0a84fd74"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:120dca58136aee3d8c7b190e30db7b6a6eb9579ea5712df84ad076a389801743"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6496ea66a6f755e48c0d82f1eee396d16edcd5592d4b3677d26fa789a636a728"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0af20327acc2c904d11611cb3a0d8d17f80c279a12e0b84189eafc35297186d"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d2f891ef53afbab6cf2eeb92ff13151884d17dc80a2d6d3c7ae74d7738b772"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2ab9c72380582bf4745d1c5b055b1df0c85f7a980a04bd7603a855dd91478c0f"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6de13be3eb5ac48053fb1635a7b4daa936b9114ad4b264942e9eb709fcaa41dd"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a9fc296860588251d8d72b4f4637cca4eef7351e042a7a23d44e6385aef1e160"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:35777b20fe35858248c22da37984469e6dd1278f55d17c53378312853d5d683d"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b9e0642ddb4c431f77c38cec9edbd0317e26c3f37d072ccf281ab58926dce69"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-win32.whl", hash = "sha256:f88ec322d86d3cc9d3936dbf6b421ad813950c2658599d48ac4ede59f2a6047e"},
+ {file = "Levenshtein-0.20.9-cp311-cp311-win_amd64.whl", hash = "sha256:2907a6888455f9915d5b656f5d058f63eaf6063b2c7f0f1ff6bc05706ae5bc39"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6bcebc79760be08488cb921732af34ade6abc7476a94866881c68b45ec4b6c82"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47d8d4f3825d1d8f3b19382537a8536e689cf57aaa224d2cb4f44cf844811885"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d40e18a5817ee7f0675401613a26c492fd4ea68d2103c1480fb5a6ab1b8763d"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d258f3d44f6bac17f33002fea34570049507d3476c3716b5267170c666b20b4"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c621e0c389546147ed43c33ca4168de0f91c920508ab8a94a400835fa084f486"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a31527dc7994353091626e62b7d82d53290cb00df48d3e5d29cb291fb4c03c"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:129c8f192e656b7c2c543bf0d704d677720771b8bc2f30c50db02fbc2001bac2"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5a01fca58255be6bf724a40af2575d7cf644c099c28a00d1f5f6a81675e60e7d"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:4c13749ea39a228f05d5bd9d473e76f726fc2dcd493cafc322f740921a6eeffb"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:69daa0f8eefa5b947255a81346741ed86fe7030e0909741dbd978e38b30da3fd"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fcc78a73ed423bbb09ac902dd2e1ff1094d159d1c6766e5e52da5f376a4cba18"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-win32.whl", hash = "sha256:d82ae57982a9f33c55778f1f0f63d5e51e291aee236abed3b90497578b944202"},
+ {file = "Levenshtein-0.20.9-cp36-cp36m-win_amd64.whl", hash = "sha256:4082379b406752fc1173ed1f8c3a122c5d5491e10e564ed721602e4e049e3d4c"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb499783b7126e6fc45c39ab34c8114148425c5d975b1ce35e6c47c0eda58a94"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce747b296aad3bd8a563cccf2119cf37bf72f668076bfdad6ec55f0a0596dd9"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1347c3ebbe8f42f7a487e8d23a95bde6529379b4939ad51d32246d001565c499"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2f1c1e8360603a6da29416da61d1907a27656843e269413091c8c3a3e6286e"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73c1caaedbee3617fd29139aac8dab7743776b59c3c1fed2790308ecb43c7b25"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1f24133df69f8b618fc508d6023695130ad3c3c8968ef43aaeca21835eb337a"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cf7260722f8170c09af5cfa714bb45626a4dfc85d71d1c1c9c52c2a6901cc501"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:01668178fd9244df290db0340293982fe7641162a12a35ad9ffb3fe145ce6377"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e46f9d3483dc4991ac60ff3711b0d40f93e352cc8edc16b68df57ccc472bd6c"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:680cd250dc1875eb80cf2a0cca742bd13f6f9ab11c48317244fcc483eba1dd67"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2346e2f7dfbbc2936bd81e19f7734984e72486ffc086760c897b39b9f674b2fa"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-win32.whl", hash = "sha256:7f31bcf257fec9719d0d97185c419d315f6f20a194f0b442919e352d19418b2e"},
+ {file = "Levenshtein-0.20.9-cp37-cp37m-win_amd64.whl", hash = "sha256:48262bc9830ad60de96411fcb2e96a522c7206e7069169e04d89dd79364a7722"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eba5696e1f8e8da225498fd1d743886d639400cafd0e5be3c553978cbb54c345"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:679333188f9791c85109d2981e97e8721a99b2b975b5c52d16aca50ac9c70757"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06c9cfc61cf66833692d1ed258ec5a0871221b0779f1281c32a10348c492e2c5"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5d80d949168df406f2ac9ade1a5d0419cef0a8df611c8c2efe88f0248c9d0c0"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9275c6e601ff7f659116e2235e8585950c9c39d72504006077be85bf27950b35"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6414eea342d9632045e12b66bef043dbc6557189a283dc4dcc5966f63fa48998"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56571c58700600a382ecdf3f9efcb132ed16a0476cbb4e23a9478ab0ae788fd9"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7ccb76ffd9b851384f9cf1595b90b17cae46f0ab895e234de11ea48f9d9f73a"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109172943cff7fb10f28a9eb819eb3eaf9c88fe38661fb1d0f230a8ae68a615c"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:534c8bbdfd033fa20575d57332d9ac0447b5afbeca7db975ba169762ece2051f"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:381a725963c392585135654caa3c7fc32cb1755ed977fb9db72e8838fee261be"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7e4a44b1223980a9880e6f2bbf19121a125928580df9e4e81207199190343e11"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc0ced58ee6d07351cde140a7ec88e5f2ceb053c805af1f90514d21914d21cad"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-win32.whl", hash = "sha256:5eec0868ffcd825564dd5e3399305eaa159220554d1aedbff13af0de1fe01f6c"},
+ {file = "Levenshtein-0.20.9-cp38-cp38-win_amd64.whl", hash = "sha256:e9db476e40a3aa184631d102b716a019f70837eb0fcdd5b5d1504f099f91359c"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d5a20ecc20a09a32c72128c43d7df23877a2469b3c17780ae83f9a9d55873c08"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b7b772f2f62a19a15ccb1b09c6c7754ca7430bb7e19d4ca4ff232958786873b"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af92326b90ea6fe4521cf6a5dfe450e21150393c573ef3ad9ee446f1009fbfbd"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b48554dad328e198a636f937e2f4c057aac8e4bfcb8467b10e0f5daa94307b17"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82304821e128d5453d1755d1c2f3d9cdf75e9def3517cf913b09df174e20283b"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2052357c5da195ede7dbc81a4e3408ebd6374a1ff1b86a0a9d8b8ce9562b32c3"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d60c6b47ccd6841c990418f7f4f58c28f7da9b07b81eaafc99b836cf351df1"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dc2194c917e4466cb604580b16e42286f04e3fe0424489459e68f0834f5c527"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb1e20965d759d89318cac7ff7eb045eb1fafcb5c3fa3047a23f6ae20c810ad7"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:74e959035da10a54e7a2eee28408eff672297ce96cdadd6f4a2f269a06e395c4"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4a441b23d9704f57eb34af6a300ae5c335b9e77e6a065ada36ca69d6fc582af9"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f59470c49114a5da064712a427317f2b1fa5bb89aa2dfd0e300f8289e26aec28"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:06191f5d0527e3224107aea260b5cffc8a78722e0efb4e793f0e45c449b813a2"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-win32.whl", hash = "sha256:3235c461904fe94b4f62fee78a1658c1316344411c81b02400c27d692a893f8f"},
+ {file = "Levenshtein-0.20.9-cp39-cp39-win_amd64.whl", hash = "sha256:8b852def43d165c2f2b468239d66b847d9e6f52a775fc657773ced04d26062bd"},
+ {file = "Levenshtein-0.20.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f674cc75f127692525563155e500a3fa16aaf24dafd33a9bcda46e2979f793a1"},
+ {file = "Levenshtein-0.20.9-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a34e3fd21acb31fcd29a0c8353dca74dfbb59957210a6f142505907a9dff3d59"},
+ {file = "Levenshtein-0.20.9-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0ddddf2beafd1a2e17a87f80be562a7f7478e6098ccfc15de4c879972dfa2f9"},
+ {file = "Levenshtein-0.20.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9649af1a896a4a7fc7f6f1fd093e8a92f463297f56c7bd0f8d7d16dfabeb236d"},
+ {file = "Levenshtein-0.20.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d7bd7f25336849027fbe5ed32b6ffd404436727d78a014e348dcd17347c73fd8"},
+ {file = "Levenshtein-0.20.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0371d996ae81089296f42b6e886c7bf138d1cb0f002b0c724a9e5d689b29b5a0"},
+ {file = "Levenshtein-0.20.9-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e00e2fda9f225b5f4537647f6195cf220d468532739d3390eaf082b1d76c87"},
+ {file = "Levenshtein-0.20.9-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1600f5ebe2f2aebf13e88cf488ec2e5ce25f7a42b5846335018693baf4ea63bd"},
+ {file = "Levenshtein-0.20.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bcd59fcf06aaedda98da185ec289dc2c2c9922ce789f6a9c101709d4a22cac9"},
+ {file = "Levenshtein-0.20.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1549e307028fa5c3a8cf28ae8bcb1f6072df2abf7f36b9d7adf7fd60690fe372"},
+ {file = "Levenshtein-0.20.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:795f2e95d09a33c66c73cd49be3ee632fb4b8c41be72c0cb8df29a329ce7d111"},
+ {file = "Levenshtein-0.20.9-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:726bfb361d3b6786bea31392752f0ffcca568db7dc3f1e274f1b529489b8ad05"},
+ {file = "Levenshtein-0.20.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0fd315132786375de532355fa06b2f11c4b4af5784b7e064dc54b6ee0c3281"},
+ {file = "Levenshtein-0.20.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0674bc0549d5ea9edb934b3b03a160a116cc410feb5739a51f9c4f618ee674e3"},
+ {file = "Levenshtein-0.20.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1ef8f3ecdfca5d6f0538226338d58617270439a1cc9b6cacb30a388984bb1608"},
+ {file = "Levenshtein-0.20.9.tar.gz", hash = "sha256:70a8ad5e28bb76d87da1eb3f31de940836596547d6d01317c2289f5b7cd0b0ea"},
+]
+
+[package.dependencies]
+rapidfuzz = ">=2.3.0,<3.0.0"
+
+[[package]]
+name = "rapidfuzz"
+version = "2.15.1"
+description = "rapid fuzzy string matching"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc0bc259ebe3b93e7ce9df50b3d00e7345335d35acbd735163b7c4b1957074d3"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d59fb3a410d253f50099d7063855c2b95df1ef20ad93ea3a6b84115590899f25"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c525a3da17b6d79d61613096c8683da86e3573e807dfaecf422eea09e82b5ba6"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4deae6a918ecc260d0c4612257be8ba321d8e913ccb43155403842758c46fbe"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2577463d10811386e704a3ab58b903eb4e2a31b24dfd9886d789b0084d614b01"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f67d5f56aa48c0da9de4ab81bffb310683cf7815f05ea38e5aa64f3ba4368339"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7927722ff43690e52b3145b5bd3089151d841d350c6f8378c3cfac91f67573a"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6534afc787e32c4104f65cdeb55f6abe4d803a2d0553221d00ef9ce12788dcde"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d0ae6ec79a1931929bb9dd57bc173eb5ba4c7197461bf69e3a34b6dd314feed2"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be7ccc45c4d1a7dfb595f260e8022a90c6cb380c2a346ee5aae93f85c96d362b"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ba013500a2b68c64b2aecc5fb56a2dad6c2872cf545a0308fd044827b6e5f6a"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4d9f7d10065f657f960b48699e7dddfce14ab91af4bab37a215f0722daf0d716"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e24a1b802cea04160b3fccd75d2d0905065783ebc9de157d83c14fb9e1c6ce2"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-win32.whl", hash = "sha256:dffdf03499e0a5b3442951bb82b556333b069e0661e80568752786c79c5b32de"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d150d90a7c6caae7962f29f857a4e61d42038cfd82c9df38508daf30c648ae7"},
+ {file = "rapidfuzz-2.15.1-cp310-cp310-win_arm64.whl", hash = "sha256:87c30e9184998ff6eb0fa9221f94282ce7c908fd0da96a1ef66ecadfaaa4cdb7"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6986413cb37035eb796e32f049cbc8c13d8630a4ac1e0484e3e268bb3662bd1b"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a72f26e010d4774b676f36e43c0fc8a2c26659efef4b3be3fd7714d3491e9957"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5cd54c98a387cca111b3b784fc97a4f141244bbc28a92d4bde53f164464112e"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7fac7c3da39f93e6b2ebe386ed0ffe1cefec91509b91857f6e1204509e931f"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f976e76ac72f650790b3a5402431612175b2ac0363179446285cb3c901136ca9"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abde47e1595902a490ed14d4338d21c3509156abb2042a99e6da51f928e0c117"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca8f1747007a3ce919739a60fa95c5325f7667cccf6f1c1ef18ae799af119f5e"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35da09ab9797b020d0d4f07a66871dfc70ea6566363811090353ea971748b5a"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3a769ca7580686a66046b77df33851b3c2d796dc1eb60c269b68f690f3e1b65"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d50622efefdb03a640a51a6123748cd151d305c1f0431af762e833d6ffef71f0"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b7461b0a7651d68bc23f0896bffceea40f62887e5ab8397bf7caa883592ef5cb"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:074ee9e17912e025c72a5780ee4c7c413ea35cd26449719cc399b852d4e42533"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7025fb105a11f503943f17718cdb8241ea3bb4d812c710c609e69bead40e2ff0"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-win32.whl", hash = "sha256:2084d36b95139413cef25e9487257a1cc892b93bd1481acd2a9656f7a1d9930c"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:5a738fcd24e34bce4b19126b92fdae15482d6d3a90bd687fd3d24ce9d28ce82d"},
+ {file = "rapidfuzz-2.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:dc3cafa68cfa54638632bdcadf9aab89a3d182b4a3f04d2cad7585ed58ea8731"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c53d57ba7a88f7bf304d4ea5a14a0ca112db0e0178fff745d9005acf2879f7d"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6ee758eec4cf2215dc8d8eafafcea0d1f48ad4b0135767db1b0f7c5c40a17dd"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d93ba3ae59275e7a3a116dac4ffdb05e9598bf3ee0861fecc5b60fb042d539e"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c3ff75e647908ddbe9aa917fbe39a112d5631171f3fcea5809e2363e525a59d"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d89c421702474c6361245b6b199e6e9783febacdbfb6b002669e6cb3ef17a09"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f69e6199fec0f58f9a89afbbaea78d637c7ce77f656a03a1d6ea6abdc1d44f8"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:41dfea282844d0628279b4db2929da0dacb8ac317ddc5dcccc30093cf16357c1"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2dd03477feefeccda07b7659dd614f6738cfc4f9b6779dd61b262a73b0a9a178"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5efe035aa76ff37d1b5fa661de3c4b4944de9ff227a6c0b2e390a95c101814c0"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ed2cf7c69102c7a0a06926d747ed855bc836f52e8d59a5d1e3adfd980d1bd165"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a0e441d4c2025110ec3eba5d54f11f78183269a10152b3a757a739ffd1bb12bf"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-win32.whl", hash = "sha256:a4a54efe17cc9f53589c748b53f28776dfdfb9bc83619685740cb7c37985ac2f"},
+ {file = "rapidfuzz-2.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bb8318116ecac4dfb84841d8b9b461f9bb0c3be5b616418387d104f72d2a16d1"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e9296c530e544f68858c3416ad1d982a1854f71e9d2d3dcedb5b216e6d54f067"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c4bcdb9238f11f8c4eba1b898937f09b92280d6f900023a8216008f299b41a"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb40a279e134bb3fef099a8b58ed5beefb201033d29bdac005bddcdb004ef71"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7381c11cb590bbd4e6f2d8779a0b34fdd2234dfa13d0211f6aee8ca166d9d05"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfdcdedfd12a0077193f2cf3626ff6722c5a184adf0d2d51f1ec984bf21c23c3"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85bece1ec59bda8b982bd719507d468d4df746dfb1988df11d916b5e9fe19e8"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b393f4a1eaa6867ffac6aef58cfb04bab2b3d7d8e40b9fe2cf40dd1d384601"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53de456ef020a77bf9d7c6c54860a48e2e902584d55d3001766140ac45c54bc7"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2492330bc38b76ed967eab7bdaea63a89b6ceb254489e2c65c3824efcbf72993"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:099e4c6befaa8957a816bdb67ce664871f10aaec9bebf2f61368cf7e0869a7a1"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:46599b2ad4045dd3f794a24a6db1e753d23304699d4984462cf1ead02a51ddf3"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:591f19d16758a3c55c9d7a0b786b40d95599a5b244d6eaef79c7a74fcf5104d8"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed17359061840eb249f8d833cb213942e8299ffc4f67251a6ed61833a9f2ea20"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-win32.whl", hash = "sha256:aa1e5aad325168e29bf8e17006479b97024aa9d2fdbe12062bd2f8f09080acf8"},
+ {file = "rapidfuzz-2.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:c2bb68832b140c551dbed691290bef4ee6719d4e8ce1b7226a3736f61a9d1a83"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fac40972cf7b6c14dded88ae2331eb50dfbc278aa9195473ef6fc6bfe49f686"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0e456cbdc0abf39352800309dab82fd3251179fa0ff6573fa117f51f4e84be8"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:22b9d22022b9d09fd4ece15102270ab9b6a5cfea8b6f6d1965c1df7e3783f5ff"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46754fe404a9a6f5cbf7abe02d74af390038d94c9b8c923b3f362467606bfa28"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91abb8bf7610efe326394adc1d45e1baca8f360e74187f3fa0ef3df80cdd3ba6"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e40a2f60024f9d3c15401e668f732800114a023f3f8d8c40f1521a62081ff054"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a48ee83916401ac73938526d7bd804e01d2a8fe61809df7f1577b0b3b31049a3"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71580052f9dbac443c02f60484e5a2e5f72ad4351b84b2009fbe345b1f38422"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82b86d5b8c1b9bcbc65236d75f81023c78d06a721c3e0229889ff4ed5c858169"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fc4528b7736e5c30bc954022c2cf410889abc19504a023abadbc59cdf9f37cae"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e1e0e569108a5760d8f01d0f2148dd08cc9a39ead79fbefefca9e7c7723c7e88"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:94e1c97f0ad45b05003806f8a13efc1fc78983e52fa2ddb00629003acf4676ef"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47e81767a962e41477a85ad7ac937e34d19a7d2a80be65614f008a5ead671c56"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-win32.whl", hash = "sha256:79fc574aaf2d7c27ec1022e29c9c18f83cdaf790c71c05779528901e0caad89b"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:f3dd4bcef2d600e0aa121e19e6e62f6f06f22a89f82ef62755e205ce14727874"},
+ {file = "rapidfuzz-2.15.1-cp39-cp39-win_arm64.whl", hash = "sha256:cac095cbdf44bc286339a77214bbca6d4d228c9ebae3da5ff6a80aaeb7c35634"},
+ {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b89d1126be65c85763d56e3b47d75f1a9b7c5529857b4d572079b9a636eaa8a7"},
+ {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7460e91168229768be882ea365ba0ac7da43e57f9416e2cfadc396a7df3c2"},
+ {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c33c03e7092642c38f8a15ca2d8fc38da366f2526ec3b46adf19d5c7aa48ba"},
+ {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040faca2e26d9dab5541b45ce72b3f6c0e36786234703fc2ac8c6f53bb576743"},
+ {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6e2a3b23e1e9aa13474b3c710bba770d0dcc34d517d3dd6f97435a32873e3f28"},
+ {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e597b9dfd6dd180982684840975c458c50d447e46928efe3e0120e4ec6f6686"},
+ {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d14752c9dd2036c5f36ebe8db5f027275fa7d6b3ec6484158f83efb674bab84e"},
+ {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558224b6fc6124d13fa32d57876f626a7d6188ba2a97cbaea33a6ee38a867e31"},
+ {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c89cfa88dc16fd8c9bcc0c7f0b0073f7ef1e27cceb246c9f5a3f7004fa97c4d"},
+ {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:509c5b631cd64df69f0f011893983eb15b8be087a55bad72f3d616b6ae6a0f96"},
+ {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0f73a04135a03a6e40393ecd5d46a7a1049d353fc5c24b82849830d09817991f"},
+ {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99d53138a2dfe8ada67cb2855719f934af2733d726fbf73247844ce4dd6dd5"},
+ {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f01fa757f0fb332a1f045168d29b0d005de6c39ee5ce5d6c51f2563bb53c601b"},
+ {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60368e1add6e550faae65614844c43f8a96e37bf99404643b648bf2dba92c0fb"},
+ {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785744f1270828cc632c5a3660409dee9bcaac6931a081bae57542c93e4d46c4"},
+ {file = "rapidfuzz-2.15.1.tar.gz", hash = "sha256:d62137c2ca37aea90a11003ad7dc109c8f1739bfbe5a9a217f3cdb07d7ac00f6"},
+]
+
+[package.extras]
+full = ["numpy"]
+
+[[package]]
+name = "textwrap3"
+version = "0.9.2"
+description = "textwrap from Python 3.6 backport (plus a few tweaks)"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"},
+ {file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"},
+]
+
+[[package]]
+name = "watchdog"
+version = "2.3.1"
+description = "Filesystem events monitoring"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "watchdog-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1f1200d4ec53b88bf04ab636f9133cb703eb19768a39351cee649de21a33697"},
+ {file = "watchdog-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:564e7739abd4bd348aeafbf71cc006b6c0ccda3160c7053c4a53b67d14091d42"},
+ {file = "watchdog-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95ad708a9454050a46f741ba5e2f3468655ea22da1114e4c40b8cbdaca572565"},
+ {file = "watchdog-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a073c91a6ef0dda488087669586768195c3080c66866144880f03445ca23ef16"},
+ {file = "watchdog-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa8b028750b43e80eea9946d01925168eeadb488dfdef1d82be4b1e28067f375"},
+ {file = "watchdog-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964fd236cd443933268ae49b59706569c8b741073dbfd7ca705492bae9d39aab"},
+ {file = "watchdog-2.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:91fd146d723392b3e6eb1ac21f122fcce149a194a2ba0a82c5e4d0ee29cd954c"},
+ {file = "watchdog-2.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efe3252137392a471a2174d721e1037a0e6a5da7beb72a021e662b7000a9903f"},
+ {file = "watchdog-2.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85bf2263290591b7c5fa01140601b64c831be88084de41efbcba6ea289874f44"},
+ {file = "watchdog-2.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f2df370cd8e4e18499dd0bfdef476431bcc396108b97195d9448d90924e3131"},
+ {file = "watchdog-2.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ea5d86d1bcf4a9d24610aa2f6f25492f441960cf04aed2bd9a97db439b643a7b"},
+ {file = "watchdog-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f5d0f7eac86807275eba40b577c671b306f6f335ba63a5c5a348da151aba0fc"},
+ {file = "watchdog-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b848c71ef2b15d0ef02f69da8cc120d335cec0ed82a3fa7779e27a5a8527225"},
+ {file = "watchdog-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d9878be36d2b9271e3abaa6f4f051b363ff54dbbe7e7df1af3c920e4311ee43"},
+ {file = "watchdog-2.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cd61f98cb37143206818cb1786d2438626aa78d682a8f2ecee239055a9771d5"},
+ {file = "watchdog-2.3.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3d2dbcf1acd96e7a9c9aefed201c47c8e311075105d94ce5e899f118155709fd"},
+ {file = "watchdog-2.3.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03f342a9432fe08107defbe8e405a2cb922c5d00c4c6c168c68b633c64ce6190"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7a596f9415a378d0339681efc08d2249e48975daae391d58f2e22a3673b977cf"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:0e1dd6d449267cc7d6935d7fe27ee0426af6ee16578eed93bacb1be9ff824d2d"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_i686.whl", hash = "sha256:7a1876f660e32027a1a46f8a0fa5747ad4fcf86cb451860eae61a26e102c8c79"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:2caf77ae137935c1466f8cefd4a3aec7017b6969f425d086e6a528241cba7256"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:53f3e95081280898d9e4fc51c5c69017715929e4eea1ab45801d5e903dd518ad"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:9da7acb9af7e4a272089bd2af0171d23e0d6271385c51d4d9bde91fe918c53ed"},
+ {file = "watchdog-2.3.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8a4d484e846dcd75e96b96d80d80445302621be40e293bfdf34a631cab3b33dc"},
+ {file = "watchdog-2.3.1-py3-none-win32.whl", hash = "sha256:a74155398434937ac2780fd257c045954de5b11b5c52fc844e2199ce3eecf4cf"},
+ {file = "watchdog-2.3.1-py3-none-win_amd64.whl", hash = "sha256:5defe4f0918a2a1a4afbe4dbb967f743ac3a93d546ea4674567806375b024adb"},
+ {file = "watchdog-2.3.1-py3-none-win_ia64.whl", hash = "sha256:4109cccf214b7e3462e8403ab1e5b17b302ecce6c103eb2fc3afa534a7f27b96"},
+ {file = "watchdog-2.3.1.tar.gz", hash = "sha256:d9f9ed26ed22a9d331820a8432c3680707ea8b54121ddcc9dc7d9f2ceeb36906"},
+]
+
+[package.extras]
+watchmedo = ["PyYAML (>=3.10)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.7"
+content-hash = "f48df5c526c5e9e9b6c8dd83bb06b628c347f616a66670800e3032a83ba50c08"
diff --git a/tools/asm-differ/test.py b/tools/asm-differ/test.py
new file mode 100644
index 0000000..c0c2a9c
--- /dev/null
+++ b/tools/asm-differ/test.py
@@ -0,0 +1,185 @@
+import unittest
+import diff
+import json
+
+class TestSh2(unittest.TestCase):
+ def get_config(self):
+ arch = diff.get_arch('sh2')
+ formatter = diff.JsonFormatter(arch_str='sh2')
+ config = diff.Config(
+ arch=arch,
+ diff_obj=True,
+ objfile='',
+ make=False,
+ source_old_binutils=True,
+ diff_section='.text',
+ inlines=False,
+ max_function_size_lines=25000,
+ max_function_size_bytes=100000,
+ formatter=formatter,
+ diff_mode=diff.DiffMode.NORMAL,
+ base_shift=0,
+ skip_lines=0,
+ compress=None,
+ show_rodata_refs=True,
+ show_branches=True,
+ show_line_numbers=False,
+ show_source=False,
+ stop_at_ret=False,
+ ignore_large_imms=False,
+ ignore_addr_diffs=True,
+ algorithm='levenshtein',
+ reg_categories={})
+ return config
+
+ # check that comment <> regex has ? to avoid "<func_060E8780+0x44>,r1 ! 60e87d0"
+ # all being a comment for:
+ # mov.l 44 <func_060E8780+0x44>,r1 ! 60e87d0
+ def test_sh2_comment(self):
+ # parser specifically looks for tabs so make sure they are represented
+
+ # 16: d1 0b mov.l 44 <func_060E8780+0x44>,r1 ! 60e87d0
+ sh2_theirs = " 16:\td1 0b \tmov.l\t44 <func_060E8780+0x44>,r1\t! 60e87d0\n"
+
+ # 16: d1 0b mov.l 44 <_func_060E8780+0x44>,r1 ! 0 <_func_060E8780>
+ sh2_ours = " 16:\td1 0b \tmov.l\t44 <_func_060E8780+0x44>,r1\t! 0 <_func_060E8780>\n"
+
+ config = self.get_config()
+ display = diff.Display(sh2_theirs, sh2_ours, config)
+ loaded = json.loads(display.run_diff()[0])
+
+ curr = loaded['rows'][0]['current']['src_comment']
+
+ assert(curr != "<_func_060E8780+0x44>,r1 ! 0 <_func_060E8780>")
+ assert(curr == "<_func_060E8780+0x44>")
+
+ def test_sh2_immediates(self):
+ # test parsing these immediates
+ # func_0606B760():
+ # 0: ec 01 mov #1,r12
+ # 2: 71 01 add #1,r1
+ # 4: ec ff mov #-1,r12
+ # 6: 71 ff add #-1,r1
+ # 8: ec 7f mov #127,r12
+ # a: 71 7f add #127,r1
+ # c: ec 80 mov #-128,r12
+ # e: 71 80 add #-128,r1
+ sh2_theirs = 'func_0606B760():\n 0:\tec 01 \tmov\t#1,r12\n 2:\t71 01 \tadd\t#1,r1\n 4:\tec ff \tmov\t#-1,r12\n 6:\t71 ff \tadd\t#-1,r1\n 8:\tec 7f \tmov\t#127,r12\n a:\t71 7f \tadd\t#127,r1\n c:\tec 80 \tmov\t#-128,r12\n e:\t71 80 \tadd\t#-128,r1'
+
+ # just diff with self
+ sh2_ours = sh2_theirs
+
+ config = self.get_config()
+ display = diff.Display(sh2_theirs, sh2_ours, config)
+ loaded = json.loads(display.run_diff()[0])
+
+ expected = [
+ "0: mov #0x1,r12",
+ "2: add #0x1,r1",
+ "4: mov #0xff,r12",
+ "6: add #0xff,r1",
+ "8: mov #0x7f,r12",
+ "a: add #0x7f,r1",
+ "c: mov #0x80,r12",
+ "e: add #0x80,r1"
+ ]
+
+ i = 0
+ for text in loaded['rows']:
+ assert(text['base']['text'][0]['text'] == expected[i])
+ i += 1
+
+ def test_more_sh2_immediates(self):
+ # test that the re_int regex is able to catch all these "boundary" numbers
+ # since we have to match 0-9 one digit at a time
+ # 0: 71 00 add #0,r1
+ # 2: 71 01 add #1,r1
+ # 4: 71 09 add #9,r1
+ # 6: 71 0a add #10,r1
+ # 8: 71 0b add #11,r1
+ # a: 71 13 add #19,r1
+ # c: 71 64 add #100,r1
+ # e: 71 65 add #101,r1
+ # 10: 71 6d add #109,r1
+ # 12: 71 6f add #111,r1
+ # 14: 71 77 add #119,r1
+ # 16: 71 f7 add #-9,r1
+ # 18: 71 f6 add #-10,r1
+ # 1a: 71 f5 add #-11,r1
+ # 1c: 71 ed add #-19,r1
+ # 1e: 71 9c add #-100,r1
+ # 20: 71 9b add #-101,r1
+ # 22: 71 93 add #-109,r1
+ # 24: 71 91 add #-111,r1
+ # 26: 71 89 add #-119,r1
+ sh2_theirs = 'func_0606B760():\n 0:\t71 00 \tadd\t#0,r1\n 2:\t71 01 \tadd\t#1,r1\n 4:\t71 09 \tadd\t#9,r1\n 6:\t71 0a \tadd\t#10,r1\n 8:\t71 0b \tadd\t#11,r1\n a:\t71 13 \tadd\t#19,r1\n c:\t71 64 \tadd\t#100,r1\n e:\t71 65 \tadd\t#101,r1\n 10:\t71 6d \tadd\t#109,r1\n 12:\t71 6f \tadd\t#111,r1\n 14:\t71 77 \tadd\t#119,r1\n 16:\t71 f7 \tadd\t#-9,r1\n 18:\t71 f6 \tadd\t#-10,r1\n 1a:\t71 f5 \tadd\t#-11,r1\n 1c:\t71 ed \tadd\t#-19,r1\n 1e:\t71 9c \tadd\t#-100,r1\n 20:\t71 9b \tadd\t#-101,r1\n 22:\t71 93 \tadd\t#-109,r1\n 24:\t71 91 \tadd\t#-111,r1\n 26:\t71 89 \tadd\t#-119,r1'
+
+ # just diff with self
+ sh2_ours = sh2_theirs
+
+ config = self.get_config()
+ display = diff.Display(sh2_theirs, sh2_ours, config)
+ loaded = json.loads(display.run_diff()[0])
+
+ expected = [
+ "0: add #0x0,r1",
+ "2: add #0x1,r1",
+ "4: add #0x9,r1",
+ "6: add #0xa,r1",
+ "8: add #0xb,r1",
+ "a: add #0x13,r1",
+ "c: add #0x64,r1",
+ "e: add #0x65,r1",
+ "10: add #0x6d,r1",
+ "12: add #0x6f,r1",
+ "14: add #0x77,r1",
+ "16: add #0xf7,r1",
+ "18: add #0xf6,r1",
+ "1a: add #0xf5,r1",
+ "1c: add #0xed,r1",
+ "1e: add #0x9c,r1",
+ "20: add #0x9b,r1",
+ "22: add #0x93,r1",
+ "24: add #0x91,r1",
+ "26: add #0x89,r1"
+ ]
+
+ i = 0
+ for text in loaded['rows']:
+ assert(text['base']['text'][0]['text'] == expected[i])
+ i += 1
+
+ def test_branch(self):
+ # test that bt.s and bra get ~>
+ # func():
+ # 0: 8d 02 bt.s 8 <lab_0606B780>
+ # 2: 6e f3 mov r15,r14
+ # 4: a0 01 bra a <lab_0606B8E0>
+ # 6: 00 09 nop
+
+ # 00000008 <lab_0606B780>:
+ # lab_0606B780():
+ # 8: db 32 mov.l d4 <lab_0606B8E0+0xca>,r11
+
+ # 0000000a <lab_0606B8E0>:
+ # lab_0606B8E0():
+ # a: 00 0b rts
+ # c: 00 09 nop
+ sh2_theirs = 'func():\n 0:\t8d 02 \tbt.s\t8 <lab_0606B780>\n 2:\t6e f3 \tmov\tr15,r14\n 4:\ta0 01 \tbra\ta <lab_0606B8E0>\n 6:\t00 09 \tnop\t\n\n00000008 <lab_0606B780>:\nlab_0606B780():\n 8:\tdb 32 \tmov.l\td4 <lab_0606B8E0+0xca>,r11\n\n0000000a <lab_0606B8E0>:\nlab_0606B8E0():\n a:\t00 0b \trts\t\n c:\t00 09 \tnop\t'
+ sh2_ours = sh2_theirs
+
+ config = self.get_config()
+ display = diff.Display(sh2_theirs, sh2_ours, config)
+ loaded = json.loads(display.run_diff()[0])
+
+ # bt.s 8
+ print(loaded['rows'][0]['base']['text'][1]['text'] == '~>')
+ print(loaded['rows'][0]['base']['text'][1]['key'] == '8')
+
+ # bra a
+ print(loaded['rows'][2]['base']['text'][1]['text'] == '~>')
+ print(loaded['rows'][2]['base']['text'][1]['key'] == '10')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/asm-processor/.gitrepo b/tools/asm-processor/.gitrepo
index ef2baa1..fd4c97a 100644
--- a/tools/asm-processor/.gitrepo
+++ b/tools/asm-processor/.gitrepo
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:simonlindholm/asm-processor.git
branch = main
- commit = bbd86ea1faf84e6a7a0e101ab8068a00a3dfb2fc
- parent = 75437386de04179489f4fd5fb345d122ff8a2dc0
+ commit = 6dd4d3adcbfb3ee607b9a1e2bba3502d27217d44
+ parent = 39a2e453bb882b2498c7c4aa9073a9f85d048f0c
method = merge
cmdver = 0.4.3
diff --git a/tools/asm-processor/asm_processor.py b/tools/asm-processor/asm_processor.py
index 0d62bc3..23f6102 100644
--- a/tools/asm-processor/asm_processor.py
+++ b/tools/asm-processor/asm_processor.py
@@ -605,11 +605,11 @@ class GlobalAsmBlock:
line = re.sub(r'^[a-zA-Z0-9_]+:\s*', '', line)
changed_section = False
emitting_double = False
- if line.startswith('glabel ') and self.cur_section == '.text':
+ if (line.startswith('glabel ') or line.startswith('jlabel ')) and self.cur_section == '.text':
self.text_glabels.append(line.split()[1])
if not line:
pass # empty line
- elif line.startswith('glabel ') or line.startswith('dlabel ') or line.startswith('endlabel ') or (' ' not in line and line.endswith(':')):
+ elif line.startswith('glabel ') or line.startswith('dlabel ') or line.startswith('jlabel ') or line.startswith('endlabel ') or (' ' not in line and line.endswith(':')):
pass # label
elif line.startswith('.section') or line in ['.text', '.data', '.rdata', '.rodata', '.bss', '.late_rodata']:
# section change
@@ -651,17 +651,22 @@ class GlobalAsmBlock:
emitting_double = True
elif line.startswith('.space'):
self.add_sized(int(line.split()[1], 0), real_line)
- elif line.startswith('.balign') or line.startswith('.align'):
+ elif line.startswith('.balign'):
align = int(line.split()[1])
if align != 4:
self.fail("only .balign 4 is supported", real_line)
self.align4()
+ elif line.startswith('.align'):
+ align = int(line.split()[1])
+ if align != 2:
+ self.fail("only .align 2 is supported", real_line)
+ self.align4()
elif line.startswith('.asci'):
z = (line.startswith('.asciz') or line.startswith('.asciiz'))
self.add_sized(self.count_quoted_size(line, z, real_line, output_enc), real_line)
elif line.startswith('.byte'):
self.add_sized(len(line.split(',')), real_line)
- elif line.startswith('.half'):
+ elif line.startswith('.half') or line.startswith('.hword'):
self.align2()
self.add_sized(2*len(line.split(',')), real_line)
elif line.startswith('.'):
diff --git a/tools/asm-processor/prelude.inc b/tools/asm-processor/prelude.inc
index 3e58ff1..9bc8939 100644
--- a/tools/asm-processor/prelude.inc
+++ b/tools/asm-processor/prelude.inc
@@ -1,11 +1,21 @@
.set noat
.set noreorder
.set gp=64
+
.macro glabel label
.global \label
\label:
.endm
+.macro dlabel label
+ .global \label
+ \label:
+.endm
+
+.macro jlabel label
+ \label:
+.endm
+
# Float register aliases (o32 ABI, odd ones are rarely used)
diff --git a/tools/splat/.github/workflows/mypy.yml b/tools/splat/.github/workflows/mypy.yml
index d5bb1fa..98c16b8 100644
--- a/tools/splat/.github/workflows/mypy.yml
+++ b/tools/splat/.github/workflows/mypy.yml
@@ -24,3 +24,16 @@ jobs:
run: mypy --show-column-numbers --hide-error-context .
- name: black
run: black --check .
+ unit_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.8
+ - name: Install Dependencies
+ run: pip install -r requirements.txt
+ - name: Run unit tests
+ run: sh run_tests.sh
diff --git a/tools/splat/.gitrepo b/tools/splat/.gitrepo
index 5899cd2..98978cc 100644
--- a/tools/splat/.gitrepo
+++ b/tools/splat/.gitrepo
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:ethteck/splat.git
branch = master
- commit = 4fec014c10aa1fc90d200fe6722000425bf7d43f
- parent = 6a5e64cc4c26398cc607bb02cdc12a056ddf42d1
+ commit = 7869ef2e51a5974fb8e583e45e8b47793d3fecd2
+ parent = c567c13e68bc84d3e6032886ec5bfd594363f563
method = merge
cmdver = 0.4.3
diff --git a/tools/splat/CHANGELOG.md b/tools/splat/CHANGELOG.md
index 0fad6fb..a8ccf40 100644
--- a/tools/splat/CHANGELOG.md
+++ b/tools/splat/CHANGELOG.md
@@ -1,5 +1,59 @@
# splat Release Notes
+### 0.15.2
+* Various cleanup and fixes to support more liberal use of `auto` for rom addresses
+
+### 0.15.1
+* Made some modifications such that linker object paths should be simpler in some circumstances
+
+### 0.15.0
+
+* New options:
+ * `data_string_encoding` can be set at the global level (or `str_encoding` at the segment level) to specify the encoding using when guessing and disassembling strings the the data section. In spimdisasm this value defaults to ASCII.
+ * `rodata_string_guesser_level` changes the behaviour of the rodata string guesser. A higher value means more agressive guessing, while 0 and negative means no guessing at all. Even if the guesser feature is disabled, symbols manually marked as strings in the symbol_addrs.txt file will still be disassembled as strings. In spimdisasm this value defaults to 1.
+ * level 0: Completely disable the guessing feature.
+ * level 1: The most conservative guessing level. Imposes the following restrictions:
+ * Do not try to guess if the user provided a type for the symbol.
+ * Do no try to guess if type information for the symbol can be inferred by other means.
+ * A string symbol must be referenced only once.
+ * Strings must not be empty.
+ * level 2: A string no longer needs to be referenced only once to be considered a possible string. This can happen because of a deduplication optimization.
+ * level 3: Empty strings are allowed.
+ * level 4: Symbols with autodetected type information but no user type information can still be guessed as strings.
+ * `data_string_guesser_level` is similar to `rodata_string_guesser_level`, but for the data section instead. In spimdisasm this value defaults to 2.
+ * `asm_emit_size_directive` toggles the size directived emitted by the disassembler. In spimdisasm this defaults to True.
+
+### 0.14.1
+
+* Fix bug, cod cleanup
+
+### 0.14.0
+
+* Add support for PSX's GTE instruction set
+
+### 0.13.10
+
+* New option `disasm_unknown` (False by default)
+ * If enabled it tells the disassembler to try disassembling functions with unknown instructions instead of falling back to disassembling as raw data
+
+### 0.13.9
+
+* New segment option `linker_entry` (true by default).
+ * If disabled, this segment will not produce entries in the linker script.
+
+### 0.13.8
+
+* New option `segment_end_before_align`.
+ * If enabled, the end symbol for each segment will be placed before the alignment directive for the segment
+
+### 0.13.7
+
+* Severely sped-up linker entry writing by using a dict instead of a list. Symbol headers will no longer be in any specific order (which shouldn't matter, because they're headers).
+
+### 0.13.6
+
+* Changed CI image processing so that their data is fetched during the scan phase, supporting palettes that come before CI images.
+
### 0.13.5
* An error will be produced if a symbol is declared with an unknown type in the symbol_addrs file.
@@ -13,7 +67,7 @@
### 0.13.3
-* Added a new symbol_addrs attribute `appears_after_overlays_addr:0x1234` which will modify the linker script such that the symbol's address is equal to the value of the end of the longest overlay starting with address 0x1234. It achieve this by writing a series of sym = MAX(sym, seg_vram_END) statements into the linker script. For some games, it's feasible to manually create such statements, but for games with hundreds of overlays at the same address, this is very tedious and prone to error. The new attribute allows you to have peace of mind that the symbol will end up after all of these overlays.
+* Added a new symbol_addrs attribute `appears_after_overlays_addr:0x1234` which will modify the linker script such that the symbol's address is equal to the value of the end of the longest overlay starting with address 0x1234. It achieves this by writing a series of sym = MAX(sym, seg_vram_END) statements into the linker script. For some games, it's feasible to manually create such statements, but for games with hundreds of overlays at the same address, this is very tedious and prone to error. The new attribute allows you to have peace of mind that the symbol will end up after all of these overlays.
### 0.13.2
@@ -34,7 +88,7 @@
### 0.12.14
* New option: `pair_rodata_to_text`.
- * If enabled, splat will try to find to which text segment an unpaired rodata segment belongs and it will hint it to the user.
+ * If enabled, splat will try to find to which text segment an unpaired rodata segment belongs, and it will hint it to the user.
### 0.12.13
@@ -64,7 +118,7 @@
### 0.12.8
* The gfx and vtx segments now have a `data_only` option, which, if enabled, will emit only the plain data for the type and omit the enclosing symbol definition. This mode is useful when you want to manually declare the symbol and then #include the extracted data within the declaration.
-* The gfx segment has a method, `format_sym_name()`, which will allow custom overriding of the output of symbol names by extending the `gfx` segment. For example, this can be used to transform context-specific symbol names like mac_01_vtx into N(vtx), where N() is a macro that applies the current "namespace" to the symbol. Paper Mario plans to use this so we can extract an asset once and then #include it in multiple places, while giving each inclusion unique symbol names for each component.
+* The gfx segment has a method, `format_sym_name()`, which will allow custom overriding of the output of symbol names by extending the `gfx` segment. For example, this can be used to transform context-specific symbol names like mac_01_vtx into N(vtx), where N() is a macro that applies the current "namespace" to the symbol. Paper Mario plans to use this, so we can extract an asset once and then #include it in multiple places, while giving each inclusion unique symbol names for each component.
### 0.12.7
@@ -88,7 +142,7 @@
* Update minimal spimdisasm version to 1.7.1.
* Fix spimdisasm>=1.7.0 non being able to see symbols which only are referenced by other data symbols.
-* An check was added to prevent segments marked with `exclusive_ram_id` have a vram address range which overlaps with segments not marked with said tag. If this happens it will be warned to the user.
+* A check was added to prevent segments marked with `exclusive_ram_id` have a vram address range which overlaps with segments not marked with said tag. If this happens it will be warned to the user.
### 0.12.4
diff --git a/tools/splat/Dockerfile b/tools/splat/Dockerfile
new file mode 100644
index 0000000..0c84c24
--- /dev/null
+++ b/tools/splat/Dockerfile
@@ -0,0 +1,8 @@
+FROM ubuntu:22.04
+RUN apt-get update
+RUN apt install -y binutils-mips-linux-gnu
+ADD https://github.com/decompals/mips-binutils-2.6/releases/download/main/binutils-2.6-linux.tar.gz .
+ADD https://github.com/decompals/mips-gcc-2.7.2/releases/download/main/gcc-2.7.2-linux.tar.gz .
+RUN mkdir -p ./gcc-2.7.2 && \
+ tar -xvf gcc-2.7.2-linux.tar.gz -C ./gcc-2.7.2 && \
+ tar -xvf binutils-2.6-linux.tar.gz -C ./gcc-2.7.2
diff --git a/tools/splat/disassembler/__init__.py b/tools/splat/disassembler/__init__.py
new file mode 100644
index 0000000..09c2ca5
--- /dev/null
+++ b/tools/splat/disassembler/__init__.py
@@ -0,0 +1,2 @@
+from .disassembler import Disassembler
+from .spimdisasm_disassembler import SpimdisasmDisassembler
diff --git a/tools/splat/disassembler/disassembler.py b/tools/splat/disassembler/disassembler.py
new file mode 100644
index 0000000..e64aa26
--- /dev/null
+++ b/tools/splat/disassembler/disassembler.py
@@ -0,0 +1,17 @@
+from abc import ABC, abstractmethod
+from util.options import SplatOpts
+from typing import Set
+
+
+class Disassembler(ABC):
+ @abstractmethod
+ def configure(self, options: SplatOpts):
+ raise NotImplementedError("configure")
+
+ @abstractmethod
+ def check_version(self, skip_version_check: bool, splat_version: str):
+ raise NotImplementedError("check_version")
+
+ @abstractmethod
+ def known_types(self) -> Set[str]:
+ raise NotImplementedError("known_types")
diff --git a/tools/splat/disassembler/disassembler_instance.py b/tools/splat/disassembler/disassembler_instance.py
new file mode 100644
index 0000000..988fe3c
--- /dev/null
+++ b/tools/splat/disassembler/disassembler_instance.py
@@ -0,0 +1,26 @@
+from .disassembler import Disassembler
+from .spimdisasm_disassembler import SpimdisasmDisassembler
+from .null_disassembler import NullDisassembler
+
+__instance: Disassembler = NullDisassembler()
+__initialized = False
+
+
+def create_disassembler_instance(platform: str):
+ global __instance
+ global __initialized
+ if platform in ["n64", "psx", "ps2"]:
+ __instance = SpimdisasmDisassembler()
+ __initialized = True
+ return
+
+ raise NotImplementedError("No disassembler for requested platform")
+
+
+def get_instance() -> Disassembler:
+ global __instance
+ global __initialized
+ if not __initialized:
+ raise Exception("Disassembler instance not initialized")
+ return None
+ return __instance
diff --git a/tools/splat/disassembler/null_disassembler.py b/tools/splat/disassembler/null_disassembler.py
new file mode 100644
index 0000000..046725e
--- /dev/null
+++ b/tools/splat/disassembler/null_disassembler.py
@@ -0,0 +1,14 @@
+from disassembler import disassembler
+from util.options import SplatOpts
+from typing import Set
+
+
+class NullDisassembler(disassembler.Disassembler):
+ def configure(self, opts: SplatOpts):
+ pass
+
+ def check_version(self, skip_version_check: bool, splat_version: str):
+ pass
+
+ def known_types(self) -> Set[str]:
+ return set()
diff --git a/tools/splat/disassembler/spimdisasm_disassembler.py b/tools/splat/disassembler/spimdisasm_disassembler.py
new file mode 100644
index 0000000..399b4e3
--- /dev/null
+++ b/tools/splat/disassembler/spimdisasm_disassembler.py
@@ -0,0 +1,118 @@
+from disassembler import disassembler
+import spimdisasm
+import rabbitizer
+from util import log, compiler
+from util.options import SplatOpts
+from typing import Set
+
+
+class SpimdisasmDisassembler(disassembler.Disassembler):
+ # This value should be kept in sync with the version listed on requirements.txt
+ SPIMDISASM_MIN = (1, 15, 0)
+
+ def configure(self, opts: SplatOpts):
+ # Configure spimdisasm
+ spimdisasm.common.GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET = True
+ spimdisasm.common.GlobalConfig.TRUST_USER_FUNCTIONS = True
+ spimdisasm.common.GlobalConfig.TRUST_JAL_FUNCTIONS = True
+ spimdisasm.common.GlobalConfig.GLABEL_ASM_COUNT = False
+
+ if opts.rom_address_padding:
+ spimdisasm.common.GlobalConfig.ASM_COMMENT_OFFSET_WIDTH = 6
+ else:
+ spimdisasm.common.GlobalConfig.ASM_COMMENT_OFFSET_WIDTH = 0
+
+ # spimdisasm is not performing any analyzis on non-text sections so enabling this options is pointless
+ spimdisasm.common.GlobalConfig.AUTOGENERATED_NAMES_BASED_ON_SECTION_TYPE = False
+ spimdisasm.common.GlobalConfig.AUTOGENERATED_NAMES_BASED_ON_DATA_TYPE = False
+
+ spimdisasm.common.GlobalConfig.SYMBOL_FINDER_FILTERED_ADDRESSES_AS_HILO = False
+
+ if opts.rodata_string_guesser_level is not None:
+ spimdisasm.common.GlobalConfig.RODATA_STRING_GUESSER_LEVEL = (
+ opts.rodata_string_guesser_level
+ )
+
+ if opts.data_string_guesser_level is not None:
+ spimdisasm.common.GlobalConfig.DATA_STRING_GUESSER_LEVEL = (
+ opts.data_string_guesser_level
+ )
+
+ rabbitizer.config.regNames_userFpcCsr = False
+ rabbitizer.config.regNames_vr4300Cop0NamedRegisters = False
+
+ rabbitizer.config.misc_opcodeLJust = opts.mnemonic_ljust - 1
+
+ rabbitizer.config.regNames_gprAbiNames = rabbitizer.Abi.fromStr(
+ opts.mips_abi_gpr
+ )
+ rabbitizer.config.regNames_fprAbiNames = rabbitizer.Abi.fromStr(
+ opts.mips_abi_float_regs
+ )
+
+ if opts.endianness == "big":
+ spimdisasm.common.GlobalConfig.ENDIAN = spimdisasm.common.InputEndian.BIG
+ else:
+ spimdisasm.common.GlobalConfig.ENDIAN = spimdisasm.common.InputEndian.LITTLE
+
+ rabbitizer.config.pseudos_pseudoMove = False
+
+ selected_compiler = opts.compiler
+ if selected_compiler == compiler.SN64:
+ rabbitizer.config.regNames_namedRegisters = False
+ rabbitizer.config.toolchainTweaks_sn64DivFix = True
+ rabbitizer.config.toolchainTweaks_treatJAsUnconditionalBranch = True
+ spimdisasm.common.GlobalConfig.ASM_COMMENT = False
+ spimdisasm.common.GlobalConfig.SYMBOL_FINDER_FILTERED_ADDRESSES_AS_HILO = (
+ False
+ )
+ spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.SN64
+ elif selected_compiler == compiler.GCC:
+ rabbitizer.config.toolchainTweaks_treatJAsUnconditionalBranch = True
+ spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.GCC
+ elif selected_compiler == compiler.IDO:
+ spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.IDO
+
+ spimdisasm.common.GlobalConfig.GP_VALUE = opts.gp
+
+ spimdisasm.common.GlobalConfig.ASM_TEXT_LABEL = opts.asm_function_macro
+ spimdisasm.common.GlobalConfig.ASM_JTBL_LABEL = opts.asm_jtbl_label_macro
+ spimdisasm.common.GlobalConfig.ASM_DATA_LABEL = opts.asm_data_macro
+ spimdisasm.common.GlobalConfig.ASM_TEXT_END_LABEL = opts.asm_end_label
+
+ if opts.asm_emit_size_directive is not None:
+ spimdisasm.common.GlobalConfig.ASM_EMIT_SIZE_DIRECTIVE = (
+ opts.asm_emit_size_directive
+ )
+
+ if spimdisasm.common.GlobalConfig.ASM_TEXT_LABEL == ".globl":
+ spimdisasm.common.GlobalConfig.ASM_TEXT_ENT_LABEL = ".ent"
+ spimdisasm.common.GlobalConfig.ASM_TEXT_FUNC_AS_LABEL = True
+
+ if spimdisasm.common.GlobalConfig.ASM_DATA_LABEL == ".globl":
+ spimdisasm.common.GlobalConfig.ASM_DATA_SYM_AS_LABEL = True
+
+ spimdisasm.common.GlobalConfig.LINE_ENDS = opts.c_newline
+
+ spimdisasm.common.GlobalConfig.ALLOW_ALL_ADDENDS_ON_DATA = (
+ opts.allow_data_addends
+ )
+
+ spimdisasm.common.GlobalConfig.ASM_GENERATED_BY = opts.asm_generated_by
+
+ spimdisasm.common.GlobalConfig.DISASSEMBLE_UNKNOWN_INSTRUCTIONS = (
+ opts.disasm_unknown
+ )
+
+ def check_version(self, skip_version_check: bool, splat_version: str):
+ if not skip_version_check and spimdisasm.__version_info__ < self.SPIMDISASM_MIN:
+ log.error(
+ f"splat {splat_version} requires as minimum spimdisasm {self.SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}"
+ )
+
+ log.write(
+ f"splat {splat_version} (powered by spimdisasm {spimdisasm.__version__})"
+ )
+
+ def known_types(self) -> Set[str]:
+ return spimdisasm.common.gKnownTypes
diff --git a/tools/splat/disassembler_section.py b/tools/splat/disassembler_section.py
new file mode 100644
index 0000000..2eb745a
--- /dev/null
+++ b/tools/splat/disassembler_section.py
@@ -0,0 +1,283 @@
+import spimdisasm
+from util import symbols
+from typing import Optional, Set, Tuple
+from segtypes.segment import Segment
+from util import log, options, symbols
+
+
+from abc import ABC, abstractmethod
+from typing import Callable
+
+
+class DisassemblerSection(ABC):
+ @abstractmethod
+ def disassemble(self):
+ raise NotImplementedError("disassemble")
+
+ @abstractmethod
+ def analyze(self):
+ raise NotImplementedError("analyze")
+
+ @abstractmethod
+ def set_comment_offset(self, rom_start: int):
+ raise NotImplementedError("set_comment_offset")
+
+ @abstractmethod
+ def make_bss_section(
+ self,
+ rom_start,
+ rom_end,
+ vram_start,
+ bss_end,
+ name,
+ segment_rom_start,
+ exclusive_ram_id,
+ ):
+ raise NotImplementedError("make_bss_section")
+
+ @abstractmethod
+ def make_data_section(
+ self,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ ):
+ raise NotImplementedError("make_data_section")
+
+ @abstractmethod
+ def get_section(self):
+ raise NotImplementedError("get_section")
+
+ @abstractmethod
+ def make_rodata_section(
+ self,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ ):
+ raise NotImplementedError("make_rodata_section")
+
+ @abstractmethod
+ def make_text_section(
+ self,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ ):
+ raise NotImplementedError("make_text_section")
+
+
+class SpimdisasmDisassemberSection(DisassemblerSection):
+ def __init__(self):
+ self.spim_section: Optional[spimdisasm.mips.sections.SectionBase] = None
+
+ def disassemble(self) -> str:
+ assert self.spim_section is not None
+ return self.spim_section.disassemble()
+
+ def analyze(self):
+ assert self.spim_section is not None
+ self.spim_section.analyze()
+
+ def set_comment_offset(self, rom_start: int):
+ assert self.spim_section is not None
+ self.spim_section.setCommentOffset(rom_start)
+
+ def make_bss_section(
+ self,
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ bss_end: int,
+ name: str,
+ segment_rom_start: int,
+ exclusive_ram_id,
+ ):
+ self.spim_section = spimdisasm.mips.sections.SectionBss(
+ symbols.spim_context,
+ rom_start,
+ rom_end,
+ vram_start,
+ bss_end,
+ name,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+
+ def make_data_section(
+ self,
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+ ):
+ self.spim_section = spimdisasm.mips.sections.SectionData(
+ symbols.spim_context,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+
+ def get_section(self) -> Optional[spimdisasm.mips.sections.SectionBase]:
+ return self.spim_section
+
+ def make_rodata_section(
+ self,
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+ ):
+ self.spim_section = spimdisasm.mips.sections.SectionRodata(
+ symbols.spim_context,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+
+ def make_text_section(
+ self,
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+ ):
+ self.spim_section = spimdisasm.mips.sections.SectionText(
+ symbols.spim_context,
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+
+
+def make_disassembler_section() -> Optional[SpimdisasmDisassemberSection]:
+ if options.opts.platform in ["n64", "psx", "ps2"]:
+ return SpimdisasmDisassemberSection()
+
+ raise NotImplementedError("No disassembler section for requested platform")
+ return None
+
+
+def make_text_section(
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+) -> DisassemblerSection:
+ section = make_disassembler_section()
+ assert section is not None
+ section.make_text_section(
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+ return section
+
+
+def make_data_section(
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+) -> DisassemblerSection:
+ section = make_disassembler_section()
+ assert section is not None
+ section.make_data_section(
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+ return section
+
+
+def make_rodata_section(
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ name: str,
+ rom_bytes: bytes,
+ segment_rom_start: int,
+ exclusive_ram_id,
+) -> DisassemblerSection:
+ section = make_disassembler_section()
+ assert section is not None
+ section.make_rodata_section(
+ rom_start,
+ rom_end,
+ vram_start,
+ name,
+ rom_bytes,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+ return section
+
+
+def make_bss_section(
+ rom_start: int,
+ rom_end: int,
+ vram_start: int,
+ bss_end: int,
+ name: str,
+ segment_rom_start: int,
+ exclusive_ram_id,
+) -> DisassemblerSection:
+ section = make_disassembler_section()
+ assert section is not None
+ section.make_bss_section(
+ rom_start,
+ rom_end,
+ vram_start,
+ bss_end,
+ name,
+ segment_rom_start,
+ exclusive_ram_id,
+ )
+ return section
diff --git a/tools/splat/requirements.txt b/tools/splat/requirements.txt
index 711d553..56576c2 100644
--- a/tools/splat/requirements.txt
+++ b/tools/splat/requirements.txt
@@ -3,8 +3,8 @@ pylibyaml
tqdm
intervaltree
colorama
-# This value should be keep in sync with the version listed on split.py
-spimdisasm>=1.12.0
-rabbitizer>=1.4.0
+# This value should be keep in sync with the version listed on disassembler/spimdisasm_disassembler.py
+spimdisasm>=1.15.0
+rabbitizer>=1.7.0
pygfxd
n64img>=0.1.4
diff --git a/tools/splat/run_tests.sh b/tools/splat/run_tests.sh
new file mode 100644
index 0000000..ca362d7
--- /dev/null
+++ b/tools/splat/run_tests.sh
@@ -0,0 +1,9 @@
+# docker build container
+docker build --tag splat-build:latest . && \
+# get compilers and tools
+# clean
+cd test/basic_app && sh clean.sh && cd ../../ && \
+# build
+docker run --rm -v $(pwd):/splat -w /splat/test/basic_app splat-build sh build.sh && \
+# test
+python3 test.py
diff --git a/tools/splat/segtypes/common/bss.py b/tools/splat/segtypes/common/bss.py
index ef79311..4bc74d1 100644
--- a/tools/splat/segtypes/common/bss.py
+++ b/tools/splat/segtypes/common/bss.py
@@ -1,8 +1,9 @@
-import spimdisasm
from util import options, symbols, log
from segtypes.common.data import CommonSegData
+from disassembler_section import make_bss_section
+
class CommonSegBss(CommonSegData):
def get_linker_section(self) -> str:
@@ -37,8 +38,7 @@ class CommonSegBss(CommonSegData):
bss_end = next_subsegment.vram_start
assert isinstance(bss_end, int), f"{self.name} {bss_end}"
- self.spim_section = spimdisasm.mips.sections.SectionBss(
- symbols.spim_context,
+ self.spim_section = make_bss_section(
self.rom_start,
self.rom_end,
self.vram_start,
@@ -48,10 +48,12 @@ class CommonSegBss(CommonSegData):
self.get_exclusive_ram_id(),
)
+ assert self.spim_section is not None
+
self.spim_section.analyze()
- self.spim_section.setCommentOffset(self.rom_start)
+ self.spim_section.set_comment_offset(self.rom_start)
- for spim_sym in self.spim_section.symbolList:
+ for spim_sym in self.spim_section.get_section().symbolList:
symbols.create_symbol_from_spim_symbol(
self.get_most_parent(), spim_sym.contextSym
)
diff --git a/tools/splat/segtypes/common/c.py b/tools/splat/segtypes/common/c.py
index 76d6981..5990b6c 100644
--- a/tools/splat/segtypes/common/c.py
+++ b/tools/splat/segtypes/common/c.py
@@ -153,10 +153,10 @@ class CommonSegC(CommonSegCodeSubsegment):
self.print_file_boundaries()
assert self.spim_section is not None and isinstance(
- self.spim_section, spimdisasm.mips.sections.SectionText
+ self.spim_section.get_section(), spimdisasm.mips.sections.SectionText
), f"{self.name}, rom_start:{self.rom_start}, rom_end:{self.rom_end}"
- rodata_spim_segment = None
+ rodata_spim_segment: Optional[spimdisasm.mips.sections.SectionRodata] = None
if (
options.opts.migrate_rodata_to_functions
and self.rodata_sibling is not None
@@ -166,15 +166,15 @@ class CommonSegC(CommonSegCodeSubsegment):
), self.rodata_sibling.type
if self.rodata_sibling.spim_section is not None:
assert isinstance(
- self.rodata_sibling.spim_section,
+ self.rodata_sibling.spim_section.get_section(),
spimdisasm.mips.sections.SectionRodata,
)
- rodata_spim_segment = self.rodata_sibling.spim_section
+ rodata_spim_segment = self.rodata_sibling.spim_section.get_section()
# Precompute function-rodata pairings
symbols_entries = (
spimdisasm.mips.FunctionRodataEntry.getAllEntriesFromSections(
- self.spim_section, rodata_spim_segment
+ self.spim_section.get_section(), rodata_spim_segment
)
)
@@ -251,7 +251,7 @@ class CommonSegC(CommonSegCodeSubsegment):
f"\t The address of the missing rodata symbol is 0x{rodata_sym.vramEnd:08X}"
)
log.write(
- f"\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force:not_migration:True`"
+ f"\t Try to force the migration of that symbol with `force_migration:True` in the symbol_addrs.txt file; or avoid the migration of symbols around this address with `force_not_migration:True`"
)
def create_c_asm_file(
@@ -319,7 +319,7 @@ class CommonSegC(CommonSegCodeSubsegment):
macro_name: str,
) -> str:
if options.opts.compiler == IDO:
- # IDO uses the asm processor to embeed assembly and it doesn't require a special directive to include symbols
+ # IDO uses the asm processor to embeed assembly, and it doesn't require a special directive to include symbols
asm_outpath = Path(
os.path.join(asm_out_dir, self.name, spim_sym.getName() + ".s")
)
diff --git a/tools/splat/segtypes/common/code.py b/tools/splat/segtypes/common/code.py
index dc9a5bc..a3f6147 100644
--- a/tools/splat/segtypes/common/code.py
+++ b/tools/splat/segtypes/common/code.py
@@ -278,8 +278,17 @@ class CommonSegCode(CommonSegGroup):
)
segment.sibling = base_segments.get(segment.name, None)
- if segment.is_rodata() and segment.sibling is not None:
- segment.sibling.rodata_sibling = segment
+
+ if segment.sibling is not None:
+ if self.section_order.index(".text") < self.section_order.index(
+ ".rodata"
+ ):
+ if segment.is_rodata():
+ segment.sibling.rodata_sibling = segment
+ else:
+ if segment.is_text() and segment.sibling.is_rodata():
+ segment.rodata_sibling = segment.sibling
+ segment.sibling.sibling = segment
segment.parent = self
if segment.special_vram_segment:
@@ -300,6 +309,10 @@ class CommonSegCode(CommonSegGroup):
if segment.is_text():
base_segments[segment.name] = segment
+ if self.section_order.index(".rodata") < self.section_order.index(".text"):
+ if segment.is_rodata() and segment.sibling is None:
+ base_segments[segment.name] = segment
+
prev_start = start
if end is not None:
last_rom_end = end
diff --git a/tools/splat/segtypes/common/codesubsegment.py b/tools/splat/segtypes/common/codesubsegment.py
index 0423705..a26f2cb 100644
--- a/tools/splat/segtypes/common/codesubsegment.py
+++ b/tools/splat/segtypes/common/codesubsegment.py
@@ -10,6 +10,8 @@ from segtypes.common.code import CommonSegCode
from segtypes.segment import Segment
+from disassembler_section import DisassemblerSection, make_text_section
+
# abstract class for c, asm, data, etc
class CommonSegCodeSubsegment(Segment):
@@ -24,10 +26,12 @@ class CommonSegCodeSubsegment(Segment):
self.yaml.get("str_encoding", None) if isinstance(self.yaml, dict) else None
)
- self.spim_section: Optional[spimdisasm.mips.sections.SectionBase] = None
+ self.spim_section: Optional[DisassemblerSection] = None
self.instr_category = rabbitizer.InstrCategory.CPU
if options.opts.platform == "ps2":
self.instr_category = rabbitizer.InstrCategory.R5900
+ elif options.opts.platform == "psx":
+ self.instr_category = rabbitizer.InstrCategory.R3000GTE
@property
def needs_symbols(self) -> bool:
@@ -54,8 +58,7 @@ class CommonSegCodeSubsegment(Segment):
f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'"
)
- self.spim_section = spimdisasm.mips.sections.SectionText(
- symbols.spim_context,
+ self.spim_section = make_text_section(
self.rom_start,
self.rom_end,
self.vram_start,
@@ -65,13 +68,15 @@ class CommonSegCodeSubsegment(Segment):
self.get_exclusive_ram_id(),
)
- self.spim_section.isHandwritten = is_hasm
- self.spim_section.instrCat = self.instr_category
+ assert self.spim_section is not None
+
+ self.spim_section.get_section().isHandwritten = is_hasm
+ self.spim_section.get_section().instrCat = self.instr_category
self.spim_section.analyze()
- self.spim_section.setCommentOffset(self.rom_start)
+ self.spim_section.set_comment_offset(self.rom_start)
- for func in self.spim_section.symbolList:
+ for func in self.spim_section.get_section().symbolList:
assert isinstance(func, spimdisasm.mips.symbols.SymbolFunction)
self.process_insns(func)
@@ -83,7 +88,7 @@ class CommonSegCodeSubsegment(Segment):
jtbl_label_vram, True, type="jtbl_label", define=True
)
sym.type = "jtbl_label"
- symbols.add_symbol_to_spim_section(self.spim_section, sym)
+ symbols.add_symbol_to_spim_section(self.spim_section.get_section(), sym)
def process_insns(
self,
@@ -101,7 +106,7 @@ class CommonSegCodeSubsegment(Segment):
# Gather symbols found by spimdisasm and create those symbols in splat's side
for referenced_vram in func_spim.instrAnalyzer.referencedVrams:
- context_sym = self.spim_section.getSymbol(
+ context_sym = self.spim_section.get_section().getSymbol(
referenced_vram, tryPlusOffset=False
)
if context_sym is not None:
@@ -122,7 +127,7 @@ class CommonSegCodeSubsegment(Segment):
if instr_offset in func_spim.instrAnalyzer.symbolInstrOffset:
sym_address = func_spim.instrAnalyzer.symbolInstrOffset[instr_offset]
- context_sym = self.spim_section.getSymbol(
+ context_sym = self.spim_section.get_section().getSymbol(
sym_address, tryPlusOffset=False
)
if context_sym is not None:
@@ -139,7 +144,7 @@ class CommonSegCodeSubsegment(Segment):
assert isinstance(self.rom_start, int)
- for in_file_offset in self.spim_section.fileBoundaries:
+ for in_file_offset in self.spim_section.get_section().fileBoundaries:
if (in_file_offset % 16) != 0:
continue
@@ -148,8 +153,10 @@ class CommonSegCodeSubsegment(Segment):
# Look up for the last symbol in this boundary
sym_addr = 0
- for sym in self.spim_section.symbolList:
- symOffset = sym.inFileOffset - self.spim_section.inFileOffset
+ for sym in self.spim_section.get_section().symbolList:
+ symOffset = (
+ sym.inFileOffset - self.spim_section.get_section().inFileOffset
+ )
if in_file_offset == symOffset:
break
sym_addr = sym.vram
diff --git a/tools/splat/segtypes/common/data.py b/tools/splat/segtypes/common/data.py
index e97ce6d..7a93653 100644
--- a/tools/splat/segtypes/common/data.py
+++ b/tools/splat/segtypes/common/data.py
@@ -1,12 +1,13 @@
from pathlib import Path
from typing import Optional
-import spimdisasm
from util import options, symbols, log
from segtypes.common.codesubsegment import CommonSegCodeSubsegment
from segtypes.common.group import CommonSegGroup
+from disassembler_section import make_data_section
+
class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
def out_path(self) -> Optional[Path]:
@@ -24,7 +25,7 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
def scan(self, rom_bytes: bytes):
CommonSegGroup.scan(self, rom_bytes)
- if self.should_scan():
+ if self.rom_start is not None and self.rom_end is not None:
self.disassemble_data(rom_bytes)
def split(self, rom_bytes: bytes):
@@ -55,9 +56,7 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
return options.opts.is_mode_active("data")
def should_scan(self) -> bool:
- # Ensure data segments are scanned even if extract is False so subsegments get scanned too
- # Check for not None so we avoid scanning "auto" segments
- return self.rom_start is not None and self.rom_end is not None
+ return True
def should_split(self) -> bool:
return True
@@ -89,8 +88,7 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'"
)
- self.spim_section = spimdisasm.mips.sections.SectionData(
- symbols.spim_context,
+ self.spim_section = make_data_section(
self.rom_start,
self.rom_end,
self.vram_start,
@@ -100,12 +98,25 @@ class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup):
self.get_exclusive_ram_id(),
)
+ assert self.spim_section is not None
+
+ # Set rodata string encoding
+ # First check the global configuration
+ if options.opts.data_string_encoding is not None:
+ self.spim_section.get_section().stringEncoding = (
+ options.opts.data_string_encoding
+ )
+
+ # Then check the per-segment configuration in case we want to override the global one
+ if self.str_encoding is not None:
+ self.spim_section.get_section().stringEncoding = self.str_encoding
+
self.spim_section.analyze()
- self.spim_section.setCommentOffset(self.rom_start)
+ self.spim_section.set_comment_offset(self.rom_start)
rodata_encountered = False
- for symbol in self.spim_section.symbolList:
+ for symbol in self.spim_section.get_section().symbolList:
symbols.create_symbol_from_spim_symbol(
self.get_most_parent(), symbol.contextSym
)
diff --git a/tools/splat/segtypes/common/group.py b/tools/splat/segtypes/common/group.py
index fa263de..5687ab0 100644
--- a/tools/splat/segtypes/common/group.py
+++ b/tools/splat/segtypes/common/group.py
@@ -68,8 +68,12 @@ class CommonSegGroup(CommonSegment):
vram = None
if start is not None:
- assert isinstance(start, int)
- vram = self.get_most_parent().rom_to_ram(start)
+ most_parent = self.get_most_parent()
+ if (
+ most_parent.vram_start is not None
+ and most_parent.rom_start is not None
+ ):
+ vram = most_parent.vram_start + start - most_parent.rom_start
if segment_class.is_noload():
# Pretend bss's rom address is after the last actual rom segment
diff --git a/tools/splat/segtypes/common/rodata.py b/tools/splat/segtypes/common/rodata.py
index a189671..bac0826 100644
--- a/tools/splat/segtypes/common/rodata.py
+++ b/tools/splat/segtypes/common/rodata.py
@@ -5,6 +5,8 @@ from util import log, options, symbols
from segtypes.common.data import CommonSegData
+from disassembler_section import make_rodata_section
+
class CommonSegRodata(CommonSegData):
def get_linker_section(self) -> str:
@@ -53,8 +55,7 @@ class CommonSegRodata(CommonSegData):
f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'"
)
- self.spim_section = spimdisasm.mips.sections.SectionRodata(
- symbols.spim_context,
+ self.spim_section = make_rodata_section(
self.rom_start,
self.rom_end,
self.vram_start,
@@ -64,21 +65,25 @@ class CommonSegRodata(CommonSegData):
self.get_exclusive_ram_id(),
)
+ assert self.spim_section is not None
+
# Set rodata string encoding
# First check the global configuration
if options.opts.string_encoding is not None:
- self.spim_section.stringEncoding = options.opts.string_encoding
+ self.spim_section.get_section().stringEncoding = (
+ options.opts.string_encoding
+ )
# Then check the per-segment configuration in case we want to override the global one
if self.str_encoding is not None:
- self.spim_section.stringEncoding = self.str_encoding
+ self.spim_section.get_section().stringEncoding = self.str_encoding
self.spim_section.analyze()
- self.spim_section.setCommentOffset(self.rom_start)
+ self.spim_section.set_comment_offset(self.rom_start)
possible_text_segments: Set[Segment] = set()
- for symbol in self.spim_section.symbolList:
+ for symbol in self.spim_section.get_section().symbolList:
generated_symbol = symbols.create_symbol_from_spim_symbol(
self.get_most_parent(), symbol.contextSym
)
diff --git a/tools/splat/segtypes/gc/rarc.py b/tools/splat/segtypes/gc/rarc.py
index 43eda14..bea41ad 100644
--- a/tools/splat/segtypes/gc/rarc.py
+++ b/tools/splat/segtypes/gc/rarc.py
@@ -56,7 +56,7 @@ class GCRARCArchive:
# Yay0
elif compression_scheme == 0x59617930:
self.compression = "yay0"
- return Yay0Decompressor().decompress(file_bytes)
+ return Yay0Decompressor.decompress(file_bytes)
# Not compressed!
else:
return file_bytes
diff --git a/tools/splat/segtypes/linker_entry.py b/tools/splat/segtypes/linker_entry.py
index 3750ae6..981d3d3 100644
--- a/tools/splat/segtypes/linker_entry.py
+++ b/tools/splat/segtypes/linker_entry.py
@@ -39,7 +39,10 @@ def path_to_object_path(path: Path) -> Path:
full_suffix = ".o"
else:
full_suffix = path.suffix + ".o"
- return clean_up_path(options.opts.build_path / path.with_suffix(full_suffix))
+
+ if not str(path).startswith(str(options.opts.build_path)):
+ path = options.opts.build_path / path
+ return clean_up_path(path.with_suffix(full_suffix))
def write_file_if_different(path: Path, new_content: str):
@@ -111,7 +114,7 @@ class LinkerWriter:
self.entries: List[LinkerEntry] = []
self.buffer: List[str] = []
- self.symbols: List[str] = []
+ self.header_symbols: Set[str] = set()
self._indent_level = 0
@@ -303,7 +306,7 @@ class LinkerWriter:
"\n"
'#include "common.h"\n'
"\n"
- + "".join(f"extern Addr {symbol};\n" for symbol in self.symbols)
+ + "".join(f"extern Addr {symbol};\n" for symbol in self.header_symbols)
+ "\n"
"#endif\n",
)
@@ -330,8 +333,7 @@ class LinkerWriter:
self._writeln(f"{symbol} = {value};")
- if symbol not in self.symbols:
- self.symbols.append(symbol)
+ self.header_symbols.add(symbol)
def _begin_segment(self, segment: Segment):
if options.opts.ld_use_follows and segment.vram_of_symbol:
@@ -389,11 +391,16 @@ class LinkerWriter:
self._writeln(f"__romPos += SIZEOF(.{name});")
# Align directive
- if segment.align:
- self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});")
+ if not options.opts.segment_end_before_align:
+ if segment.align:
+ self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});")
self._write_symbol(f"{name}_ROM_END", "__romPos")
-
self._write_symbol(get_segment_vram_end_symbol_name(segment), ".")
+ # Align directive
+ if options.opts.segment_end_before_align:
+ if segment.align:
+ self._writeln(f"__romPos = ALIGN(__romPos, {segment.align});")
+
self._writeln("")
diff --git a/tools/splat/segtypes/n64/ci.py b/tools/splat/segtypes/n64/ci.py
index 436908f..16756bf 100644
--- a/tools/splat/segtypes/n64/ci.py
+++ b/tools/splat/segtypes/n64/ci.py
@@ -26,6 +26,9 @@ class N64SegCi(N64SegImg):
self.palette: "Optional[N64SegPalette]" = None
self.palette_name = self.parse_palette_name(self.yaml, self.args)
+ def scan(self, rom_bytes: bytes) -> None:
+ self.n64img.data = rom_bytes[self.rom_start : self.rom_end]
+
def split(self, rom_bytes):
if self.palette is None:
# TODO: output with blank palette
diff --git a/tools/splat/segtypes/n64/gfx.py b/tools/splat/segtypes/n64/gfx.py
index 62d0965..b73e1c0 100644
--- a/tools/splat/segtypes/n64/gfx.py
+++ b/tools/splat/segtypes/n64/gfx.py
@@ -164,7 +164,7 @@ class N64SegGfx(CommonSegCodeSubsegment):
sym = self.create_symbol(
addr=addr, in_segment=True, type="data", reference=True
)
- gfxd_printf(self.format_sym_name(sym))
+ gfxd_printf(f"&{self.format_sym_name(sym)}")
return 1
def macro_fn(self):
diff --git a/tools/splat/segtypes/n64/img.py b/tools/splat/segtypes/n64/img.py
index 7c4bf80..8ed00e6 100644
--- a/tools/splat/segtypes/n64/img.py
+++ b/tools/splat/segtypes/n64/img.py
@@ -77,7 +77,8 @@ class N64SegImg(N64Segment):
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)
- self.n64img.data = rom_bytes[self.rom_start : self.rom_end]
+ if self.n64img.data is None:
+ self.n64img.data = rom_bytes[self.rom_start : self.rom_end]
self.n64img.write(path)
self.log(f"Wrote {self.name} to {path}")
diff --git a/tools/splat/segtypes/n64/vtx.py b/tools/splat/segtypes/n64/vtx.py
index 8d961b2..e0d1bc9 100644
--- a/tools/splat/segtypes/n64/vtx.py
+++ b/tools/splat/segtypes/n64/vtx.py
@@ -34,7 +34,7 @@ class N64SegVtx(CommonSegCodeSubsegment):
args=args,
yaml=yaml,
)
- self.file_text = None
+ self.file_text: Optional[str] = None
self.data_only = isinstance(yaml, dict) and yaml.get("data_only", False)
def format_sym_name(self, sym) -> str:
@@ -49,7 +49,7 @@ class N64SegVtx(CommonSegCodeSubsegment):
def scan(self, rom_bytes: bytes):
self.file_text = self.disassemble_data(rom_bytes)
- def disassemble_data(self, rom_bytes):
+ def disassemble_data(self, rom_bytes) -> str:
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)
assert isinstance(self.vram_start, int)
@@ -96,11 +96,7 @@ class N64SegVtx(CommonSegCodeSubsegment):
f.write(self.file_text)
def should_scan(self) -> bool:
- return (
- options.opts.is_mode_active("vtx")
- and self.rom_start is not None
- and self.rom_end is not None
- )
+ return options.opts.is_mode_active("vtx")
def should_split(self) -> bool:
return self.extract and options.opts.is_mode_active("vtx")
diff --git a/tools/splat/segtypes/segment.py b/tools/splat/segtypes/segment.py
index 2650b9d..9944a84 100644
--- a/tools/splat/segtypes/segment.py
+++ b/tools/splat/segtypes/segment.py
@@ -223,6 +223,7 @@ class Segment:
self.yaml = yaml
self.extract: bool = True
+ self.has_linker_entry: bool = True
if self.rom_start is None:
self.extract = False
elif self.type.startswith("."):
@@ -235,7 +236,7 @@ class Segment:
# For segments which are not in the usual VRAM segment space, like N64's IPL3 which lives in 0xA4...
self.special_vram_segment: bool = False
- if isinstance(self.rom_start, int) and isinstance(self.rom_end, int):
+ if self.rom_start is not None and self.rom_end is not None:
if self.rom_start > self.rom_end:
log.error(
f"Error: segments out of order - ({self.name} starts at 0x{self.rom_start:X}, but next segment starts at 0x{self.rom_end:X})"
@@ -270,6 +271,7 @@ class Segment:
ret.extract = bool(yaml.get("extract", ret.extract))
ret.exclusive_ram_id = yaml.get("exclusive_ram_id")
ret.given_dir = Path(yaml.get("dir", ""))
+ ret.has_linker_entry = bool(yaml.get("linker_entry", True))
ret.given_symbol_name_format = Segment.parse_segment_symbol_name_format(yaml)
ret.given_symbol_name_format_no_rom = (
Segment.parse_segment_symbol_name_format_no_rom(yaml)
@@ -341,7 +343,7 @@ class Segment:
# For larger symbols, add their ranges to interval trees for faster lookup
if symbol.size > 4:
self.symbol_ranges_ram.addi(symbol.vram_start, symbol.vram_end, symbol)
- if symbol.rom and isinstance(symbol.rom, int):
+ if symbol.rom is not None:
self.symbol_ranges_rom.addi(symbol.rom, symbol.rom_end, symbol)
@property
@@ -353,7 +355,7 @@ class Segment:
@property
def size(self) -> Optional[int]:
- if isinstance(self.rom_start, int) and isinstance(self.rom_end, int):
+ if self.rom_start is not None and self.rom_end is not None:
return self.rom_end - self.rom_start
else:
return None
@@ -384,16 +386,13 @@ class Segment:
return False
def contains_rom(self, rom: int) -> bool:
- if isinstance(self.rom_start, int) and isinstance(self.rom_end, int):
+ if self.rom_start is not None and self.rom_end is not None:
return rom >= self.rom_start and rom < self.rom_end
else:
return False
def rom_to_ram(self, rom_addr: int) -> Optional[int]:
- if not self.contains_rom(rom_addr) and rom_addr != self.rom_end:
- return None
-
- if self.vram_start is not None and isinstance(self.rom_start, int):
+ if self.vram_start is not None and self.rom_start is not None:
return self.vram_start + rom_addr - self.rom_start
else:
return None
@@ -402,7 +401,7 @@ class Segment:
if not self.contains_vram(ram_addr) and ram_addr != self.vram_end:
return None
- if self.vram_start is not None and isinstance(self.rom_start, int):
+ if self.vram_start is not None and self.rom_start is not None:
return self.rom_start + ram_addr - self.vram_start
else:
return None
@@ -439,6 +438,9 @@ class Segment:
def get_linker_entries(self) -> "List[LinkerEntry]":
from segtypes.linker_entry import LinkerEntry
+ if not self.has_linker_entry:
+ return []
+
path = self.out_path()
if path:
@@ -456,6 +458,10 @@ class Segment:
def max_length(self):
return None
+ @staticmethod
+ def get_default_name(addr) -> str:
+ return f"{addr:X}"
+
def is_name_default(self):
return self.name == self.get_default_name(self.rom_start)
@@ -467,18 +473,6 @@ class Segment:
return s + self.type + "_" + self.name
- def status(self):
- if len(self.warnings) > 0:
- return "warn"
- elif self.did_run:
- return "ok"
- else:
- return "skip"
-
- @staticmethod
- def get_default_name(addr) -> str:
- return f"{addr:X}"
-
@staticmethod
def visible_ram(seg1: "Segment", seg2: "Segment") -> bool:
if seg1.get_most_parent() == seg2.get_most_parent():
diff --git a/tools/splat/split.py b/tools/splat/split.py
index 18ab2c8..642890a 100755
--- a/tools/splat/split.py
+++ b/tools/splat/split.py
@@ -5,9 +5,7 @@ import hashlib
import importlib
import pickle
from typing import Any, Dict, List, Optional, Set, Tuple, Union
-
-import rabbitizer
-import spimdisasm
+from disassembler import disassembler_instance
import tqdm
import yaml
from colorama import Fore, Style
@@ -19,13 +17,9 @@ from segtypes.linker_entry import (
to_cname,
)
from segtypes.segment import Segment
-from util import compiler, log, options, palettes, symbols, relocs
-
-from util.symbols import Symbol
+from util import log, options, palettes, symbols, relocs
-VERSION = "0.13.5"
-# This value should be kept in sync with the version listed on requirements.txt
-SPIMDISASM_MIN = (1, 12, 0)
+VERSION = "0.15.2"
parser = argparse.ArgumentParser(
description="Split a rom given a rom, a config, and output directory"
@@ -208,78 +202,6 @@ def merge_configs(main_config, additional_config):
return main_config
-def configure_disassembler():
- # Configure spimdisasm
- spimdisasm.common.GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET = True
- spimdisasm.common.GlobalConfig.TRUST_USER_FUNCTIONS = True
- spimdisasm.common.GlobalConfig.TRUST_JAL_FUNCTIONS = True
- spimdisasm.common.GlobalConfig.GLABEL_ASM_COUNT = False
-
- if options.opts.rom_address_padding:
- spimdisasm.common.GlobalConfig.ASM_COMMENT_OFFSET_WIDTH = 6
- else:
- spimdisasm.common.GlobalConfig.ASM_COMMENT_OFFSET_WIDTH = 0
-
- # spimdisasm is not performing any analyzis on non-text sections so enabling this options is pointless
- spimdisasm.common.GlobalConfig.AUTOGENERATED_NAMES_BASED_ON_SECTION_TYPE = False
- spimdisasm.common.GlobalConfig.AUTOGENERATED_NAMES_BASED_ON_DATA_TYPE = False
-
- spimdisasm.common.GlobalConfig.SYMBOL_FINDER_FILTERED_ADDRESSES_AS_HILO = False
-
- rabbitizer.config.regNames_userFpcCsr = False
- rabbitizer.config.regNames_vr4300Cop0NamedRegisters = False
-
- rabbitizer.config.misc_opcodeLJust = options.opts.mnemonic_ljust - 1
-
- rabbitizer.config.regNames_gprAbiNames = rabbitizer.Abi.fromStr(
- options.opts.mips_abi_gpr
- )
- rabbitizer.config.regNames_fprAbiNames = rabbitizer.Abi.fromStr(
- options.opts.mips_abi_float_regs
- )
-
- if options.opts.endianness == "big":
- spimdisasm.common.GlobalConfig.ENDIAN = spimdisasm.common.InputEndian.BIG
- else:
- spimdisasm.common.GlobalConfig.ENDIAN = spimdisasm.common.InputEndian.LITTLE
-
- rabbitizer.config.pseudos_pseudoMove = False
-
- selected_compiler = options.opts.compiler
- if selected_compiler == compiler.SN64:
- rabbitizer.config.regNames_namedRegisters = False
- rabbitizer.config.toolchainTweaks_sn64DivFix = True
- rabbitizer.config.toolchainTweaks_treatJAsUnconditionalBranch = True
- spimdisasm.common.GlobalConfig.ASM_COMMENT = False
- spimdisasm.common.GlobalConfig.SYMBOL_FINDER_FILTERED_ADDRESSES_AS_HILO = False
- spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.SN64
- elif selected_compiler == compiler.GCC:
- rabbitizer.config.toolchainTweaks_treatJAsUnconditionalBranch = True
- spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.GCC
- elif selected_compiler == compiler.IDO:
- spimdisasm.common.GlobalConfig.COMPILER = spimdisasm.common.Compiler.IDO
-
- spimdisasm.common.GlobalConfig.GP_VALUE = options.opts.gp
-
- spimdisasm.common.GlobalConfig.ASM_TEXT_LABEL = options.opts.asm_function_macro
- spimdisasm.common.GlobalConfig.ASM_JTBL_LABEL = options.opts.asm_jtbl_label_macro
- spimdisasm.common.GlobalConfig.ASM_DATA_LABEL = options.opts.asm_data_macro
- spimdisasm.common.GlobalConfig.ASM_TEXT_END_LABEL = options.opts.asm_end_label
-
- if spimdisasm.common.GlobalConfig.ASM_TEXT_LABEL == ".globl":
- spimdisasm.common.GlobalConfig.ASM_TEXT_ENT_LABEL = ".ent"
- spimdisasm.common.GlobalConfig.ASM_TEXT_FUNC_AS_LABEL = True
-
- if spimdisasm.common.GlobalConfig.ASM_DATA_LABEL == ".globl":
- spimdisasm.common.GlobalConfig.ASM_DATA_SYM_AS_LABEL = True
-
- spimdisasm.common.GlobalConfig.LINE_ENDS = options.opts.c_newline
-
- spimdisasm.common.GlobalConfig.ALLOW_ALL_ADDENDS_ON_DATA = (
- options.opts.allow_data_addends
- )
-
-
def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str:
s = seg.name.strip()
if len(s) > limit:
@@ -290,13 +212,6 @@ def brief_seg_name(seg: Segment, limit: int, ellipsis="…") -> str:
def main(config_path, modes, verbose, use_cache=True, skip_version_check=False):
global config
- if not skip_version_check and spimdisasm.__version_info__ < SPIMDISASM_MIN:
- log.error(
- f"splat {VERSION} requires as minimum spimdisasm {SPIMDISASM_MIN}, but the installed version is {spimdisasm.__version_info__}"
- )
-
- log.write(f"splat {VERSION} (powered by spimdisasm {spimdisasm.__version__})")
-
# Load config
config = {}
for entry in config_path:
@@ -306,6 +221,9 @@ def main(config_path, modes, verbose, use_cache=True, skip_version_check=False):
options.initialize(config, config_path, modes, verbose)
+ disassembler_instance.create_disassembler_instance(options.opts.platform)
+ disassembler_instance.get_instance().check_version(skip_version_check, VERSION)
+
with options.opts.target_path.open("rb") as f2:
rom_bytes = f2.read()
@@ -346,7 +264,7 @@ def main(config_path, modes, verbose, use_cache=True, skip_version_check=False):
"__options__": config.get("options"),
}
- configure_disassembler()
+ disassembler_instance.get_instance().configure(options.opts)
platform_module = importlib.import_module(f"platforms.{options.opts.platform}")
platform_init = getattr(platform_module, "init")
@@ -431,7 +349,7 @@ def main(config_path, modes, verbose, use_cache=True, skip_version_check=False):
if (
options.opts.is_mode_active("ld") and options.opts.platform != "gc"
): # TODO move this to platform initialization when it gets implemented
- # Calculate list of segments for which we need to find the largest so we can safely place the symbol after it
+ # Calculate list of segments for which we need to find the largest, so we can safely place the symbol after it
max_vram_end_syms: Dict[str, List[Segment]] = {}
for sym in symbols.appears_after_overlays_syms:
max_vram_end_syms[sym.name] = [
diff --git a/tools/splat/test.py b/tools/splat/test.py
new file mode 100644
index 0000000..3685541
--- /dev/null
+++ b/tools/splat/test.py
@@ -0,0 +1,515 @@
+from spimdisasm.common import FileSectionType
+
+from split import *
+import unittest
+import io
+import filecmp
+from util import symbols, options
+import spimdisasm
+from segtypes.common.rodata import CommonSegRodata
+from segtypes.common.code import CommonSegCode
+from segtypes.common.c import CommonSegC
+from segtypes.common.bss import CommonSegBss
+import difflib
+
+
+class Testing(unittest.TestCase):
+ def compare_files(self, test_path, ref_path):
+ with io.open(test_path) as test_f, io.open(ref_path) as ref_f:
+ self.assertListEqual(list(test_f), list(ref_f))
+
+ def get_same_files(self, dcmp, out):
+ for name in dcmp.same_files:
+ out.append((name, dcmp.left, dcmp.right))
+
+ for sub_dcmp in dcmp.subdirs.values():
+ self.get_same_files(sub_dcmp, out)
+
+ def get_diff_files(self, dcmp, out):
+ for name in dcmp.diff_files:
+ out.append((name, dcmp.left, dcmp.right))
+
+ for sub_dcmp in dcmp.subdirs.values():
+ self.get_diff_files(sub_dcmp, out)
+
+ def get_left_only_files(self, dcmp, out):
+ for name in dcmp.left_only:
+ out.append((name, dcmp.left, dcmp.right))
+
+ for sub_dcmp in dcmp.subdirs.values():
+ self.get_left_only_files(sub_dcmp, out)
+
+ def get_right_only_files(self, dcmp, out):
+ for name in dcmp.right_only:
+ out.append((name, dcmp.left, dcmp.right))
+
+ for sub_dcmp in dcmp.subdirs.values():
+ self.get_right_only_files(sub_dcmp, out)
+
+ def test_basic_app(self):
+ main(["test/basic_app/splat.yaml"], None, None)
+
+ comparison = filecmp.dircmp("test/basic_app/split", "test/basic_app/expected")
+
+ diff_files: List[Tuple[str, str, str]] = []
+ self.get_diff_files(comparison, diff_files)
+
+ same_files: List[Tuple[str, str, str]] = []
+ self.get_same_files(comparison, same_files)
+
+ left_only_files: List[Tuple[str, str, str]] = []
+ self.get_left_only_files(comparison, left_only_files)
+
+ right_only_files: List[Tuple[str, str, str]] = []
+ self.get_right_only_files(comparison, right_only_files)
+
+ print("same_files", same_files)
+ print("diff_files", diff_files)
+ print("left_only_files", left_only_files)
+ print("right_only_files", right_only_files)
+
+ # if the files are different print out the difference
+ for file in diff_files:
+ # can't diff binary
+ if file[0] == ".splache":
+ continue
+ with open(f"{file[1]}/{file[0]}") as file1:
+ file1_lines = file1.readlines()
+ with open(f"{file[2]}/{file[0]}") as file2:
+ file2_lines = file2.readlines()
+
+ for line in difflib.unified_diff(
+ file1_lines, file2_lines, fromfile="file1", tofile="file2", lineterm=""
+ ):
+ print(line)
+
+ assert len(diff_files) == 0
+ assert len(left_only_files) == 0
+ assert len(right_only_files) == 0
+
+
+def test_init():
+ options_dict = {
+ "options": {
+ "platform": "n64",
+ "basename": "basic_app",
+ "base_path": ".",
+ "build_path": "build",
+ "target_path": "build/main.bin",
+ "asm_path": "split/asm",
+ "src_path": "split/src",
+ "ld_script_path": "split/basic_app.ld",
+ "cache_path": "split/.splache",
+ "symbol_addrs_path": "split/generated.symbols.txt",
+ "undefined_funcs_auto_path": "split/undefined_funcs_auto.txt",
+ "undefined_syms_auto_path": "split/undefined_syms_auto.txt",
+ },
+ "segments": [
+ {
+ "name": "basic_app",
+ "type": "code",
+ "start": 0,
+ "vram": 0x400000,
+ "subalign": 4,
+ "subsegments": [[0, "data"], [0x1DC, "c", "main"], [0x1FC, "data"]],
+ },
+ [0x1290],
+ ],
+ }
+ options.initialize(options_dict, ["./test/basic_app/splat.yaml"], [], False)
+
+
+class Symbols(unittest.TestCase):
+ def test_check_valid_type(self):
+ disassembler_instance.create_disassembler_instance("n64")
+
+ # first char is uppercase
+ assert symbols.check_valid_type("Symbol")
+
+ splat_sym_types = {"func", "jtbl", "jtbl_label", "label"}
+
+ for type in splat_sym_types:
+ assert symbols.check_valid_type(type)
+
+ spim_types = [
+ "char*",
+ "u32",
+ "Vec3f",
+ "u8",
+ "char",
+ "u16",
+ "f32",
+ "u64",
+ "asciz",
+ "s8",
+ "s64",
+ "f64",
+ "s16",
+ "s32",
+ ]
+
+ for type in spim_types:
+ assert symbols.check_valid_type(type)
+
+ def test_add_symbol_to_spim_segment(self):
+ segment = spimdisasm.common.SymbolsSegment(
+ context=spimdisasm.common.Context(),
+ vromStart=0x0,
+ vromEnd=0x10,
+ vramStart=0x40000000 + 0x0,
+ vramEnd=0x40000000 + 0x10,
+ )
+ sym = symbols.Symbol(0x40000000)
+ sym.user_declared = False
+ sym.defined = True
+ sym.rom = 0x0
+ sym.type = "func"
+ result = symbols.add_symbol_to_spim_segment(segment, sym)
+ assert result.type == spimdisasm.common.SymbolSpecialType.function
+ assert sym.user_declared == result.isUserDeclared
+ assert sym.defined == result.isDefined
+
+ def test_add_symbol_to_spim_section(self):
+ section = spimdisasm.mips.sections.SectionBase(
+ context=spimdisasm.common.Context(),
+ vromStart=0x0,
+ vromEnd=0x10,
+ vram=0x40000000,
+ filename="test",
+ words=[],
+ sectionType=FileSectionType.Text,
+ segmentVromStart=0x0,
+ overlayCategory=None,
+ )
+ sym = symbols.Symbol(0x100)
+ sym.type = "func"
+ sym.user_declared = False
+ sym.defined = True
+ result = symbols.add_symbol_to_spim_section(section, sym)
+ assert result.type == spimdisasm.common.SymbolSpecialType.function
+ assert sym.user_declared == result.isUserDeclared
+ assert sym.defined == result.isDefined
+
+ def test_create_symbol_from_spim_symbol(self):
+ # need to init otherwise options.opts isn't defined.
+ # used in initializing a Segment
+ test_init()
+
+ segment = Segment(
+ rom_start=0x0,
+ rom_end=0x100,
+ type="func",
+ name="MyFunc",
+ vram_start=0x40000000,
+ args=[],
+ yaml=None,
+ )
+ context_sym = spimdisasm.common.ContextSymbol(address=0)
+ result = symbols.create_symbol_from_spim_symbol(segment, context_sym)
+ assert result.referenced
+ assert result.extract
+ assert result.name == "D_0"
+
+
+def get_yaml():
+ return {
+ "name": "basic_app",
+ "type": "code",
+ "start": 0,
+ "vram": 0x400000,
+ "subalign": 4,
+ "subsegments": [[0, "data"], [0x1DC, "c", "main"], [0x1FC, "data"]],
+ }
+
+
+class Rodata(unittest.TestCase):
+ def test_disassemble_data(self):
+ test_init()
+ common_seg_rodata = CommonSegRodata(
+ rom_start=0x0,
+ rom_end=0x100,
+ type=".rodata",
+ name="MyRodata",
+ vram_start=0x400,
+ args=None,
+ yaml=None,
+ )
+ rom_data = []
+ for i in range(0x100):
+ rom_data.append(i)
+ common_seg_rodata.disassemble_data(bytes(rom_data))
+ assert common_seg_rodata.spim_section is not None
+ assert common_seg_rodata.spim_section.get_section().words[0] == 0x0010203
+ assert symbols.get_all_symbols()[0].vram_start == 0x400
+ assert symbols.get_all_symbols()[0].segment == common_seg_rodata
+ assert symbols.get_all_symbols()[0].linker_section == ".rodata"
+
+ def test_get_possible_text_subsegment_for_symbol(self):
+ context = spimdisasm.common.Context()
+
+ result_symbol_addr = 0x2DC
+
+ # use SymbolRodata to test migration
+ rodata_sym = spimdisasm.mips.symbols.SymbolRodata(
+ context=context,
+ vromStart=0x100,
+ vromEnd=0x200,
+ inFileOffset=0,
+ vram=0x100,
+ words=[0, 1, 2, 3, 4, 5, 6, 7],
+ segmentVromStart=0,
+ overlayCategory=None,
+ )
+ rodata_sym.contextSym.forceMigration = True
+
+ context_sym = spimdisasm.common.ContextSymbol(address=0)
+ context_sym.address = result_symbol_addr
+
+ rodata_sym.contextSym.referenceFunctions = {context_sym}
+ # Segment __init__ requires opts to be initialized
+ test_init()
+
+ common_seg_rodata = CommonSegRodata(
+ rom_start=0x0,
+ rom_end=0x100,
+ type=".rodata",
+ name="MyRodata",
+ vram_start=0x400,
+ args=None,
+ yaml=None,
+ )
+
+ common_seg_rodata.parent = CommonSegCode(
+ rom_start=0x0,
+ rom_end=0x200,
+ type="code",
+ name="MyCode",
+ vram_start=0x100,
+ args=[],
+ yaml=get_yaml(),
+ )
+
+ result = common_seg_rodata.get_possible_text_subsegment_for_symbol(rodata_sym)
+ assert result is not None
+ assert type(result[0]) == CommonSegC
+ assert result[1].address == result_symbol_addr
+
+
+class Bss(unittest.TestCase):
+ def test_disassemble_data(self):
+ # Segment __init__ requires opts to be initialized
+ test_init()
+
+ bss = CommonSegBss(
+ rom_start=0x0,
+ rom_end=0x100,
+ type=".bss",
+ name=None,
+ vram_start=0x40000000,
+ args=None,
+ yaml=None,
+ )
+
+ bss.parent = CommonSegCode(
+ rom_start=0x0,
+ rom_end=0x200,
+ type="code",
+ name="MyCode",
+ vram_start=0x100,
+ args=[],
+ yaml=get_yaml(),
+ )
+
+ rom_bytes = bytes([0, 1, 2, 3, 4, 5, 6, 7])
+ bss.disassemble_data(rom_bytes)
+
+ assert bss.spim_section is not None
+
+ assert isinstance(
+ bss.spim_section.get_section(), spimdisasm.mips.sections.SectionBss
+ )
+ assert bss.spim_section.get_section().bssVramStart == 0x40000000
+ assert bss.spim_section.get_section().bssVramEnd == 0x300
+
+
+class SymbolsInitialize(unittest.TestCase):
+ def test_attrs(self):
+ import pathlib
+
+ symbols.reset_symbols()
+ test_init()
+
+ sym_addrs_lines = [
+ "func_1 = 0x100 // type:func size:10 rom:100 segment:test_segment name_end:the_name_end "
+ "appears_after_overlays_addr:1234"
+ ]
+
+ all_segments = [
+ Segment(
+ rom_start=0x100,
+ rom_end=0x200,
+ type="func",
+ name="test_segment",
+ vram_start=0x300,
+ args=[],
+ yaml={},
+ )
+ ]
+
+ symbols.handle_sym_addrs(
+ pathlib.Path("/tmp/thing"), sym_addrs_lines, all_segments
+ )
+ assert symbols.all_symbols[0].given_name == "func_1"
+ assert symbols.all_symbols[0].type == "func"
+ assert symbols.all_symbols[0].given_size == 10
+ assert symbols.all_symbols[0].rom == 100
+ assert symbols.all_symbols[0].segment == all_segments[0]
+ assert symbols.all_symbols[0].given_name_end == "the_name_end"
+ assert symbols.appears_after_overlays_syms[0] == symbols.all_symbols[0]
+
+ def test_boolean_attrs(self):
+ import pathlib
+
+ symbols.reset_symbols()
+ test_init()
+
+ sym_addrs_lines = [
+ "func_1 = 0x100 // dead:True defined:True extract:True force_migration:True force_not_migration:True "
+ "allow_addend:True dont_allow_addend:True"
+ ]
+
+ all_segments = [
+ Segment(
+ rom_start=0x100,
+ rom_end=0x200,
+ type="func",
+ name="test_segment",
+ vram_start=0x300,
+ args=[],
+ yaml={},
+ )
+ ]
+
+ symbols.handle_sym_addrs(
+ pathlib.Path("/tmp/thing"), sym_addrs_lines, all_segments
+ )
+ assert symbols.all_symbols[0].dead == True
+ assert symbols.all_symbols[0].defined == True
+ assert symbols.all_symbols[0].force_migration == True
+ assert symbols.all_symbols[0].force_not_migration == True
+ assert symbols.all_symbols[0].allow_addend == True
+ assert symbols.all_symbols[0].dont_allow_addend == True
+
+ # test spim ban range
+ def test_ignore(self):
+ import pathlib
+
+ symbols.reset_symbols()
+ test_init()
+
+ sym_addrs_lines = ["func_1 = 0x100 // ignore:True size:4"]
+
+ all_segments = [
+ Segment(
+ rom_start=0x100,
+ rom_end=0x200,
+ type="func",
+ name="test_segment",
+ vram_start=0x300,
+ args=[],
+ yaml={},
+ )
+ ]
+
+ symbols.handle_sym_addrs(
+ pathlib.Path("/tmp/thing"), sym_addrs_lines, all_segments
+ )
+ assert symbols.spim_context.bannedRangedSymbols[0].start == 16
+ assert symbols.spim_context.bannedRangedSymbols[0].end == 20
+
+
+class InitializeSpimContext(unittest.TestCase):
+ def test_overlay(self):
+ symbols.reset_symbols()
+ test_init()
+
+ yaml = {
+ "name": "boot",
+ "type": "code",
+ "start": 4096,
+ "vram": 2147484672,
+ "bss_size": 128,
+ "exclusive_ram_id": "overlay",
+ "subsegments": [
+ [4096, "c", "main"],
+ [4336, "hasm", "handwritten"],
+ [4352, "data", "main"],
+ [4368, "rodata", "main"],
+ {"type": "bss", "vram": 2147484992, "name": "main"},
+ ],
+ }
+
+ all_segments: List["Segment"] = [
+ CommonSegCode(
+ rom_start=0x0,
+ rom_end=0x200,
+ type="code",
+ name="main",
+ vram_start=0x100,
+ args=[],
+ yaml=yaml,
+ )
+ ]
+
+ # force this since it's hard to set up
+ all_segments[0].exclusive_ram_id = "overlay"
+
+ symbols.initialize_spim_context(all_segments)
+ # spim should have added something to overlaySegments
+ assert (
+ type(symbols.spim_context.overlaySegments["overlay"][0])
+ == spimdisasm.common.SymbolsSegment
+ )
+
+ # test globalSegment settings
+ def test_global(self):
+ symbols.reset_symbols()
+ test_init()
+
+ yaml = {
+ "name": "boot",
+ "type": "code",
+ "start": 4096,
+ "vram": 2147484672,
+ "bss_size": 128,
+ "exclusive_ram_id": "overlay",
+ "subsegments": [
+ [4096, "c", "main"],
+ [4336, "hasm", "handwritten"],
+ [4352, "data", "main"],
+ [4368, "rodata", "main"],
+ {"type": "bss", "vram": 2147484992, "name": "main"},
+ ],
+ }
+
+ all_segments: List["Segment"] = [
+ CommonSegCode(
+ rom_start=0x0,
+ rom_end=0x200,
+ type="code",
+ name="main",
+ vram_start=0x100,
+ args=[],
+ yaml=yaml,
+ )
+ ]
+
+ assert symbols.spim_context.globalSegment.vramStart == 2147483648
+ assert symbols.spim_context.globalSegment.vramEnd == 2147487744
+ symbols.initialize_spim_context(all_segments)
+ assert symbols.spim_context.globalSegment.vramStart == 256
+ assert symbols.spim_context.globalSegment.vramEnd == 896
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/splat/test/basic_app/basic_app.ld b/tools/splat/test/basic_app/basic_app.ld
new file mode 100644
index 0000000..7a94e16
--- /dev/null
+++ b/tools/splat/test/basic_app/basic_app.ld
@@ -0,0 +1,78 @@
+SECTIONS
+{
+ __romPos = 0;
+ _gp = 0x0;
+ header_ROM_START = __romPos;
+ header_VRAM = ADDR(.header);
+ .header : AT(header_ROM_START) SUBALIGN(16)
+ {
+ header_DATA_START = .;
+ build/header.o(.data);
+ header_DATA_END = .;
+ header_DATA_SIZE = ABSOLUTE(header_DATA_END - header_DATA_START);
+ }
+ __romPos += SIZEOF(.header);
+ header_ROM_END = __romPos;
+ header_VRAM_END = .;
+
+ dummy_ipl3_ROM_START = __romPos;
+ dummy_ipl3_VRAM = ADDR(.dummy_ipl3);
+ .dummy_ipl3 0xA4000040 : AT(dummy_ipl3_ROM_START) SUBALIGN(16)
+ {
+ dummy_ipl3_DATA_START = .;
+ build/dummy_ipl3.o(.data);
+ dummy_ipl3_DATA_END = .;
+ dummy_ipl3_DATA_SIZE = ABSOLUTE(dummy_ipl3_DATA_END - dummy_ipl3_DATA_START);
+ }
+ __romPos += SIZEOF(.dummy_ipl3);
+ __romPos = ALIGN(__romPos, 16);
+ dummy_ipl3_ROM_END = __romPos;
+ dummy_ipl3_VRAM_END = .;
+
+ boot_ROM_START = __romPos;
+ boot_VRAM = ADDR(.boot);
+ .boot 0x80000400 : AT(boot_ROM_START) SUBALIGN(16)
+ {
+ boot_TEXT_START = .;
+ build/main.o(.text);
+ build/handwritten.o(.text);
+ boot_TEXT_END = .;
+ boot_TEXT_SIZE = ABSOLUTE(boot_TEXT_END - boot_TEXT_START);
+
+ boot_DATA_START = .;
+ build/main.o(.data);
+ build/handwritten.o(.data);
+ boot_DATA_END = .;
+ boot_DATA_SIZE = ABSOLUTE(boot_DATA_END - boot_DATA_START);
+
+ boot_RODATA_START = .;
+ build/main.o(.rodata);
+ build/handwritten.o(.rodata);
+ boot_RODATA_END = .;
+ boot_RODATA_SIZE = ABSOLUTE(boot_RODATA_END - boot_RODATA_START);
+ }
+
+ boot_bss_VRAM = ADDR(.boot_bss);
+ .boot_bss (NOLOAD) : SUBALIGN(16)
+ {
+ boot_BSS_START = .;
+ build/main.o(.bss);
+ build/main.o(.scommon);
+ build/main.o(COMMON);
+ build/handwritten.o(.bss);
+ build/handwritten.o(.scommon);
+ build/handwritten.o(COMMON);
+ boot_BSS_END = .;
+ boot_BSS_SIZE = ABSOLUTE(boot_BSS_END - boot_BSS_START);
+ }
+
+ __romPos += SIZEOF(.boot);
+ __romPos = ALIGN(__romPos, 16);
+ boot_ROM_END = __romPos;
+ boot_VRAM_END = .;
+
+ /DISCARD/ :
+ {
+ *(*);
+ }
+}
diff --git a/tools/splat/test/basic_app/build.sh b/tools/splat/test/basic_app/build.sh
new file mode 100755
index 0000000..0e11e36
--- /dev/null
+++ b/tools/splat/test/basic_app/build.sh
@@ -0,0 +1,12 @@
+mkdir -p build
+echo "Building..."
+export PATH=/gcc-2.7.2:$PATH
+COMPILER_PATH=/gcc-2.7.2 /gcc-2.7.2/gcc -v -G0 -mgp32 -mfp32 -mips3 main.c -c -o build/main.o
+COMPILER_PATH=/gcc-2.7.2 /gcc-2.7.2/gcc -G0 -mgp32 -mfp32 -mips3 header.s -c -o build/header.o
+COMPILER_PATH=/gcc-2.7.2 /gcc-2.7.2/gcc -G0 -mgp32 -mfp32 -mips3 handwritten.s -c -o build/handwritten.o
+COMPILER_PATH=/gcc-2.7.2 /gcc-2.7.2/gcc -G0 -mgp32 -mfp32 -mips3 dummy_ipl3.s -c -o build/dummy_ipl3.o
+mips-linux-gnu-strip -N dummy_symbol_name build/*.o
+echo "Linking..."
+mips-linux-gnu-ld -Map build/basic_app.map -T basic_app.ld -o build/basic_app.elf
+echo "Dumping bin..."
+mips-linux-gnu-objcopy build/basic_app.elf -O binary build/basic_app.bin --pad-to=0x1140 --gap-fill=0xFF
diff --git a/tools/splat/test/basic_app/clean.sh b/tools/splat/test/basic_app/clean.sh
new file mode 100644
index 0000000..2fcd367
--- /dev/null
+++ b/tools/splat/test/basic_app/clean.sh
@@ -0,0 +1,2 @@
+rm -rf ./build
+rm -rf ./split
diff --git a/tools/splat/test/basic_app/dummy_ipl3.s b/tools/splat/test/basic_app/dummy_ipl3.s
new file mode 100644
index 0000000..fc223a1
--- /dev/null
+++ b/tools/splat/test/basic_app/dummy_ipl3.s
@@ -0,0 +1,3 @@
+.section .data
+dummy_ipl3:
+.zero 0x1000 - 0x40
diff --git a/tools/splat/test/basic_app/expected/.splache b/tools/splat/test/basic_app/expected/.splache
new file mode 100644
index 0000000..c855f15
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/.splache
Binary files differ
diff --git a/tools/splat/test/basic_app/expected/asm/data/main.bss.s b/tools/splat/test/basic_app/expected/asm/data/main.bss.s
new file mode 100644
index 0000000..96a20c2
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/data/main.bss.s
@@ -0,0 +1,6 @@
+.include "macro.inc"
+
+.section .bss
+
+glabel D_80000540
+/* 1110 80000540 */ .space 0x78
diff --git a/tools/splat/test/basic_app/expected/asm/data/main.data.s b/tools/splat/test/basic_app/expected/asm/data/main.data.s
new file mode 100644
index 0000000..1ed582a
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/data/main.data.s
@@ -0,0 +1,13 @@
+.include "macro.inc"
+
+.section .data
+
+glabel D_80000500
+/* 1100 80000500 */ .word 0x00000001
+.size D_80000500, . - D_80000500
+
+glabel D_80000504
+/* 1104 80000504 */ .word 0x00000000
+/* 1108 80000508 */ .word 0x00000000
+/* 110C 8000050C */ .word 0x00000000
+.size D_80000504, . - D_80000504
diff --git a/tools/splat/test/basic_app/expected/asm/handwritten.s b/tools/splat/test/basic_app/expected/asm/handwritten.s
new file mode 100644
index 0000000..539d92a
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/handwritten.s
@@ -0,0 +1,16 @@
+.include "macro.inc"
+
+/* assembler directives */
+.set noat /* allow manual use of $at */
+.set noreorder /* don't insert nops after branches */
+.set gp=64 /* allow use of 64-bit general purpose registers */
+
+.section .text, "ax"
+
+# Handwritten function
+glabel func_800004F0
+/* 10F0 800004F0 00851020 */ add $v0, $a0, $a1 # handwritten instruction
+/* 10F4 800004F4 03E00008 */ jr $ra
+/* 10F8 800004F8 00000000 */ nop
+/* 10FC 800004FC 00000000 */ nop
+.size func_800004F0, . - func_800004F0
diff --git a/tools/splat/test/basic_app/expected/asm/header.s b/tools/splat/test/basic_app/expected/asm/header.s
new file mode 100644
index 0000000..be176b7
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/header.s
@@ -0,0 +1,16 @@
+.section .data
+
+.word 0x80371240 /* PI BSB Domain 1 register */
+.word 0x0000000F /* Clockrate setting */
+.word 0x800004A0 /* Entrypoint address */
+.word 0x00001444 /* Revision */
+.word 0x00000000 /* Checksum 1 */
+.word 0x00000000 /* Checksum 2 */
+.word 0x00000000 /* Unknown 1 */
+.word 0x00000000 /* Unknown 2 */
+.ascii "SPLAT TEST ROM " /* Internal name */
+.word 0x00000000 /* Unknown 3 */
+.word 0x0000004E /* Cartridge */
+.ascii "SP" /* Cartridge ID */
+.ascii "E" /* Country code */
+.byte 0x00 /* Version */
diff --git a/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_80000400.s b/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_80000400.s
new file mode 100644
index 0000000..21d0010
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_80000400.s
@@ -0,0 +1,55 @@
+.set noat /* allow manual use of $at */
+.set noreorder /* don't insert nops after branches */
+
+glabel func_80000400
+/* 1000 80000400 27BDFFF8 */ addiu $sp, $sp, -0x8
+/* 1004 80000404 AFBE0000 */ sw $fp, 0x0($sp)
+/* 1008 80000408 03A0F021 */ addu $fp, $sp, $zero
+/* 100C 8000040C 3C028000 */ lui $v0, %hi(D_80000504)
+/* 1010 80000410 8C420504 */ lw $v0, %lo(D_80000504)($v0)
+/* 1014 80000414 2C430008 */ sltiu $v1, $v0, 0x8
+/* 1018 80000418 1060001B */ beqz $v1, .L80000488
+/* 101C 8000041C 00000000 */ nop
+/* 1020 80000420 3C028000 */ lui $v0, %hi(D_80000504)
+/* 1024 80000424 8C420504 */ lw $v0, %lo(D_80000504)($v0)
+/* 1028 80000428 00401821 */ addu $v1, $v0, $zero
+/* 102C 8000042C 00031080 */ sll $v0, $v1, 2
+/* 1030 80000430 3C038000 */ lui $v1, %hi(jtbl_80000518)
+/* 1034 80000434 24630518 */ addiu $v1, $v1, %lo(jtbl_80000518)
+/* 1038 80000438 00431021 */ addu $v0, $v0, $v1
+/* 103C 8000043C 8C430000 */ lw $v1, 0x0($v0)
+/* 1040 80000440 00600008 */ jr $v1
+/* 1044 80000444 00000000 */ nop
+glabel .L80000448
+/* 1048 80000448 08000124 */ j .L80000490
+/* 104C 8000044C 24020007 */ addiu $v0, $zero, 0x7
+glabel .L80000450
+/* 1050 80000450 08000124 */ j .L80000490
+/* 1054 80000454 24020006 */ addiu $v0, $zero, 0x6
+glabel .L80000458
+/* 1058 80000458 08000124 */ j .L80000490
+/* 105C 8000045C 24020005 */ addiu $v0, $zero, 0x5
+glabel .L80000460
+/* 1060 80000460 08000124 */ j .L80000490
+/* 1064 80000464 24020004 */ addiu $v0, $zero, 0x4
+glabel .L80000468
+/* 1068 80000468 08000124 */ j .L80000490
+/* 106C 8000046C 24020003 */ addiu $v0, $zero, 0x3
+glabel .L80000470
+/* 1070 80000470 08000124 */ j .L80000490
+/* 1074 80000474 24020002 */ addiu $v0, $zero, 0x2
+glabel .L80000478
+/* 1078 80000478 08000124 */ j .L80000490
+/* 107C 8000047C 24020001 */ addiu $v0, $zero, 0x1
+glabel .L80000480
+/* 1080 80000480 08000124 */ j .L80000490
+/* 1084 80000484 00001021 */ addu $v0, $zero, $zero
+.L80000488:
+/* 1088 80000488 08000124 */ j .L80000490
+/* 108C 8000048C 00001021 */ addu $v0, $zero, $zero
+.L80000490:
+/* 1090 80000490 03C0E821 */ addu $sp, $fp, $zero
+/* 1094 80000494 8FBE0000 */ lw $fp, 0x0($sp)
+/* 1098 80000498 03E00008 */ jr $ra
+/* 109C 8000049C 27BD0008 */ addiu $sp, $sp, 0x8
+.size func_80000400, . - func_80000400
diff --git a/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_800004A0.s b/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_800004A0.s
new file mode 100644
index 0000000..112eb68
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/asm/nonmatchings/main/func_800004A0.s
@@ -0,0 +1,26 @@
+.set noat /* allow manual use of $at */
+.set noreorder /* don't insert nops after branches */
+
+glabel func_800004A0
+/* 10A0 800004A0 27BDFFE8 */ addiu $sp, $sp, -0x18
+/* 10A4 800004A4 AFBF0014 */ sw $ra, 0x14($sp)
+/* 10A8 800004A8 AFBE0010 */ sw $fp, 0x10($sp)
+/* 10AC 800004AC 0C000100 */ jal func_80000400
+/* 10B0 800004B0 03A0F021 */ addu $fp, $sp, $zero
+.L800004B4:
+/* 10B4 800004B4 3C028000 */ lui $v0, %hi(D_80000500)
+/* 10B8 800004B8 8C420500 */ lw $v0, %lo(D_80000500)($v0)
+/* 10BC 800004BC 24430001 */ addiu $v1, $v0, 0x1
+/* 10C0 800004C0 3C018000 */ lui $at, %hi(D_80000500)
+/* 10C4 800004C4 AC230500 */ sw $v1, %lo(D_80000500)($at)
+/* 10C8 800004C8 3C028000 */ lui $v0, %hi(D_80000500)
+/* 10CC 800004CC 8C420500 */ lw $v0, %lo(D_80000500)($v0)
+/* 10D0 800004D0 0800012D */ j .L800004B4
+/* 10D4 800004D4 00000000 */ nop
+/* 10D8 800004D8 03C0E821 */ addu $sp, $fp, $zero
+/* 10DC 800004DC 8FBF0014 */ lw $ra, 0x14($sp)
+/* 10E0 800004E0 8FBE0010 */ lw $fp, 0x10($sp)
+/* 10E4 800004E4 03E00008 */ jr $ra
+/* 10E8 800004E8 27BD0018 */ addiu $sp, $sp, 0x18
+/* 10EC 800004EC 00000000 */ nop
+.size func_800004A0, . - func_800004A0
diff --git a/tools/splat/test/basic_app/expected/assets/dummy_ipl3.bin b/tools/splat/test/basic_app/expected/assets/dummy_ipl3.bin
new file mode 100644
index 0000000..26460b7
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/assets/dummy_ipl3.bin
Binary files differ
diff --git a/tools/splat/test/basic_app/expected/basic_app.ld b/tools/splat/test/basic_app/expected/basic_app.ld
new file mode 100644
index 0000000..0779b7e
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/basic_app.ld
@@ -0,0 +1,70 @@
+SECTIONS
+{
+ __romPos = 0;
+ _gp = 0x0;
+ header_ROM_START = __romPos;
+ header_VRAM = ADDR(.header);
+ .header : AT(header_ROM_START) SUBALIGN(16)
+ {
+ header_DATA_START = .;
+ header_s = .;
+ build/split/asm/header.s.o(.data);
+ header_DATA_END = .;
+ header_DATA_SIZE = ABSOLUTE(header_DATA_END - header_DATA_START);
+ }
+ __romPos += SIZEOF(.header);
+ header_ROM_END = __romPos;
+ header_VRAM_END = .;
+
+ dummy_ipl3_ROM_START = __romPos;
+ dummy_ipl3_VRAM = ADDR(.dummy_ipl3);
+ .dummy_ipl3 0xA4000040 : AT(dummy_ipl3_ROM_START) SUBALIGN(16)
+ {
+ dummy_ipl3_DATA_START = .;
+ dummy_ipl3_bin = .;
+ build/split/assets/dummy_ipl3.bin.o(.data);
+ dummy_ipl3_DATA_END = .;
+ dummy_ipl3_DATA_SIZE = ABSOLUTE(dummy_ipl3_DATA_END - dummy_ipl3_DATA_START);
+ }
+ __romPos += SIZEOF(.dummy_ipl3);
+ __romPos = ALIGN(__romPos, 16);
+ dummy_ipl3_ROM_END = __romPos;
+ dummy_ipl3_VRAM_END = .;
+
+ boot_ROM_START = __romPos;
+ boot_VRAM = ADDR(.boot);
+ .boot 0x80000400 : AT(boot_ROM_START) SUBALIGN(16)
+ {
+ boot_TEXT_START = .;
+ build/split/src/main.c.o(.text);
+ build/split/asm/handwritten.s.o(.text);
+ boot_TEXT_END = .;
+ boot_TEXT_SIZE = ABSOLUTE(boot_TEXT_END - boot_TEXT_START);
+ boot_DATA_START = .;
+ main_data__s = .;
+ build/split/asm/data/main.data.s.o(.data);
+ boot_DATA_END = .;
+ boot_DATA_SIZE = ABSOLUTE(boot_DATA_END - boot_DATA_START);
+ boot_RODATA_START = .;
+ build/split/asm/data/main.rodata.s.o(.rodata);
+ boot_RODATA_END = .;
+ boot_RODATA_SIZE = ABSOLUTE(boot_RODATA_END - boot_RODATA_START);
+ }
+ boot_bss_VRAM = ADDR(.boot_bss);
+ .boot_bss (NOLOAD) : SUBALIGN(16)
+ {
+ boot_BSS_START = .;
+ build/split/asm/data/main.bss.s.o(.bss);
+ boot_BSS_END = .;
+ boot_BSS_SIZE = ABSOLUTE(boot_BSS_END - boot_BSS_START);
+ }
+ __romPos += SIZEOF(.boot);
+ __romPos = ALIGN(__romPos, 16);
+ boot_ROM_END = __romPos;
+ boot_VRAM_END = .;
+
+ /DISCARD/ :
+ {
+ *(*);
+ }
+}
diff --git a/tools/splat/test/basic_app/expected/src/main.c b/tools/splat/test/basic_app/expected/src/main.c
new file mode 100644
index 0000000..41b2e70
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/src/main.c
@@ -0,0 +1,5 @@
+#include "common.h"
+
+INCLUDE_ASM(const s32, "main", func_80000400);
+
+INCLUDE_ASM(const s32, "main", func_800004A0);
diff --git a/tools/splat/test/basic_app/expected/undefined_funcs_auto.txt b/tools/splat/test/basic_app/expected/undefined_funcs_auto.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/undefined_funcs_auto.txt
diff --git a/tools/splat/test/basic_app/expected/undefined_syms_auto.txt b/tools/splat/test/basic_app/expected/undefined_syms_auto.txt
new file mode 100644
index 0000000..8b96b0c
--- /dev/null
+++ b/tools/splat/test/basic_app/expected/undefined_syms_auto.txt
@@ -0,0 +1 @@
+jtbl_80000518 = 0x80000518;
diff --git a/tools/splat/test/basic_app/handwritten.s b/tools/splat/test/basic_app/handwritten.s
new file mode 100644
index 0000000..1a5f482
--- /dev/null
+++ b/tools/splat/test/basic_app/handwritten.s
@@ -0,0 +1,12 @@
+.section .text
+.balign 16
+.set noreorder
+
+.global asm_func
+.type asm_func, @function
+.ent asm_func
+asm_func:
+ add $v0, $a0, $a1 # Instruction that a compiler will never emit
+ jr $ra
+ nop
+.end asm_func
diff --git a/tools/splat/test/basic_app/header.s b/tools/splat/test/basic_app/header.s
new file mode 100644
index 0000000..1e19f59
--- /dev/null
+++ b/tools/splat/test/basic_app/header.s
@@ -0,0 +1,16 @@
+.section .data
+
+.word 0x80371240 /* PI BSB Domain 1 register */
+.word 0x0000000F /* Clockrate setting */
+.word bootproc /* Entrypoint address */
+.word 0x00001444 /* Revision */
+.word 0x00000000 /* Checksum 1 */
+.word 0x00000000 /* Checksum 2 */
+.word 0x00000000 /* Unknown 1 */
+.word 0x00000000 /* Unknown 2 */
+.ascii "SPLAT TEST ROM " /* Internal name */
+.word 0x00000000 /* Unknown 3 */
+.word 0x0000004E /* Cartridge */
+.ascii "SP" /* Cartridge ID */
+.ascii "E" /* Country code */
+.byte 0x00 /* Version */
diff --git a/tools/splat/test/basic_app/main.c b/tools/splat/test/basic_app/main.c
new file mode 100644
index 0000000..cf119f7
--- /dev/null
+++ b/tools/splat/test/basic_app/main.c
@@ -0,0 +1,45 @@
+// .data
+volatile int test = 1;
+
+// bin (.rodata)
+const char bin_data[] = {0,1,2,3,4,5,6,7};
+
+// .bss
+unsigned long long bss_data[0x10];
+
+// .data
+volatile int switch_arg = 0;
+
+int do_switch()
+{
+ switch(switch_arg)
+ {
+ case 0:
+ return 7;
+ case 1:
+ return 6;
+ case 2:
+ return 5;
+ case 3:
+ return 4;
+ case 4:
+ return 3;
+ case 5:
+ return 2;
+ case 6:
+ return 1;
+ case 7:
+ return 0;
+ }
+ return 0;
+}
+
+// c
+void bootproc()
+{
+ do_switch();
+ for (;;)
+ {
+ test++;
+ }
+}
diff --git a/tools/splat/test/basic_app/splat.yaml b/tools/splat/test/basic_app/splat.yaml
new file mode 100644
index 0000000..c1d2cf9
--- /dev/null
+++ b/tools/splat/test/basic_app/splat.yaml
@@ -0,0 +1,38 @@
+options:
+ platform: n64
+ basename: basic_app
+ base_path: .
+ build_path: build
+ target_path: build/basic_app.bin
+ asm_path: split/asm
+ src_path: split/src
+ ld_script_path: split/basic_app.ld
+ cache_path: split/.splache
+ symbol_addrs_path: split/generated.symbols.txt
+ undefined_funcs_auto_path: split/undefined_funcs_auto.txt
+ undefined_syms_auto_path: split/undefined_syms_auto.txt
+ asset_path: split/assets
+ asm_generated_by: False
+ compiler: GCC
+segments:
+ - name: header
+ type: header
+ start: 0x00
+ - name: dummy_ipl3
+ type: code
+ start: 0x40
+ vram: 0xA4000040
+ subsegments:
+ - [0x0040, bin, dummy_ipl3]
+ - name: boot
+ type: code
+ start: 0x1000
+ vram: 0x80000400
+ bss_size: 0x80
+ subsegments:
+ - [0x1000, c, main]
+ - [0x10F0, hasm, handwritten]
+ - [0x1100, data, main]
+ - [0x1110, rodata, main]
+ - { type: bss, vram: 0x80000540, name: main }
+ - [0x1138]
diff --git a/tools/splat/test_gen_expected.sh b/tools/splat/test_gen_expected.sh
new file mode 100644
index 0000000..1b4a629
--- /dev/null
+++ b/tools/splat/test_gen_expected.sh
@@ -0,0 +1,3 @@
+docker run --rm -it -v $(pwd):/splat -w /splat/test/basic_app splat-build sh build.sh && \
+rm -rf $(pwd)/test/basic_app/expected && \
+cp -r $(pwd)/test/basic_app/split $(pwd)/test/basic_app/expected
diff --git a/tools/splat/util/n64/Yay0decompress.py b/tools/splat/util/n64/Yay0decompress.py
index ca24c23..780d1cb 100644
--- a/tools/splat/util/n64/Yay0decompress.py
+++ b/tools/splat/util/n64/Yay0decompress.py
@@ -1,9 +1,9 @@
import argparse
import os
import sys
-from ctypes import *
+from ctypes import CDLL, Structure, byref, c_bool, c_uint32, c_uint8, cdll
from struct import pack, unpack_from
-from typing import Optional
+from typing import Literal, Optional
try:
from .. import log
@@ -23,8 +23,8 @@ def setup_lib():
return True
if tried_loading:
return False
+ tried_loading = True
try:
- tried_loading = True
lib = cdll.LoadLibrary(
os.path.dirname(os.path.realpath(__file__)) + "/Yay0decompress"
)
@@ -39,11 +39,12 @@ def setup_lib():
class Yay0Decompressor(Decompressor):
- def decompress(self, in_bytes, byte_order="big") -> bytearray:
+ @staticmethod
+ def decompress(in_bytes, byte_order: Literal["little", "big"] = "big") -> bytearray:
# attempt to load the library only once per execution
global lib
if not setup_lib():
- return self.decompress_python(in_bytes, byte_order)
+ return Yay0Decompressor.decompress_python(in_bytes, byte_order)
assert lib is not None
class Yay0(Structure):
@@ -81,7 +82,7 @@ class Yay0Decompressor(Decompressor):
return bytearray(dst)
@staticmethod
- def decompress_python(in_bytes, byte_order="big"):
+ def decompress_python(in_bytes, byte_order: Literal["little", "big"] = "big"):
if in_bytes[:4] != b"Yay0":
log.error("Input file is not Yay0")
@@ -151,8 +152,7 @@ def main():
with open(args.infile, "rb") as f:
raw_bytes = f.read()
- yay0decompressor = Yay0Decompressor()
- decompressed = yay0decompressor.decompress(raw_bytes, args.byte_order)
+ decompressed = Yay0Decompressor.decompress(raw_bytes, args.byte_order)
with open(args.outfile, "wb") as f:
f.write(decompressed)
diff --git a/tools/splat/util/n64/decompressor.py b/tools/splat/util/n64/decompressor.py
index cc5ddb8..7df0de0 100644
--- a/tools/splat/util/n64/decompressor.py
+++ b/tools/splat/util/n64/decompressor.py
@@ -1,7 +1,10 @@
from abc import ABC, abstractmethod
+from typing import Literal
class Decompressor(ABC):
@abstractmethod
- def decompress(self, in_bytes, byte_order="big") -> bytearray:
+ def decompress(
+ self, in_bytes, byte_order: Literal["little", "big"] = "big"
+ ) -> bytearray:
pass
diff --git a/tools/splat/util/n64/rominfo.py b/tools/splat/util/n64/rominfo.py
index fae1fee..cede4de 100755
--- a/tools/splat/util/n64/rominfo.py
+++ b/tools/splat/util/n64/rominfo.py
@@ -31,8 +31,8 @@ country_codes = {
0x00: "Unknown",
0x37: "Beta",
0x41: "Asian (NTSC)",
- 0x42: "Brazillian",
- 0x43: "Chiniese",
+ 0x42: "Brazilian",
+ 0x43: "Chinese",
0x44: "German",
0x45: "North America",
0x46: "French",
@@ -46,7 +46,7 @@ country_codes = {
0x50: "European (basic spec.)",
0x53: "Spanish",
0x55: "Australian",
- 0x57: "Scandanavian",
+ 0x57: "Scandinavian",
0x58: "European",
0x59: "European",
}
@@ -106,7 +106,7 @@ class N64EntrypointInfo:
register_values[insn.rt.value] = insn.getProcessedImmediate() << 16
elif insn.canBeLo():
if insn.isLikelyHandwritten():
- # Try to skip this instructions:
+ # Try to skip these instructions:
# addi $t0, $t0, 0x8
# addi $t1, $t1, -0x8
pass
@@ -241,7 +241,7 @@ def get_info_bytes(rom_bytes: bytes, header_encoding: str) -> N64Rom:
checksum = rom_bytes[0x10:0x18].hex().upper()
try:
- name = rom_bytes[0x20:0x34].decode(header_encoding).strip()
+ name = rom_bytes[0x20:0x34].decode(header_encoding).rstrip(" \0") or "empty"
except:
sys.exit(
"splat could not decode the game name;"
diff --git a/tools/splat/util/options.py b/tools/splat/util/options.py
index a8a3eba..2c09363 100644
--- a/tools/splat/util/options.py
+++ b/tools/splat/util/options.py
@@ -1,4 +1,5 @@
from dataclasses import dataclass
+import os
from pathlib import Path
from typing import cast, Dict, List, Literal, Mapping, Optional, Set, Type, TypeVar
@@ -98,9 +99,11 @@ class SplatOpts:
ld_section_labels: List[str]
# Determines whether to add wildcards for section linking in the linker script (.rodata* for example)
ld_wildcard_sections: bool
- # Determines whether to use use "follows" settings to determine locations of overlays in the linker script.
+ # Determines whether to use "follows" settings to determine locations of overlays in the linker script.
# If disabled, this effectively ignores "follows" directives in the yaml.
ld_use_follows: bool
+ # If enabled, the end symbol for each segment will be placed before the alignment directive for the segment
+ segment_end_before_align: bool
################################################################################
# C file options
@@ -139,6 +142,8 @@ class SplatOpts:
asm_data_macro: str
# Determines the macro used at the end of a function, such as endlabel or .end
asm_end_label: str
+ # Toggles the .size directive emitted by the disassembler
+ asm_emit_size_directive: Optional[bool]
# Determines including the macro.inc file on non-migrated rodata variables
include_macro_inc: bool
# Determines the number of characters to left align before the TODO finish documenting
@@ -152,14 +157,24 @@ class SplatOpts:
# o32 is highly recommended, as it provides logically named registers for floating point instructions
# For more info, see https://gist.github.com/EllipticEllipsis/27eef11205c7a59d8ea85632bc49224d
mips_abi_float_regs: str
- # Determines whether to ad ".set gp=64 to asm/hasm files"
+ # Determines whether to add ".set gp=64" to asm/hasm files
add_set_gp_64: bool
# Generate .asmproc.d dependency files for each C file which still reference functions in assembly files
create_asm_dependencies: bool
# Global option for rodata string encoding. This can be overriden per segment
string_encoding: Optional[str]
+ # Global option for data string encoding. This can be overriden per segment
+ data_string_encoding: Optional[str]
+ # Global option for the rodata string guesser. 0 disables the guesser completely.
+ rodata_string_guesser_level: Optional[int]
+ # Global option for the data string guesser. 0 disables the guesser completely.
+ data_string_guesser_level: Optional[int]
# Global option for allowing data symbols using addends on symbol references. It can be overriden per symbol
allow_data_addends: bool
+ # Determines whether to include the "Generated by spimdisasm" text in the asm
+ asm_generated_by: bool
+ # Tells the disassembler to try disassembling functions with unknown instructions instead of falling back to disassembling as raw data
+ disasm_unknown: bool
################################################################################
# N64-specific options
@@ -234,7 +249,7 @@ class OptParser:
def parse_path(
self, base_path: Path, opt: str, default: Optional[str] = None
) -> Path:
- return base_path / Path(self.parse_opt(opt, str, default))
+ return Path(os.path.normpath(base_path / self.parse_opt(opt, str, default)))
def parse_optional_path(self, base_path: Path, opt: str) -> Optional[Path]:
if opt not in self._yaml:
@@ -269,7 +284,9 @@ def _parse_yaml(
platform = p.parse_opt_within("platform", str, ["n64", "psx", "gc", "ps2"])
comp = compiler.for_name(p.parse_opt("compiler", str, "IDO"))
- base_path = Path(config_paths[0]).parent / p.parse_opt("base_path", str)
+ base_path = Path(
+ os.path.normpath(Path(config_paths[0]).parent / p.parse_opt("base_path", str))
+ )
asm_path: Path = p.parse_path(base_path, "asm_path", "asm")
def parse_endianness() -> Literal["big", "little"]:
@@ -347,6 +364,7 @@ def _parse_yaml(
),
ld_wildcard_sections=p.parse_opt("ld_wildcard_sections", bool, False),
ld_use_follows=p.parse_opt("ld_use_follows", bool, True),
+ segment_end_before_align=p.parse_opt("segment_end_before_align", bool, False),
create_c_files=p.parse_opt("create_c_files", bool, True),
auto_decompile_empty_functions=p.parse_opt(
"auto_decompile_empty_functions", bool, True
@@ -371,6 +389,7 @@ def _parse_yaml(
),
asm_data_macro=p.parse_opt("asm_data_macro", str, comp.asm_data_macro),
asm_end_label=p.parse_opt("asm_end_label", str, comp.asm_end_label),
+ asm_emit_size_directive=p.parse_optional_opt("asm_emit_size_directive", bool),
include_macro_inc=p.parse_opt(
"include_macro_inc", bool, comp.include_macro_inc
),
@@ -391,6 +410,13 @@ def _parse_yaml(
add_set_gp_64=p.parse_opt("add_set_gp_64", bool, True),
create_asm_dependencies=p.parse_opt("create_asm_dependencies", bool, False),
string_encoding=p.parse_optional_opt("string_encoding", str),
+ data_string_encoding=p.parse_optional_opt("data_string_encoding", str),
+ rodata_string_guesser_level=p.parse_optional_opt(
+ "rodata_string_guesser_level", int
+ ),
+ data_string_guesser_level=p.parse_optional_opt(
+ "data_string_guesser_level", int
+ ),
allow_data_addends=p.parse_opt("allow_data_addends", bool, True),
header_encoding=p.parse_opt("header_encoding", str, "ASCII"),
gfx_ucode=p.parse_opt_within(
@@ -403,6 +429,8 @@ def _parse_yaml(
hardware_regs=p.parse_opt("hardware_regs", bool, False),
use_legacy_include_asm=p.parse_opt("use_legacy_include_asm", bool, True),
filesystem_path=p.parse_optional_path(base_path, "filesystem_path"),
+ asm_generated_by=p.parse_opt("asm_generated_by", bool, True),
+ disasm_unknown=p.parse_opt("disasm_unknown", bool, False),
)
p.check_no_unread_opts()
return ret
diff --git a/tools/splat/util/symbols.py b/tools/splat/util/symbols.py
index 07db03c..9a6bc68 100644
--- a/tools/splat/util/symbols.py
+++ b/tools/splat/util/symbols.py
@@ -5,6 +5,8 @@ from typing import Dict, List, Optional, Set, TYPE_CHECKING
import spimdisasm
import tqdm
from intervaltree import IntervalTree
+from disassembler import disassembler_instance
+from pathlib import Path
# circular import
if TYPE_CHECKING:
@@ -35,7 +37,7 @@ def check_valid_type(typename: str) -> bool:
if typename in splat_sym_types:
return True
- if typename in spimdisasm.common.gKnownTypes:
+ if typename in disassembler_instance.get_instance().known_types():
return True
return False
@@ -70,196 +72,190 @@ def to_cname(symbol_name: str) -> str:
return symbol_name
-def initialize(all_segments: "List[Segment]"):
- global all_symbols
- global all_symbols_dict
- global all_symbols_ranges
-
- all_symbols = []
- all_symbols_dict = {}
- all_symbols_ranges = IntervalTree()
-
+def handle_sym_addrs(path: Path, sym_addrs_lines: List[str], all_segments):
def get_seg_for_name(name: str) -> Optional["Segment"]:
for segment in all_segments:
if segment.name == name:
return segment
return None
- # Manual list of func name / addrs
- for path in options.opts.symbol_addrs_paths:
- if path.exists():
- with open(path) as f:
- sym_addrs_lines = f.readlines()
- for line_num, line in enumerate(
- tqdm.tqdm(sym_addrs_lines, desc=f"Loading symbols ({path.stem})")
- ):
- line = line.strip()
- if not line == "" and not line.startswith("//"):
- comment_loc = line.find("//")
- line_main = line
- line_ext = ""
-
- if comment_loc != -1:
- line_ext = line[comment_loc + 2 :].strip()
- line_main = line[:comment_loc].strip()
+ for line_num, line in enumerate(
+ tqdm.tqdm(sym_addrs_lines, desc=f"Loading symbols ({path.stem})")
+ ):
+ line = line.strip()
+ if not line == "" and not line.startswith("//"):
+ comment_loc = line.find("//")
+ line_main = line
+ line_ext = ""
+
+ if comment_loc != -1:
+ line_ext = line[comment_loc + 2 :].strip()
+ line_main = line[:comment_loc].strip()
+
+ try:
+ line_split = line_main.split("=")
+ name = line_split[0].strip()
+ addr = int(line_split[1].strip()[:-1], 0)
+ except:
+ log.parsing_error_preamble(path, line_num, line)
+ log.write("Line should be of the form")
+ log.write("<function_name> = <address> // attr0:val0 attr1:val1 [...]")
+ log.write("with <address> in hex preceded by 0x, or dec")
+ log.write("")
+ raise
+
+ sym = Symbol(addr, given_name=name)
+
+ ignore_sym = False
+ if line_ext:
+ for info in line_ext.split(" "):
+ if ":" in info:
+ if info.count(":") > 1:
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(f"Too many ':'s in '{info}'")
+ log.error("")
+
+ attr_name, attr_val = info.split(":")
+ if attr_name == "":
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(
+ f"Missing attribute name in '{info}', is there extra whitespace?"
+ )
+ log.error("")
+ if attr_val == "":
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(
+ f"Missing attribute value in '{info}', is there extra whitespace?"
+ )
+ log.error("")
+ # Non-Boolean attributes
try:
- line_split = line_main.split("=")
- name = line_split[0].strip()
- addr = int(line_split[1].strip()[:-1], 0)
+ if attr_name == "type":
+ if not check_valid_type(attr_val):
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(
+ f"Unrecognized symbol type in '{info}', it should be one of"
+ )
+ log.write(
+ [
+ *splat_sym_types,
+ *spimdisasm.common.gKnownTypes,
+ ]
+ )
+ log.write(
+ "You may use a custom type that starts with a capital letter"
+ )
+ log.error("")
+ type = attr_val
+ sym.type = type
+ continue
+ if attr_name == "size":
+ size = int(attr_val, 0)
+ sym.given_size = size
+ continue
+ if attr_name == "rom":
+ rom_addr = int(attr_val, 0)
+ sym.rom = rom_addr
+ continue
+ if attr_name == "segment":
+ seg = get_seg_for_name(attr_val)
+ if seg is None:
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(f"Cannot find segment '{attr_val}'")
+ log.error("")
+ else:
+ # Add segment to symbol
+ sym.segment = seg
+ continue
+ if attr_name == "name_end":
+ sym.given_name_end = attr_val
+ continue
+ if attr_name == "appears_after_overlays_addr":
+ sym.appears_after_overlays_addr = int(attr_val, 0)
+ appears_after_overlays_syms.append(sym)
+ continue
except:
log.parsing_error_preamble(path, line_num, line)
- log.write("Line should be of the form")
log.write(
- "<function_name> = <address> // attr0:val0 attr1:val1 [...]"
+ f"value of attribute '{attr_name}' could not be read:"
)
- log.write("with <address> in hex preceded by 0x, or dec")
log.write("")
raise
- sym = Symbol(addr, given_name=name)
-
- ignore_sym = False
- if line_ext:
- for info in line_ext.split(" "):
- if ":" in info:
- if info.count(":") > 1:
- log.parsing_error_preamble(path, line_num, line)
- log.write(f"Too many ':'s in '{info}'")
- log.error("")
-
- attr_name, attr_val = info.split(":")
- if attr_name == "":
- log.parsing_error_preamble(path, line_num, line)
- log.write(
- f"Missing attribute name in '{info}', is there extra whitespace?"
- )
- log.error("")
- if attr_val == "":
- log.parsing_error_preamble(path, line_num, line)
- log.write(
- f"Missing attribute value in '{info}', is there extra whitespace?"
- )
- log.error("")
-
- # Non-Boolean attributes
- try:
- if attr_name == "type":
- if not check_valid_type(attr_val):
- log.parsing_error_preamble(
- path, line_num, line
- )
- log.write(
- f"Unrecognized symbol type in '{info}', it should be one of"
- )
- log.write(
- [
- *splat_sym_types,
- *spimdisasm.common.gKnownTypes,
- ]
- )
- log.write(
- "You may use a custom type that starts with a capital letter"
- )
- log.error("")
- type = attr_val
- sym.type = type
- continue
- if attr_name == "size":
- size = int(attr_val, 0)
- sym.given_size = size
- continue
- if attr_name == "rom":
- rom_addr = int(attr_val, 0)
- sym.rom = rom_addr
- continue
- if attr_name == "segment":
- seg = get_seg_for_name(attr_val)
- if seg is None:
- log.parsing_error_preamble(
- path, line_num, line
- )
- log.write(
- f"Cannot find segment '{attr_val}'"
- )
- log.error("")
- else:
- # Add segment to symbol
- sym.segment = seg
- continue
- if attr_name == "name_end":
- sym.given_name_end = attr_val
- continue
- if attr_name == "appears_after_overlays_addr":
- sym.appears_after_overlays_addr = int(
- attr_val, 0
- )
- appears_after_overlays_syms.append(sym)
- continue
- except:
- log.parsing_error_preamble(path, line_num, line)
- log.write(
- f"value of attribute '{attr_name}' could not be read:"
- )
- log.write("")
- raise
-
- # Boolean attributes
- tf_val = (
- True
- if is_truey(attr_val)
- else False
- if is_falsey(attr_val)
- else None
- )
- if tf_val is None:
- log.parsing_error_preamble(path, line_num, line)
- log.write(
- f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of"
- )
- log.write([*TRUEY_VALS, *FALSEY_VALS])
- log.error("")
- else:
- if attr_name == "dead":
- sym.dead = tf_val
- continue
- if attr_name == "defined":
- sym.defined = tf_val
- continue
- if attr_name == "extract":
- sym.extract = tf_val
- continue
- if attr_name == "ignore":
- ignore_sym = tf_val
- continue
- if attr_name == "force_migration":
- sym.force_migration = tf_val
- continue
- if attr_name == "force_not_migration":
- sym.force_not_migration = tf_val
- continue
- if attr_name == "allow_addend":
- sym.allow_addend = tf_val
- continue
- if attr_name == "dont_allow_addend":
- sym.dont_allow_addend = tf_val
- continue
- if ignore_sym:
- if sym.given_size == None or sym.given_size == 0:
- ignored_addresses.add(sym.vram_start)
- else:
- spim_context.addBannedSymbolRangeBySize(
- sym.vram_start, sym.given_size
- )
-
- ignore_sym = False
- continue
-
- if sym.segment:
- sym.segment.add_symbol(sym)
-
- sym.user_declared = True
- add_symbol(sym)
+ # Boolean attributes
+ tf_val = (
+ True
+ if is_truey(attr_val)
+ else False
+ if is_falsey(attr_val)
+ else None
+ )
+ if tf_val is None:
+ log.parsing_error_preamble(path, line_num, line)
+ log.write(
+ f"Invalid Boolean value '{attr_val}' for attribute '{attr_name}', should be one of"
+ )
+ log.write([*TRUEY_VALS, *FALSEY_VALS])
+ log.error("")
+ else:
+ if attr_name == "dead":
+ sym.dead = tf_val
+ continue
+ if attr_name == "defined":
+ sym.defined = tf_val
+ continue
+ if attr_name == "extract":
+ sym.extract = tf_val
+ continue
+ if attr_name == "ignore":
+ ignore_sym = tf_val
+ continue
+ if attr_name == "force_migration":
+ sym.force_migration = tf_val
+ continue
+ if attr_name == "force_not_migration":
+ sym.force_not_migration = tf_val
+ continue
+ if attr_name == "allow_addend":
+ sym.allow_addend = tf_val
+ continue
+ if attr_name == "dont_allow_addend":
+ sym.dont_allow_addend = tf_val
+ continue
+
+ if ignore_sym:
+ if sym.given_size is None or sym.given_size == 0:
+ ignored_addresses.add(sym.vram_start)
+ else:
+ spim_context.addBannedSymbolRangeBySize(
+ sym.vram_start, sym.given_size
+ )
+
+ continue
+
+ if sym.segment:
+ sym.segment.add_symbol(sym)
+
+ sym.user_declared = True
+ add_symbol(sym)
+
+
+def initialize(all_segments: "List[Segment]"):
+ global all_symbols
+ global all_symbols_dict
+ global all_symbols_ranges
+
+ all_symbols = []
+ all_symbols_dict = {}
+ all_symbols_ranges = IntervalTree()
+
+ # Manual list of func name / addrs
+ for path in options.opts.symbol_addrs_paths:
+ if path.exists():
+ with open(path) as f:
+ sym_addrs_lines = f.readlines()
+ handle_sym_addrs(path, sym_addrs_lines, all_segments)
def initialize_spim_context(all_segments: "List[Segment]") -> None:
@@ -291,6 +287,7 @@ def initialize_spim_context(all_segments: "List[Segment]") -> None:
continue
ram_id = segment.get_exclusive_ram_id()
+
if ram_id is None:
if global_vram_start is None:
global_vram_start = segment.vram_start
@@ -638,3 +635,23 @@ class Symbol:
def contains_rom(self, offset):
return offset >= self.rom and offset < self.rom_end
+
+
+def get_all_symbols():
+ global all_symbols
+ return all_symbols
+
+
+def reset_symbols():
+ global all_symbols
+ global all_symbols_dict
+ global all_symbols_ranges
+ global ignored_addresses
+ global to_mark_as_defined
+ global appears_after_overlays_syms
+ all_symbols = []
+ all_symbols_dict = {}
+ all_symbols_ranges = IntervalTree()
+ ignored_addresses = set()
+ to_mark_as_defined = set()
+ appears_after_overlays_syms = []