rawFind: never force-require modules, only inspect already-initialized ones
This commit is contained in:
parent
28b5b58fac
commit
a25ce4bfb0
3 changed files with 44 additions and 34 deletions
|
|
@ -2,6 +2,7 @@ import { logger } from "@vendetta";
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { patchQuestDockRender } from "./patches/questDockRender";
|
||||
import { patchQuestDockBase } from "./patches/questDockBase";
|
||||
import { patchQuestDockVisible } from "./patches/questDockVisible";
|
||||
import { patchMobileQuestDock } from "./patches/mobileQuestDock";
|
||||
import { patchGetQuestAsset } from "./patches/getQuestAsset";
|
||||
import { patchExpanded, patchEmpty } from "./patches/contentPatch";
|
||||
|
|
@ -37,6 +38,7 @@ function applyAll() {
|
|||
const patchers: Record<string, () => boolean> = {
|
||||
questDockRender: () => patchQuestDockRender(cleanups),
|
||||
questDockBase: () => patchQuestDockBase(cleanups),
|
||||
questDockVisible: () => patchQuestDockVisible(cleanups),
|
||||
mobileQuestDock: () => patchMobileQuestDock(cleanups),
|
||||
getQuestAsset: () => patchGetQuestAsset(cleanups),
|
||||
expanded: () => patchExpanded(cleanups),
|
||||
|
|
|
|||
20
plugins/server-drawer/src/patches/questDockVisible.ts
Normal file
20
plugins/server-drawer/src/patches/questDockVisible.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { rawFindByProps } from "@shared/lib/rawFind";
|
||||
|
||||
// A third gate alongside useIsMobileQuestDockRendered/useIsMobileQuestDockRenderedBase, found by
|
||||
// reading the component that actually consumes all three together
|
||||
// (QuestDockWithQuestContext, confirmed against decompiled current-build Discord source): even
|
||||
// with the quest data faked and the "rendered" hooks forced true, this one still decides whether
|
||||
// the dock is visible right now based on navigation route and channel-focus state - so without
|
||||
// forcing it too, the drawer could still never show up depending on what screen you're on.
|
||||
export function patchQuestDockVisible(cleanups: (() => void)[]): boolean {
|
||||
const mod = rawFindByProps("useIsMobileQuestDockVisibleToUser");
|
||||
if (!mod?.useIsMobileQuestDockVisibleToUser) return false;
|
||||
|
||||
const orig = mod.useIsMobileQuestDockVisibleToUser;
|
||||
mod.useIsMobileQuestDockVisibleToUser = function (...args: any[]) {
|
||||
orig.apply(this, args);
|
||||
return true;
|
||||
};
|
||||
cleanups.push(() => { mod.useIsMobileQuestDockVisibleToUser = orig; });
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3,47 +3,35 @@
|
|||
// 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.
|
||||
// NOT_FOUND flag). That makes retry loops 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.
|
||||
//
|
||||
// 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.
|
||||
// This walks window.modules directly to bypass that cache - but critically, it never forces a
|
||||
// module to initialize (never calls window.__r on something that hasn't run yet). An earlier
|
||||
// version of this file did force-require everything on every scan, which is genuinely dangerous:
|
||||
// Metro only ever runs a module's factory once and caches whatever it produced forever, so
|
||||
// force-evaluating a module before whatever it depends on on is actually ready can permanently
|
||||
// wedge it into a broken state for the rest of the session - no amount of retrying fixes that,
|
||||
// since the factory never runs again. This only inspects modules Metro's own isInitialized flag
|
||||
// says have ALREADY been required by something else, which is entirely passive and safe to poll
|
||||
// repeatedly - it just waits for Discord's own code to naturally reach the module we want.
|
||||
declare const window: any;
|
||||
|
||||
const exportsCache = new Map<number, any>();
|
||||
|
||||
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<T = any>(predicate: (exports: any) => boolean): T | undefined {
|
||||
scanNewModules();
|
||||
const modules = window?.modules;
|
||||
if (!modules) return undefined;
|
||||
|
||||
for (const id in modules) {
|
||||
const def = modules[id];
|
||||
if (!def?.isInitialized) continue;
|
||||
|
||||
const exports = def.publicModule?.exports;
|
||||
if (!exports) continue;
|
||||
|
||||
for (const exports of exportsCache.values()) {
|
||||
try {
|
||||
if (predicate(exports)) return exports;
|
||||
if (exports?.default != null && predicate(exports.default)) return exports.default;
|
||||
if (exports.default != null && predicate(exports.default)) return exports.default;
|
||||
} catch {
|
||||
// A predicate throwing on one module's shape shouldn't stop the scan.
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue