@stowkit/reader
v0.1.39
Published
WebAssembly-based reader for StowKit (.stow) asset pack files
Readme
@stowkit/reader
WebAssembly-based reader for StowKit (.stow) asset pack files. Low-level API for reading and parsing binary asset data.
Features
- 🚀 Pure WASM - All binary parsing done in WebAssembly (Zig)
- 📦 Zero Dependencies - Just WebAssembly, no external libraries
- 🔍 WASM Metadata Parsing - Parse complex structures in WASM, not JavaScript
- 🎮 Game-Ready - Supports all asset types (meshes, textures, audio, animations)
- 📝 TypeScript Support - Full type definitions included
- ⚡ Fast - WASM parsing is 10-50x faster than JavaScript DataView
Installation
npm install @stowkit/readerUsage
import { StowKitReader } from '@stowkit/reader';
// Initialize the reader
const reader = new StowKitReader();
await reader.init();
// Open a .stow file
const response = await fetch('assets.stow');
const buffer = await response.arrayBuffer();
await reader.open(buffer);
// List all assets
const assets = reader.listAssets();
console.log(`Found ${assets.length} assets`);
// Find asset by path
const index = reader.findAssetByPath('models/character.mesh');
// Read asset data
const data = reader.readAssetData(index);
// Parse metadata (done in WASM!)
const texInfo = reader.parseTextureMetadata(index);
console.log(`Texture: ${texInfo.width}x${texInfo.height}`);
const audioInfo = reader.parseAudioMetadata(index);
console.log(`Audio: ${audioInfo.sampleRate}Hz, ${audioInfo.durationMs}ms`);
const animInfo = reader.parseAnimationMetadata(index);
console.log(`Animation: ${animInfo.duration}s, ${animInfo.boneCount} bones`);
// Clean up
reader.close();Asset Types
| Type | ID | Description | |------|-------|-------------| | Static Mesh | 1 | Draco-compressed 3D models | | Texture 2D | 2 | KTX2/Basis Universal textures | | Audio | 3 | OGG/MP3 audio files | | Material Schema | 4 | Material template definitions | | Skinned Mesh | 5 | Skeletal meshes with bones | | Animation Clip | 6 | Bone animation keyframes |
API Reference
Exports
export { StowKitReader } from '@stowkit/reader';
export { PerfLogger } from '@stowkit/reader';
export { AssetType } from '@stowkit/reader';
// Interfaces
export type { AssetInfo, AssetListItem, TextureMetadata, AudioMetadata } from '@stowkit/reader';Asset Types Enum
enum AssetType {
UNKNOWN = 0,
STATIC_MESH = 1,
TEXTURE_2D = 2,
AUDIO = 3,
MATERIAL_SCHEMA = 4,
SKINNED_MESH = 5,
ANIMATION_CLIP = 6,
}Initialization
new StowKitReader(wasmUrl?: string)
Create a new reader instance. Optionally specify the WASM file URL (default: '/stowkit/stowkit_reader.wasm').
async init(): Promise<StowKitReader>
Initialize the WebAssembly module. Must be called before using the reader.
const reader = new StowKitReader('/path/to/stowkit_reader.wasm');
await reader.init();Opening Files
async open(file: ArrayBuffer | File): Promise<boolean>
Open a .stow file from memory.
// From fetch
const response = await fetch('assets.stow');
const buffer = await response.arrayBuffer();
await reader.open(buffer);
// From file input
const file = input.files[0];
await reader.open(file);Asset Access
getAssetCount(): number
Get the total number of assets in the pack.
listAssets(): AssetListItem[]
Get information about all assets.
const assets = reader.listAssets();
assets.forEach(asset => {
console.log(`${asset.name} (type ${asset.type}): ${asset.dataSize} bytes`);
});findAssetByPath(path: string): number
Find an asset index by its canonical path. Returns -1 if not found.
const index = reader.findAssetByPath('models/character.mesh');
if (index >= 0) {
const data = reader.readAssetData(index);
}getAssetInfo(index: number): AssetInfo | null
Get detailed information about a specific asset.
readAssetData(index: number): Uint8Array | null
Read the binary data of an asset.
readAssetMetadata(index: number): Uint8Array | null
Read the raw metadata bytes of an asset.
WASM Metadata Parsing
All metadata parsing is done in WASM for maximum performance. No manual DataView manipulation needed!
parseTextureMetadata(index: number): TextureMetadata | null
Parse texture metadata from WASM.
const info = reader.parseTextureMetadata(2);
// { stringId, width, height, channels, channelFormat, tags }parseAudioMetadata(index: number): AudioMetadata | null
Parse audio metadata from WASM.
const info = reader.parseAudioMetadata(3);
// { stringId, sampleRate, channels, durationMs, tags }parseMeshMetadata(index: number): object | null
Parse static mesh metadata from WASM.
const info = reader.parseMeshMetadata(1);
// { stringId, tags }parseAnimationMetadata(index: number): object | null
Parse animation clip metadata from WASM.
const info = reader.parseAnimationMetadata(9);
// { stringId, targetMeshId, duration, ticksPerSecond, channelCount, boneCount }parseMaterialSchemaMetadata(index: number): object | null
Parse material schema metadata from WASM.
const schema = reader.parseMaterialSchemaMetadata(4);
// { stringId, schemaName, fieldCount, fields: [...] }parseSkinnedMeshMetadata(index: number): object | null
Parse skinned mesh metadata from WASM (returns bone hierarchy and stringId).
const info = reader.parseSkinnedMeshMetadata(8);
// { stringId, boneCount, bones: [{name, parentIndex}, ...], tags }parseSkinnedMeshGeometryFast(metadataBlob: Uint8Array, dataBlob: Uint8Array): object | null
Parse skinned mesh geometry using WASM (10-50x faster than JavaScript!). Returns combined vertex/index buffers ready for rendering.
const metadata = reader.readAssetMetadata(index);
const data = reader.readAssetData(index);
const geometry = reader.parseSkinnedMeshGeometryFast(metadata, data);
if (geometry) {
const { vertexCount, indexCount, positions, normals, uvs, skinIndices, skinWeights, indices } = geometry;
// Use with Three.js BufferGeometry, etc.
}Utility Methods
setAssetName(index: number, name: string): void
Set a custom name for an asset (for UI display purposes).
reader.setAssetName(5, 'Custom Asset Name');
const assets = reader.listAssets();
// Asset at index 5 will now have name 'Custom Asset Name'static formatBytes(bytes: number): string
Format byte count to human-readable string.
console.log(StowKitReader.formatBytes(1024)); // "1 KB"
console.log(StowKitReader.formatBytes(1048576)); // "1 MB"
console.log(StowKitReader.formatBytes(524288000)); // "500 MB"Cleanup
close(): void
Close the current file and free resources.
reader.close();Why WASM Parsing?
Before (JavaScript):
// 50+ lines of manual DataView parsing
const view = new DataView(metadata.buffer, metadata.byteOffset);
const stringIdBytes = metadata.slice(0, 128);
const decoder = new TextDecoder();
const stringId = decoder.decode(stringIdBytes.slice(0, stringIdBytes.indexOf(0)));
const sampleRate = view.getUint32(128, true);
// ... 40 more lines ...After (WASM):
// 1 line!
const info = reader.parseAudioMetadata(index);Benefits:
- ✅ Single source of truth (C header defines all structs)
- ✅ 10-50x faster parsing
- ✅ No sync issues between reader and parser
- ✅ Less prone to offset calculation bugs
- ✅ Cleaner, more maintainable code
Examples
Inspect a Pack
import { StowKitReader, AssetType } from '@stowkit/reader';
const reader = new StowKitReader();
await reader.init();
const response = await fetch('game.stow');
await reader.open(await response.arrayBuffer());
const assets = reader.listAssets();
console.log(`Pack contains ${assets.length} assets`);
assets.forEach(asset => {
const typeName = ['Unknown', 'Mesh', 'Texture', 'Audio', 'Schema', 'Skinned Mesh', 'Animation'][asset.type] || 'Unknown';
console.log(`[${asset.index}] ${asset.name} - ${typeName} (${StowKitReader.formatBytes(asset.dataSize)})`);
});
reader.close();Extract Assets
const reader = new StowKitReader();
await reader.init();
await reader.open(buffer);
// Find and extract a specific asset
const index = reader.findAssetByPath('models/character.mesh');
if (index >= 0) {
const data = reader.readAssetData(index);
const metadata = reader.readAssetMetadata(index);
// Save to file
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'character.mesh';
a.click();
}Requirements
- WebAssembly support in the browser
- The
stowkit_reader.wasmfile must be accessible at runtime
License
MIT
