import { isProxy } from '@revenge-mod/utils/proxy' import { _executeSubscription } from './subscriptions/_internal' import type { Metro } from '#/metro' export const _bl = new Set() /** Initializing ID */ export let _mInitingId = -1 /** Uninitialized IDs */ export const _mUninited = new Set() /** Initialized IDs */ export const _mInited = new Set() export const _mMetadatas = new Map() export const _mPaths = new Map() export function patchMetroDefine(metroDefine: Metro.DefineFn) { return ((origFactory, id, deps) => { // deps won't be undefined last time I checked const metadata = [deps!] as typeof _mMetadatas extends Map ? V : never _mMetadatas.set(id, metadata) if (_bl.has(id)) { metroDefine(origFactory, id, deps) return } _mUninited.add(id) metroDefine( ((g, r, ipD, ipA, m, e, d) => { metadata[1] = m const prevIId = _mInitingId _mInitingId = id try { origFactory(g, r, ipD, ipA, m, e, d) if (_isExportsBad(m.exports)) _blacklist(id) // If exports isn't bad, we can put it in the list of initialized modules else _mInited.add(id) } finally { _mInitingId = prevIId _mUninited.delete(id) _executeSubscription(id, m.exports) } }) satisfies Metro.FactoryFn, id, deps, ) }) satisfies Metro.DefineFn } /** * Blacklists a module. * * Blacklisting modules prevents Revenge from doing the following things: * * - Iterating over the module during `find()`s * * @param id The module ID to blacklist * @internal */ export function _blacklist(id: Metro.ModuleID) { _bl.add(id) // Usually we'd also remove the module from _mInited too, // but we already have an else block in the patchMetroDefine function that does that for us. // _mInited.delete(id) // TODO(modules): caching } /** * Returns whether a particular export of a module is bad. * @param export The exports of the module * @returns Whether the module has bad exports * @internal */ export function _isExportBad(exp: Metro.ModuleExports) { return ( // Nullish? exp === undefined || exp === null || // Is it a proxy? isProxy(exp) ) } /** * Returns whether the module has bad exports. If it does, it should be blacklisted and never hooked into. * @param exports The exports of the module * @returns Whether the module has bad exports * @internal */ export function _isExportsBad(exports: Metro.ModuleExports) { return ( // Exports will always be an object, we are checking if the object is empty !Reflect.ownKeys(exports).length || // Circular reference exports === globalThis ) }