80un 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,517 @@
1
+ Metadata-Version: 2.4
2
+ Name: 80un
3
+ Version: 0.2.0
4
+ Summary: Unpacker for CP/M compression and packing formats
5
+ Author: avwohl
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/avwohl/80un
8
+ Project-URL: Repository, https://github.com/avwohl/80un
9
+ Keywords: cpm,cp/m,8080,unpack,squeeze,crunch,lbr,arc,retro
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: System :: Archiving :: Compression
22
+ Classifier: Topic :: System :: Archiving
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # 80un
32
+
33
+ Unpack and decompress archive and compression formats used on the CP/M operating system for Z80 computers.
34
+
35
+ Two implementations are provided:
36
+
37
+ | Version | Runs On | Use Case |
38
+ |---------|---------|----------|
39
+ | **80un.com** | CP/M 2.2+ | Extract archives on vintage hardware or emulators |
40
+ | **80un (Python)** | Python 3.8+ | Extract archives on modern systems |
41
+
42
+ Both support the same formats and produce identical output.
43
+
44
+ ---
45
+
46
+ ## Python Version
47
+
48
+ ### Installation
49
+
50
+ ```bash
51
+ pip install 80un
52
+ ```
53
+
54
+ Requires Python 3.8 or later. No external dependencies.
55
+
56
+ ## Quick Start
57
+
58
+ ```bash
59
+ # Extract an LBR archive
60
+ 80un archive.lbr
61
+
62
+ # Extract an ARC archive to a specific directory
63
+ 80un archive.arc -o extracted/
64
+
65
+ # List contents of an archive without extracting
66
+ 80un archive.lbr -l
67
+
68
+ # Decompress a crunched file
69
+ 80un document.tzt
70
+ ```
71
+
72
+ ## Supported Formats
73
+
74
+ ### Archive Formats (contain multiple files)
75
+
76
+ | Format | Extensions | Description |
77
+ |--------|------------|-------------|
78
+ | **LBR** | `.lbr`, `.lqr`, `.lzr` | Library archive, similar to tar. Files inside may be compressed individually. |
79
+ | **ARC** | `.arc`, `.ark` | Compressed archive supporting multiple compression methods (stored, packed, squeezed, crunched, squashed). |
80
+
81
+ ### Compression Formats (single file)
82
+
83
+ | Format | Extensions | Magic Bytes | Description |
84
+ |--------|------------|-------------|-------------|
85
+ | **Squeeze** | `.?q?` | `76 FF` | Huffman coding with run-length encoding. Devised by Richard Greenlaw, 1981. |
86
+ | **Crunch** | `.?z?` | `76 FE` | LZW compression similar to Unix compress. More efficient than squeeze. |
87
+ | **CrLZH** | `.?y?` | `76 FD` | LZH compression (Lempel-Ziv + Huffman). Most efficient CP/M compression. |
88
+
89
+ ### CP/M File Naming Convention
90
+
91
+ CP/M used 8.3 filenames. Compressed files indicated their compression by replacing the **middle letter** of the extension:
92
+
93
+ | Original | Squeezed | Crunched | CrLZH |
94
+ |----------|----------|----------|-------|
95
+ | `FILE.TXT` | `FILE.TQT` | `FILE.TZT` | `FILE.TYT` |
96
+ | `FILE.COM` | `FILE.CQM` | `FILE.CZM` | `FILE.CYM` |
97
+ | `FILE.ASM` | `FILE.AQM` | `FILE.AZM` | `FILE.AYM` |
98
+ | `FILE.DOC` | `FILE.DQC` | `FILE.DZC` | `FILE.DYC` |
99
+
100
+ Files with no extension used `.QQQ`, `.ZZZ`, or `.YYY`.
101
+
102
+ ## Command Line Usage
103
+
104
+ ```
105
+ usage: 80un [-h] [--version] [-o DIR] [-l] [-t] [-f FORMAT] [-n] file
106
+
107
+ Unpacker for CP/M compression and packing formats
108
+
109
+ positional arguments:
110
+ file File to extract or decompress
111
+
112
+ options:
113
+ -h, --help Show this help message and exit
114
+ --version Show program's version number and exit
115
+ -o, --output DIR Output directory for extracted files
116
+ -l, --list List contents without extracting
117
+ -t, --text Convert text files (strip ^Z, CR/LF to LF)
118
+ -f, --format FORMAT Force file format: lbr, arc, squeeze, crunch, crlzh
119
+ -n, --no-clobber Do not overwrite existing files
120
+ ```
121
+
122
+ ### Examples
123
+
124
+ **List contents of an LBR archive:**
125
+ ```bash
126
+ $ 80un myarchive.lbr -l
127
+ Filename Size Sectors
128
+ ------------------------------------
129
+ README.TZT 512 4
130
+ PROGRAM.CZM 8192 64
131
+ DATA.DZT 1024 8
132
+
133
+ 3 file(s)
134
+ ```
135
+
136
+ **List contents of an ARC archive:**
137
+ ```bash
138
+ $ 80un myarchive.arc -l
139
+ Filename Original Compressed Method
140
+ ------------------------------------------------------
141
+ README.TXT 1024 512 crunched LZW
142
+ PROGRAM.COM 16384 8192 crunched LZW
143
+ DATA.DAT 2048 1024 squeezed
144
+
145
+ 3 file(s)
146
+ ```
147
+
148
+ **Extract an archive:**
149
+ ```bash
150
+ $ 80un myarchive.lbr
151
+ README.TXT
152
+ PROGRAM.COM
153
+ DATA.DAT
154
+
155
+ Extracted 3 file(s)
156
+ ```
157
+
158
+ **Extract to a specific directory:**
159
+ ```bash
160
+ $ 80un myarchive.lbr -o output/
161
+ README.TXT
162
+ PROGRAM.COM
163
+ DATA.DAT
164
+
165
+ Extracted 3 file(s)
166
+ ```
167
+
168
+ **Extract and convert text files to Unix format:**
169
+ ```bash
170
+ $ 80un myarchive.lbr -t -o output/
171
+ ```
172
+
173
+ This strips the ^Z (Ctrl-Z) end-of-file padding and converts CR/LF line endings to Unix LF.
174
+
175
+ **Decompress a single crunched file:**
176
+ ```bash
177
+ $ 80un document.tzt
178
+ document.txt (2048 bytes)
179
+ ```
180
+
181
+ The original filename is recovered from the compressed file header.
182
+
183
+ **Force a specific format:**
184
+ ```bash
185
+ $ 80un unknown.dat -f crunch
186
+ ```
187
+
188
+ **Extract without overwriting existing files:**
189
+ ```bash
190
+ $ 80un myarchive.lbr -o output/ -n
191
+ README.TXT
192
+ PROGRAM.COM (skipped, already exists)
193
+ DATA.DAT
194
+
195
+ 3 file(s): 2 extracted, 1 skipped
196
+ ```
197
+
198
+ The `-n` / `--no-clobber` option is useful when extracting multiple archives to the same directory, or when you want to preserve files you've already modified.
199
+
200
+ ## Python API
201
+
202
+ ### Extracting Archives
203
+
204
+ ```python
205
+ from un80 import extract_lbr, extract_arc
206
+
207
+ # Extract LBR archive
208
+ # Returns list of (filename, data) tuples
209
+ files = extract_lbr("archive.lbr", "output_dir/")
210
+ for filename, data in files:
211
+ print(f"Extracted {filename}: {len(data)} bytes")
212
+
213
+ # Extract without writing to disk
214
+ files = extract_lbr("archive.lbr") # No output_dir
215
+ for filename, data in files:
216
+ process(data)
217
+
218
+ # Extract with text conversion
219
+ files = extract_lbr("archive.lbr", "output/", convert_text=True)
220
+
221
+ # Extract ARC archive
222
+ files = extract_arc("archive.arc", "output_dir/")
223
+ ```
224
+
225
+ ### Decompressing Single Files
226
+
227
+ ```python
228
+ from un80 import unsqueeze, uncrunch, uncrlzh
229
+
230
+ # Read compressed file
231
+ with open("document.tqt", "rb") as f:
232
+ compressed = f.read()
233
+
234
+ # Decompress based on format
235
+ decompressed = unsqueeze(compressed) # For .?q? files
236
+ decompressed = uncrunch(compressed) # For .?z? files
237
+ decompressed = uncrlzh(compressed) # For .?y? files
238
+
239
+ # Write decompressed data
240
+ with open("document.txt", "wb") as f:
241
+ f.write(decompressed)
242
+ ```
243
+
244
+ ### Listing Archive Contents
245
+
246
+ ```python
247
+ from un80.lbr import list_lbr
248
+ from un80.arc import list_arc
249
+
250
+ # List LBR contents
251
+ for entry in list_lbr("archive.lbr"):
252
+ print(f"{entry.filename}: {entry.data_size} bytes")
253
+
254
+ # List ARC contents
255
+ for entry in list_arc("archive.arc"):
256
+ print(f"{entry.filename}: {entry.original_size} bytes ({entry.method_name})")
257
+ ```
258
+
259
+ ### CP/M Text File Utilities
260
+
261
+ ```python
262
+ from un80 import strip_cpm_eof, crlf_to_lf, is_text_file
263
+
264
+ # Strip ^Z EOF padding from CP/M text file
265
+ data = strip_cpm_eof(data)
266
+
267
+ # Convert CR/LF to Unix LF
268
+ data = crlf_to_lf(data)
269
+
270
+ # Check if file is likely text based on extension
271
+ if is_text_file("readme.txt"):
272
+ data = strip_cpm_eof(data)
273
+ data = crlf_to_lf(data)
274
+ ```
275
+
276
+ ### Format Detection
277
+
278
+ ```python
279
+ from un80.cpm import detect_compression
280
+
281
+ with open("unknown.file", "rb") as f:
282
+ data = f.read()
283
+
284
+ format_type = detect_compression(data)
285
+ # Returns: 'squeeze', 'crunch', 'crlzh', 'arc', 'lbr', or None
286
+ ```
287
+
288
+ ### Getting Original Filenames
289
+
290
+ Compressed files store the original filename in their header:
291
+
292
+ ```python
293
+ from un80.squeeze import get_squeezed_filename
294
+ from un80.crunch import get_crunched_filename
295
+ from un80.crlzh import get_crlzh_filename
296
+
297
+ with open("file.tzt", "rb") as f:
298
+ data = f.read()
299
+
300
+ original_name = get_crunched_filename(data)
301
+ print(f"Original filename: {original_name}") # e.g., "FILE.TXT"
302
+ ```
303
+
304
+ ## CP/M File Handling
305
+
306
+ CP/M files have characteristics that differ from modern systems:
307
+
308
+ ### 128-Byte Records
309
+
310
+ CP/M measured file sizes in 128-byte records (sectors), not bytes. A file's actual byte length wasn't stored; only the record count. This means:
311
+
312
+ - Files are always multiples of 128 bytes
313
+ - The last record may contain padding
314
+
315
+ ### ^Z End-of-File Marker
316
+
317
+ Text files that didn't fill their last 128-byte record were padded. The convention was to mark the end of actual content with a Ctrl-Z character (0x1A), with the remainder filled with more ^Z characters or garbage.
318
+
319
+ Use `--text` or `strip_cpm_eof()` to remove this padding.
320
+
321
+ ### CR/LF Line Endings
322
+
323
+ CP/M text files used CR/LF (carriage return + line feed, 0x0D 0x0A) line endings, like DOS/Windows. Use `--text` or `crlf_to_lf()` to convert to Unix-style LF endings.
324
+
325
+ ## ARC Compression Methods
326
+
327
+ ARC archives can contain files compressed with different methods:
328
+
329
+ | Method | Name | Description |
330
+ |--------|------|-------------|
331
+ | 1 | Stored (old) | No compression (obsolete) |
332
+ | 2 | Stored | No compression |
333
+ | 3 | Packed | Run-length encoding only |
334
+ | 4 | Squeezed | Huffman coding after RLE |
335
+ | 5 | Crunched (old) | 12-bit LZW (obsolete) |
336
+ | 6 | Crunched+RLE | 12-bit LZW with RLE (obsolete) |
337
+ | 7 | Crunched | LZW with faster hash |
338
+ | 8 | Crunched | 9-12 bit LZW (most common) |
339
+ | 9 | Squashed | 13-bit LZW (Phil Katz) |
340
+
341
+ ## Troubleshooting
342
+
343
+ ### "Cannot determine format"
344
+
345
+ The file doesn't have a recognized magic number or extension. Try specifying the format manually:
346
+
347
+ ```bash
348
+ 80un mystery.dat -f crunch
349
+ ```
350
+
351
+ ### Garbled output from text files
352
+
353
+ The file may still have CP/M formatting. Use the `--text` option:
354
+
355
+ ```bash
356
+ 80un archive.lbr -t
357
+ ```
358
+
359
+ ### "Invalid magic" or decompression errors
360
+
361
+ The file may be corrupted, truncated, or not actually in the detected format. Try:
362
+
363
+ 1. Verify the file is complete
364
+ 2. Try a different format with `-f`
365
+ 3. Check if it's a different vintage format not yet supported
366
+
367
+ ### Files extract with wrong names
368
+
369
+ Some very old archives don't store original filenames. The tool will use the archive member name with the compression indicator removed.
370
+
371
+ ### Duplicate filenames in archive
372
+
373
+ Some archives contain multiple files with the same name (e.g., from different directories that CP/M flattened). When this happens, 80un automatically renames duplicates by appending `_1`, `_2`, etc.:
374
+
375
+ ```bash
376
+ $ 80un archive_with_dupes.lbr
377
+ README.TXT
378
+ README.TXT -> README_1.TXT
379
+ DATA.DAT
380
+
381
+ 3 file(s): 3 extracted
382
+ ```
383
+
384
+ ## History
385
+
386
+ These compression formats were developed in the early 1980s for CP/M systems:
387
+
388
+ - **1981**: Squeeze (SQ/USQ) by Richard Greenlaw - first widely-used CP/M compression
389
+ - **1984**: LBR format by Gary P. Novosielski - library/archive format
390
+ - **1985**: ARC by System Enhancement Associates - compressed archives
391
+ - **1985**: Crunch - LZW compression, more efficient than squeeze
392
+ - **1986**: Crunch v2.0 - improved with "metastatic code reassignment"
393
+ - **Late 1980s**: CrLZH - LZH compression, most efficient
394
+
395
+ ## License
396
+
397
+ GPL v3 License
398
+
399
+ ## Contributing
400
+
401
+ Bug reports and pull requests welcome at https://github.com/avwohl/80un
402
+
403
+ ---
404
+
405
+ ## CP/M Version (80un.com)
406
+
407
+ A native CP/M program written in PL/M-80 that runs on real vintage hardware or emulators.
408
+
409
+ ### Getting 80un.com
410
+
411
+ Download `80un.com` directly from this repository, or build from source (see below).
412
+
413
+ Transfer to your CP/M system via:
414
+ - XMODEM/YMODEM from a terminal program
415
+ - Write to a disk image and mount it
416
+ - Your emulator's file import feature
417
+
418
+ ### Usage on CP/M
419
+
420
+ Extract an LBR archive:
421
+ ```
422
+ A>80UN MYLIB.LBR
423
+
424
+ 80UN - CP/M Archive Unpacker v2.1
425
+
426
+ Extracting:
427
+ README.TXT OK
428
+ PROGRAM.COM OK
429
+ SOURCE.ASM OK
430
+
431
+ 3 file(s) extracted
432
+ ```
433
+
434
+ Extract an ARC archive:
435
+ ```
436
+ A>80UN SOFTWARE.ARC
437
+
438
+ 80UN - CP/M Archive Unpacker v2.1
439
+
440
+ Extracting:
441
+ INSTALL.DOC OK
442
+ PROG.COM OK
443
+ CONFIG.DAT OK
444
+
445
+ 3 file(s) extracted
446
+ ```
447
+
448
+ Decompress a squeezed file:
449
+ ```
450
+ A>80UN MANUAL.TQT
451
+
452
+ 80UN - CP/M Archive Unpacker v2.1
453
+
454
+ Extracting:
455
+ Creating: MANUAL.TXT OK
456
+
457
+ 1 file(s) extracted
458
+ ```
459
+
460
+ Decompress a crunched file:
461
+ ```
462
+ A>80UN SOURCE.AZM
463
+
464
+ 80UN - CP/M Archive Unpacker v2.1
465
+
466
+ Extracting:
467
+ Creating: SOURCE.ASM OK
468
+
469
+ 1 file(s) extracted
470
+ ```
471
+
472
+ ### Notes
473
+
474
+ - Files extract to current drive/user area
475
+ - Existing files are overwritten without warning
476
+ - Original filenames are restored from compressed file headers
477
+ - Nested compression is handled (e.g., crunched files inside LBR)
478
+
479
+ ### Building from Source
480
+
481
+ Requires the [uplm80](https://github.com/avwohl/uplm80) toolchain:
482
+
483
+ ```bash
484
+ make # Build 80un.com
485
+ make test # Test with sample archives
486
+ make clean # Remove build artifacts
487
+ ```
488
+
489
+ ### Source Files
490
+
491
+ PL/M-80 source is in `src/plm/`:
492
+
493
+ | File | Purpose |
494
+ |------|---------|
495
+ | `startup.plm` | Entry point |
496
+ | `common.plm` | BDOS interface, memory ops |
497
+ | `io.plm` | Buffered I/O, bit readers |
498
+ | `squeeze.plm` | Huffman decompressor |
499
+ | `crunch.plm` | LZW decompressor |
500
+ | `lzh.plm` | LZSS decompressor |
501
+ | `arc.plm` | ARC archive extractor |
502
+ | `lbr.plm` | LBR archive extractor |
503
+ | `main.plm` | Main program logic |
504
+ | `heap.asm` | Heap allocation bridge |
505
+
506
+ ### Requirements
507
+
508
+ - CP/M 2.2 or compatible (MP/M, ZCPR, etc.)
509
+ - ~40KB TPA (Transient Program Area)
510
+ - Z80 processor
511
+
512
+ ---
513
+
514
+ ## See Also
515
+
516
+ - [CP/M information archive](https://www.seasip.info/Cpm/) - CP/M documentation
517
+ - [Walnut Creek CP/M CD-ROM](http://www.classiccmp.org/cpmarchives/) - Large CP/M software archive
@@ -0,0 +1,14 @@
1
+ 80un-0.2.0.dist-info/licenses/LICENSE,sha256=sBYYnbXG62D6ry-endlqms9Htu8Iep8ToRSN0T77-rk,34939
2
+ un80/__init__.py,sha256=GhAjE0fmaFMph5cY5YfOPes6wRg2aX0bGMUaLM1h0W8,629
3
+ un80/arc.py,sha256=HxPSBTzrpUSGaCSCE-AgiabZMiLoAL-xws_CHw3E8a8,14814
4
+ un80/cli.py,sha256=sJIKrTqcWjoXToLnMJ8UJf4IKsWzA5pwtHj-nh52mwM,10491
5
+ un80/cpm.py,sha256=JhjujcFT6ucF9BLnxpS83IYOUfBDkzGUxPILcyz0co8,5080
6
+ un80/crlzh.py,sha256=eMrgPRb6Pl5IFNKfbbIpHa-xqryEJ4s9wAyG8WcT2YU,5746
7
+ un80/crunch.py,sha256=y-BnpJGsSWl2i7Eh4599iBDp43upAc9RuMaUnkZHV5I,7699
8
+ un80/lbr.py,sha256=PCaO-GPbUowYj9_psW9Nh9XRJANjeDx5Y-_lOK-mgCI,8084
9
+ un80/squeeze.py,sha256=nnbCV7Hhzcfwa1dE_mbQZrwumrfgJj9GAC1mD5n9isE,6533
10
+ 80un-0.2.0.dist-info/METADATA,sha256=caOexXadgDhe1ahEs035L7AlE-IERqYSme3ytoRZCbk,13558
11
+ 80un-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ 80un-0.2.0.dist-info/entry_points.txt,sha256=BQwGYo9FS8ffH86XZXwMJUf3lN2nIarrIBK8r9ESq9w,39
13
+ 80un-0.2.0.dist-info/top_level.txt,sha256=KlBvz54ZY7H_TT27VVi6_ux_i0vPNd4yNBkvOyodQ64,5
14
+ 80un-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ 80un = un80.cli:main