diff --git a/lib/assets/package.json b/lib/assets/package.json index 7324ec4..5adb418 100644 --- a/lib/assets/package.json +++ b/lib/assets/package.json @@ -8,6 +8,9 @@ }, "./preinit": { "default": "./src/preinit.ts" + }, + "./types": { + "types": "./src/types.ts" } } } diff --git a/lib/assets/src/_internal.ts b/lib/assets/src/_internal.ts index dca8d61..f4fdaaf 100644 --- a/lib/assets/src/_internal.ts +++ b/lib/assets/src/_internal.ts @@ -1,5 +1,5 @@ import type { Metro } from '@revenge-mod/modules/types' -import type { Asset, AssetId } from '.' +import type { Asset, AssetId } from './types' export const _assets = new Map]>() export const _overrides = new Map() diff --git a/lib/assets/src/index.ts b/lib/assets/src/index.ts index 31e4a51..1948f6d 100644 --- a/lib/assets/src/index.ts +++ b/lib/assets/src/index.ts @@ -1,19 +1,11 @@ import { ReactNative } from '@revenge-mod/react' import { _assets, _metas, _overrides } from './_internal' -import { AssetRegistry } from './patches' +import { AssetRegistry } from './preinit' -import type { ReactNative as RN } from '@revenge-mod/react/types' +import type { Asset, AssetId, RegisterableAsset } from './types' -export type Asset = ReactNativeAsset | RevengeAsset -export type AssetId = number - -export type ReactNativeAsset = RN.AssetsRegistry.PackagerAsset -export interface RevengeAsset extends Pick { - uri: string -} - -export type RegisterableAsset = Omit +export * from './preinit' // iOS cannot display SVGs let _preferredType: Asset['type'] = ReactNative.Platform.OS === 'ios' ? 'png' : 'svg' diff --git a/lib/assets/src/patches.ts b/lib/assets/src/patches.ts deleted file mode 100644 index 3caedee..0000000 --- a/lib/assets/src/patches.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { _mInitingId, _mUninited } from '@revenge-mod/modules/_/metro' -import { waitForModules } from '@revenge-mod/modules/finders' -import { byName, byProps } from '@revenge-mod/modules/finders/filters' -import { getModuleDependencies } from '@revenge-mod/modules/metro' - -import { _assets, _metas, _overrides } from './_internal' - -import type { ReactNative } from '@revenge-mod/react/types' -import type { Asset, ReactNativeAsset } from '.' - -export let AssetRegistry: ReactNative.AssetsRegistry -export let AssetRegistryModuleId: number - -// Tracking/caching assets -const unsubForAssetRegistry = waitForModules(byProps('registerAsset'), (arId, exports) => { - // We want to do caching only for the re-exported asset-registry - if (!getModuleDependencies(arId)!.length) { - AssetRegistry = exports as ReactNative.AssetsRegistry - - const orig = exports.registerAsset - exports.registerAsset = function registerAsset(asset: Asset) { - const { name, type } = asset - - // In-memory cache - if (!_assets.has(name)) _assets.set(name, [asset, {}]) - const reg = _assets.get(name)! - reg[1][type] = asset - - const result = orig(asset) - _metas.set(asset, [result, _mInitingId]) - return result - } - - return - } - - unsubForAssetRegistry() - AssetRegistryModuleId = arId - - // TODO(assets/patches): conditionally run this if cache does not exist - // More fragile way, but more performant: - // There is exactly one asset before the reexported asset registry, thanks Discord! - const firstAssetModuleId = arId - 1 - for (const id of _mUninited) { - if (id < firstAssetModuleId) continue - - const deps = getModuleDependencies(id)! - if (deps.length === 1 && deps[0] === arId) __r(id) - } -}) - -// Asset overrides -const unsubForResolveAssetSource = waitForModules( - byName<{ - addCustomSourceTransformer: (transformer: (arg: { asset: Asset }) => ReactNativeAsset) => void - }>('resolveAssetSource'), - (_, resolveAssetSource) => { - unsubForResolveAssetSource() - - // Custom assets - // Why do we need to do this? Because RN/Dicord (unsure which) will attempt to resolve the asset via its path, which we don't provide via custom assets. - // This will result in a crash, because it tries to read the path and run checks on it, but the path is undefined. - // @ts-expect-error - resolveAssetSource.addCustomSourceTransformer(({ asset }) => { - // @ts-expect-error - if (!asset.__packager_asset) return asset - }) - - // Asset overrides - // @ts-expect-error - resolveAssetSource.addCustomSourceTransformer(({ asset }) => _overrides.get(asset.name)) - }, -) diff --git a/lib/assets/src/preinit.ts b/lib/assets/src/preinit.ts index cf254d0..a73b109 100644 --- a/lib/assets/src/preinit.ts +++ b/lib/assets/src/preinit.ts @@ -1 +1,83 @@ -import './patches' +import { _mInitingId, _mUninited } from '@revenge-mod/modules/_/metro' +import { waitForModules } from '@revenge-mod/modules/finders' +import { byName, byProps } from '@revenge-mod/modules/finders/filters' +import { getModuleDependencies } from '@revenge-mod/modules/metro' + +import { _assets, _metas, _overrides } from './_internal' + +import type { ReactNative } from '@revenge-mod/react/types' +import type { Asset, ReactNativeAsset } from './types' + +export let AssetRegistry: ReactNative.AssetsRegistry +export let AssetRegistryModuleId: number + +// Tracking/caching assets +const unsubAR = waitForModules(byProps('registerAsset'), (id, exports) => { + AssetRegistryModuleId = id + AssetRegistry = exports as ReactNative.AssetsRegistry + + // There are two matching exports. One is the original, and one is a re-export. + // The original asset-registry is simply required by the re-exported one with no changes. + + // This is the re-exported registry, because it has dependencies. + // We can begin the caching process here, asset registrar modules have the reexported registry as a dependency. + if (getModuleDependencies(id)!.length) { + unsubAR() + + // TODO(assets/patches): conditionally run this if cache does not exist + // More fragile way, but also more performant: + // There is exactly one asset before the reexported asset registry :/ + const firstAssetModuleId = id - 1 + for (const mId of _mUninited) { + if (mId < firstAssetModuleId) continue + + const deps = getModuleDependencies(mId)! + if (deps.length === 1 && deps[0] === id) __r(mId) + } + + // We already patched the original asset registry, so we don't need to patch again. + return + } + + const orig = exports.registerAsset + exports.registerAsset = (asset: Asset) => { + const result = orig(asset as ReactNativeAsset) + + const { name, type } = asset + + let reg = _assets.get(name) + if (!reg) { + reg = [asset, {}] + _assets.set(name, reg) + } + + // In-memory cache + reg[1][type] = asset + _metas.set(asset, [result, _mInitingId]) + + return result + } +}) + +// Asset overrides +const unsubRAS = waitForModules( + byName<{ + addCustomSourceTransformer: (transformer: (arg: { asset: Asset }) => ReactNativeAsset) => void + }>('resolveAssetSource'), + (_, rAS) => { + unsubRAS() + + // Custom assets + // Why do we need to do this? Because RN/Dicord (unsure which) will attempt to resolve the asset via its path, which we don't provide via custom assets. + // This will result in a crash, because it tries to read the path and run checks on it, but the path is undefined. + // @ts-expect-error + rAS.addCustomSourceTransformer(({ asset }) => { + // @ts-expect-error + if (!asset.__packager_asset) return asset + }) + + // Asset overrides + // @ts-expect-error + rAS.addCustomSourceTransformer(({ asset }) => _overrides.get(asset.name)) + }, +) diff --git a/lib/assets/src/types.ts b/lib/assets/src/types.ts new file mode 100644 index 0000000..bbcb60a --- /dev/null +++ b/lib/assets/src/types.ts @@ -0,0 +1,11 @@ +import type { ReactNative } from '@revenge-mod/react/types' + +export type Asset = ReactNativeAsset | RevengeAsset +export type AssetId = number + +export type ReactNativeAsset = ReactNative.AssetsRegistry.PackagerAsset +export interface RevengeAsset extends Pick { + uri: string +} + +export type RegisterableAsset = Omit