From 9258695ec735d271e8da29d582662b13ab5cc733 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 12 Aug 2026 01:12:14 +0700 Subject: [PATCH] feat(lib/modules/finders)!: initialized boolean for filter predicates + remove `FilterFlag` and weird assumptions + fix partial matches causing modules skip in lookup --- lib/discord/src/flux/stores.ts | 34 ++--- lib/modules/src/finders/_internal.ts | 40 +---- lib/modules/src/finders/filters/composite.ts | 148 +++++++++---------- lib/modules/src/finders/filters/constants.ts | 31 +--- lib/modules/src/finders/filters/dynamic.ts | 4 +- lib/modules/src/finders/filters/index.ts | 7 +- lib/modules/src/finders/filters/utils.ts | 72 +++------ lib/modules/src/finders/lookup.ts | 23 ++- lib/utils/src/discord.ts | 7 +- 9 files changed, 145 insertions(+), 221 deletions(-) diff --git a/lib/discord/src/flux/stores.ts b/lib/discord/src/flux/stores.ts index 00eb471..3150fbe 100644 --- a/lib/discord/src/flux/stores.ts +++ b/lib/discord/src/flux/stores.ts @@ -17,10 +17,7 @@ import { Uncached, } from '../../../modules/src/caches' import { FilterResultFlags } from '../../../modules/src/finders/_internal' -import { - FilterFlag, - FilterScopes, -} from '../../../modules/src/finders/filters/constants' +import { FilterScopes } from '../../../modules/src/finders/filters/constants' import type { Filter, FilterGenerator, @@ -91,7 +88,6 @@ const withLeadingFluxStoreDeps = withDependencies( export type WithStore = FilterGenerator< () => Filter<{ Result: DiscordModules.Flux.Store - RequiresExports: boolean Scopes: [ typeof FilterScopes.Uninitialized, typeof FilterScopes.Initialized, @@ -103,27 +99,21 @@ export type WithStore = FilterGenerator< * A dynamic filter that matches all Flux stores. */ export const withStore = createFilterGenerator( - (_, id, exports) => { - if (exports) return Boolean(exports._dispatchToken) - else { - if (!withLeadingFluxStoreDeps(id)) return false - const deps = getModuleDependencies(id)! - return deps[deps.length - 1] === ImportTrackerModuleId - } + (_, id, exports, initialized) => { + if (initialized) return Boolean(exports?._dispatchToken) + + if (!withLeadingFluxStoreDeps(id)) return false + const deps = getModuleDependencies(id)! + return deps[deps.length - 1] === ImportTrackerModuleId }, () => 'revenge.discord.store', - FilterFlag.Dynamic, FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithStore export type WithStoreName = FilterGenerator< (name: string) => Filter<{ Result: DiscordModules.Flux.Store - RequiresExports: true - Scopes: [ - typeof FilterScopes.Uninitialized, - typeof FilterScopes.Initialized, - ] + Scopes: [typeof FilterScopes.Initialized] }> > @@ -132,10 +122,12 @@ export type WithStoreName = FilterGenerator< */ export const withStoreName = createFilterGenerator( ([name], _, exports) => - exports?.getName?.length === 0 && exports.getName() === name, + typeof exports?.getName === 'function' && + exports.getName.length === 0 && + exports.getName() === name, ([name]) => `revenge.discord.storeName(${name})`, - FilterFlag.RequiresExports, - FilterScopes.Uninitialized | FilterScopes.Initialized, + // Uninitialized to make sure cached results resolve immediately + FilterScopes.Initialized | FilterScopes.Uninitialized, ) as WithStoreName /// STORE CACHING diff --git a/lib/modules/src/finders/_internal.ts b/lib/modules/src/finders/_internal.ts index 6266d69..88187d4 100644 --- a/lib/modules/src/finders/_internal.ts +++ b/lib/modules/src/finders/_internal.ts @@ -2,7 +2,7 @@ import { getCurrentStack } from '@revenge-mod/utils/error' import { cacheFilterResultForId } from '../caches' import { mInitialized } from '../metro/patches' import { metroRequire } from '../metro/runtime' -import { FilterFlag } from './filters' +import { isModuleInitialized } from '../metro/utils' import type { If } from '@revenge-mod/utils/types' import type { Metro } from '../types' import type { Filter } from './filters' @@ -72,34 +72,14 @@ const noDefaultExportsCache = new Set() // Currently, we only have options that are relevant for checking exports // If we add more options later on, do NOT forget about // adding them here, and passing them in the lookup* functions -export function runFilter( - filter: Filter<{ Result: any; RequiresExports: false; Scopes: any[] }>, - id: Metro.ModuleID, -): FilterResultFlag | undefined - -export function runFilter( - filter: Filter, - id: Metro.ModuleID, - exports: Metro.ModuleExports, - options?: RunFilterOptions, -): FilterResultFlag | undefined - export function runFilter( filter: Filter, id: Metro.ModuleID, exports?: Metro.ModuleExports, options?: RunFilterOptions, ): FilterResultFlag | undefined { - if (exports === undefined) { - if (filter.flags & FilterFlag.RequiresExports) return - - const filter_ = filter as Filter<{ - Result: any - RequiresExports: false - Scopes: any[] - }> - - if (filter_(id)) { + if (!isModuleInitialized(id)) { + if (filter(id, undefined, false)) { if (options?.initialize ?? true) { const module = metroRequire(id) // Check if the required module is not blacklisted @@ -118,13 +98,7 @@ export function runFilter( return } - const filter_ = filter as Filter<{ - Result: any - RequiresExports: true - Scopes: any[] - }> - - if (filter_(id, exports)) + if (filter(id, exports, true)) return cacheFilterResultForId( filter.key, id, @@ -133,13 +107,15 @@ export function runFilter( if (options?.skipDefault) return if (noDefaultExportsCache.has(id)) return - if (!('default' in exports)) { + + const type = typeof exports + if (type !== 'object' || exports === null || !('default' in exports)) { noDefaultExportsCache.add(id) return } const { default: defaultExport } = exports - if (filter_(id, defaultExport)) + if (filter(id, defaultExport, true)) return cacheFilterResultForId(filter.key, id, FilterResultFlags.Default) } diff --git a/lib/modules/src/finders/filters/composite.ts b/lib/modules/src/finders/filters/composite.ts index 6bfe13c..571da96 100644 --- a/lib/modules/src/finders/filters/composite.ts +++ b/lib/modules/src/finders/filters/composite.ts @@ -1,4 +1,4 @@ -import { FilterFlag } from './constants' +import { FilterScopes } from '.' import { createFilterGenerator } from './utils' import type { Metro } from '@revenge-mod/modules/types' import type { @@ -6,33 +6,48 @@ import type { FilterBase, FilterGenerator, FilterInfoOf, + FilterScopeValue, MergeFilterInfo, UnionFilterInfo, } from '.' +/** {@link FilterScopes.All} sets no predicate assumptions. Only {@link FilterScopes.Uninitialized} implies exportless. */ +const canFilterUninitialized = (scopes: FilterScopeValue) => + Boolean(scopes & FilterScopes.Uninitialized) + +/** + * If both or neither can filter uninitialized, positional order is preserved. + * + * If only one can, the one that can will be used as a prefilter for uninitialized modules, + * and the other will only run on initialized modules. + */ const compositeHandler = any>( - sameHandler: G, - dynamicHandler: G, + directHandler: G, + prefilteredHandler: G, ) => - ((a, b) => - a.flags === b.flags - ? sameHandler(a, b) - : a.flags === FilterFlag.RequiresExports - ? dynamicHandler(a, b) - : dynamicHandler(b, a)) as G + ((a, b) => { + const ea = canFilterUninitialized(a.scopes) + return ea === canFilterUninitialized(b.scopes) + ? directHandler(a, b) + : ea + ? prefilteredHandler(b, a) + : prefilteredHandler(a, b) + }) as G const compositeArrayHandler = < G extends (args: [a: FilterBase, b: FilterBase]) => any, >( - sameHandler: G, - dynamicHandler: G, + directHandler: G, + prefilteredHandler: G, ) => - (([a, b]) => - a.flags === b.flags - ? sameHandler([a, b]) - : a.flags === FilterFlag.RequiresExports - ? dynamicHandler([a, b]) - : dynamicHandler([b, a])) as G + (([a, b]) => { + const ea = canFilterUninitialized(a.scopes) + return ea === canFilterUninitialized(b.scopes) + ? directHandler([a, b]) + : ea + ? prefilteredHandler([b, a]) + : prefilteredHandler([a, b]) + }) as G export type And = FilterGenerator< ( @@ -46,49 +61,49 @@ const andKeyGenerator = ([a, b]: Parameters) => const andScopesGenerator = ([a, b]: Parameters) => a.scopes | b.scopes -const sameFlagsAnd = createFilterGenerator( - ([a, b], id, exports) => a(id, exports) && b(id, exports), +const directAnd = createFilterGenerator( + ([a, b], id, exports, initialized) => + a(id, exports, initialized) && b(id, exports, initialized), andKeyGenerator, - ([a]) => a.flags, andScopesGenerator, ) as And -const andFallbackCache = new WeakMap>() +const andPrefilterCache = new WeakMap>() -const dynamicFlagsAnd = createFilterGenerator( - ([filter, fallbackFilter], id, exports) => { - if (exports) { - if (filter(id, exports)) { - // Avoid running the fallback filter if we already know the first one passed - const cache = andFallbackCache.get(fallbackFilter) +const prefilteredAnd = createFilterGenerator( + ([filter, prefilter], id, exports, initialized) => { + if (initialized) { + if (filter(id, exports, true)) { + // Avoid running the prefilter again if we already know it passed + const cache = andPrefilterCache.get(prefilter) return ( // biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized - (cache && cache.has(id)) || fallbackFilter(id, exports) + (cache && cache.has(id)) || prefilter(id, exports, true) ) } return false } - const result = fallbackFilter(id) + const result = prefilter(id, undefined, false) if (result) { - // Cache fallback hits to avoid calling the fallback filter again - // Fallback filters are usually more expensive - let set = andFallbackCache.get(fallbackFilter) - if (!set) andFallbackCache.set(fallbackFilter, (set = new Set())) + // Cache prefilter hits to avoid calling the prefilter again + // Prefilters are usually more expensive + let set = andPrefilterCache.get(prefilter) + if (!set) andPrefilterCache.set(prefilter, (set = new Set())) set.add(id) } return result }, andKeyGenerator, - FilterFlag.Dynamic, andScopesGenerator, ) as And /** * Combines two filters into one, returning true if **every** filter matches. * - * If each filter has different flags, + * If only one of the filters can run on uninitialized modules ({@link FilterScopes.Uninitialized}), + * it is used as the prefilter for uninitialized modules, and the other only runs once a candidate is initialized. * * @param filters The filters to combine. * @@ -111,23 +126,13 @@ const dynamicFlagsAnd = createFilterGenerator( * ) * ``` */ -export const and = Object.assign( - compositeHandler(sameFlagsAnd, dynamicFlagsAnd), - { - keyFor: compositeArrayHandler( - sameFlagsAnd.keyFor, - dynamicFlagsAnd.keyFor, - ), - flagsFor: compositeArrayHandler( - sameFlagsAnd.flagsFor, - dynamicFlagsAnd.flagsFor, - ), - defaultScopesFor: compositeArrayHandler( - sameFlagsAnd.defaultScopesFor, - dynamicFlagsAnd.defaultScopesFor, - ), - }, -) satisfies And +export const and = Object.assign(compositeHandler(directAnd, prefilteredAnd), { + keyFor: compositeArrayHandler(directAnd.keyFor, prefilteredAnd.keyFor), + defaultScopesFor: compositeArrayHandler( + directAnd.defaultScopesFor, + prefilteredAnd.defaultScopesFor, + ), +}) satisfies And export type Or = FilterGenerator< ( @@ -141,21 +146,21 @@ const orKeyGenerator = ([a, b]: Parameters) => const orScopesGenerator = ([a, b]: Parameters) => a.scopes | b.scopes -const sameFlagOr = createFilterGenerator( - ([a, b], id, exports) => a(id, exports) || b(id, exports), +const directOr = createFilterGenerator( + ([a, b], id, exports, initialized) => + a(id, exports, initialized) || b(id, exports, initialized), orKeyGenerator, - ([a]) => a.flags, orScopesGenerator, ) as Or -const dynamicFlagOr = createFilterGenerator( - ([filter, fallbackFilter], id, exports) => { - // TODO(PalmDevs): Potential optimization: Add fallback cache here too? - if (exports) return filter(id, exports) || fallbackFilter(id, exports) - return fallbackFilter(id) +const prefilteredOr = createFilterGenerator( + ([filter, prefilter], id, exports, initialized) => { + // TODO(PalmDevs): Potential optimization: Add prefilter cache here too? + if (initialized) + return filter(id, exports, true) || prefilter(id, exports, true) + return prefilter(id, undefined, false) }, orKeyGenerator, - FilterFlag.Dynamic, orScopesGenerator, ) as Or @@ -183,17 +188,10 @@ const dynamicFlagOr = createFilterGenerator( * ) * ``` */ -export const or = Object.assign( - compositeHandler(sameFlagOr, dynamicFlagOr), - { - keyFor: compositeArrayHandler(sameFlagOr.keyFor, dynamicFlagOr.keyFor), - flagsFor: compositeArrayHandler( - sameFlagOr.flagsFor, - dynamicFlagOr.flagsFor, - ), - defaultScopesFor: compositeArrayHandler( - sameFlagOr.defaultScopesFor, - dynamicFlagOr.defaultScopesFor, - ), - }, -) satisfies Or +export const or = Object.assign(compositeHandler(directOr, prefilteredOr), { + keyFor: compositeArrayHandler(directOr.keyFor, prefilteredOr.keyFor), + defaultScopesFor: compositeArrayHandler( + directOr.defaultScopesFor, + prefilteredOr.defaultScopesFor, + ), +}) satisfies Or diff --git a/lib/modules/src/finders/filters/constants.ts b/lib/modules/src/finders/filters/constants.ts index 5871853..571dc71 100644 --- a/lib/modules/src/finders/filters/constants.ts +++ b/lib/modules/src/finders/filters/constants.ts @@ -1,21 +1,3 @@ -export const FilterFlag = { - /** - * This filter works with and without module exports. - * Allowing for both initialized and uninitialized modules to be matched. - */ - Dynamic: 0, - /** - * This filter requires module exports to work. - * Only initialized modules will be matched. - */ - RequiresExports: 1, -} as const - -/** - * @see {@link FilterFlag} - */ -export type FilterFlag = number - /** * Scopes to limit filters to certain module states. */ @@ -23,10 +5,16 @@ export const FilterScopes = { /** * Include all modules (both initialized and uninitialized, including blacklisted). * This overrides {@link FilterScopes.Uninitialized} and {@link FilterScopes.Initialized}. + * + * **Filter generators generally don't need this scope.** + * + * When combining multiple filters with composite filters, the {@link FilterScopes.All} scope doesn't set assumptions for the filter predicate. + * It only decides which modules to run the predicate against. + * **Filter generators must include {@link FilterScopes.Uninitialized} and/or {@link FilterScopes.Initialized} as well.** */ All: 1, /** - * Include uninitialized modules in the search. + * Include uninitialized modules in the search. Implies the predicate can run without exports. */ Uninitialized: 2, /** @@ -47,10 +35,6 @@ export interface FilterInfo { * The result type of the filter. */ Result: any - /** - * Whether the filter requires exports to work. - */ - RequiresExports: boolean /** * Scopes the filter matches modules in. */ @@ -59,6 +43,5 @@ export interface FilterInfo { export interface DefaultFilterInfo extends FilterInfo { Result: any - RequiresExports: boolean Scopes: FilterScope[] } diff --git a/lib/modules/src/finders/filters/dynamic.ts b/lib/modules/src/finders/filters/dynamic.ts index dc8de9e..f6faef8 100644 --- a/lib/modules/src/finders/filters/dynamic.ts +++ b/lib/modules/src/finders/filters/dynamic.ts @@ -4,7 +4,7 @@ import { getModuleDependencies, } from '../../metro/utils' import { runFilter } from '../_internal' -import { FilterFlag, FilterScopes } from './constants' +import { FilterScopes } from './constants' import { createFilterGenerator } from './utils' import type { Metro } from '../../types' import type { Filter, FilterGenerator } from './utils' @@ -76,7 +76,6 @@ export interface ComparableDependencyMap const withDependencies_ = createFilterGenerator>( ([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id), deps => `revenge.deps(${depGenFilterKey(deps)})`, - FilterFlag.Dynamic, FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithDependencies @@ -98,7 +97,6 @@ withDependencies.relative = relative type WithDependencies = FilterGenerator< (deps: ComparableDependencyMap) => Filter<{ Result: T - RequiresExports: false Scopes: [ typeof FilterScopes.Uninitialized, typeof FilterScopes.Initialized, diff --git a/lib/modules/src/finders/filters/index.ts b/lib/modules/src/finders/filters/index.ts index a8439eb..3db77aa 100644 --- a/lib/modules/src/finders/filters/index.ts +++ b/lib/modules/src/finders/filters/index.ts @@ -1,5 +1,5 @@ import { isModuleExportBad } from '@revenge-mod/modules/metro/utils' -import { FilterFlag, FilterScopes } from './constants' +import { FilterScopes } from './constants' import { createFilterGenerator } from './utils' import type { Filter, FilterGenerator } from './utils' @@ -8,7 +8,6 @@ export * from './utils' type FilterRequiringExports = Filter<{ Result: T - RequiresExports: true Scopes: [typeof FilterScopes.Initialized] }> @@ -41,7 +40,6 @@ export const withProps = createFilterGenerator>( return false }, props => `revenge.props(${props.join(',')})`, - FilterFlag.RequiresExports, FilterScopes.Initialized, ) as WithProps @@ -67,7 +65,6 @@ export const withoutProps = createFilterGenerator>( return true }, props => `revenge.withoutProps(${props.join(',')})`, - FilterFlag.RequiresExports, FilterScopes.Initialized, ) as WithoutProps @@ -97,7 +94,6 @@ export const withSingleProp = createFilterGenerator>( return false }, ([prop]) => `revenge.singleProp(${prop})`, - FilterFlag.RequiresExports, FilterScopes.Initialized, ) as WithSingleProp @@ -138,7 +134,6 @@ export type WithSingleProp = FilterGenerator< export const withName = createFilterGenerator>( ([name], _, exports) => exports?.name === name, ([name]) => `revenge.name(${name})`, - FilterFlag.RequiresExports, FilterScopes.Initialized, ) as WithName diff --git a/lib/modules/src/finders/filters/utils.ts b/lib/modules/src/finders/filters/utils.ts index 0f3d101..e853899 100644 --- a/lib/modules/src/finders/filters/utils.ts +++ b/lib/modules/src/finders/filters/utils.ts @@ -1,40 +1,32 @@ +import { isModuleInitialized } from '@revenge-mod/modules/metro/utils' import { getErrorStack } from '@revenge-mod/utils/error' import { withProps } from '.' import { and, or } from './composite' import { FilterScopes } from './constants' import type { Metro } from '@revenge-mod/modules/types' -import type { If, LogicalAnd } from '@revenge-mod/utils/types' import type { DefaultFilterInfo, - FilterFlag, FilterInfo, FilterScope, - FilterScopes as FilterScopesObject, FilterScopeValue, } from './constants' export type FilterResult = F extends Filter ? I['Result'] : never -export type FilterRequiresExports = - F extends Filter ? I['RequiresExports'] : never - export type FilterInfoOf = F extends Filter ? I : FilterInfo +declare const FilterInfoBrand: unique symbol + export interface FilterBase { ( - ...args: If< - Info['RequiresExports'], - [id: Metro.ModuleID, exports: Metro.ModuleExports], - [id: Metro.ModuleID, exports?: never] - > + id: Metro.ModuleID, + exports?: Metro.ModuleExports, + initialized?: boolean, ): boolean key: string - flags: If< - Info['RequiresExports'], - (typeof FilterFlag)['RequiresExports'], - FilterFlag - > scopes: FilterScopeValue + /** @internal */ + readonly [FilterInfoBrand]?: Info } export type Filter = @@ -42,13 +34,11 @@ export type Filter = export type MergeFilterInfo = { Result: I1['Result'] & I2['Result'] - RequiresExports: LogicalAnd Scopes: [...I1['Scopes'], ...I2['Scopes']] } export type UnionFilterInfo = { Result: I1['Result'] | I2['Result'] - RequiresExports: LogicalAnd Scopes: [...I1['Scopes'], ...I2['Scopes']] } @@ -91,25 +81,12 @@ export interface FilterHelpers { */ scope, const S extends FilterScope[]>( this: T, - ...scopes: If< - Info['RequiresExports'], - [typeof FilterScopesObject.Initialized], - S - > - ): Filter< - Info & { - Scopes: If< - Info['RequiresExports'], - [typeof FilterScopesObject.Initialized], - S - > - } - > + ...scopes: S + ): Filter } export type FilterGenerator Filter> = G & { keyFor(args: Parameters): string - flagsFor(args: Parameters): FilterFlag defaultScopesFor(args: Parameters): FilterScopeValue } @@ -129,7 +106,8 @@ const Helpers: FilterHelpers = Object.setPrototypeOf( const newFilter = (( id: Metro.ModuleID, exports?: Metro.ModuleExports, - ) => this(id, exports)) as typeof this + initialized?: boolean, + ) => this(id, exports, initialized)) as typeof this Object.assign(newFilter, this) @@ -139,7 +117,9 @@ const Helpers: FilterHelpers = Object.setPrototypeOf( const filter = this.new() filter.scopes = 0 for (const scope of scopes) filter.scopes |= scope - return filter + return filter as Filter< + FilterInfoOf & { Scopes: typeof scopes } + > }, } satisfies FilterHelpers, Function.prototype, @@ -150,15 +130,14 @@ const Helpers: FilterHelpers = Object.setPrototypeOf( * * @param filter The function that filters the modules. * @param keyFor The function that generates the key for the filter. - * @param flagFor The function that generates the flags for the filter, or a static flag. * @param defaultScopesFor The function that generates the default scopes for the filter, or static scopes. Defaults to {@link FilterScopes.Initialized}. * @returns A function that generates a filter with the specified arguments. * * @example * ```ts * const custom = createFilterGenerator<[arg1: number, arg2: string]>( - * ([arg1, arg2], id, exports) => { - * // WARNING: exports can be Proxy or nullish, so be careful when using it + * ([arg1, arg2], id, exports, initialized) => { + * // WARNING: exports can be a Proxy, nullish, or a primitive, so be careful when using it * // filter logic * return true * }, @@ -173,16 +152,15 @@ export function createFilterGenerator( args: A, id: Metro.ModuleID, exports: Metro.ModuleExports, + initialized: boolean, ) => boolean, keyFor: (args: A) => string, - flagFor: ((args: A) => FilterFlag) | FilterFlag, defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, ): FilterGenerator<(...args: A) => Filter> export function createFilterGenerator( filter: (args: A, id: Metro.ModuleID) => boolean, keyFor: (args: A) => string, - flagFor: ((args: A) => FilterFlag) | FilterFlag, defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, ): FilterGenerator<(...args: A) => Filter> @@ -190,16 +168,14 @@ export function createFilterGenerator( filter: ( args: A, id: Metro.ModuleID, - exports?: Metro.ModuleExports, + exports: Metro.ModuleExports, + initialized: boolean, ) => boolean, keyFor: (args: A) => string, - flagFor: ((args: A) => FilterFlag) | FilterFlag, defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, ): FilterGenerator<(...args: A) => Filter> { type GeneratorType = ReturnType> - const isFlagsStatic = typeof flagFor === 'number' - const defaultScopes = defaultScopesFor ?? FilterScopes.Initialized const isDefaultScopesStatic = typeof defaultScopes === 'number' @@ -207,9 +183,10 @@ export function createFilterGenerator( const filter_ = (( id: Metro.ModuleID, exports?: Metro.ModuleExports, + initialized: boolean = Boolean(isModuleInitialized(id)), ) => { try { - return filter(args, id, exports) + return filter(args, id, exports, initialized) } catch (e) { DEBUG_warnFilterThrown(id, keyFor(args), e) return false @@ -217,17 +194,12 @@ export function createFilterGenerator( }) as ReturnType filter_.key = keyFor(args) - filter_.flags = isFlagsStatic ? flagFor : flagFor(args) filter_.scopes = isDefaultScopesStatic ? defaultScopes : defaultScopes(args) return Object.setPrototypeOf(filter_, Helpers) } - generator.flagsFor = isFlagsStatic - ? () => flagFor - : (args: A) => flagFor(args) - generator.defaultScopesFor = isDefaultScopesStatic ? () => defaultScopes : defaultScopes diff --git a/lib/modules/src/finders/lookup.ts b/lib/modules/src/finders/lookup.ts index c517e3a..9623246 100644 --- a/lib/modules/src/finders/lookup.ts +++ b/lib/modules/src/finders/lookup.ts @@ -183,11 +183,19 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { } if (includeUninit) - for (const id of mUninitialized) { + // Must snapshot because partial matches can cause modules that haven't been looped over to be initialized + // which would remove them from the Set + for (const id of Array.from(mUninitialized)) { // biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized if (cached && cached.has(id)) continue - const flag = runFilter(filter, id, undefined, options) + const flag = runFilter( + filter, + id, + // Could be initialized by a previous partial match + getInitializedModuleExports(id), + options, + ) if (flag) { notFound = false @@ -308,8 +316,15 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) { } if (includeUninit) - for (const id of mUninitialized) { - const flag = runFilter(filter, id, undefined, options) + // See equivalent code in `lookupModules` for why this is snapshotted + for (const id of [...mUninitialized]) { + const flag = runFilter( + filter, + id, + // See equivalent code in `lookupModules` for why this isn't undefined + getInitializedModuleExports(id), + options, + ) if (flag) { if (__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__) DEBUG_logLookupMatched(filter.key, flag, id) diff --git a/lib/utils/src/discord.ts b/lib/utils/src/discord.ts index b058856..b50ddae 100644 --- a/lib/utils/src/discord.ts +++ b/lib/utils/src/discord.ts @@ -12,10 +12,7 @@ import { ReactModuleId, ReactNativeModuleId, } from '@revenge-mod/react' -import { - FilterFlag, - FilterScopes, -} from '../../modules/src/finders/filters/constants' +import { FilterScopes } from '../../modules/src/finders/filters/constants' import type { Filter, FilterGenerator, @@ -50,7 +47,6 @@ export type WithGeneratedIconComponent = FilterGenerator< ...assets: string[] ) => Filter<{ Result: { [K in N]: FC } - RequiresExports: boolean Scopes: [ typeof FilterScopes.Uninitialized, typeof FilterScopes.Initialized, @@ -126,7 +122,6 @@ export const withGeneratedIconComponent = createFilterGenerator< return false }, names => `revenge.utils.discord.generatedIconComponent(${names.join(',')})`, - FilterFlag.Dynamic, FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithGeneratedIconComponent