hook(fifa17): repair the store tab bar by rebinding the native binder
Replaces three disproven store-entry mechanisms (category clamp, late *_CATEGORY_ID publish, purchase-group pre-warm) with the one repair the reversing actually supports. FUN_18007e5e0(ctx, panel) is the native tab binder the screen framework invokes at screen-show. It is an unrolled six-slot loop; each slot gates on one hard-coded category token and either publishes that group's id as PANEL_ID for the slot or hides the slot: slot 0 mypacks, 1 bronze, 2 silver, 3 gold, 4 special, slot 5 points (extra gate: (*(store_vtbl+0x30))(store) must be false) The gate FUN_180014df0(_, idx) resolves the token through FUN_180014380, which linearly scans the loaded purchase groups (stride 0x108) comparing the token at group+0x70. So a tab exists iff a purchase group carrying that token is loaded AT BIND TIME. Our server emits mypacks/bronze/silver/gold as displayGroup.value, so four tabs are expected. On a cold session the store screen shows before its own GET store/purchasegroup/all response arrives: every gate fails, all six slots take the hide path, and the binder is never invoked again for that screen. Re-entry works only because the groups are cached by then -- which is exactly the reported symptom. The repair re-invokes the binder once, with the framework's own (ctx, panel), at the first render after the groups arrive, reproducing the re-entry ordering on the first entry. Repeating the binder is safe: it only publishes PANEL_ID or hides per slot, reads the group list from a process singleton, and finishes by tail-calling panel->vtbl[0xd0](panel, true) -- the provider commit that rebuilds the movie's bar. Fail-closed: rebind only when the framework's bind observed an EMPTY mask and at least one token now resolves (a store that already bound tabs is never touched); one rebind per bind generation, claimed by compare-exchange; only framework-supplied pointers are ever used; image plus all three function signatures verified before any write and re-verified under thread suspension. The gate probe passes a null this, which is sound because FUN_180014df0 forwards rcx to FUN_180014380, which discards it and uses a singleton. Why the earlier attempts could not work: the clamp forced a single category (regressing Browse Packs to bronze-only), the publish targeted FUN_18007df60 which does not bind panels, and the pre-warm ran from the FUT event dispatcher -- after screen-show, so the slot decisions were already made. Promoted (build-armed, no env var). Rollback is a version.dll file swap.
This commit is contained in:
@@ -443,10 +443,6 @@ unsafe extern "system" fn event_wrapper(
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Piggyback the store pre-warm on this game-thread event: it loads the purchase
|
||||
// groups once, long before the store screen is shown, so the store's native
|
||||
// screen-show tab bind sees a populated group list (see `store_entry`).
|
||||
crate::store_entry::maybe_prewarm_groups();
|
||||
let original: EventDispatchFn = core::mem::transmute(EVENT_TRAMPOLINE.load(Ordering::Acquire));
|
||||
let result = original(controller, event, payload);
|
||||
EVENT_EXITS.fetch_add(1, Ordering::Release);
|
||||
|
||||
Reference in New Issue
Block a user