Skip to content

fix(art): default the .tar cover loader ON (wOPL parity) + 4 real tar-engine defects (#54)#207

Merged
NathanNeurotic merged 1 commit into
masterfrom
claude/arttar-default-on
Jul 17, 2026
Merged

fix(art): default the .tar cover loader ON (wOPL parity) + 4 real tar-engine defects (#54)#207
NathanNeurotic merged 1 commit into
masterfrom
claude/arttar-default-on

Conversation

@NathanNeurotic

Copy link
Copy Markdown
Owner

The report (brenotomaz-eng, #54)

I used to use Wopl and the .tar covers worked perfectly, but in this fork RiptOPL they don't load. I looked for an activation option but couldn't find it in the display tab.

Root cause — it's one line

gEnableArtTar = 0;

wOPL has no equivalent gate on either branch — verified against the real source, not from memory:

git grep -iE 'gEnableArtTar|ENABLE_ART_TAR' wopl/wOPL-base wopl/wOPL-release   → (nothing)

Both wOPL branches try the archive first, unconditionally, with the loose ART/ file as fallback. We shipped exactly the feature he asked for, switched off — and he told us in the report that he went looking for the switch and couldn't find it.

Worth noting #54 was never HW-confirmed: he tried to retest, and his console hung at boot — that's the separate exFAT-USB/WOPLSDK boot hang, not art.tar.

Settled negatives (checked against wOPL — don't re-litigate)

  • In-archive entry name is byte-for-byte correct (<value>_<suffix>.png, no folder prefix, no ./).
  • ustar parser matches (size@124, 12 octal digits, 512-byte blocks, zero-block terminator).
  • art_cache.bin header is byte-identical; a version mismatch is rejected and self-heals.

The engine was never the problem.

What else this fixes (default-ON makes these load-bearing)

1. Index corruption — we were less robust than wOPL. tarParseFile clamped rawSize to MAX_FILE_SIZE then derived paddedSize from the clamped value, so any member >4 MiB seeked short: the walk landed mid-data and indexed every subsequent entry at a garbage offset — and tarWriteCache persisted that to art_cache.bin, where it passes revalidation (which only rejects > MAX_FILE_SIZE, and the clamped value is exactly ==) and survives reboots. Now the seek always uses the uncapped padded size and oversized members are skipped.

2. Silent session-wide kill switch. A redundant stat() existence probe latched s_inactive[kind] — write-once, process-wide, and nothing live cleared it (tarInvalidate had zero callers). The probe was pointless: the caller already proved the file opens and the function opens it again two lines later. Any driver where open() works but stat() doesn't killed art.tar for the whole session with the archive sitting right there.

3. The toggle couldn't re-arm. Because that latch is write-once and tarInvalidate was dead code, turning the loader on after boot returned nothing until a reboot — i.e. the toggle looked broken. Now wired on change.

4. OOB on the declared type. filename[41], but the parser derives its copy length from the entry stride: nameMax = 64-16-1 = 47 → memcpy 47 + NUL at [47] = 7 bytes past. Benign (landed in tail padding) but out of bounds and one refactor from corrupting the next entry. Verified [48] keeps sizeof(TarEntryArt) == 64:

base=16
OLD filename[41]: sizeof=64 nameMax=47 (NUL at [47] -> OUT OF BOUNDS)
NEW filename[48]: sizeof=64 nameMax=47 (NUL at [47] -> in bounds)
stride unchanged (64 == 64)? YES - art_cache.bin layout + wOPL compat preserved

Plus tracing: src/tar.c shipped with zero LOG() calls, so every "art.tar doesn't work" report was unfalsifiable (the CHT path announces its hit + device). Added logging for the index hit, per-device parse failure, the oversized skip, and the latch arming.

Why default-ON is safe

wOPL's own author answered the perf objection — commit dcf273bd "Fix tar device probing ... Fixes lag on game selection when tar files aren't present": a setup with no art.tar costs one failed open per device per session, then zero. That latch is precisely why wOPL needs no user toggle. Ours stays as an escape hatch.

Validation

Struct changed → full make clean rebuild, exit 0.

🤖 Generated with Claude Code

… defects in the tar engine (#54)

brenotomaz-eng (#54): "I used to use Wopl and the .tar covers worked perfectly, but in this fork RiptOPL
they don't load. I looked for an activation option but couldn't find it in the display tab."

ROOT CAUSE: `gEnableArtTar = 0`. That is the whole bug. wOPL has NO equivalent gate on EITHER branch --
verified against the actual source (`git grep -iE 'gEnableArtTar|ENABLE_ART_TAR' wopl/wOPL-base
wopl/wOPL-release` returns nothing); both try the archive FIRST, unconditionally, with the loose ART/ file
as the fallback. So we shipped the feature he asked for, switched off, and he told us in the report that he
went looking for the switch and could not find it. Note #54 was never HW-confirmed either: he tried to
retest and his console hung at boot -- that is the separate exFAT-USB/WOPLSDK boot hang, not art.tar.

SETTLED NEGATIVES (checked against wOPL, do not re-litigate): the in-archive entry name is byte-for-byte
correct ("<value>_<suffix>.png", no folder prefix, no "./"), the ustar parser matches (size@124, 12 octal
digits, 512-byte blocks, zero-block terminator), and art_cache.bin's header is byte-identical -- a version
mismatch is rejected and self-heals by re-parsing. The engine was never the problem.

Flip the default (the toggle stays as an escape hatch) and fix what default-ON makes load-bearing:

1. INDEX CORRUPTION (real bug, we were LESS robust than wOPL). tarParseFile clamped rawSize to
   MAX_FILE_SIZE and then derived paddedSize FROM THE CLAMPED VALUE, so any member >4 MiB seeked SHORT:
   the walk landed mid-data and indexed every SUBSEQUENT entry at a garbage offset -- and tarWriteCache
   PERSISTED that corrupt index to art_cache.bin, where it passes revalidation (which only rejects
   rawSize > MAX_FILE_SIZE, and the clamped value is exactly ==) and survives reboots until the archive's
   size changes. Now the seek always uses the UNCAPPED padded size and an oversized member is skipped
   (wOPL refuses the whole archive; skipping keeps the rest usable and still never desynchronises).

2. SILENT SESSION-WIDE KILL SWITCH. tarParseFile opened with a stat() existence probe whose failure
   latched s_inactive[kind] -- write-once, process-wide, and nothing live cleared it (tarInvalidate had
   ZERO callers). The probe was pure redundancy: the caller already proved the file opens, and this
   function opens it again two lines later. So any driver where open() works but stat() does not killed
   art.tar for the whole session with the archive sitting right there. Removed; open() is now the single
   arbiter of existence.

3. THE TOGGLE COULDN'T RE-ARM. Because that latch is write-once and tarInvalidate was dead code, turning
   the loader ON after boot kept returning nothing until a reboot -- i.e. the toggle looked broken.
   gui.c now calls tarInvalidate(TAR_KIND_ART) when the setting actually changes.

4. OOB ON THE DECLARED TYPE. TarEntryArt.filename was [41], but the parser derives its copy length from
   the entry STRIDE, not the array: nameMax = entrySize - sizeof(TarEntryBase) - 1 = 64-16-1 = 47, so it
   memcpy'd 47 bytes and NUL-terminated at [47] -- 7 past the declared size. Benign in practice (it landed
   in the struct's own tail padding) but out of bounds on the type and one refactor away from corrupting
   the next entry. Verified [48] keeps sizeof(TarEntryArt) == 64, so entrySize, nameMax, copyLen, the
   on-disk art_cache.bin layout and wOPL cache compatibility are ALL unchanged (no ARC_VERSION bump).

Also: src/tar.c shipped with ZERO LOG() calls, so every "art.tar doesn't work" report was unfalsifiable
(the CHT path announces its hit + device at supportbase.c). Added tracing for the index hit, per-device
parse failure, the oversized skip, and the latch arming.

Perf of default-ON is answered by wOPL's own author (dcf273bd, "Fix tar device probing ... Fixes lag on
game selection when tar files aren't present"): a setup with no art.tar costs one failed open per device
per session, then zero -- that latch is exactly why wOPL needs no user toggle.

Struct changed -> validated with a full `make clean` rebuild, exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@NathanNeurotic
NathanNeurotic merged commit ba6894b into master Jul 17, 2026
60 checks passed
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