Skip to content

force_server_validation: pass-through hooks, force-invoke BuildCheatData, NetCacheOne dump + replay#1

Open
devin-ai-integration[bot] wants to merge 7 commits into
mainfrom
devin/1778497743-force-server-validation
Open

force_server_validation: pass-through hooks, force-invoke BuildCheatData, NetCacheOne dump + replay#1
devin-ai-integration[bot] wants to merge 7 commits into
mainfrom
devin/1778497743-force-server-validation

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented May 11, 2026

Copy link
Copy Markdown

Summary

A default-OFF force_server_validation master switch that lets an owned-server operator study the post-battle settlement-validation flow end-to-end without any fabrication. Three layered, opt-in features, all built on the same five validation entry points the v7.9.1 static dump in Archero-7.9.1-hook-gold-report.md already identified:

  • BattleModuleData.BuildCheatData()
  • HTTPSendClient.CheckGameOverCheat(NetCacheOne)
  • LocalSave.BattleIn_DropEquipByServer
  • LocalSave.BattleIn_DropEquipDataByTransId(uint)
  • GameOverModeCtrlBase.CheckDropEquipsByServer(...)

1. Pass-through observation (force_server_validation=1)

Each of the five methods gets a pass-through hook that calls the original, returns whatever the original returned, and bumps a hits.validate_* counter visible in archero_mod_status.txt. The hook never alters return values or fabricates server responses, so it cannot fake a successful validation. If hits.validate_check_gameover_cheat ticks up after every battle, the operator knows the server actually received and validated the settlement packet.

Resolution is metadata-only via the existing HookSpec table. The five new rva:: entries (0xFE000001-0xFE000005) are intentional sentinels — they're outside any real libil2cpp.so executable mapping, so install_hook's address_in_libil2cpp_exec(target) guard safely skips any hook whose metadata lookup fails on a future Archero version.

2. Force-invoke BuildCheatData at settlement (force_server_validation=1)

To make the settlement payload reproducible run-over-run, the AddGold pass-throughs (BattleModuleData.AddGold(float) / (int)) capture the live BattleModuleData* into a global. At settlement (LocalSave.BattleIn_UpdateGold) the unmodified original BattleModuleData.BuildCheatData is re-invoked on that pointer so the cheat-data payload is rebuilt against the final coherent battle state right before the gameover packet is sent — even if some other code path tried to skip the natural rebuild. Numbers are not altered. Counters: hits.force_build_cheat_invoked, hits.force_build_cheat_missed.

Per-function install guards (g_hooked_add_gold_float, g_hooked_add_gold_int, g_hooked_battlein_update_gold) make this coexist with the existing g_gold_* switches without ever A64-hooking the same target twice.

3. NetCacheOne layout dump (dump_netcacheone=1, requires force_server_validation=1)

On every HTTPSendClient.CheckGameOverCheat(NetCacheOne) call, the pass-through walks the live NetCacheOne* argument with the IL2CPP runtime API and writes the result to archero_mod_netcacheone.txt:

  • Class chain via il2cpp_object_get_class + nested il2cpp_class_get_declaring_type (already used elsewhere in the module).
  • Every field via il2cpp_class_get_fields(klass, &iter).
  • Field name (il2cpp_field_get_name), full .NET type (il2cpp_field_get_type + il2cpp_type_get_name), byte offset (il2cpp_field_get_offset), and the primitive value read directly from obj + offset.
  • System.String slots are dereferenced via il2cpp_string_chars + il2cpp_string_length, ASCII-clamped, length-bounded (≤ 512 chars), and quoted in the file.
  • Reference-type slots that aren't strings are printed as name : ClassName @offset = pointer, with optional one-level-deeper recursion controlled by netcacheone_dump_max_depth (default 1, max 8).

Pure read — no field is written, no constructor is called, no object is allocated. The operator gets the exact wire layout the server sees for a real run.

4. NetCacheOne replay (replay_netcacheone=1, requires force_server_validation=1)

After the natural CheckGameOverCheat call returns, the pass-through immediately re-invokes the original implementation a second time with the exact same live HTTPSendClient* and live NetCacheOne* arguments the game just produced. The replayed packet carries the real transid from the run; nothing is fabricated. The server's idempotency / replay-rejection behavior on a legitimate request becomes observable: accept → server is undervalidating; reject → server's replay detector is working. Counters: hits.netcacheone_replay, hits.netcacheone_replay_skipped.

IL2CPP API surface added

Extended Il2CppApi with object_get_class, class_get_fields, field_get_name, field_get_type, field_get_offset, gchandle_new/free/get_target, string_chars, and string_length. The gchandle_* entries are wired up but unused by the current implementation (inline dump + inline replay), so any build where they don't resolve still works.

Default behavior unchanged

