Skip to content

smaky6fs: fix size bug, 0xFF deleted entries, decode directory metadata#855

Open
Sch-LikA wants to merge 3 commits intodavidgiven:masterfrom
Sch-LikA:smaky6fs-fix-directory-parsing
Open

smaky6fs: fix size bug, 0xFF deleted entries, decode directory metadata#855
Sch-LikA wants to merge 3 commits intodavidgiven:masterfrom
Sch-LikA:smaky6fs-fix-directory-parsing

Conversation

@Sch-LikA
Copy link
Copy Markdown

@Sch-LikA Sch-LikA commented May 5, 2026

Three bugs in lib/vfs/smaky6fs.cc found by cross-referencing the original Smaky 6 hardware documentation and independent reverse-engineering of disk images (including Winchester hard-disk images not previously analysed).

Bug 1 — Wrong file size when last sector is completely full

When last_bytes == 0 the last sector holds a full 256 bytes. The old formula:

length = (endSector - startSector - 1) * 256 + lastSectorLength;

gives a result 256 bytes short in that case. Fixed to:

length = lastSectorLength
    ? (endSector - startSector - 1) * 256 + lastSectorLength
    : (endSector - startSector) * 256;

Bug 2 — Deleted entries with first byte 0xFF were not skipped

The original check if (dbuf[0]) only skips empty slots (first byte = 0x00). On real Smaky 6 disks and all known Winchester hard-disk images, deleted/system entries use 0xFF as a sentinel. This caused those entries to be parsed as live files with garbage field values.

Fixed to: if (dbuf[0] && dbuf[0] != 0xff)

Bug 3 — Directory bytes [0x0e–0x17] were treated as unknown

These fields are fully decodable (documented in the original Smaky 6 hardware manual and confirmed from disk image analysis):

Offset Field Notes
0x0e–0x0f flags uint16 LE, purpose TBD
0x10–0x11 last_bytes bytes used in last sector (0 = full)
0x12–0x13 load_addr high byte then low byte; reliable for type SM
0x14–0x15 entry_addr same encoding as load_addr
0x16 month BCD (e.g. 0x07 = July); 0x00 or 0xFF = no date
0x17 year BCD (e.g. 0x82 = 1982); year ≥ 78 → 19xx, else 20xx

The raw smaky6.metadata_bytes attribute is replaced by the properly decoded smaky6.flags, smaky6.load_addr, smaky6.entry_addr, and smaky6.date attributes.

Sch-LikA added 2 commits May 5, 2026 15:59
Three bugs fixed by cross-referencing the original Smaky 6 hardware
documentation and independent reverse-engineering of disk images
(including Winchester hard-disk images not previously analysed):

1. Wrong file size when last sector is completely full
   When last_bytes == 0 the last sector holds a full 256 bytes;
   the old formula (end-start-1)*256 + last_bytes gave a result
   256 bytes short in that case.

2. Deleted entries with first byte 0xFF were not skipped
   The original check 'if (dbuf[0])' only skipped empty slots
   (first byte 0x00). On real Smaky 6 disks and all known Winchester
   images, deleted/system entries use 0xFF as a marker. This caused
   those entries to be parsed as live files with garbage field values.

3. Directory bytes [0x0e-0x17] were treated as unknown
   These fields are fully decodable:
     0x0e-0x0f  flags      (uint16 LE, purpose TBD)
     0x10-0x11  last_bytes (bytes used in last sector)
     0x12-0x13  load_addr  (high byte first; reliable for type SM)
     0x14-0x15  entry_addr (same encoding)
     0x16       month BCD  (0x00/0xFF = no date)
     0x17       year  BCD  (0x82 = 1982; >= 78 -> 19xx, else 20xx)

   The raw smaky6.metadata_bytes attribute is replaced by the decoded
   smaky6.flags, smaky6.load_addr, smaky6.entry_addr, smaky6.date.
DR (Directory) entries in SAMOS contain a sub-directory in their first
3 sectors, using the same 24-byte entry format as the root directory.
Sector addresses within these sub-directories are relative to the DR
entry's own start sector.

Changes:
- SmakyDirent: set file_type = TYPE_DIRECTORY when the filename ends
  with '.DR', so list/getDirent/getFile can distinguish containers from
  plain files.
- Directory: factor parsing into parseFrom(bytes, sectorBase) and add a
  second constructor Directory(fs, drStartSector) for sub-directories;
  relative sector numbers are converted to absolute by adding sectorBase.
- list(): handle path.size() == 1 by finding the named DR entry and
  listing its sub-directory.
- getDirent(): handle path.size() == 2 for sub-directory entries.
- getFile(): handle path.size() == 2, resolving absolute sector addresses
  via the sub-directory; guard against calling getFile on a directory.

Tested against SM6WIN0.DSK (Winchester): BASIC.DR contains BINBASIC.SM,
BINBAS32.SM, BCDBASIC.SM, BCDBAS32.SM at the correct absolute sectors.
Comment thread lib/vfs/smaky6fs.cc Outdated
/* Sub-directory inside a DR container:
* The first 3 sectors of the DR entry hold the sub-directory.
* Sector numbers stored there are relative to drStartSector. */
Directory(Smaky6Filesystem* fs, unsigned drStartSector)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could annotate this as =0 and not need the other constructor.

Comment thread lib/vfs/smaky6fs.cc Outdated
return result;
}

if (path.size() == 1)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the filesystem not support nested subdirectories?

Comment thread lib/vfs/smaky6fs.cc Outdated
{
if (path.size() != 1)
throw BadPathException(path);
if (path.size() == 1)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory lookup code here is repeated multiple times. It'd probably be better to have a single method which looks up a path and call it from all these places.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Actually, getDirent should already do this?)

…olveDirent helpers

- Merge the two Directory constructors into one with drStartSector=0,
  eliminating the redundant overload (per review comment).
- Add private directoryAt(path) and resolveDirent(path) helpers so the
  repeated two-level path-traversal logic lives in exactly one place.
- list(), getDirent() and getFile() now each delegate to those helpers,
  removing all duplicated root/sub-directory lookup code (per review
  comment; getDirent now effectively drives the shared resolution).
@Sch-LikA
Copy link
Copy Markdown
Author

Sch-LikA commented May 6, 2026

Thanks for the review! I've pushed a new commit (a4a56dd) addressing all three points:

Constructor redundancy — merged the two Directory constructors into one with unsigned drStartSector = 0. Sector 0 is the root, so the default covers both cases cleanly without a separate overload.

Repeated path-traversal code — added two private helpers:

  • directoryAt(path) — returns the Directory for the path's parent level (root for depth 0, DR sub-directory for depth 1, throws BadPathException beyond that).
  • resolveDirent(path) — calls directoryAt on the parent path then findFile on the last component.

list(), getDirent(), and getFile() each now delegate to these helpers — all the traversal logic lives in one place, and getDirent effectively drives the shared resolution as you suggested.

Nested sub-directories — the Smaky 6 filesystem is structurally flat-within-one-level: a .DR entry is a container whose first three sectors hold a plain 32-entry directory; the spec does not allow a .DR inside another .DR. So depth > 1 is correctly rejected with BadPathException. The new directoryAt helper documents this constraint explicitly in its comment.

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.

2 participants