Skip to content

Add Free Story shield hooks (S.1 send_use_key + S.2 co-hooks)#2

Open
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1778613623-free-story-shield-hooks
Open

Add Free Story shield hooks (S.1 send_use_key + S.2 co-hooks)#2
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1778613623-free-story-shield-hooks

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Implements the Tier S.1 + Tier S.2 hooks from the v7.9.1 Free Story
reverse-engineering report as native LSPosed hooks in mod.cpp.

Strategy: stop patching GameLogic.GetModeLevelKey (which only flips the
displayed cost) and intercept the actual choke point — GameLogic.send_use_key
— so Free Story plays never reach the network. Because the in-game predictive
Modify_Key(-cost) lives inside send_use_key itself, blocking the method
also leaves the displayed Key count untouched. The result has no on-the-wire
fingerprint relative to a no-op, so there is no client/server drift for
Vip.IsFreeStoryNumValid / IsCanFreeStoryToday to resync.

Two optional defense-in-depth co-hooks are wired but OFF by default:

Tier Target v7.9.1 RVA Default Purpose
S.1 GameLogic.send_use_key() 0x58CB67C ON Swallow the outbound key-consumption packet (no server hit, no local deduct).
S.2a LocalSave.Modify_Key(long, bool) 0x5A494C4 OFF Drop only negative deltas from any other predictive decrement path.
S.2b LocalSave.UserInfo.SetKey(int) 0x5B1E9E8 OFF Refuse authoritative resync writes that would lower the displayed Key.

All three are resolved at runtime through IL2CPP metadata (class name +
method name + arg count + optional first-param type), so a future game patch
that shifts RVAs but keeps the class/method names will keep working without
recompilation. The v7.9.1 RVAs are kept in namespace rva only as dump
anchors. LocalSave.UserInfo.<Key>k__BackingField (offset 0xB0 in v7.9.1)
is similarly resolved through metadata, and the SetKey hook falls back to
passthrough if the offset failed to resolve so a metadata regression cannot
accidentally permanent-freeze keys.

Status file now reports the three new toggles, the resolved Key field
offset, and four hit counters
(hits.free_story_send_blocked / modify_blocked / set_key_blocked / passthrough).

Verified locally:

  • IL2CPP dump confirms RVAs and class/method names against the 7.9.1 split
    APK (libil2cpp.so 146 MB, global-metadata.dat 31 MB).
  • cmake -G Ninja against the NDK r27c toolchain builds cleanly.
  • ./gradlew assembleDebug produces a 152 KB libarchero_mod.so that
    contains all three hook handlers and config key strings (send_use_key,
    LocalSave.UserInfo, Modify_Key, free_story, hk_send_use_key,
    hk_localsave_modify_key, hk_userinfo_set_key).
  • See docs/free-story-shield.md for the full strategy + verification
    recipe.

Review & Testing Checklist for Human

  • Field-offset metadata resolution. Confirm
    archero_mod_status.txt contains
    field_offsets.localsave_userinfo_key=0xb0 after a clean launch on
    v7.9.1. A 0x0 here means the SetKey co-hook will passthrough — the
    module behaves correctly but the freeze toggle becomes a no-op.
  • S.1 effect on real device. Enter Free Story and confirm
    hits.free_story_send_blocked increments by 1 per attempt, the
    displayed Key value does not change, and no useKey request is sent
    (verifiable via mitmproxy or tcpdump).
  • Stock control. Set free_story=0 in
    /storage/emulated/0/Android/data/com.habby.archero/files/archero_mod_config.txt,
    restart Archero, and verify the Key decrements normally and the
    packet goes out — i.e. only the hook is changing behavior.
  • Co-hook regressions. Briefly verify ad refills and shop Key
    purchases still credit the player when free_story_freeze_key=1
    (positive writes must still pass through).

Notes

  • Module remains arm64-only and targeted at v7.9.1.
  • Default behavior toggles only the S.1 shield ON; S.2 co-hooks are OFF so
    legitimate Key adjustments (ad refills, shop grants, login bootstrap) are
    not blocked.
  • The original Lua hack's GetModeLevelKey=8 patch is deliberately not
    ported — it doesn't suppress the network request and only delays drift.

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

Hooks GameLogic.send_use_key so Free Story plays never reach the network
and the local Key counter stays in lockstep with the server (S.1). Two
optional co-hooks (LocalSave.Modify_Key, LocalSave.UserInfo.SetKey) add
defense-in-depth resync blocking when enabled.

All hooks resolve through IL2CPP metadata at runtime; v7.9.1 RVAs are kept
as anchors. Adds field-offset resolution for LocalSave.UserInfo's
<Key>k__BackingField, status-file telemetry (toggles + four hit counters +
resolved offset), three config keys, and an updated archero_mod_config.low-risk.txt
profile. See docs/free-story-shield.md for the full strategy writeup.
@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

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