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
package/.prettierrc
ADDED
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Read and write Brickadia save files (.brs)
|
|
4
4
|
|
|
5
|
-
Currently supports save versions <=
|
|
5
|
+
Currently supports save versions <= 10
|
|
6
6
|
|
|
7
7
|
**Warning:** __Unreal Engine uses numbers potentially larger than Javascript can handle.__
|
|
8
8
|
|
|
@@ -116,7 +116,7 @@ Unsigned ints, while unlikely, may overflow.
|
|
|
116
116
|
| field | type | default | optional | description |
|
|
117
117
|
|-----------------------------|--------|--------------------------------------|----------|----------------------------------|
|
|
118
118
|
| version | short | Latest Save Version | auto | Save file version |
|
|
119
|
-
|
|
|
119
|
+
| game_version | int | Game Version | ☑ | Saving version of the game |
|
|
120
120
|
| map | string | 'Unknown' | ☑ | Map where the save was generated |
|
|
121
121
|
| author.id | uuid | 00000000-0000-0000-0000-000000000000 | ☑ | Save author UUID |
|
|
122
122
|
| author.name | string | 'Unknown' | ☑ | Save author name |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/debug/a5p2.2.brs
ADDED
|
Binary file
|
package/debug/a5p2.brs
ADDED
|
Binary file
|
package/debug/audio.brs
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/debug/brsv10.brs
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
const {utils: {read, isEqual}, constants: {MAGIC}} = require('../dist/dist.node.js');
|
|
4
|
+
|
|
5
|
+
// arg parsing
|
|
6
|
+
const file = process.argv.find(f => f.endsWith('brs'));
|
|
7
|
+
if (!file) {
|
|
8
|
+
console.error('missing save argument');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
if (!fs.existsSync(file)) {
|
|
12
|
+
console.error('file does not exist');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// read the file
|
|
17
|
+
const data = Array.from(fs.readFileSync(file));
|
|
18
|
+
const copyData = () => data.slice();
|
|
19
|
+
|
|
20
|
+
const bgra = ([b, g, r, a]) => [r, g, b, a];
|
|
21
|
+
const PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82];
|
|
22
|
+
let quiet = false;
|
|
23
|
+
|
|
24
|
+
function tryRead(data, type, fn, results) {
|
|
25
|
+
try {
|
|
26
|
+
if (typeof type === 'function')
|
|
27
|
+
return type(data, results);
|
|
28
|
+
|
|
29
|
+
switch(type) {
|
|
30
|
+
case 'magic': return isEqual(data.splice(0, MAGIC.length), MAGIC);
|
|
31
|
+
case 'pngmagic': return isEqual(data.splice(0, PNG_MAGIC.length), PNG_MAGIC);
|
|
32
|
+
case 'compressed': return read.compressed(data);
|
|
33
|
+
case 'compressed.array': return read.array(read.compressed(data), data => fn(data, results));
|
|
34
|
+
case 'string': return read.string(data);
|
|
35
|
+
case 'uuid': return read.uuid(data);
|
|
36
|
+
case 'byte': return data.splice(0, 1)[0];
|
|
37
|
+
case 'u16': return read.u16(data);
|
|
38
|
+
case 'i32': return read.i32(data);
|
|
39
|
+
case 'time': return data.splice(0, 8);
|
|
40
|
+
case 'word': return data.splice(0, 4);
|
|
41
|
+
case 'bits': return read.bits(data);
|
|
42
|
+
case 'array': return read.array(data, data => fn(data, results));
|
|
43
|
+
case 'rest': return data.splice(0, data.length);
|
|
44
|
+
case 'bricks':
|
|
45
|
+
const brickData = read.compressed(data);
|
|
46
|
+
// console.log(brickData.map(d => d.toString(2).padStart(8, '0')).join(''));
|
|
47
|
+
const brickBits = read.bits(brickData);
|
|
48
|
+
const bricks = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
while(!brickBits.empty() && bricks.length < results.header1.brickCount) {
|
|
52
|
+
brickBits.align();
|
|
53
|
+
const start = brickBits.pos;
|
|
54
|
+
const brick = fn(brickBits, results);
|
|
55
|
+
brick.bitsize = (brickBits.pos - start);
|
|
56
|
+
bricks.push(brick);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(e)
|
|
60
|
+
return bricks;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return bricks;
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.log(e);
|
|
67
|
+
return ';;badvalue;;';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function bruteforce(data, steps, indent=0) {
|
|
72
|
+
let i = 0;
|
|
73
|
+
const results = {};
|
|
74
|
+
for (let step of steps) {
|
|
75
|
+
i ++;
|
|
76
|
+
|
|
77
|
+
// convert string and arrays into objects
|
|
78
|
+
if (typeof step === 'string')
|
|
79
|
+
step = {name: step, bin: false, type: step};
|
|
80
|
+
if (typeof step === 'object' && typeof step.length === 'number')
|
|
81
|
+
step = {name: 'scan', attempt: step};
|
|
82
|
+
|
|
83
|
+
// check if ther's any data left
|
|
84
|
+
if (data.length === 0) {
|
|
85
|
+
if(!quiet) console.log(''.padStart(indent * 2, ' '), i, ')', step.name || '?', '[no bytes left]');
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let type = step.type;
|
|
90
|
+
|
|
91
|
+
// if this is a conditional step, assign the next steps based on this one
|
|
92
|
+
if (step.test) {
|
|
93
|
+
const test = step.test(tryRead(data, step.type));
|
|
94
|
+
if(!quiet) console.log(''.padStart(indent * 2, ' '), i, '-', step.name || '?', 'test', test);
|
|
95
|
+
step.contains = test ? step.yes : step.no;
|
|
96
|
+
step.type = 'rest';
|
|
97
|
+
} else if (!type) {
|
|
98
|
+
// scan for a matching type
|
|
99
|
+
type = step.attempt.find(t => tryRead(data.slice(), typeof t === 'object' ? t.name : t, t.fn) !== ';;badvalue;;');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
const val = step.test ? 'test' : tryRead(data, type, step.fn, results);
|
|
104
|
+
if(!quiet) console.log(''.padStart(indent * 2, '.'), i, ')', step.name || '?', '=', step.bin ? val.length + ' bytes' : val);
|
|
105
|
+
results[step.name] = step.contains
|
|
106
|
+
? bruteforce(step.test ? data : val, step.contains, indent+1, results)
|
|
107
|
+
: val;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const go = (before, after) => bruteforce(copyData(), [
|
|
114
|
+
{name: 'BRS', type: 'magic'},
|
|
115
|
+
{name: 'version', type: 'u16'},
|
|
116
|
+
{name: 'game version', type: 'i32'},
|
|
117
|
+
{
|
|
118
|
+
name: 'header1', type: 'compressed', bin: true,
|
|
119
|
+
contains: [
|
|
120
|
+
{name: 'map', type: 'string'},
|
|
121
|
+
{name: 'saverName', type: 'string'},
|
|
122
|
+
{name: 'description', type: 'string'},
|
|
123
|
+
{name: 'saverId', type: 'uuid'},
|
|
124
|
+
{name: 'hostName', type: 'string'},
|
|
125
|
+
{name: 'hostId', type: 'uuid'},
|
|
126
|
+
{name: 'saveDate', type: 'time'},
|
|
127
|
+
{name: 'brickCount', type: 'i32'},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'header2', type: 'compressed', bin: true,
|
|
132
|
+
contains: [
|
|
133
|
+
{name: 'mods', type: 'array', fn: read.string},
|
|
134
|
+
{name: 'assets', type: 'array', fn: read.string},
|
|
135
|
+
{name: 'colors', type: 'array', fn: data => bgra(data.splice(0, 4))},
|
|
136
|
+
{name: 'materials', type: 'array', fn: read.string},
|
|
137
|
+
{name: 'owners', type: 'array', fn: data => ({
|
|
138
|
+
id: read.uuid(data),
|
|
139
|
+
name: read.string(data),
|
|
140
|
+
count: read.i32(data)
|
|
141
|
+
})},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'preview',
|
|
146
|
+
test: v => v, type: 'byte',
|
|
147
|
+
yes: [
|
|
148
|
+
{name: 'data', type: data => {
|
|
149
|
+
const len = read.i32(data);
|
|
150
|
+
const png = data.splice(0, len);
|
|
151
|
+
return len + ' bytes';
|
|
152
|
+
}},
|
|
153
|
+
],
|
|
154
|
+
no: [],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'bricks',
|
|
158
|
+
type: 'bricks',
|
|
159
|
+
fn: (bits, parent) => ({
|
|
160
|
+
asset_index: bits.int(Math.max(parent.header2.assets.length, 2)),
|
|
161
|
+
size: bits.bit() ? [bits.uint_packed(), bits.uint_packed(), bits.uint_packed()] : [0, 0, 0],
|
|
162
|
+
position: [bits.int_packed(), bits.int_packed(), bits.int_packed()],
|
|
163
|
+
...(orientation => ({
|
|
164
|
+
direction: (orientation >> 2) % 6,
|
|
165
|
+
rotation: orientation & 3,
|
|
166
|
+
}))(bits.int(24)),
|
|
167
|
+
collision: bits.bit(),
|
|
168
|
+
visibility: bits.bit(),
|
|
169
|
+
material_index: bits.bit() ? bits.uint_packed() : 1,
|
|
170
|
+
// material_index: bits.int(Math.max(parent.header2.materials.length, 2)),
|
|
171
|
+
color: bits.bit() ? bgra(bits.bytes(4)) : bits.int(parent.header2.colors.length),
|
|
172
|
+
owner_index: bits.uint_packed(true),
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'components', type: 'compressed.array', fn: (data, parent) => {
|
|
177
|
+
const name = read.string(data);
|
|
178
|
+
const bits = read.bits(data.splice(0, read.i32(data)));
|
|
179
|
+
const version = read.i32(bits.bytes(4));
|
|
180
|
+
const brick_indices = bits.array(() => bits.int(Math.max(parent.header1.brickCount, 2)));
|
|
181
|
+
const properties = bits.array(() => [bits.string(), bits.string()]);
|
|
182
|
+
|
|
183
|
+
const bricks = brick_indices.map(i => ({
|
|
184
|
+
index: i,
|
|
185
|
+
props: Object.fromEntries(properties.map(([name, type]) => {
|
|
186
|
+
let val;
|
|
187
|
+
switch(type) {
|
|
188
|
+
case 'Class':
|
|
189
|
+
val = bits.string();
|
|
190
|
+
break;
|
|
191
|
+
case 'Boolean':
|
|
192
|
+
val = !!read.i32(bits.bytes(4));
|
|
193
|
+
break;
|
|
194
|
+
case 'Float':
|
|
195
|
+
val = bits.float();
|
|
196
|
+
break;
|
|
197
|
+
case 'Color':
|
|
198
|
+
val = bgra(bits.bytes(4));
|
|
199
|
+
break;
|
|
200
|
+
case 'Rotator':
|
|
201
|
+
val = [bits.float(), bits.float(), bits.float()];
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
return [name, val];
|
|
205
|
+
})),
|
|
206
|
+
}));
|
|
207
|
+
return {
|
|
208
|
+
name,
|
|
209
|
+
version,
|
|
210
|
+
brick_indices,
|
|
211
|
+
properties: Object.fromEntries(properties),
|
|
212
|
+
bricks,
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
for each component type used in the build, it will
|
|
220
|
+
write the component type name [done]
|
|
221
|
+
write the version [done]
|
|
222
|
+
write an array of brick indices to attach to [done]
|
|
223
|
+
write the data layout
|
|
224
|
+
write an array of property infos
|
|
225
|
+
write the name of the property
|
|
226
|
+
write the type of the property
|
|
227
|
+
write the size of the following data section
|
|
228
|
+
write the data
|
|
229
|
+
write each component instance
|
|
230
|
+
write each property
|
|
231
|
+
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
// console.log(go(1).bricks.bricks.slice(0, 2))
|
|
235
|
+
|
|
236
|
+
quiet = true;
|
|
237
|
+
for (let before = 0; before < 1; before++) {
|
|
238
|
+
for (let after = 0; after < 1; after++) {
|
|
239
|
+
const res = go(before, after);
|
|
240
|
+
// return;
|
|
241
|
+
// const colorOk = res.bricks.every(b => b.color === 0);
|
|
242
|
+
const bricksOk = res.bricks.length === res.header1.brickCount && res.bricks.every(b => b.owner_index <= res.header2.owners.length );
|
|
243
|
+
// const ownerOk = res.bricks.some(b => b.owner_index !== 1) && res.bricks.some(b => b.owner_index === 0);
|
|
244
|
+
// const materialOk = res.bricks.every(b => b.color === 0);
|
|
245
|
+
if (true) {
|
|
246
|
+
const bin = arr => arr.map(a => a?'1':'0').join('');
|
|
247
|
+
res.components.map(c => console.log(c, ...c.bricks))
|
|
248
|
+
console.log(
|
|
249
|
+
res,
|
|
250
|
+
{bricks: res.bricks.length, before, after},
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
package/debug/clone.html
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<script src="../dist/dist.js"></script>
|
|
3
|
+
|
|
4
|
+
<!-- Files uploaded will be -->
|
|
5
|
+
<input id="fileInput" type="file">
|
|
6
|
+
|
|
7
|
+
<a id="anchor" download="cloned.brs">Download</a>
|
|
8
|
+
|
|
9
|
+
<!-- This will be filled with the save object as JSON or the error message -->
|
|
10
|
+
<pre id="jsonElem"></pre>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
fileInput.addEventListener('change', e => {
|
|
14
|
+
const file = e.target.files[0];
|
|
15
|
+
if (file) {
|
|
16
|
+
// Read the file into a byte array
|
|
17
|
+
file.arrayBuffer()
|
|
18
|
+
.then(buff => {
|
|
19
|
+
const save = BRS.read(buff);
|
|
20
|
+
|
|
21
|
+
// Log the save object
|
|
22
|
+
console.log(save);
|
|
23
|
+
|
|
24
|
+
// Render the save object as formatted JSON
|
|
25
|
+
jsonElem.innerText = '';
|
|
26
|
+
|
|
27
|
+
const blob = new Blob([new Uint8Array(BRS.write(save))]);
|
|
28
|
+
anchor.href = URL.createObjectURL(blob);
|
|
29
|
+
})
|
|
30
|
+
.catch(err => {
|
|
31
|
+
// Display the error
|
|
32
|
+
jsonElem.innerText = 'Error: ' + err.message;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
Binary file
|
|
Binary file
|
package/debug/evil.brs
ADDED
|
Binary file
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
}
|
|
1
|
+
const {
|
|
2
|
+
utils: { write, isEqual, concat },
|
|
3
|
+
constants: { MAGIC, LATEST_VERSION, MAX_INT },
|
|
4
|
+
} = require('../dist/dist.node.js');
|
|
18
5
|
|
|
19
6
|
// looks up a value in an object or returns a defualt value
|
|
20
|
-
function get(obj, path='', def) {
|
|
7
|
+
function get(obj, path = '', def) {
|
|
21
8
|
// Split the path up by .
|
|
22
9
|
path = path.split('.').filter(p => p.length > 0);
|
|
23
10
|
|
|
@@ -31,10 +18,8 @@ function get(obj, path='', def) {
|
|
|
31
18
|
|
|
32
19
|
const EMPTY_ARR = new Uint8Array([]);
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if(save.bricks.length > MAX_INT) {
|
|
21
|
+
function writeSave(save) {
|
|
22
|
+
if (save.bricks.length > MAX_INT) {
|
|
38
23
|
throw new Error('Brick count out of range');
|
|
39
24
|
}
|
|
40
25
|
|
|
@@ -65,7 +50,7 @@ export default function writeBrs(save) {
|
|
|
65
50
|
write.string(get(save, 'description', '')),
|
|
66
51
|
write.uuid(get(save, 'author.id', '00000000-0000-0000-0000-000000000000')),
|
|
67
52
|
version >= 8 ? concat(
|
|
68
|
-
write.string(get(save
|
|
53
|
+
write.string(get(save, 'host.name', 'Unknown')),
|
|
69
54
|
write.uuid(get(save, 'host.id', '00000000-0000-0000-0000-000000000000')),
|
|
70
55
|
) : EMPTY_ARR,
|
|
71
56
|
get(save, 'save_time', new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])),
|
|
@@ -78,7 +63,7 @@ export default function writeBrs(save) {
|
|
|
78
63
|
write.array(get(save, 'brick_assets', ['PB_DefaultBrick']), write.string),
|
|
79
64
|
write.array(get(save, 'colors', EMPTY_ARR), rgba),
|
|
80
65
|
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(
|
|
66
|
+
write.array(get(save, 'brick_owners', [{}]), ({ id = '00000000-0000-0000-0000-000000000000', name = 'Unknown', bricks = 0 } = {}) => concat(
|
|
82
67
|
write.uuid(id),
|
|
83
68
|
write.string(name),
|
|
84
69
|
version >= 8 ? write.i32(bricks) : EMPTY_ARR,
|
|
@@ -91,15 +76,15 @@ export default function writeBrs(save) {
|
|
|
91
76
|
// write the save preview if it exists
|
|
92
77
|
version >= 8
|
|
93
78
|
? concat(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
new Uint8Array([save.preview ? 1 : 0]),
|
|
80
|
+
save.preview ? write.i32(get(save, 'preview.length', 0)) : EMPTY_ARR, // <- Sorry @Uxie https://i.imgur.com/hSRxdbf.png
|
|
81
|
+
get(save, 'preview', EMPTY_ARR),
|
|
82
|
+
)
|
|
98
83
|
: EMPTY_ARR,
|
|
99
84
|
|
|
100
85
|
// Bricks
|
|
101
86
|
write.compressed(write.bits()
|
|
102
|
-
.each(save.bricks, function(brick, i) {
|
|
87
|
+
.each(save.bricks, function (brick, i) {
|
|
103
88
|
this.align();
|
|
104
89
|
this.int(get(brick, 'asset_name_index', 0), numAssets);
|
|
105
90
|
|
|
@@ -108,6 +93,7 @@ export default function writeBrs(save) {
|
|
|
108
93
|
if (!isSingularity) {
|
|
109
94
|
brick.size.map(s => this.uint_packed(s));
|
|
110
95
|
}
|
|
96
|
+
|
|
111
97
|
brick.position.map(s => this.int_packed(s));
|
|
112
98
|
const orientation = (get(brick, 'direction', 4) << 2) | get(brick, 'rotation', 0);
|
|
113
99
|
this.int(orientation, 24);
|
|
@@ -169,7 +155,7 @@ export default function writeBrs(save) {
|
|
|
169
155
|
version >= 8 ? write.compressed(write.array(Object.keys(get(save, 'components', {})).filter(name => componentBricks[name]), name => concat(
|
|
170
156
|
write.string(name),
|
|
171
157
|
write.bits()
|
|
172
|
-
.self(function() {
|
|
158
|
+
.self(function () {
|
|
173
159
|
const component = save.components[name];
|
|
174
160
|
const brick_indices = componentBricks[name];
|
|
175
161
|
const properties = Object.entries(component.properties);
|
|
@@ -200,4 +186,205 @@ export default function writeBrs(save) {
|
|
|
200
186
|
))) : EMPTY_ARR,
|
|
201
187
|
);
|
|
202
188
|
return buff;
|
|
203
|
-
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const fs = require('fs');
|
|
192
|
+
const path = require('path');
|
|
193
|
+
const _ = require('lodash');
|
|
194
|
+
|
|
195
|
+
// Write the save to a file
|
|
196
|
+
const outfile = path.resolve('/mnt/c/Users/ISAAC/AppData/Local/Brickadia/Saved/Builds', 'evil.brs');
|
|
197
|
+
|
|
198
|
+
// Print some info
|
|
199
|
+
fs.writeFileSync(outfile, writeSave({
|
|
200
|
+
"version": 10,
|
|
201
|
+
"bricks": [
|
|
202
|
+
{
|
|
203
|
+
"asset_name_index": 0,
|
|
204
|
+
"size": [ 5, 5, 6 ],
|
|
205
|
+
"position": [0, 0, 0],
|
|
206
|
+
"direction": 4,
|
|
207
|
+
"rotation": 0,
|
|
208
|
+
"collision": {
|
|
209
|
+
"player": true,
|
|
210
|
+
"weapon": true,
|
|
211
|
+
"interaction": true,
|
|
212
|
+
"tool": true
|
|
213
|
+
},
|
|
214
|
+
"visibility": true,
|
|
215
|
+
"material_index": 3,
|
|
216
|
+
"physical_index": 0,
|
|
217
|
+
"material_intensity": 5,
|
|
218
|
+
"color": 0,
|
|
219
|
+
"owner_index": 1,
|
|
220
|
+
"components": {
|
|
221
|
+
"BCD_AudioEmitter": {
|
|
222
|
+
"AudioDescriptor": "BA_AMB_Component_Fires_Fireplace_1",
|
|
223
|
+
"VolumeMultiplier": 1,
|
|
224
|
+
"PitchMultiplier": 1,
|
|
225
|
+
"InnerRadius": 15,
|
|
226
|
+
"MaxDistance": 400,
|
|
227
|
+
"bSpatialization": true
|
|
228
|
+
},
|
|
229
|
+
"BCD_ItemSpawn": {
|
|
230
|
+
"PickupClass": "BP_ItemPickup_StickGrenade",
|
|
231
|
+
"bPickupEnabled": true,
|
|
232
|
+
"bPickupRespawnOnMinigameReset": true,
|
|
233
|
+
"PickupMinigameResetRespawnDelay": 0,
|
|
234
|
+
"bPickupAutoDisableOnPickup": true,
|
|
235
|
+
"PickupRespawnTime": 0.1,
|
|
236
|
+
"PickupOffsetDirection": 4,
|
|
237
|
+
"PickupOffsetDistance": 5,
|
|
238
|
+
"PickupRotation": [ 0, 0, 5 ],
|
|
239
|
+
"PickupScale": 1,
|
|
240
|
+
"bPickupAnimationEnabled": true,
|
|
241
|
+
"PickupAnimationAxis": 2,
|
|
242
|
+
"bPickupAnimationAxisLocal": false,
|
|
243
|
+
"PickupSpinSpeed": 0.1,
|
|
244
|
+
"PickupBobSpeed": 0.1,
|
|
245
|
+
"PickupBobHeight": 4,
|
|
246
|
+
"PickupAnimationPhase": 0.1515854299068451
|
|
247
|
+
},
|
|
248
|
+
"BCD_PointLight": {
|
|
249
|
+
"bMatchBrickShape": true,
|
|
250
|
+
"Brightness": 15,
|
|
251
|
+
"Radius": 500,
|
|
252
|
+
"Color": [
|
|
253
|
+
255,
|
|
254
|
+
255,
|
|
255
|
+
255,
|
|
256
|
+
255
|
|
257
|
+
],
|
|
258
|
+
"bUseBrickColor": true,
|
|
259
|
+
"bCastShadows": true
|
|
260
|
+
},
|
|
261
|
+
"BCD_SpotLight": {
|
|
262
|
+
"Rotation": [
|
|
263
|
+
270,
|
|
264
|
+
0,
|
|
265
|
+
0
|
|
266
|
+
],
|
|
267
|
+
"InnerConeAngle": 30,
|
|
268
|
+
"OuterConeAngle": 0,
|
|
269
|
+
"Brightness": 15,
|
|
270
|
+
"Radius": 150,
|
|
271
|
+
"Color": [
|
|
272
|
+
0,
|
|
273
|
+
255,
|
|
274
|
+
255,
|
|
275
|
+
255
|
|
276
|
+
],
|
|
277
|
+
"bUseBrickColor": true,
|
|
278
|
+
"bCastShadows": true
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
"map": "Plate",
|
|
284
|
+
"author": {
|
|
285
|
+
"name": "cake",
|
|
286
|
+
"id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d"
|
|
287
|
+
},
|
|
288
|
+
"description": "a".repeat(5),
|
|
289
|
+
"host": {
|
|
290
|
+
"name": "cake",
|
|
291
|
+
"id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d"
|
|
292
|
+
},
|
|
293
|
+
"mods": [],
|
|
294
|
+
"brick_assets": ["PB_DefaultBrick"],
|
|
295
|
+
"colors": [
|
|
296
|
+
[255, 255, 255, 255],
|
|
297
|
+
],
|
|
298
|
+
"materials": [
|
|
299
|
+
"BMC_Hidden",
|
|
300
|
+
"BMC_Ghost",
|
|
301
|
+
"BMC_Ghost_Fail",
|
|
302
|
+
"BMC_Plastic",
|
|
303
|
+
"BMC_Glass",
|
|
304
|
+
"BMC_Glow",
|
|
305
|
+
"BMC_Metallic",
|
|
306
|
+
"BMC_Hologram"
|
|
307
|
+
],
|
|
308
|
+
"brick_owners": [
|
|
309
|
+
{
|
|
310
|
+
"id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d",
|
|
311
|
+
"name": "cake",
|
|
312
|
+
"bricks": 69
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
"physical_materials": [
|
|
316
|
+
"BPMC_Default"
|
|
317
|
+
],
|
|
318
|
+
"gameVersion": 6880,
|
|
319
|
+
"components": {
|
|
320
|
+
"BCD_AudioEmitter": {
|
|
321
|
+
"version": 1,
|
|
322
|
+
"brick_indices": [
|
|
323
|
+
0
|
|
324
|
+
],
|
|
325
|
+
"properties": {
|
|
326
|
+
"AudioDescriptor": "Object",
|
|
327
|
+
"VolumeMultiplier": "Float",
|
|
328
|
+
"PitchMultiplier": "Float",
|
|
329
|
+
"InnerRadius": "Float",
|
|
330
|
+
"MaxDistance": "Float",
|
|
331
|
+
"bSpatialization": "Boolean"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
"BCD_ItemSpawn": {
|
|
335
|
+
"version": -1,
|
|
336
|
+
"brick_indices": [
|
|
337
|
+
0
|
|
338
|
+
],
|
|
339
|
+
"properties": {
|
|
340
|
+
"PickupClass": "Class",
|
|
341
|
+
"bPickupEnabled": "Boolean",
|
|
342
|
+
"bPickupRespawnOnMinigameReset": "Boolean",
|
|
343
|
+
"PickupMinigameResetRespawnDelay": "Float",
|
|
344
|
+
"bPickupAutoDisableOnPickup": "Boolean",
|
|
345
|
+
"PickupRespawnTime": "Float",
|
|
346
|
+
"PickupOffsetDirection": "Byte",
|
|
347
|
+
"PickupOffsetDistance": "Float",
|
|
348
|
+
"PickupRotation": "Rotator",
|
|
349
|
+
"PickupScale": "Float",
|
|
350
|
+
"bPickupAnimationEnabled": "Boolean",
|
|
351
|
+
"PickupAnimationAxis": "Byte",
|
|
352
|
+
"bPickupAnimationAxisLocal": "Boolean",
|
|
353
|
+
"PickupSpinSpeed": "Float",
|
|
354
|
+
"PickupBobSpeed": "Float",
|
|
355
|
+
"PickupBobHeight": "Float",
|
|
356
|
+
"PickupAnimationPhase": "Float"
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
"BCD_PointLight": {
|
|
360
|
+
"version": 1,
|
|
361
|
+
"brick_indices": [
|
|
362
|
+
0
|
|
363
|
+
],
|
|
364
|
+
"properties": {
|
|
365
|
+
"bMatchBrickShape": "Boolean",
|
|
366
|
+
"Brightness": "Float",
|
|
367
|
+
"Radius": "Float",
|
|
368
|
+
"Color": "Color",
|
|
369
|
+
"bUseBrickColor": "Boolean",
|
|
370
|
+
"bCastShadows": "Boolean"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
"BCD_SpotLight": {
|
|
374
|
+
"version": 1,
|
|
375
|
+
"brick_indices": [
|
|
376
|
+
0
|
|
377
|
+
],
|
|
378
|
+
"properties": {
|
|
379
|
+
"Rotation": "Rotator",
|
|
380
|
+
"InnerConeAngle": "Float",
|
|
381
|
+
"OuterConeAngle": "Float",
|
|
382
|
+
"Brightness": "Float",
|
|
383
|
+
"Radius": "Float",
|
|
384
|
+
"Color": "Color",
|
|
385
|
+
"bUseBrickColor": "Boolean",
|
|
386
|
+
"bCastShadows": "Boolean"
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}));
|