oaknut-zip 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.
- oaknut_zip-0.1.0/LICENSE +21 -0
- oaknut_zip-0.1.0/PKG-INFO +526 -0
- oaknut_zip-0.1.0/README.md +504 -0
- oaknut_zip-0.1.0/pyproject.toml +55 -0
- oaknut_zip-0.1.0/setup.cfg +4 -0
- oaknut_zip-0.1.0/src/oaknut_zip/__init__.py +42 -0
- oaknut_zip-0.1.0/src/oaknut_zip/__main__.py +10 -0
- oaknut_zip-0.1.0/src/oaknut_zip/api.py +306 -0
- oaknut_zip-0.1.0/src/oaknut_zip/cli.py +151 -0
- oaknut_zip-0.1.0/src/oaknut_zip/formatting.py +123 -0
- oaknut_zip-0.1.0/src/oaknut_zip/models.py +65 -0
- oaknut_zip-0.1.0/src/oaknut_zip/parsing.py +231 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/PKG-INFO +526 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/SOURCES.txt +17 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/dependency_links.txt +1 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/entry_points.txt +2 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/requires.txt +5 -0
- oaknut_zip-0.1.0/src/oaknut_zip.egg-info/top_level.txt +1 -0
- oaknut_zip-0.1.0/tests/test_oaknut_zip.py +1903 -0
oaknut_zip-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Smallshire
|
|
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,526 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oaknut-zip
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Work with ZIP files containing Acorn computer metadata.
|
|
5
|
+
Author-email: Robert Smallshire <robert@smallshire.org.uk>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: click>=8.0
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: xattr>=1.0; sys_platform == "darwin"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# oaknut-zip
|
|
24
|
+
|
|
25
|
+
A standalone Python script for extracting ZIP files containing
|
|
26
|
+
[Acorn computer](https://en.wikipedia.org/wiki/Acorn_Computers) metadata.
|
|
27
|
+
|
|
28
|
+
Standard unzip tools silently discard the load addresses, execution addresses,
|
|
29
|
+
and file attributes that Acorn systems (BBC Micro, Master, Archimedes, RISC OS
|
|
30
|
+
machines) store in ZIP archives. oaknut-zip preserves this metadata, writing it out
|
|
31
|
+
in your choice of four formats.
|
|
32
|
+
|
|
33
|
+
## The problem
|
|
34
|
+
|
|
35
|
+
When software for Acorn 8-bit and 32-bit systems is distributed as ZIP files,
|
|
36
|
+
the archives often contain platform-specific metadata embedded using mechanisms
|
|
37
|
+
that non-Acorn unzip tools ignore:
|
|
38
|
+
|
|
39
|
+
- **SparkFS extra fields** in the ZIP structure itself, carrying load/exec
|
|
40
|
+
addresses and RISC OS file attributes.
|
|
41
|
+
- **Bundled INF sidecar files** inside the archive, in either Acorn
|
|
42
|
+
or PiEconetBridge format.
|
|
43
|
+
- **Unix filename encoding**, where metadata is appended to filenames as comma-
|
|
44
|
+
separated hex suffixes.
|
|
45
|
+
|
|
46
|
+
Extracting these archives with `unzip` or Python's `zipfile` module on Linux or
|
|
47
|
+
macOS produces the raw file data but loses all the Acorn metadata. For 6502 or
|
|
48
|
+
ARM executables, this means you no longer know where in memory a program should
|
|
49
|
+
be loaded or where execution should begin. For a PiEconetBridge fileserver, the
|
|
50
|
+
files become unserviceable without their attributes.
|
|
51
|
+
|
|
52
|
+
oaknut-zip reads all three metadata sources from the ZIP and writes them out in a
|
|
53
|
+
format your target system can consume.
|
|
54
|
+
|
|
55
|
+
## Prerequisites
|
|
56
|
+
|
|
57
|
+
oaknut-zip requires only [`uv`](https://docs.astral.sh/uv/). `uv` handles
|
|
58
|
+
Python installation, dependency resolution, and virtual environments
|
|
59
|
+
automatically --- you do not need to install anything else by hand.
|
|
60
|
+
|
|
61
|
+
### Installing uv
|
|
62
|
+
|
|
63
|
+
**macOS (Homebrew):**
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
brew install uv
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Linux / macOS (standalone installer):**
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Windows:**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See the [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/)
|
|
82
|
+
for other methods including pip, pipx, Cargo, Conda, Winget, and Scoop.
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
oaknut-zip is a single-file [PEP 723](https://peps.python.org/pep-0723/) script
|
|
87
|
+
with an embedded dependency table. Run it directly:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
./oaknut_zip.py <command> [options]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or explicitly via uv:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
uv run oaknut_zip.py <command> [options]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Commands
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
$ ./oaknut_zip.py --help
|
|
103
|
+
Usage: oaknut_zip.py [OPTIONS] COMMAND [ARGS]...
|
|
104
|
+
|
|
105
|
+
Work with ZIP files containing Acorn computer metadata.
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
--version Show the version and exit.
|
|
109
|
+
--help Show this message and exit.
|
|
110
|
+
|
|
111
|
+
Commands:
|
|
112
|
+
extract Extract a ZIP file, preserving Acorn metadata.
|
|
113
|
+
info Show summary of Acorn metadata in a ZIP file.
|
|
114
|
+
list List ZIP contents showing Acorn metadata.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Inspecting an archive
|
|
118
|
+
|
|
119
|
+
### list --- tabular view of all entries
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
$ ./oaknut_zip.py list NetUtils.zip
|
|
123
|
+
NetUtils.zip
|
|
124
|
+
┏━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┓
|
|
125
|
+
┃ Filename ┃ Load ┃ Exec ┃ Length ┃ Attr ┃ Type ┃ Source ┃
|
|
126
|
+
┡━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━╇━━━━━━━━━┩
|
|
127
|
+
│ Free │ FFFF0E10 │ FFFF0E10 │ 000001E6 │ C18325D │ F0E │ sparkfs │
|
|
128
|
+
│ FSList │ FFFF0900 │ FFFF0900 │ 00000200 │ 1415365D │ F09 │ sparkfs │
|
|
129
|
+
│ PSList │ FFFF0900 │ FFFF0900 │ 000001B5 │ B861C5D │ F09 │ sparkfs │
|
|
130
|
+
│ Notify │ FFFF0E23 │ FFFF0E23 │ 0000012A │ C6B1E5D │ F0E │ sparkfs │
|
|
131
|
+
│ Remote │ FFFF0E10 │ FFFF0E10 │ 000001ED │ 1347085D │ F0E │ sparkfs │
|
|
132
|
+
│ Servers │ FFFF0900 │ FFFF091A │ 000001CF │ 17162C5D │ F09 │ sparkfs │
|
|
133
|
+
│ SetStation │ FFFFDD00 │ FFFFDD00 │ 00000200 │ 1761575D │ FDD │ sparkfs │
|
|
134
|
+
│ Stations │ FFFF08D5 │ FFFF08E1 │ 0000022B │ 13615A57 │ F08 │ sparkfs │
|
|
135
|
+
│ SJMon │ FFFF1B00 │ FFFF1B00 │ 00000D7E │ F6040D │ F1B │ sparkfs │
|
|
136
|
+
│ Users │ FFFF0E23 │ FFFF0E23 │ 00000139 │ 15355D │ F0E │ sparkfs │
|
|
137
|
+
│ View │ FFFF0900 │ FFFF0904 │ 000001FF │ 135A115D │ F09 │ sparkfs │
|
|
138
|
+
│ ReadMe │ FFFFFF52 │ 2FEEAFD0 │ 00000255 │ BEB2A55 │ FFF │ sparkfs │
|
|
139
|
+
└────────────┴──────────┴──────────┴──────────┴──────────┴──────┴─────────┘
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The **Source** column shows where the metadata was found (see the
|
|
143
|
+
[format identifier table](#extracting-an-archive) below). The **Type** column
|
|
144
|
+
shows the RISC OS filetype extracted from the load address (when the top
|
|
145
|
+
12 bits are `0xFFF`).
|
|
146
|
+
|
|
147
|
+
### info --- summary statistics
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
$ ./oaknut_zip.py info NetUtils.zip
|
|
151
|
+
Archive: NetUtils.zip
|
|
152
|
+
Files: 12
|
|
153
|
+
Dirs: 0
|
|
154
|
+
SparkFS: 12 files with ARC0 extra fields
|
|
155
|
+
inf-trad: 0 files with bundled traditional INF
|
|
156
|
+
inf-pieb: 0 files with bundled PiEconetBridge INF
|
|
157
|
+
Filename: 0 files with encoded filenames
|
|
158
|
+
Plain: 0 files without Acorn metadata
|
|
159
|
+
Filetypes: 6 distinct
|
|
160
|
+
F08: 1 files
|
|
161
|
+
F09: 4 files
|
|
162
|
+
F0E: 4 files
|
|
163
|
+
F1B: 1 files
|
|
164
|
+
FDD: 1 files
|
|
165
|
+
FFF: 1 files
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Extracting an archive
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
$ ./oaknut_zip.py extract --help
|
|
172
|
+
Usage: oaknut_zip.py extract [OPTIONS] ZIPFILE_PATH
|
|
173
|
+
|
|
174
|
+
Extract a ZIP file, preserving Acorn metadata.
|
|
175
|
+
|
|
176
|
+
Options:
|
|
177
|
+
-d, --output-dir PATH Output directory (default: ZIP filename
|
|
178
|
+
without extension).
|
|
179
|
+
-v, --verbose Show extraction progress.
|
|
180
|
+
--meta-format [inf-trad|inf-pieb|xattr|filename-riscos|filename-mos|none]
|
|
181
|
+
Metadata format: inf-trad, inf-pieb, xattr,
|
|
182
|
+
filename-riscos, filename-mos, or none.
|
|
183
|
+
--no-decode-filenames Do not decode metadata from filename
|
|
184
|
+
suffixes (,xxx or ,load,exec).
|
|
185
|
+
--owner INTEGER Econet owner ID for inf-pieb files (default:
|
|
186
|
+
0 = SYST).
|
|
187
|
+
--help Show this message and exit.
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
oaknut-zip uses a consistent `<mechanism>-<flavour>` naming scheme for format
|
|
191
|
+
identifiers. The same identifiers appear as metadata source labels in
|
|
192
|
+
the `list` and `info` commands, and as `--meta-format` choices for output:
|
|
193
|
+
|
|
194
|
+
| Identifier | As input source | As output format (`--meta-format`) |
|
|
195
|
+
|-------------------|-----------------|------------------------------------|
|
|
196
|
+
| `sparkfs` | SparkFS/ARC0 extra fields in the ZIP structure | --- (not an output format) |
|
|
197
|
+
| `inf-trad` | Bundled traditional `.inf` sidecar files | Traditional INF sidecar files |
|
|
198
|
+
| `inf-pieb` | Bundled PiEconetBridge `.inf` sidecar files | PiEconetBridge INF sidecar files |
|
|
199
|
+
| `filename` | Metadata encoded in filenames (`,xxx`, `,load,exec`, `,load-exec`) | --- (not an output format) |
|
|
200
|
+
| `filename-riscos` | --- | RISC OS filename encoding (`,xxx` or `,llllllll,eeeeeeee`) |
|
|
201
|
+
| `filename-mos` | --- | MOS filename encoding (`,load-exec`) |
|
|
202
|
+
| `xattr` | --- | Extended attributes (`user.econet_*`) |
|
|
203
|
+
| `none` | --- | No metadata (raw extraction) |
|
|
204
|
+
|
|
205
|
+
### Format 1: Traditional INF (default)
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
./oaknut_zip.py extract NetUtils.zip
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Each extracted file gets a companion `.inf` sidecar file:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
$ cat SetStation.inf
|
|
215
|
+
SetStation FFFFDD00 FFFFDD00 00000200 1761575D
|
|
216
|
+
|
|
217
|
+
$ cat ReadMe.inf
|
|
218
|
+
ReadMe FFFFFF52 2FEEAFD0 00000255 BEB2A55
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The fields are space-separated, with the filename left-padded to 11 characters:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
filename load exec length [access]
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Field | Format | Description |
|
|
228
|
+
|----------|-----------------|-----------------------------------------------------------------------------|
|
|
229
|
+
| filename | ASCII string | Leaf filename, left-padded to 11 chars |
|
|
230
|
+
| load | 8-digit hex | 32-bit load address; if top 12 bits = `FFF`, bits 8--19 encode the filetype |
|
|
231
|
+
| exec | 8-digit hex | 32-bit execution address (entry point for code) |
|
|
232
|
+
| length | 8-digit hex | File length in bytes |
|
|
233
|
+
| access | 2-digit hex | Acorn file attributes (optional; see attribute bits below) |
|
|
234
|
+
|
|
235
|
+
This is the standard format understood by BBC Micro emulators, disc image tools
|
|
236
|
+
(BBCIM, Disc Image Manager), and other retro-computing utilities.
|
|
237
|
+
|
|
238
|
+
### Format 2: PiEconetBridge INF
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
./oaknut_zip.py extract --meta-format inf-pieb NetUtils.zip
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Writes `.inf` sidecar files in the format used by
|
|
245
|
+
[PiEconetBridge](https://github.com/cr12925/PiEconetBridge):
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
$ cat SetStation.inf
|
|
249
|
+
0 ffffdd00 ffffdd00 1761575d
|
|
250
|
+
|
|
251
|
+
$ cat ReadMe.inf
|
|
252
|
+
0 ffffff52 2feeafd0 beb2a55
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The fields, all lowercase hex:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
owner load exec perm
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Field | Format | Description |
|
|
262
|
+
|-------|--------------|--------------------------------------------------------|
|
|
263
|
+
| owner | short hex | Econet user ID (set with `--owner`, default 0 = SYST) |
|
|
264
|
+
| load | long hex | 32-bit load address |
|
|
265
|
+
| exec | long hex | 32-bit execution address |
|
|
266
|
+
| perm | short hex | File permissions byte |
|
|
267
|
+
|
|
268
|
+
PiEconetBridge reads this format from `<filename>.inf` when extended attribute
|
|
269
|
+
support is unavailable on the host filesystem (e.g. FAT32). This is the format
|
|
270
|
+
produced by the `parse_acorn_zip.pl` utility bundled with PiEconetBridge.
|
|
271
|
+
|
|
272
|
+
### Format 3: Extended attributes (xattr)
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
./oaknut_zip.py extract --meta-format xattr NetUtils.zip
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Writes metadata directly into the filesystem's extended attributes, with no
|
|
279
|
+
sidecar files:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
$ xattr -l SetStation
|
|
283
|
+
user.econet_exec: FFFFDD00
|
|
284
|
+
user.econet_load: FFFFDD00
|
|
285
|
+
user.econet_owner: 0000
|
|
286
|
+
user.econet_perm: 1761575D
|
|
287
|
+
|
|
288
|
+
$ xattr -l ReadMe
|
|
289
|
+
user.econet_exec: 2FEEAFD0
|
|
290
|
+
user.econet_load: FFFFFF52
|
|
291
|
+
user.econet_owner: 0000
|
|
292
|
+
user.econet_perm: BEB2A55
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The attribute names and value formats match PiEconetBridge's C implementation
|
|
296
|
+
exactly:
|
|
297
|
+
|
|
298
|
+
| Attribute | Format (sprintf) | Example |
|
|
299
|
+
|----------------------|------------------|--------------|
|
|
300
|
+
| `user.econet_owner` | `%04X` | `0000` |
|
|
301
|
+
| `user.econet_load` | `%08X` | `FFFFDD00` |
|
|
302
|
+
| `user.econet_exec` | `%08X` | `FFFFDD00` |
|
|
303
|
+
| `user.econet_perm` | `%02X` | `03` |
|
|
304
|
+
|
|
305
|
+
This is the preferred format for PiEconetBridge when the host filesystem
|
|
306
|
+
supports extended attributes (ext4, XFS, HFS+, APFS). PiEconetBridge reads
|
|
307
|
+
xattrs in preference to `.inf` files when both are present.
|
|
308
|
+
|
|
309
|
+
### Format 4: RISC OS filename encoding
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
./oaknut_zip.py extract --meta-format filename-riscos NetUtils.zip
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Encodes metadata directly into the output filenames using comma-separated hex
|
|
316
|
+
suffixes. No sidecar files are created. Filetype-stamped files get a `,xxx`
|
|
317
|
+
suffix (e.g. `FILE,fdd`); files with literal load/exec addresses get
|
|
318
|
+
`,llllllll,eeeeeeee` (e.g. `PROG,00001900,0000801f`).
|
|
319
|
+
|
|
320
|
+
This is the most portable format --- it requires no filesystem-specific
|
|
321
|
+
support and survives transfers between any systems. Files already carrying
|
|
322
|
+
the correct suffix are not double-encoded.
|
|
323
|
+
|
|
324
|
+
### Format 5: MOS filename encoding
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
./oaknut_zip.py extract --meta-format filename-mos NetUtils.zip
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Encodes metadata into output filenames using the `,load-exec` convention
|
|
331
|
+
documented in the [BeebWiki INF file format](https://beebwiki.mdfs.net/INF_file_format)
|
|
332
|
+
specification. This format uses a comma prefix with load and exec addresses
|
|
333
|
+
separated by a dash, with variable-width hex digits (no zero-padding):
|
|
334
|
+
`,1900-801f`.
|
|
335
|
+
|
|
336
|
+
This is the convention used by MOS and SparkFS when encoding addresses in
|
|
337
|
+
filenames. Unlike the RISC OS form (Format 4), it always encodes full
|
|
338
|
+
load/exec addresses and does not special-case filetype-stamped files.
|
|
339
|
+
|
|
340
|
+
### Format 6: No metadata
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
./oaknut_zip.py extract --meta-format none NetUtils.zip
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Extracts files only, discarding all Acorn metadata. Equivalent to a standard
|
|
347
|
+
`unzip`.
|
|
348
|
+
|
|
349
|
+
## Metadata sources in ZIP files
|
|
350
|
+
|
|
351
|
+
oaknut-zip reads Acorn metadata from three sources within ZIP archives. When
|
|
352
|
+
multiple sources are present for a given entry, they are used in priority order:
|
|
353
|
+
SparkFS extra fields, then bundled INF sidecar files, then filename encoding.
|
|
354
|
+
|
|
355
|
+
### SparkFS extra fields (ARC0)
|
|
356
|
+
|
|
357
|
+
ZIP archives created by [SparkFS](http://www.davidpilling.com/wiki/index.php/SparkFS),
|
|
358
|
+
SparkPlug, or RISC OS zip tools embed Acorn metadata as a ZIP extra field with
|
|
359
|
+
header ID `0x4341` ("AC") and an "ARC0" signature.
|
|
360
|
+
|
|
361
|
+
The extra field layout (all values little-endian), as defined in the
|
|
362
|
+
[Info-ZIP extra field specification](https://libzip.org/specifications/extrafld.txt)
|
|
363
|
+
by [David Pilling](http://www.davidpilling.com/wiki/index.php/SparkFS):
|
|
364
|
+
|
|
365
|
+
| Offset | Size | Description |
|
|
366
|
+
|--------|---------|--------------------------------|
|
|
367
|
+
| 0 | 4 bytes | Signature `"ARC0"` |
|
|
368
|
+
| 4 | 4 bytes | Load address (uint32) |
|
|
369
|
+
| 8 | 4 bytes | Execution address (uint32) |
|
|
370
|
+
| 12 | 4 bytes | File attributes (uint32) |
|
|
371
|
+
| 16 | 4 bytes | Reserved (zero) |
|
|
372
|
+
|
|
373
|
+
This is the most reliable metadata source, as it is embedded in the ZIP
|
|
374
|
+
structure itself and survives transfers between any systems.
|
|
375
|
+
|
|
376
|
+
### Bundled INF sidecar files
|
|
377
|
+
|
|
378
|
+
Some ZIP archives include `.inf` sidecar files alongside the data files they
|
|
379
|
+
describe. oaknut-zip detects and parses these automatically, supporting both
|
|
380
|
+
flavours:
|
|
381
|
+
|
|
382
|
+
- **Traditional INF** (`filename load exec length [access]`) --- reported
|
|
383
|
+
as source `inf-trad` in the list and info commands.
|
|
384
|
+
- **PiEconetBridge INF** (`owner load exec perm`) --- reported as source
|
|
385
|
+
`inf-pieb`.
|
|
386
|
+
|
|
387
|
+
When extracting, bundled `.inf` files are consumed as a metadata source rather
|
|
388
|
+
than extracted as separate files. The metadata is then written in whatever
|
|
389
|
+
output format was requested (inf-trad, inf-pieb, xattr, etc.). This allows,
|
|
390
|
+
for example, converting a PiEconetBridge archive to xattr format in a single
|
|
391
|
+
step.
|
|
392
|
+
|
|
393
|
+
With `--meta-format none`, bundled `.inf` files are extracted as-is (no
|
|
394
|
+
metadata is consumed).
|
|
395
|
+
|
|
396
|
+
### Unix filename encoding
|
|
397
|
+
|
|
398
|
+
Because Unix filesystems have no way to store Acorn load/exec addresses
|
|
399
|
+
natively, a [convention](https://www.riscos.info/index.php/RISC_OS_Filename_Translation)
|
|
400
|
+
exists for encoding the RISC OS filetype into the filename itself as a
|
|
401
|
+
comma-separated hex suffix:
|
|
402
|
+
|
|
403
|
+
| Pattern | Example | Meaning |
|
|
404
|
+
|---------------------------------|----------------------------|-----------------------------|
|
|
405
|
+
| `filename,xxx` | `HELLO,ffb` | RISC OS filetype `FFB` |
|
|
406
|
+
|
|
407
|
+
oaknut-zip also supports two additional forms that encode full load and exec
|
|
408
|
+
addresses for files that are not filetype-stamped:
|
|
409
|
+
|
|
410
|
+
| Pattern | Example | Meaning |
|
|
411
|
+
|---------------------------------|----------------------------|-----------------------------|
|
|
412
|
+
| `filename,llllllll,eeeeeeee` | `PROG,ffff0e10,0000801f` | Load and exec addresses |
|
|
413
|
+
| `filename,load-exec` | `PROG,1900-801f` | Load and exec (MOS/SparkFS) |
|
|
414
|
+
|
|
415
|
+
The first form is used by
|
|
416
|
+
[python-zipinfo-riscos](https://github.com/gerph/python-zipinfo-riscos)
|
|
417
|
+
and requires exactly 8 hex digits per address. The second form, documented
|
|
418
|
+
in the [BeebWiki INF file format](https://beebwiki.mdfs.net/INF_file_format)
|
|
419
|
+
specification, uses variable-width hex digits separated by a dash and is the
|
|
420
|
+
convention used by MOS and SparkFS.
|
|
421
|
+
|
|
422
|
+
oaknut-zip strips these suffixes from output filenames during extraction.
|
|
423
|
+
Use `--no-decode-filenames` to preserve the encoded filenames as-is.
|
|
424
|
+
|
|
425
|
+
## RISC OS filetype encoding
|
|
426
|
+
|
|
427
|
+
When the top 12 bits of a load address are `0xFFF`, the address does not
|
|
428
|
+
represent a literal memory location. Instead, bits 8--19 encode a
|
|
429
|
+
[RISC OS filetype](https://en.wikipedia.org/wiki/List_of_RISC_OS_filetypes):
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
Load address: FFFtttxx
|
|
433
|
+
^^^
|
|
434
|
+
filetype (3 hex digits)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Example RISC OS filetypes:
|
|
438
|
+
|
|
439
|
+
| Type | Name | Description |
|
|
440
|
+
|-------|------------|-------------------------------------------|
|
|
441
|
+
| `FF9` | Sprite | Sprite or saved screen |
|
|
442
|
+
| `FFA` | Module | Relocatable module |
|
|
443
|
+
| `FFB` | BASIC | Tokenised BASIC program |
|
|
444
|
+
| `FFC` | Utility | Position independent code |
|
|
445
|
+
| `FFD` | Data | Arbitrary data |
|
|
446
|
+
| `FFE` | Command | Command (Exec) file |
|
|
447
|
+
| `FFF` | Text | Plain ASCII text with LF newlines |
|
|
448
|
+
|
|
449
|
+
## Acorn file attribute bits
|
|
450
|
+
|
|
451
|
+
The attribute byte encodes access permissions in the Acorn filing system
|
|
452
|
+
convention:
|
|
453
|
+
|
|
454
|
+
| Bit | Mask | Meaning |
|
|
455
|
+
|-----|--------|------------------|
|
|
456
|
+
| 0 | `0x01` | Owner writable |
|
|
457
|
+
| 1 | `0x02` | Owner readable |
|
|
458
|
+
| 3 | `0x08` | Locked (DFS `L`) |
|
|
459
|
+
| 4 | `0x10` | Public writable |
|
|
460
|
+
| 5 | `0x20` | Public readable |
|
|
461
|
+
|
|
462
|
+
A file with attributes `0x03` is owner-readable and owner-writable (WR).
|
|
463
|
+
A value of `0x17` adds public read access (WR/R), which is the default
|
|
464
|
+
for files served by PiEconetBridge.
|
|
465
|
+
|
|
466
|
+
## Running the tests
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
uv run --with pytest --with xattr pytest tests/ -v
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
The test suite covers all parsing, formatting, and extraction logic,
|
|
473
|
+
including integration tests against six real-world ZIP fixtures:
|
|
474
|
+
|
|
475
|
+
- **NetUtils.zip** --- Econet utilities from [MDFS](https://mdfs.net/Apps/Networking/),
|
|
476
|
+
with SparkFS/ARC0 extra fields. This is the archive that motivated the project.
|
|
477
|
+
- **NetUtilB.zip** --- A second Econet utilities pack, also with SparkFS metadata.
|
|
478
|
+
- **MASTER.zip** --- BBC Master utilities from
|
|
479
|
+
[MDFS](https://mdfs.net/Mirror/Archive/SJ/MDFS/MASTER.zip), 344 files with
|
|
480
|
+
SparkFS metadata and 36 distinct filetypes.
|
|
481
|
+
- **sweh_econet_system.zip** --- PiEconetBridge system files from
|
|
482
|
+
[sweh](https://sweh.spuddy.org/tmp/econet-bridge/), containing bundled
|
|
483
|
+
PiEconetBridge-format `.inf` sidecar files (no SparkFS extra fields).
|
|
484
|
+
- **testdir-unix.zip** --- Test archive from
|
|
485
|
+
[python-zipinfo-riscos](https://github.com/gerph/python-zipinfo-riscos),
|
|
486
|
+
with Unix filename encoding (`,xxx` suffixes).
|
|
487
|
+
- **testdir-ro.zip** --- The same content as testdir-unix.zip but with SparkFS
|
|
488
|
+
extra fields instead of filename encoding.
|
|
489
|
+
|
|
490
|
+
## References
|
|
491
|
+
|
|
492
|
+
### Metadata format specifications
|
|
493
|
+
|
|
494
|
+
- [INF file format](https://beebwiki.mdfs.net/INF_file_format) ---
|
|
495
|
+
BeebWiki specification for the Acorn `.inf` sidecar format.
|
|
496
|
+
- [ZIP extra field registry](https://libzip.org/specifications/extrafld.txt) ---
|
|
497
|
+
Known ZIP extra field types, including the Acorn/SparkFS entry (`0x4341`).
|
|
498
|
+
- [RISC OS filetype](http://justsolve.archiveteam.org/wiki/RISC_OS_filetype) ---
|
|
499
|
+
How RISC OS encodes filetypes in load addresses.
|
|
500
|
+
- [ZipToInf documentation](https://mdfs.net/Apps/Archivers/ZipTools/ZipToInf.txt) ---
|
|
501
|
+
J.G.Harston's ZipToInf utility and INF format description.
|
|
502
|
+
- [RISC OS filename translation](https://www.riscos.info/index.php/RISC_OS_Filename_Translation) ---
|
|
503
|
+
Documents the `,xxx` filetype suffix convention used on Unix filesystems.
|
|
504
|
+
- [INF file format](https://mdfs.net/Docs/Comp/BBC/FileFormat/INFfile) ---
|
|
505
|
+
J.G.Harston's detailed INF file format specification.
|
|
506
|
+
|
|
507
|
+
### Tools and related projects
|
|
508
|
+
|
|
509
|
+
- [PiEconetBridge](https://github.com/cr12925/PiEconetBridge) ---
|
|
510
|
+
Econet bridge for Raspberry Pi; defines the `user.econet_*` xattr convention
|
|
511
|
+
and the alternative `.inf` format (`owner load exec perm`).
|
|
512
|
+
- [python-zipinfo-riscos](https://github.com/gerph/python-zipinfo-riscos) ---
|
|
513
|
+
Python library for reading/writing RISC OS extra fields in ZIP archives;
|
|
514
|
+
source of the `,llllllll,eeeeeeee` load/exec filename encoding convention.
|
|
515
|
+
- [ZipTools](https://mdfs.net/Apps/Archivers/ZipTools/) ---
|
|
516
|
+
J.G.Harston's suite of Acorn-aware ZIP utilities including ZipToInf.
|
|
517
|
+
- [SparkFS](http://www.davidpilling.com/wiki/index.php/SparkFS) ---
|
|
518
|
+
David Pilling's RISC OS filing system for ZIP/Arc/Spark archives;
|
|
519
|
+
originator of the ARC0 extra field format.
|
|
520
|
+
|
|
521
|
+
### Forum discussion
|
|
522
|
+
|
|
523
|
+
- [Stardot forum thread](https://stardot.org.uk/forums/viewtopic.php?p=478751#p478751) ---
|
|
524
|
+
Discussion of the metadata-loss problem that motivated this tool.
|
|
525
|
+
- [INF format discussion](https://www.stardot.org.uk/forums/viewtopic.php?t=31577) ---
|
|
526
|
+
Community discussion on INF format variants and standardisation.
|