diff --git a/src/lib/metro/filters.ts b/src/lib/metro/filters.ts index 55b903c..f2b6f29 100644 --- a/src/lib/metro/filters.ts +++ b/src/lib/metro/filters.ts @@ -1,4 +1,4 @@ -import { instead } from "@lib/api/patcher"; +import { after, instead } from "@lib/api/patcher"; export type MetroModules = { [id: string]: any; }; export type PropIntellisense

= Record & Record; @@ -17,21 +17,33 @@ const blacklist = (id: string) => Object.defineProperty(window.modules, id, { writable: true }); +const functionToString = Function.prototype.toString; + +for (const id in window.modules) { + const moduleDefinition = window.modules[id]; + + if (moduleDefinition.factory) { + after("factory", moduleDefinition, ({ 4: moduleObject }) => { + if (moduleObject.exports) onModuleRequire(moduleObject.exports); + }); + } +} + + // Blacklist any "bad-actor" modules, e.g. the dreaded null proxy, the window itself, or undefined modules for (const id in window.modules) { - const module = window.modules[id]?.publicModule?.exports; + const module = requireModule(id); - if (!module || module === window || module.proxygone === null) { + if (!module || module === window || module["Revenge EOL when?"] === null) { blacklist(id); continue; } } -const checked = new WeakSet(); -const onModuleCheck = (exports: any) => { - if (typeof exports !== "object" || checked.has(exports)) return; - checked.add(exports); +let patchedInspectSource = false; +function onModuleRequire(exports: any) { + // There are modules registering the same native component if (exports?.default?.name === "requireNativeComponent") { instead("default", exports, (args, orig) => { try { @@ -42,6 +54,7 @@ const onModuleCheck = (exports: any) => { }); } + // Hook DeveloperExperimentStore if (exports?.default?.constructor?.displayName === "DeveloperExperimentStore") { exports.default = new Proxy(exports.default, { get: (target, property, receiver) => { @@ -55,35 +68,32 @@ const onModuleCheck = (exports: any) => { } }); } -}; -function maybeInitializeModule(id: string) { - if (modules[id].isInitialized) return; + // Funny infinity recursion caused by a race condition + if (!patchedInspectSource && window["__core-js_shared__"]) { + const inspect = (f: Function) => typeof f === "function" && functionToString.apply(f, []); + window["__core-js_shared__"].inspectSource = inspect; + patchedInspectSource = true; + } +} + +function requireModule(id: string) { + if (modules[id].isInitialized) return __r(id); + + // Disable Internal Metro error reporting logic + const originalHandler = window.ErrorUtils.getGlobalHandler(); + window.ErrorUtils.setGlobalHandler(null); try { - // There's a dum Function.prototype.toString polyfill somewhere in Discord's codebase - const orig = Function.prototype.toString; - Object.defineProperty(Function.prototype, "toString", { - value: orig, - configurable: true, - writable: false - }); + var exports = __r(id); // metroRequire(id); + } catch { + var exports = undefined; + } - // Disable Internal Metro error reporting logic - const originalHandler = window.ErrorUtils.getGlobalHandler(); - window.ErrorUtils.setGlobalHandler(null); - - __r(id); // metroRequire(id); - - // Done initializing! Now, revert our hacks - window.ErrorUtils.setGlobalHandler(originalHandler); - Object.defineProperty(Function.prototype, "toString", { - value: orig, - configurable: true, - writable: true - }); - } catch { } + // Done initializing! Now, revert our hacks + window.ErrorUtils.setGlobalHandler(originalHandler); + return exports; } // Function to filter through modules @@ -91,25 +101,16 @@ const filterModules = (modules: MetroModules, single = false) => (filter: (m: an const found = []; for (const id in modules) { - const module = modules[id]?.publicModule?.exports; + const exports = requireModule(id); - maybeInitializeModule(id); - - if (!module) { - blacklist(id); - continue; + if (exports.default && exports.__esModule && filter(exports.default)) { + if (single) return exports.default; + found.push(exports.default); } - onModuleCheck(module); - - if (module.default && module.__esModule && filter(module.default)) { - if (single) return module.default; - found.push(module.default); - } - - if (filter(module)) { - if (single) return module; - else found.push(module); + if (filter(exports)) { + if (single) return exports; + else found.push(exports); } } diff --git a/src/lib/utils/logger.ts b/src/lib/utils/logger.ts index 6a0dbef..27fc005 100644 --- a/src/lib/utils/logger.ts +++ b/src/lib/utils/logger.ts @@ -1,4 +1,4 @@ -import { findByProps } from "@metro/filters"; +import { findByName } from "@metro/filters"; export type LoggerFunction = (...messages: any[]) => void; export interface Logger { @@ -11,5 +11,5 @@ export interface Logger { verbose: LoggerFunction; } -export const logModule = findByProps("setLogFn").default; +export const logModule = findByName("Logger"); export const logger: Logger = new logModule("Bunny"); diff --git a/src/lib/utils/onceDefined.ts b/src/lib/utils/onceDefined.ts new file mode 100644 index 0000000..4a9e587 --- /dev/null +++ b/src/lib/utils/onceDefined.ts @@ -0,0 +1,30 @@ +import { LiteralUnion } from "type-fest"; + +type KeyOfOrAny = P extends keyof T ? T[P] : any; + +export function hookDefineProperty< + T extends object, + P extends LiteralUnion +>(target: T, property: LiteralUnion, cb: (val: KeyOfOrAny) => KeyOfOrAny) { + const targetAsAny = target as any; + + if (property in target) { + return void cb(targetAsAny[property]); + } + + let value: any; + + Object.defineProperty(targetAsAny, property, { + get: () => value, + set(v) { + value = cb(v) ?? v; + }, + configurable: true, + enumerable: false + }); + + return () => { + delete targetAsAny[property]; + targetAsAny[property] = value; + }; +}