force_server_validation: pass-through hooks, force-invoke BuildCheatData, NetCacheOne dump + replay#1
Open
devin-ai-integration[bot] wants to merge 7 commits into
Conversation
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.
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
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.
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.
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.
Summary
A default-OFF
force_server_validationmaster 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 inArchero-7.9.1-hook-gold-report.mdalready identified:BattleModuleData.BuildCheatData()HTTPSendClient.CheckGameOverCheat(NetCacheOne)LocalSave.BattleIn_DropEquipByServerLocalSave.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 inarchero_mod_status.txt. The hook never alters return values or fabricates server responses, so it cannot fake a successful validation. Ifhits.validate_check_gameover_cheatticks up after every battle, the operator knows the server actually received and validated the settlement packet.Resolution is metadata-only via the existing
HookSpectable. The five newrva::entries (0xFE000001-0xFE000005) are intentional sentinels — they're outside any real libil2cpp.so executable mapping, soinstall_hook'saddress_in_libil2cpp_exec(target)guard safely skips any hook whose metadata lookup fails on a future Archero version.2. Force-invoke
BuildCheatDataat settlement (force_server_validation=1)To make the settlement payload reproducible run-over-run, the AddGold pass-throughs (
BattleModuleData.AddGold(float)/(int)) capture the liveBattleModuleData*into a global. At settlement (LocalSave.BattleIn_UpdateGold) the unmodified originalBattleModuleData.BuildCheatDatais 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 existingg_gold_*switches without ever A64-hooking the same target twice.3. NetCacheOne layout dump (
dump_netcacheone=1, requiresforce_server_validation=1)On every
HTTPSendClient.CheckGameOverCheat(NetCacheOne)call, the pass-through walks the liveNetCacheOne*argument with the IL2CPP runtime API and writes the result toarchero_mod_netcacheone.txt:il2cpp_object_get_class+ nestedil2cpp_class_get_declaring_type(already used elsewhere in the module).il2cpp_class_get_fields(klass, &iter).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 fromobj + offset.System.Stringslots are dereferenced viail2cpp_string_chars+il2cpp_string_length, ASCII-clamped, length-bounded (≤ 512 chars), and quoted in the file.name : ClassName @offset = pointer, with optional one-level-deeper recursion controlled bynetcacheone_dump_max_depth(default1, max8).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, requiresforce_server_validation=1)After the natural
CheckGameOverCheatcall returns, the pass-through immediately re-invokes the original implementation a second time with the exact same liveHTTPSendClient*and liveNetCacheOne*arguments the game just produced. The replayed packet carries the realtransidfrom 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
Il2CppApiwithobject_get_class,class_get_fields,field_get_name,field_get_type,field_get_offset,gchandle_new/free/get_target,string_chars, andstring_length. Thegchandle_*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.txtreportsforce_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.txt—force_server_validation,dump_netcacheone,netcacheone_dump_max_depth,replay_netcacheonekeys with full comments.README.mdandArchero-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
g++ -fsyntax-onlypass was performed locally). Confirm the release build still produceslibarchero_mod.soand the APK still stores it uncompressed.force_server_validation=1in the in-app config and play one battle. Inarchero_mod_status.txtconfirm:force_server_validation_installed=1,resolver.metadatacount increased by ~5 over the previous boot, andresolver.last_errordoes NOT reference any of the new method names.hits.validate_check_gameover_cheatandhits.validate_build_cheatare both non-zero (other counters depend on battle type — e.g. equipment-dropping stages light uphits.validate_drop_equip_by_serverandhits.validate_check_drop_equips_by_server).hits.force_build_cheat_invokedis non-zero (i.e., AddGold did fire and the cached pointer was usable atBattleIn_UpdateGold);hits.force_build_cheat_missedshould stay at 0 for normal PvE.dump_netcacheone=1and play one battle. Confirmarchero_mod_netcacheone.txtis created and contains aclass=...NetCacheOne...line followed by a fields block. Skim the field types and values for anything unexpected.replay_netcacheone=1and play one battle. Confirm on the server side that twoCheckGameOverCheat-style requests arrive in rapid succession with the sametransid, 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).0and confirm the client returns to the pre-change behavior:force_server_validation_installed=0, all newhits.*counters stay flat across multiple battles.Notes
resolver.last_error/resolver.metadata_stateinarchero_mod_status.txt. The fix in that case is a one-lineHookSpectweak.pointerrather than recursing by default. Bumpnetcacheone_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.g_orig_build_cheat_datalives near the top of the globals block sohk_battlein_update_gold(which appears earlier in source order than the rest of theforce_server_validationhook definitions) can re-invoke it.Link to Devin session: https://app.devin.ai/sessions/5488d011d57741d68989cc85932d562f
Requested by: @Jordan231111