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.
101 lines
6.3 KiB
Markdown
101 lines
6.3 KiB
Markdown
# 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](https://codeberg.org/raincord/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.
|