Skip to content

Releases: Treyo1928/AoQ-Modloader

Version 1.4.0 - Early mod loading + auto-updating configs

Choose a tag to compare

@Treyo1928 Treyo1928 released this 11 Oct 21:54

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

  • EarlyModLoad setting (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.keystore in 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 new libmodloader.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 in modconfig.c and exported from libmodloader.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

Choose a tag to compare

@Treyo1928 Treyo1928 released this 31 Jul 03:49

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>.log with the fault address, ARM
    registers, a stack scan, and every code address resolved to its owning
    module — mod .so files are named individually, and libil2cpp.so offsets
    match dump.cs RVAs. Constructor crashes name the mod being dlopened.
    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 as prev.log. Users can attach it to bug
    reports straight from the headset, no adb required.

Fixed — loader

  • scandir failure 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 (missing return 1 was UB).
  • Mod copies to cache are checked and looped (sendfile short writes); a
    full disk now logs an ENOSPC message and skips the mod instead of
    dlopening a truncated file. Non-regular *.so entries 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 ADR operator
    precedence, and ADR.W immediate field assembly.
  • INSTALL_HOOK now 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 (instance Player.get_IsMasterClient
    as a zero-arg static — guaranteed crash). Now 0x9A80A4, the real static
    PhotonNetwork.get_IsMasterClient.
  • aoq_late_init() only latches on full success: it verifies libil2cpp is
    mapped, drops the dangerous plain-dlopen fallback (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 on aoq_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_create destroys its orphan GameObject when AddComponent fails;
    aoq_tap_hold no 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 and aoqmm_ensure_config use 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 periodic FindObjectsOfType(Transform) GC hitches.
  • The DisabledModsFolder config value is sanitized (no /, \, ..) and
    only committed if the directory could actually be created.
  • aoqmm_register escapes quotes/backslashes in metadata JSON.

Version 1.2.0 - Stability + New Library

Choose a tag to compare

@Treyo1928 Treyo1928 released this 13 Jul 21:37
21b9c74

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.so filename (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 old Android/data path, 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/aoqcore utility 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/open failures 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.mk to build shared/aoqcore/aoq.c and read config via
    AoqCfgCache (Modding Guide §4/§15).
  • Always gate Network* hooks with aoq_is_mine() and guard cached Unity
    objects with aoq_alive() — these two rules prevent nearly every multiplayer
    crash and wrong-player bug.

Version 1.1.1 - Permission Fix

Choose a tag to compare

@Treyo1928 Treyo1928 released this 23 Mar 04:34

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

Choose a tag to compare

@Treyo1928 Treyo1928 released this 23 Mar 01:30

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

Choose a tag to compare

@Treyo1928 Treyo1928 released this 19 Mar 10:55

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.