With every new flag at default 0, none of the new hooks install and the new counters all read zero. archero_mod_status.txt reports force_server_validation=0 / force_server_validation_installed=0 / dump_netcacheone=0 / replay_netcacheone=0.

Files touched

  • archero_mod/app/src/main/cpp/mod.cpp — sentinel RVAs, HookSpec entries, hook trampolines, install routine, force-invoke at settlement, NetCacheOne walker + replay, config-key parsing, status-file output, IL2CPP API table extension.
  • archero_mod_config.low-risk.txtforce_server_validation, dump_netcacheone, netcacheone_dump_max_depth, replay_netcacheone keys with full comments.
  • README.md and Archero-7.9.1-hook-gold-report.md — describe all three features and re-state the "server is the authority" rule.

Review & Testing Checklist for Human

  • Build the APK with the project's existing Gradle/NDK setup (the workstation here has no Android NDK, so only a host g++ -fsyntax-only pass was performed locally). Confirm the release build still produces libarchero_mod.so and the APK still stores it uncompressed.
  • On a v7.9.1 device, set force_server_validation=1 in the in-app config and play one battle. In archero_mod_status.txt confirm:
    • force_server_validation_installed=1, resolver.metadata count increased by ~5 over the previous boot, and resolver.last_error does NOT reference any of the new method names.
    • hits.validate_check_gameover_cheat and hits.validate_build_cheat are both non-zero (other counters depend on battle type — e.g. equipment-dropping stages light up hits.validate_drop_equip_by_server and hits.validate_check_drop_equips_by_server).
    • hits.force_build_cheat_invoked is non-zero (i.e., AddGold did fire and the cached pointer was usable at BattleIn_UpdateGold); hits.force_build_cheat_missed should stay at 0 for normal PvE.
  • Toggle on dump_netcacheone=1 and play one battle. Confirm archero_mod_netcacheone.txt is created and contains a class=...NetCacheOne... line followed by a fields block. Skim the field types and values for anything unexpected.
  • Toggle on replay_netcacheone=1 and play one battle. Confirm on the server side that two CheckGameOverCheat-style requests arrive in rapid succession with the same transid, and that the server's existing replay detector rejects the second one (or accepts it — either result is a useful data point about your server validators).
  • Toggle all four flags back to 0 and confirm the client returns to the pre-change behavior: force_server_validation_installed=0, all new hits.* counters stay flat across multiple battles.

Notes

  • If the IL2CPP metadata search fails for any of the five validation methods (e.g. arg count mismatch on a future Archero version, or a renamed class/method), the affected hook simply does not install; no other hooks are affected; the failure is surfaced as resolver.last_error / resolver.metadata_state in archero_mod_status.txt. The fix in that case is a one-line HookSpec tweak.
  • The NetCacheOne walker prints reference-type slots as pointer rather than recursing by default. Bump netcacheone_dump_max_depth=2 (or higher, up to 8) if you want one level of inner-object expansion; each step is bounded and there's no cycle detection, so depths above ~3 may emit duplicate sub-objects on graph-shaped fields. Field-count is also clamped to 96 per object to keep the file size sane.
  • Forward declaration of g_orig_build_cheat_data lives near the top of the globals block so hk_battlein_update_gold (which appears earlier in source order than the rest of the force_server_validation hook definitions) can re-invoke it.
  • The pass-throughs and force-invoke are designed to leave client-reported numbers untouched. A clean default-config run should still validate as a successful run on a correctly-configured server — that's exactly the "force a successful, accurate run" case from the original request.

Link to Devin session: https://app.devin.ai/sessions/5488d011d57741d68989cc85932d562f
Requested by: @Jordan231111

Introduce an optional default-OFF master switch that installs pass-through
hooks on the client-side post-battle server-validation entry points
identified by the v7.9.1 static dump:

  - BattleModuleData.BuildCheatData()
  - HTTPSendClient.CheckGameOverCheat(NetCacheOne)
  - LocalSave.BattleIn_DropEquipByServer
  - LocalSave.BattleIn_DropEquipDataByTransId(uint)
  - GameOverModeCtrlBase.CheckDropEquipsByServer(...)

The hooks call the original implementation and bump per-method
hits.validate_* counters in archero_mod_status.txt. They never alter
return values or fabricate server responses, so they cannot bypass
server-side validation; the server remains the authority for settlement.

