brs-js 1.4.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/.prettierrc +7 -0
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/dist.js +1 -1
- package/dist/dist.node.js +1 -1
- package/dist/dist.web.js +1 -1
- package/package.json +17 -14
- package/tsconfig.json +22 -0
- package/webpack.config.js +51 -30
- package/src/constants.js +0 -4
- package/src/index.js +0 -12
- package/src/read.js +0 -206
- package/src/utils.js +0 -580
- package/src/uuid.js +0 -74
- package/src/write.js +0 -203
- package/test/lib.test.js +0 -43
- package/test/utils.test.js +0 -174
package/src/write.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { MAGIC, LATEST_VERSION, MAX_INT } from './constants';
|
|
2
|
-
import { write, isEqual, concat } from './utils';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// TODO: Validate input saves
|
|
6
|
-
function validate(save) {
|
|
7
|
-
if (typeof save !== 'object') {
|
|
8
|
-
throw new Error('Expected save to be an object');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (typeof save.bricks !== 'object' && save.bricks.length) {
|
|
12
|
-
throw new Error('Expected save to have bricks field');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!save.bricks.every(b => typeof b.size === 'object' && typeof b.position === 'object'))
|
|
16
|
-
throw new Error('Expected every brick to have size and position arrays')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// looks up a value in an object or returns a defualt value
|
|
20
|
-
function get(obj, path='', def) {
|
|
21
|
-
// Split the path up by .
|
|
22
|
-
path = path.split('.').filter(p => p.length > 0);
|
|
23
|
-
|
|
24
|
-
// Get the child at each part of the path
|
|
25
|
-
while (path.length && typeof obj === 'object') {
|
|
26
|
-
obj = obj[path.splice(0, 1)[0]];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return typeof obj !== 'undefined' ? obj : def;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const EMPTY_ARR = new Uint8Array([]);
|
|
33
|
-
|
|
34
|
-
export default function writeBrs(save) {
|
|
35
|
-
validate(save);
|
|
36
|
-
|
|
37
|
-
if(save.bricks.length > MAX_INT) {
|
|
38
|
-
throw new Error('Brick count out of range');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const version = save.version === 9 ? 9 : LATEST_VERSION;
|
|
42
|
-
|
|
43
|
-
// Convert from BGRA to RGBA
|
|
44
|
-
const rgba = ([r, g, b, a]) => new Uint8Array([b, g, r, a]);
|
|
45
|
-
|
|
46
|
-
// stored brick indices from components on the bricks
|
|
47
|
-
const componentBricks = {};
|
|
48
|
-
|
|
49
|
-
const numColors = Math.max(get(save, 'colors', EMPTY_ARR).length, 2);
|
|
50
|
-
const numAssets = Math.max(get(save, 'brick_assets', EMPTY_ARR).length, 2);
|
|
51
|
-
const numMats = Math.max(get(save, 'materials.length', 0), 2);
|
|
52
|
-
const numPhysMats = Math.max(get(save, 'physical_materials.length', 0), 2);
|
|
53
|
-
|
|
54
|
-
const buff = concat(
|
|
55
|
-
// Write magic bytes
|
|
56
|
-
MAGIC,
|
|
57
|
-
write.u16(version),
|
|
58
|
-
|
|
59
|
-
version >= 8 ? write.i32(save.gameVersion || 0) : EMPTY_ARR,
|
|
60
|
-
|
|
61
|
-
// Header 1
|
|
62
|
-
write.compressed(
|
|
63
|
-
write.string(get(save, 'map', 'Unknown')),
|
|
64
|
-
write.string(get(save, 'author.name', 'Unknown')),
|
|
65
|
-
write.string(get(save, 'description', '')),
|
|
66
|
-
write.uuid(get(save, 'author.id', '00000000-0000-0000-0000-000000000000')),
|
|
67
|
-
version >= 8 ? concat(
|
|
68
|
-
write.string(get(save ,'host.name', 'Unknown')),
|
|
69
|
-
write.uuid(get(save, 'host.id', '00000000-0000-0000-0000-000000000000')),
|
|
70
|
-
) : EMPTY_ARR,
|
|
71
|
-
get(save, 'save_time', new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])),
|
|
72
|
-
write.i32(get(save, 'bricks', EMPTY_ARR).length),
|
|
73
|
-
),
|
|
74
|
-
|
|
75
|
-
// Header 2
|
|
76
|
-
write.compressed(
|
|
77
|
-
write.array(get(save, 'mods', EMPTY_ARR), write.string),
|
|
78
|
-
write.array(get(save, 'brick_assets', ['PB_DefaultBrick']), write.string),
|
|
79
|
-
write.array(get(save, 'colors', EMPTY_ARR), rgba),
|
|
80
|
-
write.array(get(save, 'materials', ['BMC_Plastic']), write.string),
|
|
81
|
-
write.array(get(save, 'brick_owners', [{}]), ({ id='00000000-0000-0000-0000-000000000000', name='Unknown', bricks=0 }={}) => concat(
|
|
82
|
-
write.uuid(id),
|
|
83
|
-
write.string(name),
|
|
84
|
-
version >= 8 ? write.i32(bricks) : EMPTY_ARR,
|
|
85
|
-
)),
|
|
86
|
-
version >= 9
|
|
87
|
-
? write.array(get(save, 'physical_materials', ['BPMC_Default']), write.string)
|
|
88
|
-
: [],
|
|
89
|
-
),
|
|
90
|
-
|
|
91
|
-
// write the save preview if it exists
|
|
92
|
-
version >= 8
|
|
93
|
-
? concat(
|
|
94
|
-
new Uint8Array([save.preview ? 1 : 0]),
|
|
95
|
-
save.preview ? write.i32(get(save, 'preview.length', 0)) : EMPTY_ARR, // <- Sorry @Uxie https://i.imgur.com/hSRxdbf.png
|
|
96
|
-
get(save, 'preview', EMPTY_ARR),
|
|
97
|
-
)
|
|
98
|
-
: EMPTY_ARR,
|
|
99
|
-
|
|
100
|
-
// Bricks
|
|
101
|
-
write.compressed(write.bits()
|
|
102
|
-
.each(save.bricks, function(brick, i) {
|
|
103
|
-
this.align();
|
|
104
|
-
this.int(get(brick, 'asset_name_index', 0), numAssets);
|
|
105
|
-
|
|
106
|
-
const isSingularity = isEqual(brick.size, [0, 0, 0]);
|
|
107
|
-
this.bit(!isSingularity);
|
|
108
|
-
if (!isSingularity) {
|
|
109
|
-
brick.size.map(s => this.uint_packed(s));
|
|
110
|
-
}
|
|
111
|
-
brick.position.map(s => this.int_packed(s));
|
|
112
|
-
const orientation = (get(brick, 'direction', 4) << 2) | get(brick, 'rotation', 0);
|
|
113
|
-
this.int(orientation, 24);
|
|
114
|
-
if (version >= 10) {
|
|
115
|
-
if (typeof brick.collison === 'boolean' && !brick.collision) {
|
|
116
|
-
this.bit(false);
|
|
117
|
-
this.bit(false);
|
|
118
|
-
this.bit(false);
|
|
119
|
-
this.bit(true);
|
|
120
|
-
} else {
|
|
121
|
-
this.bit(get(brick, 'collision.player', true));
|
|
122
|
-
this.bit(get(brick, 'collision.weapon', true));
|
|
123
|
-
this.bit(get(brick, 'collision.interaction', true));
|
|
124
|
-
this.bit(get(brick, 'collision.tool', true));
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
this.bit(get(brick, 'collision', true));
|
|
128
|
-
}
|
|
129
|
-
this.bit(get(brick, 'visibility', true));
|
|
130
|
-
if (version >= 8) {
|
|
131
|
-
this.int(brick.material_index, numMats)
|
|
132
|
-
} else {
|
|
133
|
-
this.bit(brick.material_index !== 1);
|
|
134
|
-
if (brick.material_index !== 1) {
|
|
135
|
-
this.uint_packed(brick.material_index);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (version >= 9) {
|
|
139
|
-
this.int(get(brick, 'physical_index', 0), numPhysMats);
|
|
140
|
-
this.int(get(brick, 'material_intensity', 5), 11);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (typeof brick.color === 'number') {
|
|
144
|
-
this.bit(false);
|
|
145
|
-
this.int(brick.color, numColors);
|
|
146
|
-
} else {
|
|
147
|
-
this.bit(true);
|
|
148
|
-
if (version >= 9) {
|
|
149
|
-
this.bytes(new Uint8Array(get(brick, 'color', [255, 255, 255])));
|
|
150
|
-
} else {
|
|
151
|
-
this.bytes(rgba(get(brick, 'color', [255, 255, 255, 255])));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
this.uint_packed(get(brick, 'owner_index', 1));
|
|
156
|
-
|
|
157
|
-
if (version >= 8) {
|
|
158
|
-
// add all the brick indices to the components list
|
|
159
|
-
for (const key in brick.components || {}) {
|
|
160
|
-
componentBricks[key] = componentBricks[key] || [];
|
|
161
|
-
componentBricks[key].push(i);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
.finish()
|
|
166
|
-
),
|
|
167
|
-
|
|
168
|
-
// write components section
|
|
169
|
-
version >= 8 ? write.compressed(write.array(Object.keys(get(save, 'components', {})).filter(name => componentBricks[name]), name => concat(
|
|
170
|
-
write.string(name),
|
|
171
|
-
write.bits()
|
|
172
|
-
.self(function() {
|
|
173
|
-
const component = save.components[name];
|
|
174
|
-
const brick_indices = componentBricks[name];
|
|
175
|
-
const properties = Object.entries(component.properties);
|
|
176
|
-
|
|
177
|
-
// write version
|
|
178
|
-
this.bytes(write.i32(component.version));
|
|
179
|
-
|
|
180
|
-
// write bricks;
|
|
181
|
-
this.array(brick_indices, i => {
|
|
182
|
-
this.int(i, Math.max(save.bricks.length, 2))
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// write properties
|
|
186
|
-
this.array(properties, ([name, type]) => {
|
|
187
|
-
this.string(name);
|
|
188
|
-
this.string(type);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// read brick indices
|
|
192
|
-
for (const i of brick_indices) {
|
|
193
|
-
for (const [prop, type] of properties) {
|
|
194
|
-
this.unreal(type, get(save, `bricks.${i}.components.${name}.${prop}`));
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
this.align();
|
|
198
|
-
})
|
|
199
|
-
.finishSection(),
|
|
200
|
-
))) : EMPTY_ARR,
|
|
201
|
-
);
|
|
202
|
-
return buff;
|
|
203
|
-
}
|
package/test/lib.test.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const { read, write } = require('../dist/dist.node.js');
|
|
2
|
-
const _ = require('lodash');
|
|
3
|
-
|
|
4
|
-
// const uuid0 = '00000000-0000-0000-0000-000000000000';
|
|
5
|
-
const uuid0 = '12345678-4321-1234-4321-123456789012';
|
|
6
|
-
|
|
7
|
-
const save = {
|
|
8
|
-
version: 8,
|
|
9
|
-
map: 'Unknown',
|
|
10
|
-
description: '',
|
|
11
|
-
author: {id: uuid0, name: 'Test'},
|
|
12
|
-
save_time: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
|
|
13
|
-
brick_count: 1,
|
|
14
|
-
mods: [],
|
|
15
|
-
preview: undefined,
|
|
16
|
-
brick_assets: [],
|
|
17
|
-
colors: [],
|
|
18
|
-
components: {},
|
|
19
|
-
gameVersion: 0,
|
|
20
|
-
host: {id: uuid0, name: 'Test'},
|
|
21
|
-
materials: ['BMC_Hologram', 'BMC_Plastic', 'BMC_Glow', 'BMC_Metallic'],
|
|
22
|
-
brick_owners: [{id: uuid0, name: 'Test', bricks: 0}],
|
|
23
|
-
bricks: [{
|
|
24
|
-
asset_name_index: 1,
|
|
25
|
-
size: [10, 10, 10],
|
|
26
|
-
position: [0, 0, 0],
|
|
27
|
-
components: {},
|
|
28
|
-
direction: 4,
|
|
29
|
-
owner_index: 1,
|
|
30
|
-
rotation: 0,
|
|
31
|
-
collision: true,
|
|
32
|
-
visibility: true,
|
|
33
|
-
material_index: 1,
|
|
34
|
-
color: [0, 0, 0, 255],
|
|
35
|
-
}],
|
|
36
|
-
};
|
|
37
|
-
test('creating a brs from thin air', () => {
|
|
38
|
-
expect(read(write(save))).toEqual(save);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('reads no bricks when the option is passed in', () => {
|
|
42
|
-
expect(read(write(save), {bricks: false})).toEqual({...save, bricks: []});
|
|
43
|
-
});
|
package/test/utils.test.js
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
const { utils: { read, write } } = require('../dist/dist.node.js');
|
|
2
|
-
const clone = require('lodash/clone');
|
|
3
|
-
|
|
4
|
-
describe('buffer read/writing', () => {
|
|
5
|
-
// Generic testing of read and write
|
|
6
|
-
const rwTest = fn => (bytes, val, ...args) => {
|
|
7
|
-
expect(read[fn](new Uint8Array(bytes), ...args)).toEqual(val);
|
|
8
|
-
expect(write[fn](val, ...args)).toMatchObject(new Uint8Array(bytes));
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// Testing both endiannesses for read and write
|
|
12
|
-
const endianTest = fn => (bytes, val) => {
|
|
13
|
-
// Little Endian (default)
|
|
14
|
-
rwTest(fn)(bytes, val);
|
|
15
|
-
|
|
16
|
-
// Big Endian
|
|
17
|
-
rwTest(fn)(new Uint8Array(bytes).reverse(), val, false);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
describe('unsigned short', () => {
|
|
21
|
-
const shortTest = endianTest('u16');
|
|
22
|
-
|
|
23
|
-
test('00 00 -> 0', () => {
|
|
24
|
-
const bytes = [0x00, 0x00];
|
|
25
|
-
const val = 0;
|
|
26
|
-
|
|
27
|
-
shortTest(bytes, val);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('ff ff -> 65535', () => {
|
|
31
|
-
const bytes = [0xff, 0xff];
|
|
32
|
-
const val = 0xffff;
|
|
33
|
-
|
|
34
|
-
shortTest(bytes, val);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('01 20 -> 8193', () => {
|
|
38
|
-
const bytes = [0x01, 0x20];
|
|
39
|
-
const val = 0x2001;
|
|
40
|
-
|
|
41
|
-
shortTest(bytes, val);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('signed int', () => {
|
|
46
|
-
const intTest = endianTest('i32');
|
|
47
|
-
|
|
48
|
-
test('00 00 00 00 -> 0', () => {
|
|
49
|
-
const bytes = [0x00, 0x00, 0x00, 0x00];
|
|
50
|
-
const val = 0;
|
|
51
|
-
|
|
52
|
-
intTest(bytes, val);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('ff ff ff ff -> -1', () => {
|
|
56
|
-
const bytes = [0xff, 0xff, 0xff, 0xff];
|
|
57
|
-
const val = -1;
|
|
58
|
-
|
|
59
|
-
intTest(bytes, val);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('13 37 69 69 -> 322398569', () => {
|
|
63
|
-
const bytes = [0x69, 0x69, 0x37, 0x13];
|
|
64
|
-
const val = 322398569;
|
|
65
|
-
|
|
66
|
-
intTest(bytes, val);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('string', () => {
|
|
71
|
-
test('04 00 00 00 66 6f 6f 00 -> "foo"', () => {
|
|
72
|
-
const bytes = [0x04, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x00];
|
|
73
|
-
const val = 'foo';
|
|
74
|
-
|
|
75
|
-
rwTest('string')(bytes, val);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('uuid', () => {
|
|
80
|
-
test('can parse uuid', () => {
|
|
81
|
-
const bytes = [
|
|
82
|
-
205,107, 157, 27,
|
|
83
|
-
45, 75, 253, 187,
|
|
84
|
-
141,171, 93, 155,
|
|
85
|
-
237, 75, 189, 251,
|
|
86
|
-
];
|
|
87
|
-
const uuid = '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed';
|
|
88
|
-
|
|
89
|
-
rwTest('uuid')(bytes, uuid);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('array', () => {
|
|
94
|
-
/*
|
|
95
|
-
const bytes = [0x04, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x00];
|
|
96
|
-
const val = 'foo';
|
|
97
|
-
*/
|
|
98
|
-
const read_byte = b => read.bytes(b, 1)[0];
|
|
99
|
-
|
|
100
|
-
const arrTest = (bytes, arr, read_fn, write_fn) => {
|
|
101
|
-
expect(read.array(new Uint8Array(bytes), read_fn)).toMatchObject(arr);
|
|
102
|
-
expect(write.array(arr, write_fn)).toMatchObject(new Uint8Array(bytes));
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
test('00 00 00 00 -> []', () => {
|
|
106
|
-
const bytes = [0x00, 0x00, 0x00, 0x00];
|
|
107
|
-
const arr = [];
|
|
108
|
-
|
|
109
|
-
arrTest(bytes, arr, read_byte, b => [b]);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('03 00 00 00 01 02 03 -> [1, 2, 3]', () => {
|
|
113
|
-
const bytes = [0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03];
|
|
114
|
-
const arr = [1, 2, 3];
|
|
115
|
-
|
|
116
|
-
arrTest(bytes, arr, read_byte, b => [b]);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('string array', () => {
|
|
120
|
-
const str_bytes = [0x04, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x00];
|
|
121
|
-
const str = 'foo';
|
|
122
|
-
const bytes = [0x05, 0, 0, 0, ...str_bytes, ...str_bytes, ...str_bytes, ...str_bytes, ...str_bytes];
|
|
123
|
-
const arr = [str, str, str, str, str, ];
|
|
124
|
-
arrTest(bytes, arr, read.string, write.string);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('bit reader', () => {
|
|
129
|
-
it('reads bits', () => {
|
|
130
|
-
const bits = read.bits([0b10101111]);
|
|
131
|
-
expect(bits.bit()).toBe(true);
|
|
132
|
-
expect(bits.bit()).toBe(true);
|
|
133
|
-
expect(bits.bit()).toBe(true);
|
|
134
|
-
expect(bits.bit()).toBe(true);
|
|
135
|
-
expect(bits.bit()).toBe(false);
|
|
136
|
-
expect(bits.bit()).toBe(true);
|
|
137
|
-
expect(bits.bit()).toBe(false);
|
|
138
|
-
expect(bits.bit()).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('reads aligned bits', () => {
|
|
142
|
-
const bits = read.bits([0b00000001, 0b10101111]);
|
|
143
|
-
expect(bits.bit()).toBe(true);
|
|
144
|
-
bits.align();
|
|
145
|
-
expect(bits.bit()).toBe(true);
|
|
146
|
-
expect(bits.bit()).toBe(true);
|
|
147
|
-
expect(bits.bit()).toBe(true);
|
|
148
|
-
expect(bits.bit()).toBe(true);
|
|
149
|
-
expect(bits.bit()).toBe(false);
|
|
150
|
-
expect(bits.bit()).toBe(true);
|
|
151
|
-
expect(bits.bit()).toBe(false);
|
|
152
|
-
expect(bits.bit()).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('reads ints', () => {
|
|
156
|
-
const bits = read.bits([0b11001111]);
|
|
157
|
-
expect(bits.int(16)).toBe(15);
|
|
158
|
-
expect(bits.int(16)).toBe(12);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('reads floats', () => {
|
|
162
|
-
const bits = read.bits([0x42, 0xf6, 0xe6, 0x66].reverse());
|
|
163
|
-
expect(bits.float()).toBeCloseTo(123.45);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('writes floats', () => {
|
|
167
|
-
const bits = write.bits();
|
|
168
|
-
bits.float(123.45);
|
|
169
|
-
expect(bits.finish()).toStrictEqual([0x42, 0xf6, 0xe6, 0x66].reverse());
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// TODO: tests for int_packed, uint_packed, bytes
|
|
173
|
-
});
|
|
174
|
-
});
|