fix: support versions with New Architecture (#193)

This commit is contained in:
Palm 2025-07-21 20:30:39 +07:00 • committed by GitHub
commit 1b96867ac7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 144 additions and 36 deletions

View file

@ -46,6 +46,7 @@ const config = {
".png": "dataurl"
},
define: {
window: "globalThis",
__DEV__: dev ?? JSON.stringify(releaseBranch !== "main")
},
inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"],

View file

@ -1,10 +1,10 @@
import type { Metro } from "@metro/types";
import { version } from "bunny-build-info";
const { instead } = require("spitroast");
// @ts-ignore - shut up fr
// @ts-ignore - window is defined later in the bundle, so we assign it early
globalThis.window = globalThis;
async function initializeBunny() {
async function initializeRevenge() {
try {
// Make 'freeze' and 'seal' do nothing
Object.freeze = Object.seal = Object;
@ -18,51 +18,80 @@ async function initializeBunny() {
console.log(stack ?? e?.toString?.() ?? e);
alert([
"Failed to load Revenge!\n",
`Build Number: ${ClientInfoManager.Build}`,
`Build Number: ${ClientInfoManager.getConstants().Build}`,
`Revenge: ${version}`,
stack || e?.toString?.(),
].join("\n"));
}
}
// @ts-ignore
if (typeof globalThis.__r !== "undefined") {
initializeBunny();
} else {
// We hold calls from the native side
function onceIndexRequired(originalRequire: any) {
const batchedBridge = window.__fbBatchedBridge;
if (typeof window.__r === "undefined") {
// Used for storing the current require function for the global.__r getter defined below
var _requireFunc: any;
const callQueue = new Array<any>;
const unpatchHook = instead("callFunctionReturnFlushedQueue", batchedBridge, (args: any, orig: any) => {
if (args[0] === "AppRegistry" || !batchedBridge.getCallableModule(args[0])) {
callQueue.push(args);
return batchedBridge.flushedQueue();
// Calls from the native side are deferred until the index.ts(x) is loaded
// Revenge delays the execution of index.ts(x) because Revenge's initialization is asynchronous
interface DeferredQueue {
object: any;
original: any;
method: string;
args: any[];
}
const deferredCalls: Array<DeferredQueue> = [];
const deferMethodExecution = (object: any, method: string, condition?: (...args: any[]) => boolean) => {
const originalMethod = object[method];
object[method] = (...args: any[]) => {
if (condition && !condition(...args)) {
originalMethod(...args);
return;
}
return orig.apply(batchedBridge, args);
});
deferredCalls.push({ object, original: originalMethod, method, args });
};
}
const resumeDeferredAndRestore = () => {
for (const { object, method, args, original } of deferredCalls) {
object[method] = original;
object[method](...args);
}
deferredCalls.length = 0;
}
const onceIndexRequired = (originalRequire: Metro.RequireFn) => {
// We hold calls from the native side
if (window.__fbBatchedBridge) {
deferMethodExecution(window.__fbBatchedBridge, "callFunctionReturnFlushedQueue", args => {
// If the call is to AppRegistry, we want to defer it because it is not yet registered (Revenge delays it)
// Same goes to the non-callable modules, which are not registered yet, so we ensure that only registered ones can get through
return args[0] !== "AppRegistry" && window.__fbBatchedBridge.getCallableModule(args[0]);
});
}
// Introduced since RN New Architecture
if (window.RN$AppRegistry) {
deferMethodExecution(window.RN$AppRegistry, "runApplication");
}
const startDiscord = async () => {
await initializeBunny();
unpatchHook();
await initializeRevenge();
originalRequire(0);
callQueue.forEach(arg =>
batchedBridge.getCallableModule(arg[0])
&& batchedBridge.__callFunction(...arg));
resumeDeferredAndRestore();
};
startDiscord();
}
var _requireFunc: any; // We can't set properties to 'this' during __r set for some reason
Object.defineProperties(globalThis, {
__r: {
configurable: true,
get: () => _requireFunc,
set(v) {
// _requireFunc is required here, because using 'this' here errors for some unknown reason
_requireFunc = function patchedRequire(a: number) {
// Initializing index.ts(x)
if (a === 0) {
@ -85,4 +114,8 @@ if (typeof globalThis.__r !== "undefined") {
set(v) { this.value = v; }
}
});
}
} else {
// It is too late to late to hook __r, so we just initialize Revenge here
// Likely because of using the legacy loader (from Vendetta)
initializeRevenge();
}

1
src/global.d.ts vendored
View file

@ -6,6 +6,7 @@ declare global {
var globalEvalWithSourceUrl: (script: string, sourceURL: string) => any;
var nativePerformanceNow: typeof performance.now;
var nativeModuleProxy: Record<string, any>;
var __turboModuleProxy: (name: string) => any;
interface Window {
[key: string]: any;

View file

@ -113,8 +113,8 @@ export function getDebugInfo() {
}
},
discord: {
version: NativeClientInfoModule.Version,
build: NativeClientInfoModule.Build,
version: NativeClientInfoModule.getConstants().Version,
build: NativeClientInfoModule.getConstants().Build,
},
react: {
version: React.version,

View file

@ -2,10 +2,34 @@ import { RNModules } from "./types";
const nmp = window.nativeModuleProxy;
export const NativeCacheModule = (nmp.NativeCacheModule ?? nmp.MMKVManager) as RNModules.MMKVManager;
export const NativeFileModule = (nmp.NativeFileModule ?? nmp.RTNFileManager ?? nmp.DCDFileManager) as RNModules.FileManager;
export const NativeClientInfoModule = nmp.NativeClientInfoModule ?? nmp.RTNClientInfoManager ?? nmp.InfoDictionaryManager;
export const NativeDeviceModule = nmp.NativeDeviceModule ?? nmp.RTNDeviceManager ?? nmp.DCDDeviceManager;
export const NativeThemeModule = nmp.NativeThemeModule ?? nmp.RTNThemeManager ?? nmp.DCDTheme;
function getNativeModule<T = any>(...names: string[]): T | undefined {
for (const name of names) {
if (globalThis.__turboModuleProxy) {
const module = globalThis.__turboModuleProxy(name);
if (module) return module as T;
}
export const { BundleUpdaterManager } = nmp;
if (nmp[name]) return nmp[name] as T;
}
return undefined;
}
export const NativeCacheModule = getNativeModule<RNModules.MMKVManager>(
"NativeCacheModule", "MMKVManager"
)!;
export const NativeFileModule = getNativeModule<RNModules.FileManager>(
"NativeFileModule", "RTNFileManager", "DCDFileManager"
)!;
export const NativeClientInfoModule = getNativeModule<RNModules.ClientInfoModule>(
"NativeClientInfoModule", "RTNClientInfoManager", "InfoDictionaryManager"
)!;
export const NativeDeviceModule = getNativeModule(
"NativeDeviceModule", "RTNDeviceManager", "DCDDeviceManager"
)!;
export const NativeThemeModule = getNativeModule(
"NativeThemeModule", "RTNThemeManager", "DCDTheme"
)!;
export const BundleUpdaterManager = getNativeModule(
"BundleUpdaterManager"
)!;

View file

@ -86,4 +86,53 @@ export namespace RNModules {
*/
DocumentsDirPath: string;
}
export interface ClientInfoModule {
getConstants(): {
/**
* Sentry ingestion DSN URL for alpha/beta builds
*/
SentryAlphaBetaDsn: string
/**
* Sentry ingestion DSN URL for staff builds (?)
*/
SentryStaffDsn: string
/**
* Sentry ingestion DSN URL for stable builds
*/
SentryDsn: string
DeviceVendorID: string
Manifest: string
/**
* Version code
*
* Follows the format of `{MINOR}{CHANNEL}{PATCH}` for `{MINOR}.{PATCH} ({CHANNEL})`
* - `248200` for `248.0 (alpha)`
* - `247105` for `247.5 (beta)`
* - `246011` for `246.11 (stable)`
*/
Build: string
/**
* Version string
*
* Eg. `248.0`
*/
Version: string
/**
* Release channel
*/
ReleaseChannel: string
/**
* Matches `Version`
*/
OTABuild: string
/**
* Identifier for the installed client
*
* - **Android**: Package name
* - **iOS**: Bundle ID
*/
Identifier: string
}
}
}

View file

@ -18,7 +18,7 @@ export const getMetroCache = () => _metroCache;
function buildInitCache() {
const cache = {
_v: CACHE_VERSION,
_buildNumber: NativeClientInfoModule.Build as number,
_buildNumber: NativeClientInfoModule.getConstants().Build,
_modulesCount: Object.keys(window.modules).length,
flagsIndex: {} as Record<string, number>,
findIndex: {} as Record<string, ModulesMap | undefined>,
@ -48,7 +48,7 @@ export async function initMetroCache() {
_metroCache = null!;
throw "cache invalidated; cache version outdated";
}
if (_metroCache._buildNumber !== NativeClientInfoModule.Build) {
if (_metroCache._buildNumber !== NativeClientInfoModule.getConstants().Build) {
_metroCache = null!;
throw "cache invalidated; version mismatch";
}