Skip to content

Classic-Mac disk & archive support: name/OSType fixes, Twiggy/Lisa/DART/NDIF readers, MFS map fix#50

Merged
danifunker merged 15 commits into
mainfrom
fix-invalid-filenames
Jul 1, 2026
Merged

Classic-Mac disk & archive support: name/OSType fixes, Twiggy/Lisa/DART/NDIF readers, MFS map fix#50
danifunker merged 15 commits into
mainfrom
fix-invalid-filenames

Conversation

@danifunker

@danifunker danifunker commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Three related fixes to classic Mac archive handling (StuffIt, StuffIt 5, Compact Pro, MacBinary, MAR), surfaced by real .sit / .hqx files whose names were showing up garbled or being flagged as invalid.

This branch has since grown to bundle several more classic-Mac disk features — see the Added:/Updated: sections below (newest last): Commander wrapper descent, NDIF, optical resource forks, Apple Lisa FS + DART, and native Apple Twiggy .dc42 reading (plus an MFS allocation-map fix).

1. Filename mojibake — UTF-8 vs Mac Roman

The readers assumed entry names were always Mac Roman and ran mac_roman_to_utf8 unconditionally. Archives re-packed by modern tools store names as UTF-8, so HeapFixer™ 1.01 (UTF-8 bytes E2 84 A2) got re-decoded byte-by-byte into HeapFixer‚Ñ¢ 1.01.

