import { mInitializingId, mUninitialized } from '@revenge-mod/modules/_/metro' import { byName, byProps } from '@revenge-mod/modules/finders/filters' import { waitForModules } from '@revenge-mod/modules/finders/wait' import { getModuleDependencies } from '@revenge-mod/modules/metro/utils' import { aCallbacks, aOverrides } from './_internal' import { cacheAsset, cached } from './caches' import type { ReactNative } from '@revenge-mod/react/types' import type { Asset, PackagerAsset } from './types' export let AssetsRegistry: ReactNative.AssetsRegistry export let AssetsRegistryModuleId: number // Tracking/caching assets const unsubAR = waitForModules( byProps('registerAsset'), (exports, id) => { AssetsRegistryModuleId = id AssetsRegistry = exports as ReactNative.AssetsRegistry for (const cb of aCallbacks) try { cb() } catch {} aCallbacks.clear() // 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(lib/assets/caches): Ideally we should not wait for the cache promise here, see the promise impl for more info. cached.then(cached => { if (!cached) { // 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 mUninitialized) { 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 PackagerAsset) // Cache and set moduleId for packager assets only if ((asset as PackagerAsset).__packager_asset) { asset.moduleId = mInitializingId! cacheAsset(asset, mInitializingId!) } return (asset.id = result) } }, ) // Asset overrides const unsubRAS = waitForModules( byName<{ addCustomSourceTransformer: ( transformer: (arg: { asset: Asset }) => PackagerAsset, ) => void }>('resolveAssetSource'), rAS => { unsubRAS() // Custom assets // Why do we need to do this? Because RN/Discord (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 }) => { if (!(asset as PackagerAsset).__packager_asset) return asset }) // Asset overrides // @ts-expect-error rAS.addCustomSourceTransformer(({ asset }) => aOverrides.get(asset)) }, )