Resolution is metadata-only via the existing HookSpec table. Sentinel
fallback RVAs (0xFE000001-0xFE000005) intentionally land outside
libil2cpp.so's executable mappings so the address_in_libil2cpp_exec
guard in install_hook safely skips any hook whose metadata lookup
fails on a future Archero version. Existing docs, status file output,
and config template are updated to describe the new feature and re-state
the server-is-authority rule.
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Extend the observation-only pass-through patch with an active force-invoke
step so the cheat-data payload is rebuilt against the final coherent
battle state right before each gameover packet is sent:

  * Cache the live BattleModuleData* from BattleModuleData.AddGold
    (float and int variants) into g_force_validate_battle_module_thiz
    whenever force_server_validation=1.
  * At settlement (LocalSave.BattleIn_UpdateGold) re-invoke the
    original BattleModuleData.BuildCheatData on the cached pointer.
    Pass-through: no return manipulation, no fabricated objects, just a
    second call to a real zero-arg client method using a pointer the
    game itself just used.
  * Expose hits.force_build_cheat_invoked / hits.force_build_cheat_missed
    counters in archero_mod_status.txt so the operator can see how often
    the forced rebuild fired vs how often it had no cached this pointer.
  * Add per-function install guards (g_hooked_add_gold_float /
    _int / _battlein_update_gold) so the new force_server_validation
    install path and the existing install_gold_hooks_once() path do not
    A64HookFunction the same target twice when the user enables both
    features simultaneously. The existing scaling logic still gates on
    g_gold_add_scale / g_gold_update_* internally, so behavior is
    unchanged when only one of the two master switches is on.
  * Forward-declare g_orig_build_cheat_data at the top of the globals
    block so hk_battlein_update_gold can call it before the rest of the
    force_server_validation hook definitions appear in source order.
…lidation

Two new strictly read/replay (no-fabrication) features that an owned-server
operator can use to study the post-battle settlement flow end-to-end:

  * dump_netcacheone=1 (requires force_server_validation=1)
    On every HTTPSendClient.CheckGameOverCheat call the pass-through hook
    walks the live NetCacheOne argument with the IL2CPP runtime API
    (il2cpp_object_get_class, il2cpp_class_get_fields, il2cpp_field_get_*,
    il2cpp_type_get_name, il2cpp_string_chars/length) and writes the class
    chain plus every field's name, full .NET type, byte offset, and
    primitive value into archero_mod_netcacheone.txt. Strings are
    ASCII-clamped. Recursion into reference-type fields is bounded by
    netcacheone_dump_max_depth (default 1, max 8). Pure read; no
    mutation, no fabrication.

  * replay_netcacheone=1 (requires force_server_validation=1)
    After the natural CheckGameOverCheat call returns, immediately
    re-invoke the original implementation with the exact same live
    HTTPSendClient* and NetCacheOne* arguments. No fabricated object,
    no synthesized transid: just a second legitimate packet built from
    the real run's data. Lets the operator observe how their server's
    idempotency / replay-detection behaves on a genuine transid.

Both features are default-OFF and surface counters in archero_mod_status.txt:
hits.netcacheone_dump, hits.netcacheone_replay, hits.netcacheone_replay_skipped.

Extends the IL2CPP API table with object_get_class, class_get_fields,
field_get_name, field_get_type, field_get_offset, gchandle_new/free/get_target,
string_chars, and string_length. The gchandle_* entries are exported for
future use (cross-call NetCacheOne capture via gc-rooted handle); the
current implementation keeps everything inline inside the existing
CheckGameOverCheat pass-through and does not require GC handles, so the
behaviour stays safe even on builds where any of these symbols are
unresolved.

README, Archero-7.9.1-hook-gold-report.md, and archero_mod_config.low-risk.txt
are updated to describe the new flags.
@devin-ai-integration devin-ai-integration Bot changed the title Add force_server_validation pass-through hooks force_server_validation: pass-through hooks, force-invoke BuildCheatData, NetCacheOne dump + replay May 11, 2026
Jordan231111 and others added 4 commits May 11, 2026 07:44
Resolves conflicts in mod.cpp:
- Combined hit-counter globals from both branches
- Deduplicated Il2CppApi struct fields (main added field_get_name,
  field_get_offset, class_get_fields; our branch added the full set
  needed for NetCacheOne walking)
- Deduplicated il2cpp_api resolution calls

APK is from this feature branch; needs rebuild against merged source
to include both wall-shot fixes and force_server_validation hooks.
…branch

New commit on main (59ed6c4) added ad-skip hit counters and reorganized hook
resolution. Conflicts were additive only:
- Combined hit-counter globals from both branches
- Combined hits.* status output lines

APK is from this feature branch; needs rebuild against merged source.
… branch

Main commits 78ec492 + c2c2420 removed the unused AOB resolver and
trimmed HookSpec from 8 fields to 6 (dropped aob, xref_anchor).

Resolution: kept main's trimmed HookSpec table and re-added our 5
force_server_validation entries in the new 6-field format right after
LocalSave_get_CanDropFirstEquip:
  - BattleModuleData.BuildCheatData
  - HTTPSendClient.CheckGameOverCheat (System.Single arg discriminator dropped)
  - LocalSave.get_BattleIn_DropEquipByServer
  - LocalSave.BattleIn_DropEquipDataByTransId
  - GameOverModeCtrlBase.CheckDropEquipsByServer

APK is from this feature branch; needs rebuild against merged source.
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