brs-js 1.4.1 → 2.0.2
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/.prettierrc +7 -0
- package/LICENSE +1 -1
- package/README.md +2 -2
- 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/{src/write.js → debug/evilwrite.js} +218 -31
- 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 +23 -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 +170 -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 +17 -14
- package/src/constants.ts +6 -0
- package/src/{index.js → index.ts} +13 -1
- 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 +240 -0
- package/src/{utils.js → utils.ts} +241 -181
- package/src/{uuid.js → uuid.ts} +9 -5
- package/src/write.ts +209 -0
- package/test/lib.test.js +31 -23
- package/test/utils.test.js +50 -15
- package/tsconfig.json +22 -0
- package/webpack.config.js +51 -30
- package/src/constants.js +0 -4
- package/src/read.js +0 -206
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import punycode from 'punycode';
|
|
3
|
-
import {
|
|
4
|
-
uuidStringify,
|
|
5
|
-
uuidParse,
|
|
6
|
-
} from './uuid';
|
|
1
|
+
import { deflate, inflate } from 'pako';
|
|
7
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';
|
|
8
12
|
|
|
9
13
|
/*
|
|
10
14
|
Notes:
|
|
@@ -16,66 +20,87 @@ import { MAX_INT } from './constants';
|
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
// Determine if a string is ascii-only
|
|
19
|
-
function isASCII(
|
|
20
|
-
|
|
23
|
+
function isASCII(text: string): boolean {
|
|
24
|
+
return /^[\x00-\x7F]*$/.test(text);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
// convert BGRA color to RGBA color
|
|
24
|
-
const bgra = ([b, g, r, a])
|
|
28
|
+
export const bgra = ([b, g, r, a]: number[]): [
|
|
29
|
+
number,
|
|
30
|
+
number,
|
|
31
|
+
number,
|
|
32
|
+
number
|
|
33
|
+
] => [r, g, b, a];
|
|
25
34
|
|
|
26
35
|
// Compare equality of byte arrays
|
|
27
|
-
export function isEqual(arrA
|
|
28
|
-
return arrA.every((a, i) => arrB[i] === a);
|
|
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);
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
function isBRSBytes(data: Bytes): data is BRSBytes {
|
|
41
|
+
return (data as BRSBytes).brsOffset !== undefined;
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
// read `len` bytes and return slice while updating offset
|
|
33
|
-
function
|
|
45
|
+
export function subarray(data: Bytes, len: number, isCopy = false): Uint8Array {
|
|
34
46
|
if (!(data instanceof Uint8Array)) {
|
|
35
47
|
throw new Error(`Invalid data type in bytes reader (${typeof data})`);
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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;
|
|
42
63
|
return chunk;
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
// break a byte array into chunks of a specified size
|
|
46
|
-
function chunk(arr, size) {
|
|
47
|
-
|
|
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);
|
|
48
70
|
|
|
49
|
-
|
|
50
|
-
|
|
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);
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
return out;
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
// Read a u16 from a byte array
|
|
57
|
-
function read_u16(data, littleEndian=true) {
|
|
58
|
-
const [a, b] =
|
|
82
|
+
function read_u16(data: Bytes, littleEndian = true): number {
|
|
83
|
+
const [a, b] = subarray(data, 2);
|
|
59
84
|
|
|
60
|
-
return littleEndian ? (b << 8 | a
|
|
85
|
+
return littleEndian ? (b << 8) | a : (a << 8) | b;
|
|
61
86
|
}
|
|
62
87
|
|
|
63
88
|
// Write a u16 into byte array
|
|
64
|
-
function write_u16(num, littleEndian=true) {
|
|
89
|
+
function write_u16(num: number, littleEndian = true): Uint8Array {
|
|
65
90
|
const data = [num & 255, (num >> 8) & 255];
|
|
66
91
|
return new Uint8Array(!littleEndian ? data.reverse() : data);
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
// Read an i32 from a byte array
|
|
70
|
-
function read_i32(data, littleEndian=true) {
|
|
71
|
-
const [a, b, c, d] =
|
|
95
|
+
function read_i32(data: Bytes, littleEndian = true): number {
|
|
96
|
+
const [a, b, c, d] = subarray(data, 4);
|
|
72
97
|
return littleEndian
|
|
73
|
-
? (d << 24 | c << 16 | b << 8 | a
|
|
74
|
-
: (a << 24 | b << 16 | c << 8 | d
|
|
98
|
+
? (d << 24) | (c << 16) | (b << 8) | a
|
|
99
|
+
: (a << 24) | (b << 16) | (c << 8) | d;
|
|
75
100
|
}
|
|
76
101
|
|
|
77
102
|
// Write an i32 from a byte array
|
|
78
|
-
function write_i32(num, littleEndian=true) {
|
|
103
|
+
function write_i32(num: number, littleEndian = true): Uint8Array {
|
|
79
104
|
const data = new Uint8Array([
|
|
80
105
|
num & 255,
|
|
81
106
|
(num >> 8) & 255,
|
|
@@ -87,32 +112,47 @@ function write_i32(num, littleEndian=true) {
|
|
|
87
112
|
}
|
|
88
113
|
|
|
89
114
|
// Decompress a byte array of compressed data
|
|
90
|
-
function read_compressed(data) {
|
|
115
|
+
function read_compressed(data: Bytes): Bytes {
|
|
91
116
|
const uncompressedSize = read_i32(data);
|
|
92
117
|
const compressedSize = read_i32(data);
|
|
93
118
|
|
|
94
119
|
// Throw error for weird compression/uncompression sizes
|
|
95
|
-
if (
|
|
96
|
-
|
|
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
|
+
);
|
|
97
128
|
}
|
|
98
129
|
|
|
99
130
|
// No compressed data? Return those bytes
|
|
100
131
|
if (compressedSize === 0) {
|
|
101
|
-
return
|
|
132
|
+
return subarray(data, uncompressedSize);
|
|
102
133
|
} else {
|
|
103
134
|
// Decompress the data otherwise
|
|
104
|
-
const compressed =
|
|
105
|
-
return
|
|
135
|
+
const compressed = subarray(data, compressedSize);
|
|
136
|
+
return inflate(compressed);
|
|
106
137
|
}
|
|
107
138
|
}
|
|
108
139
|
|
|
109
140
|
// Compress a byte array into fewer bytes
|
|
110
|
-
function
|
|
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 {
|
|
111
151
|
// Concat the args to one massive array
|
|
112
152
|
const data = concat(...args);
|
|
113
153
|
|
|
114
154
|
// Do the compression
|
|
115
|
-
const compressed =
|
|
155
|
+
const compressed = deflate(data);
|
|
116
156
|
const uncompressedSize = data.length;
|
|
117
157
|
const compressedSize = compressed.length;
|
|
118
158
|
|
|
@@ -121,7 +161,7 @@ function write_compressed(...args) {
|
|
|
121
161
|
}
|
|
122
162
|
|
|
123
163
|
if (compressedSize > MAX_INT) {
|
|
124
|
-
throw new Error(`compressedSize (${
|
|
164
|
+
throw new Error(`compressedSize (${compressedSize}) out of range`);
|
|
125
165
|
}
|
|
126
166
|
|
|
127
167
|
// Determine if compression increases size
|
|
@@ -131,12 +171,12 @@ function write_compressed(...args) {
|
|
|
131
171
|
return concat(
|
|
132
172
|
write_i32(uncompressedSize),
|
|
133
173
|
write_i32(badCompress ? 0 : compressedSize),
|
|
134
|
-
badCompress ? data : compressed
|
|
174
|
+
badCompress ? data : compressed
|
|
135
175
|
);
|
|
136
176
|
}
|
|
137
177
|
|
|
138
178
|
// Read a string from a byte array
|
|
139
|
-
function read_string(data) {
|
|
179
|
+
function read_string(data: Bytes): string {
|
|
140
180
|
const raw_size = read_i32(data);
|
|
141
181
|
const is_ucs2 = raw_size < 0;
|
|
142
182
|
const size = is_ucs2 ? -raw_size : raw_size;
|
|
@@ -148,14 +188,13 @@ function read_string(data) {
|
|
|
148
188
|
}
|
|
149
189
|
|
|
150
190
|
// Create ucs2 encoded string
|
|
151
|
-
return
|
|
191
|
+
return String.fromCodePoint(
|
|
152
192
|
// Read the data in 2 byte windows
|
|
153
|
-
chunk(
|
|
154
|
-
.map(arr => read_u16(arr)) // Convert the two bytes into u16
|
|
193
|
+
...chunk(subarray(data, size), 2).map(arr => read_u16(arr)) // Convert the two bytes into u16
|
|
155
194
|
);
|
|
156
195
|
} else {
|
|
157
196
|
// Read the data, remove the \u0000 at the end :)
|
|
158
|
-
const strData =
|
|
197
|
+
const strData = subarray(data, size).subarray(0, -1);
|
|
159
198
|
|
|
160
199
|
// Convert into ascii
|
|
161
200
|
return String.fromCharCode.apply(null, strData);
|
|
@@ -163,46 +202,48 @@ function read_string(data) {
|
|
|
163
202
|
}
|
|
164
203
|
|
|
165
204
|
// Write a string to bytes
|
|
166
|
-
function write_string(str) {
|
|
205
|
+
function write_string(str: string): Uint8Array {
|
|
167
206
|
if (isASCII(str)) {
|
|
168
207
|
return concat(
|
|
169
208
|
write_i32(str.length + 1), // Write string length (+ null term)
|
|
170
209
|
new Uint8Array(str.split('').map(s => s.charCodeAt(0))), // Write string as bytes
|
|
171
|
-
new Uint8Array([0])
|
|
210
|
+
new Uint8Array([0]) // Null terminator
|
|
172
211
|
);
|
|
173
212
|
} else {
|
|
174
213
|
// ucs2 strings denoted by negative length
|
|
175
214
|
const len = -((str.length + 1) * 2);
|
|
176
215
|
return concat(
|
|
177
216
|
write_i32(len), // write length
|
|
178
|
-
|
|
179
|
-
new Uint8Array(
|
|
180
|
-
|
|
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
|
+
);
|
|
181
225
|
}
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
// Read uuid from 4 LE ints
|
|
185
|
-
function read_uuid(data) {
|
|
186
|
-
return uuidStringify(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
})); // each chunk is LE
|
|
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
|
+
);
|
|
191
234
|
}
|
|
192
235
|
|
|
193
236
|
// parse a uuid into 4 LE ints
|
|
194
|
-
function write_uuid(uuid) {
|
|
237
|
+
function write_uuid(uuid: Uuid) {
|
|
195
238
|
return concat(
|
|
196
|
-
...chunk(uuidParse(uuid), 4)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return new Uint8Array(arr);
|
|
200
|
-
})
|
|
239
|
+
...chunk(uuidParse(uuid), 4).map(
|
|
240
|
+
([a, b, c, d]) => new Uint8Array([d, c, b, a])
|
|
241
|
+
)
|
|
201
242
|
);
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
// Read an array of things given a fn
|
|
205
|
-
function read_array(data, fn) {
|
|
246
|
+
function read_array<T>(data: Bytes, fn: (_: Bytes) => T): T[] {
|
|
206
247
|
const length = read_i32(data);
|
|
207
248
|
const arr = Array(length);
|
|
208
249
|
for (let i = 0; i < length; i++) {
|
|
@@ -212,7 +253,7 @@ function read_array(data, fn) {
|
|
|
212
253
|
}
|
|
213
254
|
|
|
214
255
|
// iterate an array of things given a fn
|
|
215
|
-
function read_each(data, fn) {
|
|
256
|
+
function read_each(data: Bytes, fn: (_: Bytes) => void) {
|
|
216
257
|
const length = read_i32(data);
|
|
217
258
|
for (let i = 0; i < length; i++) {
|
|
218
259
|
fn(data);
|
|
@@ -220,26 +261,25 @@ function read_each(data, fn) {
|
|
|
220
261
|
}
|
|
221
262
|
|
|
222
263
|
// Write an array of things to bytes
|
|
223
|
-
function write_array(arr, fn) {
|
|
224
|
-
return concat(
|
|
225
|
-
write_i32(arr.length),
|
|
226
|
-
...arr.map(o => fn(o))
|
|
227
|
-
);
|
|
264
|
+
function write_array<T>(arr: T[], fn: (_: T) => Uint8Array) {
|
|
265
|
+
return concat(write_i32(arr.length), ...arr.map(o => fn(o)));
|
|
228
266
|
}
|
|
229
267
|
|
|
230
268
|
// Tool for reading byte arrays 1 bit at a time
|
|
231
269
|
class BitReader {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
270
|
+
buffer: Uint8Array;
|
|
271
|
+
pos: number = 0;
|
|
272
|
+
|
|
273
|
+
constructor(data: Uint8Array) {
|
|
274
|
+
this.buffer = data;
|
|
235
275
|
}
|
|
236
276
|
|
|
237
|
-
empty() {
|
|
277
|
+
empty(): boolean {
|
|
238
278
|
return this.pos >= this.buffer.length * 8;
|
|
239
279
|
}
|
|
240
280
|
|
|
241
281
|
// Read one bit as a boolean
|
|
242
|
-
bit() {
|
|
282
|
+
bit(): boolean {
|
|
243
283
|
const bit = (this.buffer[this.pos >> 3] & (1 << (this.pos & 0b111))) !== 0;
|
|
244
284
|
this.pos++;
|
|
245
285
|
return bit;
|
|
@@ -251,7 +291,7 @@ class BitReader {
|
|
|
251
291
|
}
|
|
252
292
|
|
|
253
293
|
// read an int up to max
|
|
254
|
-
int(max) {
|
|
294
|
+
int(max: number): number {
|
|
255
295
|
let value = 0;
|
|
256
296
|
let mask = 1;
|
|
257
297
|
|
|
@@ -266,7 +306,7 @@ class BitReader {
|
|
|
266
306
|
}
|
|
267
307
|
|
|
268
308
|
// read a packet in from bits
|
|
269
|
-
uint_packed() {
|
|
309
|
+
uint_packed(): number {
|
|
270
310
|
let value = 0;
|
|
271
311
|
for (let i = 0; i < 5; i++) {
|
|
272
312
|
const next = this.bit();
|
|
@@ -285,58 +325,59 @@ class BitReader {
|
|
|
285
325
|
}
|
|
286
326
|
|
|
287
327
|
// an item in a read_positive_int_vector array
|
|
288
|
-
int_packed() {
|
|
328
|
+
int_packed(): number {
|
|
289
329
|
const value = this.uint_packed();
|
|
290
|
-
return (value >> 1) * (value & 1 !== 0 ? 1 : -1);
|
|
330
|
+
return (value >> 1) * ((value & 1) !== 0 ? 1 : -1);
|
|
291
331
|
}
|
|
292
332
|
|
|
293
333
|
// read some bits
|
|
294
|
-
bits(num) {
|
|
295
|
-
const arr = [];
|
|
334
|
+
bits(num: number): number[] {
|
|
335
|
+
const arr: number[] = [];
|
|
296
336
|
for (let bit = 0; bit < num; bit++) {
|
|
297
337
|
const shift = bit & 7;
|
|
298
|
-
arr[bit >> 3] =
|
|
338
|
+
arr[bit >> 3] =
|
|
339
|
+
(arr[bit >> 3] & ~(1 << shift)) | ((this.bit() ? 1 : 0) << shift);
|
|
299
340
|
}
|
|
300
341
|
return arr;
|
|
301
342
|
}
|
|
302
343
|
|
|
303
344
|
// Read some bytes
|
|
304
|
-
bytes(num) {
|
|
305
|
-
return new Uint8Array(this.
|
|
345
|
+
bytes(num: number): Uint8Array {
|
|
346
|
+
return new Uint8Array(this.bytesArr(num));
|
|
306
347
|
}
|
|
307
348
|
|
|
308
349
|
// read some bytes but not as a Uint8Array
|
|
309
|
-
bytesArr(num) {
|
|
350
|
+
bytesArr(num: number): number[] {
|
|
310
351
|
return this.bits(num * 8);
|
|
311
352
|
}
|
|
312
353
|
|
|
313
354
|
// read an array
|
|
314
|
-
array(fn) {
|
|
355
|
+
array<T>(fn: (_: BitReader) => T): T[] {
|
|
315
356
|
const length = read_i32(this.bytes(4));
|
|
316
357
|
const arr = Array(length);
|
|
317
|
-
for(let i = 0; i < length; i++) {
|
|
358
|
+
for (let i = 0; i < length; i++) {
|
|
318
359
|
arr[i] = fn(this);
|
|
319
360
|
}
|
|
320
361
|
return arr;
|
|
321
362
|
}
|
|
322
363
|
|
|
323
364
|
// for each
|
|
324
|
-
each(fn) {
|
|
365
|
+
each(fn: (data: BitReader) => void) {
|
|
325
366
|
const length = read_i32(this.bytes(4));
|
|
326
|
-
for(let i = 0; i < length; i++) {
|
|
367
|
+
for (let i = 0; i < length; i++) {
|
|
327
368
|
fn(this);
|
|
328
369
|
}
|
|
329
370
|
}
|
|
330
371
|
|
|
331
372
|
// read a string
|
|
332
|
-
string() {
|
|
373
|
+
string(): string {
|
|
333
374
|
const lenBytes = this.bytesArr(4);
|
|
334
375
|
const len = read_i32(new Uint8Array(lenBytes));
|
|
335
376
|
return read_string(new Uint8Array(lenBytes.concat(this.bytesArr(len))));
|
|
336
377
|
}
|
|
337
378
|
|
|
338
379
|
// read a 32-bit float
|
|
339
|
-
float() {
|
|
380
|
+
float(): number {
|
|
340
381
|
const view = new DataView(new ArrayBuffer(4));
|
|
341
382
|
|
|
342
383
|
// Write the ints to it
|
|
@@ -349,35 +390,33 @@ class BitReader {
|
|
|
349
390
|
}
|
|
350
391
|
|
|
351
392
|
// read unreal types
|
|
352
|
-
unreal(type) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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()];
|
|
366
408
|
}
|
|
367
409
|
throw new Error('Unknown unreal type ' + type);
|
|
368
410
|
}
|
|
369
411
|
}
|
|
370
412
|
|
|
371
413
|
class BitWriter {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this.bitNum = 0;
|
|
376
|
-
|
|
377
|
-
}
|
|
414
|
+
buffer: number[] = [];
|
|
415
|
+
cur: number = 0;
|
|
416
|
+
bitNum: number = 0;
|
|
378
417
|
|
|
379
418
|
// Write a boolean as a bit
|
|
380
|
-
bit(val) {
|
|
419
|
+
bit(val: boolean) {
|
|
381
420
|
this.cur |= (val ? 1 : 0) << this.bitNum;
|
|
382
421
|
this.bitNum++;
|
|
383
422
|
if (this.bitNum >= 8) {
|
|
@@ -386,14 +425,14 @@ class BitWriter {
|
|
|
386
425
|
}
|
|
387
426
|
|
|
388
427
|
// Write `len` bits from `src` bytes
|
|
389
|
-
bits(src, len) {
|
|
428
|
+
bits(src: number[] | Uint8Array, len: number) {
|
|
390
429
|
for (let bit = 0; bit < len; bit++) {
|
|
391
430
|
this.bit((src[bit >> 3] & (1 << (bit & 7))) !== 0);
|
|
392
431
|
}
|
|
393
432
|
}
|
|
394
433
|
|
|
395
434
|
// Write multiple bytes
|
|
396
|
-
bytes(src) {
|
|
435
|
+
bytes(src: number[] | Uint8Array) {
|
|
397
436
|
this.bits(src, 8 * src.length);
|
|
398
437
|
}
|
|
399
438
|
|
|
@@ -407,19 +446,23 @@ class BitWriter {
|
|
|
407
446
|
}
|
|
408
447
|
|
|
409
448
|
// Write an int up to the potential max size
|
|
410
|
-
int(value, max) {
|
|
449
|
+
int(value: number, max: number) {
|
|
411
450
|
if (max < 2) {
|
|
412
|
-
throw new Error(
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Invalid input (BitWriter) -- max (${max}) must be at least 2`
|
|
453
|
+
);
|
|
413
454
|
}
|
|
414
455
|
|
|
415
456
|
if (value >= max) {
|
|
416
|
-
throw new Error(
|
|
457
|
+
throw new Error(
|
|
458
|
+
`Invalid input (BitWriter) -- value (${value}) is larger than max (${max})`
|
|
459
|
+
);
|
|
417
460
|
}
|
|
418
461
|
|
|
419
462
|
let new_val = 0;
|
|
420
463
|
let mask = 1;
|
|
421
464
|
|
|
422
|
-
while (
|
|
465
|
+
while (new_val + mask < max && mask !== 0) {
|
|
423
466
|
this.bit((value & mask) !== 0);
|
|
424
467
|
if ((value & mask) !== 0) {
|
|
425
468
|
new_val |= mask;
|
|
@@ -430,7 +473,7 @@ class BitWriter {
|
|
|
430
473
|
}
|
|
431
474
|
|
|
432
475
|
// Write a packed unsigned int
|
|
433
|
-
uint_packed(value) {
|
|
476
|
+
uint_packed(value: number) {
|
|
434
477
|
do {
|
|
435
478
|
const src = value & 0b1111111;
|
|
436
479
|
value >>= 7;
|
|
@@ -440,26 +483,28 @@ class BitWriter {
|
|
|
440
483
|
}
|
|
441
484
|
|
|
442
485
|
// Write a packed integer
|
|
443
|
-
int_packed(value) {
|
|
486
|
+
int_packed(value: number) {
|
|
444
487
|
this.uint_packed((Math.abs(value) << 1) | (value >= 0 ? 1 : 0));
|
|
445
488
|
}
|
|
446
489
|
|
|
447
490
|
// Return built buffer
|
|
448
|
-
finish() {
|
|
491
|
+
finish(): Uint8Array {
|
|
449
492
|
this.align();
|
|
450
|
-
return this.buffer;
|
|
493
|
+
return new Uint8Array(this.buffer);
|
|
451
494
|
}
|
|
452
495
|
|
|
453
496
|
// Return built buffer (and include length)
|
|
454
497
|
finishSection() {
|
|
455
498
|
this.align();
|
|
456
|
-
return concat(write_i32(this.buffer.length), this.buffer);
|
|
499
|
+
return concat(write_i32(this.buffer.length), new Uint8Array(this.buffer));
|
|
457
500
|
}
|
|
458
501
|
|
|
459
502
|
// write a string
|
|
460
|
-
string(str) {
|
|
503
|
+
string(str: string) {
|
|
504
|
+
this.bytes(write_string(str));
|
|
505
|
+
}
|
|
461
506
|
|
|
462
|
-
float(num) {
|
|
507
|
+
float(num: UnrealFloat) {
|
|
463
508
|
// create a float array
|
|
464
509
|
const floatArr = new Float32Array(1);
|
|
465
510
|
// assign the number
|
|
@@ -469,96 +514,110 @@ class BitWriter {
|
|
|
469
514
|
this.bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
|
|
470
515
|
}
|
|
471
516
|
|
|
472
|
-
//
|
|
473
|
-
self(fn) {
|
|
517
|
+
// run a function with `this` as a BitReader
|
|
518
|
+
self(fn: (this: BitWriter) => void) {
|
|
474
519
|
fn.bind(this)();
|
|
475
520
|
return this;
|
|
476
521
|
}
|
|
477
522
|
|
|
478
523
|
// write an array
|
|
479
|
-
array(arr, fn) {
|
|
524
|
+
array<T>(arr: T[], fn: (this: BitWriter, item: T, index: number) => void) {
|
|
480
525
|
this.bytes(write_i32(arr.length));
|
|
481
526
|
arr.forEach(fn.bind(this));
|
|
482
527
|
return this;
|
|
483
528
|
}
|
|
484
529
|
|
|
485
530
|
// write things from an array
|
|
486
|
-
each(arr, fn) {
|
|
531
|
+
each<T>(arr: T[], fn: (this: BitWriter, item: T, index: number) => void) {
|
|
487
532
|
arr.forEach(fn.bind(this));
|
|
488
533
|
return this;
|
|
489
534
|
}
|
|
490
535
|
|
|
491
536
|
// write unreal types
|
|
492
|
-
unreal(type, value) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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;
|
|
539
598
|
}
|
|
540
599
|
throw new Error('Unknown unreal type ' + type);
|
|
541
600
|
}
|
|
542
601
|
}
|
|
543
602
|
|
|
544
603
|
// concat uint8arrays together
|
|
545
|
-
export function concat(...arrays) {
|
|
604
|
+
export function concat(...arrays: Uint8Array[]): Uint8Array {
|
|
546
605
|
const buffLen = arrays.reduce((sum, value) => sum + value.length, 0);
|
|
547
606
|
const buff = new Uint8Array(buffLen);
|
|
548
607
|
|
|
549
608
|
// for each array - copy it over buff
|
|
550
609
|
// next array is copied right after the previous one
|
|
551
610
|
let length = 0;
|
|
552
|
-
for(const array of arrays) {
|
|
611
|
+
for (const array of arrays) {
|
|
553
612
|
buff.set(array, length);
|
|
554
613
|
length += array.length;
|
|
555
614
|
}
|
|
556
615
|
|
|
557
616
|
return buff;
|
|
558
|
-
}
|
|
617
|
+
}
|
|
559
618
|
|
|
560
619
|
export const read = {
|
|
561
|
-
bytes,
|
|
620
|
+
bytes: subarray,
|
|
562
621
|
u16: read_u16,
|
|
563
622
|
i32: read_i32,
|
|
564
623
|
compressed: read_compressed,
|
|
@@ -566,15 +625,16 @@ export const read = {
|
|
|
566
625
|
uuid: read_uuid,
|
|
567
626
|
array: read_array,
|
|
568
627
|
each: read_each,
|
|
569
|
-
bits: data => new BitReader(data),
|
|
628
|
+
bits: (data: Bytes) => new BitReader(data),
|
|
570
629
|
};
|
|
571
630
|
|
|
572
631
|
export const write = {
|
|
573
632
|
u16: write_u16,
|
|
574
633
|
i32: write_i32,
|
|
575
634
|
compressed: write_compressed,
|
|
635
|
+
uncompressed: write_uncompressed,
|
|
576
636
|
string: write_string,
|
|
577
637
|
uuid: write_uuid,
|
|
578
638
|
array: write_array,
|
|
579
|
-
bits:
|
|
639
|
+
bits: () => new BitWriter(),
|
|
580
640
|
};
|