tamagoarc 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. tamagoarc-1.0.0/.github/workflows/publish.yml +35 -0
  2. tamagoarc-1.0.0/.gitignore +35 -0
  3. tamagoarc-1.0.0/CHANGELOG.md +31 -0
  4. tamagoarc-1.0.0/LICENSE +7 -0
  5. tamagoarc-1.0.0/Makefile +13 -0
  6. tamagoarc-1.0.0/PKG-INFO +21 -0
  7. tamagoarc-1.0.0/README.md +114 -0
  8. tamagoarc-1.0.0/hatch_build.py +36 -0
  9. tamagoarc-1.0.0/local.toml.example +3 -0
  10. tamagoarc-1.0.0/pyproject.toml +218 -0
  11. tamagoarc-1.0.0/scripts/analyze_tpm.py +804 -0
  12. tamagoarc-1.0.0/scripts/curate_tlg_samples.py +412 -0
  13. tamagoarc-1.0.0/scripts/extract_tlg_samples.py +207 -0
  14. tamagoarc-1.0.0/scripts/extract_trial_zips.py +191 -0
  15. tamagoarc-1.0.0/scripts/identify_encryption.py +1025 -0
  16. tamagoarc-1.0.0/scripts/survey_engines.py +350 -0
  17. tamagoarc-1.0.0/scripts/survey_installed_games.py +432 -0
  18. tamagoarc-1.0.0/scripts/survey_tlg_samples.py +462 -0
  19. tamagoarc-1.0.0/src/tamago/__about__.py +1 -0
  20. tamagoarc-1.0.0/src/tamago/__init__.py +3 -0
  21. tamagoarc-1.0.0/src/tamago/cli.py +169 -0
  22. tamagoarc-1.0.0/src/tamago/formats/__init__.py +0 -0
  23. tamagoarc-1.0.0/src/tamago/formats/advhd/__init__.py +22 -0
  24. tamagoarc-1.0.0/src/tamago/formats/advhd/arcfile.py +308 -0
  25. tamagoarc-1.0.0/src/tamago/formats/advhd/handler.py +19 -0
  26. tamagoarc-1.0.0/src/tamago/formats/det/__init__.py +11 -0
  27. tamagoarc-1.0.0/src/tamago/formats/det/detfile.py +529 -0
  28. tamagoarc-1.0.0/src/tamago/formats/det/handler.py +28 -0
  29. tamagoarc-1.0.0/src/tamago/formats/gsp/__init__.py +17 -0
  30. tamagoarc-1.0.0/src/tamago/formats/gsp/gspfile.py +255 -0
  31. tamagoarc-1.0.0/src/tamago/formats/gsp/handler.py +19 -0
  32. tamagoarc-1.0.0/src/tamago/formats/gsp/spt_crypto.py +143 -0
  33. tamagoarc-1.0.0/src/tamago/formats/xp3/__init__.py +16 -0
  34. tamagoarc-1.0.0/src/tamago/formats/xp3/_tlg_accel.c +508 -0
  35. tamagoarc-1.0.0/src/tamago/formats/xp3/detect.py +532 -0
  36. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/__init__.py +29 -0
  37. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/base.py +33 -0
  38. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/cxcrypt.py +571 -0
  39. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/fixed_xor.py +51 -0
  40. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/hash_derived.py +27 -0
  41. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/hash_xor.py +34 -0
  42. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/null.py +21 -0
  43. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption/pinpoint.py +51 -0
  44. tamagoarc-1.0.0/src/tamago/formats/xp3/encryption_library.toml +1783 -0
  45. tamagoarc-1.0.0/src/tamago/formats/xp3/handler.py +74 -0
  46. tamagoarc-1.0.0/src/tamago/formats/xp3/models.py +230 -0
  47. tamagoarc-1.0.0/src/tamago/formats/xp3/tlg.py +970 -0
  48. tamagoarc-1.0.0/src/tamago/formats/xp3/unencrypted_library.toml +360 -0
  49. tamagoarc-1.0.0/src/tamago/formats/xp3/xp3file.py +335 -0
  50. tamagoarc-1.0.0/src/tamago/tests/__init__.py +0 -0
  51. tamagoarc-1.0.0/src/tamago/tests/advhd/__init__.py +0 -0
  52. tamagoarc-1.0.0/src/tamago/tests/advhd/test_arcfile.py +590 -0
  53. tamagoarc-1.0.0/src/tamago/tests/advhd/test_lzss.py +83 -0
  54. tamagoarc-1.0.0/src/tamago/tests/det/__init__.py +0 -0
  55. tamagoarc-1.0.0/src/tamago/tests/det/test_compress.py +59 -0
  56. tamagoarc-1.0.0/src/tamago/tests/det/test_decompress.py +44 -0
  57. tamagoarc-1.0.0/src/tamago/tests/det/test_detfile.py +525 -0
  58. tamagoarc-1.0.0/src/tamago/tests/det/test_nonfree.py +69 -0
  59. tamagoarc-1.0.0/src/tamago/tests/gsp/__init__.py +0 -0
  60. tamagoarc-1.0.0/src/tamago/tests/gsp/test_gspfile.py +531 -0
  61. tamagoarc-1.0.0/src/tamago/tests/gsp/test_spt_crypto.py +120 -0
  62. tamagoarc-1.0.0/src/tamago/tests/test_cli.py +73 -0
  63. tamagoarc-1.0.0/src/tamago/tests/xp3/__init__.py +0 -0
  64. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/cheshire_4x4.tlg +0 -0
  65. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/cheshire_4x4_tlg0.tlg +0 -0
  66. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/empty_file.xp3 +0 -0
  67. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/encrypted_shift5_with_png.xp3 +0 -0
  68. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/encrypted_with_png.xp3 +0 -0
  69. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/multi_compressed.xp3 +0 -0
  70. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/multi_encrypted.xp3 +0 -0
  71. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/owari02.tlg +0 -0
  72. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/rabbit_4x2_lzss.tlg +0 -0
  73. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/red_2x2.tlg +0 -0
  74. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/single_compressed.xp3 +0 -0
  75. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/single_encrypted_compressed.xp3 +0 -0
  76. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/single_encrypted_uncompressed.xp3 +0 -0
  77. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/single_uncompressed.xp3 +0 -0
  78. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/transparent_2x2.tlg +0 -0
  79. tamagoarc-1.0.0/src/tamago/tests/xp3/samples/with_tlg.xp3 +0 -0
  80. tamagoarc-1.0.0/src/tamago/tests/xp3/test_cli.py +35 -0
  81. tamagoarc-1.0.0/src/tamago/tests/xp3/test_cxcrypt.py +258 -0
  82. tamagoarc-1.0.0/src/tamago/tests/xp3/test_detect.py +303 -0
  83. tamagoarc-1.0.0/src/tamago/tests/xp3/test_encryption.py +356 -0
  84. tamagoarc-1.0.0/src/tamago/tests/xp3/test_identify_encryption.py +262 -0
  85. tamagoarc-1.0.0/src/tamago/tests/xp3/test_models.py +68 -0
  86. tamagoarc-1.0.0/src/tamago/tests/xp3/test_nonfree.py +105 -0
  87. tamagoarc-1.0.0/src/tamago/tests/xp3/test_nonfree_tlg.py +122 -0
  88. tamagoarc-1.0.0/src/tamago/tests/xp3/test_tlg.py +232 -0
  89. tamagoarc-1.0.0/src/tamago/tests/xp3/test_xp3file.py +354 -0
  90. tamagoarc-1.0.0/tamago/formats/xp3/_tlg_accel.cpython-312-x86_64-linux-gnu.so +0 -0
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.12"
15
+ - name: Install Hatch
16
+ run: pip install hatch
17
+ - name: Build
18
+ run: hatch build
19
+ - uses: actions/upload-artifact@v4
20
+ with:
21
+ name: dist
22
+ path: dist/
23
+
24
+ publish:
25
+ needs: build
26
+ runs-on: ubuntu-latest
27
+ environment: pypi
28
+ permissions:
29
+ id-token: write
30
+ steps:
31
+ - uses: actions/download-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,35 @@
1
+ # Virtual environments
2
+ .venv/
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ *.egg
9
+ *.so
10
+ dist/
11
+ build/
12
+
13
+ # Testing
14
+ .pytest_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Local settings
29
+ local.toml
30
+
31
+ # frida-server, for local dev
32
+ frida-server.exe
33
+
34
+ # Nonfree test samples (real game data, not distributable)
35
+ src/tamago/tests/*/nonfree/
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 1.0.0 — 2026-04-14
6
+
7
+ Initial release.
8
+
9
+ ### Features
10
+
11
+ - Unified CLI (`tamago`) with `extract`, `create`, and `identify` commands
12
+ - Auto-detection of archive formats by magic bytes
13
+ - Python API for reading and writing archives
14
+ - Plugin system for format handlers and XP3 encryption schemes
15
+
16
+ ### Supported formats
17
+
18
+ - **XP3** ([KiriKiri](https://github.com/kenkyuuka/research-vault/blob/main/engines/KiriKiri.md)) — extract and create, with encryption support
19
+ - **DET** ([μ-GameOperationSystem](https://github.com/kenkyuuka/research-vault/blob/main/engines/%CE%BC-GameOperationSystem.md)) — extract and create, ATM and AT2 index formats
20
+ - **GSP** ([AGSD](https://github.com/kenkyuuka/research-vault/blob/main/engines/AGSD.md)) — extract and create
21
+ - **ARC** (AdvHD / WillPlus V2) — extract and create, with script decryption and LZSS decompression
22
+
23
+ ### XP3 encryption
24
+
25
+ - Automatic encryption detection (TPM hash lookup, XP3 structure hash, probing)
26
+ - Built-in schemes: hash-xor, fixed-xor, null, poringsoft, cxcrypt, pinpoint
27
+ - Known-game database (`encryption_library.toml`)
28
+
29
+ ### Images
30
+
31
+ - TLG image decoding (TLG5, TLG6) with optional Pillow integration
@@ -0,0 +1,7 @@
1
+ Copyright 2026 Kenkyuuka <kenkyuuka@proton.me>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,13 @@
1
+ .PHONY: test
2
+ test:
3
+ hatch run test
4
+
5
+ .PHONY: reinstall
6
+ reinstall:
7
+ hatch env prune && hatch env create
8
+
9
+ .PHONY: help
10
+ help: ## Show this help
11
+ @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk -F ':.*## ' '{printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
12
+
13
+ .DEFAULT_GOAL := help
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: tamagoarc
3
+ Version: 1.0.0
4
+ Summary: Multi-format game archive tool
5
+ Project-URL: Homepage, https://github.com/kenkyuuka/tamago
6
+ Project-URL: Issues, https://github.com/kenkyuuka/tamago/issues
7
+ Project-URL: Source, https://github.com/kenkyuuka/tamago
8
+ Author-email: Kenkyuuka <kenkyuuka@proton.me>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: construct>=2.10
20
+ Provides-Extra: images
21
+ Requires-Dist: pillow>=9.0; extra == 'images'
@@ -0,0 +1,114 @@
1
+ # tamago
2
+
3
+ Multi-format game archive toolkit.
4
+
5
+ Requires Python 3.10 or later.
6
+
7
+ ## Supported formats
8
+
9
+ | Format | Engine | Extract | Create |
10
+ |--------|--------|:-------:|:------:|
11
+ | XP3 | [KiriKiri](https://github.com/kenkyuuka/research-vault/blob/main/engines/KiriKiri.md) | yes | yes |
12
+ | DET | [μ-GameOperationSystem](https://github.com/kenkyuuka/research-vault/blob/main/engines/%CE%BC-GameOperationSystem.md) | yes | yes |
13
+ | GSP | [AGSD](https://github.com/kenkyuuka/research-vault/blob/main/engines/AGSD.md) | yes | yes |
14
+ | ARC | AdvHD (WillPlus V2) | yes | yes |
15
+
16
+ ## Installation
17
+
18
+ ```console
19
+ pip install tamagoarc
20
+ ```
21
+
22
+ To enable TLG image conversion, install with the `images` extra:
23
+
24
+ ```console
25
+ pip install tamagoarc[images]
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### tamago CLI
31
+
32
+ tamago auto-detects archive formats by magic bytes and, for XP3 archives, automatically identifies the correct encryption scheme.
33
+
34
+ ```console
35
+ # Identify an archive's format
36
+ tamago identify data.xp3
37
+
38
+ # Extract an archive (auto-detects format and encryption)
39
+ tamago extract data.xp3 output_dir
40
+
41
+ # Use format subcommands for format-specific options
42
+ tamago xp3 extract data.xp3 output_dir --encryption hash-xor --key 3
43
+ tamago det create source_dir output.det --index-format atm
44
+
45
+ # Create an archive (format must be specified)
46
+ tamago create --format xp3 source_dir output.xp3
47
+ ```
48
+
49
+ ### Python API
50
+
51
+ ```python
52
+ from tamago.formats.xp3 import XP3File
53
+
54
+ # Extract an archive
55
+ with XP3File("data.xp3") as xp3:
56
+ xp3.extract_all("output_dir")
57
+
58
+ # Extract with explicit encryption
59
+ from tamago.formats.xp3.encryption import HashXorEncryption
60
+
61
+ enc = HashXorEncryption(shift=3)
62
+ with XP3File("data.xp3", encryption=enc) as xp3:
63
+ xp3.extract_all("output_dir")
64
+
65
+ # Auto-detect encryption
66
+ from tamago.formats.xp3.detect import auto_detect
67
+
68
+ enc = auto_detect("data.xp3")
69
+ with XP3File("data.xp3", encryption=enc) as xp3:
70
+ xp3.extract_all("output_dir")
71
+
72
+ # Create an archive
73
+ from pathlib import Path
74
+
75
+ with XP3File("output.xp3", "x") as xp3:
76
+ xp3.write_all(Path("source_dir"))
77
+ ```
78
+
79
+ ## XP3 Encryption
80
+
81
+ XP3 archives used by KiriKiri games are often encrypted with a game-specific scheme implemented in a `.tpm` plugin. tamago supports the encryption used by several games (including automatic detection of the encryption).
82
+
83
+ If the game you are interested in is not supported, please raise an issue, including a VNDB link to the game.
84
+
85
+ ## Development
86
+
87
+ This project uses [Hatch](https://hatch.pypa.io/) as the build system.
88
+
89
+ ### Setup
90
+
91
+ ```console
92
+ pip install hatch
93
+ ```
94
+
95
+ ### Running tests
96
+
97
+ ```console
98
+ hatch run test # run all tests
99
+ hatch run test -k test_name # run a single test by name
100
+ hatch run cov # tests + coverage report
101
+ ```
102
+
103
+ ### Linting and formatting
104
+
105
+ ```console
106
+ hatch run lint:style # check style (ruff + black)
107
+ hatch run lint:typing # type checking (mypy)
108
+ hatch run lint:fmt # auto-format (black + ruff --fix)
109
+ hatch run lint:all # style + typing
110
+ ```
111
+
112
+ ## License
113
+
114
+ tamago is distributed under the terms of the [MIT license](LICENSE).
@@ -0,0 +1,36 @@
1
+ """Custom hatch build hook to compile the TLG C accelerator extension."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sysconfig
8
+
9
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
10
+
11
+
12
+ class CustomBuildHook(BuildHookInterface):
13
+ PLUGIN_NAME = 'custom'
14
+
15
+ def initialize(self, version, build_data):
16
+ src = os.path.join(self.root, 'src', 'tamago', 'formats', 'xp3', '_tlg_accel.c')
17
+ if not os.path.exists(src):
18
+ return
19
+
20
+ ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') or '.so'
21
+ out_name = f'_tlg_accel{ext_suffix}'
22
+ out_path = os.path.join(self.root, 'src', 'tamago', 'formats', 'xp3', out_name)
23
+ include = sysconfig.get_path('include')
24
+
25
+ try:
26
+ subprocess.check_call(
27
+ ['gcc', '-O2', '-shared', '-fPIC', f'-I{include}', '-o', out_path, src],
28
+ stderr=subprocess.PIPE,
29
+ )
30
+ except (subprocess.CalledProcessError, FileNotFoundError):
31
+ # C compilation failed; pure-Python fallback will be used at runtime.
32
+ return
33
+
34
+ # Include the compiled .so in the wheel.
35
+ rel_path = os.path.join('tamago', 'formats', 'xp3', out_name)
36
+ build_data['force_include'][out_path] = rel_path
@@ -0,0 +1,3 @@
1
+ [paths]
2
+ # Directory containing installed KiriKiri games (used by scripts/survey_installed_games.py)
3
+ # scan_dir = "/path/to/installed/games"
@@ -0,0 +1,218 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tamagoarc"
7
+ dynamic = ["version"]
8
+ description = "Multi-format game archive tool"
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ keywords = []
12
+ authors = [
13
+ { name = "Kenkyuuka", email = "kenkyuuka@proton.me" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: Implementation :: CPython",
22
+ "Programming Language :: Python :: Implementation :: PyPy",
23
+ ]
24
+ dependencies = ["construct>=2.10"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/kenkyuuka/tamago"
28
+ Issues = "https://github.com/kenkyuuka/tamago/issues"
29
+ Source = "https://github.com/kenkyuuka/tamago"
30
+
31
+ [project.optional-dependencies]
32
+ images = ["Pillow>=9.0"]
33
+
34
+ [project.scripts]
35
+ tamago = "tamago.cli:main"
36
+
37
+ [project.entry-points."tamago.formats"]
38
+ xp3 = "tamago.formats.xp3.handler:XP3Handler"
39
+ det = "tamago.formats.det.handler:DETHandler"
40
+ gsp = "tamago.formats.gsp.handler:GSPHandler"
41
+ advhd = "tamago.formats.advhd.handler:ARCHandler"
42
+
43
+ [project.entry-points."tamago.formats.xp3.encryption"]
44
+ fixed-xor = "tamago.formats.xp3.encryption.fixed_xor:FixedXorEncryption"
45
+ hash-xor = "tamago.formats.xp3.encryption.hash_xor:HashXorEncryption"
46
+ null = "tamago.formats.xp3.encryption.null:NullEncryption"
47
+ poringsoft = "tamago.formats.xp3.encryption.hash_derived:PoringSoftEncryption"
48
+ cxcrypt = "tamago.formats.xp3.encryption.cxcrypt:CxEncryption"
49
+ pinpoint = "tamago.formats.xp3.encryption.pinpoint:PinPointEncryption"
50
+
51
+ [tool.hatch.build.hooks.custom]
52
+
53
+ [tool.hatch.version]
54
+ path = "src/tamago/__about__.py"
55
+
56
+ [tool.hatch.envs.default]
57
+ dependencies = [
58
+ "coverage[toml]>=6.5",
59
+ "pytest",
60
+ "pytest-xdist",
61
+ "frida-tools",
62
+ "Pillow>=9.0",
63
+ ]
64
+
65
+ [tool.hatch.envs.default.scripts]
66
+ test = "pytest -n auto --maxprocesses 8 src/tamago/tests {args}"
67
+ test-xp3 = "pytest -n auto --maxprocesses 8 src/tamago/tests/xp3 {args}"
68
+ test-det = "pytest -n auto --maxprocesses 8 src/tamago/tests/det {args}"
69
+ test-gsp = "pytest -n auto --maxprocesses 8 src/tamago/tests/gsp {args}"
70
+ test-advhd = "pytest -n auto --maxprocesses 8 src/tamago/tests/advhd {args}"
71
+ test-tamago = "pytest -n auto --maxprocesses 8 src/tamago/tests/test_cli.py {args}"
72
+ test-nonfree = "pytest -n auto --maxprocesses 8 src/tamago/tests -m nonfree {args}"
73
+ test-cov = "coverage run -m pytest -n auto --maxprocesses 8 src/tamago/tests {args}"
74
+
75
+ cov-report = [
76
+ "- coverage combine",
77
+ "coverage report",
78
+ ]
79
+ cov = [
80
+ "test-cov",
81
+ "cov-report",
82
+ ]
83
+
84
+ [tool.hatch.envs.hatch-test]
85
+ extra-dependencies = [
86
+ "Pillow>=9.0",
87
+ ]
88
+
89
+ [[tool.hatch.envs.all.matrix]]
90
+ python = ["3.10", "3.11", "3.12"]
91
+
92
+ [tool.hatch.envs.lint]
93
+ dependencies = [
94
+ "black>=23.1.0",
95
+ "mypy>=1.0.0",
96
+ "pytest",
97
+ "ruff>=0.0.243",
98
+ ]
99
+ [tool.hatch.envs.lint.scripts]
100
+ typing = "mypy --install-types --non-interactive {args:src/tamago}"
101
+ style = [
102
+ "ruff check {args:.}",
103
+ "black --check --diff {args:.}",
104
+ ]
105
+ fmt = [
106
+ "black {args:.}",
107
+ "ruff check --fix {args:.}",
108
+ "style",
109
+ ]
110
+ all = [
111
+ "style",
112
+ "typing",
113
+ ]
114
+
115
+ [tool.black]
116
+ target-version = ["py310"]
117
+ line-length = 120
118
+ skip-string-normalization = true
119
+
120
+ [tool.ruff]
121
+ target-version = "py310"
122
+ line-length = 120
123
+ extend-exclude = ["scripts", "packages"]
124
+ [tool.ruff.lint]
125
+ select = [
126
+ "A",
127
+ "ARG",
128
+ "B",
129
+ "C",
130
+ "DTZ",
131
+ "E",
132
+ "EM",
133
+ "F",
134
+ "FBT",
135
+ "I",
136
+ "ICN",
137
+ "ISC",
138
+ "N",
139
+ "PLC",
140
+ "PLE",
141
+ "PLR",
142
+ "PLW",
143
+ "Q",
144
+ "RUF",
145
+ "S",
146
+ "T",
147
+ "TID",
148
+ "UP",
149
+ "W",
150
+ "YTT",
151
+ ]
152
+ ignore = [
153
+ # Allow non-abstract empty methods in abstract base classes
154
+ "B027",
155
+ # Allow boolean positional values in function calls, like `dict.get(... True)`
156
+ "FBT002", "FBT003",
157
+ # Ignore checks for possible passwords
158
+ "S105", "S106", "S107",
159
+ # Ignore complexity
160
+ "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
161
+ # Quote style managed by black (skip-string-normalization = true)
162
+ "Q000",
163
+ # Allow string/f-string literals in exceptions
164
+ "EM101", "EM102",
165
+ # Allow implicit string concatenation on one line
166
+ "ISC001",
167
+ # Allow print() in CLI and diagnostic code
168
+ "T201",
169
+ # Allow conditional/lazy imports not at top level
170
+ "PLC0415",
171
+ # Allow raise without from in except blocks
172
+ "B904",
173
+ ]
174
+ unfixable = [
175
+ # Don't touch unused imports
176
+ "F401",
177
+ ]
178
+
179
+ [tool.ruff.lint.isort]
180
+ known-first-party = ["tamago"]
181
+
182
+ [tool.ruff.lint.flake8-tidy-imports]
183
+ ban-relative-imports = "all"
184
+
185
+ [tool.ruff.lint.per-file-ignores]
186
+ # Tests can use magic values, assertions, and relative imports
187
+ "**/tests/**/*" = ["ARG002", "F401", "PLR2004", "RUF059", "S101", "TID252"]
188
+ "**/src/**/*" = ["A002", "ARG002", "RUF023", "S101", "TID252"]
189
+
190
+ [tool.mypy]
191
+ [[tool.mypy.overrides]]
192
+ module = "identify_encryption"
193
+ ignore_missing_imports = true
194
+
195
+ [tool.pytest.ini_options]
196
+ markers = [
197
+ "unit: pure logic tests with no filesystem or external dependencies",
198
+ "integration: tests that read/write XP3 files or use the detection pipeline",
199
+ "nonfree: tests that require real game data in nonfree/ directories",
200
+ ]
201
+
202
+ [tool.coverage.run]
203
+ source_pkgs = ["tamago"]
204
+ branch = true
205
+ parallel = true
206
+ omit = [
207
+ "src/tamago/__about__.py",
208
+ ]
209
+
210
+ [tool.coverage.paths]
211
+ tamago = ["src/tamago", "*/tamago/src/tamago"]
212
+
213
+ [tool.coverage.report]
214
+ exclude_lines = [
215
+ "no cov",
216
+ "if __name__ == .__main__.:",
217
+ "if TYPE_CHECKING:",
218
+ ]