feat!: module finders refactor (#6)
- Renamed `byX` filters to `withX`. It is more correct. For example: You don't lookup, wait for, or get modules **BY** props. You lookup, wait for, and get modules **WITH** props. - Renamed `*byImportedPath` to `*withImportedPath` For consistency with the above change. - Renamed `getModule` to `getModules`. The API allows getting multiple modules and not just a single one. Types are renamed as well. `getModuleByImportedPath` remains the same as it only gets one single module with a unique imported path. - `withX` filter **helpers** removed. They can now be replaced with the new capability-based `and` filter. - `preferExports` and `moduleStateAware` filters removed. They can now be replaced with the new capability-based `and` filter. - Composite finders renamed. `some` -> `or`, `every` -> `and` - Composite finders takes a capability-based approach. - Filter generators now need to attach runtime flag to let the system know whether the generated filter can be used to filter modules without proper exports or not. ## Misc. - Removed useless unsub call in Profile Badges - Use `queueMicrotask` with `asap` # Features ## Capability-based approach to composite filters ```js // Old byProps(...).withDependencies(...) // Old, same as: preferExports(byProps(...), byDependencies(...)) every(byDependencies(...), byProps(...)) // Old, ordering matters, this would fail unexpectedly! every(byProps(...), byDependencies(...)) // New withProps(...).and(withDependencies(...)) // New (as well), ordering doesn't matter, this has the same result as above withDependencies(...).and(withProps(...)) ```
This commit is contained in:
parent
a7ba4a4391
commit
5f5fc6f2b2
37 changed files with 985 additions and 992 deletions
6
bun.lock
6
bun.lock
|
|
@ -4,17 +4,17 @@
|
|||
"": {
|
||||
"name": "@revenge-mod/revenge",
|
||||
"dependencies": {
|
||||
"@swc/core": "^1.13.3",
|
||||
"@swc/core": "^1.13.5",
|
||||
"chalk": "^5.6.0",
|
||||
"react-native-click-outside": "^0.1.1",
|
||||
"rolldown": "1.0.0-beta.34",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.0",
|
||||
"@biomejs/biome": "^2.2.2",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@react-native-clipboard/clipboard": "^1.16.3",
|
||||
"@types/bun": "^1.2.20",
|
||||
"rolldown-plugin-dts": "^0.15.6",
|
||||
"rolldown-plugin-dts": "^0.15.9",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/core": "^7.12.4",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import {
|
|||
waitForModules,
|
||||
} from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byName,
|
||||
byProps,
|
||||
withDependencies,
|
||||
withName,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
mInitializingId,
|
||||
|
|
@ -20,13 +20,13 @@ import type { Metro } from '@revenge-mod/modules/types'
|
|||
import type { ReactNative } from '@revenge-mod/react/types'
|
||||
import type { Asset, PackagerAsset } from './types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
const { relative } = withDependencies
|
||||
|
||||
const [, _classCallCheckId] = lookupModule(byName('_classCallCheck'))
|
||||
const [, _createClassId] = lookupModule(byName('_createClass'))
|
||||
const [, _classCallCheckId] = lookupModule(withName('_classCallCheck'))
|
||||
const [, _createClassId] = lookupModule(withName('_createClass'))
|
||||
|
||||
// https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Image/AssetSourceResolver.js
|
||||
const byAssetSourceResolver = byDependencies([
|
||||
const withAssetSourceResolver = withDependencies([
|
||||
_classCallCheckId,
|
||||
_createClassId,
|
||||
relative(1),
|
||||
|
|
@ -39,7 +39,7 @@ const cachedOnly = { cached: true }
|
|||
|
||||
// Tracking/caching assets
|
||||
const unsubAR = waitForModules(
|
||||
byProps<ReactNative.AssetsRegistry>('registerAsset'),
|
||||
withProps<ReactNative.AssetsRegistry>('registerAsset'),
|
||||
(exports, id) => {
|
||||
AssetsRegistryModuleId = id
|
||||
AssetsRegistry = exports as ReactNative.AssetsRegistry
|
||||
|
|
@ -104,7 +104,7 @@ const unsubAR = waitForModules(
|
|||
*/
|
||||
export let AssetsRegistryModuleId: Metro.ModuleID | undefined
|
||||
export let AssetsRegistry: ReactNative.AssetsRegistry = proxify(() => {
|
||||
for (const [, id] of lookupModules(byDependencies([[]]), {
|
||||
for (const [, id] of lookupModules(withDependencies([[]]), {
|
||||
initialize: false,
|
||||
uninitialized: true,
|
||||
})) {
|
||||
|
|
@ -112,7 +112,7 @@ export let AssetsRegistry: ReactNative.AssetsRegistry = proxify(() => {
|
|||
if (deps.length !== 1) continue
|
||||
|
||||
// The module next to assets-registry is AssetSourceResolver
|
||||
if (byAssetSourceResolver(deps[0] + 1)) {
|
||||
if (withAssetSourceResolver(deps[0] + 1)) {
|
||||
const module = __r(id)
|
||||
// ID will be set by the wait below
|
||||
if (module?.registerAsset) return (AssetsRegistry = module)
|
||||
|
|
@ -124,7 +124,7 @@ export let AssetsRegistry: ReactNative.AssetsRegistry = proxify(() => {
|
|||
|
||||
// Asset overrides
|
||||
const unsubRAS = waitForModules(
|
||||
byName<{
|
||||
withName<{
|
||||
addCustomSourceTransformer: (
|
||||
transformer: (arg: { asset: Asset }) => PackagerAsset,
|
||||
) => void
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { lookupModule, lookupModules } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
every,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withoutProps,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -15,19 +13,18 @@ import { proxify } from '@revenge-mod/utils/proxy'
|
|||
import { DispatcherModuleId } from './common/flux'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
const { relative } = withDependencies
|
||||
|
||||
// modules/action_sheet/native/ActionSheetActionCreators.tsx
|
||||
export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCreators =
|
||||
proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Actions.ActionSheetActionCreators>(
|
||||
'hideActionSheet',
|
||||
'openLazy',
|
||||
),
|
||||
byDependencies([
|
||||
withProps<DiscordModules.Actions.ActionSheetActionCreators>(
|
||||
'hideActionSheet',
|
||||
'openLazy',
|
||||
).and(
|
||||
withDependencies([
|
||||
null,
|
||||
ReactModuleId,
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -54,11 +51,10 @@ export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators =
|
|||
proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Actions.AlertActionCreators>(
|
||||
'openAlert',
|
||||
),
|
||||
byDependencies([
|
||||
withProps<DiscordModules.Actions.AlertActionCreators>(
|
||||
'openAlert',
|
||||
).and(
|
||||
withDependencies([
|
||||
[[], relative(1)],
|
||||
[ReactNativeModuleId, 2],
|
||||
2,
|
||||
|
|
@ -83,13 +79,9 @@ export let ToastActionCreators: DiscordModules.Actions.ToastActionCreators =
|
|||
// [Dispatcher, ImportTracker]
|
||||
|
||||
const generator = lookupModules(
|
||||
preferExports(
|
||||
every(
|
||||
byProps<DiscordModules.Actions.ToastActionCreators>('open'),
|
||||
withoutProps('init'),
|
||||
),
|
||||
byDependencies([DispatcherModuleId, 2]),
|
||||
),
|
||||
withProps<DiscordModules.Actions.ToastActionCreators>('open')
|
||||
.and(withoutProps('init'))
|
||||
.and(withDependencies([DispatcherModuleId, 2])),
|
||||
{
|
||||
uninitialized: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { DiscordModules } from '../types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
const { relative } = withDependencies
|
||||
|
||||
// ../discord_common/js/packages/flux
|
||||
|
||||
export const [Dispatcher, DispatcherModuleId] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Flux.Dispatcher>('_interceptors'),
|
||||
byDependencies([relative(1), null, null, null, null, 2]),
|
||||
withProps<DiscordModules.Flux.Dispatcher>('_interceptors').and(
|
||||
withDependencies([relative(1), null, null, null, null, 2]),
|
||||
),
|
||||
{
|
||||
uninitialized: true,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byName,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withName,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
|
|
@ -13,15 +12,15 @@ export { AppStartPerformance } from '../preinit'
|
|||
export * as flux from './flux'
|
||||
export * as utils from './utils'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
// ../discord_common/js/packages/logger/Logger.tsx
|
||||
export const [Logger, LoggerModuleId] = lookupModule(
|
||||
byName<typeof DiscordModules.Logger>('Logger'),
|
||||
withName<typeof DiscordModules.Logger>('Logger'),
|
||||
) as [typeof DiscordModules.Logger, Metro.ModuleID]
|
||||
|
||||
// ../discord_common/js/packages/tokens/native.tsx
|
||||
export const [Tokens, TokensModuleId] = lookupModule(byProps('RawColor')) as [
|
||||
export const [Tokens, TokensModuleId] = lookupModule(withProps('RawColor')) as [
|
||||
any,
|
||||
Metro.ModuleID,
|
||||
]
|
||||
|
|
@ -43,13 +42,11 @@ export let ConstantsModuleId: Metro.ModuleID | undefined
|
|||
export let Constants: DiscordModules.Constants = proxify(
|
||||
() => {
|
||||
const [module, id] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Constants>('ME'),
|
||||
|
||||
withProps<DiscordModules.Constants>('ME').and(
|
||||
// ID: 1236
|
||||
// Deps: 26, 1237, 1238, 1239, 1240, ...
|
||||
// Every module has only one dependency, which is the import tracker
|
||||
byDependencies(
|
||||
withDependencies(
|
||||
loose([
|
||||
null,
|
||||
relative.withDependencies(OnlyImportTrackerDep, 1),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byName,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withName,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { DiscordModules } from '../types'
|
||||
|
||||
const [, _createClassModuleId] = lookupModule(byName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
|
||||
const [, _createClassModuleId] = lookupModule(withName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(withName('_classCallCheck'))
|
||||
|
||||
// ../discord_common/js/shared/utils/TypedEventEmitter.tsx
|
||||
/**
|
||||
|
|
@ -17,11 +16,10 @@ const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
|
|||
export let TypedEventEmitter: typeof DiscordModules.Utils.TypedEventEmitter =
|
||||
proxify(() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byName<typeof DiscordModules.Utils.TypedEventEmitter>(
|
||||
'TypedEventEmitter',
|
||||
),
|
||||
byDependencies([
|
||||
withName<typeof DiscordModules.Utils.TypedEventEmitter>(
|
||||
'TypedEventEmitter',
|
||||
).and(
|
||||
withDependencies([
|
||||
_classCallCheckModuleId,
|
||||
_createClassModuleId,
|
||||
[],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
bySingleProp,
|
||||
preferExports,
|
||||
some,
|
||||
withDependencies,
|
||||
withProps,
|
||||
withSingleProp,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -14,7 +12,7 @@ import {
|
|||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
// design/native.tsx
|
||||
export let Design: Design = proxify(
|
||||
|
|
@ -22,25 +20,23 @@ export let Design: Design = proxify(
|
|||
// ID: 3236
|
||||
// [3237, 1366, 3238, 3239, 2, ...];
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<Design>('TableRow', 'Button'),
|
||||
some(
|
||||
byDependencies(
|
||||
loose([
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
relative.withDependencies([2], 1),
|
||||
2,
|
||||
null, // 3009
|
||||
null, // 3010
|
||||
null, // 3011
|
||||
null, // 3012
|
||||
relative.withDependencies([2], 2),
|
||||
]),
|
||||
),
|
||||
withProps<Design>('TableRow', 'Button').and(
|
||||
withDependencies(
|
||||
loose([
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
relative.withDependencies([2], 1),
|
||||
2,
|
||||
null, // 3009
|
||||
null, // 3010
|
||||
null, // 3011
|
||||
null, // 3012
|
||||
relative.withDependencies([2], 2),
|
||||
]),
|
||||
).or(
|
||||
// TODO(PalmDevs): Remove once stable channel is > 295203 (for 295203 and below)
|
||||
byDependencies(
|
||||
withDependencies(
|
||||
loose([relative(1), null, relative(2), relative(3), 2]),
|
||||
),
|
||||
),
|
||||
|
|
@ -65,11 +61,10 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => {
|
|||
// Deps: [47, 48, 35, 49]
|
||||
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
bySingleProp<{ FormSwitch: DiscordModules.Components.FormSwitch }>(
|
||||
'FormSwitch',
|
||||
),
|
||||
byDependencies([
|
||||
withSingleProp<{
|
||||
FormSwitch: DiscordModules.Components.FormSwitch
|
||||
}>('FormSwitch').and(
|
||||
withDependencies([
|
||||
[relative(1), relative(2), null, relative(3)],
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import {
|
|||
waitForModules,
|
||||
} from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byName,
|
||||
createFilterGenerator,
|
||||
withName,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||
import { asap, noop } from '@revenge-mod/utils/callback'
|
||||
import { cached, cacheFilterResultForId } from '#modules/src/caches'
|
||||
import { FilterResultFlags } from '#modules/src/finders/_internal'
|
||||
import { FilterFlag } from '#modules/src/finders/filters/constants'
|
||||
import type {
|
||||
Filter,
|
||||
FilterGenerator,
|
||||
|
|
@ -33,7 +34,7 @@ export const Stores = new Proxy(_stores, {
|
|||
get: (target, prop: string) =>
|
||||
target[prop] ??
|
||||
// @ts-expect-error: This always uses cache
|
||||
lookupModule(byStoreName(prop), { uninitialized: true })[0],
|
||||
lookupModule(withStoreName(prop), { uninitialized: true })[0],
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
@ -53,7 +54,7 @@ export function getStore<T>(
|
|||
return noop
|
||||
}
|
||||
|
||||
return waitForModules(byStoreName<T>(name), callback, { cached: true })
|
||||
return waitForModules(withStoreName<T>(name), callback, { cached: true })
|
||||
}
|
||||
|
||||
/// STORE FILTERING
|
||||
|
|
@ -70,15 +71,15 @@ export function getStore<T>(
|
|||
]
|
||||
*/
|
||||
|
||||
const [, _createClassModuleId] = lookupModule(byName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
|
||||
const [, _createClassModuleId] = lookupModule(withName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(withName('_classCallCheck'))
|
||||
const [, _possibleConstructorReturnModuleId] = lookupModule(
|
||||
byName('_possibleConstructorReturn'),
|
||||
withName('_possibleConstructorReturn'),
|
||||
)
|
||||
const [, _bound_getPrototypeOfModuleId] = lookupModule(
|
||||
byName('bound getPrototypeOf'),
|
||||
withName('bound getPrototypeOf'),
|
||||
)
|
||||
const [, _inheritsModuleId] = lookupModule(byName('_inherits'))
|
||||
const [, _inheritsModuleId] = lookupModule(withName('_inherits'))
|
||||
|
||||
const FluxStoreLeadingDeps = [
|
||||
_classCallCheckModuleId,
|
||||
|
|
@ -88,14 +89,14 @@ const FluxStoreLeadingDeps = [
|
|||
_inheritsModuleId,
|
||||
]
|
||||
|
||||
export type ByStore = FilterGenerator<
|
||||
export type WithStore = FilterGenerator<
|
||||
<T>() => Filter<DiscordModules.Flux.Store<T>, boolean>
|
||||
>
|
||||
|
||||
/**
|
||||
* A dynamic filter that matches all Flux stores.
|
||||
*/
|
||||
export const byStore = createFilterGenerator(
|
||||
export const withStore = createFilterGenerator(
|
||||
(_, id, exports) => {
|
||||
if (exports) return Boolean(exports._dispatchToken)
|
||||
else {
|
||||
|
|
@ -108,29 +109,31 @@ export const byStore = createFilterGenerator(
|
|||
return deps[deps.length - 1] === 2
|
||||
}
|
||||
},
|
||||
() => 'revenge.discord.byStore',
|
||||
) as ByStore
|
||||
() => 'revenge.discord.store',
|
||||
FilterFlag.Dynamic,
|
||||
) as WithStore
|
||||
|
||||
export type ByStoreName = FilterGenerator<
|
||||
export type WithStoreName = FilterGenerator<
|
||||
<T>(name: string) => Filter<DiscordModules.Flux.Store<T>, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* A with-exports filter that matches a Flux store by its name.
|
||||
*/
|
||||
export const byStoreName = createFilterGenerator(
|
||||
export const withStoreName = createFilterGenerator(
|
||||
([name], _, exports) =>
|
||||
exports.getName?.length === 0 && exports.getName() === name,
|
||||
([name]) => `revenge.discord.byStoreName(${name})`,
|
||||
) as ByStoreName
|
||||
([name]) => `revenge.discord.storeName(${name})`,
|
||||
FilterFlag.RequiresExports,
|
||||
) as WithStoreName
|
||||
|
||||
/// STORE CACHING
|
||||
|
||||
waitForModules(byStore(), (store, id) => {
|
||||
waitForModules(withStore(), (store, id) => {
|
||||
const name = store.getName()
|
||||
// Cache stores
|
||||
cacheFilterResultForId(
|
||||
byStoreName.keyFor([name]),
|
||||
withStoreName.keyFor([name]),
|
||||
id,
|
||||
FilterResultFlags.Default,
|
||||
)
|
||||
|
|
@ -140,7 +143,7 @@ waitForModules(byStore(), (store, id) => {
|
|||
cached.then(cached => {
|
||||
if (!cached)
|
||||
asap(() => {
|
||||
const lookup = lookupModules(byStore(), {
|
||||
const lookup = lookupModules(withStore(), {
|
||||
uninitialized: true,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { NavigationContainerRef } from '@react-navigation/core'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
export interface RootNavigationRef {
|
||||
getRootNavigationRef<
|
||||
|
|
@ -19,9 +18,8 @@ export interface RootNavigationRef {
|
|||
export let RootNavigationRef: RootNavigationRef = proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<RootNavigationRef>('getRootNavigationRef'),
|
||||
byDependencies([
|
||||
withProps<RootNavigationRef>('getRootNavigationRef').and(
|
||||
withDependencies([
|
||||
loose([[], loose([relative(1), relative(2)])]),
|
||||
2,
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -12,7 +11,7 @@ import {
|
|||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { DiscordModules } from '../../types'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
export type SettingListRenderer =
|
||||
DiscordModules.Modules.Settings.SettingListRenderer
|
||||
|
|
@ -21,9 +20,8 @@ export type SettingListRenderer =
|
|||
export let SettingListRenderer: SettingListRenderer = proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<SettingListRenderer>('SettingsList'),
|
||||
byDependencies(
|
||||
withProps<SettingListRenderer>('SettingsList').and(
|
||||
withDependencies(
|
||||
loose([
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
const { relative } = withDependencies
|
||||
|
||||
// ../discord_common/js/packages/app-start-performance/AppStartPerformance.tsx
|
||||
export const [AppStartPerformance] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.AppStartPerformance>('markAndLog'),
|
||||
byDependencies([relative(-1), null, null, 2]),
|
||||
withProps<DiscordModules.AppStartPerformance>('markAndLog').and(
|
||||
withDependencies([relative(-1), null, null, 2]),
|
||||
),
|
||||
{
|
||||
uninitialized: true,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { waitForModules } from '@revenge-mod/modules/finders'
|
||||
import { byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { sConfig, sSubscriptions } from './modules/settings/_internal'
|
||||
import type { SettingsItem } from './modules/settings'
|
||||
|
||||
export let sLoaded = false
|
||||
|
||||
const unsubSRC = waitForModules(
|
||||
byProps<{ SETTING_RENDERER_CONFIG: Record<string, SettingsItem> }>(
|
||||
withProps<{ SETTING_RENDERER_CONFIG: Record<string, SettingsItem> }>(
|
||||
'SETTING_RENDERER_CONFIG',
|
||||
),
|
||||
SettingRendererConfig => {
|
||||
|
|
|
|||
12
lib/externals/src/browserify.ts
vendored
12
lib/externals/src/browserify.ts
vendored
|
|
@ -1,19 +1,17 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
export let nodeUtil: typeof import('node:util') = proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof nodeUtil>('inspect'),
|
||||
byDependencies([
|
||||
withProps<typeof nodeUtil>('inspect').and(
|
||||
withDependencies([
|
||||
loose([relative(2, true), relative(4, true)]),
|
||||
[],
|
||||
[],
|
||||
|
|
|
|||
12
lib/externals/src/react-native-clipboard.ts
vendored
12
lib/externals/src/react-native-clipboard.ts
vendored
|
|
@ -1,13 +1,12 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { ReactModuleId, ReactNativeModuleId } from '@revenge-mod/react'
|
||||
import { destructure, proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { relative } = byDependencies
|
||||
const { relative } = withDependencies
|
||||
|
||||
let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
|
||||
proxify(
|
||||
|
|
@ -24,9 +23,8 @@ let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
|
|||
// r5 = [27, 4793];
|
||||
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof ClipboardModule>('useClipboard'),
|
||||
byDependencies([
|
||||
withProps<typeof ClipboardModule>('useClipboard').and(
|
||||
withDependencies([
|
||||
relative.withDependencies(
|
||||
[ReactModuleId, relative(2, true)],
|
||||
1,
|
||||
|
|
|
|||
23
lib/externals/src/react-navigation.ts
vendored
23
lib/externals/src/react-navigation.ts
vendored
|
|
@ -1,25 +1,23 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byName,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withName,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { ReactJSXRuntimeModuleId, ReactModuleId } from '@revenge-mod/react'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
const { loose, relative } = withDependencies
|
||||
|
||||
const [, _createClassModuleId] = lookupModule(byName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
|
||||
const [, _createClassModuleId] = lookupModule(withName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(withName('_classCallCheck'))
|
||||
|
||||
export let ReactNavigationNative: typeof import('@react-navigation/native') =
|
||||
proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof ReactNavigationNative>('useLinkTo'),
|
||||
byDependencies(
|
||||
withProps<typeof ReactNavigationNative>('useLinkTo').and(
|
||||
withDependencies(
|
||||
loose([
|
||||
[],
|
||||
loose([
|
||||
|
|
@ -44,9 +42,8 @@ export let ReactNavigationStack: typeof import('@react-navigation/stack') =
|
|||
proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof ReactNavigationStack>('StackView'),
|
||||
byDependencies(
|
||||
withProps<typeof ReactNavigationStack>('StackView').and(
|
||||
withDependencies(
|
||||
loose([
|
||||
relative.withDependencies(
|
||||
loose([
|
||||
|
|
|
|||
14
lib/externals/src/shopify.ts
vendored
14
lib/externals/src/shopify.ts
vendored
|
|
@ -1,8 +1,7 @@
|
|||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
withDependencies,
|
||||
withProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -14,9 +13,10 @@ import { proxify } from '@revenge-mod/utils/proxy'
|
|||
export let FlashList: typeof import('@shopify/flash-list') = proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof FlashList>('FlashList'),
|
||||
byDependencies([
|
||||
withProps<typeof FlashList>('FlashList').and(
|
||||
// Dependencies. One in brackets are dynamic or late initialized.
|
||||
// [React, RN, JSXRuntime, (FlashListExports), (Reanimated), (RNBottomSheet), ImportTracker, (BottomSheetFlashList)]
|
||||
withDependencies([
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
ReactJSXRuntimeModuleId,
|
||||
|
|
@ -26,8 +26,6 @@ export let FlashList: typeof import('@shopify/flash-list') = proxify(
|
|||
2,
|
||||
null,
|
||||
]),
|
||||
// Dependencies. One in brackets are dynamic or late initialized.
|
||||
// [React, RN, JSXRuntime, (FlashListExports), (Reanimated), (RNBottomSheet), ImportTracker, (BottomSheetFlashList)]
|
||||
),
|
||||
{
|
||||
uninitialized: true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"type": "module",
|
||||
"exports": {
|
||||
"./finders/filters": {
|
||||
"default": "./src/finders/filters.ts"
|
||||
"default": "./src/finders/filters/index.ts"
|
||||
},
|
||||
"./finders": {
|
||||
"default": "./src/finders/index.ts"
|
||||
|
|
@ -25,4 +25,4 @@
|
|||
"types": "./src/types.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ export const FilterResultFlagToHumanReadable: Record<FilterResultFlag, string> =
|
|||
[FilterResultFlags.Found]: '\u001b[96mexportsless\u001b[0m',
|
||||
}
|
||||
|
||||
// The reason this returns a flag is because flags are never falsy, while exports may be falsy when using ID-only filters (eg. `byDependencies`).
|
||||
// The reason this returns a flag is because flags are never falsy, while exports may be falsy when using ID-only filters (eg. `withDependencies`).
|
||||
|
||||
// Currently, we only have options that are relevant for checking exports
|
||||
// If we add more options later on, do NOT forget about
|
||||
|
|
|
|||
|
|
@ -1,739 +0,0 @@
|
|||
import { getCurrentStack } from '@revenge-mod/utils/error'
|
||||
import { mInitialized } from '../metro/patches'
|
||||
import { getModuleDependencies, isModuleInitialized } from '../metro/utils'
|
||||
import type { If, LogicalOr } from '@revenge-mod/utils/types'
|
||||
import type { Metro } from '../types'
|
||||
|
||||
export type FilterResult<F> = F extends Filter<infer R, boolean>
|
||||
? R
|
||||
: F extends FilterBase<infer R>
|
||||
? R
|
||||
: never
|
||||
|
||||
export type IsFilterWithExports<F> = F extends Filter<any, infer WE>
|
||||
? WE
|
||||
: F extends FilterBase<any, infer WE>
|
||||
? WE
|
||||
: never
|
||||
|
||||
interface FilterBase<_Inferable = any, WithExports extends boolean = boolean> {
|
||||
(
|
||||
...args: If<
|
||||
WithExports,
|
||||
[id: Metro.ModuleID, exports: Metro.ModuleExports],
|
||||
[id: Metro.ModuleID, exports?: never]
|
||||
>
|
||||
): boolean
|
||||
key: string
|
||||
}
|
||||
export type Filter<
|
||||
Inferable = any,
|
||||
WithExports extends boolean = boolean,
|
||||
> = FilterHelpers & FilterBase<Inferable, WithExports>
|
||||
|
||||
interface FilterHelpers {
|
||||
/**
|
||||
* Manually the key for this filter.
|
||||
*
|
||||
* **Don't use this unless you know what you're doing.** Only API exports should be using
|
||||
*
|
||||
* @param key The key to set for this filter.
|
||||
*/
|
||||
keyAs<T extends FilterBase>(this: T, key: string): T
|
||||
/**
|
||||
* Combines this filter with another filter, returning a new filter that matches if **both** filters match.
|
||||
*
|
||||
* @param filter The filter to combine with.
|
||||
*/
|
||||
and<T extends FilterBase, F extends FilterBase>(
|
||||
this: T,
|
||||
filter: F,
|
||||
): Filter<
|
||||
FilterResult<T> & FilterResult<F>,
|
||||
LogicalOr<IsFilterWithExports<T>, IsFilterWithExports<F>>
|
||||
>
|
||||
/**
|
||||
* Combines this filter with another filter, returning a new filter that matches if **either** filter matches.
|
||||
*
|
||||
* @param filter
|
||||
*/
|
||||
or<T extends FilterBase, F extends FilterBase>(
|
||||
this: T,
|
||||
filter: F,
|
||||
): Filter<
|
||||
FilterResult<T> | FilterResult<F>,
|
||||
IsFilterWithExports<T> | IsFilterWithExports<F>
|
||||
>
|
||||
/**
|
||||
* Combines this filter with another filter that filters by exports, returning a new filter that matches if **both** filters match.
|
||||
*
|
||||
* @see {@link preferExports} for more information on how to use this.
|
||||
*/
|
||||
withExports<T extends FilterBase<any, false>>(
|
||||
this: T,
|
||||
withExportsFilter: FilterBase<any, true>,
|
||||
strict?: boolean,
|
||||
): Filter<
|
||||
FilterResult<typeof withExportsFilter>,
|
||||
IsFilterWithExports<T> | IsFilterWithExports<typeof withExportsFilter>
|
||||
>
|
||||
/**
|
||||
* Combines this filter with another filter that filters by dependencies, returning a new filter that matches if **both** filters match.
|
||||
*
|
||||
* @see {@link byDependencies} for more information on how to use this.
|
||||
*/
|
||||
withDependencies<T extends FilterBase<any, true>>(
|
||||
this: T,
|
||||
deps: ComparableDependencyMap,
|
||||
strict?: boolean,
|
||||
): Filter<FilterResult<T>, false>
|
||||
}
|
||||
|
||||
export type FilterGenerator<G extends (...args: any[]) => Filter> = G & {
|
||||
keyFor(args: Parameters<G>): string
|
||||
}
|
||||
|
||||
const Helpers: FilterHelpers = Object.setPrototypeOf(
|
||||
{
|
||||
keyAs(key) {
|
||||
this.key = key
|
||||
return this
|
||||
},
|
||||
and(filter) {
|
||||
return every(this, filter)
|
||||
},
|
||||
or(filter) {
|
||||
return some(this, filter)
|
||||
},
|
||||
withExports(withExportsFilter, strict) {
|
||||
return preferExports(withExportsFilter, this, strict)
|
||||
},
|
||||
withDependencies(deps, strict) {
|
||||
return preferExports(this, byDependencies(deps), strict)
|
||||
},
|
||||
} satisfies FilterHelpers,
|
||||
Function.prototype,
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a filter generator.
|
||||
*
|
||||
* @param filter The function that filters the modules.
|
||||
* @param keyFor The function that generates the key for the filter.
|
||||
* @returns A function that generates a filter with the specified arguments.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const custom = createFilterGenerator<[arg1: number, arg2: string]>(
|
||||
* ([arg1, arg2], id, exports) => {
|
||||
* // filter logic
|
||||
* return true
|
||||
* },
|
||||
* ([arg1, arg2]) => `custom(${arg1}, ${arg2})`
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @see {@link byProps} for an example on custom-typed filters.
|
||||
*/
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (
|
||||
args: A,
|
||||
id: Metro.ModuleID,
|
||||
exports: Metro.ModuleExports,
|
||||
) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): FilterGenerator<(...args: A) => Filter<any, true>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (args: A, id: Metro.ModuleID) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): FilterGenerator<(...args: A) => Filter<any, false>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (
|
||||
args: A,
|
||||
id: Metro.ModuleID,
|
||||
exports?: Metro.ModuleExports,
|
||||
) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): FilterGenerator<(...args: A) => Filter> {
|
||||
type GeneratorType = ReturnType<typeof createFilterGenerator<A>>
|
||||
|
||||
const generator: GeneratorType = (...args: A) => {
|
||||
const filter_ = ((id: Metro.ModuleID, exports?: Metro.ModuleExports) =>
|
||||
filter(args, id, exports)) as ReturnType<GeneratorType>
|
||||
|
||||
filter_.key = keyFor(args)
|
||||
return Object.setPrototypeOf(filter_, Helpers)
|
||||
}
|
||||
|
||||
generator.keyFor = keyFor
|
||||
return generator
|
||||
}
|
||||
|
||||
export type ByProps = FilterGenerator<
|
||||
<T extends Record<string, any> = Record<string, any>>(
|
||||
prop: keyof T,
|
||||
...props: Array<keyof T>
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having all of the specified properties.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
* @param props More properties to check for (optional).
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [React] = lookupModule(byProps<typeof import('react')>('createElement'))
|
||||
* // const React: typeof import('react')
|
||||
* ```
|
||||
*/
|
||||
export const byProps = createFilterGenerator<Parameters<ByProps>>(
|
||||
(props, _, exports) => {
|
||||
const type = typeof exports
|
||||
if (type === 'object' || type === 'function') {
|
||||
for (const prop of props) {
|
||||
if (prop in exports) continue
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
props => `revenge.props(${props.join(',')})`,
|
||||
) as ByProps
|
||||
|
||||
export type WithoutProps = FilterGenerator<
|
||||
<T extends Record<string, any>>(
|
||||
prop: string,
|
||||
...props: string[]
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having none of the specified properties.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
* @param props More properties to check for (optional).
|
||||
*/
|
||||
export const withoutProps = createFilterGenerator<Parameters<WithoutProps>>(
|
||||
(props, _, exports) => {
|
||||
const type = typeof exports
|
||||
if (type === 'object' || type === 'function')
|
||||
for (const prop of props) if (prop in exports) return false
|
||||
|
||||
return true
|
||||
},
|
||||
props => `revenge.withoutProps(${props.join(',')})`,
|
||||
) as WithoutProps
|
||||
|
||||
export type BySingleProp = FilterGenerator<
|
||||
<T extends Record<string, any>>(prop: keyof T) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having only the specified property.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [FormSwitchModule] = lookupModule(bySingleProp('FormSwitch'))
|
||||
* // const FormSwitchModule: { FormSwitch: any }
|
||||
* ```
|
||||
*/
|
||||
export const bySingleProp = createFilterGenerator<Parameters<BySingleProp>>(
|
||||
([prop], _, exports) => {
|
||||
if (typeof exports === 'object' && prop in exports)
|
||||
return Object.keys(exports).length === 1
|
||||
|
||||
return false
|
||||
},
|
||||
([prop]) => `revenge.singleProp(${prop})`,
|
||||
) as BySingleProp
|
||||
|
||||
export type ByName = FilterGenerator<
|
||||
<T extends object = object>(name: string) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having the specified name.
|
||||
*
|
||||
* Usually used for function components or classes.
|
||||
*
|
||||
* @param name The name to check for.
|
||||
*
|
||||
* @example Auto-typing as object
|
||||
* ```ts
|
||||
* const [SomeComponent] = lookupModule(byName('SomeComponent'))
|
||||
* // const SomeComponent: { name: 'SomeComponent' }
|
||||
* ```
|
||||
*
|
||||
* @example Typing as function component
|
||||
* ```ts
|
||||
* type MyComponent = React.FC<{ foo: string }>
|
||||
*
|
||||
* const [MyComponent] = lookupModule(byName<MyComponent>('MyComponent'))
|
||||
* // const MyComponent: MyComponent & { name: 'MyComponent' }
|
||||
* ```
|
||||
*
|
||||
* @example Typing as class
|
||||
* ```
|
||||
* interface SomeClass {
|
||||
* someMethod(): void
|
||||
* }
|
||||
*
|
||||
* const [SomeClass] = lookupModule(byName<{ new(param: string): SomeClass }>('SomeClass'))
|
||||
* // const SomeClass: { new(): SomeClass, name: 'SomeClass' }
|
||||
*/
|
||||
export const byName = createFilterGenerator<Parameters<ByName>>(
|
||||
([name], _, exports) => exports.name === name,
|
||||
([name]) => `revenge.name(${name})`,
|
||||
) as ByName
|
||||
|
||||
export interface ComparableDependencyMap
|
||||
extends Array<
|
||||
Metro.ModuleID | number | null | undefined | ComparableDependencyMap
|
||||
> {
|
||||
l?: boolean
|
||||
r?: number
|
||||
}
|
||||
|
||||
type ByDependencies = FilterGenerator<
|
||||
<T>(deps: ComparableDependencyMap) => Filter<T, false>
|
||||
> & {
|
||||
loose: typeof loose
|
||||
relative: typeof relative
|
||||
}
|
||||
|
||||
const __DEBUG_WARNED_BAD_BY_DEPENDENCIES_FILTERS__ =
|
||||
new Set<ComparableDependencyMap>()
|
||||
|
||||
/**
|
||||
* Filter modules by their dependency map.
|
||||
*
|
||||
* @param deps The dependency map to check for, can be a sparse array or have `null` to be any dependency ("dynamic"). **Order and size matters!**
|
||||
*
|
||||
* To do proper fingerprinting for modules:
|
||||
* @see {@link byDependencies.loose} to loosen the checks.
|
||||
* @see {@link byDependencies.relative} to compare dependencies relatively.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { loose, relative } = byDependencies
|
||||
*
|
||||
* // Logger's module ID is 5
|
||||
* // It has 3 dependencies [4, ?, 2]
|
||||
*
|
||||
* const [Logger] = lookupModule(byDependencies([4, null, 2]))
|
||||
* // or
|
||||
* const [Logger] = lookupModule(byDependencies([4, , 2]))
|
||||
*
|
||||
* // Relative dependencies
|
||||
* const [Logger] = lookupModule(byDependencies([relative(-1), null, 2]))
|
||||
*
|
||||
* // Nested dependencies
|
||||
* // The last dependency (module ID 2) would need to have zero dependencies:
|
||||
* const [Logger] = lookupModule(byDependencies([4, null, []]))
|
||||
*
|
||||
* // Loose dependencies
|
||||
* // Module having these dependencies: [4, ...], [4, ..., ...], [4, ..., ..., ...], etc. would match:
|
||||
* const [SomeOtherModule] = lookupModule(byDependencies(loose([4])))
|
||||
* ```
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [Logger] = lookupModule(
|
||||
* byProps('log')
|
||||
* .withDependencies([4, null, 2]),
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export const byDependencies = createFilterGenerator<Parameters<ByDependencies>>(
|
||||
([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id),
|
||||
deps => `revenge.deps(${depGenFilterKey(deps)})`,
|
||||
) as ByDependencies
|
||||
|
||||
byDependencies.loose = loose
|
||||
byDependencies.relative = relative
|
||||
|
||||
/**
|
||||
* Make this set of comparable dependencies as loose.
|
||||
*
|
||||
* Making a dependency loose skips the exact length check, but the order of the set dependencies still matters.
|
||||
* If you mark an index as dynamic, the same index must also be present in the other map during comparison to pass.
|
||||
*
|
||||
* @param deps The dependency map to make loose. This permanently modifies the array.
|
||||
* @returns The modified dependency map.
|
||||
*/
|
||||
function loose(deps: ComparableDependencyMap) {
|
||||
deps.l = true
|
||||
return deps
|
||||
}
|
||||
|
||||
const RelativeSignBit = 1 << 30
|
||||
const RelativeBit = 1 << 29
|
||||
const RelativeRootBit = 1 << 28
|
||||
const RelativeBitMask = ~(RelativeSignBit | RelativeBit | RelativeRootBit)
|
||||
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared.
|
||||
*
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Marks this dependency to compare relatively to the root (returning) module ID being compared. Useful for nested comparisons where you want to compare by the root module ID instead of the parent's module ID of the nested dependency.
|
||||
*/
|
||||
function relative(id: Metro.ModuleID, root?: boolean) {
|
||||
id = (id < 0 ? -id | RelativeSignBit : id) | RelativeBit
|
||||
if (root) id |= RelativeRootBit
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared, with an additional dependencies check.
|
||||
*
|
||||
* @param deps The dependency map to add the relative dependency to. This permanently modifies the array.
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Whether to use {@link relative.toRoot} instead of {@link relative}. Defaults to `false`.
|
||||
* @returns The modified dependency map.
|
||||
*
|
||||
* @see {@link byDependencies}
|
||||
* @see {@link relative}
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { relative } = byDependencies
|
||||
*
|
||||
* // This filter will match modules having one dependency that is its module ID + 1
|
||||
* // And module ID + 1 would have exactly two dependencies: [Any, 2]
|
||||
* byDependencies(
|
||||
* relative.withDependencies(
|
||||
* [null, 2],
|
||||
* 1, // Always the next module to the one being compared
|
||||
* true, // The module ID being compared matches the returning (root) module ID
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
relative.withDependencies = (
|
||||
deps: ComparableDependencyMap,
|
||||
id: Metro.ModuleID,
|
||||
root?: boolean,
|
||||
) => {
|
||||
deps.r = relative(id, root)
|
||||
return deps
|
||||
}
|
||||
|
||||
/**
|
||||
* Warns the developer about a bad `byDependencies` filter using `undefined` in its comparisons.
|
||||
*
|
||||
* - `undefined` should only be used as a fallback to when a module ID can really not be found.
|
||||
* - Use `null` instead to indicate a dynamic dependency.
|
||||
*/
|
||||
function DEBUG_warnBadByDependenciesFilter(
|
||||
deps: ComparableDependencyMap,
|
||||
index: number,
|
||||
) {
|
||||
// already warned
|
||||
if (__DEBUG_WARNED_BAD_BY_DEPENDENCIES_FILTERS__.has(deps)) return
|
||||
|
||||
nativeLoggingHook(
|
||||
`\u001b[33mBad ${byDependencies.name} filter, undefined ID at index ${index} (if intentional, set to null): [${depGenFilterKey(deps)}]\n${getCurrentStack()}\u001b[0m`,
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
function depCompare(
|
||||
a: Metro.ModuleID[],
|
||||
b: ComparableDependencyMap,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
): boolean {
|
||||
const lenA = a.length
|
||||
const lenB = b.length
|
||||
if (b.l ? lenA < lenB : lenA !== lenB) return false
|
||||
|
||||
for (let i = 0; i < lenB; i++) {
|
||||
const compare = b[i]
|
||||
|
||||
if (__DEV__ && compare === undefined)
|
||||
DEBUG_warnBadByDependenciesFilter(b, i)
|
||||
|
||||
// Skip dynamic
|
||||
if (compare == null) continue
|
||||
|
||||
const id = a[i]
|
||||
|
||||
// Check if it's an array (typeof is faster than Array.isArray)
|
||||
if (typeof compare === 'object') {
|
||||
// relative.withDependencies?
|
||||
if (compare.r && !depShallowCompare(compare.r, id, root, parent))
|
||||
return false
|
||||
|
||||
if (depCompare(getModuleDependencies(id)!, compare, root, id))
|
||||
continue
|
||||
} else if (depShallowCompare(compare, id, root, parent)) continue
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function depShallowCompare(
|
||||
compare: number,
|
||||
id: Metro.ModuleID,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
) {
|
||||
// relative?
|
||||
if (compare & RelativeBit)
|
||||
compare =
|
||||
(compare & RelativeRootBit ? root : parent) +
|
||||
depGetRelMagnitude(compare)
|
||||
|
||||
return compare === id
|
||||
}
|
||||
|
||||
function depGetRelMagnitude(dep: number) {
|
||||
const rootRelative = dep & RelativeSignBit
|
||||
dep = dep & RelativeBitMask
|
||||
if (rootRelative) dep = -dep
|
||||
return dep
|
||||
}
|
||||
|
||||
function depGenFilterKey(deps: ComparableDependencyMap): string {
|
||||
let key = ''
|
||||
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
const dep = deps[i]
|
||||
|
||||
if (dep == null) key += ','
|
||||
else if (typeof dep === 'object') {
|
||||
if (dep.l) key += '#'
|
||||
// relative.withDependencies?
|
||||
if (dep.r) key += depGenRelativeKeyPart(dep.r)
|
||||
|
||||
key += `[${depGenFilterKey(dep)}],`
|
||||
} else {
|
||||
if (dep & RelativeBit) key += depGenRelativeKeyPart(dep)
|
||||
else key += `${dep},`
|
||||
}
|
||||
}
|
||||
|
||||
return key.substring(0, key.length - 1)
|
||||
}
|
||||
|
||||
function depGenRelativeKeyPart(dep: number) {
|
||||
const magnitude = depGetRelMagnitude(dep)
|
||||
const prefix = dep & RelativeRootBit ? '~' : '^'
|
||||
return `${prefix}${magnitude},`
|
||||
}
|
||||
|
||||
export type Every = FilterGenerator<{
|
||||
<F1 extends FilterBase, F2 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
): Filter<
|
||||
FilterResult<F1> & FilterResult<F2>,
|
||||
LogicalOr<IsFilterWithExports<F1>, IsFilterWithExports<F2>>
|
||||
>
|
||||
<F1 extends FilterBase, F2 extends FilterBase, F3 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
): Filter<
|
||||
FilterResult<F1> & FilterResult<F2> & FilterResult<F3>,
|
||||
LogicalOr<
|
||||
LogicalOr<IsFilterWithExports<F1>, IsFilterWithExports<F2>>,
|
||||
IsFilterWithExports<F3>
|
||||
>
|
||||
>
|
||||
(...filters: FilterBase[]): Filter
|
||||
}>
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one, returning true if **every** filter matches.
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* byProps('x', 'name')
|
||||
* .and(byName('SomeName'))
|
||||
* .and(byDependencies([1, 485, null, 2])),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(every(
|
||||
* byProps('x', 'name'),
|
||||
* byName('SomeName'),
|
||||
* byDependencies([1, 485, null, 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export const every = createFilterGenerator<[...filters: Filter[]]>(
|
||||
(filters, id, exports) => {
|
||||
for (const filter of filters) {
|
||||
if (filter(id, exports)) continue
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
filters => `revenge.every(${filtersToKey(filters)})`,
|
||||
) as Every
|
||||
|
||||
export type Some = FilterGenerator<{
|
||||
<F1 extends FilterBase, F2 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
): Filter<
|
||||
FilterResult<F1> | FilterResult<F2>,
|
||||
IsFilterWithExports<F1> | IsFilterWithExports<F2>
|
||||
>
|
||||
<F1 extends FilterBase, F2 extends FilterBase, F3 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
): Filter<
|
||||
FilterResult<F1> | FilterResult<F2> | FilterResult<F3>,
|
||||
| IsFilterWithExports<F1>
|
||||
| IsFilterWithExports<F2>
|
||||
| IsFilterWithExports<F3>
|
||||
>
|
||||
(...filters: FilterBase[]): Filter
|
||||
}>
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one, returning true if **some** filters match.
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* byProps('x', 'name')
|
||||
* .or(byName('SomeName'))
|
||||
* .or(byDependencies([1, 485, null, 2])),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(some(
|
||||
* byProps('x', 'name'),
|
||||
* byName('SomeName'),
|
||||
* byDependencies([1, 485, null, 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export const some = createFilterGenerator<[...filters: FilterBase[]]>(
|
||||
(filters, id, exports) => {
|
||||
for (const filter of filters) if (filter(id, exports)) return true
|
||||
return false
|
||||
},
|
||||
filters => `revenge.some(${filtersToKey(filters)})`,
|
||||
) as Some
|
||||
|
||||
function filtersToKey(filters: FilterBase[]): string {
|
||||
let s = ''
|
||||
for (const filter of filters) s += `${filter.key},`
|
||||
return s.substring(0, s.length - 1)
|
||||
}
|
||||
|
||||
export type ModuleStateAware = FilterGenerator<
|
||||
<IF extends FilterBase>(
|
||||
initializedFilter: IF,
|
||||
uninitializedFilter: FilterBase<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<IF>, false>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules depending on their initialized state. **Initialized modules with bad exports are skipped.**
|
||||
*
|
||||
* @param initializedFilter The filter to use for initialized modules.
|
||||
* @param uninitializedFilter The filter to use for uninitialized modules.
|
||||
* @param strict Whether to also filter with `uninitializedFilter` after `initializedFilter` passes, confirming the module is definitely the correct module. Defaults to `false`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // will filter byProps('x') for initialized modules
|
||||
* // and byDependencies([1, 485, null, 2]) for uninitialized modules
|
||||
* const [SomeModule] = lookupModule(moduleStateAware(
|
||||
* byProps('x'),
|
||||
* byDependencies([1, 485, null, 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export const moduleStateAware = createFilterGenerator<
|
||||
Parameters<ModuleStateAware>
|
||||
>(
|
||||
([initializedFilter, uninitializedFilter, strict], id, exports) => {
|
||||
if (isModuleInitialized(id)) {
|
||||
if (mInitialized.has(id) && initializedFilter(id, exports))
|
||||
return strict ? uninitializedFilter(id) : true
|
||||
return false
|
||||
}
|
||||
|
||||
return uninitializedFilter(id)
|
||||
},
|
||||
([f1, f2]) => `revenge.moduleStateAware(${f1.key},${f2.key})`,
|
||||
) as ModuleStateAware
|
||||
|
||||
export type PreferExports = FilterGenerator<
|
||||
<WEF extends FilterBase>(
|
||||
withExportsFilter: WEF,
|
||||
exportslessFilter: FilterBase<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<WEF>, false>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules depending on if their exports are available and filterable.
|
||||
*
|
||||
* @see {@link isModuleExportsBad} for more information on what is considered bad module exports.
|
||||
*
|
||||
* @see {@link moduleStateAware} for an alternative that filters based on the module's initialized state.
|
||||
*
|
||||
* @param withExportsFilter The filter to use for modules with proper exports.
|
||||
* @param exportslessFilter The filter to use for modules without proper exports (uninitialized or bad).
|
||||
* @param strict Whether to also filter with `exportslessFilter` after `withExportsFilter` passes, confirming the module is definitely the correct module. Defaults to `false`.
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* byDependencies([1, 485, null, 2])
|
||||
* .withExports(byProps('x')),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // will filter byProps('x') for modules with proper exports
|
||||
* // and byDependencies([1, 485, null, 2]) for without proper exports (uninitialized or bad)
|
||||
* const [SomeModule] = lookupModule(preferExports(
|
||||
* byProps('x'),
|
||||
* byDependencies([1, 485, null, 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export const preferExports = createFilterGenerator<Parameters<PreferExports>>(
|
||||
([withExportsFilter, exportslessFilter, strict], id, exports) => {
|
||||
if (mInitialized.has(id)) {
|
||||
if (withExportsFilter(id, exports))
|
||||
return strict ? exportslessFilter(id) : true
|
||||
return false
|
||||
}
|
||||
|
||||
return exportslessFilter(id)
|
||||
},
|
||||
([f1, f2]) => `revenge.preferExports(${f1.key},${f2.key})`,
|
||||
) as PreferExports
|
||||
189
lib/modules/src/finders/filters/composite.ts
Normal file
189
lib/modules/src/finders/filters/composite.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import { FilterFlag } from './constants'
|
||||
import { createFilterGenerator } from './utils'
|
||||
import type { LogicalAnd } from '@revenge-mod/utils/types'
|
||||
import type { Metro } from '#modules/src/types'
|
||||
import type {
|
||||
Filter,
|
||||
FilterBase,
|
||||
FilterGenerator,
|
||||
FilterRequiresExports,
|
||||
FilterResult,
|
||||
} from '.'
|
||||
|
||||
const compositeHandler = <G extends (a: FilterBase, b: FilterBase) => any>(
|
||||
sameHandler: G,
|
||||
dynamicHandler: G,
|
||||
) =>
|
||||
((a, b) =>
|
||||
a.flags === b.flags
|
||||
? sameHandler(a, b)
|
||||
: a.flags === FilterFlag.RequiresExports
|
||||
? dynamicHandler(a, b)
|
||||
: dynamicHandler(b, a)) as G
|
||||
|
||||
const compositeArrayHandler = <
|
||||
G extends (args: [a: FilterBase, b: FilterBase]) => any,
|
||||
>(
|
||||
sameHandler: G,
|
||||
dynamicHandler: G,
|
||||
) =>
|
||||
(([a, b]) =>
|
||||
a.flags === b.flags
|
||||
? sameHandler([a, b])
|
||||
: a.flags === FilterFlag.RequiresExports
|
||||
? dynamicHandler([a, b])
|
||||
: dynamicHandler([b, a])) as G
|
||||
|
||||
export type And = FilterGenerator<
|
||||
<F1 extends FilterBase, F2 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
) => Filter<
|
||||
FilterResult<F1> & FilterResult<F2>,
|
||||
LogicalAnd<FilterRequiresExports<F1>, FilterRequiresExports<F2>>
|
||||
>
|
||||
>
|
||||
|
||||
const andKeyGenerator = ([a, b]: Parameters<And>) =>
|
||||
`revenge.and(${a.key},${b.key})`
|
||||
|
||||
const sameFlagsAnd = createFilterGenerator(
|
||||
([a, b], id, exports) => a(id, exports) && b(id, exports),
|
||||
andKeyGenerator,
|
||||
([a]) => a.flags,
|
||||
) as And
|
||||
|
||||
const andFallbackCache = new WeakMap<FilterBase, Set<Metro.ModuleID>>()
|
||||
|
||||
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)
|
||||
return (
|
||||
// biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized
|
||||
(cache && cache.has(id)) || fallbackFilter(id, exports)
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const result = fallbackFilter(id)
|
||||
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()))
|
||||
set.add(id)
|
||||
}
|
||||
return result
|
||||
},
|
||||
andKeyGenerator,
|
||||
FilterFlag.Dynamic,
|
||||
) as And
|
||||
|
||||
/**
|
||||
* Combines two filters into one, returning true if **every** filter matches.
|
||||
*
|
||||
* If each filter has different flags,
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* withProps('x', 'name')
|
||||
* .and(withName('SomeName'))
|
||||
* .and(withDependencies([1, 485, null, 2])),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* and(
|
||||
* and(withProps('x', 'name'), withName('SomeName')),
|
||||
* withDependencies([1, 485, null, 2]),
|
||||
* ),
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export const and = Object.assign(
|
||||
compositeHandler(sameFlagsAnd, dynamicFlagsAnd),
|
||||
{
|
||||
keyFor: compositeArrayHandler(
|
||||
sameFlagsAnd.keyFor,
|
||||
dynamicFlagsAnd.keyFor,
|
||||
),
|
||||
flagsFor: compositeArrayHandler(
|
||||
sameFlagsAnd.flagsFor,
|
||||
dynamicFlagsAnd.flagsFor,
|
||||
),
|
||||
},
|
||||
) satisfies And
|
||||
|
||||
export type Or = FilterGenerator<
|
||||
<F1 extends FilterBase, F2 extends FilterBase>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
) => Filter<
|
||||
FilterResult<F1> | FilterResult<F2>,
|
||||
LogicalAnd<FilterRequiresExports<F1>, FilterRequiresExports<F2>>
|
||||
>
|
||||
>
|
||||
|
||||
const orKeyGenerator = ([a, b]: Parameters<Or>) =>
|
||||
`revenge.or(${a.key},${b.key})`
|
||||
|
||||
const sameFlagOr = createFilterGenerator(
|
||||
([a, b], id, exports) => a(id, exports) || b(id, exports),
|
||||
orKeyGenerator,
|
||||
([a]) => a.flags,
|
||||
) 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)
|
||||
},
|
||||
orKeyGenerator,
|
||||
FilterFlag.Dynamic,
|
||||
) as Or
|
||||
|
||||
/**
|
||||
* Combines two filters into one, returning true if **some** filters match.
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* withProps('x', 'name')
|
||||
* .or(withName('SomeName'))
|
||||
* .or(withDependencies([1, 485, null, 2])),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [SomeModule] = lookupModule(
|
||||
* or(
|
||||
* or(withProps('x', 'name'), withName('SomeName')),
|
||||
* withDependencies([1, 485, null, 2]),
|
||||
* ),
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export const or = Object.assign(
|
||||
compositeHandler<Or>(sameFlagOr, dynamicFlagOr),
|
||||
{
|
||||
keyFor: compositeArrayHandler(sameFlagOr.keyFor, dynamicFlagOr.keyFor),
|
||||
flagsFor: compositeArrayHandler(
|
||||
sameFlagOr.flagsFor,
|
||||
dynamicFlagOr.flagsFor,
|
||||
),
|
||||
},
|
||||
) satisfies Or
|
||||
17
lib/modules/src/finders/filters/constants.ts
Normal file
17
lib/modules/src/finders/filters/constants.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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
|
||||
247
lib/modules/src/finders/filters/dynamic.ts
Normal file
247
lib/modules/src/finders/filters/dynamic.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import { getCurrentStack } from '@revenge-mod/utils/error'
|
||||
import { getModuleDependencies } from '../../metro/utils'
|
||||
import { FilterFlag } from './constants'
|
||||
import { createFilterGenerator } from './utils'
|
||||
import type { Metro } from '../../types'
|
||||
import type { Filter, FilterGenerator } from './utils'
|
||||
|
||||
export interface ComparableDependencyMap
|
||||
extends Array<
|
||||
Metro.ModuleID | number | null | undefined | ComparableDependencyMap
|
||||
> {
|
||||
l?: boolean
|
||||
r?: number
|
||||
}
|
||||
|
||||
const __DEBUG_WARNED_BAD_BY_DEPENDENCIES_FILTERS__ =
|
||||
new Set<ComparableDependencyMap>()
|
||||
|
||||
/**
|
||||
* Filter modules by their dependency map.
|
||||
*
|
||||
* @param deps The dependency map to check for, can be a sparse array or have `null` to be any dependency ("dynamic"). **Order and size matters!**
|
||||
*
|
||||
* To do proper fingerprinting for modules:
|
||||
* @see {@link withDependencies.loose} to loosen the checks.
|
||||
* @see {@link withDependencies.relative} to compare dependencies relatively.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { loose, relative } = withDependencies
|
||||
*
|
||||
* // Logger's module ID is 5
|
||||
* // It has 3 dependencies [4, ?, 2]
|
||||
*
|
||||
* const [Logger] = lookupModule(withDependencies([4, null, 2]))
|
||||
* // or
|
||||
* const [Logger] = lookupModule(withDependencies([4, , 2]))
|
||||
*
|
||||
* // Relative dependencies
|
||||
* const [Logger] = lookupModule(withDependencies([relative(-1), null, 2]))
|
||||
*
|
||||
* // Nested dependencies
|
||||
* // The last dependency (module ID 2) would need to have zero dependencies:
|
||||
* const [Logger] = lookupModule(withDependencies([4, null, []]))
|
||||
*
|
||||
* // Loose dependencies
|
||||
* // Module having these dependencies: [4, ...], [4, ..., ...], [4, ..., ..., ...], etc. would match:
|
||||
* const [SomeOtherModule] = lookupModule(withDependencies(loose([4])))
|
||||
* ```
|
||||
*
|
||||
* @example With filter helpers (preferred)
|
||||
* ```ts
|
||||
* const [Logger] = lookupModule(
|
||||
* withProps('log')
|
||||
* .withDependencies([4, null, 2]),
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export const withDependencies = createFilterGenerator<
|
||||
Parameters<WithDependencies>
|
||||
>(
|
||||
([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id),
|
||||
deps => `revenge.deps(${depGenFilterKey(deps)})`,
|
||||
FilterFlag.Dynamic,
|
||||
) as WithDependencies
|
||||
|
||||
withDependencies.loose = loose
|
||||
withDependencies.relative = relative
|
||||
|
||||
type WithDependencies = FilterGenerator<
|
||||
<T>(deps: ComparableDependencyMap) => Filter<T, false>
|
||||
> & {
|
||||
loose: typeof loose
|
||||
relative: typeof relative
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this set of comparable dependencies as loose.
|
||||
*
|
||||
* Making a dependency loose skips the exact length check, but the order of the set dependencies still matters.
|
||||
* If you mark an index as dynamic, the same index must also be present in the other map during comparison to pass.
|
||||
*
|
||||
* @param deps The dependency map to make loose. This permanently modifies the array.
|
||||
* @returns The modified dependency map.
|
||||
*/
|
||||
function loose(deps: ComparableDependencyMap) {
|
||||
deps.l = true
|
||||
return deps
|
||||
}
|
||||
|
||||
const RelativeSignBit = 1 << 30
|
||||
const RelativeBit = 1 << 29
|
||||
const RelativeRootBit = 1 << 28
|
||||
const RelativeBitMask = ~(RelativeSignBit | RelativeBit | RelativeRootBit)
|
||||
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared.
|
||||
*
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Marks this dependency to compare relatively to the root (returning) module ID being compared. Useful for nested comparisons where you want to compare by the root module ID instead of the parent's module ID of the nested dependency.
|
||||
*/
|
||||
function relative(id: Metro.ModuleID, root?: boolean) {
|
||||
id = (id < 0 ? -id | RelativeSignBit : id) | RelativeBit
|
||||
if (root) id |= RelativeRootBit
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared, with an additional dependencies check.
|
||||
*
|
||||
* @param deps The dependency map to add the relative dependency to. This permanently modifies the array.
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Whether to use {@link relative.toRoot} instead of {@link relative}. Defaults to `false`.
|
||||
* @returns The modified dependency map.
|
||||
*
|
||||
* @see {@link withDependencies}
|
||||
* @see {@link relative}
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { relative } = withDependencies
|
||||
*
|
||||
* // This filter will match modules having one dependency that is its module ID + 1
|
||||
* // And module ID + 1 would have exactly two dependencies: [Any, 2]
|
||||
* withDependencies(
|
||||
* relative.withDependencies(
|
||||
* [null, 2],
|
||||
* 1, // Always the next module to the one being compared
|
||||
* true, // The module ID being compared matches the returning (root) module ID
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
relative.withDependencies = (
|
||||
deps: ComparableDependencyMap,
|
||||
id: Metro.ModuleID,
|
||||
root?: boolean,
|
||||
) => {
|
||||
deps.r = relative(id, root)
|
||||
return deps
|
||||
}
|
||||
|
||||
/**
|
||||
* Warns the developer about a bad `withDependencies` filter using `undefined` in its comparisons.
|
||||
*
|
||||
* - `undefined` should only be used as a fallback to when a module ID can really not be found.
|
||||
* - Use `null` instead to indicate a dynamic dependency.
|
||||
*/
|
||||
function DEBUG_warnBadWithDependenciesFilter(
|
||||
deps: ComparableDependencyMap,
|
||||
index: number,
|
||||
) {
|
||||
// already warned
|
||||
if (__DEBUG_WARNED_BAD_BY_DEPENDENCIES_FILTERS__.has(deps)) return
|
||||
|
||||
nativeLoggingHook(
|
||||
`\u001b[33mBad ${withDependencies.name} filter, undefined ID at index ${index} (if intentional, set to null): [${depGenFilterKey(deps)}]\n${getCurrentStack()}\u001b[0m`,
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
function depCompare(
|
||||
a: Metro.ModuleID[],
|
||||
b: ComparableDependencyMap,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
): boolean {
|
||||
const lenA = a.length
|
||||
const lenB = b.length
|
||||
if (b.l ? lenA < lenB : lenA !== lenB) return false
|
||||
|
||||
for (let i = 0; i < lenB; i++) {
|
||||
const compare = b[i]
|
||||
|
||||
if (__DEV__ && compare === undefined)
|
||||
DEBUG_warnBadWithDependenciesFilter(b, i)
|
||||
|
||||
// Skip dynamic
|
||||
if (compare == null) continue
|
||||
|
||||
const id = a[i]
|
||||
|
||||
// Check if it's an array (typeof is faster than Array.isArray)
|
||||
if (typeof compare === 'object') {
|
||||
// relative.withDependencies?
|
||||
if (compare.r && !depShallowCompare(compare.r, id, root, parent))
|
||||
return false
|
||||
|
||||
if (depCompare(getModuleDependencies(id)!, compare, root, id))
|
||||
continue
|
||||
} else if (depShallowCompare(compare, id, root, parent)) continue
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function depShallowCompare(
|
||||
compare: number,
|
||||
id: Metro.ModuleID,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
) {
|
||||
// relative?
|
||||
if (compare & RelativeBit)
|
||||
compare =
|
||||
(compare & RelativeRootBit ? root : parent) +
|
||||
depGetRelMagnitude(compare)
|
||||
|
||||
return compare === id
|
||||
}
|
||||
|
||||
function depGetRelMagnitude(dep: number) {
|
||||
const rootRelative = dep & RelativeSignBit
|
||||
dep = dep & RelativeBitMask
|
||||
if (rootRelative) dep = -dep
|
||||
return dep
|
||||
}
|
||||
|
||||
function depGenFilterKey(deps: ComparableDependencyMap): string {
|
||||
let key = ''
|
||||
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
const dep = deps[i]
|
||||
|
||||
if (dep == null) key += ','
|
||||
else if (typeof dep === 'object') {
|
||||
if (dep.l) key += '#'
|
||||
// relative.withDependencies?
|
||||
if (dep.r) key += depGenRelativeKeyPart(dep.r)
|
||||
|
||||
key += `[${depGenFilterKey(dep)}],`
|
||||
} else {
|
||||
if (dep & RelativeBit) key += depGenRelativeKeyPart(dep)
|
||||
else key += `${dep},`
|
||||
}
|
||||
}
|
||||
|
||||
return key.substring(0, key.length - 1)
|
||||
}
|
||||
|
||||
function depGenRelativeKeyPart(dep: number) {
|
||||
const magnitude = depGetRelMagnitude(dep)
|
||||
const prefix = dep & RelativeRootBit ? '~' : '^'
|
||||
return `${prefix}${magnitude},`
|
||||
}
|
||||
137
lib/modules/src/finders/filters/index.ts
Normal file
137
lib/modules/src/finders/filters/index.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { FilterFlag } from './constants'
|
||||
import { createFilterGenerator } from './utils'
|
||||
import type { Filter, FilterGenerator } from './utils'
|
||||
|
||||
export * from './constants'
|
||||
export * from './utils'
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having all of the specified properties.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
* @param props More properties to check for (optional).
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [React] = lookupModule(withProps<typeof import('react')>('createElement'))
|
||||
* // const React: typeof import('react')
|
||||
* ```
|
||||
*/
|
||||
export const withProps = createFilterGenerator<Parameters<WithProps>>(
|
||||
(props, _, exports) => {
|
||||
const type = typeof exports
|
||||
if (type === 'object' || type === 'function') {
|
||||
for (const prop of props) {
|
||||
if (prop in exports) continue
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
props => `revenge.props(${props.join(',')})`,
|
||||
FilterFlag.RequiresExports,
|
||||
) as WithProps
|
||||
|
||||
export type WithProps = FilterGenerator<
|
||||
<T extends Record<string, any> = Record<string, any>>(
|
||||
prop: keyof T,
|
||||
...props: Array<keyof T>
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having none of the specified properties.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
* @param props More properties to check for (optional).
|
||||
*/
|
||||
export const withoutProps = createFilterGenerator<Parameters<WithoutProps>>(
|
||||
(props, _, exports) => {
|
||||
const type = typeof exports
|
||||
if (type === 'object' || type === 'function')
|
||||
for (const prop of props) if (prop in exports) return false
|
||||
|
||||
return true
|
||||
},
|
||||
props => `revenge.withoutProps(${props.join(',')})`,
|
||||
FilterFlag.RequiresExports,
|
||||
) as WithoutProps
|
||||
|
||||
export type WithoutProps = FilterGenerator<
|
||||
<T extends Record<string, any>>(
|
||||
prop: string,
|
||||
...props: string[]
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having only the specified property.
|
||||
*
|
||||
* @param prop The property to check for.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [FormSwitchModule] = lookupModule(withSingleProp('FormSwitch'))
|
||||
* // const FormSwitchModule: { FormSwitch: any }
|
||||
* ```
|
||||
*/
|
||||
export const withSingleProp = createFilterGenerator<Parameters<WithSingleProp>>(
|
||||
([prop], _, exports) => {
|
||||
if (typeof exports === 'object' && prop in exports)
|
||||
return Object.keys(exports).length === 1
|
||||
|
||||
return false
|
||||
},
|
||||
([prop]) => `revenge.singleProp(${prop})`,
|
||||
FilterFlag.RequiresExports,
|
||||
) as WithSingleProp
|
||||
|
||||
export type WithSingleProp = FilterGenerator<
|
||||
<T extends Record<string, any>>(prop: keyof T) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having the specified name.
|
||||
*
|
||||
* Usually used for function components or classes.
|
||||
*
|
||||
* @param name The name to check for.
|
||||
*
|
||||
* @example Auto-typing as object
|
||||
* ```ts
|
||||
* const [SomeComponent] = lookupModule(withName('SomeComponent'))
|
||||
* // const SomeComponent: { name: 'SomeComponent' }
|
||||
* ```
|
||||
*
|
||||
* @example Typing as function component
|
||||
* ```ts
|
||||
* type MyComponent = React.FC<{ foo: string }>
|
||||
*
|
||||
* const [MyComponent] = lookupModule(withName<MyComponent>('MyComponent'))
|
||||
* // const MyComponent: MyComponent & { name: 'MyComponent' }
|
||||
* ```
|
||||
*
|
||||
* @example Typing as class
|
||||
* ```
|
||||
* interface SomeClass {
|
||||
* someMethod(): void
|
||||
* }
|
||||
*
|
||||
* const [SomeClass] = lookupModule(withName<{ new(param: string): SomeClass }>('SomeClass'))
|
||||
* // const SomeClass: { new(): SomeClass, name: 'SomeClass' }
|
||||
*/
|
||||
export const withName = createFilterGenerator<Parameters<WithName>>(
|
||||
([name], _, exports) => exports.name === name,
|
||||
([name]) => `revenge.name(${name})`,
|
||||
FilterFlag.RequiresExports,
|
||||
) as WithName
|
||||
|
||||
export type WithName = FilterGenerator<
|
||||
<T extends object = object>(name: string) => Filter<T, true>
|
||||
>
|
||||
|
||||
export * from './composite'
|
||||
export * from './dynamic'
|
||||
166
lib/modules/src/finders/filters/utils.ts
Normal file
166
lib/modules/src/finders/filters/utils.ts
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import { and, or } from './composite'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { If, LogicalAnd } from '@revenge-mod/utils/types'
|
||||
import type { FilterFlag } from './constants'
|
||||
|
||||
export type FilterResult<F> = F extends Filter<infer R, boolean>
|
||||
? R
|
||||
: F extends FilterBase<infer R>
|
||||
? R
|
||||
: never
|
||||
|
||||
export type FilterRequiresExports<F> = F extends Filter<any, infer RE>
|
||||
? RE
|
||||
: F extends FilterBase<any, infer RE>
|
||||
? RE
|
||||
: never
|
||||
|
||||
export interface FilterBase<
|
||||
_Result = any,
|
||||
RequiresExports extends boolean = boolean,
|
||||
> {
|
||||
(
|
||||
...args: If<
|
||||
RequiresExports,
|
||||
[id: Metro.ModuleID, exports: Metro.ModuleExports],
|
||||
[id: Metro.ModuleID, exports?: never]
|
||||
>
|
||||
): boolean
|
||||
key: string
|
||||
flags: If<
|
||||
RequiresExports,
|
||||
(typeof FilterFlag)['RequiresExports'],
|
||||
FilterFlag
|
||||
>
|
||||
}
|
||||
|
||||
export type Filter<
|
||||
Result = any,
|
||||
RequiresExports extends boolean = boolean,
|
||||
> = FilterHelpers & FilterBase<Result, RequiresExports>
|
||||
|
||||
export interface FilterHelpers {
|
||||
/**
|
||||
* Manually the key for this filter.
|
||||
*
|
||||
* **Don't use this unless you know what you're doing.** Only API exports should be using
|
||||
*
|
||||
* @param key The key to set for this filter.
|
||||
*/
|
||||
keyAs<T extends FilterBase>(this: T, key: string): T
|
||||
/**
|
||||
* Combines this filter with another filter, returning a new filter that matches if **both** filters match.
|
||||
*
|
||||
* @param filter The filter to combine with.
|
||||
*/
|
||||
and<T extends FilterBase, F extends FilterBase>(
|
||||
this: T,
|
||||
filter: F,
|
||||
): Filter<
|
||||
FilterResult<T> & FilterResult<F>,
|
||||
LogicalAnd<FilterRequiresExports<T>, FilterRequiresExports<F>>
|
||||
>
|
||||
/**
|
||||
* Combines this filter with another filter, returning a new filter that matches if **either** filter matches.
|
||||
*
|
||||
* Note that exportsless filters must come first to avoid gotchas with uninitialized modules.
|
||||
*
|
||||
* @param filter The filter to combine with.
|
||||
*/
|
||||
or<T extends FilterBase, F extends FilterBase>(
|
||||
this: T,
|
||||
filter: F,
|
||||
): Filter<
|
||||
FilterResult<T> | FilterResult<F>,
|
||||
LogicalAnd<FilterRequiresExports<T>, FilterRequiresExports<F>>
|
||||
>
|
||||
}
|
||||
|
||||
export type FilterGenerator<G extends (...args: any[]) => Filter> = G & {
|
||||
keyFor(args: Parameters<G>): string
|
||||
flagsFor(args: Parameters<G>): FilterFlag
|
||||
}
|
||||
|
||||
const Helpers: FilterHelpers = Object.setPrototypeOf(
|
||||
{
|
||||
keyAs(key) {
|
||||
this.key = key
|
||||
return this
|
||||
},
|
||||
and(filter) {
|
||||
return and(this, filter)
|
||||
},
|
||||
or(filter) {
|
||||
return or(this, filter)
|
||||
},
|
||||
} satisfies FilterHelpers,
|
||||
Function.prototype,
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a filter generator.
|
||||
*
|
||||
* @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.
|
||||
* @returns A function that generates a filter with the specified arguments.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const custom = createFilterGenerator<[arg1: number, arg2: string]>(
|
||||
* ([arg1, arg2], id, exports) => {
|
||||
* // filter logic
|
||||
* return true
|
||||
* },
|
||||
* ([arg1, arg2]) => `custom(${arg1}, ${arg2})`
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @see {@link withProps} for an example on custom-typed filters.
|
||||
*/
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (
|
||||
args: A,
|
||||
id: Metro.ModuleID,
|
||||
exports: Metro.ModuleExports,
|
||||
) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
flagFor: ((args: A) => FilterFlag) | FilterFlag,
|
||||
): FilterGenerator<(...args: A) => Filter<any, true>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (args: A, id: Metro.ModuleID) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
flagFor: ((args: A) => FilterFlag) | FilterFlag,
|
||||
): FilterGenerator<(...args: A) => Filter<any, false>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (
|
||||
args: A,
|
||||
id: Metro.ModuleID,
|
||||
exports?: Metro.ModuleExports,
|
||||
) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
flagFor: ((args: A) => FilterFlag) | FilterFlag,
|
||||
): FilterGenerator<(...args: A) => Filter> {
|
||||
type GeneratorType = ReturnType<typeof createFilterGenerator<A>>
|
||||
|
||||
const isFlagsStatic = typeof flagFor === 'number'
|
||||
|
||||
const generator: GeneratorType = (...args: A) => {
|
||||
const filter_ = ((id: Metro.ModuleID, exports?: Metro.ModuleExports) =>
|
||||
filter(args, id, exports)) as ReturnType<GeneratorType>
|
||||
|
||||
filter_.key = keyFor(args)
|
||||
filter_.flags = isFlagsStatic ? flagFor : flagFor(args)
|
||||
return Object.setPrototypeOf(filter_, Helpers)
|
||||
}
|
||||
|
||||
generator.flagsFor = isFlagsStatic
|
||||
? () => flagFor
|
||||
: (args: A) => flagFor(args)
|
||||
|
||||
generator.keyFor = keyFor
|
||||
|
||||
return generator
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { asap, noop } from '@revenge-mod/utils/callback'
|
||||
import {
|
||||
lookupModule,
|
||||
lookupModuleByImportedPath,
|
||||
lookupModules,
|
||||
lookupModuleWithImportedPath,
|
||||
} from './lookup'
|
||||
import { waitForModuleByImportedPath, waitForModules } from './wait'
|
||||
import { waitForModules, waitForModuleWithImportedPath } from './wait'
|
||||
import type { If } from '@revenge-mod/utils/types'
|
||||
import type { MaybeDefaultExportMatched, Metro } from '../types'
|
||||
import type { RunFilterReturnExportsOptions } from './_internal'
|
||||
|
|
@ -12,7 +12,7 @@ import type { Filter, FilterResult } from './filters'
|
|||
import type { LookupModulesOptions } from './lookup'
|
||||
import type { WaitForModulesOptions } from './wait'
|
||||
|
||||
export type GetModuleOptions<
|
||||
export type GetModulesOptions<
|
||||
ReturnNamespace extends boolean = boolean,
|
||||
Uninitialized extends boolean = boolean,
|
||||
All extends boolean = boolean,
|
||||
|
|
@ -26,16 +26,16 @@ export type GetModuleOptions<
|
|||
max?: number
|
||||
}
|
||||
|
||||
export type GetModuleResult<
|
||||
export type GetModulesResult<
|
||||
F extends Filter,
|
||||
O extends GetModuleOptions,
|
||||
O extends GetModulesOptions,
|
||||
> = O extends RunFilterReturnExportsOptions<true>
|
||||
? MaybeDefaultExportMatched<FilterResult<F>>
|
||||
: FilterResult<F>
|
||||
|
||||
export type GetModuleCallback<T> = (exports: T, id: Metro.ModuleID) => any
|
||||
export type GetModulesCallback<T> = (exports: T, id: Metro.ModuleID) => any
|
||||
|
||||
export type GetModuleUnsubscribeFunction = () => void
|
||||
export type GetModulesUnsubscribeFunction = () => void
|
||||
|
||||
/**
|
||||
* Get modules matching the filter.
|
||||
|
|
@ -48,40 +48,40 @@ export type GetModuleUnsubscribeFunction = () => void
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* getModule(byProps<typeof import('react')>('createElement'), React => {
|
||||
* getModules(withProps<typeof import('react')>('createElement'), React => {
|
||||
* // Immediately called because React is always initialized when plugins are loaded
|
||||
* })
|
||||
*
|
||||
* getModule(byProps<typeof import('@shopify/flash-list')>('FlashList'), FlashList => {
|
||||
* getModules(withProps<typeof import('@shopify/flash-list')>('FlashList'), FlashList => {
|
||||
* // Called when the module is initialized
|
||||
* })
|
||||
*
|
||||
* // Get multiple modules matching the filter
|
||||
* getModule(byProps<ReactNative.AssetsRegistry>('registerAsset'), AssetsRegistry => {
|
||||
* getModules(withProps<ReactNative.AssetsRegistry>('registerAsset'), AssetsRegistry => {
|
||||
* // Called 2 times, once for each module that matches the filter
|
||||
* }, { max: 2 })
|
||||
* ```
|
||||
*/
|
||||
export function getModule<F extends Filter>(
|
||||
export function getModules<F extends Filter>(
|
||||
filter: F,
|
||||
callback: GetModuleCallback<FilterResult<F>>,
|
||||
): GetModuleUnsubscribeFunction
|
||||
callback: GetModulesCallback<FilterResult<F>>,
|
||||
): GetModulesUnsubscribeFunction
|
||||
|
||||
export function getModule<
|
||||
export function getModules<
|
||||
F extends Filter,
|
||||
const O extends F extends Filter<any, infer WE>
|
||||
? If<WE, GetModuleOptions<boolean, boolean, false>, GetModuleOptions>
|
||||
const O extends F extends Filter<any, infer RE>
|
||||
? If<RE, GetModulesOptions<boolean, boolean, false>, GetModulesOptions>
|
||||
: never,
|
||||
>(
|
||||
filter: F,
|
||||
callback: GetModuleCallback<FilterResult<F>>,
|
||||
callback: GetModulesCallback<FilterResult<F>>,
|
||||
options: O,
|
||||
): GetModuleUnsubscribeFunction
|
||||
): GetModulesUnsubscribeFunction
|
||||
|
||||
export function getModule(
|
||||
export function getModules(
|
||||
filter: Filter,
|
||||
callback: GetModuleCallback<any>,
|
||||
options?: GetModuleOptions,
|
||||
callback: GetModulesCallback<any>,
|
||||
options?: GetModulesOptions,
|
||||
) {
|
||||
let max = options?.max ?? 1
|
||||
|
||||
|
|
@ -123,22 +123,22 @@ export function getModule(
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* getModuleByImportedPath('modules/main_tabs_v2/native/settings/SettingsConstants.tsx', SettingsConstants => {
|
||||
* getModuleWithImportedPath('modules/main_tabs_v2/native/settings/SettingsConstants.tsx', SettingsConstants => {
|
||||
* console.log('Settings page opened') // Logs once the module is initialized
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function getModuleByImportedPath<T>(
|
||||
export function getModuleWithImportedPath<T>(
|
||||
path: string,
|
||||
callback: GetModuleCallback<T>,
|
||||
): GetModuleUnsubscribeFunction {
|
||||
const [exports, id] = lookupModuleByImportedPath(path)
|
||||
callback: GetModulesCallback<T>,
|
||||
): GetModulesUnsubscribeFunction {
|
||||
const [exports, id] = lookupModuleWithImportedPath(path)
|
||||
if (id !== undefined) {
|
||||
callback(exports, id)
|
||||
return noop
|
||||
}
|
||||
|
||||
const unsub = waitForModuleByImportedPath(path, (exports, id) => {
|
||||
const unsub = waitForModuleWithImportedPath(path, (exports, id) => {
|
||||
unsub()
|
||||
callback(exports, id)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -128,8 +128,7 @@ type LookupNotFoundResult = typeof NotFoundResult
|
|||
/**
|
||||
* Lookup modules.
|
||||
*
|
||||
* You can lookup uninitialized modules by passing `options.uninitialized` when filtering via exportsless filters (eg. `byDependencies`).
|
||||
* Use the `moduleStateAware` helper to filter dynamically based on whether the module is initialized or not.
|
||||
* You can lookup uninitialized modules by passing `options.uninitialized` when filtering via exportsless filters (eg. `withDependencies`).
|
||||
*
|
||||
* @param filter The filter to use.
|
||||
* @param options The options to use for the lookup.
|
||||
|
|
@ -137,9 +136,9 @@ type LookupNotFoundResult = typeof NotFoundResult
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const lookup = lookupModules(byProps('x'))
|
||||
* const lookup = lookupModules(withProps('x'))
|
||||
* // Log all module exports that has exports.x
|
||||
* for (const exports of lookup) console.log(exports)
|
||||
* for (const [exports, id] of lookup) console.log(id, exports)
|
||||
* ```
|
||||
*/
|
||||
export function lookupModules<F extends Filter>(
|
||||
|
|
@ -148,9 +147,9 @@ export function lookupModules<F extends Filter>(
|
|||
|
||||
export function lookupModules<
|
||||
F extends Filter,
|
||||
const O extends F extends Filter<any, infer WE>
|
||||
const O extends F extends Filter<any, infer RE>
|
||||
? If<
|
||||
WE,
|
||||
RE,
|
||||
LookupModulesOptions<boolean, false, false>,
|
||||
LookupModulesOptions
|
||||
>
|
||||
|
|
@ -286,7 +285,7 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const React = lookupModule(byProps<typeof import('react')>('createElement'))
|
||||
* const [React, ReactModuleId] = lookupModule(withProps<typeof import('react')>('createElement'))
|
||||
* ```
|
||||
*/
|
||||
export function lookupModule<F extends Filter>(
|
||||
|
|
@ -295,9 +294,9 @@ export function lookupModule<F extends Filter>(
|
|||
|
||||
export function lookupModule<
|
||||
F extends Filter,
|
||||
const O extends F extends Filter<any, infer WE>
|
||||
const O extends F extends Filter<any, infer RE>
|
||||
? If<
|
||||
WE,
|
||||
RE,
|
||||
LookupModulesOptions<boolean, false, false>,
|
||||
LookupModulesOptions
|
||||
>
|
||||
|
|
@ -415,10 +414,10 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const [{ default: Logger }] = lookupModuleByImportedPath<{ default: typeof DiscordModules.Logger }>('modules/debug/Logger.tsx')
|
||||
* const [{ default: Logger }] = lookupModuleWithImportedPath<{ default: typeof DiscordModules.Logger }>('modules/debug/Logger.tsx')
|
||||
* ```
|
||||
*/
|
||||
export function lookupModuleByImportedPath<T = any>(
|
||||
export function lookupModuleWithImportedPath<T = any>(
|
||||
path: string,
|
||||
): [exports: T, id: Metro.ModuleID] | LookupNotFoundResult {
|
||||
const id = mImportedPaths.get(path)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export type WaitForModulesResult<
|
|||
* @example
|
||||
* ```ts
|
||||
* const unsub = waitForModules(
|
||||
* byName<typeof import('@shopify/flash-list')>('FlashList'),
|
||||
* withName<typeof import('@shopify/flash-list')>('FlashList'),
|
||||
* // (exports: typeof import('@shopify/flash-list'), id: Metro.ModuleID) => any
|
||||
* (exports, id) => {
|
||||
* unsub()
|
||||
|
|
@ -192,7 +192,7 @@ export function waitForModules(
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* waitForModuleByImportedPath(
|
||||
* waitForModuleWithImportedPath(
|
||||
* 'utils/PlatformUtils.tsx',
|
||||
* (exports, id) => {
|
||||
* // Do something with the module...
|
||||
|
|
@ -200,7 +200,7 @@ export function waitForModules(
|
|||
* )
|
||||
* ```
|
||||
*/
|
||||
export function waitForModuleByImportedPath<T = any>(
|
||||
export function waitForModuleWithImportedPath<T = any>(
|
||||
path: string,
|
||||
callback: WaitForModulesCallback<T>,
|
||||
options?: BaseWaitForModulesOptions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForModules } from '@revenge-mod/modules/finders'
|
||||
import { byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
|
||||
export let ReactModuleId: Metro.ModuleID
|
||||
|
|
@ -13,7 +13,7 @@ export let ReactJSXRuntime: typeof import('react/jsx-runtime')
|
|||
let RtCount = 0
|
||||
|
||||
const unsubRt = waitForModules(
|
||||
byProps<typeof React>('useState'),
|
||||
withProps<typeof React>('useState'),
|
||||
(exports, id) => {
|
||||
if (RtCount++ === 2) return unsubRt()
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ const unsubRt = waitForModules(
|
|||
)
|
||||
|
||||
const unsubRN = waitForModules(
|
||||
byProps<typeof ReactNative>('AppRegistry'),
|
||||
withProps<typeof ReactNative>('AppRegistry'),
|
||||
(exports, id) => {
|
||||
unsubRN()
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ const unsubRN = waitForModules(
|
|||
let RJsxRCount = 0
|
||||
|
||||
const unsubRJSXR = waitForModules(
|
||||
byProps<typeof ReactJSXRuntime>('jsxs'),
|
||||
withProps<typeof ReactJSXRuntime>('jsxs'),
|
||||
(exports, id) => {
|
||||
if (RJsxRCount++ === 2) return unsubRJSXR()
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ export const asap = (cb: (...args: any[]) => any) => {
|
|||
;(HermesInternal.useEngineQueue()
|
||||
? // @ts-expect-error
|
||||
HermesInternal.enqueueJob
|
||||
: (globalThis.setImmediate ?? globalThis.setTimeout ?? now))(cb, 0)
|
||||
: (globalThis.queueMicrotask ??
|
||||
globalThis.setImmediate ??
|
||||
globalThis.setTimeout ??
|
||||
now))(cb, 0)
|
||||
}
|
||||
|
||||
export const noop = () => {}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ import { getAssetByName } from '@revenge-mod/assets'
|
|||
import { TokensModuleId } from '@revenge-mod/discord/common'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders'
|
||||
import {
|
||||
byDependencies,
|
||||
createFilterGenerator,
|
||||
withDependencies,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
ReactJSXRuntimeModuleId,
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
} from '@revenge-mod/react'
|
||||
import { FilterFlag } from '#modules/src/finders/filters/constants'
|
||||
import type {
|
||||
Filter,
|
||||
FilterGenerator,
|
||||
|
|
@ -38,7 +39,7 @@ const MultiIconComponentFilterBase = [
|
|||
null,
|
||||
]
|
||||
|
||||
export type ByGeneratedIconComponent = FilterGenerator<
|
||||
export type WithGeneratedIconComponent = FilterGenerator<
|
||||
<N extends string>(
|
||||
name: N,
|
||||
...assets: string[]
|
||||
|
|
@ -54,7 +55,7 @@ export type ByGeneratedIconComponent = FilterGenerator<
|
|||
* @example
|
||||
* ```ts
|
||||
* const [CopyIconModule] = lookupModule(
|
||||
* byGeneratedIconComponent('CopyIcon'),
|
||||
* withGeneratedIconComponent('CopyIcon'),
|
||||
* {
|
||||
* uninitialized: true,
|
||||
* }
|
||||
|
|
@ -67,7 +68,7 @@ export type ByGeneratedIconComponent = FilterGenerator<
|
|||
* @example
|
||||
* ```ts
|
||||
* const [CircleXIconModule] = lookupModule(
|
||||
* byGeneratedIconComponent(
|
||||
* withGeneratedIconComponent(
|
||||
* 'CircleXIcon',
|
||||
* 'CircleXIcon-secondary',
|
||||
* 'CircleXIcon-primary',
|
||||
|
|
@ -78,8 +79,8 @@ export type ByGeneratedIconComponent = FilterGenerator<
|
|||
* )
|
||||
* ```
|
||||
*/
|
||||
export const byGeneratedIconComponent = createFilterGenerator<
|
||||
Parameters<ByGeneratedIconComponent>
|
||||
export const withGeneratedIconComponent = createFilterGenerator<
|
||||
Parameters<WithGeneratedIconComponent>
|
||||
>(
|
||||
(names, id, exports) => {
|
||||
if (typeof exports === 'object') {
|
||||
|
|
@ -111,13 +112,14 @@ export const byGeneratedIconComponent = createFilterGenerator<
|
|||
IconComponentFilter[4] = mid
|
||||
}
|
||||
|
||||
return byDependencies(filter)(id, exports)
|
||||
return withDependencies(filter)(id, exports)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
names => `revenge.byGeneratedIconComponent(${names.join(',')})`,
|
||||
) as ByGeneratedIconComponent
|
||||
names => `revenge.utils.discord.generatedIconComponent(${names.join(',')})`,
|
||||
FilterFlag.Dynamic,
|
||||
) as WithGeneratedIconComponent
|
||||
|
||||
/**
|
||||
* Looks up a generated icon component by its name and asset names.
|
||||
|
|
@ -139,7 +141,7 @@ export function lookupGeneratedIconComponent<N extends string>(
|
|||
if (__DEV__ && badFind) return
|
||||
}
|
||||
|
||||
const [module] = lookupModule(byGeneratedIconComponent(...names), {
|
||||
const [module] = lookupModule(withGeneratedIconComponent(...names), {
|
||||
uninitialized: true,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ export type LogicalOr<T1, T2> = T1 extends true
|
|||
: T2 extends true
|
||||
? true
|
||||
: false
|
||||
export type LogicalAnd<T1, T2> = T1 extends true
|
||||
? T2 extends true
|
||||
? true
|
||||
: false
|
||||
: false
|
||||
export type DeepPartial<T> = {
|
||||
[K in keyof T]?: T[K] extends AnyObject ? DeepPartial<T[K]> : T[K]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForModules } from '@revenge-mod/modules/finders'
|
||||
import { byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||
import { instead } from '@revenge-mod/patcher'
|
||||
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
|
||||
|
|
@ -27,7 +27,7 @@ registerPlugin(
|
|||
|
||||
// utils/SentryUtils.native.tsx
|
||||
const unsubSU = waitForModules(
|
||||
byProps<{
|
||||
withProps<{
|
||||
profiledRootComponent<T>(x: T): T
|
||||
addBreadcrumb(): void
|
||||
setTags(): void
|
||||
|
|
@ -51,7 +51,7 @@ registerPlugin(
|
|||
|
||||
// modules/errors/native/SentryInitUtils.tsx
|
||||
const unsubSIU = waitForModules(
|
||||
byProps('initSentry'),
|
||||
withProps('initSentry'),
|
||||
SentryInitUtils => {
|
||||
unsubSIU()
|
||||
|
||||
|
|
@ -62,9 +62,9 @@ registerPlugin(
|
|||
|
||||
// Discord uses ReactNavigationInstrumentation to track navigation
|
||||
// Discord also uses Profiler to track performance, but we blocked that by patching profiledRootComponent
|
||||
// Note that to actually find Sentry, we just need to add 'init' to byProps
|
||||
// Note that to actually find Sentry, we just need to add 'init' to withProps
|
||||
const unsubSentryInst = waitForModules(
|
||||
byProps('ReactNavigationInstrumentation'),
|
||||
withProps('ReactNavigationInstrumentation'),
|
||||
exports => {
|
||||
unsubSentryInst()
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ registerPlugin(
|
|||
init({ cleanup }) {
|
||||
// utils/AnalyticsUtils.tsx
|
||||
const unsubAU = waitForModules(
|
||||
byProps<{
|
||||
withProps<{
|
||||
trackNetworkAction: () => void
|
||||
default: {
|
||||
track: () => void
|
||||
|
|
@ -133,7 +133,7 @@ registerPlugin(
|
|||
}) {
|
||||
// modules/app_analytics/useTrackImpression.tsx
|
||||
const unsubTI = waitForModules(
|
||||
byProps<{
|
||||
withProps<{
|
||||
default: () => void
|
||||
trackImpression: () => void
|
||||
}>('trackImpression'),
|
||||
|
|
@ -148,7 +148,7 @@ registerPlugin(
|
|||
|
||||
// actions/AnalyticsTrackActionCreators.tsx
|
||||
const unsubATAC = waitForModules(
|
||||
byProps('track'),
|
||||
withProps('track'),
|
||||
(AnalyticsTrackActionCreators, id) => {
|
||||
if (getModuleDependencies(id)![0] === DispatcherModuleId) {
|
||||
unsubATAC()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForModules } from '@revenge-mod/modules/finders'
|
||||
import { byName } from '@revenge-mod/modules/finders/filters'
|
||||
import { withName } from '@revenge-mod/modules/finders/filters'
|
||||
import { instead } from '@revenge-mod/patcher'
|
||||
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
|
|
@ -17,7 +17,7 @@ registerPlugin(
|
|||
{
|
||||
start({ cleanup }) {
|
||||
const unsubEB = waitForModules(
|
||||
byName<typeof DiscordErrorBoundary>('ErrorBoundary'),
|
||||
withName<typeof DiscordErrorBoundary>('ErrorBoundary'),
|
||||
exports => {
|
||||
unsubEB()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { sRefresher, sSections } from '@revenge-mod/discord/_/modules/settings'
|
||||
import { onSettingsModulesLoaded } from '@revenge-mod/discord/modules/settings'
|
||||
import {
|
||||
waitForModuleByImportedPath,
|
||||
waitForModules,
|
||||
waitForModuleWithImportedPath,
|
||||
} from '@revenge-mod/modules/finders'
|
||||
import { byName } from '@revenge-mod/modules/finders/filters'
|
||||
import { withName } from '@revenge-mod/modules/finders/filters'
|
||||
import { instead } from '@revenge-mod/patcher'
|
||||
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
|
|
@ -30,7 +30,7 @@ const pluginSettings = registerPlugin(
|
|||
require('./register')
|
||||
})
|
||||
|
||||
waitForModuleByImportedPath(
|
||||
waitForModuleWithImportedPath(
|
||||
'modules/main_tabs_v2/native/settings/SettingsNavigator.tsx',
|
||||
exports => {
|
||||
patchSettingsNavigator(exports)
|
||||
|
|
@ -38,7 +38,7 @@ const pluginSettings = registerPlugin(
|
|||
)
|
||||
|
||||
const unsubSOS = waitForModules(
|
||||
byName('SettingsOverviewScreen'),
|
||||
withName('SettingsOverviewScreen'),
|
||||
exports => {
|
||||
unsubSOS()
|
||||
patchSettingsOverviewScreen(exports)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ToastActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { getStore, Stores } from '@revenge-mod/discord/flux'
|
||||
import { getModule } from '@revenge-mod/modules/finders'
|
||||
import { byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { getModules } from '@revenge-mod/modules/finders'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { instead } from '@revenge-mod/patcher'
|
||||
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
|
|
@ -50,7 +50,7 @@ registerPlugin(
|
|||
}
|
||||
|
||||
cleanup(
|
||||
getModule(byProps('isStaffEnv'), UserStoreUtils => {
|
||||
getModules(withProps('isStaffEnv'), UserStoreUtils => {
|
||||
logger.log('Patching UserStoreUtils...')
|
||||
|
||||
cleanup(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { AlertActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import { getModule } from '@revenge-mod/modules/finders'
|
||||
import { byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { getModules } from '@revenge-mod/modules/finders'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
|
||||
import { PluginFlags } from '@revenge-mod/plugins/constants'
|
||||
import { afterJSX, beforeJSX } from '@revenge-mod/react/jsx-runtime'
|
||||
|
|
@ -57,13 +57,11 @@ registerPlugin(
|
|||
},
|
||||
{
|
||||
start({ cleanup }) {
|
||||
const unsub = getModule(
|
||||
byProps<{ ProfileBadgeRows: FC<ProfileBadgeRowsProps> }>(
|
||||
const unsub = getModules(
|
||||
withProps<{ ProfileBadgeRows: FC<ProfileBadgeRowsProps> }>(
|
||||
'ProfileBadgeRows',
|
||||
),
|
||||
({ ProfileBadgeRows }) => {
|
||||
unsub()
|
||||
|
||||
cleanup(
|
||||
beforeJSX(ProfileBadgeRows, args => {
|
||||
const [, props] = args
|
||||
|
|
|
|||
1
types/globals.d.ts
vendored
1
types/globals.d.ts
vendored
|
|
@ -110,6 +110,7 @@ declare global {
|
|||
var __turboModuleProxy: ((name: string) => unknown) | undefined
|
||||
function nativeLoggingHook(str: string, level: number): void
|
||||
function alert(message: unknown): void
|
||||
function queueMicrotask(cb: () => void): void
|
||||
|
||||
var nativePerformanceNow: typeof performance.now
|
||||
var performance: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue