Skip to content

Add macOS readMtgaCardDatabase + readMtgaInventory#4

Closed
dan-blanchard wants to merge 2 commits intomtgatool:mainfrom
dan-blanchard:feat/macos-card-database-inventory
Closed

Add macOS readMtgaCardDatabase + readMtgaInventory#4
dan-blanchard wants to merge 2 commits intomtgatool:mainfrom
dan-blanchard:feat/macos-card-database-inventory

Conversation

@dan-blanchard
Copy link
Copy Markdown

Extends the heap-signature-scan approach from #3 to two more data sources: the card-printing database and the player inventory.

Depends on #3 for the scanner infrastructure.

readMtgaCardDatabase

Scans for a `Dictionary<int, CardPrintingData*>` with ~23K entries at stride 24. Returns `{ grpId, set, collectorNumber, titleId }` for every card printing in Arena's memory.

Key discoveries during implementation:

  • The runtime dict value class is `CardPrintingData`, not `CardPrintingRecord`. `CardPrintingData` is a wrapper with ~47 cached fields and an embedded `CardPrintingRecord` struct at offset `0xC0` (field named `Record`).
  • When a class is embedded as a value-type struct, the IL2CPP 16-byte object header drops out. Field offsets shift by -0x10 (e.g., `GrpId` at `0xC0 + (0x10 - 0x10) = 0xC0`, not `0xD0`).
  • IL2CPP keeps separate `Il2CppClass*` addresses for metadata-table entries vs runtime vtable owners. Comparing by class name (not pointer) is required.
  • `get_class_fields` has a 50-entry hard limit and reads past the end into adjacent class metadata. Looking up fields by name avoids the overflow.

readMtgaInventory

Scans for `ClientPlayerInventory` using a class-pointer-set pre-filter plus value plausibility and activity-score ranking. Returns wildcards, gold, gems, and vault progress.

Key discovery: `vaultProgress` is an 8-byte `double` (not `int32`), storing the UI percentage directly (e.g., 58.9). Field spacing confirms this: `vaultProgress @ 0x30`, `boosters @ 0x38` = 8-byte field.

Both functions support `MTGA_DEBUG_CARD_DB` and `MTGA_DEBUG_INVENTORY` env vars for verbose diagnostic output.

Testing

Verified against live Arena on macOS arm64 (Unity 2022.3.62f2):

  • `readMtgaCardDatabase("MTGA")` returns 23,694 rows in ~11s, 23,693/23,694 with valid set + collector number
  • `readMtgaInventory("MTGA")` returns correct wildcard counts, gold, gems, and vault progress (ground-truth verified against Arena's UI)
  • Cross-check: every `grpId` from `readMtgaCards` resolves to a valid set + collector in the card database output

🤖 Generated with Claude Code

dan-blanchard and others added 2 commits April 12, 2026 22:53
Adds a macOS-only readMtgaCards napi export that bypasses the fragile
PAPA/WrapperController walker by signature-scanning Arena's heap for
the card-collection Dictionary<int,int>.

The signature scan identifies the dictionary by its data invariant:
- hash == key (Dictionary<int,V> with default EqualityComparer<int>)
- Keys in Arena card-ID range [1, 200000]
- Values in ownership range [1, 4]
- Count in [500, 50000]
- First 30 sampled entries must satisfy all constraints

This uniquely identifies the live collection dictionary across 200K+
candidate heap positions. Supports optional MTGA_KNOWN_CARD_IDS and
MTGA_VERIFY_QTYS env vars for ground-truth cross-validation.

Also adds investigation notes in NOTES.md documenting the IL2CPP
memory layout, CardPrintingRecord field table (50 fields with offsets),
and the approaches tried during reverse engineering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends the heap-signature-scan approach to two more data sources:

readMtgaCardDatabase: scans for a Dictionary<int, CardPrintingData*>
with ~23K entries. Discovers that the runtime class is CardPrintingData
(not CardPrintingRecord as expected), with an embedded Record struct
at offset 0xC0. Handles the IL2CPP class variant duplication (metadata
vs runtime class pointers) by resolving class names instead of
comparing pointers. Adjusts for the embedded-struct header omission
(field offsets shift by -0x10 when a class is inlined as a value type).
Returns { grpId, set, collectorNumber, titleId } for all ~23,694 cards.

readMtgaInventory: scans for ClientPlayerInventory using a class-pointer
set pre-filter (via find_all_classes_by_name) plus value plausibility
checks and activity-score ranking. Discovers that vaultProgress is an
8-byte double (not int32), storing the UI percentage directly (e.g.
58.9). Ground-truth verified against Arena's UI.

Both functions support MTGA_DEBUG_CARD_DB and MTGA_DEBUG_INVENTORY
env vars for verbose diagnostic output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dan-blanchard
Copy link
Copy Markdown
Author

Superseded — the card database portion is now handled by the SQLite reader in #1. Reopening as inventory-only PR.

@dan-blanchard dan-blanchard deleted the feat/macos-card-database-inventory branch April 13, 2026 03:29
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