diff --git a/lib/assets/src/index.ts b/lib/assets/src/index.ts index f617c30..65c2710 100644 --- a/lib/assets/src/index.ts +++ b/lib/assets/src/index.ts @@ -5,9 +5,16 @@ import { AssetRegistry } from './patches' import type { ReactNative as RN } from '@revenge-mod/types' -export type Asset = RN.AssetsRegistry.PackagerAsset +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 + // iOS cannot display SVGs let _preferredType: Asset['type'] = ReactNative.Platform.OS === 'ios' ? 'png' : 'svg' @@ -60,3 +67,33 @@ export function getAssetsByName(name: string): Record | un export function setPreferredAssetType(type: Asset['type']) { _preferredType = type } + +/** + * Register an asset with the given name. + * If an asset with the same name already exists, it will be overridden if `override` is set to true. + * + * @param asset The asset to register. + * @param override Whether to override the existing asset if one exists. `false` by default. + * @throws An error if an asset with the same name and type already exists and `override` is not set to true. + */ +export function registerAsset(asset: RegisterableAsset, override?: boolean) { + const { name } = asset + + if (override) _overrides.set(name, asset) + // Make sure to not override the asset if it already exists, unless explicitly set to override + else if (_assets.get(name)?.[1]?.[asset.type]) + throw new Error(`Asset "${name}" with type "${asset.type}" already exists`) + + // @ts-expect-error + return AssetRegistry.registerAsset(asset) +} + +/** + * Remove an asset override. + * + * @param name The name of the asset to remove the override for. + * @returns Whether the override was removed. + */ +export function removeAssetOverride(name: string) { + return _overrides.delete(name) +} diff --git a/lib/assets/src/patches.ts b/lib/assets/src/patches.ts index ab65bd9..e786800 100644 --- a/lib/assets/src/patches.ts +++ b/lib/assets/src/patches.ts @@ -1,15 +1,17 @@ import { _mUninited } from '@revenge-mod/modules/_/metro' import { waitForModules } from '@revenge-mod/modules/finders' -import { byProps } from '@revenge-mod/modules/finders/filters' +import { byName, byProps } from '@revenge-mod/modules/finders/filters' import { getModuleDependencies } from '@revenge-mod/modules/metro' -import { _assets, _ids } from './_internal' +import { _assets, _ids, _overrides } from './_internal' import type { ReactNative } from '@revenge-mod/types' -import type { Asset } from '.' +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) { @@ -33,6 +35,7 @@ const unsubForAssetRegistry = waitForModules(byProps('registerAsset'), (arId, ex } unsubForAssetRegistry() + AssetRegistryModuleId = arId // TODO(assets/patches): conditionally run this if cache does not exist // More fragile way, but more performant: @@ -45,3 +48,16 @@ const unsubForAssetRegistry = waitForModules(byProps('registerAsset'), (arId, ex 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() + + // @ts-expect-error + resolveAssetSource.addCustomSourceTransformer(({ asset }) => _overrides.get(asset.name)) + }, +)