This document summarizes the research and findings for implementing IL2CPP support in mtga-reader for macOS.
Add IL2CPP backend support to mtga-reader alongside existing Mono support, enabling cross-platform memory reading (especially macOS). The goal is to create a unified API where the same paths work on both platforms.
Successfully reading card collection and player inventory from MTGA on macOS!
Example output:
=== Card Collection ===
Unique cards: 9695
Total cards: 25117
=== Player Resources ===
Wildcards - Common: 174, Uncommon: 72, Rare: 14, Mythic: 2
Gold: 1750
Gems: 140
The complete navigation path to read card data:
PAPA instance (found by scanning heap for class pointer)
└─ +224: <InventoryManager>k__BackingField
└─ +56: _inventoryServiceWrapper (AwsInventoryServiceWrapper)
├─ +64: m_inventory (ClientPlayerInventory - wildcards, gold, gems)
└─ +72: <Cards>k__BackingField (CardsAndQuantity)
└─ +0x18: entries (Entry[] - Dictionary entries)
// Il2CppClass offsets for MTGA:
class_name = 0x10 // const char* name
class_namespace = 0x18 // const char* namespace
class_fields = 0x80 // FieldInfo* fields (NOT 0x70!)
class_static_fields = 0xA8 // void* static_fields// Each FieldInfo entry:
+0x00: name_ptr (8 bytes) // Pointer to field name string
+0x08: type_ptr (8 bytes) // Pointer to Il2CppType
+0x10: parent (8 bytes) // Pointer to parent class
+0x18: offset (4 bytes) // Field offset in instance
+0x1C: token (4 bytes) // Metadata token// At Il2CppType + 0x08:
let type_attrs = read_u32(type_ptr + 0x08);
let is_static = (type_attrs & 0x10) != 0;The Cards field is NOT a Dictionary, but a custom CardsAndQuantity class:
// CardsAndQuantity layout:
+0x00: class_ptr
+0x10: buckets (Int32[]) // Hash buckets for lookup
+0x18: entries (Entry[]) // Dictionary entries with card data
+0x20: count (i32) // Number of unique cards// Each Entry in the entries array (16 bytes):
+0x00: hashCode (i32)
+0x04: next (i32)
+0x08: key (i32) // Card ID
+0x0C: value (i32) // Quantity// Player resources at m_inventory:
+16: wcCommon (i32)
+20: wcUncommon (i32)
+24: wcRare (i32)
+28: wcMythic (i32)
+32: gold (i32)
+36: gems (i32)
+48: vaultProgress (i32)Code/metadata: 0x100... - 0x128...
Heap objects: 0x145... - 0x170...
Alternate heap: 0x305... - 0x340... (also valid!)
Using mach2 crate:
let task_port = task_for_pid(mach_task_self(), pid as i32, &mut task);
mach_vm_read_overwrite(task_port, addr, size, buffer, &mut out_size);let data_base = get_second_data_segment(pid); // Second __DATA segment
let type_info_table = read_ptr(data_base + 0x24360);Scan heap regions (0x15a...) for objects where the class pointer matches PAPA class:
for addr in heap_range.step_by(8) {
if read_ptr(addr) == papa_class && read_ptr(addr + 16) != papa_class {
return Some(addr); // Skip FieldInfo entries
}
}let entries_ptr = read_ptr(cards_ptr + 0x18);
let count = read_i32(cards_ptr + 0x20);
for i in 0..count {
let entry = entries_ptr + 0x20 + i * 16;
let hash = read_i32(entry);
let card_id = read_i32(entry + 8);
let quantity = read_i32(entry + 12);
if hash >= 0 && card_id > 0 {
cards.push((card_id, quantity));
}
}Key test files that demonstrate working functionality:
| File | Purpose |
|---|---|
test_read_collection.rs |
Full working card reader |
test_heap_instances.rs |
Find real object instances in heap |
test_cards_quantity.rs |
Decode CardsAndQuantity structure |
test_read_dict.rs |
Explore dictionary-like structures |
# Run the card collection reader
sudo cargo run --example test_read_collection --releaseThe IL2CPP backend implementation:
src/il2cpp/mod.rs- Module exportssrc/il2cpp/reader.rs- Main Il2CppBackend implementationsrc/il2cpp/macos_memory.rs- macOS memory readingsrc/il2cpp/metadata.rs- Metadata parser (v31)src/il2cpp/offsets.rs- Structure offsets (needs updating to use 0x80 for fields)src/il2cpp/type_definition.rs- Type definition wrappersrc/il2cpp/field_definition.rs- Field definition wrapper
- Update
src/il2cpp/offsets.rs- Changeclass_fieldsfrom 0x70 to 0x80 - Implement PAPA instance finder - Scan heap at startup to find current instance
- Add dynamic path resolution - Navigate from PAPA to cards using field names
- Integrate with main library - Add IL2CPP backend to the unified API
- PAPA instance address changes between game restarts (need to scan each time)
- Addresses in 0x03... range are valid on this macOS version
- FieldInfo entries in metadata region have class pointer at +16, use this to filter
- The Cards data uses a custom CardsAndQuantity class, not a standard Dictionary