1c396dd562
autopatch side of the verified patched-client capability handshake: prove, at
runtime, that the empty-My-Packs resolver guard is active for a specific FIFA
process, and advertise it once on stdout for the launcher to relay.
- Add a per-pid guard verification state derived by a pure, testable
guard_state_after(cur_before, orig, patch, wrote_ok, cur_after) returning one
of VERIFIED / UNSUPPORTED_BUILD / WRITE_FAILED / VERIFY_FAILED (NOT_ATTEMPTED
is the pre-evaluation constant). VERIFIED means the live bytes at RVA 0x14858
are 7f 0f (JG) after enforcement (from an applied 75 0f->7f 0f, or already
patched). The existing fail-closed byte guard (guarded_action / STORE_PATCHES_
GUARDED) is unchanged — this only observes the outcome.
- Emit exactly once per FIFA pid: on VERIFIED,
[store-guard] verified capability fifa17.empty_mypacks_resolver=1 fifa_pid=<pid>
otherwise a non-advertising
[store-guard] guard status=<STATE> fifa_pid=<pid> (no capability advertised)
- Capability constants: EMPTY_MYPACKS_RESOLVER_VERSION=1, fully-qualified name
"fifa17.empty_mypacks_resolver".
- Tests: 5 guard-state cases + capability-constant assertions (standalone-runnable).
The capability = "the guard was verified in THIS FIFA process", never merely
"the code is present". Design: docs/plans/FIFA17_PATCHED_CLIENT_CAPABILITY.md.
66 lines
3.1 KiB
Python
Executable File
66 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Pure unit test for the empty-My-Packs store resolver guard in autopatch.py.
|
|
|
|
Covers the fail-closed guard decision (original -> PATCH, already-patched -> NOOP,
|
|
unknown -> SKIP) and pins the guarded patch table to the exact RVA/bytes proven on
|
|
the tested FIFA 17 build (JNZ 0x14869 -> JG 0x14869 at CardsDLL RVA 0x14858).
|
|
|
|
Run: python3 test_autopatch_guard.py
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import autopatch # importable: runtime loop is guarded by `if __name__ == "__main__"`
|
|
|
|
GUARD_VA = 0x180014858
|
|
ORIG = bytes.fromhex("750f") # JNZ 0x14869
|
|
PATCH = bytes.fromhex("7f0f") # JG 0x14869
|
|
|
|
|
|
def test_table_exact():
|
|
assert autopatch.STORE_PATCHES_GUARDED == {GUARD_VA: (ORIG, PATCH)}, \
|
|
autopatch.STORE_PATCHES_GUARDED
|
|
# Byte-level pin so a bad hex literal cannot slip through.
|
|
assert ORIG == b"\x75\x0f" and PATCH == b"\x7f\x0f"
|
|
|
|
|
|
def test_decision():
|
|
assert autopatch.guarded_action(ORIG, ORIG, PATCH) == "patch" # apply
|
|
assert autopatch.guarded_action(PATCH, ORIG, PATCH) == "noop" # already patched
|
|
assert autopatch.guarded_action(b"\x00\x00", ORIG, PATCH) == "skip" # build mismatch
|
|
assert autopatch.guarded_action(b"\x90", ORIG, PATCH) == "skip" # wrong length
|
|
|
|
|
|
def test_guard_state_after():
|
|
# already patched (7f0f) -> VERIFIED (guarded_action "noop"); write args irrelevant.
|
|
assert autopatch.guard_state_after(PATCH, ORIG, PATCH, True, PATCH) == autopatch.GUARD_VERIFIED
|
|
# original (750f) + write ok + reread 7f0f -> VERIFIED (guarded_action "patch").
|
|
assert autopatch.guard_state_after(ORIG, ORIG, PATCH, True, PATCH) == autopatch.GUARD_VERIFIED
|
|
# original + write FAILS -> WRITE_FAILED.
|
|
assert autopatch.guard_state_after(ORIG, ORIG, PATCH, False, ORIG) == autopatch.GUARD_WRITE_FAILED
|
|
# original + write ok but reread != 7f0f -> VERIFY_FAILED.
|
|
assert autopatch.guard_state_after(ORIG, ORIG, PATCH, True, ORIG) == autopatch.GUARD_VERIFY_FAILED
|
|
assert autopatch.guard_state_after(ORIG, ORIG, PATCH, True, b"") == autopatch.GUARD_VERIFY_FAILED
|
|
# unknown bytes -> UNSUPPORTED_BUILD (guarded_action "skip"); write args irrelevant.
|
|
assert autopatch.guard_state_after(b"\x00\x00", ORIG, PATCH, True, PATCH) == autopatch.GUARD_UNSUPPORTED_BUILD
|
|
|
|
|
|
def test_capability_constants():
|
|
assert autopatch.EMPTY_MYPACKS_RESOLVER_VERSION == 1
|
|
assert autopatch.EMPTY_MYPACKS_RESOLVER_CAPABILITY == "fifa17.empty_mypacks_resolver"
|
|
# State constant values are the exact tokens carried in the emitted status line.
|
|
assert autopatch.GUARD_VERIFIED == "VERIFIED"
|
|
assert autopatch.GUARD_UNSUPPORTED_BUILD == "UNSUPPORTED_BUILD"
|
|
assert autopatch.GUARD_WRITE_FAILED == "WRITE_FAILED"
|
|
assert autopatch.GUARD_VERIFY_FAILED == "VERIFY_FAILED"
|
|
assert autopatch.GUARD_NOT_ATTEMPTED == "NOT_ATTEMPTED"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_table_exact()
|
|
test_decision()
|
|
test_guard_state_after()
|
|
test_capability_constants()
|
|
print("OK: autopatch guard table + fail-closed decision + guard-state function + capability constants")
|