Repository navigation
Mask the game's thread-local slot operand in the gauge field templates - #184
Merged
Merged
Conversation
Since the Forza update of November 5 (6.483.970.0 / 3.483.970.0) the direct gauge reader was rejected at attach on every PC and the needle fell back to Data Out values. A live capture of the running game shows that the field reader's type function and the 18 field initializers are unchanged except for one operand: the game's thread-local storage slot offset, 0x420 through 6.461 and 0x42C from 6.483. The templates compared that immediate as fixed code. Field templates can now declare a threadLocalSlot operand. It is masked like an address operand, it has no target, and every matched reader must carry the same 4-byte-aligned value below 64 KB. The type-body template and the 18 initializer signatures declare it; their normalized hashes are recomputed from the retained 6.461 live bytes. Getters still compare their member displacements byte for byte.
On Microsoft Store 3.483.970.0 one Tune guard calls a helper through a
5-byte jump thunk while the gauge templates call the same helper directly.
The shared-reference rule saw two targets for one reference and rejected
the gauge ("Adaptive references disagree"), so the Store build kept its
gauge fallback after the thread-local slot fix.
Both shared-reference checks, in the resolver and in the field verifier,
now accept exactly one 5-byte relative jump between the two targets and
keep the helper itself. The thunk bytes join the proof so a changed jump
invalidates the accepted evidence. Any other disagreement is still
rejected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Since the Forza Horizon 6 update of November 5 (Steam 6.483.970.0, Xbox app / Microsoft Store 3.483.970.0), the direct gauge reader is rejected at attach on every PC: 0 of 29 PCs with native HUD updates on the new build got a gauge observation, including sessions that stayed open across the update, so the needle angle and blur, EV digits, power and regen fill and headlight state fell back to Data Out values while Wisp still reported the build as supported.
Cause
Read-only captures of the running 6.483 (Steam) and 3.483 (Store) games show that the adaptive discovery resolves every gauge role, and that the field reader's type function and all 18 field initializers are instruction-for-instruction the same as on 6.461. The one operand that differs is the game's thread-local storage slot offset (
mov edx, imm32after thegs:[0x58]access): 0x420 through 6.461/3.461, 0x42C from 6.483/3.483. The type-body template and the 18 initializer templates compared that immediate as fixed code, soNativeAdaptiveFieldVerifierfailed withtype-body-template-changed. The 18 getters do not contain the operand and still match byte for byte, so the member offsets Wisp reads are unchanged.On the Store build a second, smaller difference remained: one Tune guard now calls a shared helper through a 5-byte jump thunk while the gauge templates call the helper directly, and the shared-reference rule rejected the two targets as a disagreement.
Changes
threadLocalSlotoperand kind for field templates: masked like an address operand, no target, and every matched reader (type function and all 18 initializers) must carry the same value, 4-byte aligned and below 64 KB. Implemented in both verification paths:NativeAdaptiveFieldVerifier(Steam, which checks against the Store reference namespace) andNativeAdaptiveResolver.ValidateFields(Store).adaptive-store.jsondeclare the operand (offset 34 and offset 31) with their normalized hashes recomputed from the retained 6.461 live bytes.docs/adaptive-game-compatibility.mddocuments both rules.Tests:
NativeAdaptiveFieldVerifierTests(fixture carries the operand; a different value passes, a value that differs between readers is rejected withthread-slot-inconsistent, an unaligned or out-of-range value withthread-slot-bounds; a shared helper reached through one jump thunk passes and the thunk is part of the final proof),NativeAdaptiveCompatibilityTests.ThreadLocalSlotsMustAgreeAndStayBounded.Verification
NativeAdaptiveImage+NativeAdaptiveResolver+CreatePack) against read-only live captures of the running games. Steam 6.483: before the change core and tune resolve and the gauge fails withtype-body-template-changed; with the change all 115 roles resolve, tune and gauge enabled, and the generated pack keeps every one of the 76 gauge member offsets and constants of the 6.461 pack while only the 14 address roles change. Store 3.483: with the change all 126 roles resolve, tune and gauge enabled.