SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Vite plugin and file format for the game's asset pack (.ssrcpack).
//
// A pack holds every file of one asset folder. Files that gzip well (models,
// PCM audio, JSON, raw data) are stored gzip-compressed and inflated in memory
// at runtime by src/game/assets.ts; already-compressed formats (JPEG, PNG,
// WebP, MP3) stay as-is so they can be used directly as data URLs.
//
// Layout (little-endian):
// 0 8 bytes magic "\x89SSRCPK\n" (binary first byte: hosts sniff for text)
// 8 u32 format version (1)
// 12 u32 index length N
// 16 N bytes UTF-8 JSON PackIndex
// 16+N file bytes, at the offsets the index gives (relative to here)
//
// `virtual:ssrc-assets` then either embeds the pack (dev, watch and original
// builds) or names it by SHA-256 so the napplet downloads it from Blossom
// through NAP-RESOURCE (release builds). External packs are written to
// dist-pack/<sha256>.ssrcpack (plus current.json naming it). soyli uploads the
// pack at publish once it is a registered asset (pnpm assets:register), so a
// release build fails while napplet.assets.json names a different pack.
//
// Extra levels (levels/*.glb, docs/level-editing.md) are soyli assets of their
// own, "level-<name>"; the module lists them (id, hash, title) so the game can
// offer them and download one through NAP-RESOURCE when it is picked.
import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { dirname, extname, join, relative, sep } from 'node:path';
import { gzipSync } from 'node:zlib';
import type { Plugin } from 'vite';
const ID = 'virtual:ssrc-assets';
const RESOLVED = '\0' + ID;
export const PACK_MAGIC = Buffer.from([0x89, 0x53, 0x53, 0x52, 0x43, 0x50, 0x4b, 0x0a]);
const PACK_VERSION = 1;
const MIME: Record<string, string> = {
'.glb': 'model/gltf-binary',
'.bin': 'application/octet-stream',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.webp': 'image/webp',
'.wav': 'audio/wav',
'.mp3': 'audio/mpeg',
'.ogg': 'audio/ogg',
};
/** Formats whose bytes are already entropy-coded; gzip would not pay for itself. */
const PRECOMPRESSED = new Set(['.png', '.jpg', '.webp', '.mp3', '.ogg']);
/** Only keep the gzip form when it saves at least this fraction. */
const MIN_SAVING = 0.1;
/** The managed soyli asset the release pack is registered as (napplet.assets.json). */
export const RELEASE_ASSET_ID = 'game-pack';
/** soyli asset ids of extra levels: "level-<name>" (tools/assets/register.ts). */
export const LEVEL_ASSET_PREFIX = 'level-';
/** The custom pack every non-original build ships. */
export const CUSTOM_PACK = { dir: 'src/game/assets-custom', meta: { title: 'Bedroom Grand Prix', original: false } } as const;
export interface PackMeta {
/** Shown to the player when a pack is side-loaded. */
title: string;
/** Built from the 2004 game's files: local use only, flagged in the UI. */
original: boolean;
}
export interface PackIndex {
meta: PackMeta;
files: Array<{ name: string; mime: string; gz: 0 | 1; offset: number; length: number }>;
}
function walk(dir: string): string[] {
return readdirSync(dir).flatMap((name) => {
const path = join(dir, name);
return statSync(path).isDirectory() ? walk(path) : [path];
});
}
/** Every bundlable file of `dir` (sorted, so the pack and its hash are deterministic). */
function packFiles(dir: string): string[] {
return walk(dir)
.sort()
.filter((path) => MIME[extname(path).toLowerCase()]);
}
export function buildPack(dir: string, meta: PackMeta): Buffer {
const index: PackIndex = { meta, files: [] };
const blobs: Buffer[] = [];
let offset = 0;
for (const path of packFiles(dir)) {
const ext = extname(path).toLowerCase();
const raw = readFileSync(path);
const packed = PRECOMPRESSED.has(ext) ? null : gzipSync(raw, { level: 9 });
const gz = packed !== null && packed.length <= raw.length * (1 - MIN_SAVING);
const bytes = gz ? packed : raw;
index.files.push({ name: relative(dir, path).split(sep).join('/'), mime: MIME[ext], gz: gz ? 1 : 0, offset, length: bytes.length });
blobs.push(bytes);
offset += bytes.length;
}
const json = Buffer.from(JSON.stringify(index));
const header = Buffer.alloc(16);
PACK_MAGIC.copy(header, 0);
header.writeUInt32LE(PACK_VERSION, 8);
header.writeUInt32LE(json.length, 12);
return Buffer.concat([header, json, ...blobs]);
}
export interface AssetPackOptions {
dir: string;
meta: PackMeta;
/** napplet.assets.json, read for the registered extra levels (always, unlike `inventory`). */
levels: string;
/** Embed the pack in the bundle, or reference it by hash for a Blossom download. */
embed: boolean;
/** Where external packs are written (one file per hash). */
outDir: string;
/**
* napplet.assets.json: an external pack must be registered there under
* RELEASE_ASSET_ID with the same hash, or soyli would publish a build whose
* pack it never uploads. Null skips the check (watch builds testing the
* download path).
*/
inventory: string | null;
}
interface InventoryEntry {
id: string;
path: string;
hash: string;
bytes: number;
storage: string;
}
function inventoryEntries(inventory: string): InventoryEntry[] {
if (!existsSync(inventory)) return [];
return (JSON.parse(readFileSync(inventory, 'utf8')) as { assets?: InventoryEntry[] }).assets ?? [];
}
/** The hash napplet.assets.json registers for the release pack, if any. */
export function registeredPackHash(inventory: string): string | null {
return inventoryEntries(inventory).find((a) => a.id === RELEASE_ASSET_ID && a.storage === 'external')?.hash ?? null;
}
/** A level GLB's ssrc_title (its Blender scene's title), if any. */
export function levelTitle(glb: Buffer): string | null {
if (glb.length < 20 || glb.readUInt32LE(0) !== 0x46546c67) return null;
try {
const doc = JSON.parse(glb.subarray(20, 20 + glb.readUInt32LE(12)).toString('utf8')) as { scene?: number; scenes?: Array<{ extras?: { ssrc_title?: unknown } }> };
const title = doc.scenes?.[doc.scene ?? 0]?.extras?.ssrc_title;
return typeof title === 'string' && title ? title : null;
} catch {
return null;
}
}
export interface RegisteredLevel {
id: string;
sha256: string;
bytes: number;
title: string;
}
/** The extra levels napplet.assets.json registers ("level-*"), titled from their GLBs under assets/. */
export function registeredLevels(inventory: string): RegisteredLevel[] {
return inventoryEntries(inventory)
.filter((a) => a.id.startsWith(LEVEL_ASSET_PREFIX) && a.storage === 'external')
.sort((a, b) => a.id.localeCompare(b.id))
.map((a) => {
const file = join(dirname(inventory), a.path);
const title = existsSync(file) ? levelTitle(readFileSync(file)) : null;
return { id: a.id, sha256: a.hash, bytes: a.bytes, title: title ?? a.id.slice(LEVEL_ASSET_PREFIX.length) };
});
}
export function assetPack(o: AssetPackOptions): Plugin {
return {
name: 'ssrc-asset-pack',
resolveId: (id) => (id === ID ? RESOLVED : null),
load(id) {
if (id !== RESOLVED) return null;
this.addWatchFile(o.dir);
for (const path of packFiles(o.dir)) this.addWatchFile(path);
this.addWatchFile(o.levels);
const levels = registeredLevels(o.levels);
const pack = buildPack(o.dir, o.meta);
if (o.embed) return `export default ${JSON.stringify({ kind: 'embedded', pack: pack.toString('base64'), levels })};\n`;
const sha256 = createHash('sha256').update(pack).digest('hex');
mkdirSync(o.outDir, { recursive: true });
writeFileSync(join(o.outDir, `${sha256}.ssrcpack`), pack);
// The pack the latest release build points at, for the upload script.
writeFileSync(join(o.outDir, 'current.json'), JSON.stringify({ sha256, bytes: pack.length, file: `${sha256}.ssrcpack` }) + '\n');
if (o.inventory && registeredPackHash(o.inventory) !== sha256) {
this.error(
`asset pack ${sha256} is not the registered soyli asset "${RELEASE_ASSET_ID}"; ` +
'run pnpm assets:register so soyli publish uploads it',
);
}
this.info(`asset pack ${sha256} (${pack.length} bytes) -> ${relative(process.cwd(), o.outDir)}/, registered as "${RELEASE_ASSET_ID}"`);
return `export default ${JSON.stringify({ kind: 'external', sha256, bytes: pack.length, title: o.meta.title, levels })};\n`;
},
};
}
