From e510eea36ee8de14575b50ba8bbc4b9ee886afc1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 7 Jun 2025 15:14:28 +0700 Subject: [PATCH] feat(lib/assets)!: set metadata on asset objects, remove redundant APIs, add new APIs and parameters --- lib/assets/src/_internal.ts | 12 +-- lib/assets/src/caches.ts | 4 - lib/assets/src/index.ts | 85 +++++++++++-------- lib/assets/src/preinit.ts | 31 +++---- lib/assets/src/types.ts | 23 +++-- lib/utils/src/discord.ts | 6 +- .../screens/AssetBrowserSettingScreen.tsx | 66 +++++++------- 7 files changed, 119 insertions(+), 108 deletions(-) diff --git a/lib/assets/src/_internal.ts b/lib/assets/src/_internal.ts index f66a4c4..0574cdb 100644 --- a/lib/assets/src/_internal.ts +++ b/lib/assets/src/_internal.ts @@ -1,12 +1,4 @@ -import { createLogger } from '@revenge-mod/utils/logger' -import type { Metro } from '@revenge-mod/modules/types' -import type { Asset, AssetId } from './types' +import type { Asset, CustomAsset } from './types' -export const _logger = createLogger('revenge.assets') - -export const _customs = new Set() +export const _customs = new Set() export const _overrides = new WeakMap() -export const _metas = new WeakMap< - Asset, - [assetId: AssetId, moduleId: Metro.ModuleID] ->() diff --git a/lib/assets/src/caches.ts b/lib/assets/src/caches.ts index 6190ace..4c2ef57 100644 --- a/lib/assets/src/caches.ts +++ b/lib/assets/src/caches.ts @@ -3,7 +3,6 @@ import { ClientInfoModule } from '@revenge-mod/discord/native' import { getStorage } from '@revenge-mod/storage' import { debounce } from '@revenge-mod/utils/callbacks' import { mergeDeep } from '@revenge-mod/utils/objects' -import { _logger } from './_internal' import type { Metro } from '@revenge-mod/modules/types' import type { Asset } from './types' @@ -18,15 +17,12 @@ const CacheStorage = getStorage(`revenge/assets.${Key}`, { directory: 'cache', }) -_logger.log(`[${performance.now()}] CacheStorage instance created`) - // biome-ignore lint/complexity/useArrowFunction: Arrow functions are not supported CacheStorage.get().then(async function (cache_) { // TODO(lib/assets/caches): This loads way too late and requires native interop to load earlier // If we are not using the default value, merge it into the in-memory cache, then point the storage cache to the in-memory cache. if (cache_ !== cache) { - _logger.log(`[${performance.now()}] CacheStorage loaded`) Object.assign(cache, cache_) CacheStorage.cache = cache } diff --git a/lib/assets/src/index.ts b/lib/assets/src/index.ts index b0b7f64..846d2a4 100644 --- a/lib/assets/src/index.ts +++ b/lib/assets/src/index.ts @@ -1,11 +1,19 @@ import { ReactNative } from '@revenge-mod/react' -import { _customs, _metas, _overrides } from './_internal' +import { _customs, _overrides } from './_internal' import { cache } from './caches' -import { AssetRegistry } from './preinit' -import type { Metro } from '@revenge-mod/modules/types' -import type { Asset, AssetId, RegisterableAsset } from './types' +import { AssetsRegistry } from './preinit' +import type { + Asset, + AssetId, + CustomAsset, + PackagerAsset, + RegisterableAsset, +} from './types' -export { AssetRegistry, AssetRegistryModuleId } from './preinit' +export { + AssetsRegistry as AssetRegistry, + AssetsRegistryModuleId as AssetRegistryModuleId, +} from './preinit' // iOS cannot display SVGs let _preferredType: Asset['type'] = @@ -21,25 +29,44 @@ export function setPreferredAssetType(type: Asset['type']) { } /** - * Yields all registered assets, including ones with same name but different types, custom assets, and overriden assets. + * Yields all assets, both packager and custom. */ export function* getAssets(): Generator { - for (const name of Object.keys(cache!)) - for (const mid of Object.values(cache[name])) - yield AssetRegistry.getAssetByID(__r(mid)) as Asset + yield* getPackagerAssets() + yield* getCustomAssets() +} +/** + * Yields all registered custom assets. + */ +export function* getCustomAssets(): Generator { for (const asset of _customs) yield asset } +/** + * Yields all registered packager assets, including ones with same name but different types. + */ +export function* getPackagerAssets(): Generator { + for (const name in cache) { + const reg = cache[name] + for (const type in reg) + yield AssetsRegistry.getAssetByID(__r(reg[type])) + } +} + /** * Get an asset by its name. * If more than one asset is registered with the same name, this will return the one with the preferred type, or the first registered one. * * @param name The asset name. + * @param type The preferred asset type, defaults to the current preferred type. */ -export function getAssetByName(name: string): Asset | undefined { - const id = getAssetIdByName(name) - if (id !== undefined) return AssetRegistry.getAssetByID(id) +export function getAssetByName( + name: string, + type = _preferredType, +): Asset | undefined { + const id = getAssetIdByName(name, type) + if (id !== undefined) return AssetsRegistry.getAssetByID(id) } /** @@ -56,47 +83,31 @@ export function getAssetsByName( return Object.entries(reg).reduce( (acc, [type, mid]) => { - acc[type as Asset['type']] = AssetRegistry.getAssetByID(__r(mid))! + acc[type as Asset['type']] = AssetsRegistry.getAssetByID(__r(mid))! return acc }, {} as Record, ) } -/** - * Get the ID of an asset. - * - * @param asset The asset to get the ID for. - */ -export function getAssetId(asset: Asset): AssetId | undefined { - return _metas.get(asset)?.[0] -} - /** * Get an asset ID by its name. * If more than one asset is registered with the same name, this will return the one with the preferred type, or the first registered one. * * @param name The asset name. + * @param type The preferred asset type, defaults to the current preferred type. */ -export function getAssetIdByName(name: string): AssetId | undefined { +export function getAssetIdByName( + name: string, + type = _preferredType, +): AssetId | undefined { const reg = cache[name] if (!reg) return - const mid = reg[_preferredType] ?? reg[Object.keys(reg)[0]!] + const mid = reg[type] ?? reg[Object.keys(reg)[0]!] if (mid !== undefined) return __r(mid) } -/** - * Get the module ID that registered the asset. Returns `-1` if the asset is a custom asset. - * - * @param asset The asset to get the module ID for. - */ -export function getAssetModuleId( - asset: Asset, -): Metro.ModuleID | -1 | undefined { - return _metas.get(asset)?.[1] -} - /** * Register an asset with the given name. * @@ -109,10 +120,10 @@ export function registerAsset(asset: RegisterableAsset): AssetId { `Asset with name ${asset.name} and type ${asset.type} already exists!`, ) - _customs.add(asset) + _customs.add(asset as CustomAsset) // @ts-expect-error - return AssetRegistry.registerAsset(asset) + return AssetsRegistry.registerAsset(asset) } /** diff --git a/lib/assets/src/preinit.ts b/lib/assets/src/preinit.ts index cd6fa59..0f1eac9 100644 --- a/lib/assets/src/preinit.ts +++ b/lib/assets/src/preinit.ts @@ -2,20 +2,20 @@ import { _initing, _uninits } 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 { _logger, _metas, _overrides } from './_internal' +import { _overrides } from './_internal' import { cache, cacheAsset } from './caches' import type { ReactNative } from '@revenge-mod/react/types' -import type { Asset, ReactNativeAsset } from './types' +import type { Asset, PackagerAsset } from './types' -export let AssetRegistry: ReactNative.AssetsRegistry -export let AssetRegistryModuleId: number +export let AssetsRegistry: ReactNative.AssetsRegistry +export let AssetsRegistryModuleId: number // Tracking/caching assets const unsubAR = waitForModules( byProps('registerAsset'), (exports, id) => { - AssetRegistryModuleId = id - AssetRegistry = exports as ReactNative.AssetsRegistry + AssetsRegistryModuleId = id + AssetsRegistry = 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. @@ -25,8 +25,6 @@ const unsubAR = waitForModules( if (getModuleDependencies(id)!.length) { unsubAR() - _logger.log(`[${performance.now()}] Registry found: ${id}`) - // Make sure we don't try to recache assets if it is already cached. let uncached = true for (const _ in cache) { @@ -35,8 +33,6 @@ const unsubAR = waitForModules( } if (uncached) { - _logger.log('Caching assets...') - // More fragile way, but also more performant: // There is exactly one asset before the reexported asset registry :/ const firstAssetModuleId = id - 1 @@ -54,16 +50,15 @@ const unsubAR = waitForModules( const orig = exports.registerAsset exports.registerAsset = (asset: Asset) => { - const result = orig(asset as ReactNativeAsset) + const result = orig(asset as PackagerAsset) - // Cache packager assets only - if ((asset as ReactNativeAsset).__packager_asset) { - cacheAsset(asset, _initing) - // In-memory cache - _metas.set(asset, [result, _initing]) + // Cache and set moduleId for packager assets only + if ((asset as PackagerAsset).__packager_asset) { + asset.moduleId = _initing! + cacheAsset(asset, _initing!) } - return result + return (asset.id = result) } }, ) @@ -72,7 +67,7 @@ const unsubAR = waitForModules( const unsubRAS = waitForModules( byName<{ addCustomSourceTransformer: ( - transformer: (arg: { asset: Asset }) => ReactNativeAsset, + transformer: (arg: { asset: Asset }) => PackagerAsset, ) => void }>('resolveAssetSource'), rAS => { diff --git a/lib/assets/src/types.ts b/lib/assets/src/types.ts index 40a249a..12c2b74 100644 --- a/lib/assets/src/types.ts +++ b/lib/assets/src/types.ts @@ -1,12 +1,25 @@ +import type { Metro } from '@revenge-mod/modules/types' import type { ReactNative } from '@revenge-mod/react/types' -export type Asset = ReactNativeAsset | RevengeAsset +export type Asset = PackagerAsset | CustomAsset export type AssetId = number -export type ReactNativeAsset = ReactNative.AssetsRegistry.PackagerAsset -export interface RevengeAsset - extends Pick { +export type PackagerAsset = ReactNative.AssetsRegistry.PackagerAsset +export interface CustomAsset + extends Pick { uri: string + moduleId?: undefined } -export type RegisterableAsset = Omit +export type RegisterableAsset = Omit + +declare module '@revenge-mod/react/types' { + export namespace ReactNative { + export namespace AssetsRegistry { + export interface PackagerAsset { + id: AssetId + moduleId: Metro.ModuleID + } + } + } +} diff --git a/lib/utils/src/discord.ts b/lib/utils/src/discord.ts index b81130a..3278497 100644 --- a/lib/utils/src/discord.ts +++ b/lib/utils/src/discord.ts @@ -1,4 +1,4 @@ -import { getAssetByName, getAssetModuleId } from '@revenge-mod/assets' +import { getAssetByName } from '@revenge-mod/assets' import { TokensModuleId } from '@revenge-mod/discord/common' import { byDependencies, byProps } from '@revenge-mod/modules/finders/filters' import { lookupModule } from '@revenge-mod/modules/finders/lookup' @@ -45,7 +45,7 @@ export function lookupGeneratedIconComponent( const mids = [] for (const name of assetNames) { - const mid = getAssetModuleId(getAssetByName(name)!) + const mid = getAssetByName(name)?.moduleId if (!mid) return mids.push(mid) @@ -53,7 +53,7 @@ export function lookupGeneratedIconComponent( filter = [...MultiIconComponentFilterBase, ...mids, 2] } else { - const mid = getAssetModuleId(getAssetByName(compName)!) + const mid = getAssetByName(compName)?.moduleId if (!mid) return IconComponentFilter[4] = mid diff --git a/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx b/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx index f30756e..5e070aa 100644 --- a/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx +++ b/src/plugins/init/developer-settings/screens/AssetBrowserSettingScreen.tsx @@ -1,4 +1,4 @@ -import { getAssetId, getAssetModuleId, getAssets } from '@revenge-mod/assets' +import { getAssets } from '@revenge-mod/assets' import { AlertActionCreators } from '@revenge-mod/discord/actions' import { Design } from '@revenge-mod/discord/design' import { Clipboard } from '@revenge-mod/externals/react-native-clipboard' @@ -24,24 +24,13 @@ const UndisplayableFallback = { export default function AssetBrowserSettingScreen() { const [search, setSearch] = React.useState('') - const assets = React.useMemo( - () => - [...getAssets()].map( - asset => - [ - getAssetId(asset)!, - getAssetModuleId(asset)!, - asset, - ] as const, - ), - [getAssets], - ) + const assets = React.useMemo(() => [...getAssets()], [getAssets]) const filteredAssets = React.useMemo( () => !search.length ? assets - : assets.filter(([, , asset]) => + : assets.filter(asset => asset.name.toLowerCase().includes(search.toLowerCase()), ), [assets, search], @@ -53,13 +42,17 @@ export default function AssetBrowserSettingScreen() { id.toString()} - renderItem={({ item: [id, moduleId, asset], index }) => ( + keyExtractor={asset => + asset.id + ? asset.id.toString() + : `${asset.name}.${asset.type}` + } + renderItem={({ item: asset, index }) => ( )} @@ -76,7 +69,7 @@ function AssetDisplay({ end, }: { id: AssetId - moduleId: Metro.ModuleID + moduleId?: Metro.ModuleID asset: Asset start?: boolean end?: boolean @@ -85,7 +78,7 @@ function AssetDisplay({ const metadata = [ ['ID', id.toString()], ['Type', asset.type], - ['Module ID', moduleId.toString()], + ['Module ID', moduleId?.toString()], ] as const return ( @@ -111,7 +104,7 @@ function AssetDisplay({ onPress={() => openAssetDisplayAlert(asset, id, metadata)} start={start} subLabel={metadata - .map(([name, value]) => `${name}: ${value}`) + .map(([name, value]) => `${name}: ${value ?? 'N/A'}`) .join(' • ')} variant={isDisplayable ? 'default' : 'danger'} /> @@ -121,7 +114,9 @@ function AssetDisplay({ function openAssetDisplayAlert( asset: Asset, id: AssetId, - metadata: Readonly[]> | [string, string][], + metadata: + | Readonly[]> + | [string, string | undefined][], ) { const isDisplayable = Displayable.has(asset.type) @@ -137,14 +132,23 @@ function openAssetDisplayAlert( )} - {metadata.map(([name, value]) => ( - Clipboard.setString(value)} - subLabel={value} - /> - ))} + {metadata.map(([name, value]) => + value ? ( + Clipboard.setString(value)} + subLabel={value} + /> + ) : ( + + ), + )} Note: Asset IDs and module IDs are not consistent