@series-inc/stowkit-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/blob-store.d.ts +9 -0
- package/dist/app/blob-store.js +42 -0
- package/dist/app/disk-project.d.ts +84 -0
- package/dist/app/disk-project.js +70 -0
- package/dist/app/process-cache.d.ts +10 -0
- package/dist/app/process-cache.js +126 -0
- package/dist/app/state.d.ts +38 -0
- package/dist/app/state.js +16 -0
- package/dist/app/stowmat-io.d.ts +6 -0
- package/dist/app/stowmat-io.js +48 -0
- package/dist/app/stowmeta-io.d.ts +14 -0
- package/dist/app/stowmeta-io.js +207 -0
- package/dist/cleanup.d.ts +3 -0
- package/dist/cleanup.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +148 -0
- package/dist/core/binary.d.ts +41 -0
- package/dist/core/binary.js +118 -0
- package/dist/core/constants.d.ts +64 -0
- package/dist/core/constants.js +65 -0
- package/dist/core/path.d.ts +3 -0
- package/dist/core/path.js +27 -0
- package/dist/core/types.d.ts +204 -0
- package/dist/core/types.js +76 -0
- package/dist/encoders/aac-encoder.d.ts +12 -0
- package/dist/encoders/aac-encoder.js +179 -0
- package/dist/encoders/basis-encoder.d.ts +15 -0
- package/dist/encoders/basis-encoder.js +116 -0
- package/dist/encoders/draco-encoder.d.ts +11 -0
- package/dist/encoders/draco-encoder.js +155 -0
- package/dist/encoders/fbx-loader.d.ts +4 -0
- package/dist/encoders/fbx-loader.js +540 -0
- package/dist/encoders/image-decoder.d.ts +13 -0
- package/dist/encoders/image-decoder.js +33 -0
- package/dist/encoders/interfaces.d.ts +105 -0
- package/dist/encoders/interfaces.js +1 -0
- package/dist/encoders/skinned-mesh-builder.d.ts +7 -0
- package/dist/encoders/skinned-mesh-builder.js +135 -0
- package/dist/format/metadata.d.ts +18 -0
- package/dist/format/metadata.js +381 -0
- package/dist/format/packer.d.ts +8 -0
- package/dist/format/packer.js +87 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +35 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +73 -0
- package/dist/node-fs.d.ts +22 -0
- package/dist/node-fs.js +148 -0
- package/dist/orchestrator.d.ts +20 -0
- package/dist/orchestrator.js +301 -0
- package/dist/pipeline.d.ts +23 -0
- package/dist/pipeline.js +354 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +859 -0
- package/package.json +35 -0
- package/skill.md +211 -0
package/dist/cleanup.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { readProjectConfig, scanDirectory } from './node-fs.js';
|
|
4
|
+
export async function cleanupProject(projectDir, opts) {
|
|
5
|
+
const verbose = opts?.verbose ?? false;
|
|
6
|
+
const config = await readProjectConfig(projectDir);
|
|
7
|
+
const scan = await scanDirectory(config.srcArtDir);
|
|
8
|
+
// Build set of source files that exist
|
|
9
|
+
const sourceFiles = new Set(scan.sourceFiles.map(f => f.relativePath));
|
|
10
|
+
const matFiles = new Set(scan.matFiles.map(f => f.relativePath));
|
|
11
|
+
let deletedCaches = 0;
|
|
12
|
+
let deletedMetas = 0;
|
|
13
|
+
let freedBytes = 0;
|
|
14
|
+
// Walk all files looking for orphaned .stowcache and .stowmeta
|
|
15
|
+
await walkForOrphans(config.srcArtDir, '', sourceFiles, matFiles, async (orphanPath, type, size) => {
|
|
16
|
+
const fullPath = path.join(config.srcArtDir, orphanPath);
|
|
17
|
+
await fs.unlink(fullPath);
|
|
18
|
+
freedBytes += size;
|
|
19
|
+
if (type === 'cache') {
|
|
20
|
+
deletedCaches++;
|
|
21
|
+
if (verbose)
|
|
22
|
+
console.log(` [delete] ${orphanPath} (orphaned cache)`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
deletedMetas++;
|
|
26
|
+
if (verbose)
|
|
27
|
+
console.log(` [delete] ${orphanPath} (orphaned meta)`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (deletedCaches + deletedMetas === 0) {
|
|
31
|
+
console.log('No orphaned files found.');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(`Cleaned up ${deletedCaches} cache(s) and ${deletedMetas} meta(s) (${(freedBytes / 1024).toFixed(0)} KB freed)`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function walkForOrphans(basePath, prefix, sourceFiles, matFiles, onOrphan) {
|
|
38
|
+
const dirPath = prefix ? path.join(basePath, prefix) : basePath;
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
if (entry.name.startsWith('.'))
|
|
50
|
+
continue;
|
|
51
|
+
await walkForOrphans(basePath, relativePath, sourceFiles, matFiles, onOrphan);
|
|
52
|
+
}
|
|
53
|
+
else if (entry.isFile()) {
|
|
54
|
+
if (entry.name.endsWith('.stowcache')) {
|
|
55
|
+
// Cache orphan: source file doesn't exist
|
|
56
|
+
const sourceId = relativePath.replace(/\.stowcache$/, '');
|
|
57
|
+
if (!sourceFiles.has(sourceId) && !matFiles.has(sourceId)) {
|
|
58
|
+
const stat = await fs.stat(path.join(basePath, relativePath));
|
|
59
|
+
await onOrphan(relativePath, 'cache', stat.size);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (entry.name.endsWith('.stowmeta')) {
|
|
63
|
+
// Meta orphan: neither source file nor mat file exists
|
|
64
|
+
const sourceId = relativePath.replace(/\.stowmeta$/, '');
|
|
65
|
+
if (!sourceFiles.has(sourceId) && !matFiles.has(sourceId)) {
|
|
66
|
+
const stat = await fs.stat(path.join(basePath, relativePath));
|
|
67
|
+
await onOrphan(relativePath, 'meta', stat.size);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { fullBuild, scanProject, showStatus } from './orchestrator.js';
|
|
7
|
+
import { startServer } from './server.js';
|
|
8
|
+
import { initProject } from './init.js';
|
|
9
|
+
import { cleanupProject } from './cleanup.js';
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const req = createRequire(import.meta.url);
|
|
13
|
+
function printUsage() {
|
|
14
|
+
console.log(`
|
|
15
|
+
Usage:
|
|
16
|
+
stowkit init [dir] Initialize a StowKit project
|
|
17
|
+
stowkit build [dir] Full build: scan + process + pack
|
|
18
|
+
stowkit scan [dir] Detect new assets, generate .stowmeta defaults
|
|
19
|
+
stowkit process [dir] Compress assets (respects cache)
|
|
20
|
+
stowkit status [dir] Show project summary, stale asset count
|
|
21
|
+
stowkit clean [dir] Delete orphaned .stowcache and .stowmeta files
|
|
22
|
+
stowkit packer [dir] Open the packer GUI
|
|
23
|
+
stowkit editor [dir] Open the level editor
|
|
24
|
+
stowkit serve [dir] Start API server only (no GUI)
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--force Ignore cache and reprocess everything
|
|
28
|
+
--verbose Detailed output
|
|
29
|
+
--port Server port (default 3210)
|
|
30
|
+
--help Show this help message
|
|
31
|
+
`.trim());
|
|
32
|
+
}
|
|
33
|
+
function resolveAppDir(packageName, monorepoFolder) {
|
|
34
|
+
const candidates = [
|
|
35
|
+
path.resolve(thisDir, `../../${monorepoFolder}/dist`), // monorepo dev
|
|
36
|
+
];
|
|
37
|
+
try {
|
|
38
|
+
const pkgJson = req.resolve(`${packageName}/package.json`);
|
|
39
|
+
candidates.push(path.join(path.dirname(pkgJson), 'dist'));
|
|
40
|
+
}
|
|
41
|
+
catch { /* not installed */ }
|
|
42
|
+
for (const dir of candidates) {
|
|
43
|
+
if (existsSync(path.join(dir, 'index.html')))
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function openBrowser(url) {
|
|
49
|
+
import('node:child_process').then(({ exec }) => {
|
|
50
|
+
const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
51
|
+
exec(`${cmd} ${url}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function main() {
|
|
55
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
56
|
+
printUsage();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
const command = args[0];
|
|
60
|
+
const projectDir = args.find(a => !a.startsWith('-') && a !== command) ?? '.';
|
|
61
|
+
const force = args.includes('--force');
|
|
62
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
63
|
+
const portIdx = args.indexOf('--port');
|
|
64
|
+
const port = portIdx >= 0 ? parseInt(args[portIdx + 1]) : 3210;
|
|
65
|
+
const opts = { force, verbose };
|
|
66
|
+
try {
|
|
67
|
+
switch (command) {
|
|
68
|
+
case 'init':
|
|
69
|
+
await initProject(projectDir);
|
|
70
|
+
break;
|
|
71
|
+
case 'build':
|
|
72
|
+
await fullBuild(projectDir, opts);
|
|
73
|
+
break;
|
|
74
|
+
case 'scan': {
|
|
75
|
+
const report = await scanProject(projectDir, { verbose });
|
|
76
|
+
console.log(`Project: ${report.projectName}`);
|
|
77
|
+
console.log(`Source files: ${report.sourceFiles}`);
|
|
78
|
+
console.log(`Meta files: ${report.metaFiles}`);
|
|
79
|
+
console.log(`Material files: ${report.matFiles}`);
|
|
80
|
+
if (report.newFiles.length > 0) {
|
|
81
|
+
console.log(`New assets detected (${report.newFiles.length}):`);
|
|
82
|
+
for (const f of report.newFiles)
|
|
83
|
+
console.log(` ${f}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log('No new assets detected.');
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'process':
|
|
91
|
+
await fullBuild(projectDir, { ...opts, force });
|
|
92
|
+
break;
|
|
93
|
+
case 'status':
|
|
94
|
+
await showStatus(projectDir);
|
|
95
|
+
break;
|
|
96
|
+
case 'clean':
|
|
97
|
+
await cleanupProject(projectDir, { verbose });
|
|
98
|
+
break;
|
|
99
|
+
case 'packer': {
|
|
100
|
+
const packerDir = resolveAppDir('@series-inc/stowkit-packer-gui', 'stowkit-packer-gui');
|
|
101
|
+
if (!packerDir) {
|
|
102
|
+
console.error('Packer GUI not found. Install it: npm install @series-inc/stowkit-packer-gui');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const resolvedProject = path.resolve(projectDir);
|
|
106
|
+
await startServer({ port, projectDir: resolvedProject, staticApps: { '/': packerDir } });
|
|
107
|
+
console.log(`\n Packer: http://localhost:${port}\n`);
|
|
108
|
+
openBrowser(`http://localhost:${port}`);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'editor': {
|
|
112
|
+
const editorDir = resolveAppDir('@series-inc/stowkit-editor', 'stowkit-editor');
|
|
113
|
+
if (!editorDir) {
|
|
114
|
+
console.error('Editor not found. Install it: npm install @series-inc/stowkit-editor');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const resolvedProject = path.resolve(projectDir);
|
|
118
|
+
const packerPort = port + 1;
|
|
119
|
+
// Start editor server
|
|
120
|
+
await startServer({ port, projectDir: resolvedProject, staticApps: { '/': editorDir } });
|
|
121
|
+
// Start packer on next port if installed
|
|
122
|
+
const packerDir = resolveAppDir('@series-inc/stowkit-packer-gui', 'stowkit-packer-gui');
|
|
123
|
+
if (packerDir) {
|
|
124
|
+
await startServer({ port: packerPort, projectDir: resolvedProject, staticApps: { '/': packerDir } });
|
|
125
|
+
console.log(`\n Editor: http://localhost:${port}`);
|
|
126
|
+
console.log(` Packer: http://localhost:${packerPort}\n`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(`\n Editor: http://localhost:${port}\n`);
|
|
130
|
+
}
|
|
131
|
+
openBrowser(`http://localhost:${port}`);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'serve':
|
|
135
|
+
await startServer({ port, projectDir: projectDir !== '.' ? path.resolve(projectDir) : undefined });
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
console.error(`Unknown command: ${command}`);
|
|
139
|
+
printUsage();
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
main();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequential binary reader over an ArrayBuffer.
|
|
3
|
+
* All multi-byte reads use little-endian byte order (matching .stow format).
|
|
4
|
+
*/
|
|
5
|
+
export declare class BinaryReader {
|
|
6
|
+
private readonly view;
|
|
7
|
+
private pos;
|
|
8
|
+
constructor(source: ArrayBuffer | Uint8Array, offset?: number);
|
|
9
|
+
readUint32(): number;
|
|
10
|
+
readInt32(): number;
|
|
11
|
+
readUint64(): bigint;
|
|
12
|
+
readUint64AsNumber(): number;
|
|
13
|
+
readFloat32(): number;
|
|
14
|
+
readFixedString(byteLength: number): string;
|
|
15
|
+
readBytes(length: number): Uint8Array;
|
|
16
|
+
seek(offset: number): void;
|
|
17
|
+
tell(): number;
|
|
18
|
+
get remaining(): number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sequential binary writer into a pre-allocated ArrayBuffer.
|
|
22
|
+
* All multi-byte writes use little-endian byte order.
|
|
23
|
+
*/
|
|
24
|
+
export declare class BinaryWriter {
|
|
25
|
+
private readonly buffer;
|
|
26
|
+
private readonly view;
|
|
27
|
+
private readonly bytes;
|
|
28
|
+
private pos;
|
|
29
|
+
constructor(size: number);
|
|
30
|
+
writeUint32(value: number): void;
|
|
31
|
+
writeInt32(value: number): void;
|
|
32
|
+
writeUint64(value: bigint): void;
|
|
33
|
+
writeUint64FromNumber(value: number): void;
|
|
34
|
+
writeFloat32(value: number): void;
|
|
35
|
+
writeFixedString(value: string, byteLength: number): void;
|
|
36
|
+
writeBytes(data: Uint8Array): void;
|
|
37
|
+
seek(offset: number): void;
|
|
38
|
+
tell(): number;
|
|
39
|
+
getBuffer(): ArrayBuffer;
|
|
40
|
+
getUint8Array(): Uint8Array;
|
|
41
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequential binary reader over an ArrayBuffer.
|
|
3
|
+
* All multi-byte reads use little-endian byte order (matching .stow format).
|
|
4
|
+
*/
|
|
5
|
+
export class BinaryReader {
|
|
6
|
+
view;
|
|
7
|
+
pos;
|
|
8
|
+
constructor(source, offset = 0) {
|
|
9
|
+
if (source instanceof Uint8Array) {
|
|
10
|
+
this.view = new DataView(source.buffer, source.byteOffset, source.byteLength);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
this.view = new DataView(source);
|
|
14
|
+
}
|
|
15
|
+
this.pos = offset;
|
|
16
|
+
}
|
|
17
|
+
readUint32() {
|
|
18
|
+
const val = this.view.getUint32(this.pos, true);
|
|
19
|
+
this.pos += 4;
|
|
20
|
+
return val;
|
|
21
|
+
}
|
|
22
|
+
readInt32() {
|
|
23
|
+
const val = this.view.getInt32(this.pos, true);
|
|
24
|
+
this.pos += 4;
|
|
25
|
+
return val;
|
|
26
|
+
}
|
|
27
|
+
readUint64() {
|
|
28
|
+
const val = this.view.getBigUint64(this.pos, true);
|
|
29
|
+
this.pos += 8;
|
|
30
|
+
return val;
|
|
31
|
+
}
|
|
32
|
+
readUint64AsNumber() {
|
|
33
|
+
return Number(this.readUint64());
|
|
34
|
+
}
|
|
35
|
+
readFloat32() {
|
|
36
|
+
const val = this.view.getFloat32(this.pos, true);
|
|
37
|
+
this.pos += 4;
|
|
38
|
+
return val;
|
|
39
|
+
}
|
|
40
|
+
readFixedString(byteLength) {
|
|
41
|
+
const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, byteLength);
|
|
42
|
+
this.pos += byteLength;
|
|
43
|
+
const nullIdx = bytes.indexOf(0);
|
|
44
|
+
return new TextDecoder().decode(bytes.subarray(0, nullIdx >= 0 ? nullIdx : byteLength));
|
|
45
|
+
}
|
|
46
|
+
readBytes(length) {
|
|
47
|
+
const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, length);
|
|
48
|
+
this.pos += length;
|
|
49
|
+
return bytes.slice();
|
|
50
|
+
}
|
|
51
|
+
seek(offset) {
|
|
52
|
+
this.pos = offset;
|
|
53
|
+
}
|
|
54
|
+
tell() {
|
|
55
|
+
return this.pos;
|
|
56
|
+
}
|
|
57
|
+
get remaining() {
|
|
58
|
+
return this.view.byteLength - this.pos;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sequential binary writer into a pre-allocated ArrayBuffer.
|
|
63
|
+
* All multi-byte writes use little-endian byte order.
|
|
64
|
+
*/
|
|
65
|
+
export class BinaryWriter {
|
|
66
|
+
buffer;
|
|
67
|
+
view;
|
|
68
|
+
bytes;
|
|
69
|
+
pos;
|
|
70
|
+
constructor(size) {
|
|
71
|
+
this.buffer = new ArrayBuffer(size);
|
|
72
|
+
this.view = new DataView(this.buffer);
|
|
73
|
+
this.bytes = new Uint8Array(this.buffer);
|
|
74
|
+
this.pos = 0;
|
|
75
|
+
}
|
|
76
|
+
writeUint32(value) {
|
|
77
|
+
this.view.setUint32(this.pos, value, true);
|
|
78
|
+
this.pos += 4;
|
|
79
|
+
}
|
|
80
|
+
writeInt32(value) {
|
|
81
|
+
this.view.setInt32(this.pos, value, true);
|
|
82
|
+
this.pos += 4;
|
|
83
|
+
}
|
|
84
|
+
writeUint64(value) {
|
|
85
|
+
this.view.setBigUint64(this.pos, value, true);
|
|
86
|
+
this.pos += 8;
|
|
87
|
+
}
|
|
88
|
+
writeUint64FromNumber(value) {
|
|
89
|
+
this.view.setBigUint64(this.pos, BigInt(value), true);
|
|
90
|
+
this.pos += 8;
|
|
91
|
+
}
|
|
92
|
+
writeFloat32(value) {
|
|
93
|
+
this.view.setFloat32(this.pos, value, true);
|
|
94
|
+
this.pos += 4;
|
|
95
|
+
}
|
|
96
|
+
writeFixedString(value, byteLength) {
|
|
97
|
+
const encoded = new TextEncoder().encode(value);
|
|
98
|
+
this.bytes.fill(0, this.pos, this.pos + byteLength);
|
|
99
|
+
this.bytes.set(encoded.subarray(0, Math.min(encoded.length, byteLength - 1)), this.pos);
|
|
100
|
+
this.pos += byteLength;
|
|
101
|
+
}
|
|
102
|
+
writeBytes(data) {
|
|
103
|
+
this.bytes.set(data, this.pos);
|
|
104
|
+
this.pos += data.length;
|
|
105
|
+
}
|
|
106
|
+
seek(offset) {
|
|
107
|
+
this.pos = offset;
|
|
108
|
+
}
|
|
109
|
+
tell() {
|
|
110
|
+
return this.pos;
|
|
111
|
+
}
|
|
112
|
+
getBuffer() {
|
|
113
|
+
return this.buffer;
|
|
114
|
+
}
|
|
115
|
+
getUint8Array() {
|
|
116
|
+
return this.bytes;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Magic number: 'STKT' as little-endian uint32 (0x544B5453) */
|
|
2
|
+
export declare const STOW_MAGIC = 1414222931;
|
|
3
|
+
/** Current format version */
|
|
4
|
+
export declare const STOW_VERSION = 1;
|
|
5
|
+
/** Size of the FileHeader on disk (bytes) */
|
|
6
|
+
export declare const FILE_HEADER_SIZE = 20;
|
|
7
|
+
/** Size of a single AssetDirectoryEntry on disk (bytes) */
|
|
8
|
+
export declare const DIRECTORY_ENTRY_SIZE = 40;
|
|
9
|
+
/** Alignment boundary for data and metadata blobs (bytes) */
|
|
10
|
+
export declare const DATA_ALIGNMENT = 16;
|
|
11
|
+
/** Maximum canonical path length (characters) */
|
|
12
|
+
export declare const MAX_PATH_LENGTH = 512;
|
|
13
|
+
/** Size of the string_id field in metadata structs (bytes) */
|
|
14
|
+
export declare const STRING_ID_SIZE = 128;
|
|
15
|
+
/** Size of TextureMetadata on disk (bytes) */
|
|
16
|
+
export declare const TEXTURE_METADATA_SIZE = 144;
|
|
17
|
+
/** Size of AudioMetadata on disk (bytes) */
|
|
18
|
+
export declare const AUDIO_METADATA_SIZE = 140;
|
|
19
|
+
/** Size of MeshGeometryInfo on disk (bytes) */
|
|
20
|
+
export declare const MESH_GEOMETRY_INFO_SIZE = 40;
|
|
21
|
+
/** Size of SceneNode on disk (bytes) */
|
|
22
|
+
export declare const SCENE_NODE_SIZE = 116;
|
|
23
|
+
/** Size of MaterialData fixed portion on disk (bytes) */
|
|
24
|
+
export declare const MATERIAL_DATA_FIXED_SIZE = 196;
|
|
25
|
+
/** Size of MaterialPropertyValue on disk (bytes) */
|
|
26
|
+
export declare const MATERIAL_PROPERTY_VALUE_SIZE = 144;
|
|
27
|
+
/** Size of MeshMetadata fixed portion on disk (bytes) */
|
|
28
|
+
export declare const MESH_METADATA_FIXED_SIZE = 140;
|
|
29
|
+
/** Node name field size */
|
|
30
|
+
export declare const NODE_NAME_SIZE = 64;
|
|
31
|
+
/** Material name field size */
|
|
32
|
+
export declare const MATERIAL_NAME_SIZE = 64;
|
|
33
|
+
/** Material schema_id field size */
|
|
34
|
+
export declare const MATERIAL_SCHEMA_ID_SIZE = 128;
|
|
35
|
+
/** MaterialPropertyValue field_name / texture_id size */
|
|
36
|
+
export declare const MATERIAL_FIELD_NAME_SIZE = 64;
|
|
37
|
+
/** Material schema_name field size */
|
|
38
|
+
export declare const MATERIAL_SCHEMA_NAME_SIZE = 64;
|
|
39
|
+
/** MaterialSchemaField default_texture_id size */
|
|
40
|
+
export declare const MATERIAL_SCHEMA_DEFAULT_TEXTURE_ID_SIZE = 128;
|
|
41
|
+
/** Size of MaterialSchemaMetadata fixed portion on disk (bytes) */
|
|
42
|
+
export declare const MATERIAL_SCHEMA_METADATA_FIXED_SIZE = 196;
|
|
43
|
+
/** Size of MaterialSchemaField on disk (bytes) */
|
|
44
|
+
export declare const MATERIAL_SCHEMA_FIELD_SIZE = 216;
|
|
45
|
+
/** Bone name field size */
|
|
46
|
+
export declare const BONE_NAME_SIZE = 64;
|
|
47
|
+
/** Size of SkinnedMeshGeometryInfo on disk (bytes) */
|
|
48
|
+
export declare const SKINNED_MESH_GEOMETRY_INFO_SIZE = 72;
|
|
49
|
+
/** Size of SkinnedMeshMetadata fixed portion on disk (bytes) */
|
|
50
|
+
export declare const SKINNED_MESH_METADATA_FIXED_SIZE = 144;
|
|
51
|
+
/** Size of Bone on disk (bytes): name[64] + parent_index(4) + offset_matrix[64] */
|
|
52
|
+
export declare const BONE_SIZE = 132;
|
|
53
|
+
/** Size of VertexWeights on disk (bytes): bone_indices[16] + weights[16] */
|
|
54
|
+
export declare const VERTEX_WEIGHTS_SIZE = 32;
|
|
55
|
+
/** Interleaved vertex size for skinned meshes: pos[3] + normal[3] + uv[2] = 8 floats */
|
|
56
|
+
export declare const SKINNED_VERTEX_STRIDE = 32;
|
|
57
|
+
/** Size of AnimationTrackDescriptor on disk (bytes) */
|
|
58
|
+
export declare const ANIMATION_TRACK_DESCRIPTOR_SIZE = 88;
|
|
59
|
+
/** Size of AnimationClipMetadata fixed portion on disk (bytes) */
|
|
60
|
+
export declare const ANIMATION_CLIP_METADATA_FIXED_SIZE = 268;
|
|
61
|
+
/** Track name field size in AnimationTrackDescriptor */
|
|
62
|
+
export declare const TRACK_NAME_SIZE = 64;
|
|
63
|
+
/** Metadata version discriminator for v2 animation clips */
|
|
64
|
+
export declare const ANIMATION_METADATA_VERSION = 2;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Magic number: 'STKT' as little-endian uint32 (0x544B5453) */
|
|
2
|
+
export const STOW_MAGIC = 0x544B5453;
|
|
3
|
+
/** Current format version */
|
|
4
|
+
export const STOW_VERSION = 1;
|
|
5
|
+
/** Size of the FileHeader on disk (bytes) */
|
|
6
|
+
export const FILE_HEADER_SIZE = 20;
|
|
7
|
+
/** Size of a single AssetDirectoryEntry on disk (bytes) */
|
|
8
|
+
export const DIRECTORY_ENTRY_SIZE = 40;
|
|
9
|
+
/** Alignment boundary for data and metadata blobs (bytes) */
|
|
10
|
+
export const DATA_ALIGNMENT = 16;
|
|
11
|
+
/** Maximum canonical path length (characters) */
|
|
12
|
+
export const MAX_PATH_LENGTH = 512;
|
|
13
|
+
/** Size of the string_id field in metadata structs (bytes) */
|
|
14
|
+
export const STRING_ID_SIZE = 128;
|
|
15
|
+
/** Size of TextureMetadata on disk (bytes) */
|
|
16
|
+
export const TEXTURE_METADATA_SIZE = 144;
|
|
17
|
+
/** Size of AudioMetadata on disk (bytes) */
|
|
18
|
+
export const AUDIO_METADATA_SIZE = 140;
|
|
19
|
+
/** Size of MeshGeometryInfo on disk (bytes) */
|
|
20
|
+
export const MESH_GEOMETRY_INFO_SIZE = 40;
|
|
21
|
+
/** Size of SceneNode on disk (bytes) */
|
|
22
|
+
export const SCENE_NODE_SIZE = 116;
|
|
23
|
+
/** Size of MaterialData fixed portion on disk (bytes) */
|
|
24
|
+
export const MATERIAL_DATA_FIXED_SIZE = 196;
|
|
25
|
+
/** Size of MaterialPropertyValue on disk (bytes) */
|
|
26
|
+
export const MATERIAL_PROPERTY_VALUE_SIZE = 144;
|
|
27
|
+
/** Size of MeshMetadata fixed portion on disk (bytes) */
|
|
28
|
+
export const MESH_METADATA_FIXED_SIZE = 140;
|
|
29
|
+
/** Node name field size */
|
|
30
|
+
export const NODE_NAME_SIZE = 64;
|
|
31
|
+
/** Material name field size */
|
|
32
|
+
export const MATERIAL_NAME_SIZE = 64;
|
|
33
|
+
/** Material schema_id field size */
|
|
34
|
+
export const MATERIAL_SCHEMA_ID_SIZE = 128;
|
|
35
|
+
/** MaterialPropertyValue field_name / texture_id size */
|
|
36
|
+
export const MATERIAL_FIELD_NAME_SIZE = 64;
|
|
37
|
+
/** Material schema_name field size */
|
|
38
|
+
export const MATERIAL_SCHEMA_NAME_SIZE = 64;
|
|
39
|
+
/** MaterialSchemaField default_texture_id size */
|
|
40
|
+
export const MATERIAL_SCHEMA_DEFAULT_TEXTURE_ID_SIZE = 128;
|
|
41
|
+
/** Size of MaterialSchemaMetadata fixed portion on disk (bytes) */
|
|
42
|
+
export const MATERIAL_SCHEMA_METADATA_FIXED_SIZE = 196;
|
|
43
|
+
/** Size of MaterialSchemaField on disk (bytes) */
|
|
44
|
+
export const MATERIAL_SCHEMA_FIELD_SIZE = 216;
|
|
45
|
+
/** Bone name field size */
|
|
46
|
+
export const BONE_NAME_SIZE = 64;
|
|
47
|
+
/** Size of SkinnedMeshGeometryInfo on disk (bytes) */
|
|
48
|
+
export const SKINNED_MESH_GEOMETRY_INFO_SIZE = 72;
|
|
49
|
+
/** Size of SkinnedMeshMetadata fixed portion on disk (bytes) */
|
|
50
|
+
export const SKINNED_MESH_METADATA_FIXED_SIZE = 144;
|
|
51
|
+
/** Size of Bone on disk (bytes): name[64] + parent_index(4) + offset_matrix[64] */
|
|
52
|
+
export const BONE_SIZE = 132;
|
|
53
|
+
/** Size of VertexWeights on disk (bytes): bone_indices[16] + weights[16] */
|
|
54
|
+
export const VERTEX_WEIGHTS_SIZE = 32;
|
|
55
|
+
/** Interleaved vertex size for skinned meshes: pos[3] + normal[3] + uv[2] = 8 floats */
|
|
56
|
+
export const SKINNED_VERTEX_STRIDE = 32;
|
|
57
|
+
// ─── Animation V2 Constants ─────────────────────────────────────────────────
|
|
58
|
+
/** Size of AnimationTrackDescriptor on disk (bytes) */
|
|
59
|
+
export const ANIMATION_TRACK_DESCRIPTOR_SIZE = 88;
|
|
60
|
+
/** Size of AnimationClipMetadata fixed portion on disk (bytes) */
|
|
61
|
+
export const ANIMATION_CLIP_METADATA_FIXED_SIZE = 268;
|
|
62
|
+
/** Track name field size in AnimationTrackDescriptor */
|
|
63
|
+
export const TRACK_NAME_SIZE = 64;
|
|
64
|
+
/** Metadata version discriminator for v2 animation clips */
|
|
65
|
+
export const ANIMATION_METADATA_VERSION = 2;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MAX_PATH_LENGTH } from './constants.js';
|
|
2
|
+
export function normalizePath(path) {
|
|
3
|
+
let result = path.replace(/\\/g, '/');
|
|
4
|
+
result = result.replace(/\/+/g, '/');
|
|
5
|
+
result = result.toLowerCase();
|
|
6
|
+
if (result.length > 1 && result.endsWith('/')) {
|
|
7
|
+
result = result.slice(0, -1);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
export function validatePath(path) {
|
|
12
|
+
if (!path || path.length === 0)
|
|
13
|
+
return 'Path is empty';
|
|
14
|
+
if (path.length > MAX_PATH_LENGTH)
|
|
15
|
+
return `Path exceeds ${MAX_PATH_LENGTH} characters`;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export function djb2Hash(input) {
|
|
19
|
+
const normalized = normalizePath(input);
|
|
20
|
+
let hash = 5381n;
|
|
21
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
22
|
+
const c = BigInt(normalized.charCodeAt(i));
|
|
23
|
+
hash = ((hash << 5n) + hash) + c;
|
|
24
|
+
hash = hash & 0xffffffffffffffffn;
|
|
25
|
+
}
|
|
26
|
+
return hash;
|
|
27
|
+
}
|