revenge-tweak/AUDIT.md
Ally Founder 0044c2c12a fix: replace speculative Bridgeless hook with RainTweak's verified approach
The RCTInstance/-_loadScriptFromSource: + fake-RCTSource hook from the
previous commit was a plausible guess from RN's public source, but
codeberg.org/raincord/RainTweak - an actively-shipping loader for a sibling
Revenge/Bunny fork - proves both the Bridgeless diagnosis correct (its %ctor
refuses to run below Discord v332, i.e. pre-Bridgeless) and shows a cleaner,
verified mechanism: hook RCTHost/-instance:didInitializeRuntime:, let
Discord's own bundle load untouched via %orig, then evaluate Revenge's
scripts directly against the live JSI runtime instead of faking another
bundle load.

- Add Headers/JSI.h + Sources/JSI.mm (ported from RainTweak's JSI.mm):
  evaluates NSData as JS or Hermes bytecode directly against a
  facebook::jsi::Runtime.
- Add Headers/RCTHost.h declaring the hook target's signature (RN ships no
  public header for this internal class).
- Vendor Headers/jsi/{jsi.h,jsi-inl.h,instrumentation.h} - Meta's MIT-licensed
  JSI headers, which Theos doesn't provide; RainTweak vendors the same files.
- Rewrite Tweak.x -> Tweak.xm (needs C++ for jsi::Runtime&): runRevengeInjection
  now takes a plain data/async execute callback, driven by either the new
  RCTHost+JSI hook or the legacy RCTCxxBridge hook (kept as a no-cost
  fallback), guarded by a didInject flag so only one path actually injects.
- Makefile: build .xm/.mm sources, add -std=c++17 CCFLAGS matching RainTweak.

Still unverified against a real device, but the mechanism itself is no longer
a guess - it's the one running in a comparable, currently-shipping mod.
2026-08-03 10:10:32 -04:00

6.3 KiB

iOS parity audit (2026-08-03)

Revenge dropped official iOS support; this repo was archived upstream in Nov 2024 (revenge-mod/revenge-tweak, last commit 35ad28b) while the Android loader (revenge-mod/revenge-xposed) kept shipping. This audit compares the two and fixes what could be fixed without a jailbroken device to test against.

Fixed in this fork

  • Sources/Sideloading.x: BunnyLog(...) referenced a macro that no longer exists (the project was renamed Bunny → Revenge; only RevengeLog is defined in Headers/Logger.h). This made the file fail to compile. Fixed by using RevengeLog.
  • Sources/LoaderConfig.m: -loadConfig and +defaultConfig were each defined twice in the same @implementation (leftover merge conflict). Removed the duplicates.
  • .github/workflows/deploy.yml: the CI patcher step pointed at Cristiandis/ipa-patcher release release-pyon, which 404s — that repo no longer exists. Repointed at the still-live fork bunny-mod/ipa-patcher (same tag, release build) and pinned it with a verified SHA-256 checksum. (These three match the fixes proposed in the never-merged upstream PR #8, which the sole maintainer confirmed worked but never landed before the repo was archived.)

The real blocker: this tweak's hook point is very likely dead

Sources/Tweak.x hooks RCTCxxBridge -executeApplicationScript:url:async: — the script-injection point for React Native's legacy bridge. Evidence points to Discord having moved off that architecture entirely:

  • revenge-bundle's package.json pins react-native: 0.81.4, which defaults to the New Architecture ("Bridgeless mode").
  • revenge-xposed (the actively-maintained Android loader) hooks com.facebook.react.runtime.ReactInstance$loadJSBundle$1 — the Bridgeless-mode class — not the legacy CatalystInstanceImpl, and its DiscordDevSupport.kt explicitly targets com.facebook.react.devsupport.BridgelessDevSupportManager.

Cross-platform RN apps built from one codebase almost always enable/disable the New Architecture together, so if Android Discord is Bridgeless, iOS Discord almost certainly is too — meaning RCTCxxBridge is likely never instantiated on current Discord iOS builds, and the tweak's only injection point silently never fires.

This is now confirmed, not just theorized. RainTweak is the iOS loader for raincord/rain, a sibling Revenge/Bunny-derived mod that is actively shipping (last commit 2026-07-17, vs. this repo's Nov 2024). Its %ctor explicitly checks RCTNewArchEnabled from Info.plist and refuses to run with "Please update Discord to v332 or above" if it's off — i.e. current Discord iOS is Bridgeless-only, exactly as the Android-side evidence predicted, and RainTweak has real users validating this on-device.

Fix applied, ported from RainTweak's verified mechanism (better than the RCTInstance -_loadScriptFromSource: + fake-RCTSource approach this fork originally tried — see below for why that was replaced):

  • %hook RCTHost / -instance:didInitializeRuntime: (Headers/RCTHost.h, Sources/Tweak.xm) — fires once the Bridgeless JS runtime is ready. Discord's own bundle is left completely untouched via %orig first; Revenge's own scripts (loader payload, downloaded bundle, theme, preloads) are then run directly against the live facebook::jsi::Runtime via [JSI evaluate:tag:runtime:] (Headers/JSI.h / Sources/JSI.mm, ported near-verbatim from RainTweak's JSI.mm) instead of being smuggled in as fake bundle loads.
  • This sidesteps the problem the first attempt ran into: RCTSource (what -_loadScriptFromSource: takes) has no public initializer, so faking one meant poking private ivars — fragile and, per RainTweak's actual production code, not even the path they use. Evaluating directly against the runtime via JSI is both simpler and how the proven-working reference implementation does it.
  • The legacy RCTCxxBridge -executeApplicationScript:... hook is kept as a no-cost fallback for any host still on the old bridge (Logos skips hooking classes that aren't present at runtime, and a didInject guard stops both hooks from double-firing if they somehow both resolve).
  • Headers/jsi/{jsi.h,jsi-inl.h,instrumentation.h} are vendored copies of Meta's JSI headers (MIT-licensed, same as RN itself) — Theos doesn't ship them, and RainTweak vendors the identical files for the same reason.

Still unverified against a real device or the current Discord binary — I don't have a jailbreak or Xcode/Theos here. But unlike the first attempt, this is no longer a guess extrapolated from RN's public source: the mechanism (RCTHost hook + JSI evaluation) is the exact one running in a comparable, currently-shipping mod. Confidence is meaningfully higher; it should still be tested before relying on it, since Discord's specific build could in principle differ.

Remaining feature gap vs. Android (not attempted here)

Beyond the injection hook, revenge-xposed has grown a much larger feature set that iOS has no equivalent of. Porting these is real native engineering work, not something to guess blind without a device to test each step against:

Feature Android iOS
Script injection RevengeScriptLoader + Bridgeless hook Fixed above (unverified)
Theme/font/sideloading themes/fonts/sysColors, resource-ID fix Theme.m/Fonts.x/Sideloading.x (present, untouched)
Native↔JS RPC bridge RevengeBridge — bidirectional calls smuggled through existing exposed RN native modules (RNSVGRenderableManager.getBBox, FileReaderModule.readAsDataURL) none
Plugin system External plugin loading + repos (PluginLoader, RepoStore, RepoInstall, Resolver, ...) none
Internal plugins Recovery mode, no-track, OTA-update prevention, example plugin none
Dev support LogBox + shake-to-recovery wired into RN's dev menu none
Caches/lifecycle management Caches.kt, LifecycleSupport.kt none

The bridge + plugin system is the biggest gap and the most architecturally novel part of the Android side — it's what everything else (internal plugins, dev support) is built on. Recommend treating it as its own follow-up project once the injection hook above is confirmed working on-device, rather than attempting it speculatively.