universal-game-modder 0.1.4

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.
Files changed (92) hide show
  1. package/ACCEPTABLE_USE.md +49 -0
  2. package/CHANGELOG.md +119 -0
  3. package/LICENSE +21 -0
  4. package/LICENSE-EULA.md +62 -0
  5. package/README.md +162 -0
  6. package/VERIFICATION.md +132 -0
  7. package/dist/backends/child-pool.js +91 -0
  8. package/dist/backends/mcp-child.js +102 -0
  9. package/dist/backends/mcp-child.js.map +1 -0
  10. package/dist/core/config.d.ts +10 -0
  11. package/dist/core/config.js +48 -0
  12. package/dist/core/config.js.map +1 -0
  13. package/dist/core/engine-detector.d.ts +13 -0
  14. package/dist/core/engine-detector.js +247 -0
  15. package/dist/core/engine-detector.js.map +1 -0
  16. package/dist/core/event-bus.js +18 -0
  17. package/dist/core/file-classifier.js +394 -0
  18. package/dist/core/model-db.js +517 -0
  19. package/dist/core/model-db.js.map +1 -0
  20. package/dist/core/router.d.ts +6 -0
  21. package/dist/core/router.js +143 -0
  22. package/dist/core/router.js.map +1 -0
  23. package/dist/core/session.js +114 -0
  24. package/dist/decoders/audio-export.js +57 -0
  25. package/dist/decoders/mesh-export.d.ts +20 -0
  26. package/dist/decoders/mesh-export.js +174 -0
  27. package/dist/decoders/mesh-export.js.map +1 -0
  28. package/dist/decoders/native/disassembler.js +85 -0
  29. package/dist/decoders/native/disassembler.js.map +1 -0
  30. package/dist/decoders/png-encode.d.ts +17 -0
  31. package/dist/decoders/png-encode.js +33 -0
  32. package/dist/decoders/texture-decode.js +278 -0
  33. package/dist/decoders/texture-decode.js.map +1 -0
  34. package/dist/decoders/unity/audioclip.d.ts +55 -0
  35. package/dist/decoders/unity/audioclip.js +87 -0
  36. package/dist/decoders/unity/audioclip.js.map +1 -0
  37. package/dist/decoders/unity/binary-reader.d.ts +45 -0
  38. package/dist/decoders/unity/binary-reader.js +141 -0
  39. package/dist/decoders/unity/binary-reader.js.map +1 -0
  40. package/dist/decoders/unity/mesh.js +198 -0
  41. package/dist/decoders/unity/mesh.js.map +1 -0
  42. package/dist/decoders/unity/ress-reader.d.ts +33 -0
  43. package/dist/decoders/unity/ress-reader.js +82 -0
  44. package/dist/decoders/unity/ress-reader.js.map +1 -0
  45. package/dist/decoders/unity/serialized-file.d.ts +65 -0
  46. package/dist/decoders/unity/serialized-file.js +144 -0
  47. package/dist/decoders/unity/texture2d.js +185 -0
  48. package/dist/decoders/unity/vertex-data.d.ts +54 -0
  49. package/dist/decoders/unity/vertex-data.js +174 -0
  50. package/dist/decoders/unity/vertex-data.js.map +1 -0
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.js +23 -0
  53. package/dist/server.d.ts +1 -0
  54. package/dist/server.js +66 -0
  55. package/dist/tools/decode-tools.d.ts +29 -0
  56. package/dist/tools/decode-tools.js +441 -0
  57. package/dist/tools/delegates/jar-editor.d.ts +2 -0
  58. package/dist/tools/delegates/jar-editor.js +276 -0
  59. package/dist/tools/delegates/unity-decompiler.d.ts +2 -0
  60. package/dist/tools/delegates/unity-decompiler.js +291 -0
  61. package/dist/tools/delegates/unreal-assets.d.ts +2 -0
  62. package/dist/tools/delegates/unreal-assets.js +122 -0
  63. package/dist/tools/detection-tools.d.ts +2 -0
  64. package/dist/tools/detection-tools.js +164 -0
  65. package/dist/tools/detection-tools.js.map +1 -0
  66. package/dist/tools/index.d.ts +8 -0
  67. package/dist/tools/index.js +30 -0
  68. package/dist/tools/index.js.map +1 -0
  69. package/dist/tools/native/index.d.ts +2 -0
  70. package/dist/tools/native/index.js +1201 -0
  71. package/dist/tools/native-comprehension-tools.js +298 -0
  72. package/dist/tools/native-comprehension-tools.js.map +1 -0
  73. package/dist/tools/unpack-tools.d.ts +2 -0
  74. package/dist/tools/unpack-tools.js +382 -0
  75. package/dist/tools/workflow-tools.d.ts +2 -0
  76. package/dist/tools/workflow-tools.js +434 -0
  77. package/dist/utils/logger.d.ts +11 -0
  78. package/dist/utils/logger.js +49 -0
  79. package/dist/utils/logger.js.map +1 -0
  80. package/dist/utils/path-utils.d.ts +15 -0
  81. package/dist/utils/path-utils.js +24 -0
  82. package/dist/utils/path-utils.js.map +1 -0
  83. package/dist/utils/steam-finder.d.ts +8 -0
  84. package/dist/utils/steam-finder.js +32 -0
  85. package/dist/web/web-server.d.ts +1 -0
  86. package/dist/web/web-server.js +369 -0
  87. package/licenses/THIRD-PARTY-NOTICES.md +35 -0
  88. package/llms-install.md +34 -0
  89. package/package.json +69 -0
  90. package/server.json +20 -0
  91. package/setup.ps1 +34 -0
  92. package/ugm.config.json +9 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/core/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import { eventBus } from './event-bus.js';
