Repository navigation
Releases: Treyo1928/AoQ-Modloader
Release list
Version 1.4.0 - Early mod loading + auto-updating configs
Mods now load before the game's first scene, so everything they change applies from the moment you launch: no more "reload the level and then it works". Mod configs also update automatically when a mod adds new settings.
Upgrading
- From v1.3.0: just install this APK over the top (SideQuest drag-and-drop or
adb install -r). No uninstall needed, and your saves and mods are kept. - From an older version, or if the install is rejected: back up your game data in SideQuest, uninstall, install this APK, then restore the backup (same steps as the README's Step 3).
- No mods need to be rebuilt.
Fixed
-
Mods missed the first scene. Since v1.3.0, mods loaded on the first frame of every launch, after the opening scene (
TitanForest) had already set itself up without them. Anything a mod changes at scene start only took effect after a level reload, for example:- TitanSpawner's titan settings
- BeastTitan's flare
- BetterMP and CustomMaps' scene setup
- leaderboard and reward fixes
Mods now load right after the game's scripting runtime starts, before any scene. The one exception is the very first launch after a fresh install: mods load right after you approve the storage prompt, as before.
-
New config settings never appeared. When a newer version of a mod (or the mod manager itself) added a setting, your existing config file hid it until you deleted the file. Configs now merge on launch:
- new settings show up with their defaults
- descriptions update
- every value you changed is kept
Mods get this once they're rebuilt against this version's
modapi.h.
Added
EarlyModLoadsetting (Mods → Configure Mods → Mod Manager, default on). Turn it off to go back to v1.3's load-on-first-frame behaviour if a mod misbehaves; it takes effect next launch.signing/debug.keystorein the repo. This is the public key every patched APK is signed with, so future releases (and APKs you build yourself) install over each other without an uninstall. The README's "Patch the APK" step covers signing with it and swapping in a newlibmodloader.so.
For mod authors
lib_main()now usually runs before any scene exists. Keep it to setup: install hooks, register, read config. Don't call Unity or game methods there; do that from a hook. Every mod checked for this release already does.aoqmm_ensure_config()now merges new entries into existing configs (rules in MODDING_GUIDE §3).merge_config_defaults()is available inmodconfig.cand exported fromlibmodloader.so.- Code that has to catch the first scene on the fresh-install launch: see MODDING_GUIDE §1, "Don't rely on catching the first scene".
Full details are in CHANGELOG.md.
APK: AoQ-0.5.0-Patched.apk
SHA-256: 0e9382bc379798f1855a524a9f1892f194dcdc83ff1338ad984fe04c72eb408a
Signing certificate SHA-256: 1e08a903aef9c3a721510b64ec764d01d3d094eb954161b62544ea8f187b5953 (same as v1.3.0)
Version 1.3.0 - Tons of bug/crash fixes + Mod Performance and Log files in a new logs folder
Changelog
v1.3.0 — release hardening pass (2026-07-30)
Hopefull the final fix pass before public release: every known bug, plus a built-in crash reporter and mod performance log.
Added
- Crash reports with mod attribution (
modloader/crashtrace.c). Fatal
signals (SIGSEGV/SIGBUS/SIGILL/SIGFPE/SIGABRT) now write
/sdcard/DCIM/AoQMods/logs/crash-<time>.logwith the fault address, ARM
registers, a stack scan, and every code address resolved to its owning
module — mod.sofiles are named individually, andlibil2cpp.sooffsets
matchdump.csRVAs. Constructor crashes name the mod beingdlopened.
The system tombstone still runs afterwards. - Mod performance log (
/sdcard/DCIM/AoQMods/logs/modperf.log): each
mod's load/constructor time, plus frame-time stats (avg/max/slow-frame
count) every 30 s. Compare windows with a mod disabled to measure its
per-frame cost. - Run log (
/sdcard/DCIM/AoQMods/logs/latest.log): the complete logcat
of the last run — every tag from the game process, captured by a logcat
child that lives and dies with the game. Capped at ~8 MB (+ one rollover
file); the previous run is kept asprev.log. Users can attach it to bug
reports straight from the headset, no adb required.
Fixed — loader
scandirfailure paths no longer free an uninitialized pointer, and no
longer latch "loaded" with zero mods on unexpected errors (ENOENT now
retries like EACCES/EPERM; anything else logs once and retries).load_mods()reports success correctly (missingreturn 1was UB).- Mod copies to cache are checked and looped (
sendfileshort writes); a
full disk now logs an ENOSPC message and skips the mod instead of
dlopening a truncated file. Non-regular*.soentries are skipped, and
stale cache copies from previous runs are cleaned before loading. - The per-frame "Directories ready." logcat spam while the storage prompt was
pending is gone.
Fixed — hooking engine
- The trampoline page's instruction cache is now flushed after writing
(previously only the patch site was flushed — worked on some devices by
luck, crashed on others). orig_boundaries[]sized for 6 Thumb-16 prologue instructions (was 4 —
overflowed on 5-6 instruction prologues).- Two silent-corruption relocation bugs fixed: Thumb-16
ADRoperator
precedence, andADR.Wimmediate field assembly. INSTALL_HOOKnow logs loudly when a hook fails to install (missing
libil2cpp, bad RVA, engine error) instead of silently doing nothing.- Documented that hook installs are main-thread-only (the ptrace freeze is a
yama no-op on modern Android and patch writes are not atomic).
Fixed — aoqcore
aoq_is_master()called the wrong RVA (instancePlayer.get_IsMasterClient
as a zero-arg static — guaranteed crash). Now0x9A80A4, the real static
PhotonNetwork.get_IsMasterClient.aoq_late_init()only latches on full success: it verifies libil2cpp is
mapped, drops the dangerous plain-dlopenfallback (which could map a
second copy), and retries NULL TypeInfos instead of poisoning every helper
forever when called too early.- All per-frame helpers (transform setters, TMP setters,
aoq_photon_view/
aoq_is_mine,aoq_billboard) now gate onaoq_alive()— using a
destroyed object no longer throws a managed exception through the hook
frame (process abort). - Config getters return the fallback on type mismatch instead of 0;
aoq_tmp_createdestroys its orphan GameObject when AddComponent fails;
aoq_tap_holdno longer misreads a release-frame hold as a tap.
Fixed — mod manager
- Configs are saved atomically (write-to-temp + rename), and a config that
didn't fully load (over 32 entries / unparseable entries) refuses to save
instead of silently deleting the missing entries on the next +/- press.
Default-config extraction andaoqmm_ensure_configuse the same pattern. - cJSON has a nesting depth cap (64) and an iterative delete — a hostile or
corrupt shared config can no longer stack-overflow the game. - A scene change during the initial async bundle load no longer kills the
Mods button for the whole session: each scene retries a failed bundle
state, and still-pending async requests are re-adopted instead of
double-loaded. - Menu-scan polling backs off (10 → 300 frames) in scenes with no injectable
menu, ending the periodicFindObjectsOfType(Transform)GC hitches. - The
DisabledModsFolderconfig value is sanitized (no/,\,..) and
only committed if the directory could actually be created. aoqmm_registerescapes quotes/backslashes in metadata JSON.
Version 1.2.0 - Stability + New Library
Changelog
v1.2.0
Big reliability pass on the loader and in-game mod manager, plus a new shared
utility library for mod authors.
Fixed
- Mods now load on a fresh install without adb. Mods were loaded at native
library-load time — before Android grants the storage permission on first
launch — so nothing loaded and the mod list was empty. Loading is now deferred
and retried every frame until external storage is reachable (a couple seconds
in, right after you approve the storage prompt, still on the main menu). - Mods button disappearing after a level restart. Re-injection used a camera
rig handle captured once at startup; the rig is destroyed and recreated on a
scene reload, leaving a stale handle so the menu couldn't be found. It's now
refreshed every frame. The destroyed-button check also runs every frame
instead of every ~2 seconds, closing a use-after-free window that made the
button occasionally fail to come back. - Config menus stopped responding in long sessions. The button registry
leaked an entry on every list repopulation until it filled up; list rows also
leaked as hidden GameObjects. Rows are now unregistered and destroyed on
refresh. - Disabled mods no longer appear in the Configure list (they kept their
config files on disk, so the old filter still showed them). - The mod manager lists itself as "Mod Manager" instead of the raw
libaoqmodmanager.sofilename (it never wrote its own metadata). - Config files are read and written from a single location again —
/sdcard/DCIM/AoQMods/modconfigs/. A half-finished path migration had the
loader writing there while the manager read the oldAndroid/datapath, so
mod names and defaults never showed up. - App-data directories that don't exist yet on a fresh install are created
before first use (bundle extraction, temp mod copies) instead of failing
silently.
Added
shared/aoqcoreutility library for mod authors:photonView.IsMine
gating, destroyed-object checks (aoq_alive), a cached config reader, a
tap/hold input helper, TMP/transform helpers, and typed field access. See the
Modding Guide, section 15.- Each mod is copied to its own temp filename before loading, so crash
backtraces name the actual mod;dlopen/openfailures are now logged.
Patched APK note
- The distributed patched APK now sets
targetSdkVersion=29. At the game's
original target 30, Android 11+ enforces scoped storage and ignores
requestLegacyExternalStorage, which blocks the game from reading
/sdcard/DCIM/AoQMods. At 29 the single storage permission prompt is all a
user needs — no adb, no "All files access" toggle. If you build your own APK,
apply this (see README → Patch the APK, step 3b).
For mod authors
- Update your
Android.mkto buildshared/aoqcore/aoq.cand read config via
AoqCfgCache(Modding Guide §4/§15). - Always gate
Network*hooks withaoq_is_mine()and guard cached Unity
objects withaoq_alive()— these two rules prevent nearly every multiplayer
crash and wrong-player bug.
Version 1.1.1 - Permission Fix
The original mods folder was a subdirectory of the game's install folder, which some quest users did not have write permissions to. I've moved the mods folder to /sdcard/DCIM/ which should always have read and write permissions
v1.1.0 - Mod Manager Update
This version of the mod loader adds an integrated mod manager into the main game's UI. There's a new mods button which opens a mods menu for editing mod configs and enabling/disabling mods
v1.0.0 - Initial Release
v1.0.0 — Initial Release
This is the first public release of the Attack on Quest modloader framework, based on a modernized fork of jakibaki/beatsaber-hook rebuilt for NDK r26d and Android 12.
Included
- Pre-patched APK ready to sideload
Notes
- For installation instructions, see the README
- The mods folder (
/sdcard/Android/data/com.AoQ.AttackOnQuest/files/mods/) may need to be created manually before loading mods - If you wish to build from source or patch your own APK, see Steps 1–3 in the README
Bug Reports
If you encounter any bugs with the modloader itself, please open a GitHub issue so I can track and address them. I did not write the original modloader and am unaware of any bugs that transferred over to this project.