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'],
},
// ...
});