JS implementation of Unity AssetBundle unpacking.
Only the minimum implementation required for the project was done. If you need complete functionality, it is recommended to use a more complete library in other languages.
Currently supports:
- TextAsset
- Texture2d
- Sprite
- SpriteAtlas
- MonoBehaviour
- MonoScript
- AudioClip
- Material
import fs from 'fs'; import { loadAssetBundle, AssetType, BundleEnv } from '@arkntools/unity-js'; // TextAsset const bundle = await loadAssetBundle( fs.readFileSync('character_table003334.ab', { env: BundleEnv.ARKNIGHTS }), ); for (const obj of bundle.objects) { if (obj.type === AssetType.TextAsset) { fs.writeFileSync(`${obj.name}.bytes`, obj.data); break; } } // Sprite const bundle = await loadAssetBundle( fs.readFileSync('spritepack_ui_char_avatar_h1_0.ab', { env: BundleEnv.ARKNIGHTS }), ); for (const obj of bundle.objects) { if (obj.type === AssetType.Sprite && obj.name === 'char_002_amiya') { fs.writeFileSync(`${obj.name}.png`, await obj.getImage()!); break; } } // Sprite with custom alpha texture const bundle = await loadAssetBundle( fs.readFileSync('char_1028_texas2.ab', { env: BundleEnv.ARKNIGHTS }), { // Some sprites may not give the PathID of the alpha texture // You can provide a custom function to find it findAlphaTexture: (texture, assets) => assets.find(({ name }) => name === `${texture.name}[alpha]`), }, ); for (const obj of bundle.objects) { if (obj.type === AssetType.Sprite && obj.name === 'char_1028_texas2_1') { fs.writeFileSync(`${obj.name}.png`, await obj.getImage()!); break; } }
Warning
If convertFsb() is executed in a Node environment, the process can't exit for unknown reasons (possibly due to internal implementation issues with FMOD causing node-web-audio-api to not be properly released). Please manually call process.exit().
Note
convertFsb() require Web Audio API, so it can't be called inside worker.
import fs from 'fs'; import { loadAssetBundle, AssetType, BundleEnv } from '@arkntools/unity-js'; import { convertFsb, FsbConvertFormat } from '@arkntools/unity-js/audio'; const bundle = await loadAssetBundle( fs.readFileSync('audio_sound_beta_2_voice_char_002_amiya.dat'), { env: BundleEnv.ARKNIGHTS }, ); for (const obj of bundle.objects) { if (obj.type === AssetType.AudioClip) { const audio = await obj.getAudio(); if (audio.format === 'fsb') { // Support conversion to mp3 or wav fs.writeFileSync(`${obj.name}.mp3`, await convertFsb(audio, FsbConvertFormat.MP3)); fs.writeFileSync(`${obj.name}.wav`, await convertFsb(audio, FsbConvertFormat.WAV)); } else { fs.writeFileSync(`${obj.name}.${audio.format}`, audio.data); } } }
import fs from 'fs'; import { loadAssetBundle, AssetType, BundleEnv } from '@arkntools/unity-js'; const bundle = await loadAssetBundle( fs.readFileSync('chararts_char_4195_radian.dat'), { env: BundleEnv.ARKNIGHTS }, ); for (const obj of bundle.objects) { if (obj.type === AssetType.MonoBehaviour && obj.isSpine) { const data = await obj.getSpine(); if (!data) continue; const dir = crypto.randomUUID(); fs.mkdirSync(dir); const { skel, atlas, image } = data; Object.entries({ ...skel, ...atlas, ...image }).forEach(([name, buffer]) => { fs.writeFileSync(`${dir}/${name}`, Buffer.from(buffer)); }); } }
This package supports use in a web environment, but you need to polyfill Buffer, and Vite needs to exclude the @jimp/wasm-png module from optimization.
Example vite.config.ts:
import { defineConfig } from 'vite'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; export default defineConfig({ plugins: [ nodePolyfills({ include: ['buffer'], globals: { Buffer: true, }, }), // ... ], optimizeDeps: { exclude: ['@jimp/wasm-png'], }, // ... });