modmanifest 0.1.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 (38) hide show
  1. modmanifest-0.1.0/.gitignore +15 -0
  2. modmanifest-0.1.0/CHANGELOG.md +11 -0
  3. modmanifest-0.1.0/CONTRIBUTING.md +13 -0
  4. modmanifest-0.1.0/LICENSE +21 -0
  5. modmanifest-0.1.0/PKG-INFO +176 -0
  6. modmanifest-0.1.0/README.md +145 -0
  7. modmanifest-0.1.0/SECURITY.md +16 -0
  8. modmanifest-0.1.0/pyproject.toml +68 -0
  9. modmanifest-0.1.0/src/modmanifest/__init__.py +4 -0
  10. modmanifest-0.1.0/src/modmanifest/__main__.py +4 -0
  11. modmanifest-0.1.0/src/modmanifest/archive/__init__.py +1 -0
  12. modmanifest-0.1.0/src/modmanifest/archive/limits.py +36 -0
  13. modmanifest-0.1.0/src/modmanifest/archive/signatures.py +47 -0
  14. modmanifest-0.1.0/src/modmanifest/archive/sources.py +196 -0
  15. modmanifest-0.1.0/src/modmanifest/archive/zip_inspector.py +302 -0
  16. modmanifest-0.1.0/src/modmanifest/cli.py +172 -0
  17. modmanifest-0.1.0/src/modmanifest/codes.py +95 -0
  18. modmanifest-0.1.0/src/modmanifest/context.py +60 -0
  19. modmanifest-0.1.0/src/modmanifest/games/__init__.py +1 -0
  20. modmanifest-0.1.0/src/modmanifest/games/base.py +22 -0
  21. modmanifest-0.1.0/src/modmanifest/games/dbpf.py +167 -0
  22. modmanifest-0.1.0/src/modmanifest/games/inzoi.py +345 -0
  23. modmanifest-0.1.0/src/modmanifest/games/sims4.py +171 -0
  24. modmanifest-0.1.0/src/modmanifest/inspector.py +112 -0
  25. modmanifest-0.1.0/src/modmanifest/metadata/__init__.py +1 -0
  26. modmanifest-0.1.0/src/modmanifest/metadata/ai_audit.py +375 -0
  27. modmanifest-0.1.0/src/modmanifest/metadata/ai_signatures.py +77 -0
  28. modmanifest-0.1.0/src/modmanifest/metadata/parsers.py +158 -0
  29. modmanifest-0.1.0/src/modmanifest/models.py +200 -0
  30. modmanifest-0.1.0/src/modmanifest/py.typed +0 -0
  31. modmanifest-0.1.0/src/modmanifest/reporting.py +80 -0
  32. modmanifest-0.1.0/tests/__init__.py +0 -0
  33. modmanifest-0.1.0/tests/helpers.py +89 -0
  34. modmanifest-0.1.0/tests/test_ai_metadata.py +150 -0
  35. modmanifest-0.1.0/tests/test_archives.py +121 -0
  36. modmanifest-0.1.0/tests/test_cli.py +140 -0
  37. modmanifest-0.1.0/tests/test_inzoi.py +131 -0
  38. modmanifest-0.1.0/tests/test_sims4.py +112 -0
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ htmlcov/
12
+ .idea/
13
+ .vscode/
14
+ .DS_Store
15
+ Thumbs.db
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release.
6
+
7
+ - `inspect`, `validate` and `audit-ai` commands with stable JSON output (schema version 1).
8
+ - Archive and folder safety checks with configurable limits.
9
+ - The Sims 4: DBPF 2.1 structure and `.ts4script` archive checks.
10
+ - inZOI: candidate-package manifest inspection and observation-based checks.
11
+ - AI metadata audit: manifests, PNG text chunks, XMP, EXIF; C2PA detection only.
@@ -0,0 +1,13 @@
1
+ # Contributing
2
+
3
+ 1. Fork, branch, and keep changes focused.
4
+ 2. `pip install -e ".[dev]"`
5
+ 3. `ruff check . && ruff format --check . && pytest` must pass.
6
+
7
+ Guidelines:
8
+
9
+ - Tests use synthetic fixtures built in code. Never commit real mod content.
10
+ - Every emitted diagnostic code must be registered in `src/modmanifest/codes.py` with a
11
+ basis. Keep observed conventions separate from verified format rules.
12
+ - The tool is read-only: no extraction, execution, network access or file modification.
13
+ - Do not guess unknown formats or checksum algorithms; report `NOT CHECKED`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CyberStudios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: modmanifest
3
+ Version: 0.1.0
4
+ Summary: Read-only inspection, validation and metadata auditing for game mod packages (The Sims 4, inZOI).
5
+ Project-URL: Homepage, https://github.com/CyberStudiosTM/ModManifest
6
+ Project-URL: Issues, https://github.com/CyberStudiosTM/ModManifest/issues
7
+ Project-URL: Changelog, https://github.com/CyberStudiosTM/ModManifest/blob/main/CHANGELOG.md
8
+ Author: CyberStudios
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: audit,cli,inzoi,metadata,mods,sims4
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: defusedxml>=0.7.1
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: rich>=13.7
26
+ Requires-Dist: typer>=0.12
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # ModManifest
33
+
34
+ Read-only inspection, validation and metadata auditing for game mod packages.
35
+ Supports **The Sims 4** and **inZOI**. Part of [CyberStudios](https://github.com/CyberStudiosTM).
36
+
37
+ ModManifest never extracts, executes, modifies or uploads anything. It reports what it can
38
+ verify, what it only observed, and what it did not check.
39
+
40
+ ## Install
41
+
42
+ ```powershell
43
+ pip install modmanifest
44
+ ```
45
+
46
+ Requires Python 3.11+. From source: `pip install .`
47
+
48
+ ## Usage
49
+
50
+ ```text
51
+ modmanifest inspect PATH [--json]
52
+ modmanifest validate PATH --game sims4|inzoi [--json]
53
+ modmanifest audit-ai PATH [--json]
54
+ modmanifest --version
55
+ ```
56
+
57
+ `PATH` is a folder, `.zip`, `.ts4script` or `.package`.
58
+
59
+ | Command | Runs |
60
+ |---|---|
61
+ | `inspect` | archive safety, game recognition and validation, AI metadata audit |
62
+ | `validate` | archive safety and the selected game's validation |
63
+ | `audit-ai` | archive safety and the AI metadata audit |
64
+
65
+ Options: `--json`, `--verbose`, `--timeout`, `--max-entries`, `--max-total-mb`, `--max-entry-mb`.
66
+
67
+ ### Statuses
68
+
69
+ `PASS`, `WARNING`, `ERROR`, `UNSUPPORTED`, `NOT CHECKED` (`NOT_CHECKED` in JSON).
70
+ Every finding carries a stable code (`MM-ARC-*`, `MM-SCN-*`, `MM-S4-*`, `MM-INZ-*`) and a
71
+ basis: `format-rule`, `observed-convention`, `safety-limit` or `scan-coverage`.
72
+
73
+ ### Exit codes
74
+
75
+ | Code | Meaning |
76
+ |---|---|
77
+ | 0 | scan complete, no `ERROR` findings |
78
+ | 1 | one or more `ERROR` findings |
79
+ | 2 | invalid arguments or missing path |
80
+ | 3 | incomplete scan (limit, timeout, read failure), or `validate` recognized nothing |
81
+
82
+ AI indicators never change the exit code.
83
+
84
+ ## Example
85
+
86
+ ```text
87
+ $ modmanifest validate SampleMod --game inzoi
88
+ ModManifest validate SampleMod
89
+ target: folder scan: complete
90
+ game selected: inzoi recognized: inzoi
91
+ Status Code Finding
92
+ NOT_CHECKED MM-INZ-015 no official inZOI package specification was used; ...
93
+ NOT_CHECKED MM-INZ-013 declared dependencies are reported as written; ...
94
+ NOT_CHECKED MM-INZ-017 game version metadata is reported as written; ...
95
+ NOT_CHECKED MM-INZ-009 checksum digests were not verified: the algorithm ...
96
+ PASS 2 WARNING 0 ERROR 0 UNSUPPORTED 0 NOT_CHECKED 5
97
+ ```
98
+
99
+ ## Validation coverage
100
+
101
+ ### The Sims 4
102
+
103
+ - `.package` (DBPF 2.1): signature, header, index bounds and size, resource ranges,
104
+ duplicate keys. Resource contents are **not** decoded.
105
+ - `.ts4script`: opened as a ZIP, `.py`/`.pyc` entries listed, compiled-code header checked.
106
+ Script contents are **not** analyzed or executed.
107
+ - No standard Sims 4 manifest is assumed, so dependencies and versions are not extracted.
108
+
109
+ ### inZOI
110
+
111
+ A root `mod_manifest.json` marks a **candidate** mod, not a valid package. There is no
112
+ official specification behind these checks.
113
+
114
+ - JSON syntax and field types, compared with sample packages. Observed fields are never required.
115
+ - Identity, author, version, description, declared dependencies and game-version metadata are
116
+ reported as written. Dependencies and compatibility are **not** verified.
117
+ - `ProjectName` versus folder name is informational.
118
+ - `FileChecksums`: shape, path safety and listed-file existence are checked. Digests are
119
+ **NOT CHECKED**: in sample packages the algorithm and path semantics could not be
120
+ established, and none is guessed. A matching digest would show consistency with the
121
+ manifest only, not that a mod is safe or authentic.
122
+ - `.pak`, `.ucas`, `.utoc`: recognized as Unreal content containers and inventoried;
123
+ internals are not inspected. A lone `.ucas` or `.utoc` is reported as a warning.
124
+ - Unrelated folders (mod-manager state, hosted content) are unrecognized, not invalid.
125
+ Point ModManifest at one mod folder at a time.
126
+
127
+ ### Archive and folder safety (all targets)
128
+
129
+ Traversal and absolute paths, drive letters and alternate streams, backslashes, duplicates,
130
+ case collisions, symlinks and special files, entry count, total and per-entry size,
131
+ compression ratio, signature mismatch, encrypted entries (`UNSUPPORTED`), and nested
132
+ archives (listed, not opened). Limits are configurable.
133
+
134
+ ## AI metadata audit
135
+
136
+ Reports indicators found in metadata, in three categories: explicit declaration, tool
137
+ reference, ambiguous indicator. Confidence refers to the interpretation of the metadata
138
+ only.
139
+
140
+ Supported sources: JSON, TOML, YAML and XML manifests; text and Markdown notes; PNG text
141
+ chunks; XMP; EXIF `Software`/`ImageDescription`. C2PA is **detection only**: possible
142
+ structures are reported, never parsed or signature-validated.
143
+
144
+ When nothing is found, the result is:
145
+
146
+ > No supported AI metadata indicators found.
147
+
148
+ Metadata may be absent, incomplete or removed. That result is not evidence about how a
149
+ package was made. Prompt text is never copied into reports. Name tables used for matching
150
+ live in `src/modmanifest/metadata/ai_signatures.py` and are detection data only.
151
+
152
+ ## JSON output
153
+
154
+ Stable schema, `schema_version: 1`. Top-level keys: `tool`, `version`, `command`, `target`,
155
+ `game`, `scan`, `recognized_files`, `capabilities_exercised`, `package`, `findings`,
156
+ `summary`, `unsupported`, `inspection_errors`, `ai_metadata`. The target is a base name,
157
+ never an absolute path. Untrusted text has control and bidirectional characters escaped.
158
+
159
+ ## Limitations
160
+
161
+ - Format checks reflect observed samples and public format knowledge, not vendor specs.
162
+ - Only listed metadata containers are audited; scripts and binary game data are not.
163
+ - 7z, RAR and other archive formats are listed, not opened.
164
+ - Package validity here does not mean a mod is safe, compatible or working in game.
165
+
166
+ ## Development
167
+
168
+ ```powershell
169
+ pip install -e ".[dev]"
170
+ ruff check . ; ruff format --check .
171
+ pytest
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT. Copyright (c) CyberStudios.
@@ -0,0 +1,145 @@
1
+ # ModManifest
2
+
3
+ Read-only inspection, validation and metadata auditing for game mod packages.
4
+ Supports **The Sims 4** and **inZOI**. Part of [CyberStudios](https://github.com/CyberStudiosTM).
5
+
6
+ ModManifest never extracts, executes, modifies or uploads anything. It reports what it can
7
+ verify, what it only observed, and what it did not check.
8
+
9
+ ## Install
10
+
11
+ ```powershell
12
+ pip install modmanifest
13
+ ```
14
+
15
+ Requires Python 3.11+. From source: `pip install .`
16
+
17
+ ## Usage
18
+
19
+ ```text
20
+ modmanifest inspect PATH [--json]
21
+ modmanifest validate PATH --game sims4|inzoi [--json]
22
+ modmanifest audit-ai PATH [--json]
23
+ modmanifest --version
24
+ ```
25
+
26
+ `PATH` is a folder, `.zip`, `.ts4script` or `.package`.
27
+
28
+ | Command | Runs |
29
+ |---|---|
30
+ | `inspect` | archive safety, game recognition and validation, AI metadata audit |
31
+ | `validate` | archive safety and the selected game's validation |
32
+ | `audit-ai` | archive safety and the AI metadata audit |
33
+
34
+ Options: `--json`, `--verbose`, `--timeout`, `--max-entries`, `--max-total-mb`, `--max-entry-mb`.
35
+
36
+ ### Statuses
37
+
38
+ `PASS`, `WARNING`, `ERROR`, `UNSUPPORTED`, `NOT CHECKED` (`NOT_CHECKED` in JSON).
39
+ Every finding carries a stable code (`MM-ARC-*`, `MM-SCN-*`, `MM-S4-*`, `MM-INZ-*`) and a
40
+ basis: `format-rule`, `observed-convention`, `safety-limit` or `scan-coverage`.
41
+
42
+ ### Exit codes
43
+
44
+ | Code | Meaning |
45
+ |---|---|
46
+ | 0 | scan complete, no `ERROR` findings |
47
+ | 1 | one or more `ERROR` findings |
48
+ | 2 | invalid arguments or missing path |
49
+ | 3 | incomplete scan (limit, timeout, read failure), or `validate` recognized nothing |
50
+
51
+ AI indicators never change the exit code.
52
+
53
+ ## Example
54
+
55
+ ```text
56
+ $ modmanifest validate SampleMod --game inzoi
57
+ ModManifest validate SampleMod
58
+ target: folder scan: complete
59
+ game selected: inzoi recognized: inzoi
60
+ Status Code Finding
61
+ NOT_CHECKED MM-INZ-015 no official inZOI package specification was used; ...
62
+ NOT_CHECKED MM-INZ-013 declared dependencies are reported as written; ...
63
+ NOT_CHECKED MM-INZ-017 game version metadata is reported as written; ...
64
+ NOT_CHECKED MM-INZ-009 checksum digests were not verified: the algorithm ...
65
+ PASS 2 WARNING 0 ERROR 0 UNSUPPORTED 0 NOT_CHECKED 5
66
+ ```
67
+
68
+ ## Validation coverage
69
+
70
+ ### The Sims 4
71
+
72
+ - `.package` (DBPF 2.1): signature, header, index bounds and size, resource ranges,
73
+ duplicate keys. Resource contents are **not** decoded.
74
+ - `.ts4script`: opened as a ZIP, `.py`/`.pyc` entries listed, compiled-code header checked.
75
+ Script contents are **not** analyzed or executed.
76
+ - No standard Sims 4 manifest is assumed, so dependencies and versions are not extracted.
77
+
78
+ ### inZOI
79
+
80
+ A root `mod_manifest.json` marks a **candidate** mod, not a valid package. There is no
81
+ official specification behind these checks.
82
+
83
+ - JSON syntax and field types, compared with sample packages. Observed fields are never required.
84
+ - Identity, author, version, description, declared dependencies and game-version metadata are
85
+ reported as written. Dependencies and compatibility are **not** verified.
86
+ - `ProjectName` versus folder name is informational.
87
+ - `FileChecksums`: shape, path safety and listed-file existence are checked. Digests are
88
+ **NOT CHECKED**: in sample packages the algorithm and path semantics could not be
89
+ established, and none is guessed. A matching digest would show consistency with the
90
+ manifest only, not that a mod is safe or authentic.
91
+ - `.pak`, `.ucas`, `.utoc`: recognized as Unreal content containers and inventoried;
92
+ internals are not inspected. A lone `.ucas` or `.utoc` is reported as a warning.
93
+ - Unrelated folders (mod-manager state, hosted content) are unrecognized, not invalid.
94
+ Point ModManifest at one mod folder at a time.
95
+
96
+ ### Archive and folder safety (all targets)
97
+
98
+ Traversal and absolute paths, drive letters and alternate streams, backslashes, duplicates,
99
+ case collisions, symlinks and special files, entry count, total and per-entry size,
100
+ compression ratio, signature mismatch, encrypted entries (`UNSUPPORTED`), and nested
101
+ archives (listed, not opened). Limits are configurable.
102
+
103
+ ## AI metadata audit
104
+
105
+ Reports indicators found in metadata, in three categories: explicit declaration, tool
106
+ reference, ambiguous indicator. Confidence refers to the interpretation of the metadata
107
+ only.
108
+
109
+ Supported sources: JSON, TOML, YAML and XML manifests; text and Markdown notes; PNG text
110
+ chunks; XMP; EXIF `Software`/`ImageDescription`. C2PA is **detection only**: possible
111
+ structures are reported, never parsed or signature-validated.
112
+
113
+ When nothing is found, the result is:
114
+
115
+ > No supported AI metadata indicators found.
116
+
117
+ Metadata may be absent, incomplete or removed. That result is not evidence about how a
118
+ package was made. Prompt text is never copied into reports. Name tables used for matching
119
+ live in `src/modmanifest/metadata/ai_signatures.py` and are detection data only.
120
+
121
+ ## JSON output
122
+
123
+ Stable schema, `schema_version: 1`. Top-level keys: `tool`, `version`, `command`, `target`,
124
+ `game`, `scan`, `recognized_files`, `capabilities_exercised`, `package`, `findings`,
125
+ `summary`, `unsupported`, `inspection_errors`, `ai_metadata`. The target is a base name,
126
+ never an absolute path. Untrusted text has control and bidirectional characters escaped.
127
+
128
+ ## Limitations
129
+
130
+ - Format checks reflect observed samples and public format knowledge, not vendor specs.
131
+ - Only listed metadata containers are audited; scripts and binary game data are not.
132
+ - 7z, RAR and other archive formats are listed, not opened.
133
+ - Package validity here does not mean a mod is safe, compatible or working in game.
134
+
135
+ ## Development
136
+
137
+ ```powershell
138
+ pip install -e ".[dev]"
139
+ ruff check . ; ruff format --check .
140
+ pytest
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT. Copyright (c) CyberStudios.
@@ -0,0 +1,16 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Use GitHub private vulnerability reporting on this repository (Security tab, "Report a
6
+ vulnerability"). Do not open a public issue for security problems.
7
+
8
+ ## Scope
9
+
10
+ ModManifest parses untrusted archives and files. In scope: crashes, resource exhaustion,
11
+ path handling, or any behavior that writes, executes or transmits data. Out of scope:
12
+ whether a mod is safe to install; ModManifest makes no such claim.
13
+
14
+ ## Supported versions
15
+
16
+ The latest release.
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "modmanifest"
7
+ dynamic = ["version"]
8
+ description = "Read-only inspection, validation and metadata auditing for game mod packages (The Sims 4, inZOI)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "CyberStudios" }]
14
+ keywords = ["mods", "sims4", "inzoi", "audit", "metadata", "cli"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Quality Assurance",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [
28
+ "typer>=0.12",
29
+ "rich>=13.7",
30
+ "PyYAML>=6.0",
31
+ "defusedxml>=0.7.1",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=8.0", "ruff>=0.6"]
36
+
37
+ [project.scripts]
38
+ modmanifest = "modmanifest.cli:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/CyberStudiosTM/ModManifest"
42
+ Issues = "https://github.com/CyberStudiosTM/ModManifest/issues"
43
+ Changelog = "https://github.com/CyberStudiosTM/ModManifest/blob/main/CHANGELOG.md"
44
+
45
+ [tool.hatch.version]
46
+ path = "src/modmanifest/__init__.py"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/modmanifest"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = ["src", "tests", "README.md", "LICENSE", "CHANGELOG.md", "SECURITY.md", "CONTRIBUTING.md", "pyproject.toml"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ addopts = "-q"
57
+
58
+ [tool.ruff]
59
+ line-length = 100
60
+ target-version = "py311"
61
+ src = ["src", "tests"]
62
+
63
+ [tool.ruff.lint]
64
+ select = ["E", "F", "I", "UP", "B", "SIM", "ANN", "PT"]
65
+ ignore = ["ANN401"]
66
+
67
+ [tool.ruff.lint.per-file-ignores]
68
+ "tests/**" = ["ANN", "PT018", "E501"]
@@ -0,0 +1,4 @@
1
+ """ModManifest: read-only inspection, validation and metadata auditing for mod packages."""
2
+
3
+ __version__ = "0.1.0"
4
+ SCHEMA_VERSION = 1
@@ -0,0 +1,4 @@
1
+ from modmanifest.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1 @@
1
+ """Package marker."""
@@ -0,0 +1,36 @@
1
+ """Configurable scan limits and the scan deadline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from dataclasses import dataclass
7
+
8
+ MB = 1024 * 1024
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class ScanLimits:
13
+ max_entries: int = 20_000
14
+ max_archive_bytes: int = 1024 * MB
15
+ max_total_bytes: int = 500 * MB
16
+ max_entry_bytes: int = 50 * MB
17
+ max_ratio: float = 100.0
18
+ max_metadata_bytes: int = 1 * MB
19
+ max_image_bytes: int = 20 * MB
20
+ timeout_s: float = 30.0
21
+ # Ratio warnings are skipped for tiny entries, which compress extremely well legitimately.
22
+ ratio_min_bytes: int = 64 * 1024
23
+
24
+
25
+ class ScanTimeout(Exception):
26
+ """Raised when the scan deadline passes."""
27
+
28
+
29
+ class Deadline:
30
+ def __init__(self, seconds: float) -> None:
31
+ self._end = time.monotonic() + seconds
32
+ self.seconds = seconds
33
+
34
+ def check(self) -> None:
35
+ if time.monotonic() > self._end:
36
+ raise ScanTimeout(f"scan exceeded the {self.seconds:g}s time limit")
@@ -0,0 +1,47 @@
1
+ """Structural file-signature checks for extensions where a signature is well defined."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from modmanifest.context import ScanContext
6
+ from modmanifest.models import Finding, Status
7
+
8
+ _ZIP = (b"PK\x03\x04", b"PK\x05\x06")
9
+ SIGNATURES: dict[str, tuple[bytes, ...]] = {
10
+ ".png": (b"\x89PNG\r\n\x1a\n",),
11
+ ".jpg": (b"\xff\xd8\xff",),
12
+ ".jpeg": (b"\xff\xd8\xff",),
13
+ ".zip": _ZIP,
14
+ ".ts4script": _ZIP,
15
+ }
16
+ # .package signatures are verified by the Sims 4 DBPF parser, not here.
17
+
18
+
19
+ def check_signatures(ctx: ScanContext) -> None:
20
+ checked = 0
21
+ for entry in ctx.source.files():
22
+ ext = "." + entry.path.rsplit(".", 1)[-1].lower() if "." in entry.path else ""
23
+ sigs = SIGNATURES.get(ext)
24
+ is_webp = ext == ".webp"
25
+ if sigs is None and not is_webp:
26
+ continue
27
+ ctx.deadline.check()
28
+ head = ctx.head(entry.path, 12)
29
+ if head is None:
30
+ continue
31
+ checked += 1
32
+ ok = (
33
+ head[:4] == b"RIFF" and head[8:12] == b"WEBP"
34
+ if is_webp
35
+ else any(head.startswith(s) for s in sigs or ())
36
+ )
37
+ if not ok:
38
+ ctx.report.add(
39
+ Finding(
40
+ "MM-ARC-013",
41
+ Status.WARNING,
42
+ f"extension '{ext}' does not match the file signature",
43
+ source=entry.path,
44
+ )
45
+ )
46
+ if checked:
47
+ ctx.report.capability("archive.signature_check")