chore(lib/assets): refactor

This commit is contained in:
PalmDevs 2025-05-08 23:41:55 +07:00
parent 5de00474d4
commit 5587ede6ba
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
6 changed files with 101 additions and 86 deletions

View file

@ -8,6 +8,9 @@
},
"./preinit": {
"default": "./src/preinit.ts"
},
"./types": {
"types": "./src/types.ts"
}
}
}

View file

@ -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<string, [first: Asset, all: Record<Asset['type'], Asset>]>()
export const _overrides = new Map<string, Asset>()

View file

@ -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<ReactNativeAsset, 'name' | 'width' | 'height' | 'type'> {
uri: string
}
export type RegisterableAsset = Omit<RevengeAsset, 'id'>
export * from './preinit'
// iOS cannot display SVGs
let _preferredType: Asset['type'] = ReactNative.Platform.OS === 'ios' ? 'png' : 'svg'

View file

@ -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))
},
)

View file

@ -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<ReactNative.AssetsRegistry>('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))
},
)

11
lib/assets/src/types.ts Normal file
View file

@ -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<ReactNativeAsset, 'name' | 'width' | 'height' | 'type'> {
uri: string
}
export type RegisterableAsset = Omit<RevengeAsset, 'id'>