@threenative/raw-unreal 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ThreeNative contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @threenative/raw-unreal
2
+
3
+ Loads **raw Unreal editor `.uasset` static meshes** directly into three.js geometry — no glTF/GLB/FBX/OBJ conversion step at any point.
4
+
5
+ ```
6
+ raw .uasset → package summary → payload scan → decode → typed arrays → THREE.Mesh
7
+ ```
8
+
9
+ Three serialized source-model layouts are decoded, from four payload stores:
10
+
11
+ | Layout | Engine generation | Where the data lives |
12
+ |---|---|---|
13
+ | `mesh-description` | UE5.x (`FMeshDescription`, named element containers) | Inline, or inside a UE5 `FCompressedBuffer` package-trailer payload |
14
+ | `mesh-description-ue4` | UE4.2x (`FMeshDescription`, fixed-order containers) | `FByteBulkData`, usually zlib-compressed at the end of the package |
15
+ | `raw-mesh` | UE4.6–UE4.2x (`FRawMesh` source models) | Inline uncompressed, or `FByteBulkData` |
16
+
17
+ `FByteBulkData` payloads are read wherever the flags put them: inline after the header, at the end
18
+ of the `.uasset` against the summary's `BulkDataStartOffset`, or in a sibling `.ubulk`/`.uptnl`
19
+ file whose bytes the caller supplies through `bulkDataFiles`.
20
+
21
+ Every path is self-validating: a candidate offset is trusted only after a full parse consumes it
22
+ exactly with every count agreeing, and the summary walk that anchors bulk data is accepted only
23
+ when it ends on the package's own name table. Unsupported layouts throw a typed `UAssetError`
24
+ with a code and details; nothing invents fallback geometry.
25
+
26
+ ## Use
27
+
28
+ ```ts
29
+ import { UAssetLoader } from "@threenative/raw-unreal";
30
+
31
+ const loader = new UAssetLoader(manager, { parse: { oodle } });
32
+ const mesh = await new Promise((resolve, reject) =>
33
+ loader.load("/assets/SM_pine01.uasset", resolve, undefined, reject),
34
+ );
35
+ scene.add(mesh); // materials via mesh.userData.unreal.sections / geometry groups
36
+ ```
37
+
38
+ The format layer is independent of three.js until the final adapter:
39
+
40
+ ```ts
41
+ import { parseUAssetStaticMesh } from "@threenative/raw-unreal";
42
+
43
+ const decoded = parseUAssetStaticMesh(bytes, { oodle });
44
+ // decoded.positions / normals / uvs / indices / sections / bounds / metadata / unreal
45
+ ```
46
+
47
+ ## Codec injection and licensing
48
+
49
+ This package is MIT and **bundles no compression codecs**. UE5 `FCompressedBuffer` payloads are
50
+ commonly Oodle-compressed, and every Oodle decompressor that is known to work in JavaScript is
51
+ GPL-licensed (`ooz-wasm`). To keep the GPL out of this package while keeping the capability
52
+ reachable, the loader takes codecs as call arguments:
53
+
54
+ ```ts
55
+ import { decompress as oodle } from "ooz-wasm"; // GPL-3.0-or-later — the game's choice to make
56
+
57
+ parseUAssetStaticMesh(bytes, { oodle });
58
+ ```
59
+
60
+ Adding `ooz-wasm` to a game applies that game's GPL obligations to that game's distribution;
61
+ it never touches this package. Uncompressed payloads need no codec, and LZ4 payloads take an
62
+ `lz4(compressed, rawSize)` function the same way. A payload whose codec was not supplied throws
63
+ `MISSING_CODEC` rather than guessing.
64
+
65
+ UE4 editor bulk data is stored zlib-compressed, which takes a `zlib(compressed, rawSize)` function
66
+ on the same terms — `node:zlib`'s `inflateSync` in Node, any of the small MIT inflate libraries in
67
+ the browser:
68
+
69
+ ```ts
70
+ import { inflateSync } from "node:zlib";
71
+
72
+ parseUAssetStaticMesh(bytes, { zlib: (data) => new Uint8Array(inflateSync(data)) });
73
+ ```
74
+
75
+ The test suite uses `ooz-wasm` as a **devDependency only** — it decodes the committed fixture and
76
+ ships in no published artifact.
77
+
78
+ ## Fixture provenance
79
+
80
+ `fixtures/SM_cube.uasset` is a real Unreal Engine 5.7 editor package from the MIT-licensed
81
+ [uassets](https://github.com/1danielcoelho/uassets) sample set, pinned by SHA-256 in the specs —
82
+ see `fixtures/PROVENANCE.md`. The FRawMesh specs run against synthetic packages built in
83
+ `__tests__/fixture-builder.ts`; the real-pack conformance spec runs only when the licensed
84
+ Landscape Pro pack is present on disk and commits nothing from it.
85
+
86
+ ## Current compatibility
87
+
88
+ **Decoded:** legacy-tag uncooked editor packages (`.uasset`/`.umap`), the package summary prefix
89
+ and the full summary walk (`TotalHeaderSize`, `BulkDataStartOffset`, `FEditorObjectVersion`) for
90
+ `LegacyFileVersion` −3 through −8, UE5 `FCompressedBuffer` payloads (uncompressed/Oodle/LZ4
91
+ methods), UE5 and UE4.2x `FMeshDescription` serializations, UE4-era `FRawMesh` source models,
92
+ `FByteBulkData` payloads stored inline, at the end of the package, or in a supplied sibling file,
93
+ `FArchive::SerializeCompressed` zlib containers, per-wedge positions/normals/UVs, polygon-group
94
+ sections with their `ImportedMaterialSlotName`s from a MeshDescription, Unreal→three.js coordinate
95
+ conversion (`(x, z, −y)`) and winding repair.
96
+
97
+ **Not decoded (throws, honestly):** IoStore containers (`.utoc`/`.ucas`), PAK archives, cooked
98
+ render buffers, Nanite clusters, skeletal meshes and skin weights, textures and material graphs,
99
+ `LegacyFileVersion` −9 and below (UE 5.5+ replaced the package GUID with a saved hash; the summary
100
+ walk declines rather than guessing, so bulk data in those packages is not reached), material-slot
101
+ *names* from FRawMesh packages (the section indices are real; the names need the package name map),
102
+ and full `.umap` actor reconstruction.
@@ -0,0 +1,428 @@
1
+ import * as three from 'three';
2
+ import { Material, BufferGeometry, Object3D, Loader, LoadingManager } from 'three';
3
+
4
+ /** Signature of an Oodle decompressor, matching the `decompress(compressed, rawSize)` shape the
5
+ * common WebAssembly Oodle builds expose. The package never bundles one; see README licensing. */
6
+ type OodleCodec = (compressed: Uint8Array, rawSize: number) => Uint8Array;
7
+ /** Signature of an LZ4 block decompressor. The package never bundles one. */
8
+ type Lz4Codec = (compressed: Uint8Array, rawSize: number) => Uint8Array;
9
+ /** Signature of a zlib (RFC 1950) decompressor, the form editor bulk data is stored in when the
10
+ * `BULKDATA_SerializeCompressedZLIB` flag is set. The package never bundles one; `node:zlib`'s
11
+ * `inflateSync` and any of the small browser inflate libraries fit this shape. */
12
+ type ZlibCodec = (compressed: Uint8Array, rawSize: number) => Uint8Array;
13
+ /** Bytes of the package's sibling payload files, for the bulk-data flag that says the payload
14
+ * was written beside the `.uasset` rather than inside it. The parser stays a pure format layer:
15
+ * it never opens a file, so a caller that has these bytes hands them over. */
16
+ interface IUAssetBulkDataFiles {
17
+ uexp?: ArrayBuffer | ArrayBufferView;
18
+ ubulk?: ArrayBuffer | ArrayBufferView;
19
+ uptnl?: ArrayBuffer | ArrayBufferView;
20
+ }
21
+ interface IUAssetParseOptions {
22
+ /** Required for UE5 `FCompressedBuffer` payloads compressed with Oodle (method 3). */
23
+ oodle?: OodleCodec;
24
+ /** Required for `FCompressedBuffer` payloads compressed with LZ4 (method 4). */
25
+ lz4?: Lz4Codec;
26
+ /** Required for editor bulk data stored with `BULKDATA_SerializeCompressedZLIB`. */
27
+ zlib?: ZlibCodec;
28
+ /** Sibling `.uexp`/`.ubulk`/`.uptnl` bytes, when a bulk payload lives outside the package. */
29
+ bulkDataFiles?: IUAssetBulkDataFiles;
30
+ /** Convert Unreal's Z-up left-handed coordinates to three.js Y-up. Default true. */
31
+ convertCoordinates?: boolean;
32
+ /** Swap each triangle's second and third indices after conversion. Default true. */
33
+ flipWinding?: boolean;
34
+ /** Flip texture V so three.js sampling matches Unreal's UV convention. Default false. */
35
+ flipV?: boolean;
36
+ }
37
+ /** One material draw range, already in output index order. */
38
+ interface IUAssetSection {
39
+ materialIndex: number;
40
+ /** The section (polygon-group / face-material) index the package itself records. */
41
+ sectionIndex: number;
42
+ /** The package's material-slot name when the layout carries one; otherwise the section index
43
+ * as a string. Slot-name extraction from FRawMesh packages needs the name map and tagged
44
+ * properties, which this loader does not walk. */
45
+ materialName: string;
46
+ start: number;
47
+ count: number;
48
+ }
49
+ interface IUAssetBounds {
50
+ min: readonly [number, number, number];
51
+ max: readonly [number, number, number];
52
+ }
53
+ interface IUAssetSourceStats {
54
+ vertices: number;
55
+ vertexInstances: number;
56
+ triangles: number;
57
+ }
58
+ /** Which serialized source-model layout the geometry came from. `mesh-description` is the UE5
59
+ * form, whose element containers carry their own names; `mesh-description-ue4` is the UE4.2x
60
+ * form, whose containers are a fixed sequence and whose triangles trail the attribute sets. */
61
+ type UAssetMeshLayout = "mesh-description" | "mesh-description-ue4" | "raw-mesh";
62
+ /** Where an `FByteBulkData` payload was stored, per its flags. */
63
+ type UAssetBulkDataStorage = "inline" | "end-of-file" | "separate-file";
64
+ /** Which file the payload bytes were read from. */
65
+ type UAssetBulkDataFile = "uasset" | "uexp" | "ubulk" | "uptnl";
66
+ interface IUAssetBulkDataInfo {
67
+ /** Byte offset of the `FByteBulkData` header inside the `.uasset`. */
68
+ headerOffset: number;
69
+ /** `EBulkDataFlags` exactly as serialized. */
70
+ flags: number;
71
+ storage: UAssetBulkDataStorage;
72
+ file: UAssetBulkDataFile;
73
+ compression: "none" | "zlib";
74
+ elementCount: number;
75
+ /** Bytes the payload occupies on disk, compressed size included. */
76
+ sizeOnDisk: number;
77
+ /** The header's own offset field, before the `BulkDataStartOffset` fix-up. */
78
+ offsetInFile: number;
79
+ /** Where the payload actually begins in the file it lives in. */
80
+ payloadOffset: number;
81
+ }
82
+ interface IUAssetMetadata {
83
+ assetClass: "StaticMesh";
84
+ engineVersion: string;
85
+ objectPath: string;
86
+ packageByteLength: number;
87
+ }
88
+ interface IUAssetCompressedBufferInfo {
89
+ offset: number;
90
+ method: number;
91
+ compressor: number;
92
+ compressionLevel: number;
93
+ rawSize: number;
94
+ compressedSize: number;
95
+ blockCount: number;
96
+ }
97
+ interface IUAssetUnrealInfo {
98
+ /** Unreal's legacy package tag, `0x9e2a83c1`. */
99
+ packageTag: number;
100
+ /** `FPackageFileSummary.LegacyFileVersion`, negative (−7 for UE4-era, −8 and below for UE5). */
101
+ legacyFileVersion: number;
102
+ fileVersionUE4?: number;
103
+ fileVersionUE5?: number;
104
+ licenseeVersion?: number;
105
+ /** `FEditorObjectVersion` from the summary's custom-version list, when present. */
106
+ editorObjectVersion?: number;
107
+ layout: UAssetMeshLayout;
108
+ /** Where the decoded source-model payload was found. `frame` names the coordinate system the
109
+ * offset is relative to: the package file itself, the buffer left after decompression, or the
110
+ * resolved bulk-data payload — whose own location is in `bulkData`. */
111
+ payload: {
112
+ frame: "package" | "decompressed" | "bulk-data";
113
+ offset: number;
114
+ byteLength: number;
115
+ };
116
+ compressedBuffer?: IUAssetCompressedBufferInfo;
117
+ bulkData?: IUAssetBulkDataInfo;
118
+ }
119
+ interface IDecodedUAssetStaticMesh {
120
+ /** Per-vertex-instance positions, already converted per the parse options. */
121
+ positions: Float32Array;
122
+ /** Per-vertex-instance normals, present when the package carries them. */
123
+ normals: Float32Array | undefined;
124
+ /** Per-vertex-instance UV set 0. */
125
+ uvs: Float32Array;
126
+ indices: Uint16Array | Uint32Array;
127
+ sections: readonly IUAssetSection[];
128
+ bounds: IUAssetBounds;
129
+ metadata: IUAssetMetadata;
130
+ unreal: IUAssetUnrealInfo;
131
+ sourceStats: IUAssetSourceStats;
132
+ }
133
+
134
+ /** Parses a raw Unreal editor `.uasset` into validated, plain static-mesh data — no interchange
135
+ * conversion, no Three.js objects, no fallback geometry invented for unsupported layouts. */
136
+ declare function parseUAssetStaticMesh(input: ArrayBuffer | ArrayBufferView, options?: IUAssetParseOptions): IDecodedUAssetStaticMesh;
137
+
138
+ /** Unreal's legacy package tag: every editor `.uasset`/`.umap` starts with it, little-endian. */
139
+ declare const PACKAGE_FILE_TAG = 2653586369;
140
+ interface IPackageSummary {
141
+ packageTag: number;
142
+ legacyFileVersion: number;
143
+ legacyUE3Version: number;
144
+ fileVersionUE4?: number;
145
+ fileVersionUE5?: number;
146
+ licenseeVersion?: number;
147
+ editorObjectVersion?: number;
148
+ }
149
+ /** Reads the fixed prefix of `FPackageFileSummary` — the legacy tag and the engine version
150
+ * fields — and locates the `FEditorObjectVersion` custom-version entry. The rest of the summary
151
+ * (name map, export map, dependency graph) belongs to a full package index; this package's
152
+ * payload readers locate data by self-validating signature scans instead. */
153
+ declare function readPackageSummary(bytes: Uint8Array): IPackageSummary;
154
+ /**
155
+ * The parts of `FPackageFileSummary` a bulk-data reader needs: how long the header is, where the
156
+ * export data that carries `FByteBulkData` headers begins, and where the bulk payload region
157
+ * starts. `readPackageSummary` deliberately reads only the fixed prefix; this walk reads the
158
+ * whole summary, which is the only way to learn `BulkDataStartOffset`.
159
+ */
160
+ interface IPackageLayout extends IPackageSummary {
161
+ /** End of the package header — the first byte of export data. */
162
+ totalHeaderSize: number;
163
+ /** First byte of the bulk-data region, the anchor every end-of-file payload offset is fixed
164
+ * up against. */
165
+ bulkDataStartOffset: number;
166
+ nameCount: number;
167
+ nameOffset: number;
168
+ exportCount: number;
169
+ exportOffset: number;
170
+ }
171
+ /**
172
+ * Walks the whole `FPackageFileSummary` for the offsets bulk data is addressed against, and
173
+ * returns `undefined` when the walk cannot be trusted — an unmodelled legacy version, or a
174
+ * summary that does not end on its own name table. Declining is the point: an offset guessed
175
+ * from a mis-walked summary would send the bulk reader somewhere plausible and wrong.
176
+ */
177
+ declare function readPackageLayout(bytes: Uint8Array): IPackageLayout | undefined;
178
+
179
+ /**
180
+ * Unreal's `EBulkDataFlags`. Only the bits this reader acts on are named; the rest are carried
181
+ * through untouched on `IUAssetBulkDataInfo.flags`.
182
+ */
183
+ declare const BULK_DATA_FLAG: Readonly<{
184
+ PAYLOAD_AT_END_OF_FILE: 1;
185
+ SERIALIZE_COMPRESSED_ZLIB: 2;
186
+ FORCE_INLINE_PAYLOAD: 64;
187
+ PAYLOAD_IN_SEPARATE_FILE: 256;
188
+ OPTIONAL_PAYLOAD: 2048;
189
+ MEMORY_MAPPED_PAYLOAD: 4096;
190
+ SIZE_64_BIT: 8192;
191
+ NO_OFFSET_FIX_UP: 65536;
192
+ }>;
193
+ interface IBulkDataHeader extends IUAssetBulkDataInfo {
194
+ /** First byte after the header — the payload itself for an inline payload. */
195
+ headerEnd: number;
196
+ }
197
+ /**
198
+ * Parses one `FByteBulkData` header at `offset`: the flags, the element count and size on disk
199
+ * (widened to 64 bits when `BULKDATA_Size64Bit` is set), and the offset the payload sits at.
200
+ * Throws `UAssetError` "INVALID_BULK_DATA" when the fields cannot describe a payload.
201
+ */
202
+ declare function parseBulkDataHeader(bytes: Uint8Array, offset: number, layout: IPackageLayout): IBulkDataHeader;
203
+ /**
204
+ * Scans the package's export-data region for `FByteBulkData` headers. Export data is where
205
+ * Unreal serializes them, so the scan is bounded by the summary's own offsets rather than
206
+ * sweeping the whole file. Every candidate is decided by `parseBulkDataHeader` plus a range
207
+ * check against the file the payload claims to live in; anything else is dropped silently,
208
+ * because a rejected candidate is a byte pattern, not a defect.
209
+ */
210
+ declare function findBulkDataHeaders(bytes: Uint8Array, layout: IPackageLayout, files?: IUAssetBulkDataFiles): IBulkDataHeader[];
211
+ /**
212
+ * Inverts `FArchive::SerializeCompressed`: the package tag and the loading chunk size, the
213
+ * compressed and raw totals, one `FCompressedChunkInfo` per chunk, then the chunk data. The
214
+ * zlib codec is the caller's; a payload whose codec was not supplied throws `MISSING_CODEC`
215
+ * rather than returning a partial buffer.
216
+ */
217
+ declare function decompressBulkData(container: Uint8Array, codecs: {
218
+ zlib?: ZlibCodec;
219
+ }): Uint8Array;
220
+ /**
221
+ * Resolves one header's payload bytes: reads them out of the `.uasset`, the sibling file the
222
+ * flags name, or the compressed container, and hands back plain bytes. Throws
223
+ * `MISSING_BULK_DATA_FILE` when the payload lives beside the package and the caller did not
224
+ * supply that file, and `MISSING_CODEC` when it is compressed with a codec that was not.
225
+ */
226
+ declare function resolveBulkDataPayload(bytes: Uint8Array, header: IBulkDataHeader, options?: {
227
+ files?: IUAssetBulkDataFiles;
228
+ zlib?: ZlibCodec;
229
+ }): Uint8Array;
230
+
231
+ interface IUe4AttributeEntry {
232
+ name: string;
233
+ type: string;
234
+ components: number;
235
+ numElements: number;
236
+ values: Float32Array | Int32Array | Uint8Array | string[];
237
+ }
238
+ type Ue4AttributeSet = Map<string, IUe4AttributeEntry>;
239
+
240
+ interface IUe4MeshDescription {
241
+ byteLength: number;
242
+ /** Allocated vertices; `vertexSlots` is how many ids the container spans. */
243
+ vertexCount: number;
244
+ vertexSlots: number;
245
+ validVertexIds: readonly number[];
246
+ /** Vertex-instance ids the file allocated, ascending. */
247
+ validVertexInstanceIds: readonly number[];
248
+ /** Vertex index of each vertex-instance **slot**; unallocated slots hold −1. */
249
+ vertexInstanceVertices: Int32Array;
250
+ /** Polygon-group index of each polygon **slot**; unallocated slots hold −1. */
251
+ polygonGroups: Int32Array;
252
+ polygonGroupCount: number;
253
+ /** Triangle ids the file allocated, ascending. */
254
+ validTriangleIds: readonly number[];
255
+ /** Three vertex-instance indices per triangle **slot**. */
256
+ triangleVertexInstances: Int32Array;
257
+ /** Polygon index of each triangle **slot**. */
258
+ trianglePolygons: Int32Array;
259
+ vertexAttributes: Ue4AttributeSet;
260
+ vertexInstanceAttributes: Ue4AttributeSet;
261
+ polygonGroupAttributes: Ue4AttributeSet;
262
+ }
263
+ /**
264
+ * Parses one UE4.2x `FMeshDescription` payload, validating that the walk consumes it exactly and
265
+ * that every index lands inside the container it references. Throws `UAssetError`
266
+ * "INVALID_MESH_DESCRIPTION" when anything disagrees; nothing is inferred.
267
+ */
268
+ declare function parseMeshDescriptionUe4(input: Uint8Array, offset?: number): IUe4MeshDescription;
269
+ /** The serialized signature of a UE4 MeshDescription payload: a vertex allocation bit array
270
+ * followed by a second one whose own bit count starts exactly where the first one ends. Strong
271
+ * enough to gate a full parse attempt, not strong enough to trust alone — the parse decides. */
272
+ declare function looksLikeMeshDescriptionUe4(bytes: Uint8Array, offset?: number): boolean;
273
+
274
+ /** `FCompressedBuffer`'s big-endian magic. */
275
+ declare const COMPRESSED_BUFFER_MAGIC = 3077923682;
276
+ declare const COMPRESSION_METHOD: Readonly<{
277
+ readonly NONE: 0;
278
+ readonly OODLE: 3;
279
+ readonly LZ4: 4;
280
+ }>;
281
+ interface ICompressedBuffer {
282
+ offset: number;
283
+ crc32: number;
284
+ method: number;
285
+ compressor: number;
286
+ compressionLevel: number;
287
+ blockSizeExponent: number;
288
+ blockCount: number;
289
+ totalRawSize: number;
290
+ totalCompressedSize: number;
291
+ /** BLAKE3 hash the writer recorded; validated by the caller, not here. */
292
+ rawHash: Uint8Array;
293
+ blockSizes: readonly number[];
294
+ payloadBytes: Uint8Array;
295
+ }
296
+ interface IUAssetCodecs {
297
+ oodle?: OodleCodec;
298
+ lz4?: Lz4Codec;
299
+ }
300
+ /** Scans for candidate `FCompressedBuffer` magics. Candidates are speculative — the four magic
301
+ * bytes can appear in a payload — so every candidate goes through the full validating parse. */
302
+ declare function findCompressedBufferOffsets(bytes: Uint8Array): number[];
303
+ declare function parseCompressedBuffer(bytes: Uint8Array, offset?: number): ICompressedBuffer;
304
+ /** Decompresses a parsed `FCompressedBuffer` block-by-block. Uncompressed payloads are copied
305
+ * natively; Oodle and LZ4 payloads require the caller's codec. */
306
+ declare function decompressCompressedBuffer(buffer: ICompressedBuffer, codecs: IUAssetCodecs): Uint8Array;
307
+
308
+ interface IBitArray {
309
+ numBits: number;
310
+ validIds: number[];
311
+ }
312
+ interface IAttributeChannel {
313
+ extent: number;
314
+ values: Float32Array | Int32Array | Uint8Array | string[];
315
+ serializedCount: number;
316
+ }
317
+ interface IAttributeEntry {
318
+ name: string;
319
+ type: string;
320
+ components: number;
321
+ extent: number;
322
+ numElements: number;
323
+ channels: readonly IAttributeChannel[];
324
+ }
325
+ interface IElementContainer {
326
+ allocation: IBitArray;
327
+ attributeSet: {
328
+ numElements: number;
329
+ attributes: Map<string, IAttributeEntry>;
330
+ };
331
+ }
332
+ interface IMeshDescription {
333
+ byteLength: number;
334
+ trailingBytes: number;
335
+ elements: Map<string, {
336
+ name: string;
337
+ channels: readonly IElementContainer[];
338
+ }>;
339
+ }
340
+ /** Parses a serialized `FMeshDescription` — element containers, allocation bit arrays, and
341
+ * attribute sets — into typed arrays, validating every count against its neighbors. */
342
+ declare function parseMeshDescription(input: Uint8Array, offset?: number): IMeshDescription;
343
+ /** The serialized MeshDescription signature: an element-type count followed by the FString
344
+ * "Vertices". Strong enough to gate a full parse attempt, not strong enough to trust alone. */
345
+ declare function looksLikeMeshDescription(bytes: Uint8Array, offset?: number): boolean;
346
+ declare function findMeshDescriptionOffsets(bytes: Uint8Array): number[];
347
+
348
+ interface IRawMesh {
349
+ version: number;
350
+ /** Per-face material (section) index. */
351
+ faceMaterialIndices: Int32Array;
352
+ /** Per-vertex positions, three floats each, in Unreal coordinates. */
353
+ vertexPositions: Float32Array;
354
+ vertexCount: number;
355
+ /** Per-wedge vertex index — the instance-to-vertex table. */
356
+ wedgeIndices: Uint32Array;
357
+ wedgeNormals: Float32Array | undefined;
358
+ /** One UV set per non-empty channel, each with `wedgeCount` uv pairs. */
359
+ wedgeUvs: Float32Array[];
360
+ }
361
+ interface IRawMeshBlob {
362
+ offset: number;
363
+ byteLength: number;
364
+ mesh: IRawMesh;
365
+ }
366
+ /** Parses an FRawMesh blob at `offset`, per `operator<<(FArchive&, FRawMesh&)`: a version pair
367
+ * then eighteen plain TArrays in a fixed order. A correct parse consumes the blob to its last
368
+ * byte and every count must agree with the wedge/face totals, which is what makes a candidate
369
+ * offset trustworthy rather than a lucky match. Throws `UAssetError` "INVALID_RAW_MESH" when
370
+ * anything disagrees. */
371
+ declare function parseRawMesh(bytes: Uint8Array, offset?: number): IRawMeshBlob;
372
+ /** Scans for FRawMesh blob candidates. Each candidate is decided by the full validating parse,
373
+ * which must consume the blob exactly and pass every cross-count check, so false positives cost
374
+ * only a rejected attempt. Offsets come back in file order — the first is LOD0. */
375
+ declare function findRawMeshBlobs(bytes: Uint8Array): IRawMeshBlob[];
376
+
377
+ interface IThreeAdapterOptions {
378
+ /** Override the fallback material. Every material is the game's decision; the fallback is one
379
+ * plain `MeshStandardMaterial` per section (or a single one for single-section meshes). */
380
+ materialFactory?: (decoded: IDecodedUAssetStaticMesh) => Material | Material[];
381
+ }
382
+ /** Converts one decoded `.uasset` static mesh into a `THREE.BufferGeometry` — positions, UVs,
383
+ * normals, an index buffer sized to the vertex count, and one draw group per material section.
384
+ * Coordinate conversion and winding repair already happened at parse time per the parse
385
+ * options; this builder makes no further geometry decisions. */
386
+ declare function createThreeGeometry(decoded: IDecodedUAssetStaticMesh): BufferGeometry;
387
+ /** Converts a decoded `.uasset` static mesh into a renderable `THREE.Mesh` with provenance in
388
+ * `userData.unreal`. Materials come from `materialFactory`; without one, a plain fallback is
389
+ * used so nothing renders invisible by accident. */
390
+ declare function createThreeObject(decoded: IDecodedUAssetStaticMesh, options?: IThreeAdapterOptions): Object3D;
391
+
392
+ interface IUAssetLoaderOptions {
393
+ parse?: IUAssetParseOptions;
394
+ three?: IThreeAdapterOptions;
395
+ }
396
+ /** Loads a raw Unreal editor `.uasset` static mesh under the standard three.js loader protocol:
397
+ * `load(url)` for the browser, `parse(data)` for bytes you already hold. Parse and adapter
398
+ * options pass straight through; the parse options carry the injected codecs. */
399
+ declare class UAssetLoader extends Loader {
400
+ readonly options: IUAssetLoaderOptions;
401
+ constructor(manager?: LoadingManager, options?: IUAssetLoaderOptions);
402
+ parse(data: ArrayBuffer | ArrayBufferView): three.Object3D<three.Object3DEventMap>;
403
+ load(url: string, onLoad: (object: ReturnType<UAssetLoader["parse"]>) => void, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): this;
404
+ }
405
+
406
+ /**
407
+ * The stable error codes a `UAssetError` can carry.
408
+ * @situation switch on a raw .uasset failure without parsing error strings
409
+ * @constraint codes are part of the public surface; new failure modes add codes rather than reusing them
410
+ * @example if (error.code === "MISSING_CODEC") hintAtOodleSetup();
411
+ */
412
+ type UAssetErrorCode = "INVALID_PACKAGE_TAG" | "TRUNCATED_PACKAGE" | "INVALID_PACKAGE_SUMMARY" | "INVALID_COMPRESSED_BUFFER" | "UNSUPPORTED_COMPRESSION_METHOD" | "MISSING_CODEC" | "CODEC_SIZE_MISMATCH" | "INCOMPLETE_DECOMPRESSION" | "INVALID_MESH_DESCRIPTION" | "INVALID_BULK_DATA" | "MISSING_BULK_DATA_FILE" | "INVALID_RAW_MESH" | "MISSING_MESH_ATTRIBUTE" | "INVALID_MESH_REFERENCE" | "UNSUPPORTED_STATIC_MESH_LAYOUT" | "INVALID_GEOMETRY_OPTIONS";
413
+ /**
414
+ * The error thrown for every malformed, truncated, or unsupported `.uasset` input, carrying a
415
+ * stable `code` and structured details instead of invented fallback geometry.
416
+ * @situation tell why a raw .uasset failed to load, by code, before any geometry is shown
417
+ * @situation branch on an unsupported Unreal layout instead of shipping broken geometry
418
+ * @constraint every parse, decompression, and geometry failure surfaces as this error; the loader never invents fallback geometry
419
+ * @requires npm i @threenative/raw-unreal
420
+ * @example catch (error) { if (error instanceof UAssetError) log(error.code, error.details); }
421
+ */
422
+ declare class UAssetError extends Error {
423
+ readonly code: UAssetErrorCode;
424
+ readonly details: Readonly<Record<string, unknown>>;
425
+ constructor(code: UAssetErrorCode, message: string, details?: Readonly<Record<string, unknown>>);
426
+ }
427
+
428
+ export { BULK_DATA_FLAG, COMPRESSED_BUFFER_MAGIC, COMPRESSION_METHOD, type IBulkDataHeader, type IDecodedUAssetStaticMesh, type IPackageLayout, type IUAssetBounds, type IUAssetBulkDataFiles, type IUAssetBulkDataInfo, type IUAssetCompressedBufferInfo, type IUAssetLoaderOptions, type IUAssetMetadata, type IUAssetParseOptions, type IUAssetSection, type IUAssetSourceStats, type IUAssetUnrealInfo, type IUe4MeshDescription, type Lz4Codec, type OodleCodec, PACKAGE_FILE_TAG, type UAssetBulkDataFile, type UAssetBulkDataStorage, UAssetError, type UAssetErrorCode, UAssetLoader, type UAssetMeshLayout, type ZlibCodec, createThreeGeometry, createThreeObject, decompressBulkData, decompressCompressedBuffer, findBulkDataHeaders, findCompressedBufferOffsets, findMeshDescriptionOffsets, findRawMeshBlobs, looksLikeMeshDescription, looksLikeMeshDescriptionUe4, parseBulkDataHeader, parseCompressedBuffer, parseMeshDescription, parseMeshDescriptionUe4, parseRawMesh, parseUAssetStaticMesh, readPackageLayout, readPackageSummary, resolveBulkDataPayload };