@babylonjs/loaders 9.11.0 → 9.12.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/FBX/fbxFileLoader.d.ts +194 -0
- package/FBX/fbxFileLoader.js +2440 -0
- package/FBX/fbxFileLoader.js.map +1 -0
- package/FBX/fbxFileLoader.metadata.d.ts +11 -0
- package/FBX/fbxFileLoader.metadata.js +11 -0
- package/FBX/fbxFileLoader.metadata.js.map +1 -0
- package/FBX/index.d.ts +3 -0
- package/FBX/index.js +3 -0
- package/FBX/index.js.map +1 -0
- package/FBX/interpreter/animation.d.ts +122 -0
- package/FBX/interpreter/animation.js +648 -0
- package/FBX/interpreter/animation.js.map +1 -0
- package/FBX/interpreter/blendShapes.d.ts +44 -0
- package/FBX/interpreter/blendShapes.js +192 -0
- package/FBX/interpreter/blendShapes.js.map +1 -0
- package/FBX/interpreter/connections.d.ts +95 -0
- package/FBX/interpreter/connections.js +233 -0
- package/FBX/interpreter/connections.js.map +1 -0
- package/FBX/interpreter/fbxInterpreter.d.ts +149 -0
- package/FBX/interpreter/fbxInterpreter.js +496 -0
- package/FBX/interpreter/fbxInterpreter.js.map +1 -0
- package/FBX/interpreter/geometry.d.ts +55 -0
- package/FBX/interpreter/geometry.js +573 -0
- package/FBX/interpreter/geometry.js.map +1 -0
- package/FBX/interpreter/materials.d.ts +50 -0
- package/FBX/interpreter/materials.js +144 -0
- package/FBX/interpreter/materials.js.map +1 -0
- package/FBX/interpreter/propertyTemplates.d.ts +22 -0
- package/FBX/interpreter/propertyTemplates.js +125 -0
- package/FBX/interpreter/propertyTemplates.js.map +1 -0
- package/FBX/interpreter/rig.d.ts +20 -0
- package/FBX/interpreter/rig.js +259 -0
- package/FBX/interpreter/rig.js.map +1 -0
- package/FBX/interpreter/sceneDiagnostics.d.ts +14 -0
- package/FBX/interpreter/sceneDiagnostics.js +55 -0
- package/FBX/interpreter/sceneDiagnostics.js.map +1 -0
- package/FBX/interpreter/skeleton.d.ts +93 -0
- package/FBX/interpreter/skeleton.js +515 -0
- package/FBX/interpreter/skeleton.js.map +1 -0
- package/FBX/interpreter/transform.d.ts +21 -0
- package/FBX/interpreter/transform.js +92 -0
- package/FBX/interpreter/transform.js.map +1 -0
- package/FBX/parsers/fbxAsciiParser.d.ts +5 -0
- package/FBX/parsers/fbxAsciiParser.js +330 -0
- package/FBX/parsers/fbxAsciiParser.js.map +1 -0
- package/FBX/parsers/fbxBinaryParser.d.ts +6 -0
- package/FBX/parsers/fbxBinaryParser.js +255 -0
- package/FBX/parsers/fbxBinaryParser.js.map +1 -0
- package/FBX/parsers/zlibInflate.d.ts +7 -0
- package/FBX/parsers/zlibInflate.js +350 -0
- package/FBX/parsers/zlibInflate.js.map +1 -0
- package/FBX/types/fbxTypes.d.ts +54 -0
- package/FBX/types/fbxTypes.js +66 -0
- package/FBX/types/fbxTypes.js.map +1 -0
- package/SPLAT/gaussianSplattingStream.d.ts +341 -0
- package/SPLAT/gaussianSplattingStream.js +976 -0
- package/SPLAT/gaussianSplattingStream.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBuffer.d.ts +51 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js +159 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.d.ts +25 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js +255 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js.map +1 -0
- package/SPLAT/index.d.ts +1 -0
- package/SPLAT/index.js +1 -0
- package/SPLAT/index.js.map +1 -1
- package/SPLAT/sog.js +18 -16
- package/SPLAT/sog.js.map +1 -1
- package/SPLAT/splatFileLoader.d.ts +8 -0
- package/SPLAT/splatFileLoader.js +49 -0
- package/SPLAT/splatFileLoader.js.map +1 -1
- package/dynamic.js +9 -0
- package/dynamic.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { inflateZlib } from "./zlibInflate.js";
|
|
2
|
+
const FBX_MAGIC = "Kaydara FBX Binary \0";
|
|
3
|
+
const HEADER_SIZE = 27; // 21 magic + 2 padding + 4 version uint32
|
|
4
|
+
/**
|
|
5
|
+
* Parse a binary FBX file into an FBXDocument.
|
|
6
|
+
* Supports FBX versions 7.0–7.7 (v7.5+ uses 64-bit node headers).
|
|
7
|
+
*/
|
|
8
|
+
export function parseBinaryFBX(buffer) {
|
|
9
|
+
const view = new DataView(buffer);
|
|
10
|
+
const bytes = new Uint8Array(buffer);
|
|
11
|
+
// Validate magic
|
|
12
|
+
const magic = decodeASCII(bytes, 0, 21);
|
|
13
|
+
if (magic !== FBX_MAGIC) {
|
|
14
|
+
throw new Error("Not a valid binary FBX file");
|
|
15
|
+
}
|
|
16
|
+
if (buffer.byteLength < HEADER_SIZE) {
|
|
17
|
+
throw new Error("Truncated binary FBX header");
|
|
18
|
+
}
|
|
19
|
+
const version = view.getUint32(23, true);
|
|
20
|
+
// v7.5+ uses 64-bit offsets in node records
|
|
21
|
+
const is64Bit = version >= 7500;
|
|
22
|
+
const nodes = [];
|
|
23
|
+
let offset = HEADER_SIZE;
|
|
24
|
+
while (offset < buffer.byteLength) {
|
|
25
|
+
const result = parseNode(view, bytes, offset, is64Bit, buffer.byteLength);
|
|
26
|
+
if (result === null) {
|
|
27
|
+
break; // null sentinel node
|
|
28
|
+
}
|
|
29
|
+
nodes.push(result.node);
|
|
30
|
+
offset = result.endOffset;
|
|
31
|
+
}
|
|
32
|
+
return { version, nodes };
|
|
33
|
+
}
|
|
34
|
+
function parseNode(view, bytes, offset, is64Bit, limit) {
|
|
35
|
+
// Read node header
|
|
36
|
+
let endOffset;
|
|
37
|
+
let numProperties;
|
|
38
|
+
let propertyListLen;
|
|
39
|
+
let headerSize;
|
|
40
|
+
if (is64Bit) {
|
|
41
|
+
ensureRange(bytes, offset, 25, limit, "FBX node header");
|
|
42
|
+
endOffset = readUint64AsNumber(view, offset);
|
|
43
|
+
numProperties = readUint64AsNumber(view, offset + 8);
|
|
44
|
+
propertyListLen = readUint64AsNumber(view, offset + 16);
|
|
45
|
+
headerSize = 25; // 8+8+8+1 (nameLen byte)
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
ensureRange(bytes, offset, 13, limit, "FBX node header");
|
|
49
|
+
endOffset = view.getUint32(offset, true);
|
|
50
|
+
numProperties = view.getUint32(offset + 4, true);
|
|
51
|
+
propertyListLen = view.getUint32(offset + 8, true);
|
|
52
|
+
headerSize = 13; // 4+4+4+1 (nameLen byte)
|
|
53
|
+
}
|
|
54
|
+
// Null sentinel: all header fields are zero
|
|
55
|
+
if (endOffset === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (endOffset <= offset || endOffset > limit) {
|
|
59
|
+
throw new Error(`Invalid FBX node end offset ${endOffset} at offset ${offset}`);
|
|
60
|
+
}
|
|
61
|
+
const nameLen = bytes[offset + headerSize - 1];
|
|
62
|
+
ensureRange(bytes, offset + headerSize, nameLen, endOffset, "FBX node name");
|
|
63
|
+
const name = decodeASCII(bytes, offset + headerSize, nameLen);
|
|
64
|
+
let cursor = offset + headerSize + nameLen;
|
|
65
|
+
const propertiesStart = cursor;
|
|
66
|
+
const propertiesEnd = propertiesStart + propertyListLen;
|
|
67
|
+
if (propertiesEnd > endOffset) {
|
|
68
|
+
throw new Error(`Invalid FBX property list length for node '${name}' at offset ${offset}`);
|
|
69
|
+
}
|
|
70
|
+
// Parse properties
|
|
71
|
+
const properties = [];
|
|
72
|
+
for (let i = 0; i < numProperties; i++) {
|
|
73
|
+
const result = parseProperty(view, bytes, cursor, propertiesEnd);
|
|
74
|
+
properties.push(result.property);
|
|
75
|
+
cursor = result.nextOffset;
|
|
76
|
+
}
|
|
77
|
+
if (cursor !== propertiesEnd) {
|
|
78
|
+
throw new Error(`Invalid FBX property list length for node '${name}' at offset ${offset}`);
|
|
79
|
+
}
|
|
80
|
+
// Parse nested child nodes (between end of properties and endOffset)
|
|
81
|
+
const children = [];
|
|
82
|
+
if (cursor < endOffset) {
|
|
83
|
+
while (cursor < endOffset) {
|
|
84
|
+
const child = parseNode(view, bytes, cursor, is64Bit, endOffset);
|
|
85
|
+
if (child === null) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (child.endOffset <= cursor || child.endOffset > endOffset) {
|
|
89
|
+
throw new Error(`Invalid FBX child node end offset ${child.endOffset} at offset ${cursor}`);
|
|
90
|
+
}
|
|
91
|
+
children.push(child.node);
|
|
92
|
+
cursor = child.endOffset;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
node: { name, properties, children },
|
|
97
|
+
endOffset,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseProperty(view, bytes, offset, limit) {
|
|
101
|
+
ensureRange(bytes, offset, 1, limit, "FBX property type");
|
|
102
|
+
const typeCode = String.fromCharCode(bytes[offset]);
|
|
103
|
+
offset += 1;
|
|
104
|
+
switch (typeCode) {
|
|
105
|
+
case "C": {
|
|
106
|
+
// Boolean (1 byte)
|
|
107
|
+
ensureRange(bytes, offset, 1, limit, "FBX boolean property");
|
|
108
|
+
const value = bytes[offset] !== 0;
|
|
109
|
+
return { property: { type: "boolean", value }, nextOffset: offset + 1 };
|
|
110
|
+
}
|
|
111
|
+
case "Y": {
|
|
112
|
+
// Int16
|
|
113
|
+
ensureRange(bytes, offset, 2, limit, "FBX int16 property");
|
|
114
|
+
const value = view.getInt16(offset, true);
|
|
115
|
+
return { property: { type: "int16", value }, nextOffset: offset + 2 };
|
|
116
|
+
}
|
|
117
|
+
case "I": {
|
|
118
|
+
// Int32
|
|
119
|
+
ensureRange(bytes, offset, 4, limit, "FBX int32 property");
|
|
120
|
+
const value = view.getInt32(offset, true);
|
|
121
|
+
return { property: { type: "int32", value }, nextOffset: offset + 4 };
|
|
122
|
+
}
|
|
123
|
+
case "F": {
|
|
124
|
+
// Float32
|
|
125
|
+
ensureRange(bytes, offset, 4, limit, "FBX float32 property");
|
|
126
|
+
const value = view.getFloat32(offset, true);
|
|
127
|
+
return { property: { type: "float32", value }, nextOffset: offset + 4 };
|
|
128
|
+
}
|
|
129
|
+
case "D": {
|
|
130
|
+
// Float64
|
|
131
|
+
ensureRange(bytes, offset, 8, limit, "FBX float64 property");
|
|
132
|
+
const value = view.getFloat64(offset, true);
|
|
133
|
+
return { property: { type: "float64", value }, nextOffset: offset + 8 };
|
|
134
|
+
}
|
|
135
|
+
case "L": {
|
|
136
|
+
// Int64
|
|
137
|
+
ensureRange(bytes, offset, 8, limit, "FBX int64 property");
|
|
138
|
+
const value = readInt64AsNumber(view, offset);
|
|
139
|
+
return { property: { type: "int64", value }, nextOffset: offset + 8 };
|
|
140
|
+
}
|
|
141
|
+
case "S": {
|
|
142
|
+
// String (uint32 length + data)
|
|
143
|
+
ensureRange(bytes, offset, 4, limit, "FBX string property length");
|
|
144
|
+
const len = view.getUint32(offset, true);
|
|
145
|
+
ensureRange(bytes, offset + 4, len, limit, "FBX string property data");
|
|
146
|
+
const value = decodeUTF8(bytes, offset + 4, len);
|
|
147
|
+
return { property: { type: "string", value }, nextOffset: offset + 4 + len };
|
|
148
|
+
}
|
|
149
|
+
case "R": {
|
|
150
|
+
// Raw binary data (uint32 length + data)
|
|
151
|
+
ensureRange(bytes, offset, 4, limit, "FBX raw property length");
|
|
152
|
+
const len = view.getUint32(offset, true);
|
|
153
|
+
ensureRange(bytes, offset + 4, len, limit, "FBX raw property data");
|
|
154
|
+
const value = bytes.slice(offset + 4, offset + 4 + len);
|
|
155
|
+
return { property: { type: "raw", value }, nextOffset: offset + 4 + len };
|
|
156
|
+
}
|
|
157
|
+
// Array types
|
|
158
|
+
case "f":
|
|
159
|
+
return parseArrayProperty(view, bytes, offset, "float32[]", 4, limit);
|
|
160
|
+
case "d":
|
|
161
|
+
return parseArrayProperty(view, bytes, offset, "float64[]", 8, limit);
|
|
162
|
+
case "i":
|
|
163
|
+
return parseArrayProperty(view, bytes, offset, "int32[]", 4, limit);
|
|
164
|
+
case "l":
|
|
165
|
+
return parseArrayProperty(view, bytes, offset, "int64[]", 8, limit);
|
|
166
|
+
case "b":
|
|
167
|
+
return parseArrayProperty(view, bytes, offset, "boolean[]", 1, limit);
|
|
168
|
+
default:
|
|
169
|
+
throw new Error(`Unknown FBX property type: '${typeCode}' at offset ${offset - 1}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function parseArrayProperty(view, bytes, offset, type, elementSize, limit) {
|
|
173
|
+
ensureRange(bytes, offset, 12, limit, `FBX array property header for ${type}`);
|
|
174
|
+
const arrayLength = view.getUint32(offset, true);
|
|
175
|
+
const encoding = view.getUint32(offset + 4, true); // 0=raw, 1=zlib
|
|
176
|
+
const compressedLength = view.getUint32(offset + 8, true);
|
|
177
|
+
offset += 12;
|
|
178
|
+
const expectedByteLength = arrayLength * elementSize;
|
|
179
|
+
ensureRange(bytes, offset, compressedLength, limit, `FBX array property data for ${type}`);
|
|
180
|
+
let arrayData;
|
|
181
|
+
if (encoding === 1) {
|
|
182
|
+
// zlib compressed
|
|
183
|
+
const compressed = bytes.subarray(offset, offset + compressedLength);
|
|
184
|
+
arrayData = inflateZlib(compressed, expectedByteLength);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
if (encoding !== 0) {
|
|
188
|
+
throw new Error(`Unsupported FBX array encoding: ${encoding}`);
|
|
189
|
+
}
|
|
190
|
+
if (compressedLength !== expectedByteLength) {
|
|
191
|
+
throw new Error(`Invalid FBX array byte length for ${type}`);
|
|
192
|
+
}
|
|
193
|
+
arrayData = bytes.slice(offset, offset + compressedLength);
|
|
194
|
+
}
|
|
195
|
+
const arrayBuffer = arrayData.buffer.slice(arrayData.byteOffset, arrayData.byteOffset + arrayData.byteLength);
|
|
196
|
+
let value;
|
|
197
|
+
switch (type) {
|
|
198
|
+
case "float32[]":
|
|
199
|
+
value = new Float32Array(arrayBuffer);
|
|
200
|
+
break;
|
|
201
|
+
case "float64[]":
|
|
202
|
+
value = new Float64Array(arrayBuffer);
|
|
203
|
+
break;
|
|
204
|
+
case "int32[]":
|
|
205
|
+
value = new Int32Array(arrayBuffer);
|
|
206
|
+
break;
|
|
207
|
+
case "boolean[]":
|
|
208
|
+
value = arrayData;
|
|
209
|
+
break;
|
|
210
|
+
case "int64[]":
|
|
211
|
+
value = readInt64ArrayData(arrayData);
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
throw new Error(`Unexpected array type: ${type}`);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
property: { type, value },
|
|
218
|
+
nextOffset: offset + compressedLength,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function ensureRange(bytes, offset, byteLength, limit, context) {
|
|
222
|
+
if (offset < 0 || byteLength < 0 || offset + byteLength > limit || offset + byteLength > bytes.byteLength) {
|
|
223
|
+
throw new Error(`${context}: unexpected end of input`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function readUint64AsNumber(view, offset) {
|
|
227
|
+
const low = view.getUint32(offset, true);
|
|
228
|
+
const high = view.getUint32(offset + 4, true);
|
|
229
|
+
return high * 0x100000000 + low;
|
|
230
|
+
}
|
|
231
|
+
function readInt64AsNumber(view, offset) {
|
|
232
|
+
const low = view.getUint32(offset, true);
|
|
233
|
+
const high = view.getInt32(offset + 4, true);
|
|
234
|
+
return high * 0x100000000 + low;
|
|
235
|
+
}
|
|
236
|
+
function readInt64ArrayData(arrayData) {
|
|
237
|
+
const view = new DataView(arrayData.buffer, arrayData.byteOffset, arrayData.byteLength);
|
|
238
|
+
const values = new Float64Array(arrayData.byteLength / 8);
|
|
239
|
+
for (let i = 0; i < values.length; i++) {
|
|
240
|
+
values[i] = readInt64AsNumber(view, i * 8);
|
|
241
|
+
}
|
|
242
|
+
return values;
|
|
243
|
+
}
|
|
244
|
+
function decodeASCII(bytes, offset, length) {
|
|
245
|
+
let result = "";
|
|
246
|
+
for (let i = 0; i < length; i++) {
|
|
247
|
+
result += String.fromCharCode(bytes[offset + i]);
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function decodeUTF8(bytes, offset, length) {
|
|
252
|
+
const decoder = new TextDecoder("utf-8");
|
|
253
|
+
return decoder.decode(bytes.subarray(offset, offset + length));
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=fbxBinaryParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fbxBinaryParser.js","sourceRoot":"","sources":["../../../../../dev/loaders/src/FBX/parsers/fbxBinaryParser.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */\r\nimport { type FBXDocument, type FBXNode, type FBXProperty, type FBXPropertyType } from \"../types/fbxTypes\";\r\nimport { inflateZlib } from \"./zlibInflate\";\r\n\r\nconst FBX_MAGIC = \"Kaydara FBX Binary \\0\";\r\nconst HEADER_SIZE = 27; // 21 magic + 2 padding + 4 version uint32\r\n\r\n/**\r\n * Parse a binary FBX file into an FBXDocument.\r\n * Supports FBX versions 7.0–7.7 (v7.5+ uses 64-bit node headers).\r\n */\r\nexport function parseBinaryFBX(buffer: ArrayBuffer): FBXDocument {\r\n const view = new DataView(buffer);\r\n const bytes = new Uint8Array(buffer);\r\n\r\n // Validate magic\r\n const magic = decodeASCII(bytes, 0, 21);\r\n if (magic !== FBX_MAGIC) {\r\n throw new Error(\"Not a valid binary FBX file\");\r\n }\r\n if (buffer.byteLength < HEADER_SIZE) {\r\n throw new Error(\"Truncated binary FBX header\");\r\n }\r\n\r\n const version = view.getUint32(23, true);\r\n // v7.5+ uses 64-bit offsets in node records\r\n const is64Bit = version >= 7500;\r\n\r\n const nodes: FBXNode[] = [];\r\n let offset = HEADER_SIZE;\r\n\r\n while (offset < buffer.byteLength) {\r\n const result = parseNode(view, bytes, offset, is64Bit, buffer.byteLength);\r\n if (result === null) {\r\n break; // null sentinel node\r\n }\r\n nodes.push(result.node);\r\n offset = result.endOffset;\r\n }\r\n\r\n return { version, nodes };\r\n}\r\n\r\ninterface ParsedNode {\r\n node: FBXNode;\r\n endOffset: number;\r\n}\r\n\r\nfunction parseNode(view: DataView, bytes: Uint8Array, offset: number, is64Bit: boolean, limit: number): ParsedNode | null {\r\n // Read node header\r\n let endOffset: number;\r\n let numProperties: number;\r\n let propertyListLen: number;\r\n let headerSize: number;\r\n\r\n if (is64Bit) {\r\n ensureRange(bytes, offset, 25, limit, \"FBX node header\");\r\n endOffset = readUint64AsNumber(view, offset);\r\n numProperties = readUint64AsNumber(view, offset + 8);\r\n propertyListLen = readUint64AsNumber(view, offset + 16);\r\n headerSize = 25; // 8+8+8+1 (nameLen byte)\r\n } else {\r\n ensureRange(bytes, offset, 13, limit, \"FBX node header\");\r\n endOffset = view.getUint32(offset, true);\r\n numProperties = view.getUint32(offset + 4, true);\r\n propertyListLen = view.getUint32(offset + 8, true);\r\n headerSize = 13; // 4+4+4+1 (nameLen byte)\r\n }\r\n\r\n // Null sentinel: all header fields are zero\r\n if (endOffset === 0) {\r\n return null;\r\n }\r\n if (endOffset <= offset || endOffset > limit) {\r\n throw new Error(`Invalid FBX node end offset ${endOffset} at offset ${offset}`);\r\n }\r\n\r\n const nameLen = bytes[offset + headerSize - 1];\r\n ensureRange(bytes, offset + headerSize, nameLen, endOffset, \"FBX node name\");\r\n const name = decodeASCII(bytes, offset + headerSize, nameLen);\r\n\r\n let cursor = offset + headerSize + nameLen;\r\n const propertiesStart = cursor;\r\n const propertiesEnd = propertiesStart + propertyListLen;\r\n if (propertiesEnd > endOffset) {\r\n throw new Error(`Invalid FBX property list length for node '${name}' at offset ${offset}`);\r\n }\r\n\r\n // Parse properties\r\n const properties: FBXProperty[] = [];\r\n for (let i = 0; i < numProperties; i++) {\r\n const result = parseProperty(view, bytes, cursor, propertiesEnd);\r\n properties.push(result.property);\r\n cursor = result.nextOffset;\r\n }\r\n if (cursor !== propertiesEnd) {\r\n throw new Error(`Invalid FBX property list length for node '${name}' at offset ${offset}`);\r\n }\r\n\r\n // Parse nested child nodes (between end of properties and endOffset)\r\n const children: FBXNode[] = [];\r\n if (cursor < endOffset) {\r\n while (cursor < endOffset) {\r\n const child = parseNode(view, bytes, cursor, is64Bit, endOffset);\r\n if (child === null) {\r\n break;\r\n }\r\n if (child.endOffset <= cursor || child.endOffset > endOffset) {\r\n throw new Error(`Invalid FBX child node end offset ${child.endOffset} at offset ${cursor}`);\r\n }\r\n children.push(child.node);\r\n cursor = child.endOffset;\r\n }\r\n }\r\n\r\n return {\r\n node: { name, properties, children },\r\n endOffset,\r\n };\r\n}\r\n\r\ninterface ParsedProperty {\r\n property: FBXProperty;\r\n nextOffset: number;\r\n}\r\n\r\nfunction parseProperty(view: DataView, bytes: Uint8Array, offset: number, limit: number): ParsedProperty {\r\n ensureRange(bytes, offset, 1, limit, \"FBX property type\");\r\n const typeCode = String.fromCharCode(bytes[offset]);\r\n offset += 1;\r\n\r\n switch (typeCode) {\r\n case \"C\": {\r\n // Boolean (1 byte)\r\n ensureRange(bytes, offset, 1, limit, \"FBX boolean property\");\r\n const value = bytes[offset] !== 0;\r\n return { property: { type: \"boolean\", value }, nextOffset: offset + 1 };\r\n }\r\n case \"Y\": {\r\n // Int16\r\n ensureRange(bytes, offset, 2, limit, \"FBX int16 property\");\r\n const value = view.getInt16(offset, true);\r\n return { property: { type: \"int16\", value }, nextOffset: offset + 2 };\r\n }\r\n case \"I\": {\r\n // Int32\r\n ensureRange(bytes, offset, 4, limit, \"FBX int32 property\");\r\n const value = view.getInt32(offset, true);\r\n return { property: { type: \"int32\", value }, nextOffset: offset + 4 };\r\n }\r\n case \"F\": {\r\n // Float32\r\n ensureRange(bytes, offset, 4, limit, \"FBX float32 property\");\r\n const value = view.getFloat32(offset, true);\r\n return { property: { type: \"float32\", value }, nextOffset: offset + 4 };\r\n }\r\n case \"D\": {\r\n // Float64\r\n ensureRange(bytes, offset, 8, limit, \"FBX float64 property\");\r\n const value = view.getFloat64(offset, true);\r\n return { property: { type: \"float64\", value }, nextOffset: offset + 8 };\r\n }\r\n case \"L\": {\r\n // Int64\r\n ensureRange(bytes, offset, 8, limit, \"FBX int64 property\");\r\n const value = readInt64AsNumber(view, offset);\r\n return { property: { type: \"int64\", value }, nextOffset: offset + 8 };\r\n }\r\n case \"S\": {\r\n // String (uint32 length + data)\r\n ensureRange(bytes, offset, 4, limit, \"FBX string property length\");\r\n const len = view.getUint32(offset, true);\r\n ensureRange(bytes, offset + 4, len, limit, \"FBX string property data\");\r\n const value = decodeUTF8(bytes, offset + 4, len);\r\n return { property: { type: \"string\", value }, nextOffset: offset + 4 + len };\r\n }\r\n case \"R\": {\r\n // Raw binary data (uint32 length + data)\r\n ensureRange(bytes, offset, 4, limit, \"FBX raw property length\");\r\n const len = view.getUint32(offset, true);\r\n ensureRange(bytes, offset + 4, len, limit, \"FBX raw property data\");\r\n const value = bytes.slice(offset + 4, offset + 4 + len);\r\n return { property: { type: \"raw\", value }, nextOffset: offset + 4 + len };\r\n }\r\n // Array types\r\n case \"f\":\r\n return parseArrayProperty(view, bytes, offset, \"float32[]\", 4, limit);\r\n case \"d\":\r\n return parseArrayProperty(view, bytes, offset, \"float64[]\", 8, limit);\r\n case \"i\":\r\n return parseArrayProperty(view, bytes, offset, \"int32[]\", 4, limit);\r\n case \"l\":\r\n return parseArrayProperty(view, bytes, offset, \"int64[]\", 8, limit);\r\n case \"b\":\r\n return parseArrayProperty(view, bytes, offset, \"boolean[]\", 1, limit);\r\n default:\r\n throw new Error(`Unknown FBX property type: '${typeCode}' at offset ${offset - 1}`);\r\n }\r\n}\r\n\r\nfunction parseArrayProperty(view: DataView, bytes: Uint8Array, offset: number, type: FBXPropertyType, elementSize: number, limit: number): ParsedProperty {\r\n ensureRange(bytes, offset, 12, limit, `FBX array property header for ${type}`);\r\n const arrayLength = view.getUint32(offset, true);\r\n const encoding = view.getUint32(offset + 4, true); // 0=raw, 1=zlib\r\n const compressedLength = view.getUint32(offset + 8, true);\r\n offset += 12;\r\n const expectedByteLength = arrayLength * elementSize;\r\n ensureRange(bytes, offset, compressedLength, limit, `FBX array property data for ${type}`);\r\n\r\n let arrayData: Uint8Array;\r\n if (encoding === 1) {\r\n // zlib compressed\r\n const compressed = bytes.subarray(offset, offset + compressedLength);\r\n arrayData = inflateZlib(compressed, expectedByteLength);\r\n } else {\r\n if (encoding !== 0) {\r\n throw new Error(`Unsupported FBX array encoding: ${encoding}`);\r\n }\r\n if (compressedLength !== expectedByteLength) {\r\n throw new Error(`Invalid FBX array byte length for ${type}`);\r\n }\r\n arrayData = bytes.slice(offset, offset + compressedLength);\r\n }\r\n\r\n const arrayBuffer = arrayData.buffer.slice(arrayData.byteOffset, arrayData.byteOffset + arrayData.byteLength);\r\n\r\n let value: Float32Array | Float64Array | Int32Array | Uint8Array;\r\n switch (type) {\r\n case \"float32[]\":\r\n value = new Float32Array(arrayBuffer);\r\n break;\r\n case \"float64[]\":\r\n value = new Float64Array(arrayBuffer);\r\n break;\r\n case \"int32[]\":\r\n value = new Int32Array(arrayBuffer);\r\n break;\r\n case \"boolean[]\":\r\n value = arrayData;\r\n break;\r\n case \"int64[]\":\r\n value = readInt64ArrayData(arrayData);\r\n break;\r\n default:\r\n throw new Error(`Unexpected array type: ${type}`);\r\n }\r\n\r\n return {\r\n property: { type, value },\r\n nextOffset: offset + compressedLength,\r\n };\r\n}\r\n\r\nfunction ensureRange(bytes: Uint8Array, offset: number, byteLength: number, limit: number, context: string): void {\r\n if (offset < 0 || byteLength < 0 || offset + byteLength > limit || offset + byteLength > bytes.byteLength) {\r\n throw new Error(`${context}: unexpected end of input`);\r\n }\r\n}\r\n\r\nfunction readUint64AsNumber(view: DataView, offset: number): number {\r\n const low = view.getUint32(offset, true);\r\n const high = view.getUint32(offset + 4, true);\r\n return high * 0x100000000 + low;\r\n}\r\n\r\nfunction readInt64AsNumber(view: DataView, offset: number): number {\r\n const low = view.getUint32(offset, true);\r\n const high = view.getInt32(offset + 4, true);\r\n return high * 0x100000000 + low;\r\n}\r\n\r\nfunction readInt64ArrayData(arrayData: Uint8Array): Float64Array {\r\n const view = new DataView(arrayData.buffer, arrayData.byteOffset, arrayData.byteLength);\r\n const values = new Float64Array(arrayData.byteLength / 8);\r\n for (let i = 0; i < values.length; i++) {\r\n values[i] = readInt64AsNumber(view, i * 8);\r\n }\r\n return values;\r\n}\r\n\r\nfunction decodeASCII(bytes: Uint8Array, offset: number, length: number): string {\r\n let result = \"\";\r\n for (let i = 0; i < length; i++) {\r\n result += String.fromCharCode(bytes[offset + i]);\r\n }\r\n return result;\r\n}\r\n\r\nfunction decodeUTF8(bytes: Uint8Array, offset: number, length: number): string {\r\n const decoder = new TextDecoder(\"utf-8\");\r\n return decoder.decode(bytes.subarray(offset, offset + length));\r\n}\r\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inflate a zlib-wrapped deflate stream.
|
|
3
|
+
*
|
|
4
|
+
* This implementation is intentionally scoped to FBX binary array payloads: one-shot,
|
|
5
|
+
* synchronous zlib streams with the exact uncompressed length known up front.
|
|
6
|
+
*/
|
|
7
|
+
export declare function inflateZlib(input: Uint8Array, expectedLength: number): Uint8Array;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */
|
|
2
|
+
const ADLER_MOD = 65521;
|
|
3
|
+
const MAX_BITS = 15;
|
|
4
|
+
const LENGTH_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
|
|
5
|
+
const LENGTH_EXTRA_BITS = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
|
|
6
|
+
const DISTANCE_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
|
|
7
|
+
const DISTANCE_EXTRA_BITS = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
|
|
8
|
+
const CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
9
|
+
/**
|
|
10
|
+
* Inflate a zlib-wrapped deflate stream.
|
|
11
|
+
*
|
|
12
|
+
* This implementation is intentionally scoped to FBX binary array payloads: one-shot,
|
|
13
|
+
* synchronous zlib streams with the exact uncompressed length known up front.
|
|
14
|
+
*/
|
|
15
|
+
export function inflateZlib(input, expectedLength) {
|
|
16
|
+
if (!Number.isInteger(expectedLength) || expectedLength < 0) {
|
|
17
|
+
throw new Error("zlib: invalid expected length");
|
|
18
|
+
}
|
|
19
|
+
if (input.byteLength < 6) {
|
|
20
|
+
throw new Error("zlib: unexpected end of input");
|
|
21
|
+
}
|
|
22
|
+
const cmf = input[0];
|
|
23
|
+
const flg = input[1];
|
|
24
|
+
if ((cmf & 0x0f) !== 8 || cmf >> 4 > 7 || ((cmf << 8) + flg) % 31 !== 0) {
|
|
25
|
+
throw new Error("zlib: invalid header");
|
|
26
|
+
}
|
|
27
|
+
if ((flg & 0x20) !== 0) {
|
|
28
|
+
throw new Error("zlib: preset dictionary not supported");
|
|
29
|
+
}
|
|
30
|
+
const reader = new BitReader(input, 2, input.byteLength - 4);
|
|
31
|
+
const output = new OutputWriter(expectedLength);
|
|
32
|
+
let isFinalBlock = false;
|
|
33
|
+
while (!isFinalBlock) {
|
|
34
|
+
isFinalBlock = reader.readBits(1) === 1;
|
|
35
|
+
const blockType = reader.readBits(2);
|
|
36
|
+
switch (blockType) {
|
|
37
|
+
case 0:
|
|
38
|
+
inflateStoredBlock(reader, output);
|
|
39
|
+
break;
|
|
40
|
+
case 1:
|
|
41
|
+
inflateCompressedBlock(reader, output, getFixedLiteralLengthTree(), getFixedDistanceTree());
|
|
42
|
+
break;
|
|
43
|
+
case 2: {
|
|
44
|
+
const { literalLengthTree, distanceTree } = readDynamicTrees(reader);
|
|
45
|
+
inflateCompressedBlock(reader, output, literalLengthTree, distanceTree);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
throw new Error("deflate: invalid block type");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (reader.byteOffset < input.byteLength - 4) {
|
|
53
|
+
throw new Error("zlib: trailing deflate data");
|
|
54
|
+
}
|
|
55
|
+
output.finish();
|
|
56
|
+
const expectedAdler = ((input[input.byteLength - 4] << 24) | (input[input.byteLength - 3] << 16) | (input[input.byteLength - 2] << 8) | input[input.byteLength - 1]) >>> 0;
|
|
57
|
+
if (output.adler32() !== expectedAdler) {
|
|
58
|
+
throw new Error("zlib: adler32 mismatch");
|
|
59
|
+
}
|
|
60
|
+
return output.bytes;
|
|
61
|
+
}
|
|
62
|
+
class BitReader {
|
|
63
|
+
constructor(input, byteOffset, endOffset) {
|
|
64
|
+
this.input = input;
|
|
65
|
+
this.byteOffset = byteOffset;
|
|
66
|
+
this.endOffset = endOffset;
|
|
67
|
+
this.bitBuffer = 0;
|
|
68
|
+
this.bitCount = 0;
|
|
69
|
+
}
|
|
70
|
+
readBits(count) {
|
|
71
|
+
this.ensureBits(count);
|
|
72
|
+
const value = this.bitBuffer & ((1 << count) - 1);
|
|
73
|
+
this.bitBuffer >>>= count;
|
|
74
|
+
this.bitCount -= count;
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
readBit() {
|
|
78
|
+
if (this.bitCount === 0) {
|
|
79
|
+
if (this.byteOffset >= this.endOffset) {
|
|
80
|
+
throw new Error("zlib: unexpected end of input");
|
|
81
|
+
}
|
|
82
|
+
this.bitBuffer = this.input[this.byteOffset++];
|
|
83
|
+
this.bitCount = 8;
|
|
84
|
+
}
|
|
85
|
+
const bit = this.bitBuffer & 1;
|
|
86
|
+
this.bitBuffer >>>= 1;
|
|
87
|
+
this.bitCount--;
|
|
88
|
+
return bit;
|
|
89
|
+
}
|
|
90
|
+
alignToByte() {
|
|
91
|
+
this.bitBuffer = 0;
|
|
92
|
+
this.bitCount = 0;
|
|
93
|
+
}
|
|
94
|
+
readUint16LE() {
|
|
95
|
+
this.ensureByteAligned();
|
|
96
|
+
if (this.byteOffset + 2 > this.endOffset) {
|
|
97
|
+
throw new Error("zlib: unexpected end of input");
|
|
98
|
+
}
|
|
99
|
+
const value = this.input[this.byteOffset] | (this.input[this.byteOffset + 1] << 8);
|
|
100
|
+
this.byteOffset += 2;
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
readByte() {
|
|
104
|
+
this.ensureByteAligned();
|
|
105
|
+
if (this.byteOffset >= this.endOffset) {
|
|
106
|
+
throw new Error("zlib: unexpected end of input");
|
|
107
|
+
}
|
|
108
|
+
return this.input[this.byteOffset++];
|
|
109
|
+
}
|
|
110
|
+
ensureByteAligned() {
|
|
111
|
+
if (this.bitCount !== 0) {
|
|
112
|
+
throw new Error("deflate: expected byte alignment");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
ensureBits(count) {
|
|
116
|
+
while (this.bitCount < count) {
|
|
117
|
+
if (this.byteOffset >= this.endOffset) {
|
|
118
|
+
throw new Error("zlib: unexpected end of input");
|
|
119
|
+
}
|
|
120
|
+
this.bitBuffer |= this.input[this.byteOffset++] << this.bitCount;
|
|
121
|
+
this.bitCount += 8;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
class OutputWriter {
|
|
126
|
+
constructor(expectedLength) {
|
|
127
|
+
this.offset = 0;
|
|
128
|
+
this.adlerA = 1;
|
|
129
|
+
this.adlerB = 0;
|
|
130
|
+
this.bytes = new Uint8Array(expectedLength);
|
|
131
|
+
}
|
|
132
|
+
writeByte(value) {
|
|
133
|
+
if (this.offset >= this.bytes.byteLength) {
|
|
134
|
+
throw new Error("zlib: output length mismatch");
|
|
135
|
+
}
|
|
136
|
+
const byte = value & 0xff;
|
|
137
|
+
this.bytes[this.offset++] = byte;
|
|
138
|
+
this.adlerA += byte;
|
|
139
|
+
this.adlerB += this.adlerA;
|
|
140
|
+
this.adlerA %= ADLER_MOD;
|
|
141
|
+
this.adlerB %= ADLER_MOD;
|
|
142
|
+
}
|
|
143
|
+
copy(distance, length) {
|
|
144
|
+
if (distance <= 0 || distance > this.offset) {
|
|
145
|
+
throw new Error("deflate: distance out of range");
|
|
146
|
+
}
|
|
147
|
+
for (let i = 0; i < length; i++) {
|
|
148
|
+
this.writeByte(this.bytes[this.offset - distance]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
finish() {
|
|
152
|
+
if (this.offset !== this.bytes.byteLength) {
|
|
153
|
+
throw new Error("zlib: output length mismatch");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
adler32() {
|
|
157
|
+
return ((this.adlerB << 16) | this.adlerA) >>> 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
class HuffmanTree {
|
|
161
|
+
constructor(codeLengths, options = {}) {
|
|
162
|
+
const counts = new Array(MAX_BITS + 1).fill(0);
|
|
163
|
+
let nonZeroCount = 0;
|
|
164
|
+
let maxCodeLength = 0;
|
|
165
|
+
for (const length of codeLengths) {
|
|
166
|
+
if (!Number.isInteger(length) || length < 0 || length > MAX_BITS) {
|
|
167
|
+
throw new Error("deflate: invalid huffman code lengths");
|
|
168
|
+
}
|
|
169
|
+
if (length > 0) {
|
|
170
|
+
counts[length]++;
|
|
171
|
+
nonZeroCount++;
|
|
172
|
+
maxCodeLength = Math.max(maxCodeLength, length);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (nonZeroCount === 0) {
|
|
176
|
+
if (options.allowEmpty) {
|
|
177
|
+
this.symbolsByLength = [];
|
|
178
|
+
this.maxCodeLength = 0;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
throw new Error("deflate: invalid huffman code lengths");
|
|
182
|
+
}
|
|
183
|
+
let remaining = 1;
|
|
184
|
+
for (let bits = 1; bits <= MAX_BITS; bits++) {
|
|
185
|
+
remaining = (remaining << 1) - counts[bits];
|
|
186
|
+
if (remaining < 0) {
|
|
187
|
+
throw new Error("deflate: invalid huffman code lengths");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (remaining !== 0 && nonZeroCount !== 1) {
|
|
191
|
+
throw new Error("deflate: invalid huffman code lengths");
|
|
192
|
+
}
|
|
193
|
+
const nextCode = new Array(MAX_BITS + 1).fill(0);
|
|
194
|
+
let code = 0;
|
|
195
|
+
for (let bits = 1; bits <= MAX_BITS; bits++) {
|
|
196
|
+
code = (code + counts[bits - 1]) << 1;
|
|
197
|
+
nextCode[bits] = code;
|
|
198
|
+
}
|
|
199
|
+
this.symbolsByLength = Array.from({ length: MAX_BITS + 1 }, (_, length) => {
|
|
200
|
+
if (counts[length] === 0) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
const symbols = new Int16Array(1 << length);
|
|
204
|
+
symbols.fill(-1);
|
|
205
|
+
return symbols;
|
|
206
|
+
});
|
|
207
|
+
for (let symbol = 0; symbol < codeLengths.length; symbol++) {
|
|
208
|
+
const length = codeLengths[symbol];
|
|
209
|
+
if (length === 0) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
this.symbolsByLength[length][nextCode[length]++] = symbol;
|
|
213
|
+
}
|
|
214
|
+
this.maxCodeLength = maxCodeLength;
|
|
215
|
+
}
|
|
216
|
+
decode(reader) {
|
|
217
|
+
if (this.maxCodeLength === 0) {
|
|
218
|
+
throw new Error("deflate: invalid huffman code");
|
|
219
|
+
}
|
|
220
|
+
let code = 0;
|
|
221
|
+
for (let length = 1; length <= this.maxCodeLength; length++) {
|
|
222
|
+
code = (code << 1) | reader.readBit();
|
|
223
|
+
const symbol = this.symbolsByLength[length]?.[code] ?? -1;
|
|
224
|
+
if (symbol >= 0) {
|
|
225
|
+
return symbol;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
throw new Error("deflate: invalid huffman code");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let fixedLiteralLengthTree;
|
|
232
|
+
let fixedDistanceTree;
|
|
233
|
+
function getFixedLiteralLengthTree() {
|
|
234
|
+
if (!fixedLiteralLengthTree) {
|
|
235
|
+
const lengths = new Array(288);
|
|
236
|
+
for (let symbol = 0; symbol <= 143; symbol++) {
|
|
237
|
+
lengths[symbol] = 8;
|
|
238
|
+
}
|
|
239
|
+
for (let symbol = 144; symbol <= 255; symbol++) {
|
|
240
|
+
lengths[symbol] = 9;
|
|
241
|
+
}
|
|
242
|
+
for (let symbol = 256; symbol <= 279; symbol++) {
|
|
243
|
+
lengths[symbol] = 7;
|
|
244
|
+
}
|
|
245
|
+
for (let symbol = 280; symbol <= 287; symbol++) {
|
|
246
|
+
lengths[symbol] = 8;
|
|
247
|
+
}
|
|
248
|
+
fixedLiteralLengthTree = new HuffmanTree(lengths);
|
|
249
|
+
}
|
|
250
|
+
return fixedLiteralLengthTree;
|
|
251
|
+
}
|
|
252
|
+
function getFixedDistanceTree() {
|
|
253
|
+
if (!fixedDistanceTree) {
|
|
254
|
+
fixedDistanceTree = new HuffmanTree(new Array(32).fill(5));
|
|
255
|
+
}
|
|
256
|
+
return fixedDistanceTree;
|
|
257
|
+
}
|
|
258
|
+
function inflateStoredBlock(reader, output) {
|
|
259
|
+
reader.alignToByte();
|
|
260
|
+
const length = reader.readUint16LE();
|
|
261
|
+
const inverseLength = reader.readUint16LE();
|
|
262
|
+
if (((length ^ inverseLength) & 0xffff) !== 0xffff) {
|
|
263
|
+
throw new Error("deflate: invalid stored block length");
|
|
264
|
+
}
|
|
265
|
+
for (let i = 0; i < length; i++) {
|
|
266
|
+
output.writeByte(reader.readByte());
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function inflateCompressedBlock(reader, output, literalLengthTree, distanceTree) {
|
|
270
|
+
while (true) {
|
|
271
|
+
const symbol = literalLengthTree.decode(reader);
|
|
272
|
+
if (symbol < 256) {
|
|
273
|
+
output.writeByte(symbol);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (symbol === 256) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (symbol > 285) {
|
|
280
|
+
throw new Error("deflate: invalid literal/length symbol");
|
|
281
|
+
}
|
|
282
|
+
const lengthIndex = symbol - 257;
|
|
283
|
+
const length = LENGTH_BASE[lengthIndex] + reader.readBits(LENGTH_EXTRA_BITS[lengthIndex]);
|
|
284
|
+
const distanceSymbol = distanceTree.decode(reader);
|
|
285
|
+
if (distanceSymbol > 29) {
|
|
286
|
+
throw new Error("deflate: invalid distance symbol");
|
|
287
|
+
}
|
|
288
|
+
const distance = DISTANCE_BASE[distanceSymbol] + reader.readBits(DISTANCE_EXTRA_BITS[distanceSymbol]);
|
|
289
|
+
output.copy(distance, length);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function readDynamicTrees(reader) {
|
|
293
|
+
const literalLengthCount = reader.readBits(5) + 257;
|
|
294
|
+
const distanceCount = reader.readBits(5) + 1;
|
|
295
|
+
const codeLengthCount = reader.readBits(4) + 4;
|
|
296
|
+
const codeLengthLengths = new Array(19).fill(0);
|
|
297
|
+
for (let i = 0; i < codeLengthCount; i++) {
|
|
298
|
+
codeLengthLengths[CODE_LENGTH_ORDER[i]] = reader.readBits(3);
|
|
299
|
+
}
|
|
300
|
+
const codeLengthTree = new HuffmanTree(codeLengthLengths);
|
|
301
|
+
const lengths = readCodeLengths(reader, codeLengthTree, literalLengthCount + distanceCount);
|
|
302
|
+
const literalLengthLengths = lengths.slice(0, literalLengthCount);
|
|
303
|
+
const distanceLengths = lengths.slice(literalLengthCount);
|
|
304
|
+
if (literalLengthLengths[256] === 0) {
|
|
305
|
+
throw new Error("deflate: missing end-of-block code");
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
literalLengthTree: new HuffmanTree(literalLengthLengths),
|
|
309
|
+
distanceTree: new HuffmanTree(distanceLengths, { allowEmpty: true }),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function readCodeLengths(reader, codeLengthTree, count) {
|
|
313
|
+
const lengths = [];
|
|
314
|
+
while (lengths.length < count) {
|
|
315
|
+
const symbol = codeLengthTree.decode(reader);
|
|
316
|
+
if (symbol <= 15) {
|
|
317
|
+
lengths.push(symbol);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
let repeatLength;
|
|
321
|
+
let repeatedValue;
|
|
322
|
+
switch (symbol) {
|
|
323
|
+
case 16:
|
|
324
|
+
if (lengths.length === 0) {
|
|
325
|
+
throw new Error("deflate: invalid code length repeat");
|
|
326
|
+
}
|
|
327
|
+
repeatedValue = lengths[lengths.length - 1];
|
|
328
|
+
repeatLength = reader.readBits(2) + 3;
|
|
329
|
+
break;
|
|
330
|
+
case 17:
|
|
331
|
+
repeatedValue = 0;
|
|
332
|
+
repeatLength = reader.readBits(3) + 3;
|
|
333
|
+
break;
|
|
334
|
+
case 18:
|
|
335
|
+
repeatedValue = 0;
|
|
336
|
+
repeatLength = reader.readBits(7) + 11;
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
throw new Error("deflate: invalid code length symbol");
|
|
340
|
+
}
|
|
341
|
+
if (lengths.length + repeatLength > count) {
|
|
342
|
+
throw new Error("deflate: invalid code length repeat");
|
|
343
|
+
}
|
|
344
|
+
for (let i = 0; i < repeatLength; i++) {
|
|
345
|
+
lengths.push(repeatedValue);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return lengths;
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=zlibInflate.js.map
|