105 lines
3 KiB
TypeScript
105 lines
3 KiB
TypeScript
import { isProxy } from '@revenge-mod/utils/proxy'
|
|
import { _executeSubscription } from './subscriptions/_internal'
|
|
|
|
import type { Metro } from '#/metro'
|
|
|
|
export const _bl = new Set<Metro.ModuleID>()
|
|
|
|
/** Initializing ID */
|
|
export let _mInitingId = -1
|
|
/** Uninitialized IDs */
|
|
export const _mUninited = new Set<Metro.ModuleID>()
|
|
/** Initialized IDs */
|
|
export const _mInited = new Set<Metro.ModuleID>()
|
|
|
|
export const _mMetadatas = new Map<Metro.ModuleID, [deps: Metro.DependencyMap, module?: Metro.Module]>()
|
|
export const _mPaths = new Map<string, Metro.ModuleID>()
|
|
|
|
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<any, infer V> ? 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
|
|
)
|
|
}
|