Skip to content

foscail: DAT unpack/convert tool + DataArchive hardening + empty-frame render fix - #18

Merged
baughj merged 4 commits into
mainfrom
foscail
Jul 23, 2026
Merged

foscail: DAT unpack/convert tool + DataArchive hardening + empty-frame render fix#18
baughj merged 4 commits into
mainfrom
foscail

Conversation

@baughj

@baughj baughj commented Jul 23, 2026

Copy link
Copy Markdown
Member

What this adds

foscail (Irish, "open") — a dotnet tool CLI shipped alongside DALib. foscail unpack extracts every entry from one or more DAT archives into per-archive directories; --convert additionally renders the self-contained image formats (SPF, EFA) to PNG. Palette-dependent formats (EPF/HPF/MPF/tiles) are raw-extracted for now — TBL/PAL resolution is the next milestone.

Verified against 3.61 and 7.41 client corpora: 20 archives, 55,518 entries, 3,605 PNGs, zero failures.

Library fixes along the way

  • Empty-frame NRE: empty SPF placeholder frames (non-positive dimensions, no pixel data) crashed the shared SimpleRender helper; the EPF/EFA paths were guarded but the helper wasn't. Fixed centrally — degenerate frames render as 1×1 transparent — with a regression test.
  • DataArchive hardening (from an external Codex review of the tool, all four findings verified against source before fixing):
    • ExtractTo canonicalizes each output path and refuses entry names that resolve outside the destination (path traversal via ../x or rooted names — latent for all DALib consumers, not just foscail)
    • the constructor rejects negative entry-count words (InvalidDataException) instead of silently yielding an empty archive when handed the extended 0xFFFFFFFF-headed layout or other garbage — note this is a small public behavior change
    • FromFile honors an existing path as-given; WithExtension remains only the extensionless-name convenience (it used to rewrite FOO.DATFOO.dat, which breaks on case-sensitive filesystems)

Tool robustness (same review)

  • extraction runs inside the per-archive failure boundary — one bad archive costs a failure count, not the batch
  • extended-format archives are reported as unsupported failures, never silently parsed as empty
  • .dat discovery is case-insensitive (the 3.61 corpus's own FILE00XX.DAT was previously invisible on Linux)
  • PNG output paths get the same containment check as extraction

Tests

New Foscail.Tests project drives UnpackCommand.Run end-to-end over synthetic archives: happy path, hostile-archive batch continuation, extended-format reporting, uppercase-extension discovery (glob and direct path). New DataArchiveTests covers the library guards, including fail-without-fix cases for traversal and the negative-count parse. 940 tests green.

Generated with Imbas imbas@eris.co

baughj and others added 3 commits July 22, 2026 17:46
Empty SPF placeholder frames carry non-positive (often negative)
dimensions and no pixel data. The EPF/EFA render paths guarded this
but the shared SimpleRender helper did not, so rendering such a frame
threw NullReferenceException. Guard centrally; empty frames render as
1x1 transparent images. Regression test included (test project gains
the Linux Skia natives so render tests run on Linux).

Co-Authored-By: Imbas <imbas@eris.co>
Per external review (Codex):

- ExtractTo: entry names come from archive bytes; canonicalize each
  output path and refuse any that resolves outside the destination
  directory (path traversal via "../x" or rooted names)
- ctor: reject negative entry-count words with InvalidDataException
  instead of silently yielding an empty archive (the extended-format
  layout leads with 0xFFFFFFFF, which read as count -1)
- FromFile: honor an existing path as-given; WithExtension remains
  only the extensionless-name convenience (it rewrote "FOO.DAT" to
  "FOO.dat", which does not exist on case-sensitive filesystems)

Co-Authored-By: Imbas <imbas@eris.co>
foscail unpack extracts every entry from one or more DAT archives,
optionally converting the self-contained image formats (SPF, EFA) to
PNG. Palette-dependent formats (EPF/HPF/MPF/tiles) are raw-extracted;
converting those via TBL/PAL resolution is a later milestone.

Includes hardening from external review (Codex): extraction runs
inside the per-archive failure boundary so one bad archive cannot
abort the batch, extended-format (0xFFFFFFFF) archives are reported
as unsupported rather than silently parsed as empty, .dat discovery
is case-insensitive, and PNG output paths get the same containment
check as extraction. Foscail.Tests covers the CLI end-to-end over
synthetic archives, including the hostile and extended-format cases.

Verified against 3.61 and 7.41 client corpora: 55,518 entries
extracted, 3,605 PNGs, no failures.

Co-Authored-By: Imbas <imbas@eris.co>
Usage, the self-contained vs palette-dependent conversion split, and
behavior notes (failure handling, extended-format reporting, output
containment). Wired as PackageReadmeFile so nuget.org displays it.

Co-Authored-By: Imbas <imbas@eris.co>
@baughj
baughj merged commit ce399cc into main Jul 23, 2026
1 check passed
@baughj

baughj commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Milestone 2 pushed (86c6642): the palette-dependent tier now converts — stc HPF statics, MPF monsters (embedded palette number → mns pals, named sequences), effect and item-sheet EPFs, all khan part sprites (letter→family mapping incl. shared families and the palm skin ramp, m/w overrides), ground tiles from the tilea bank, and a gated legend.pal fallback for single-palette-era archives (3.61). --animate emits looping APNG, --gif animated GIF, both at authored intervals where they exist.

Verified: 7.41 corpus 55,518 entries → 536,125 images, zero failures (99%+ of image-format entries); 3.61 → 52,693 images. 17 Foscail tests pin the conventions at pixel level.

Generated with Imbas imbas@eris.co

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