// Revenge's own metro finders (findByProps/findByName/findByTypeName/findByStoreName, and even // the bare `find`) cache their result per search - including a NEGATIVE result. Once a search // comes up empty, it's marked "not found" and every later call with the same search never // rescans window.modules again, even after the real module registers (confirmed by reading // Revenge's own source: metro/internals/modules.ts's getModules() returns early on a cached // NOT_FOUND flag, before ever reaching the loop that walks currently-registered modules). That // makes retry loops (this repo's waitFor/lazy helpers) pointless for any search whose first // attempt happens to run before the target is registered - every retry after that first miss // hits the same poisoned cache instead of actually looking again. This walks window.modules // directly, bypassing that cache entirely, for use specifically inside retry loops. // // The module-exports cache below is shared across every call (not per-predicate) and only ever // grows - each call only evaluates modules it hasn't seen before, reusing everything already // pulled. Requiring every not-yet-loaded module in the bundle is inherently a one-time cost (the // exact same cost Revenge's own finders pay on their first, uncached search), but paying it once // and reusing the result is very different from paying it on every 200ms retry tick across // several different patchers, which is what calling window.__r fresh every time would do. declare const window: any; const exportsCache = new Map(); function scanNewModules(): void { const modules = window?.modules; if (!modules) return; for (const key in modules) { const id = Number(key); if (exportsCache.has(id)) continue; try { const exports = window.__r(id); if (exports) exportsCache.set(id, exports); } catch { // Some modules throw when required this early/out of order - skip and try again // next call, once whatever they depend on has loaded. } } } export function rawFind(predicate: (exports: any) => boolean): T | undefined { scanNewModules(); for (const exports of exportsCache.values()) { try { if (predicate(exports)) return exports; if (exports?.default != null && predicate(exports.default)) return exports.default; } catch { // A predicate throwing on one module's shape shouldn't stop the scan. } } return undefined; } export function rawFindByTypeName(name: string): T | undefined { return rawFind((m) => m?.name === name || m?.displayName === name || m?.type?.name === name || m?.type?.displayName === name); } export function rawFindByName(name: string): T | undefined { return rawFind((m) => m?.name === name || m?.displayName === name); } export function rawFindByProps(...props: string[]): T | undefined { return rawFind((m) => props.every((p) => m?.[p] !== undefined)); } // Matches Revenge's own byStoreName filter (metro/filters.ts): a zero-arg getName() returning // the store's name. export function rawFindByStoreName(name: string): T | undefined { return rawFind((m) => typeof m?.getName === "function" && m.getName.length === 0 && m.getName() === name); }