diff --git a/lib/discord/src/design.ts b/lib/discord/src/design.ts index d17f2e6..3c3a9d0 100644 --- a/lib/discord/src/design.ts +++ b/lib/discord/src/design.ts @@ -1,6 +1,5 @@ import { lookupModule, lookupModules } from '@revenge-mod/modules/finders' import { withDependencies } from '@revenge-mod/modules/finders/filters' -import { getModuleDependencies } from '@revenge-mod/modules/metro/utils' import { ReactJSXRuntimeModuleId, ReactModuleId, @@ -10,7 +9,7 @@ import { proxify } from '@revenge-mod/utils/proxy' import { ImportTrackerModuleId } from './common/import-tracker' import type { DiscordModules } from './types' -const { loose } = withDependencies +const { atLeast, last, loose, relative } = withDependencies /** * The lowest amount of dependencies `design/native.tsx` is expected to have. @@ -29,13 +28,12 @@ export let Design: Design = proxify( // [3909, 4608, 5182, 13172, 2, ...] (141 dependencies) const [, id] = lookupModule( withDependencies( - loose([ + atLeast(DesignMinimumDependencies, [ [ImportTrackerModuleId], [ImportTrackerModuleId], [ReactNativeModuleId, ImportTrackerModuleId], [ImportTrackerModuleId], ImportTrackerModuleId, - ...Array(DesignMinimumDependencies - 5).fill(null), ]), ).keyAs('revenge.discord.design.Design'), { @@ -55,9 +53,6 @@ export let Design: Design = proxify( // design/components/Forms/native/FormSwitch.native.tsx export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => { - // TODO: Possibly come up with a better dependency fingerprinting API - // to not have to deal with this bullshit - for (const [, id] of lookupModules( withDependencies( loose([ @@ -66,17 +61,25 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => { ReactNativeModuleId, ReactJSXRuntimeModuleId, ]), - ).keyAs('revenge.discord.design.FormSwitch'), + ) + .and( + withDependencies( + last([ + relative(1), + relative(2), + null, + null, + ImportTrackerModuleId, + ]), + ), + ) + .keyAs('revenge.discord.design.FormSwitch'), { initialize: false, }, )) { - const deps = getModuleDependencies(id)! - if (deps.at(-1) !== ImportTrackerModuleId) continue - if (deps.at(-4) === id + 2 && deps.at(-5) === id + 1) { - const FormSwitch_ = __r(id)!.FormSwitch - if (FormSwitch_) return (FormSwitch = FormSwitch_) - } + const FormSwitch_ = __r(id)!.FormSwitch + if (FormSwitch_) return (FormSwitch = FormSwitch_) } })! diff --git a/lib/discord/src/flux/stores.ts b/lib/discord/src/flux/stores.ts index 3150fbe..4737a3e 100644 --- a/lib/discord/src/flux/stores.ts +++ b/lib/discord/src/flux/stores.ts @@ -9,7 +9,6 @@ import { withDependencies, withName, } from '@revenge-mod/modules/finders/filters' -import { getModuleDependencies } from '@revenge-mod/modules/metro/utils' import { asap, noop } from '@revenge-mod/utils/callback' import { cache, @@ -75,14 +74,19 @@ export function getStore( ] */ -const withLeadingFluxStoreDeps = withDependencies( - withDependencies.loose([ - withName('_classCallCheck'), - withName('_createClass'), - withName('_possibleConstructorReturn'), - withName('bound getPrototypeOf'), - withName('_inherits'), - ]), +const { last, loose } = withDependencies + +// The import tracker is checked first, as it is far cheaper than resolving the leading dependencies' exports +const withFluxStoreDeps = withDependencies(last([ImportTrackerModuleId])).and( + withDependencies( + loose([ + withName('_classCallCheck'), + withName('_createClass'), + withName('_possibleConstructorReturn'), + withName('bound getPrototypeOf'), + withName('_inherits'), + ]), + ), ) export type WithStore = FilterGenerator< @@ -102,9 +106,7 @@ export const withStore = createFilterGenerator( (_, id, exports, initialized) => { if (initialized) return Boolean(exports?._dispatchToken) - if (!withLeadingFluxStoreDeps(id)) return false - const deps = getModuleDependencies(id)! - return deps[deps.length - 1] === ImportTrackerModuleId + return withFluxStoreDeps(id, undefined, false) }, () => 'revenge.discord.store', FilterScopes.Uninitialized | FilterScopes.Initialized, diff --git a/lib/utils/src/discord.ts b/lib/utils/src/discord.ts index b50ddae..e4f0454 100644 --- a/lib/utils/src/discord.ts +++ b/lib/utils/src/discord.ts @@ -7,39 +7,50 @@ import { withDependencies, } from '@revenge-mod/modules/finders/filters' import { isModuleExportBad } from '@revenge-mod/modules/metro/utils' -import { - ReactJSXRuntimeModuleId, - ReactModuleId, - ReactNativeModuleId, -} from '@revenge-mod/react' import { FilterScopes } from '../../modules/src/finders/filters/constants' import type { Filter, FilterGenerator, } from '@revenge-mod/modules/finders/filters' +import type { Metro } from '@revenge-mod/modules/types' import type { FC } from 'react' -// [React, ReactJsxRuntime, (Tokens), (BaseIconImage), (Asset), ImportTracker] -// Asset = relativeDep(1) -const IconComponentFilter = [ - ReactModuleId, - ReactJSXRuntimeModuleId, - TokensModuleId, - null, - null, - ImportTrackerModuleId, -] +const { last } = withDependencies -// Generated IconComponents that have multiple assets, there is no specific length, so the filter has to be generated on-the-fly. -// [React, ReactNative, ReactJsxRuntime, (Tokens), (BaseIconImage), (...Assets), ImportTracker] -// ...Assets = relativeDep(1..n) -const MultiIconComponentFilterBase = [ - ReactModuleId, - ReactNativeModuleId, - ReactJSXRuntimeModuleId, - TokensModuleId, - null, -] +const depsFilters: Record = {} + +/** + * Builds a dependency filter for an icon component. + * + * `[React, (ReactNative), ReactJsxRuntime, Tokens, (BaseIconImage), (...Assets), ImportTracker]` + * + * `ReactNative` is only present on components with multiple assets, and it sits right after `React`, + * so everything from `ReactJsxRuntime` onwards is at a fixed distance from the end on both shapes. + * + * @param names The component name, then the asset names if the component has multiple assets. + * @returns The filter, or `undefined` if any of the assets aren't registered yet. + */ +function depsFilterFor(names: string[]) { + const key = names.join(',') + const cached = depsFilters[key] + if (cached) return cached + + // Components with multiple assets are named separately from their assets + const assets = names.length > 1 ? names.slice(1) : names + const mids: Metro.ModuleID[] = [] + + for (const name of assets) { + const mid = getAssetByName(name)?.moduleId + // Module ID can never be zero, so if it's falsy, it means the asset was not found. + if (!mid) return + + mids.push(mid) + } + + return (depsFilters[key] = withDependencies( + last([TokensModuleId, null, ...mids, ImportTrackerModuleId]), + )) +} export type WithGeneratedIconComponent = FilterGenerator< ( @@ -82,44 +93,11 @@ export type WithGeneratedIconComponent = FilterGenerator< export const withGeneratedIconComponent = createFilterGenerator< Parameters >( - (names, id, exports) => { - if (typeof exports === 'object' && isModuleExportBad(exports)) { - if (typeof exports[names[0]] === 'function') - return Object.keys(exports).length === 1 - } else { - let filter = IconComponentFilter + (names, id, exports, initialized) => { + // Icon components always export their component, so unusable exports can never match + if (initialized && isModuleExportBad(exports)) return false - if (names.length > 1) { - const mids = [] - - for (let i = 1; i < names.length; i++) { - const name = names[i] - const mid = getAssetByName(name)?.moduleId - // Module ID can never be zero, so if it's falsy, it means the asset was not found. - if (!mid) return false - - mids.push(mid) - } - - filter = [ - ...MultiIconComponentFilterBase, - ...mids, - ImportTrackerModuleId, - ] - } else { - const [name] = names - - const mid = getAssetByName(name)?.moduleId - // Module ID can never be zero, so if it's falsy, it means the asset was not found. - if (!mid) return false - - IconComponentFilter[4] = mid - } - - return withDependencies(filter)(id, exports) - } - - return false + return depsFilterFor(names)?.(id, exports, initialized) ?? false }, names => `revenge.utils.discord.generatedIconComponent(${names.join(',')})`, FilterScopes.Uninitialized | FilterScopes.Initialized,