New fs::hfs::decode_mac_filename: prefers UTF-8 when the bytes are valid UTF-8 with a multibyte sequence, otherwise falls back to Mac Roman. Safe because a genuine Mac Roman high byte (e.g. é = 0x8E) is never valid standalone UTF-8. Genuine Mac Roman archives (names with ƒ//®, e.g. 911 Utilities) are unaffected.

2. OSType (type/creator) code garbling

Codes are raw 4-byte Mac Roman but were rendered with String::from_utf8_lossy in both the CLI archive list and the GUI Archives tab, so high bytes ( ) became . New fs::hfs::format_ostype (Mac Roman aware, control/NUL padding → space, trailing trim) replaces both call sites. A font test asserts the bundled Noto fallback covers the Mac Roman symbol repertoire these render with (the Apple-logo U+F8FF is the one known, pre-existing exception no font carries).

3. Silent name sanitization on extract

Extraction rewrote names with characters illegal in a host path component (most often /, legal on HFS) to _ with no notice. Now tracked in ExtractStats.names_sanitized, with a per-file warning plus a closing recommendation to keep the archive compressed or extract into an HFS/HFS+ disk image where the original names are legal.

Test files verified

File Before After
HeapFixer1.01.sit HeapFixer‚Ñ¢ HeapFixer™
ScriptEdit.sit_.hqx ScriptEdit‚Ñ¢ ScriptEdit™
LifeSent.hqx creator Ls � Ls ™
911_Utilities_2.0.sit names OK, creator S�L names still OK, S•L

Notes

  • All logic lands in the engine layer (fs/, macarchive/) with thin CLI/GUI adapter touches, per CONTRIBUTING's "one model, many UIs" rule — the future TUI gets all three free.
  • 6 new unit tests; cargo test --lib green, cargo fmt + cargo clippy --all-targets -- -D warnings clean (enforced by the pre-commit hook).

🤖 Generated with Claude Code


Added: Apple Twiggy .dc42 — read natively (commits 0e8b6d3, 897fa50, fbcde51)

Apple Twiggy / FileWare prototype images (DiskCopy-4.2-wrapped, disk-format 0x54, 871 KB — the recovered MacPaint 0.5 / early Finder & System disks) used to fail with a cryptic Invalid MBR. They now open and read like any other disk.

Twiggy stores the two sides sequentially — the entire upper side, then the lower side (per the BLU manual, Sigma Seven Systems) — and the Macintosh writes its logical block 0 at the start of the lower side, so the DiskCopy data region is the logical volume rotated by one side. dc42::deinterleave_twiggy scans the sectors for a valid MFS/HFS Master Directory Block (logical block 2) and rotates the array onto it; the existing superfloppy MFS/HFS detection then mounts the flat volume. Wired into both the GUI/detect path (ImageFormat::TwiggyDc42wrap_image_reader) and the CLI open_read path. Images with no recognizable Mac volume (damaged, or a non-MFS Lisa Twiggy disk) still fall back to a clear diagnostic. This supersedes the earlier diagnostic-only commit 0e8b6d3.

Underlying MFS fix (897fa50). Getting real Twiggy disks to actually extract files exposed a latent MFS bug: the allocation block map was read at 1024 + 36+1+name_len with block*12 indexing, but the map lives at a fixed offset 1024+64 and is indexed with allocation block 2 at map entry 0. The old convention only coincides with correct for a full 27-char volume name, so every real MFS disk with a shorter name had its map read — and edited — from the wrong bytes, surfacing as "MFS chain references out-of-range alloc block N" on any multi-allocation-block file. It was invisible because all the MFS unit/e2e/cli tests built synthetic volumes with the same non-standard convention and only ever exercised single-block files (whose chains never consult the map). Fixed the reader, the editable writer, and the three synthetic test builders.

Validated end-to-end on a real MacPaint 0.5 Twiggy image: mounts MFS "MacStuff", lists all 15 files, and extracts a byte-valid MacPaint v2 document. NewFinder&System1.7.dc42 has no MFS/HFS MDB anywhere and remains unrecoverable (degrades cleanly to the diagnostic). New deinterleave_twiggy round-trip + rejection tests; full cargo test --lib green, fmt / clippy -D warnings clean.


Added: Apple Lisa FS + DART, and Mac file dates on export (commit 6dfa610)

  • DART (rbformats/dart.rs + lzhuf.rs): Apple's compressed disk-image container, contemporary with DiskCopy 4.2. Fast (word-RLE), best (clean-room LZHUF, validated byte-exact against a real Apple chunk), and uncompressed chunks; content-detected (no magic number). Preserves the 12-byte sector tags, so a Lisa DART opens as a Lisa volume and Mac / Apple II / MS-DOS DARTs decode to their block data.
  • Apple Lisa File System (fs/lisa.rs): read-only browse + extract from tag-bearing DiskCopy 4.2 / DART images. Files are reconstructed from the per-sector tags (file ID + file-relative block) rather than the three version-specific catalogs, so extraction works on every disk; flat-table / flat-hash catalogs give friendly names, the B-tree version falls back to file-XXXX. Mirrors Ray Arachelian's lisafsh-tool.
  • Mac file dates on export (fs/resource_fork.rs): AppleDouble now emits a File Dates Info entry and MacBinary carries create/modify dates, threaded through the GUI browse-view and the optical extract paths (sourced from HFS/HFS+ catalog dates). Output stays byte-identical when dates are unknown.

Added: NDIF + optical resource forks + Commander wrapper expansion (commits 9c7371f, 244c657, 78f0a87, b765720, f8f7625, c90d865, 361acd6)

  • NDIF (Disk Copy 6): reader wired into the Commander wrapper tree; disk images are classified by their Mac type code (fixes an ADC off-by-one).
  • Optical resource forks: resource forks + Finder type/creator from HFS/HFS+ CDs now carry through both the CLI optical extract and the Commander optical browse (fixes a CLI extract bug).
  • Commander inline + tree: also expands optical disc images (ISO / bin-cue / MDF-MDS) and ZIP/tar archives, and shows resource-fork size in its own column.

These GUI (Commander) additions are engine/model-first with thin egui wiring; the interactive surface still needs in-app testing.

danifunker and others added 15 commits June 30, 2026 15:11
Classic Mac archive readers (StuffIt, StuffIt 5, Compact Pro, MacBinary,
MAR) assumed entry names were always Mac Roman and ran mac_roman_to_utf8
unconditionally. Archives re-packed by modern tools store names as UTF-8,
so a name like "HeapFixer™ 1.01" arrived as the UTF-8 bytes E2 84 A2 and
got re-decoded byte-by-byte into mojibake ("HeapFixer‚Ñ¢ 1.01"). Add
fs::hfs::decode_mac_filename, which prefers UTF-8 when the bytes are valid
UTF-8 with a multibyte sequence and otherwise falls back to Mac Roman —
safe because a genuine Mac Roman high byte is never valid standalone UTF-8.
Genuine Mac Roman archives (e.g. names with ƒ/•/®) are unaffected.

Type/creator OSType codes are raw 4-byte Mac Roman but were rendered with
String::from_utf8_lossy in both the CLI list and the GUI Archives tab, so
high bytes (• ™) showed as the replacement char. Add fs::hfs::format_ostype
(Mac Roman aware, control/NUL padding -> space, trailing trim) and use it
in both surfaces. A font test asserts the bundled Noto fallback covers the
Mac Roman symbol repertoire these codes/names render with.

Extraction silently rewrote names containing characters illegal in a host
path component (most often '/', legal on HFS) to '_'. Track these in
ExtractStats.names_sanitized and log a per-file warning plus a closing
recommendation to keep the archive compressed or extract into an HFS/HFS+
disk image where the original names are legal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apple Twiggy / FileWare disk images (the pre-release Macintosh prototype
5.25" 871 KB GCR format, e.g. the recovered MacPaint 0.5 / early Finder
disks) are DiskCopy-4.2-wrapped MFS volumes, but their sectors are stored
in a non-linear physical layout (zone-based variable sectors-per-track)
that we do not de-interleave. CiderPress2 doesn't decode them either, and
there is no reference algorithm to port. Opening one previously fell
through to partition detection and surfaced the confusing
"Invalid MBR: invalid boot signature" error.

Detect Twiggy by the non-standard DiskCopy disk-format byte 0x54 (with the
"Twiggy" image-name marker as a fallback) and emit a clear diagnostic
explaining what the image is and why it can't be read, pointing the user
at the normal 400K/800K MFS/HFS images that are supported. MFS itself is
already fully supported (read + edit); only this exotic container geometry
is out of scope.

The check lives in fs::hfs-adjacent dc42::Dc42Header (is_twiggy +
twiggy_unsupported_message) and fires from both the GUI/export detect path
(detect_image_format_with_path) and the CLI source-reader dispatch tail,
so every open surface gives the same message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the engine + model layer that lets a Commander pane browse into a
compressed/wrapped file as if it were a directory and copy its contents
out — without extracting to disk first. GUI wiring follows in a later
commit; this lands the testable foundation.

- fs/archive_fs.rs: ArchiveFilesystem, a read-only `Filesystem` over an
  in-memory Mac archive (StuffIt / StuffIt 5 / Compact Pro / MacBinary /
  BinHex, via macarchive::extract). Maps entries to FileEntry with Finder
  type/creator/flags/dates, serves both forks via decompress_fork, and
  synthesizes intermediate directories missing an explicit marker. Because
  the Commander copy engine is generic over `Filesystem`, presenting an
  archive this way makes list/select/copy (forks included) work unchanged.

- model/dir_listing.rs: layered descent. A pane's listing can now push a
  parent context (`descend_into`) when entering a wrapper and restore it on
  `up()` at the inner root, supporting arbitrary nesting (archive in
  archive, image in archive). `show_parent` offers `..` to ascend back out;
  `descent_depth`/`descent_labels` drive a breadcrumb.

Read-only by design. Unit tests cover archive listing + fork reads and the
descend/ascend round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the GUI half of "view the contents of a compressed/wrapped file
without extracting it." A local pane row that is a Mac archive (.hqx /
.sit / .cpt / MacBinary) or a disk-image container now shows an <ARC> /
<IMG> tag in the Type column; double-clicking it (or "Open contents" in
the right-click menu) browses inside it like a directory. Selecting files
there and copying to the other pane pulls them straight out — both forks
and Finder type/creator preserved — with no extract-to-disk step.

- model/commander_descend.rs: GUI-free helper — classify a name as
  Archive/DiskImage, materialize wrapper bytes pulled from a parent layer
  to a temp file (host files open in place), open an archive, and probe /
  open a nested image's browsable partitions.

- gui/commander/pane.rs: descend orchestration on top of DirListing's
  layered descent. Materializes the child, opens it (ArchiveFilesystem or
  an image partition), and pushes a layer; a per-layer temp-guard stack
  frees each materialized file exactly when its layer is popped. A
  multi-volume nested image opens a "Choose a volume" picker first.
  Descended layers are read-only (copy out, not in): delete / rename /
  paste are gated off, and a breadcrumb shows the wrapper chain.

Arbitrary nesting works (archive in archive, image in archive) because
each layer is itself a Filesystem. Read-only by design. New unit tests
cover classify + materialize/open round-trip; full suite green, zero
warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworks "view inside a wrapped file" from the full-pane descent (which
replaced the whole listing and felt like fullscreen navigation) into an
inline disclosure tree, matching the browse_view CollapsingState pattern
and the requested +/- UX.

A local pane row that is a Mac archive or disk image (<ARC>/<IMG> in the
Type column) now carries a `+` toggle. Clicking it mounts the wrapper and
splices its contents in as indented child rows directly beneath it, in the
same grid. Folders and further wrappers inside get their own +/- and
expand to any depth. Real top-level folders still navigate by double-click
as before; only wrappers (and folders inside an expanded wrapper) expand
inline.

