xfh 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.
- xfh-0.1.0/LICENSE +26 -0
- xfh-0.1.0/NOTICE +10 -0
- xfh-0.1.0/PKG-INFO +140 -0
- xfh-0.1.0/README.md +104 -0
- xfh-0.1.0/docs/codec-support.md +32 -0
- xfh-0.1.0/docs/format.md +35 -0
- xfh-0.1.0/docs/oracle-matrix.md +121 -0
- xfh-0.1.0/docs/release-checklist.md +77 -0
- xfh-0.1.0/docs/research/initial-findings.md +107 -0
- xfh-0.1.0/pyproject.toml +77 -0
- xfh-0.1.0/src/xfh/__init__.py +33 -0
- xfh-0.1.0/src/xfh/__main__.py +5 -0
- xfh-0.1.0/src/xfh/api.py +171 -0
- xfh-0.1.0/src/xfh/cli.py +101 -0
- xfh-0.1.0/src/xfh/codecs/__init__.py +63 -0
- xfh-0.1.0/src/xfh/codecs/_prefix.py +41 -0
- xfh-0.1.0/src/xfh/codecs/_streams.py +102 -0
- xfh-0.1.0/src/xfh/codecs/acca.py +71 -0
- xfh-0.1.0/src/xfh/codecs/artm.py +91 -0
- xfh-0.1.0/src/xfh/codecs/blzw.py +103 -0
- xfh-0.1.0/src/xfh/codecs/crm.py +145 -0
- xfh-0.1.0/src/xfh/codecs/dlta.py +19 -0
- xfh-0.1.0/src/xfh/codecs/fast.py +22 -0
- xfh-0.1.0/src/xfh/codecs/fbr2.py +40 -0
- xfh-0.1.0/src/xfh/codecs/hfmn.py +56 -0
- xfh-0.1.0/src/xfh/codecs/huff.py +53 -0
- xfh-0.1.0/src/xfh/codecs/ilzr.py +37 -0
- xfh-0.1.0/src/xfh/codecs/impl.py +114 -0
- xfh-0.1.0/src/xfh/codecs/lhlb.py +124 -0
- xfh-0.1.0/src/xfh/codecs/lz_small.py +105 -0
- xfh-0.1.0/src/xfh/codecs/lzw_variants.py +96 -0
- xfh-0.1.0/src/xfh/codecs/mash.py +54 -0
- xfh-0.1.0/src/xfh/codecs/none.py +14 -0
- xfh-0.1.0/src/xfh/codecs/nuke.py +100 -0
- xfh-0.1.0/src/xfh/codecs/pwpk.py +104 -0
- xfh-0.1.0/src/xfh/codecs/rake.py +137 -0
- xfh-0.1.0/src/xfh/codecs/rdcn.py +44 -0
- xfh-0.1.0/src/xfh/codecs/rle.py +69 -0
- xfh-0.1.0/src/xfh/codecs/shri.py +305 -0
- xfh-0.1.0/src/xfh/codecs/smpl.py +32 -0
- xfh-0.1.0/src/xfh/codecs/sqsh.py +112 -0
- xfh-0.1.0/src/xfh/codecs/standard.py +56 -0
- xfh-0.1.0/src/xfh/codecs/wrappers.py +84 -0
- xfh-0.1.0/src/xfh/codecs/zeno.py +79 -0
- xfh-0.1.0/src/xfh/container.py +258 -0
- xfh-0.1.0/src/xfh/errors.py +31 -0
- xfh-0.1.0/src/xfh/limits.py +20 -0
- xfh-0.1.0/src/xfh/models.py +57 -0
- xfh-0.1.0/tests/__init__.py +1 -0
- xfh-0.1.0/tests/fixtures/diskexpander/cancel-packed.hex +9 -0
- xfh-0.1.0/tests/fixtures/diskexpander/cancel-plain.hex +13 -0
- xfh-0.1.0/tests/fixtures/oracle/acca100-text.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/blzw060-zero256.hex +3 -0
- xfh-0.1.0/tests/fixtures/oracle/bytes.hex +6 -0
- xfh-0.1.0/tests/fixtures/oracle/bzp2040-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/cbr0050-zero256.hex +2 -0
- xfh-0.1.0/tests/fixtures/oracle/crm2-independent-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/crms-independent-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/dlta100-text.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/duke050-bytes.hex +3 -0
- xfh-0.1.0/tests/fixtures/oracle/fast050-repeat1k.hex +5 -0
- xfh-0.1.0/tests/fixtures/oracle/fbr2100-zero256.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/frle016-zero256.hex +2 -0
- xfh-0.1.0/tests/fixtures/oracle/gzip065-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/hfmn000-text.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/huff050-bytes.hex +7 -0
- xfh-0.1.0/tests/fixtures/oracle/ilzr050-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/impl100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lhlb100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lzbs100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lzw2100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lzw3100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lzw4100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/lzw5100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/manifest.json +121 -0
- xfh-0.1.0/tests/fixtures/oracle/mash100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/none050-repeat1k.hex +23 -0
- xfh-0.1.0/tests/fixtures/oracle/nuke050-repeat1k.hex +3 -0
- xfh-0.1.0/tests/fixtures/oracle/pwpk-ppmc-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/rake100-repeat1k.hex +2 -0
- xfh-0.1.0/tests/fixtures/oracle/rdcn100-repeat1k.hex +9 -0
- xfh-0.1.0/tests/fixtures/oracle/repeat1k.hex +22 -0
- xfh-0.1.0/tests/fixtures/oracle/rlen050-text.hex +5 -0
- xfh-0.1.0/tests/fixtures/oracle/sdhc047-repeat1k.hex +17 -0
- xfh-0.1.0/tests/fixtures/oracle/shri100-repeat1k.hex +2 -0
- xfh-0.1.0/tests/fixtures/oracle/shri100-repeat64k.hex +6 -0
- xfh-0.1.0/tests/fixtures/oracle/slz3100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/sqsh100-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/text.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/zeno050-repeat1k.hex +1 -0
- xfh-0.1.0/tests/fixtures/oracle/zero256.hex +2 -0
- xfh-0.1.0/tests/helpers.py +54 -0
- xfh-0.1.0/tests/test_api.py +54 -0
- xfh-0.1.0/tests/test_cli.py +60 -0
- xfh-0.1.0/tests/test_codecs.py +336 -0
- xfh-0.1.0/tests/test_fixture_manifest.py +130 -0
- xfh-0.1.0/tests/test_legacy.py +53 -0
- xfh-0.1.0/tests/test_oracle_tools.py +40 -0
- xfh-0.1.0/tests/test_properties.py +70 -0
- xfh-0.1.0/tools/abk2png.ts +43 -0
- xfh-0.1.0/tools/amigadates.ts +67 -0
- xfh-0.1.0/tools/amigaget.ts +56 -0
- xfh-0.1.0/tools/amigapeek.ts +42 -0
- xfh-0.1.0/tools/fsuae/README.md +49 -0
- xfh-0.1.0/tools/fsuae/Startup-Sequence +9 -0
- xfh-0.1.0/tools/fsuae/oracle.py +501 -0
- xfh-0.1.0/tools/fsuae/probe-stage +19 -0
- xfh-0.1.0/tools/fsuae/query-stage +6 -0
- xfh-0.1.0/tools/fsuae/run.py +61 -0
- xfh-0.1.0/tools/try_xpkf.py +34 -0
- xfh-0.1.0/tools/verify_fixture_manifest.py +150 -0
xfh-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, xfh contributors
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
20
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
21
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
22
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
23
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
24
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
25
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
26
|
+
POSSIBILITY OF SUCH DAMAGE.
|
xfh-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Parts of the XPK codec implementations are derived from Ancient:
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2025, Teemu Suutari
|
|
4
|
+
https://github.com/temisu/ancient
|
|
5
|
+
|
|
6
|
+
Ancient is distributed under the BSD 2-Clause License. Its copyright and
|
|
7
|
+
license terms are retained in the relevant source modules.
|
|
8
|
+
|
|
9
|
+
Historical XFH and XPK source code is used as format documentation and test
|
|
10
|
+
oracle material only. It is not included in this distribution.
|
xfh-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xfh
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Recover Amiga XPK files, including files written through XFH
|
|
5
|
+
Keywords: amiga,compression,data-recovery,xpk,xfh
|
|
6
|
+
Author: xfh contributors
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-Expression: BSD-2-Clause
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: System :: Archiving :: Compression
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
License-File: NOTICE
|
|
23
|
+
Requires-Dist: build>=1.2 ; extra == "dev"
|
|
24
|
+
Requires-Dist: coverage>=7.6 ; extra == "dev"
|
|
25
|
+
Requires-Dist: flit_core>=3.10 ; extra == "dev"
|
|
26
|
+
Requires-Dist: hypothesis>=6.100 ; extra == "dev"
|
|
27
|
+
Requires-Dist: pre-commit>=4 ; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest>=8 ; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=6 ; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.9 ; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=6 ; extra == "dev"
|
|
32
|
+
Project-URL: Issues, https://github.com/bitplane/xfh/issues
|
|
33
|
+
Project-URL: Repository, https://github.com/bitplane/xfh
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
|
|
36
|
+
# xfh
|
|
37
|
+
|
|
38
|
+
`xfh` is a pure-Python data-recovery library and command-line tool for files
|
|
39
|
+
compressed with the Amiga XPK system, including files written transparently
|
|
40
|
+
through DiskExpander.
|
|
41
|
+
|
|
42
|
+
The project is under active format-recovery work. It currently supports the
|
|
43
|
+
later `XPKF` container, the DiskExpander 2.1 wrapper, and 41 codec identifiers
|
|
44
|
+
in pure Python. Historical compatibility identifiers `CBR1` (CBR0) and `FRHT`
|
|
45
|
+
(RAKE) are accepted as exact stream-format aliases. See the
|
|
46
|
+
[codec support table](https://github.com/bitplane/xfh/blob/master/docs/codec-support.md)
|
|
47
|
+
for the evidence and limitations behind each codec.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
`xfh` requires Python 3.10 or newer and has no runtime dependencies. After the
|
|
52
|
+
first release is published to PyPI, install it with:
|
|
53
|
+
|
|
54
|
+
```console
|
|
55
|
+
python -m pip install xfh
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Until then, install a checkout directly:
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
python -m pip install .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
Inspect a stream before writing anything, strictly verify it, and then unpack
|
|
67
|
+
it:
|
|
68
|
+
|
|
69
|
+
```console
|
|
70
|
+
xfh info packed-file
|
|
71
|
+
xfh verify packed-file
|
|
72
|
+
xfh unpack packed-file recovered-file
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Existing output files are not overwritten unless `--force` is supplied.
|
|
76
|
+
Decompression is limited to 256 MiB by default; use `--max-output-size` to set
|
|
77
|
+
an appropriate byte limit for a known larger file.
|
|
78
|
+
|
|
79
|
+
For a damaged stream, salvage mode writes only the prefix decoded before the
|
|
80
|
+
first failing chunk. Its exit status is `4` when recovery is incomplete:
|
|
81
|
+
|
|
82
|
+
```console
|
|
83
|
+
xfh unpack damaged-file recovered-prefix --salvage --report recovery.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The public Python API accepts bytes-like input:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
import xfh
|
|
92
|
+
|
|
93
|
+
packed = Path("packed-file").read_bytes()
|
|
94
|
+
info = xfh.inspect(packed)
|
|
95
|
+
plain = xfh.decompress(packed, limits=xfh.Limits(max_output_size=512 * 1024 * 1024))
|
|
96
|
+
Path("recovered-file").write_bytes(plain)
|
|
97
|
+
|
|
98
|
+
print(info.codec, len(plain))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use `xfh.decompress_file()` when writing to disk: it uses an atomic destination
|
|
102
|
+
write and refuses to replace an existing file by default.
|
|
103
|
+
|
|
104
|
+
## Recovery and stability
|
|
105
|
+
|
|
106
|
+
Strict decoding validates the container, declared sizes, checksums, chunk
|
|
107
|
+
output sizes, and initial-byte prefix where the format provides them. It either
|
|
108
|
+
returns the complete declared output or raises an `xfh.XfhError` subclass.
|
|
109
|
+
Password-protected streams are detected but not decoded.
|
|
110
|
+
|
|
111
|
+
Salvage is deliberately opt-in. Its output can be useful evidence, but an
|
|
112
|
+
incomplete result is not proof that every returned byte represents an intact
|
|
113
|
+
original file. Keep the source media and packed files unchanged, work on
|
|
114
|
+
copies, review the recovery report, and validate recovered data with tools
|
|
115
|
+
specific to its file type.
|
|
116
|
+
|
|
117
|
+
This is alpha software reconstructed from historical implementations and
|
|
118
|
+
surviving samples. Codec support varies in strength from headless-UAE packer
|
|
119
|
+
fixtures to source-derived decoder tests; the support table records that
|
|
120
|
+
distinction. The API and command-line interface may change before 1.0.
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
Development follows the template workflow:
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
make dev
|
|
128
|
+
make test
|
|
129
|
+
make coverage
|
|
130
|
+
make lint
|
|
131
|
+
make dist
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See [the original investigation](https://github.com/bitplane/xfh/blob/master/docs/research/initial-findings.md)
|
|
135
|
+
for the provenance of the project and
|
|
136
|
+
[the format notes](https://github.com/bitplane/xfh/blob/master/docs/format.md)
|
|
137
|
+
for current support boundaries. The
|
|
138
|
+
[oracle matrix](https://github.com/bitplane/xfh/blob/master/docs/oracle-matrix.md)
|
|
139
|
+
records results from the isolated original Amiga implementation.
|
|
140
|
+
|
xfh-0.1.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# xfh
|
|
2
|
+
|
|
3
|
+
`xfh` is a pure-Python data-recovery library and command-line tool for files
|
|
4
|
+
compressed with the Amiga XPK system, including files written transparently
|
|
5
|
+
through DiskExpander.
|
|
6
|
+
|
|
7
|
+
The project is under active format-recovery work. It currently supports the
|
|
8
|
+
later `XPKF` container, the DiskExpander 2.1 wrapper, and 41 codec identifiers
|
|
9
|
+
in pure Python. Historical compatibility identifiers `CBR1` (CBR0) and `FRHT`
|
|
10
|
+
(RAKE) are accepted as exact stream-format aliases. See the
|
|
11
|
+
[codec support table](https://github.com/bitplane/xfh/blob/master/docs/codec-support.md)
|
|
12
|
+
for the evidence and limitations behind each codec.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
`xfh` requires Python 3.10 or newer and has no runtime dependencies. After the
|
|
17
|
+
first release is published to PyPI, install it with:
|
|
18
|
+
|
|
19
|
+
```console
|
|
20
|
+
python -m pip install xfh
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Until then, install a checkout directly:
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
python -m pip install .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Inspect a stream before writing anything, strictly verify it, and then unpack
|
|
32
|
+
it:
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
xfh info packed-file
|
|
36
|
+
xfh verify packed-file
|
|
37
|
+
xfh unpack packed-file recovered-file
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Existing output files are not overwritten unless `--force` is supplied.
|
|
41
|
+
Decompression is limited to 256 MiB by default; use `--max-output-size` to set
|
|
42
|
+
an appropriate byte limit for a known larger file.
|
|
43
|
+
|
|
44
|
+
For a damaged stream, salvage mode writes only the prefix decoded before the
|
|
45
|
+
first failing chunk. Its exit status is `4` when recovery is incomplete:
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
xfh unpack damaged-file recovered-prefix --salvage --report recovery.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The public Python API accepts bytes-like input:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
import xfh
|
|
57
|
+
|
|
58
|
+
packed = Path("packed-file").read_bytes()
|
|
59
|
+
info = xfh.inspect(packed)
|
|
60
|
+
plain = xfh.decompress(packed, limits=xfh.Limits(max_output_size=512 * 1024 * 1024))
|
|
61
|
+
Path("recovered-file").write_bytes(plain)
|
|
62
|
+
|
|
63
|
+
print(info.codec, len(plain))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use `xfh.decompress_file()` when writing to disk: it uses an atomic destination
|
|
67
|
+
write and refuses to replace an existing file by default.
|
|
68
|
+
|
|
69
|
+
## Recovery and stability
|
|
70
|
+
|
|
71
|
+
Strict decoding validates the container, declared sizes, checksums, chunk
|
|
72
|
+
output sizes, and initial-byte prefix where the format provides them. It either
|
|
73
|
+
returns the complete declared output or raises an `xfh.XfhError` subclass.
|
|
74
|
+
Password-protected streams are detected but not decoded.
|
|
75
|
+
|
|
76
|
+
Salvage is deliberately opt-in. Its output can be useful evidence, but an
|
|
77
|
+
incomplete result is not proof that every returned byte represents an intact
|
|
78
|
+
original file. Keep the source media and packed files unchanged, work on
|
|
79
|
+
copies, review the recovery report, and validate recovered data with tools
|
|
80
|
+
specific to its file type.
|
|
81
|
+
|
|
82
|
+
This is alpha software reconstructed from historical implementations and
|
|
83
|
+
surviving samples. Codec support varies in strength from headless-UAE packer
|
|
84
|
+
fixtures to source-derived decoder tests; the support table records that
|
|
85
|
+
distinction. The API and command-line interface may change before 1.0.
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
Development follows the template workflow:
|
|
90
|
+
|
|
91
|
+
```console
|
|
92
|
+
make dev
|
|
93
|
+
make test
|
|
94
|
+
make coverage
|
|
95
|
+
make lint
|
|
96
|
+
make dist
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
See [the original investigation](https://github.com/bitplane/xfh/blob/master/docs/research/initial-findings.md)
|
|
100
|
+
for the provenance of the project and
|
|
101
|
+
[the format notes](https://github.com/bitplane/xfh/blob/master/docs/format.md)
|
|
102
|
+
for current support boundaries. The
|
|
103
|
+
[oracle matrix](https://github.com/bitplane/xfh/blob/master/docs/oracle-matrix.md)
|
|
104
|
+
records results from the isolated original Amiga implementation.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Codec support and evidence
|
|
2
|
+
|
|
3
|
+
`xfh` currently recognizes 41 XPK identifiers. “Amiga pack” means at least one
|
|
4
|
+
fixture was created and unpacked by the original Amiga libraries under
|
|
5
|
+
headless UAE, then decoded byte-exactly by Python. “Derived” means the decoder
|
|
6
|
+
is covered by source-derived or transformed fixtures but an original packer
|
|
7
|
+
artifact is not yet available.
|
|
8
|
+
|
|
9
|
+
| Evidence | Codecs |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Original Amiga pack/unpack | ACCA, BLZW, BZP2, CBR0, DLTA, DUKE, FAST, FBR2, FRLE, GZIP, HFMN, HUFF, ILZR, IMPL, LHLB, LZBS, LZW2, LZW3, LZW4, LZW5, MASH, NONE, NUKE, RAKE, RDCN, RLEN, SDHC, SHRI, SLZ3, SQSH, ZENO |
|
|
12
|
+
| Exact historical aliases | CBR1, FRHT |
|
|
13
|
+
| Derived or transformed | ARTM, CRM2, CRMS, CYB2, PWPK, SHR3, SMPL, TDCS |
|
|
14
|
+
|
|
15
|
+
The important remaining evidence gaps are:
|
|
16
|
+
|
|
17
|
+
- CYB2 needs a genuine file created through XpkCybPrefs; its standalone
|
|
18
|
+
library does not expose packing modes and rejects hand-built containers.
|
|
19
|
+
- TDCS needs an original library or authentic packed sample.
|
|
20
|
+
- ARTM and SHR3 need preserved original libraries.
|
|
21
|
+
- SMPL’s original packer selected raw chunks for every tested vector, so its
|
|
22
|
+
compressed-stream test remains source-derived.
|
|
23
|
+
|
|
24
|
+
PWPK has a positive stream produced by the original Amiga PPMC tool and
|
|
25
|
+
cross-checked with Ancient. CRM2 and CRMS have positive streams generated and
|
|
26
|
+
decoded by an independent Crunch-Mania implementation. They remain in the
|
|
27
|
+
derived row because the committed XPK envelopes were constructed outside the
|
|
28
|
+
original XPK master library.
|
|
29
|
+
|
|
30
|
+
The preservation archive contains further codecs that are not implemented
|
|
31
|
+
yet. Wrapper/backend formats are being prioritized before encryption,
|
|
32
|
+
authentication, and preference-system formats.
|
xfh-0.1.0/docs/format.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Format notes
|
|
2
|
+
|
|
3
|
+
## XPKF
|
|
4
|
+
|
|
5
|
+
`xfh` validates the 36-byte global header, optional extended header, short and
|
|
6
|
+
long chunk headers, header XORs, payload XORs, four-byte alignment, declared
|
|
7
|
+
sizes, and the terminal chunk before decoding. Integers are big-endian.
|
|
8
|
+
|
|
9
|
+
## DiskExpander 2.1 stream
|
|
10
|
+
|
|
11
|
+
The recovered files begin with `01 80 63 68 05 61 01 0a`, followed by the
|
|
12
|
+
unpacked size, a packed-length field (measured from byte 8), and an eight-byte
|
|
13
|
+
NUL-padded sublibrary name such as `xpkNUKE`. It is not a later `XPKF`
|
|
14
|
+
stream.
|
|
15
|
+
|
|
16
|
+
The second length is the absolute offset of a chunk-boundary table at the end
|
|
17
|
+
of the file. The table is a sequence of big-endian 32-bit offsets. It begins
|
|
18
|
+
with 24, ends with its own file offset, and therefore describes every chunk as
|
|
19
|
+
a pair of adjacent boundaries. Each chunk starts with a big-endian 16-bit
|
|
20
|
+
compressed-stream length followed by exactly that many codec bytes.
|
|
21
|
+
The length value `0xffff` is a raw-block sentinel; in that case exactly
|
|
22
|
+
10,000 uncompressed bytes follow.
|
|
23
|
+
|
|
24
|
+
DiskExpander's `Block 10` setting divides unpacked data into decimal 10,000-byte
|
|
25
|
+
chunks; the final chunk contains the remainder. Its bundled `xpkNUKE.library`
|
|
26
|
+
identifies itself as NUKE 1.0 and is byte-identical to the public XPK 2.4-era
|
|
27
|
+
library. Passing only the bytes after each two-byte chunk length to the NUKE
|
|
28
|
+
decoder recovers the known 130-byte `Cancel.gads` sample to its 204-byte
|
|
29
|
+
plaintext exactly.
|
|
30
|
+
|
|
31
|
+
The identification comes from the recovered startup command
|
|
32
|
+
`DiskExpander ... Compressor NUKE Block 10 Table 1000 ...`, the `$VER:
|
|
33
|
+
DiskExpander 2.1` strings in its executable and handlers, and matching wrapper
|
|
34
|
+
magic in those handlers. It is distinct from XFH and from the later `XPKF`
|
|
35
|
+
container.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Original-code oracle matrix
|
|
2
|
+
|
|
3
|
+
The isolated oracle uses FS-UAE 3.2.35, Workbench 3.1, xpkmaster 4.16
|
|
4
|
+
(21-Aug-1997), and recovered compressor libraries. ROMs, Amiga binaries,
|
|
5
|
+
private files, logs, and the complete workspace are not distributed.
|
|
6
|
+
|
|
7
|
+
`xQuery` reported these mode ranges:
|
|
8
|
+
|
|
9
|
+
| Codec | Default | Ranges |
|
|
10
|
+
| --- | ---: | --- |
|
|
11
|
+
| NONE | 50 | 0–100 |
|
|
12
|
+
| NUKE | 50 | 0–100 |
|
|
13
|
+
| FAST | 50 | 0–79, 80–100 |
|
|
14
|
+
| RAKE | 100 | 0–25, 26–50, 51–75, 76–100 |
|
|
15
|
+
| HUFF | 50 | 0–100 |
|
|
16
|
+
| SHRI | 100 | 0–14, 15–28, 29–42, 43–56, 57–70, 71–84, 85–100 |
|
|
17
|
+
| DLTA | 100 | 0–100 |
|
|
18
|
+
| SMPL | 50 | 0–100 |
|
|
19
|
+
| HFMN | 0 | 0–100 |
|
|
20
|
+
| MASH | 100 | 0–100 |
|
|
21
|
+
| SQSH | 100 | 0–100 |
|
|
22
|
+
| LZBS | 100 | 0, 1–10, 11–20, 21–30, 31–40, 41–50, 51–60, 61–70, 71–80, 81–90, 91–100 |
|
|
23
|
+
| SLZ3 | 100 | 0–100 |
|
|
24
|
+
| SDHC | 50 | 0–7, 8–15, 16–23, 24–31, 32–39, 40–47, 48–55, 56–63, 64–71, 72–79, 80–87, 88–100 |
|
|
25
|
+
| LHLB | 100 | 0–100 |
|
|
26
|
+
| BZP2 | 40 | 0–19, 20–29, 30–39, 40–49, 50–59, 60–69, 70–79, 80–89, 90–100 |
|
|
27
|
+
| GZIP | 65 | 0–9, 10–19, 20–29, 30–39, 40–49, 50–59, 60–69, 70–79, 80–89, 90–100 |
|
|
28
|
+
| IMPL | 100 | 0–10, 11–30, 31–50, 51–75, 76–98, 99–100 |
|
|
29
|
+
|
|
30
|
+
The matrix exercised every numeric mode on a canonical text vector and every
|
|
31
|
+
range boundary/default on deterministic pattern and size vectors:
|
|
32
|
+
|
|
33
|
+
- 990 byte-exact pack/unpack successes;
|
|
34
|
+
- 36 empty-input cases exposing an original-tool defect: a 36-byte XPKF header
|
|
35
|
+
is emitted, then rejected by the same master library;
|
|
36
|
+
- one HUFF case which hangs after reporting successful compression, followed
|
|
37
|
+
by 11 deliberately unrun HUFF cases;
|
|
38
|
+
- zero round-trip mismatches among completed non-empty cases.
|
|
39
|
+
|
|
40
|
+
All 1,026 emitted containers begin with `XPKF`. Small representative fixtures
|
|
41
|
+
for all six codecs are committed as reviewable hexadecimal files and validate
|
|
42
|
+
the current pure-Python implementations.
|
|
43
|
+
|
|
44
|
+
A second isolated run added `CBR0`, `RLEN`, `FRLE`, `RDCN`, `BLZW`, and
|
|
45
|
+
`DUKE`. Across the completed non-empty cases, all 372 original packed/plain
|
|
46
|
+
artifacts decode byte-exactly in Python. Compact acceptance containers for
|
|
47
|
+
each codec are committed as hexadecimal fixtures. The historical packers are
|
|
48
|
+
run in separate or short-lived emulator sessions because repeated use can
|
|
49
|
+
crash the original Amiga process; this is an oracle limitation rather than an
|
|
50
|
+
accepted decoder failure.
|
|
51
|
+
|
|
52
|
+
A third set covers `DLTA`, `SMPL`, `HFMN`, `MASH`, and `SQSH`. Short isolated
|
|
53
|
+
runs produced 35 byte-exact original pack/unpack artifacts across modes 0,
|
|
54
|
+
default, and 100. Python directly decoded all 20 generated compressed DLTA,
|
|
55
|
+
HFMN, MASH, and SQSH containers. The SMPL packer selected raw XPK chunks for
|
|
56
|
+
all 15 vectors, so its decoder additionally has a source-derived synthetic
|
|
57
|
+
prefix-code test rather than claiming compressed oracle coverage. DLTA hangs
|
|
58
|
+
on a one-byte input and HFMN hangs on one broad byte-pattern input; those
|
|
59
|
+
original-packer defects are excluded from the safe fixture stage.
|
|
60
|
+
|
|
61
|
+
Every compact committed acceptance container is recorded in the public
|
|
62
|
+
manifest with its input, output, size, and SHA-256 hashes. Known hanging
|
|
63
|
+
packer/vector pairs are machine-readable exclusions and are never emitted
|
|
64
|
+
into a guest stage. `oracle.py verify-python` performs the direct comparison
|
|
65
|
+
and records its results as JSON.
|
|
66
|
+
|
|
67
|
+
The CyberYAFA `LZW2`–`LZW5` variants are now backed by preserved original
|
|
68
|
+
libraries and headless UAE fixtures across every reported mode boundary.
|
|
69
|
+
`SHR3` remains source-derived because no corresponding original library has
|
|
70
|
+
been located; it is additionally tested by converting both packed chunks from
|
|
71
|
+
the 64 KiB SHRI continuation fixture to its headerless chunk representation.
|
|
72
|
+
|
|
73
|
+
Preserved original `ACCA`, `FBR2`, `ILZR`, and `ZENO` libraries were also
|
|
74
|
+
located and exercised under UAE. Together with the LZW family, 157 generated
|
|
75
|
+
artifacts round-trip in the original master library and decode byte-exactly in
|
|
76
|
+
Python. Compact fixtures, private library hashes, and the preservation archive
|
|
77
|
+
hash are linked in the public manifest; the binaries themselves are not
|
|
78
|
+
redistributed. `ARTM` remains source-derived because its original library has
|
|
79
|
+
not been located. Compact source-derived XPK containers for these codecs were
|
|
80
|
+
also independently compared byte-for-byte with Ancient 2.3.0 where Ancient
|
|
81
|
+
supports the emitted variant.
|
|
82
|
+
|
|
83
|
+
The next batch adds `LZBS`, `SLZ3`, `TDCS`, `LHLB`, `SDHC`, and `CYB2`.
|
|
84
|
+
Preserved `LZBS`, `SLZ3`, and `SDHC` packers produced 83 completed artifacts
|
|
85
|
+
across every reported mode boundary; Python decoded all of them byte-exactly.
|
|
86
|
+
Low-mode LZBS output also confirmed that its original depacker stops a final
|
|
87
|
+
literal run at the declared output size even when the packer rounds that run
|
|
88
|
+
up. Representative original-Amiga fixtures for all three codecs are committed.
|
|
89
|
+
|
|
90
|
+
`LHLB` initially appeared unpack-only because its required `lh.library` was
|
|
91
|
+
missing. Installing the original Aminet dependency exposes its 0–100 packing
|
|
92
|
+
range; a mode-100 artifact now round-trips in the Amiga master library and
|
|
93
|
+
decodes byte-exactly in Python. The dependency hash is recorded in the public
|
|
94
|
+
manifest, but the binary is not redistributed.
|
|
95
|
+
|
|
96
|
+
`CYB2` still reports no packing modes. Its preserved library also requires
|
|
97
|
+
`xpkcybhandle.library`; with that dependency and its `CYB1` companion installed,
|
|
98
|
+
the original library continues to reject source-constructed CYB2 containers
|
|
99
|
+
before unpacking. Consequently CYB2 remains source-derived rather than being
|
|
100
|
+
mislabelled as original-Amiga verified. `TDCS` has no library in the
|
|
101
|
+
preservation archive. Both retain synthetic success and malformed-stream
|
|
102
|
+
coverage.
|
|
103
|
+
|
|
104
|
+
Preserved BZP2, GZIP, and IMPL packers generated representative compressed
|
|
105
|
+
fixtures that round-trip under headless UAE and decode byte-exactly in Python.
|
|
106
|
+
The complete boundary/default matrix now contains 36 BZP2, 42 GZIP, 24 IMPL,
|
|
107
|
+
and six LHLB byte-exact successes with no Python mismatches.
|
|
108
|
+
|
|
109
|
+
PWPK, CRM2, and CRMS expose no packing modes in the preserved XPK libraries.
|
|
110
|
+
An original Amiga PPMC utility generated the positive PowerPacker stream used
|
|
111
|
+
by the PWPK fixture, which was also checked with Ancient. CRM2 and CRMS use
|
|
112
|
+
positive streams generated and decoded by an independent Crunch-Mania
|
|
113
|
+
implementation. Their XPK envelopes remain source-derived and are labelled
|
|
114
|
+
accordingly.
|
|
115
|
+
|
|
116
|
+
FS-UAE runs behind Xvfb with Mesa software rendering, so oracle generation
|
|
117
|
+
does not map a window onto the host desktop.
|
|
118
|
+
|
|
119
|
+
The recovered private wrapper beginning `01 80 63 68 05 61 01 0a` was not
|
|
120
|
+
recognized as packed data by the original master library. That result rules
|
|
121
|
+
out using generated XPKF files to infer its outer framing.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Release checklist
|
|
2
|
+
|
|
3
|
+
This checklist covers an `xfh` release. CI modernization is intentionally
|
|
4
|
+
deferred to `example-python-project` so it can be back-ported consistently
|
|
5
|
+
across repositories.
|
|
6
|
+
|
|
7
|
+
## Prepare
|
|
8
|
+
|
|
9
|
+
- Confirm the intended version and supported Python versions.
|
|
10
|
+
- Confirm the `xfh` distribution name is still available on PyPI. A missing
|
|
11
|
+
project page does not reserve a name; only a successful upload does.
|
|
12
|
+
- Create the PyPI project/account configuration, enable two-factor
|
|
13
|
+
authentication, and prefer a narrowly scoped trusted publisher or project
|
|
14
|
+
token.
|
|
15
|
+
- Update `project.version` in `pyproject.toml`.
|
|
16
|
+
- Check that the codec support table and recovery limitations match the code.
|
|
17
|
+
- Review `git diff`, `git status`, and the commits included since the previous
|
|
18
|
+
release.
|
|
19
|
+
|
|
20
|
+
## Validate
|
|
21
|
+
|
|
22
|
+
Run the repository checks from a clean checkout:
|
|
23
|
+
|
|
24
|
+
```console
|
|
25
|
+
make coverage
|
|
26
|
+
make lint
|
|
27
|
+
make dist
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then verify the built artifacts rather than the source checkout:
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
python -m venv ~/tmp/xfh-release-venv
|
|
34
|
+
~/tmp/xfh-release-venv/bin/python -m pip install dist/xfh-*.whl
|
|
35
|
+
~/tmp/xfh-release-venv/bin/xfh --help
|
|
36
|
+
~/tmp/xfh-release-venv/bin/python -c "import xfh; print(xfh.__file__)"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- Inspect the wheel and source archive file lists for missing or private data.
|
|
40
|
+
- Confirm `twine check dist/*` passes.
|
|
41
|
+
- Exercise `info`, `verify`, and `unpack` against a known fixture with the
|
|
42
|
+
installed wheel.
|
|
43
|
+
- Confirm strict failure, output-size limiting, overwrite refusal, and salvage
|
|
44
|
+
exit status/reporting still behave as documented.
|
|
45
|
+
|
|
46
|
+
## Publish
|
|
47
|
+
|
|
48
|
+
- Upload to TestPyPI first when changing packaging or publishing credentials.
|
|
49
|
+
- Install the exact TestPyPI artifact in a fresh environment and repeat the
|
|
50
|
+
smoke test.
|
|
51
|
+
- Tag the reviewed release commit with the exact project version, such as
|
|
52
|
+
`0.1.0`, and push the tag.
|
|
53
|
+
- Upload exactly the already-reviewed artifacts to PyPI; do not rebuild between
|
|
54
|
+
TestPyPI and PyPI.
|
|
55
|
+
- Create a GitHub release from the tag with concise release notes.
|
|
56
|
+
|
|
57
|
+
The first PyPI upload bootstraps the project and therefore needs a temporary
|
|
58
|
+
account-wide token:
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
read -rsp "PyPI token: " PYPI_TOKEN && echo
|
|
62
|
+
export PYPI_TOKEN
|
|
63
|
+
make release
|
|
64
|
+
unset PYPI_TOKEN
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
After that upload, replace it with a token scoped to the new `xfh` project.
|
|
68
|
+
For a TestPyPI upload, additionally set `PYPI_REPOSITORY_URL` to the TestPyPI
|
|
69
|
+
legacy upload endpoint.
|
|
70
|
+
|
|
71
|
+
## Verify
|
|
72
|
+
|
|
73
|
+
- Confirm the PyPI metadata, project links, license, Python requirement, wheel,
|
|
74
|
+
and source archive render correctly.
|
|
75
|
+
- Install from PyPI in a fresh environment and repeat the fixture smoke test.
|
|
76
|
+
- Confirm the GitHub tag and release point to the published source.
|
|
77
|
+
- Announce material recovery limitations alongside the release.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Initial XFH / xpkNUKE recovery notes
|
|
2
|
+
|
|
3
|
+
## The job
|
|
4
|
+
|
|
5
|
+
`~/Documents/amiga/amiga.ddr.img` is a 3-partition Amiga drive image. Its
|
|
6
|
+
third partition, **dh2 `SHITE`**, ran **XFH** — the AmigaOS *xpk File
|
|
7
|
+
Handler*, a DOS handler that transparently compresses everything written to
|
|
8
|
+
a directory or partition via `xpkmaster.library`. **2,356 of its 5,276
|
|
9
|
+
files** are compressed with the **xpkNUKE** sublibrary and cannot currently
|
|
10
|
+
be read.
|
|
11
|
+
|
|
12
|
+
| partition | sector range | crunched | total |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| dh1 `WORKBENCH` | 1536 + 82944 | 1 | 4,459 |
|
|
15
|
+
| dh0 `WORK` | 84480 + 411648 | 0 | 7,433 |
|
|
16
|
+
| **dh2 `SHITE`** | **496128 + 217344** | **2,356** | **5,276** |
|
|
17
|
+
|
|
18
|
+
Goal: read them. The headline prize is `kaos/shit/` — 63 files of digitised
|
|
19
|
+
animation frames from 1991–1995 by Matt Benson, Dave Gary, James Benson and
|
|
20
|
+
John Hayes, including a claymation wombat with six arms, one of which falls
|
|
21
|
+
off mid-animation. But it is 2,356 files in total, across `Other`, `Games`,
|
|
22
|
+
`t`, `GadToolsBox`, `Legion`, `GMS` and more.
|
|
23
|
+
|
|
24
|
+
## Why it is XFH and not someone crunching files
|
|
25
|
+
|
|
26
|
+
- **252 crunched files in `t/`** — the temp directory. Nobody hand-crunches
|
|
27
|
+
temp files.
|
|
28
|
+
- `.info` icons of ~300 bytes are crunched, where compression is pointless.
|
|
29
|
+
- It spans a dozen unrelated drawers, and is confined to one partition.
|
|
30
|
+
|
|
31
|
+
No `xpkmaster.library` and no XFH handler survive on the drive (only
|
|
32
|
+
`xfdmaster`, bundled with virus checkers, which is the unrelated *decrunch*
|
|
33
|
+
library). So the handler binary is not available to disassemble.
|
|
34
|
+
|
|
35
|
+
## The container, as far as it is decoded
|
|
36
|
+
|
|
37
|
+
Verified byte-exactly against `samples/matt1.info` (310 bytes on disk):
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
+0 01 80 63 68 05 61 01 0a constant magic, identical in every file
|
|
41
|
+
+8 ULONG unpacked length (460 for matt1.info)
|
|
42
|
+
+12 ULONG length of everything from +8 onward
|
|
43
|
+
(302; and 302 + 8 = 310 = file size)
|
|
44
|
+
+16 "xpkNUKE\0" 8 bytes, NUL-padded
|
|
45
|
+
+24 compressed data
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Sample sizes:
|
|
49
|
+
|
|
50
|
+
| file | on disk | unpacked (from +8) |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `matt1.info` | 310 | 460 |
|
|
53
|
+
| `wombat1.pic` | 12,492 | 19,844 |
|
|
54
|
+
| `wombat` | 112,134 | 122,780 |
|
|
55
|
+
| `wombat2` | 190,746 | 206,006 |
|
|
56
|
+
|
|
57
|
+
**Unsolved:** for `wombat` the arithmetic leaves 560 bytes unaccounted for
|
|
58
|
+
(file − 8 − declared length). 560 = 35 × 16, and an XPK chunk header is 16
|
|
59
|
+
bytes, so the payload is probably chunked with per-chunk headers. The small
|
|
60
|
+
single-chunk files fit the simple layout; the large ones do not.
|
|
61
|
+
|
|
62
|
+
## What has been ruled out
|
|
63
|
+
|
|
64
|
+
**`ancient` (temisu, `apt install ancient`, installed) does NOT read these.**
|
|
65
|
+
It implements xpkNUKE correctly, but expects an `XPKF` *stream*. These are
|
|
66
|
+
XFH's container. Keep it installed anyway — it handles DMS and LhA, which
|
|
67
|
+
the corpus is full of.
|
|
68
|
+
|
|
69
|
+
`tools/try_xpkf.py` wraps the payload from +24 in a synthetic `XPKF` header
|
|
70
|
+
and brute-forces all 256 header-checksum values, looking for one `ancient`
|
|
71
|
+
will accept. It finds none, so the payload is not a bare XPK chunk stream —
|
|
72
|
+
or the assumed 36-byte `XPKF` header layout is wrong. Worth retrying with
|
|
73
|
+
other header lengths and with reconstructed chunk headers.
|
|
74
|
+
|
|
75
|
+
## The lever: known plaintext, in quantity
|
|
76
|
+
|
|
77
|
+
**382 filenames exist both crunched on dh2 and uncrunched on dh0/dh1.**
|
|
78
|
+
Pick one whose uncrunched size matches the crunched file's declared unpacked
|
|
79
|
+
length and you have a verified plaintext/ciphertext pair to develop and
|
|
80
|
+
check against. Reproduce with:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# tools/amigapeek.ts <image> <path-filter> <first-sector> <blocks>
|
|
84
|
+
# prints "size path <first 24 bytes as text>" per file
|
|
85
|
+
npx tsx tools/amigapeek.ts ~/Documents/amiga/amiga.ddr.img "" 496128 217344 > peek-dh2.txt
|
|
86
|
+
npx tsx tools/amigapeek.ts ~/Documents/amiga/amiga.ddr.img "" 84480 411648 > peek-dh0.txt
|
|
87
|
+
grep xpkNUKE peek-dh2.txt | awk '{print $2}' | awk -F/ '{print $NF}' | LC_ALL=C sort -u > c.txt
|
|
88
|
+
grep -v xpkNUKE peek-dh0.txt | awk '{print $2}' | awk -F/ '{print $NF}' | LC_ALL=C sort -u > u.txt
|
|
89
|
+
LC_ALL=C comm -12 c.txt u.txt
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Tools here
|
|
93
|
+
|
|
94
|
+
Run from `~/src/tmp/amos/amos-ts` (they import from its `src/`):
|
|
95
|
+
|
|
96
|
+
- `tools/amigaget.ts <img> <path> <out> [sector] [blocks]` — extract one file
|
|
97
|
+
- `tools/amigapeek.ts <img> <filter> [sector] [blocks]` — list files + first bytes
|
|
98
|
+
- `tools/amigadates.ts <img> [--sector N] [--blocks N]` — real AmigaDOS
|
|
99
|
+
datestamps, tick precision
|
|
100
|
+
- `tools/abk2png.ts <file.abk> <prefix>` — render an AMOS sprite/icon bank
|
|
101
|
+
(handles the `AmBs` multi-bank wrapper)
|
|
102
|
+
- `tools/try_xpkf.py <file>` — the failed XPKF-wrapping experiment
|
|
103
|
+
|
|
104
|
+
## Where this is written up
|
|
105
|
+
|
|
106
|
+
`~/src/tmp/amos/dating-amos-games.md`, section "Blocked: dh2 ran an XPK
|
|
107
|
+
filesystem packer".
|