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.
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; onlyRevengeLogis defined inHeaders/Logger.h). This made the file fail to compile. Fixed by usingRevengeLog.Sources/LoaderConfig.m:-loadConfigand+defaultConfigwere each defined twice in the same@implementation(leftover merge conflict). Removed the duplicates..github/workflows/deploy.yml: the CI patcher step pointed atCristiandis/ipa-patcherreleaserelease-pyon, which 404s — that repo no longer exists. Repointed at the still-live forkbunny-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'spackage.jsonpinsreact-native: 0.81.4, which defaults to the New Architecture ("Bridgeless mode").revenge-xposed(the actively-maintained Android loader) hookscom.facebook.react.runtime.ReactInstance$loadJSBundle$1— the Bridgeless-mode class — not the legacyCatalystInstanceImpl, and itsDiscordDevSupport.ktexplicitly targetscom.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%origfirst; Revenge's own scripts (loader payload, downloaded bundle, theme, preloads) are then run directly against the livefacebook::jsi::Runtimevia[JSI evaluate:tag:runtime:](Headers/JSI.h/Sources/JSI.mm, ported near-verbatim from RainTweak'sJSI.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 adidInjectguard 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.