- model/wrapper_tree.rs: GUI-free expansion state — mounts (opened wrapper
  filesystems + temp guards), the expanded set, and the recursive walk that
  produces indented rows. Reads route to the owning mount.
- gui/commander/pane.rs: renders the tree (indent + ASCII +/- toggle, hit-
  tested in a fixed slot), a per-frame node->row index, and a wrapper-scoped
  selection kept separate from the base listing. selected_entries / fs_mut /
  is_host_pane / session become tree-aware, so the existing copy dispatch
  pulls selected files (and whole subtrees, since stage_copy recurses)
  straight out of a mount — forks + Finder info preserved — with no
  copy-engine changes. Expansion is per-directory (collapses on
  navigate/switch) and read-only.

Drops the DirListing layered-descent added earlier (the full-pane model is
gone). New model unit tests cover expand/collapse/walk + mount reads; full
suite green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NDIF ("New Disk Image Format", classic Mac OS Disk Copy 6 — type rohd /
creator ddsk) is the older sibling of the UDIF .dmg we already read. Its
disk blocks live in the data fork as zero/raw/ADC chunks, with the block
map ('bcem') in the resource fork, so reconstructing the raw image needs
both forks. These commonly arrive MacBinary- or BinHex-wrapped
(e.g. Macbench3.0.img.bin -> MacBinary -> NDIF -> HFS volume), which the
Commander tree already exposes both forks for.

- rbformats/ndif.rs: a self-contained NDIF reader. `extract_bcem` walks the
  classic-Mac resource map for the block map; `reconstruct` rebuilds the
  flat image from the chunk table (zero-fill / raw / ADC, reusing the UDIF
  ADC codec, now shared from dmg.rs). Format pinned + validated against a
  real image (reconstructs to a valid HFS volume).
- model/wrapper_tree.rs: when reading a disk-image row's bytes for
  expansion, read the resource fork too and reconstruct if it's NDIF — so
  expanding a MacBinary/BinHex-wrapped NDIF image in the Commander +/- tree
  yields the inner HFS files to browse and copy out.

Carriers covered now: MacBinary and BinHex (both go through ArchiveFilesystem,
which serves both forks). AppleDouble sidecars and flat/top-level (inspect)
NDIF are a follow-up. Unit tests cover the resource-map walk and chunk
reconstruction; full suite green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two fixes for browsing wrapped disk images in the Commander tree, found
testing real images:

1. ADC off-by-one. The shared ADC decoder (decompress_adc, used by both
   UDIF and the new NDIF reader) copied matches from `out.len() - offset`,
   but ADC stores the back-distance as offset-1, so it must copy from
   `offset + 1` back. The bug shifted every match by one byte, so an NDIF
   image reconstructed with a valid-looking MDB but a corrupt catalog -
   browsing it showed zero files. With the fix the MacBench NDIF image
   lists its 11 real files. The two synthetic ADC unit tests encoded the
   wrong convention and are corrected; this also repairs latent UDIF ADC.

2. Type-code classification. A Disk Copy image named `MacBottom MFS.image`
   has no recognized extension, so it wasn't offered a `+` (it showed its
   raw `dImg` type instead). New `classify_entry` consults the Mac Finder
   type code ('dImg'/'dimg'/NDIF types) as a fallback, and the wrapper-open
   path now takes the resolved DescendKind instead of re-deriving it from
   the file name - so odd-extension images expand correctly.

Verified end-to-end against both real images (NDIF HFS -> 11 files; Disk
Copy MFS -> 20 files). New tests cover the type-code classifier; full suite
green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mac files carry a second fork the data-size column never showed. Add a
dedicated "Rsrc" column to the Commander grid (the "Size" header becomes
"Data" to disambiguate), painted right-aligned and only when a file
actually has a resource fork — blank for forkless files and non-Mac
filesystems, so it stays quiet.

