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