2
+ class GameSession {
3
+ state;
4
+ constructor() {
5
+ this.state = this.createEmptyState();
6
+ }
7
+ createEmptyState() {
8
+ return {
9
+ gamePath: null,
10
+ gameName: null,
11
+ engine: null,
12
+ engineVersion: null,
13
+ runtime: null,
14
+ loadedAssemblies: [],
15
+ loadedUnrealGame: null,
16
+ openJarSessions: [],
17
+ discoveredTypes: new Map(),
18
+ discoveredValues: new Map(),
19
+ decompilationCache: new Map(),
20
+ modFramework: null,
21
+ modProjectPath: null,
22
+ lastBuildResult: null,
23
+ deployPath: null,
24
+ toolCallHistory: [],
25
+ };
26
+ }
27
+ getState() {
28
+ return this.state;
29
+ }
30
+ getSerializableState() {
31
+ return {
32
+ gamePath: this.state.gamePath,
33
+ gameName: this.state.gameName,
34
+ engine: this.state.engine,
35
+ engineVersion: this.state.engineVersion,
36
+ runtime: this.state.runtime,
37
+ loadedAssemblies: this.state.loadedAssemblies,
38
+ loadedUnrealGame: this.state.loadedUnrealGame,
39
+ openJarSessions: this.state.openJarSessions,
40
+ discoveredTypesCount: this.state.discoveredTypes.size,
41
+ discoveredValuesCount: this.state.discoveredValues.size,
42
+ decompilationCacheSize: this.state.decompilationCache.size,
43
+ modFramework: this.state.modFramework,
44
+ modProjectPath: this.state.modProjectPath,
45
+ lastBuildResult: this.state.lastBuildResult,
46
+ deployPath: this.state.deployPath,
47
+ toolCallCount: this.state.toolCallHistory.length,
48
+ recentToolCalls: this.state.toolCallHistory.slice(-20),
49
+ };
50
+ }
51
+ setGame(gamePath, gameName, engine, runtime) {
52
+ this.state.gamePath = gamePath;
53
+ this.state.gameName = gameName;
54
+ this.state.engine = engine;
55
+ this.state.runtime = runtime;
56
+ eventBus.emit('session:updated', {
57
+ gamePath,
58
+ gameName,
59
+ engine,
60
+ runtime,
61
+ });
62
+ }
63
+ addAssembly(key, path, typeCount) {
64
+ const existing = this.state.loadedAssemblies.find(a => a.key === key);
65
+ if (existing) {
66
+ existing.typeCount = typeCount;
67
+ }
68
+ else {
69
+ this.state.loadedAssemblies.push({ key, path, typeCount });
70
+ }
71
+ }
72
+ cacheType(name, info) {
73
+ this.state.discoveredTypes.set(name, info);
74
+ }
75
+ cacheValue(key, info) {
76
+ this.state.discoveredValues.set(key, info);
77
+ }
78
+ cacheDecompilation(typeKey, source) {
79
+ this.state.decompilationCache.set(typeKey, source);
80
+ }
81
+ getCachedDecompilation(typeKey) {
82
+ return this.state.decompilationCache.get(typeKey);
83
+ }
84
+ recordToolCall(record) {
85
+ this.state.toolCallHistory.push(record);
86
+ // Keep last 500 entries
87
+ if (this.state.toolCallHistory.length > 500) {
88
+ this.state.toolCallHistory = this.state.toolCallHistory.slice(-500);
89
+ }
90
+ }
91
+ setModFramework(framework) {
92
+ this.state.modFramework = framework;
93
+ }
94
+ setBuildResult(result) {
95
+ this.state.lastBuildResult = result;
96
+ }
97
+ setDeployPath(path) {
98
+ this.state.deployPath = path;
99
+ }
100
+ setModProjectPath(path) {
101
+ this.state.modProjectPath = path;
102
+ }
103
+ reset() {
104
+ this.state = this.createEmptyState();
105
+ eventBus.emit('session:updated', {
106
+ gamePath: null,
107
+ gameName: null,
108
+ engine: null,
109
+ runtime: null,
110
+ });
111
+ }
112
+ }
113
+ export const session = new GameSession();
114
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * audio-export.ts — turn Unity AudioClip stream bytes into a playable file.
3
+ *
4
+ * No codec library is needed for the two formats Baldi uses (verified by recon):
5
+ * - PCM : the stream bytes are raw little-endian samples; we prepend a 44-byte WAV header.
6
+ * - Vorbis: Unity stores the bytes as a COMPLETE Ogg-Vorbis stream; we write them verbatim as .ogg.
7
+ *
8
+ * Other formats (ADPCM, MP3, etc.) are not handled here — the caller checks compressionFormat
9
+ * and records a noted decoded=0 row rather than producing a broken file.
10
+ */
11
+ import { AudioCompressionFormat } from './unity/audioclip.js';
12
+ /** Build a canonical 44-byte WAV (RIFF/PCM) header for the given format + data length. */
13
+ function wavHeader(channels, sampleRate, bitsPerSample, dataLen) {
14
+ const h = Buffer.alloc(44);
15
+ const byteRate = (sampleRate * channels * bitsPerSample) / 8;
16
+ const blockAlign = (channels * bitsPerSample) / 8;
17
+ h.write('RIFF', 0, 'ascii');
18
+ h.writeUInt32LE(36 + dataLen, 4); // ChunkSize
19
+ h.write('WAVE', 8, 'ascii');
20
+ h.write('fmt ', 12, 'ascii');
21
+ h.writeUInt32LE(16, 16); // Subchunk1Size (PCM)
22
+ h.writeUInt16LE(1, 20); // AudioFormat = 1 (PCM)
23
+ h.writeUInt16LE(channels, 22);
24
+ h.writeUInt32LE(sampleRate, 24);
25
+ h.writeUInt32LE(byteRate, 28);
26
+ h.writeUInt16LE(blockAlign, 32);
27
+ h.writeUInt16LE(bitsPerSample, 34);
28
+ h.write('data', 36, 'ascii');
29
+ h.writeUInt32LE(dataLen, 40); // Subchunk2Size
30
+ return h;
31
+ }
32
+ /**
33
+ * Export an AudioClip's stream bytes. Returns null (caller notes it) for an unsupported
34
+ * compression format. `streamBytes` is what the .resource reader returned at offset/size.
35
+ */
36
+ export function exportAudio(clip, streamBytes) {
37
+ switch (clip.compressionFormat) {
38
+ case AudioCompressionFormat.PCM: {
39
+ const bits = clip.bitsPerSample > 0 ? clip.bitsPerSample : 16;
40
+ const header = wavHeader(clip.channels, clip.frequency, bits, streamBytes.length);
41
+ return { ext: 'wav', bytes: Buffer.concat([header, streamBytes]) };
42
+ }
43
+ case AudioCompressionFormat.Vorbis: {
44
+ // Unity's Vorbis payload is a complete Ogg stream — write verbatim.
45
+ // Sanity: a real Ogg stream begins with the "OggS" capture pattern.
46
+ if (streamBytes.length >= 4 && streamBytes.toString('ascii', 0, 4) === 'OggS') {
47
+ return { ext: 'ogg', bytes: streamBytes };
48
+ }
49
+ // Some Unity builds wrap Vorbis with an FSB5 header before the Ogg pages; if we don't
50
+ // see OggS we cannot safely passthrough — let the caller note it rather than emit garbage.
51
+ return null;
52
+ }
53
+ default:
54
+ return null; // ADPCM / MP3 / etc. — unsupported in this slice
55
+ }
56
+ }
57
+ //# sourceMappingURL=audio-export.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * mesh-export.ts — turn a ParsedMesh into a standard 3D file.
3
+ *
4
+ * Two outputs, both hand-rolled (no native dep — same clean-install rule as the rest):
5
+ * - glTF binary (.glb): the primary, modern format. A glb is a tiny binary container:
6
+ * 12-byte header + a JSON chunk (the glTF document) + a BIN chunk (the raw buffers).
7
+ * We emit one mesh, one primitive, with POSITION/NORMAL/TEXCOORD_0 accessors and an
8
+ * indices accessor. This loads in Blender, three.js, Windows 3D Viewer, etc.
9
+ * - Wavefront OBJ (.obj): a dead-simple text fallback (v / vn / vt / f lines). No deps,
10
+ * universally readable, lossy on anything fancy but perfect for a geometry sanity check.
11
+ *
12
+ * The glb writer is intentionally minimal and spec-correct for the single-mesh case; it is
13
+ * not a general glTF library. Coordinates are written as-is (Unity's left-handed Y-up) — a
14
+ * handedness flip is a downstream concern we note rather than silently apply.
15
+ */
16
+ import type { ParsedMesh } from './unity/mesh.js';
17
+ /** Export a parsed mesh to a .glb byte buffer. Throws if there are no positions. */
18
+ export declare function exportGlb(mesh: ParsedMesh): Buffer;
19
+ /** Export a parsed mesh to Wavefront OBJ text. Simple, lossless on geometry, no deps. */
20
+ export declare function exportObj(mesh: ParsedMesh): Buffer;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * mesh-export.ts — turn a ParsedMesh into a standard 3D file.
3
+ *
4
+ * Two outputs, both hand-rolled (no native dep — same clean-install rule as the rest):
5
+ * - glTF binary (.glb): the primary, modern format. A glb is a tiny binary container:
6
+ * 12-byte header + a JSON chunk (the glTF document) + a BIN chunk (the raw buffers).
7
+ * We emit one mesh, one primitive, with POSITION/NORMAL/TEXCOORD_0 accessors and an
8
+ * indices accessor. This loads in Blender, three.js, Windows 3D Viewer, etc.
9
+ * - Wavefront OBJ (.obj): a dead-simple text fallback (v / vn / vt / f lines). No deps,
10
+ * universally readable, lossy on anything fancy but perfect for a geometry sanity check.
11
+ *
12
+ * The glb writer is intentionally minimal and spec-correct for the single-mesh case; it is
13
+ * not a general glTF library. Coordinates are written as-is (Unity's left-handed Y-up) — a
14
+ * handedness flip is a downstream concern we note rather than silently apply.
15
+ */
16
+ const GLB_MAGIC = 0x46546c67; // "glTF"
17
+ const GLB_VERSION = 2;
18
+ const CHUNK_JSON = 0x4e4f534a; // "JSON"
19
+ const CHUNK_BIN = 0x004e4942; // "BIN\0"
20
+ const COMPONENT_FLOAT = 5126;
21
+ const COMPONENT_UINT = 5125;
22
+ const TARGET_ARRAY_BUFFER = 34962;
23
+ const TARGET_ELEMENT_ARRAY_BUFFER = 34963;
24
+ function pad4(n) {
25
+ return (4 - (n % 4)) % 4;
26
+ }
27
+ /** Min/max per component, for the glTF accessor (required on POSITION). */
28
+ function minMax(arr, dim) {
29
+ const min = new Array(dim).fill(Infinity);
30
+ const max = new Array(dim).fill(-Infinity);
31
+ for (let i = 0; i < arr.length; i += dim) {
32
+ for (let c = 0; c < dim; c++) {
33
+ const v = arr[i + c];
34
+ if (v < min[c])
35
+ min[c] = v;
36
+ if (v > max[c])
37
+ max[c] = v;
38
+ }
39
+ }
40
+ return { min, max };
41
+ }
42
+ /** Export a parsed mesh to a .glb byte buffer. Throws if there are no positions. */
43
+ export function exportGlb(mesh) {
44
+ if (!mesh.positions || mesh.vertexCount === 0) {
45
+ throw new Error(`exportGlb("${mesh.name}"): no positions to export`);
46
+ }
47
+ // Build the BIN buffer: positions, [normals], [uvs], indices — each 4-byte aligned.
48
+ const parts = [];
49
+ let binLength = 0;
50
+ const pushFloat = (arr) => {
51
+ const b = Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
52
+ const byteOffset = binLength;
53
+ parts.push({ data: b, byteOffset, byteLength: b.length });
54
+ binLength += b.length + pad4(b.length);
55
+ return byteOffset;
56
+ };
57
+ const posOffset = pushFloat(mesh.positions);
58
+ const normOffset = mesh.normals ? pushFloat(mesh.normals) : -1;
59
+ const uvOffset = mesh.uvs ? pushFloat(mesh.uvs) : -1;
60
+ // indices as uint32
61
+ const idxBuf = Buffer.from(mesh.indices.buffer, mesh.indices.byteOffset, mesh.indices.byteLength);
62
+ const idxOffset = binLength;
63
+ parts.push({ data: idxBuf, byteOffset: idxOffset, byteLength: idxBuf.length });
64
+ binLength += idxBuf.length + pad4(idxBuf.length);
65
+ const bin = Buffer.alloc(binLength);
66
+ for (const part of parts)
67
+ part.data.copy(bin, part.byteOffset);
68
+ // Accessors + bufferViews.
69
+ const posMM = minMax(mesh.positions, 3);
70
+ const bufferViews = [];
71
+ const accessors = [];
72
+ const addView = (byteOffset, byteLength, target) => {
73
+ bufferViews.push({ buffer: 0, byteOffset, byteLength, target });
74
+ return bufferViews.length - 1;
75
+ };
76
+ const addAccessor = (a) => {
77
+ accessors.push(a);
78
+ return accessors.length - 1;
79
+ };
80
+ const posView = addView(posOffset, mesh.positions.byteLength, TARGET_ARRAY_BUFFER);
81
+ const posAcc = addAccessor({
82
+ bufferView: posView,
83
+ componentType: COMPONENT_FLOAT,
84
+ count: mesh.vertexCount,
85
+ type: 'VEC3',
86
+ min: posMM.min,
87
+ max: posMM.max,
88
+ });
89
+ const attributes = { POSITION: posAcc };
90
+ if (mesh.normals && normOffset >= 0) {
91
+ const v = addView(normOffset, mesh.normals.byteLength, TARGET_ARRAY_BUFFER);
92
+ attributes.NORMAL = addAccessor({ bufferView: v, componentType: COMPONENT_FLOAT, count: mesh.vertexCount, type: 'VEC3' });
93
+ }
94
+ if (mesh.uvs && uvOffset >= 0) {
95
+ const v = addView(uvOffset, mesh.uvs.byteLength, TARGET_ARRAY_BUFFER);
96
+ attributes.TEXCOORD_0 = addAccessor({ bufferView: v, componentType: COMPONENT_FLOAT, count: mesh.vertexCount, type: 'VEC2' });
97
+ }
98
+ const idxView = addView(idxOffset, idxBuf.length, TARGET_ELEMENT_ARRAY_BUFFER);
99
+ const idxAcc = addAccessor({
100
+ bufferView: idxView,
101
+ componentType: COMPONENT_UINT,
102
+ count: mesh.indices.length,
103
+ type: 'SCALAR',
104
+ });
105
+ const gltf = {
106
+ asset: { version: '2.0', generator: 'autopsy-engine/decode_assets' },
107
+ buffers: [{ byteLength: binLength }],
108
+ bufferViews,
109
+ accessors,
110
+ meshes: [{ name: mesh.name, primitives: [{ attributes, indices: idxAcc, mode: 4 /* TRIANGLES */ }] }],
111
+ nodes: [{ name: mesh.name, mesh: 0 }],
112
+ scenes: [{ nodes: [0] }],
113
+ scene: 0,
114
+ };
115
+ let jsonStr = JSON.stringify(gltf);
116
+ // JSON chunk must be 4-byte aligned, padded with spaces.
117
+ const jsonPad = pad4(Buffer.byteLength(jsonStr, 'utf8'));
118
+ jsonStr += ' '.repeat(jsonPad);
119
+ const jsonBuf = Buffer.from(jsonStr, 'utf8');
120
+ // BIN chunk padded with zeros (already aligned via binLength).
121
+ const totalLength = 12 + 8 + jsonBuf.length + 8 + bin.length;
122
+ const out = Buffer.alloc(totalLength);
123
+ let o = 0;
124
+ out.writeUInt32LE(GLB_MAGIC, o);
125
+ o += 4;
126
+ out.writeUInt32LE(GLB_VERSION, o);
127
+ o += 4;
128
+ out.writeUInt32LE(totalLength, o);
129
+ o += 4;
130
+ out.writeUInt32LE(jsonBuf.length, o);
131
+ o += 4;
132
+ out.writeUInt32LE(CHUNK_JSON, o);
133
+ o += 4;
134
+ jsonBuf.copy(out, o);
135
+ o += jsonBuf.length;
136
+ out.writeUInt32LE(bin.length, o);
137
+ o += 4;
138
+ out.writeUInt32LE(CHUNK_BIN, o);
139
+ o += 4;
140
+ bin.copy(out, o);
141
+ o += bin.length;
142
+ return out;
143
+ }
144
+ /** Export a parsed mesh to Wavefront OBJ text. Simple, lossless on geometry, no deps. */
145
+ export function exportObj(mesh) {
146
+ if (!mesh.positions || mesh.vertexCount === 0) {
147
+ throw new Error(`exportObj("${mesh.name}"): no positions to export`);
148
+ }
149
+ const lines = [`# ${mesh.name} — exported by autopsy-engine/decode_assets`, `o ${mesh.name}`];
150
+ for (let i = 0; i < mesh.vertexCount; i++) {
151
+ lines.push(`v ${mesh.positions[i * 3]} ${mesh.positions[i * 3 + 1]} ${mesh.positions[i * 3 + 2]}`);
152
+ }
153
+ if (mesh.normals) {
154
+ for (let i = 0; i < mesh.vertexCount; i++) {
155
+ lines.push(`vn ${mesh.normals[i * 3]} ${mesh.normals[i * 3 + 1]} ${mesh.normals[i * 3 + 2]}`);
156
+ }
157
+ }
158
+ if (mesh.uvs) {
159
+ for (let i = 0; i < mesh.vertexCount; i++) {
160
+ lines.push(`vt ${mesh.uvs[i * 2]} ${mesh.uvs[i * 2 + 1]}`);
161
+ }
162
+ }
163
+ const hasN = !!mesh.normals;
164
+ const hasUV = !!mesh.uvs;
165
+ for (let i = 0; i + 2 < mesh.indices.length; i += 3) {
166
+ const a = mesh.indices[i] + 1;
167
+ const b = mesh.indices[i + 1] + 1;
168
+ const c = mesh.indices[i + 2] + 1;
169
+ const ref = (v) => (hasUV && hasN ? `${v}/${v}/${v}` : hasN ? `${v}//${v}` : hasUV ? `${v}/${v}` : `${v}`);
170
+ lines.push(`f ${ref(a)} ${ref(b)} ${ref(c)}`);
171
+ }
172
+ return Buffer.from(lines.join('\n') + '\n', 'utf8');
173
+ }
174
+ //# sourceMappingURL=mesh-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mesh-export.js","sourceRoot":"","sources":["../../src/decoders/mesh-export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * disassembler.ts — PR-3.1 (V3 Native Comprehension): the disassembly binding.
3
+ *
4
+ * Wraps capstone-wasm (chosen 2026-07-01 by an evidence-based 3-way probe vs a
5
+ * real notepad.exe .text section — see .forge/pr_log.md). The WASM binding won
6
+ * on install story (0 deps, pure bundle), correctness (byte-identical to the
7
+ * proven mko/ Python Capstone oracle), and in-process speed. The native addon
8
+ * `capstone@3.0.1` was ELIMINATED (dead node-ffi/ref, won't build on Node 22);
9
+ * Python-Capstone via child-process is retained ONLY as an offline verify oracle.
10
+ *
11
+ * Two gotchas this module hides from callers (both learned in the probe):
12
+ * 1. disasm(bytes, { address }) — the base VA is an OPTIONS field, NOT positional.
13
+ * Passing a bare number silently decodes from address 0.
14
+ * 2. address must be a BigInt for 64-bit runtime VAs (e.g. 0x140001000); a plain
15
+ * number works for small values but BigInt is correct across the range. The
16
+ * library returns Insn.address as number | bigint — we normalise on the way out.
17
+ */
18
+ import { Capstone, Const, loadCapstone } from 'capstone-wasm';
19
+ /**
20
+ * capstone-wasm must be initialised (WASM compiled/instantiated) exactly once
21
+ * per process before any Capstone instance is constructed. loadCapstone() is
22
+ * idempotent-safe to await, but we guard so concurrent tool calls share one init.
23
+ */
24
+ let initPromise = null;
25
+ export async function ensureCapstone() {
26
+ if (!initPromise)
27
+ initPromise = loadCapstone();
28
+ await initPromise;
29
+ }
30
+ /** Convert Insn.address (number | bigint) to a safe JS number. */
31
+ function toNum(v) {
32
+ return typeof v === 'bigint' ? Number(v) : v;
33
+ }
34
+ /** Convert raw insn bytes to "48 8b 05" style hex. */
35
+ function bytesToHex(bytes) {
36
+ return Array.from(bytes)
37
+ .map((b) => b.toString(16).padStart(2, '0'))
38
+ .join(' ');
39
+ }
40
+ /**
41
+ * Disassemble a raw byte range as x86-64. `bytes` is the exact code slice; the
42
+ * caller has already resolved section/offset and knows the runtime VA the first
43
+ * byte maps to. Returns normalised instructions with both VA and RVA.
44
+ *
45
+ * Read-only: this touches no files and mutates nothing — the caller reads the
46
+ * binary; we only decode the bytes it hands us.
47
+ */
48
+ export async function disassembleX64(bytes, opts) {
49
+ await ensureCapstone();
50
+ const cs = new Capstone(Const.CS_ARCH_X86, Const.CS_MODE_64);
51
+ try {
52
+ // GOTCHA 1 + 2: address is an options field, and BigInt for 64-bit VAs.
53
+ const insns = cs.disasm(bytes, {
54
+ address: BigInt(opts.runtimeVA),
55
+ ...(opts.maxInsns && opts.maxInsns > 0 ? { count: opts.maxInsns } : {}),
56
+ });
57
+ return insns.map((i) => {
58
+ const va = toNum(i.address);
59
+ return {
60
+ va,
61
+ rva: va - opts.imageBase,
62
+ size: i.size,
63
+ mnemonic: i.mnemonic,
64
+ opStr: i.opStr ?? '',
65
+ bytes: bytesToHex(i.bytes),
66
+ };
67
+ });
68
+ }
69
+ finally {
70
+ // Free the WASM handle; a leaked handle would grow WASM memory across calls.
71
+ try {
72
+ cs.close();
73
+ }
74
+ catch {
75
+ /* best-effort */
76
+ }
77
+ }
78
+ }
79
+ /** The capstone-wasm version, for provenance/reporting. */
80
+ export async function capstoneVersion() {
81
+ await ensureCapstone();
82
+ const v = Capstone.version();
83
+ return `${v.major}.${v.minor}`;
84
+ }
85
+ //# sourceMappingURL=disassembler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disassembler.js","sourceRoot":"","sources":["../../../src/decoders/native/disassembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA2B9D;;;;GAIG;AACH,IAAI,WAAW,GAAyB,IAAI,CAAC;AAC7C,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,YAAY,EAAE,CAAC;IAC/C,MAAM,WAAW,CAAC;AACpB,CAAC;AAED,kEAAkE;AAClE,SAAS,KAAK,CAAC,CAAkB;IAC/B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,sDAAsD;AACtD,SAAS,UAAU,CAAC,KAAiB;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAiB,EACjB,IAAmB;IAEnB,MAAM,cAAc,EAAE,CAAC;IAEvB,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7D,IAAI,CAAC;QACH,wEAAwE;QACxE,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;YAC7B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;YAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC,CAAC;QAEH,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAc,EAAE;YACjC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC5B,OAAO;gBACL,EAAE;gBACF,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS;gBACxB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;gBACpB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;aAC3B,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,6EAA6E;QAC7E,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,cAAc,EAAE,CAAC;IACvB,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;IAC7B,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * png-encode.ts — encode tightly-packed RGBA8 pixels to a PNG buffer.
3
+ *
4
+ * Unity textures are stored bottom-up (origin at the lower-left). PNG (and every image
5
+ * viewer) expects top-down. We flip vertically here so the exported PNG looks correct
6
+ * when opened — the single most common "the decode worked but the image is upside down"
7
+ * bug, handled once, centrally.
8
+ *
9
+ * Uses pngjs (pure-JS, no native compile — same install-cleanliness rule as better-sqlite3).
10
+ */
11
+ /**
12
+ * @param rgba tightly packed RGBA8, length === width*height*4, bottom-up (Unity order)
13
+ * @param width texture width in pixels
14
+ * @param height texture height in pixels
15
+ * @returns PNG file bytes (top-down, RGBA)
16
+ */
17
+ export declare function encodePng(rgba: Buffer, width: number, height: number): Buffer;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * png-encode.ts — encode tightly-packed RGBA8 pixels to a PNG buffer.
3
+ *
4
+ * Unity textures are stored bottom-up (origin at the lower-left). PNG (and every image
5
+ * viewer) expects top-down. We flip vertically here so the exported PNG looks correct
6
+ * when opened — the single most common "the decode worked but the image is upside down"
7
+ * bug, handled once, centrally.
8
+ *
9
+ * Uses pngjs (pure-JS, no native compile — same install-cleanliness rule as better-sqlite3).
10
+ */
11
+ import { PNG } from 'pngjs';
12
+ /**
13
+ * @param rgba tightly packed RGBA8, length === width*height*4, bottom-up (Unity order)
14
+ * @param width texture width in pixels
15
+ * @param height texture height in pixels
16
+ * @returns PNG file bytes (top-down, RGBA)
17
+ */
18
+ export function encodePng(rgba, width, height) {
19
+ const expected = width * height * 4;
20
+ if (rgba.length < expected) {
21
+ throw new RangeError(`encodePng: rgba length ${rgba.length} < expected ${expected} for ${width}x${height}`);
22
+ }
23
+ const png = new PNG({ width, height, colorType: 6 /* RGBA */ });
24
+ const rowBytes = width * 4;
25
+ // Flip vertically: source row (height-1-y) -> destination row y.
26
+ for (let y = 0; y < height; y++) {
27
+ const srcStart = (height - 1 - y) * rowBytes;
28
+ const dstStart = y * rowBytes;
29
+ rgba.copy(png.data, dstStart, srcStart, srcStart + rowBytes);
30
+ }
31
+ return PNG.sync.write(png);
32
+ }
33
+ //# sourceMappingURL=png-encode.js.map