The value comes straight off FileEntry.resource_fork_size, which HFS /
HFS+ / MFS and the archive filesystem already populate during listing, so
it covers base listings and the inline wrapper tree alike at no extra I/O.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two changes so the inline + tree handles host archives gracefully.

1. ZIP and tar support. New fs::mem_archive presents a ZIP or tar (plain /
   .tgz / .tar.gz / .tar.zst) as a read-only Filesystem over its decoded
   members, built on the zip / tar / flate2 crates already in the tree.
   classify now routes .zip / .tar / .tgz / .tar.gz / .tar.zst to the
   archive path (so .zip browses its files instead of being treated as a
   zip-wrapped disk image), and open_archive_bytes sniffs content + name to
   pick the ZIP, tar, or Mac-archive reader. Archives mount from an owned
   byte buffer, so they no longer need a temp file (Mount._temp is now
   optional, used only by image mounts). A 2 GiB cap guards against
   decompressing a huge tarball into RAM.

2. Expand errors go to the log, not the status bar. A failed wrapper
   expansion previously returned the full anyhow error chain as the
   one-line pane status, which overflowed it and made Commander unusable
   (e.g. clicking + on something that isn't a recognized container). Now
   the detail is written to the session Log and the status is a short
   "couldn't open 'X' (see Log)"; the row just stays collapsed.

Unit tests cover the ZIP + tar readers and the new classify routing; full
suite green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tree

Adds optical disc images to the inline + tree, alongside Mac archives,
disk images, and ZIP/tar. New fs::optical_fs::OpticalFilesystem adapts the
opticaldiscs crate's own browse::Filesystem (ISO 9660, HFS/HFS+, and SGI
EFS) to our Filesystem trait, caching each entry by path so its
filesystem-specific location hint round-trips on reads. Gated behind the
existing `optical` feature.

classify now routes .iso / .cue / .mdf / .mds / .nrg / .ccd / .cdr /
.toast to a new DescendKind::Optical (before the disk-image extensions, so
an ISO opens as an optical filesystem rather than a partitioned disk), and
open_mount opens them by path via opticaldiscs.

To make two-file formats (bin + cue, mdf + mds) work, wrapper expansion
now carries a WrapperSource: a host base row opens by its real on-disk path
(so the sibling data file is found, and large images aren't slurped into
RAM), while a wrapper pulled out of a parent layer still passes bytes.

Verified against real images from a large ISO collection: HFS Mac CDs
(Mac OS 7.x, Office 2001), a bin/cue Mac CD (sibling .bin resolved via the
real path), and IRIX EFS CDs (6.5 Foundation, overlays) all list correctly.
Full suite green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The adapter dropped the HFS-on-CD metadata opticaldiscs actually exposes,
so HFS optical discs showed a blank Rsrc column, lost their Mac type in
the Type column, and copied without their resource fork. Map
resource_fork_size / type_code / creator_code / finder_flags in translate,
and wire write_resource_fork_to + resource_fork_size to the crate's
read_resource_fork. Verified against a Mac OS 7.6.1 CD: APPL/ttro files now
report their resource-fork sizes (e.g. 84 KB / 552 KB) and type/creator.

