brs-js 2.0.1 → 2.0.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.
- package/debug/Conf Spacestation.brs +0 -0
- package/debug/Electrks_Feminine_Challenge.brs +0 -0
- package/debug/a5bricks.brs +0 -0
- package/debug/a5p2.2.brs +0 -0
- package/debug/a5p2.brs +0 -0
- package/debug/audio.brs +0 -0
- package/debug/brick a5.brs +0 -0
- package/debug/brick qa.brs +0 -0
- package/debug/brsv10.brs +0 -0
- package/debug/brsv10brick.brs +0 -0
- package/debug/bruteforce.js +254 -0
- package/debug/clone.html +36 -0
- package/debug/ctf_tileset.brs +0 -0
- package/debug/ctf_tileset_5.brs +0 -0
- package/debug/evil.brs +0 -0
- package/debug/evilwrite.js +390 -0
- package/debug/foo.txt +3080 -0
- package/debug/kenko_big.brs +0 -0
- package/debug/light.brs +0 -0
- package/debug/out.json +105 -0
- package/debug/read.js +30 -0
- package/debug/readSpeed.js +32 -0
- package/debug/readTest.js +45 -0
- package/debug/temp.brs +0 -0
- package/debug/western.brs +0 -0
- package/dist/dist.js +1 -1
- package/dist/dist.node.js +1 -1
- package/dist/dist.web.js +1 -1
- package/dist/src/constants.d.ts +6 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/read.d.ts +3 -0
- package/dist/src/read.d.ts.map +1 -0
- package/dist/src/read.v1.d.ts +3 -0
- package/dist/src/read.v1.d.ts.map +1 -0
- package/dist/src/read.v10.d.ts +3 -0
- package/dist/src/read.v10.d.ts.map +1 -0
- package/dist/src/read.v2.d.ts +3 -0
- package/dist/src/read.v2.d.ts.map +1 -0
- package/dist/src/read.v3.d.ts +3 -0
- package/dist/src/read.v3.d.ts.map +1 -0
- package/dist/src/read.v4.d.ts +3 -0
- package/dist/src/read.v4.d.ts.map +1 -0
- package/dist/src/read.v8.d.ts +3 -0
- package/dist/src/read.v8.d.ts.map +1 -0
- package/dist/src/read.v9.d.ts +3 -0
- package/dist/src/read.v9.d.ts.map +1 -0
- package/dist/src/types.d.ts +286 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +87 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/uuid.d.ts +4 -0
- package/dist/src/uuid.d.ts.map +1 -0
- package/dist/src/write.d.ts +3 -0
- package/dist/src/write.d.ts.map +1 -0
- package/examples/ATCFort.brs +0 -0
- package/examples/read_example.html +34 -0
- package/examples/read_example.js +21 -0
- package/examples/write_example.js +25 -0
- package/examples/write_planet.html +144 -0
- package/examples/write_simplex.html +82 -0
- package/examples/write_wedge_sphere.html +173 -0
- package/package.json +1 -1
- package/src/constants.ts +6 -0
- package/src/index.ts +25 -0
- package/src/read.ts +57 -0
- package/src/read.v1.ts +99 -0
- package/src/read.v10.ts +185 -0
- package/src/read.v2.ts +102 -0
- package/src/read.v3.ts +111 -0
- package/src/read.v4.ts +112 -0
- package/src/read.v8.ts +172 -0
- package/src/read.v9.ts +181 -0
- package/src/types.ts +354 -0
- package/src/utils.ts +640 -0
- package/src/uuid.ts +78 -0
- package/src/write.ts +209 -0
- package/test/lib.test.js +51 -0
- package/test/utils.test.js +209 -0
- package/tsconfig.json +1 -5
package/src/utils.ts
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { deflate, inflate } from 'pako';
|
|
2
|
+
import { MAX_INT } from './constants';
|
|
3
|
+
import {
|
|
4
|
+
BRSBytes,
|
|
5
|
+
Bytes,
|
|
6
|
+
UnrealColor,
|
|
7
|
+
UnrealFloat,
|
|
8
|
+
UnrealType,
|
|
9
|
+
Uuid,
|
|
10
|
+
} from './types';
|
|
11
|
+
import { uuidParse, uuidStringify } from './uuid';
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
Notes:
|
|
15
|
+
- Everything is Little Endian by default because
|
|
16
|
+
UE4 uses it.
|
|
17
|
+
- I'd use Buffer.readUInt16LE if I was making this
|
|
18
|
+
nodejs only. I don't want to require('Buffer/')
|
|
19
|
+
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Determine if a string is ascii-only
|
|
23
|
+
function isASCII(text: string): boolean {
|
|
24
|
+
return /^[\x00-\x7F]*$/.test(text);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// convert BGRA color to RGBA color
|
|
28
|
+
export const bgra = ([b, g, r, a]: number[]): [
|
|
29
|
+
number,
|
|
30
|
+
number,
|
|
31
|
+
number,
|
|
32
|
+
number
|
|
33
|
+
] => [r, g, b, a];
|
|
34
|
+
|
|
35
|
+
// Compare equality of byte arrays
|
|
36
|
+
export function isEqual<T>(arrA: Array<T>, arrB: Array<T>): boolean {
|
|
37
|
+
return arrA.length === arrB.length && arrA.every((a: T, i) => arrB[i] === a);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isBRSBytes(data: Bytes): data is BRSBytes {
|
|
41
|
+
return (data as BRSBytes).brsOffset !== undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// read `len` bytes and return slice while updating offset
|
|
45
|
+
export function subarray(data: Bytes, len: number, isCopy = false): Uint8Array {
|
|
46
|
+
if (!(data instanceof Uint8Array)) {
|
|
47
|
+
throw new Error(`Invalid data type in bytes reader (${typeof data})`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let bytes: BRSBytes;
|
|
51
|
+
if (!isBRSBytes(data)) {
|
|
52
|
+
bytes = data as BRSBytes;
|
|
53
|
+
bytes.brsOffset = 0;
|
|
54
|
+
} else {
|
|
55
|
+
bytes = data;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const chunk = bytes[isCopy ? 'slice' : 'subarray'](
|
|
59
|
+
bytes.brsOffset,
|
|
60
|
+
bytes.brsOffset + len
|
|
61
|
+
);
|
|
62
|
+
bytes.brsOffset += len;
|
|
63
|
+
return chunk;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// break a byte array into chunks of a specified size
|
|
67
|
+
export function chunk(arr: Bytes, size: number): BRSBytes[] {
|
|
68
|
+
// relative length based on the offset of the array's data view
|
|
69
|
+
const length = arr.length - (isBRSBytes(arr) ? arr.brsOffset : 0);
|
|
70
|
+
|
|
71
|
+
// out array of chunks pre-allocated
|
|
72
|
+
const out = Array(Math.floor(length / size));
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < length / size; i++) {
|
|
75
|
+
out[i] = subarray(arr, size, true);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read a u16 from a byte array
|
|
82
|
+
function read_u16(data: Bytes, littleEndian = true): number {
|
|
83
|
+
const [a, b] = subarray(data, 2);
|
|
84
|
+
|
|
85
|
+
return littleEndian ? (b << 8) | a : (a << 8) | b;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Write a u16 into byte array
|
|
89
|
+
function write_u16(num: number, littleEndian = true): Uint8Array {
|
|
90
|
+
const data = [num & 255, (num >> 8) & 255];
|
|
91
|
+
return new Uint8Array(!littleEndian ? data.reverse() : data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Read an i32 from a byte array
|
|
95
|
+
function read_i32(data: Bytes, littleEndian = true): number {
|
|
96
|
+
const [a, b, c, d] = subarray(data, 4);
|
|
97
|
+
return littleEndian
|
|
98
|
+
? (d << 24) | (c << 16) | (b << 8) | a
|
|
99
|
+
: (a << 24) | (b << 16) | (c << 8) | d;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Write an i32 from a byte array
|
|
103
|
+
function write_i32(num: number, littleEndian = true): Uint8Array {
|
|
104
|
+
const data = new Uint8Array([
|
|
105
|
+
num & 255,
|
|
106
|
+
(num >> 8) & 255,
|
|
107
|
+
(num >> 16) & 255,
|
|
108
|
+
(num >> 24) & 255,
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
return !littleEndian ? data.reverse() : data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Decompress a byte array of compressed data
|
|
115
|
+
function read_compressed(data: Bytes): Bytes {
|
|
116
|
+
const uncompressedSize = read_i32(data);
|
|
117
|
+
const compressedSize = read_i32(data);
|
|
118
|
+
|
|
119
|
+
// Throw error for weird compression/uncompression sizes
|
|
120
|
+
if (
|
|
121
|
+
compressedSize < 0 ||
|
|
122
|
+
uncompressedSize < 0 ||
|
|
123
|
+
compressedSize >= uncompressedSize
|
|
124
|
+
) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Invalid compressed section size (comp: ${compressedSize}, uncomp: ${uncompressedSize})`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// No compressed data? Return those bytes
|
|
131
|
+
if (compressedSize === 0) {
|
|
132
|
+
return subarray(data, uncompressedSize);
|
|
133
|
+
} else {
|
|
134
|
+
// Decompress the data otherwise
|
|
135
|
+
const compressed = subarray(data, compressedSize);
|
|
136
|
+
return inflate(compressed);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Compress a byte array into fewer bytes
|
|
141
|
+
function write_uncompressed(...args: Uint8Array[]): Uint8Array {
|
|
142
|
+
// Concat the args to one massive array
|
|
143
|
+
const data = concat(...args);
|
|
144
|
+
|
|
145
|
+
// Build the output
|
|
146
|
+
return concat(write_i32(data.length), write_i32(0), data);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Compress a byte array into fewer bytes
|
|
150
|
+
function write_compressed(...args: Uint8Array[]): Uint8Array {
|
|
151
|
+
// Concat the args to one massive array
|
|
152
|
+
const data = concat(...args);
|
|
153
|
+
|
|
154
|
+
// Do the compression
|
|
155
|
+
const compressed = deflate(data);
|
|
156
|
+
const uncompressedSize = data.length;
|
|
157
|
+
const compressedSize = compressed.length;
|
|
158
|
+
|
|
159
|
+
if (uncompressedSize > MAX_INT) {
|
|
160
|
+
throw new Error(`uncompressedSize (${uncompressedSize}) out of range`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (compressedSize > MAX_INT) {
|
|
164
|
+
throw new Error(`compressedSize (${compressedSize}) out of range`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Determine if compression increases size
|
|
168
|
+
const badCompress = compressedSize >= uncompressedSize;
|
|
169
|
+
|
|
170
|
+
// Build the output
|
|
171
|
+
return concat(
|
|
172
|
+
write_i32(uncompressedSize),
|
|
173
|
+
write_i32(badCompress ? 0 : compressedSize),
|
|
174
|
+
badCompress ? data : compressed
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Read a string from a byte array
|
|
179
|
+
function read_string(data: Bytes): string {
|
|
180
|
+
const raw_size = read_i32(data);
|
|
181
|
+
const is_ucs2 = raw_size < 0;
|
|
182
|
+
const size = is_ucs2 ? -raw_size : raw_size;
|
|
183
|
+
|
|
184
|
+
// Determine if we are using UCS-2
|
|
185
|
+
if (is_ucs2) {
|
|
186
|
+
if (size % 2 !== 0) {
|
|
187
|
+
throw new Error('Invalid UCS-2 data size');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create ucs2 encoded string
|
|
191
|
+
return String.fromCodePoint(
|
|
192
|
+
// Read the data in 2 byte windows
|
|
193
|
+
...chunk(subarray(data, size), 2).map(arr => read_u16(arr)) // Convert the two bytes into u16
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
// Read the data, remove the \u0000 at the end :)
|
|
197
|
+
const strData = subarray(data, size).subarray(0, -1);
|
|
198
|
+
|
|
199
|
+
// Convert into ascii
|
|
200
|
+
return String.fromCharCode.apply(null, strData);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Write a string to bytes
|
|
205
|
+
function write_string(str: string): Uint8Array {
|
|
206
|
+
if (isASCII(str)) {
|
|
207
|
+
return concat(
|
|
208
|
+
write_i32(str.length + 1), // Write string length (+ null term)
|
|
209
|
+
new Uint8Array(str.split('').map(s => s.charCodeAt(0))), // Write string as bytes
|
|
210
|
+
new Uint8Array([0]) // Null terminator
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
// ucs2 strings denoted by negative length
|
|
214
|
+
const len = -((str.length + 1) * 2);
|
|
215
|
+
return concat(
|
|
216
|
+
write_i32(len), // write length
|
|
217
|
+
// convert string to little endian ucs2
|
|
218
|
+
new Uint8Array(
|
|
219
|
+
str
|
|
220
|
+
.split('')
|
|
221
|
+
.flatMap(s => [s.charCodeAt(0) & 0xff, s.charCodeAt(0) >> 8])
|
|
222
|
+
),
|
|
223
|
+
new Uint8Array([0]) // Null terminator
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Read uuid from 4 LE ints
|
|
229
|
+
function read_uuid(data: Bytes): string {
|
|
230
|
+
return uuidStringify(
|
|
231
|
+
// each chunk is LE
|
|
232
|
+
chunk(subarray(data, 16), 4).flatMap(([a, b, c, d]) => [d, c, b, a])
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// parse a uuid into 4 LE ints
|
|
237
|
+
function write_uuid(uuid: Uuid) {
|
|
238
|
+
return concat(
|
|
239
|
+
...chunk(uuidParse(uuid), 4).map(
|
|
240
|
+
([a, b, c, d]) => new Uint8Array([d, c, b, a])
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Read an array of things given a fn
|
|
246
|
+
function read_array<T>(data: Bytes, fn: (_: Bytes) => T): T[] {
|
|
247
|
+
const length = read_i32(data);
|
|
248
|
+
const arr = Array(length);
|
|
249
|
+
for (let i = 0; i < length; i++) {
|
|
250
|
+
arr[i] = fn(data);
|
|
251
|
+
}
|
|
252
|
+
return arr;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// iterate an array of things given a fn
|
|
256
|
+
function read_each(data: Bytes, fn: (_: Bytes) => void) {
|
|
257
|
+
const length = read_i32(data);
|
|
258
|
+
for (let i = 0; i < length; i++) {
|
|
259
|
+
fn(data);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Write an array of things to bytes
|
|
264
|
+
function write_array<T>(arr: T[], fn: (_: T) => Uint8Array) {
|
|
265
|
+
return concat(write_i32(arr.length), ...arr.map(o => fn(o)));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Tool for reading byte arrays 1 bit at a time
|
|
269
|
+
class BitReader {
|
|
270
|
+
buffer: Uint8Array;
|
|
271
|
+
pos: number = 0;
|
|
272
|
+
|
|
273
|
+
constructor(data: Uint8Array) {
|
|
274
|
+
this.buffer = data;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
empty(): boolean {
|
|
278
|
+
return this.pos >= this.buffer.length * 8;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Read one bit as a boolean
|
|
282
|
+
bit(): boolean {
|
|
283
|
+
const bit = (this.buffer[this.pos >> 3] & (1 << (this.pos & 0b111))) !== 0;
|
|
284
|
+
this.pos++;
|
|
285
|
+
return bit;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Align the pos to the nearest byte
|
|
289
|
+
align() {
|
|
290
|
+
this.pos = (this.pos + 7) & ~7;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// read an int up to max
|
|
294
|
+
int(max: number): number {
|
|
295
|
+
let value = 0;
|
|
296
|
+
let mask = 1;
|
|
297
|
+
|
|
298
|
+
while (value + mask < max && mask !== 0) {
|
|
299
|
+
if (this.bit()) {
|
|
300
|
+
value |= mask;
|
|
301
|
+
}
|
|
302
|
+
mask <<= 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// read a packet in from bits
|
|
309
|
+
uint_packed(): number {
|
|
310
|
+
let value = 0;
|
|
311
|
+
for (let i = 0; i < 5; i++) {
|
|
312
|
+
const next = this.bit();
|
|
313
|
+
|
|
314
|
+
let part = 0;
|
|
315
|
+
for (let shift = 0; shift < 7; shift++) {
|
|
316
|
+
part |= (this.bit() ? 1 : 0) << shift;
|
|
317
|
+
}
|
|
318
|
+
value |= part << (7 * i);
|
|
319
|
+
if (!next) {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return value;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// an item in a read_positive_int_vector array
|
|
328
|
+
int_packed(): number {
|
|
329
|
+
const value = this.uint_packed();
|
|
330
|
+
return (value >> 1) * ((value & 1) !== 0 ? 1 : -1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// read some bits
|
|
334
|
+
bits(num: number): number[] {
|
|
335
|
+
const arr: number[] = [];
|
|
336
|
+
for (let bit = 0; bit < num; bit++) {
|
|
337
|
+
const shift = bit & 7;
|
|
338
|
+
arr[bit >> 3] =
|
|
339
|
+
(arr[bit >> 3] & ~(1 << shift)) | ((this.bit() ? 1 : 0) << shift);
|
|
340
|
+
}
|
|
341
|
+
return arr;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Read some bytes
|
|
345
|
+
bytes(num: number): Uint8Array {
|
|
346
|
+
return new Uint8Array(this.bytesArr(num));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// read some bytes but not as a Uint8Array
|
|
350
|
+
bytesArr(num: number): number[] {
|
|
351
|
+
return this.bits(num * 8);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// read an array
|
|
355
|
+
array<T>(fn: (_: BitReader) => T): T[] {
|
|
356
|
+
const length = read_i32(this.bytes(4));
|
|
357
|
+
const arr = Array(length);
|
|
358
|
+
for (let i = 0; i < length; i++) {
|
|
359
|
+
arr[i] = fn(this);
|
|
360
|
+
}
|
|
361
|
+
return arr;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// for each
|
|
365
|
+
each(fn: (data: BitReader) => void) {
|
|
366
|
+
const length = read_i32(this.bytes(4));
|
|
367
|
+
for (let i = 0; i < length; i++) {
|
|
368
|
+
fn(this);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// read a string
|
|
373
|
+
string(): string {
|
|
374
|
+
const lenBytes = this.bytesArr(4);
|
|
375
|
+
const len = read_i32(new Uint8Array(lenBytes));
|
|
376
|
+
return read_string(new Uint8Array(lenBytes.concat(this.bytesArr(len))));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// read a 32-bit float
|
|
380
|
+
float(): number {
|
|
381
|
+
const view = new DataView(new ArrayBuffer(4));
|
|
382
|
+
|
|
383
|
+
// Write the ints to it
|
|
384
|
+
view.setUint16(2, read_u16(this.bytes(2)));
|
|
385
|
+
view.setUint16(0, read_u16(this.bytes(2)));
|
|
386
|
+
|
|
387
|
+
// Read the bits as a float; note that by doing this, we're implicitly
|
|
388
|
+
// converting it from a 32-bit float into JavaScript's native 64-bit double
|
|
389
|
+
return view.getFloat32(0);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// read unreal types
|
|
393
|
+
unreal(type: string): UnrealType {
|
|
394
|
+
switch (type) {
|
|
395
|
+
case 'Class':
|
|
396
|
+
case 'Object':
|
|
397
|
+
return this.string();
|
|
398
|
+
case 'Boolean':
|
|
399
|
+
return !!read_i32(this.bytes(4));
|
|
400
|
+
case 'Float':
|
|
401
|
+
return this.float();
|
|
402
|
+
case 'Color':
|
|
403
|
+
return bgra(this.bytesArr(4)) as UnrealColor;
|
|
404
|
+
case 'Byte':
|
|
405
|
+
return this.bytes(1)[0];
|
|
406
|
+
case 'Rotator':
|
|
407
|
+
return [this.float(), this.float(), this.float()];
|
|
408
|
+
}
|
|
409
|
+
throw new Error('Unknown unreal type ' + type);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
class BitWriter {
|
|
414
|
+
buffer: number[] = [];
|
|
415
|
+
cur: number = 0;
|
|
416
|
+
bitNum: number = 0;
|
|
417
|
+
|
|
418
|
+
// Write a boolean as a bit
|
|
419
|
+
bit(val: boolean) {
|
|
420
|
+
this.cur |= (val ? 1 : 0) << this.bitNum;
|
|
421
|
+
this.bitNum++;
|
|
422
|
+
if (this.bitNum >= 8) {
|
|
423
|
+
this.align();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Write `len` bits from `src` bytes
|
|
428
|
+
bits(src: number[] | Uint8Array, len: number) {
|
|
429
|
+
for (let bit = 0; bit < len; bit++) {
|
|
430
|
+
this.bit((src[bit >> 3] & (1 << (bit & 7))) !== 0);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Write multiple bytes
|
|
435
|
+
bytes(src: number[] | Uint8Array) {
|
|
436
|
+
this.bits(src, 8 * src.length);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Push the current bit into the buffer
|
|
440
|
+
align() {
|
|
441
|
+
if (this.bitNum > 0) {
|
|
442
|
+
this.buffer.push(this.cur);
|
|
443
|
+
this.cur = 0;
|
|
444
|
+
this.bitNum = 0;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Write an int up to the potential max size
|
|
449
|
+
int(value: number, max: number) {
|
|
450
|
+
if (max < 2) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Invalid input (BitWriter) -- max (${max}) must be at least 2`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (value >= max) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`Invalid input (BitWriter) -- value (${value}) is larger than max (${max})`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let new_val = 0;
|
|
463
|
+
let mask = 1;
|
|
464
|
+
|
|
465
|
+
while (new_val + mask < max && mask !== 0) {
|
|
466
|
+
this.bit((value & mask) !== 0);
|
|
467
|
+
if ((value & mask) !== 0) {
|
|
468
|
+
new_val |= mask;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
mask <<= 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Write a packed unsigned int
|
|
476
|
+
uint_packed(value: number) {
|
|
477
|
+
do {
|
|
478
|
+
const src = value & 0b1111111;
|
|
479
|
+
value >>= 7;
|
|
480
|
+
this.bit(value !== 0);
|
|
481
|
+
this.bits([src], 7);
|
|
482
|
+
} while (value !== 0);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Write a packed integer
|
|
486
|
+
int_packed(value: number) {
|
|
487
|
+
this.uint_packed((Math.abs(value) << 1) | (value >= 0 ? 1 : 0));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Return built buffer
|
|
491
|
+
finish(): Uint8Array {
|
|
492
|
+
this.align();
|
|
493
|
+
return new Uint8Array(this.buffer);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Return built buffer (and include length)
|
|
497
|
+
finishSection() {
|
|
498
|
+
this.align();
|
|
499
|
+
return concat(write_i32(this.buffer.length), new Uint8Array(this.buffer));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// write a string
|
|
503
|
+
string(str: string) {
|
|
504
|
+
this.bytes(write_string(str));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
float(num: UnrealFloat) {
|
|
508
|
+
// create a float array
|
|
509
|
+
const floatArr = new Float32Array(1);
|
|
510
|
+
// assign the number
|
|
511
|
+
floatArr[0] = num;
|
|
512
|
+
// convert it into a byte array
|
|
513
|
+
const bytes = new Int8Array(floatArr.buffer);
|
|
514
|
+
this.bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// run a function with `this` as a BitReader
|
|
518
|
+
self(fn: (this: BitWriter) => void) {
|
|
519
|
+
fn.bind(this)();
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// write an array
|
|
524
|
+
array<T>(arr: T[], fn: (this: BitWriter, item: T, index: number) => void) {
|
|
525
|
+
this.bytes(write_i32(arr.length));
|
|
526
|
+
arr.forEach(fn.bind(this));
|
|
527
|
+
return this;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// write things from an array
|
|
531
|
+
each<T>(arr: T[], fn: (this: BitWriter, item: T, index: number) => void) {
|
|
532
|
+
arr.forEach(fn.bind(this));
|
|
533
|
+
return this;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// write unreal types
|
|
537
|
+
unreal(type: string, value: UnrealType) {
|
|
538
|
+
switch (type) {
|
|
539
|
+
case 'Class':
|
|
540
|
+
if (typeof value !== 'string') {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`writing unreal type Class, did not receive string (${value})`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
this.string(value);
|
|
546
|
+
return;
|
|
547
|
+
case 'Object':
|
|
548
|
+
if (typeof value !== 'string') {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`writing unreal type Object, did not receive string (${value})`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
this.string(value);
|
|
554
|
+
return;
|
|
555
|
+
case 'Boolean':
|
|
556
|
+
if (typeof value !== 'boolean') {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`writing unreal type Boolean, did not receive boolean (${value})`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
this.bytes(write_i32(value ? 1 : 0));
|
|
562
|
+
return;
|
|
563
|
+
case 'Float':
|
|
564
|
+
if (typeof value !== 'number') {
|
|
565
|
+
throw new Error(
|
|
566
|
+
`writing unreal type Float, did not receive float (${value})`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
this.float(value);
|
|
570
|
+
return;
|
|
571
|
+
case 'Byte':
|
|
572
|
+
if (typeof value !== 'number') {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`writing unreal type Byte, did not receive Byte (${value})`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
this.bytes([value & 255]);
|
|
578
|
+
return;
|
|
579
|
+
case 'Color':
|
|
580
|
+
if (!Array.isArray(value) || value.length !== 4) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
`writing unreal type Array, did not receive Array (${value})`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
this.bytes(bgra(value));
|
|
586
|
+
return;
|
|
587
|
+
case 'Rotator':
|
|
588
|
+
if (!Array.isArray(value) || value.length !== 3) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`writing unreal type Array, did not receive Array (${value})`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
this.float(value[0]);
|
|
595
|
+
this.float(value[1]);
|
|
596
|
+
this.float(value[2]);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
throw new Error('Unknown unreal type ' + type);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// concat uint8arrays together
|
|
604
|
+
export function concat(...arrays: Uint8Array[]): Uint8Array {
|
|
605
|
+
const buffLen = arrays.reduce((sum, value) => sum + value.length, 0);
|
|
606
|
+
const buff = new Uint8Array(buffLen);
|
|
607
|
+
|
|
608
|
+
// for each array - copy it over buff
|
|
609
|
+
// next array is copied right after the previous one
|
|
610
|
+
let length = 0;
|
|
611
|
+
for (const array of arrays) {
|
|
612
|
+
buff.set(array, length);
|
|
613
|
+
length += array.length;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return buff;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export const read = {
|
|
620
|
+
bytes: subarray,
|
|
621
|
+
u16: read_u16,
|
|
622
|
+
i32: read_i32,
|
|
623
|
+
compressed: read_compressed,
|
|
624
|
+
string: read_string,
|
|
625
|
+
uuid: read_uuid,
|
|
626
|
+
array: read_array,
|
|
627
|
+
each: read_each,
|
|
628
|
+
bits: (data: Bytes) => new BitReader(data),
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
export const write = {
|
|
632
|
+
u16: write_u16,
|
|
633
|
+
i32: write_i32,
|
|
634
|
+
compressed: write_compressed,
|
|
635
|
+
uncompressed: write_uncompressed,
|
|
636
|
+
string: write_string,
|
|
637
|
+
uuid: write_uuid,
|
|
638
|
+
array: write_array,
|
|
639
|
+
bits: () => new BitWriter(),
|
|
640
|
+
};
|
package/src/uuid.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// this code is modified from https://github.com/uuidjs/uuid
|
|
2
|
+
|
|
3
|
+
import { Uuid } from './types';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The MIT License (MIT)
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
15
|
+
*/
|
|
16
|
+
const hexTable: string[] = Array.from({ length: 256 }).map((_, i) =>
|
|
17
|
+
(i + 0x100).toString(16).substr(1)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// stringify uuid
|
|
21
|
+
export function uuidStringify(arr: number[]): Uuid {
|
|
22
|
+
return (
|
|
23
|
+
hexTable[arr[0]] +
|
|
24
|
+
hexTable[arr[1]] +
|
|
25
|
+
hexTable[arr[2]] +
|
|
26
|
+
hexTable[arr[3]] +
|
|
27
|
+
'-' +
|
|
28
|
+
hexTable[arr[4]] +
|
|
29
|
+
hexTable[arr[5]] +
|
|
30
|
+
'-' +
|
|
31
|
+
hexTable[arr[6]] +
|
|
32
|
+
hexTable[arr[7]] +
|
|
33
|
+
'-' +
|
|
34
|
+
hexTable[arr[8]] +
|
|
35
|
+
hexTable[arr[9]] +
|
|
36
|
+
'-' +
|
|
37
|
+
hexTable[arr[10]] +
|
|
38
|
+
hexTable[arr[11]] +
|
|
39
|
+
hexTable[arr[12]] +
|
|
40
|
+
hexTable[arr[13]] +
|
|
41
|
+
hexTable[arr[14]] +
|
|
42
|
+
hexTable[arr[15]]
|
|
43
|
+
).toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function uuidParse(uuid: Uuid): Uint8Array {
|
|
47
|
+
let v;
|
|
48
|
+
const arr = new Uint8Array(16);
|
|
49
|
+
|
|
50
|
+
// Parse ########-....-....-....-............
|
|
51
|
+
arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
|
|
52
|
+
arr[1] = (v >>> 16) & 0xff;
|
|
53
|
+
arr[2] = (v >>> 8) & 0xff;
|
|
54
|
+
arr[3] = v & 0xff;
|
|
55
|
+
|
|
56
|
+
// Parse ........-####-....-....-............
|
|
57
|
+
arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
|
|
58
|
+
arr[5] = v & 0xff;
|
|
59
|
+
|
|
60
|
+
// Parse ........-....-####-....-............
|
|
61
|
+
arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
|
|
62
|
+
arr[7] = v & 0xff;
|
|
63
|
+
|
|
64
|
+
// Parse ........-....-....-####-............
|
|
65
|
+
arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
|
|
66
|
+
arr[9] = v & 0xff;
|
|
67
|
+
|
|
68
|
+
// Parse ........-....-....-....-############
|
|
69
|
+
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
|
|
70
|
+
arr[10] = ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
|
|
71
|
+
arr[11] = (v / 0x100000000) & 0xff;
|
|
72
|
+
arr[12] = (v >>> 24) & 0xff;
|
|
73
|
+
arr[13] = (v >>> 16) & 0xff;
|
|
74
|
+
arr[14] = (v >>> 8) & 0xff;
|
|
75
|
+
arr[15] = v & 0xff;
|
|
76
|
+
|
|
77
|
+
return arr;
|
|
78
|
+
}
|