(opticaldiscs' browse FileEntry has no date field, so the Modified column
necessarily stays blank for optical discs — a crate limitation, not wiring.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI `optical extract` gated resource-fork handling on
`is_hfs = matches!(info.filesystem, FilesystemType::Hfs)` — which excludes
HfsPlus. So extracting from an HFS+ optical disc (e.g. a Mac OS X install
CD) silently dropped every resource fork, even in MacBinary/AppleDouble
mode. The Inspect-tab browser gated on `Hfs | HfsPlus` and was correct.

Fix both to key off the entry's `resource_fork_size` instead of the
filesystem type: opticaldiscs reports `None` on non-fork filesystems
(ISO 9660) and `Some(n)` on HFS/HFS+, so this is both correct and
future-proof — no FilesystemType enum to keep in sync as the crate gains
fork filesystems. The now-unused `is_hfs` params are removed.

Verified against a Mac OS X 10.3 HFS+ CD: files like "Install Mac OS X"
(APPL, 50 KB resource fork) now extract with their fork intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three strands of work that had accumulated on this branch:

1. Apple Lisa File System + DART (compressed disk image) support
   - src/rbformats/lzhuf.rs: clean-room LZHUF decoder (DART's "best"
     scheme), validated byte-exact against a real Apple 800K chunk
     (CRC32 0xd882b699).
   - src/rbformats/dart.rs: DART container decoder (fast/word-RLE, best/
     LZHUF, uncompressed). Preserves the 12-byte sector tags; Lisa disks
     (src_type 2) yield (data, tags), all others their block data.
   - src/fs/lisa.rs: read-only Lisa FS browse + extract, reconstructing
     files from the per-sector tags (file ID + relative block) rather than
     the three version-specific catalogs. Mirrors lisafsh-tool.
   - src/rbformats/dc42.rs: encode_dc42_with_tags to re-wrap a decoded
     (data, tags) pair as tag-bearing DiskCopy 4.2 for the Lisa path.
   - Wiring: ImageFormat::Dart + content-detect in rbformats/mod.rs;
     try_decode_dart in source_reader; looks_like_lisa superfloppy hint
     and "lisafs" dispatch in partition/mod.rs + fs/mod.rs; "dart"
     picker extension in file_types; README format/FS rows.

2. Preserve Mac file dates when exporting resource-forked files
   - src/fs/resource_fork.rs: MacFileDates; build_appledouble now emits a
     File Dates Info (id 8) entry and build_macbinary carries create/modify
     dates. Output stays byte-identical when dates are unknown.
   - Threaded through the GUI browse-view extract and the optical CLI/
     browse extract paths, sourced from HFS/HFS+ catalog Mac-1904 dates.

3. deps: bump opticaldiscs 0.5 -> 0.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MFS volume allocation block map lives at a fixed offset 64 within the
MDB block and is indexed with allocation block 2 at map entry 0. The reader
computed the map offset as `36 + 1 + name_len` (right after the actual
volume name) and indexed entries with `block * 12` (block 0 at entry 0).
Both only coincide with the correct layout when the volume name is the full
27 characters, so every real MFS disk with a shorter name had its map read
-- and edited -- from the wrong bytes, surfacing as "MFS chain references
out-of-range alloc block N" on any multi-allocation-block file.

The bug was invisible because the unit / e2e / cli tests all built synthetic
MFS volumes with the same non-standard convention and only ever exercised
single-block files, whose chains never consult the map.

Fix map_offset (fixed 1024+64) and the map_get / map_set entry index
((block - 2) * 12) on both the read and edit paths, and update the three
synthetic test builders to the standard layout. Validated by extracting
byte-correct multi-block files from a real MacPaint 0.5 MFS volume.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apple Twiggy / FileWare DiskCopy 4.2 images (the recovered pre-release
Macintosh prototype 871 KB GCR disks -- MacPaint 0.5, early Finder/System)
store the two sides sequentially rather than interleaved: the entire upper
side, then the lower side (per the BLU manual, Sigma Seven Systems). The
Macintosh writes its logical block 0 at the start of the lower side, so the
DiskCopy data region is the logical volume rotated by one side.

Add dc42::deinterleave_twiggy, which scans the sectors for a valid MFS/HFS
Master Directory Block (logical block 2) and rotates the sector array onto
it -- a single rotation recovers the flat volume, which the existing
superfloppy MFS/HFS detection then mounts. Replace the previous
diagnostic-only Twiggy handling in both the GUI/detect path
(ImageFormat::TwiggyDc42 + wrap_image_reader) and the CLI source-reader
path; images with no recognizable Mac volume (damaged, or non-MFS Lisa
Twiggy) still fall back to the clear diagnostic.

Validated end-to-end on a real MacPaint 0.5 Twiggy image: mounts MFS
"MacStuff", lists 15 files, extracts a valid MacPaint v2 document.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danifunker danifunker changed the title Fix Mac archive name/OSType decoding and warn on host-illegal names Classic-Mac disk & archive support: name/OSType fixes, Twiggy/Lisa/DART/NDIF readers, MFS map fix Jul 1, 2026
@danifunker danifunker merged commit 8c952e3 into main Jul 1, 2026
19 checks passed
@danifunker danifunker deleted the fix-invalid-filenames branch July 4, 2026 00:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant