dredless 0.2.0
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/LICENSE +21 -0
- package/README.md +48 -0
- package/docs/API_REFERENCE.md +2481 -0
- package/docs/BROWSER_API.md +98 -0
- package/docs/NODE_API.md +359 -0
- package/package.json +173 -0
- package/src/browser.d.ts +212 -0
- package/src/browser.js +133 -0
- package/src/client/core.js +1202 -0
- package/src/client/domains/index.js +1818 -0
- package/src/client/index.d.ts +2 -0
- package/src/client/index.js +1 -0
- package/src/compression/lz4.d.ts +1 -0
- package/src/compression/lz4.js +112 -0
- package/src/constants.js +74 -0
- package/src/crypto/chacha-native.d.ts +9 -0
- package/src/crypto/chacha-native.js +48 -0
- package/src/crypto/chacha.d.ts +25 -0
- package/src/crypto/chacha.js +274 -0
- package/src/data/item-schema.js +2235 -0
- package/src/data/official-client-data.js +156 -0
- package/src/events.js +39 -0
- package/src/index.d.ts +2277 -0
- package/src/index.js +125 -0
- package/src/network/browser-session.js +291 -0
- package/src/network/connection.d.ts +1 -0
- package/src/network/connection.js +31 -0
- package/src/network/cookies.js +85 -0
- package/src/network/http.js +34 -0
- package/src/network/index.d.ts +4 -0
- package/src/network/index.js +4 -0
- package/src/network/servers.d.ts +7 -0
- package/src/network/servers.js +133 -0
- package/src/network/session.d.ts +7 -0
- package/src/network/session.js +320 -0
- package/src/network/ships.d.ts +12 -0
- package/src/network/ships.js +73 -0
- package/src/node.d.ts +2 -0
- package/src/node.js +8 -0
- package/src/protocol/codec/binary.d.ts +3 -0
- package/src/protocol/codec/binary.js +52 -0
- package/src/protocol/codec/msgpack.d.ts +3 -0
- package/src/protocol/codec/msgpack.js +555 -0
- package/src/protocol/inbound/frame.d.ts +1 -0
- package/src/protocol/inbound/frame.js +8 -0
- package/src/protocol/inbound/world-payload.d.ts +3 -0
- package/src/protocol/inbound/world-payload.js +21 -0
- package/src/protocol/index.d.ts +11 -0
- package/src/protocol/index.js +19 -0
- package/src/protocol/outbound/blueprint.d.ts +22 -0
- package/src/protocol/outbound/blueprint.js +36 -0
- package/src/protocol/outbound/commands.d.ts +3 -0
- package/src/protocol/outbound/commands.js +149 -0
- package/src/protocol/outbound/comms.d.ts +28 -0
- package/src/protocol/outbound/comms.js +40 -0
- package/src/protocol/outbound/inventory.d.ts +64 -0
- package/src/protocol/outbound/inventory.js +147 -0
- package/src/protocol/outbound/ship-management.d.ts +84 -0
- package/src/protocol/outbound/ship-management.js +221 -0
- package/src/protocol/outbound/sign.d.ts +18 -0
- package/src/protocol/outbound/sign.js +45 -0
- package/src/protocol/outbound/ui-config.d.ts +157 -0
- package/src/protocol/outbound/ui-config.js +578 -0
- package/src/runtime.js +36 -0
- package/src/state/generator-maze.d.ts +32 -0
- package/src/state/generator-maze.js +370 -0
- package/src/state/index.d.ts +3 -0
- package/src/state/index.js +3 -0
- package/src/state/items.js +20 -0
- package/src/state/model/decoder.js +1554 -0
- package/src/state/model/index.d.ts +33 -0
- package/src/state/model/index.js +2 -0
- package/src/state/model/loader-config.js +1020 -0
- package/src/state/model/reader.js +69 -0
- package/src/state/model/state.js +1257 -0
- package/src/state/overworld.js +72 -0
- package/src/state/tilesets.js +53 -0
- package/src/state/world/index.d.ts +29 -0
- package/src/state/world/index.js +180 -0
- package/src/state/world/state.js +297 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import {COMMAND_DEFAULT_FORMATS, COMMAND_FIELDS, decoder, encoder} from "../../constants.js";
|
|
2
|
+
import {concatBytes, toUint8Array, valuesEqual} from "./binary.js";
|
|
3
|
+
|
|
4
|
+
function cloneDragValue(drag) {
|
|
5
|
+
if (!drag || typeof drag !== "object") return null;
|
|
6
|
+
return {
|
|
7
|
+
source: Number(drag.source),
|
|
8
|
+
target: Number(drag.target),
|
|
9
|
+
split: Boolean(drag.split)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function cloneCommand(command) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const key of COMMAND_FIELDS) {
|
|
16
|
+
out[key] = key === "drag" ? cloneDragValue(command[key]) : command[key];
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function encodeStringBytes(text) {
|
|
22
|
+
const bytes = encoder.encode(String(text));
|
|
23
|
+
const length = bytes.length;
|
|
24
|
+
if (length <= 31) {
|
|
25
|
+
const out = new Uint8Array(1 + length);
|
|
26
|
+
out[0] = 0xa0 | length;
|
|
27
|
+
out.set(bytes, 1);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
if (length <= 0xff) {
|
|
31
|
+
const out = new Uint8Array(2 + length);
|
|
32
|
+
out[0] = 0xd9;
|
|
33
|
+
out[1] = length;
|
|
34
|
+
out.set(bytes, 2);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
const out = new Uint8Array(3 + length);
|
|
38
|
+
out[0] = 0xda;
|
|
39
|
+
out[1] = (length >>> 8) & 0xff;
|
|
40
|
+
out[2] = length & 0xff;
|
|
41
|
+
out.set(bytes, 3);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function encodeNumberWithFormat(value, format) {
|
|
46
|
+
const number = Number(value);
|
|
47
|
+
switch (format) {
|
|
48
|
+
case "positive-fixint":
|
|
49
|
+
return Uint8Array.of(number & 0x7f);
|
|
50
|
+
case "negative-fixint":
|
|
51
|
+
return Uint8Array.of(number & 0xff);
|
|
52
|
+
case "uint8":
|
|
53
|
+
return Uint8Array.of(0xcc, number & 0xff);
|
|
54
|
+
case "uint16":
|
|
55
|
+
return Uint8Array.of(0xcd, (number >>> 8) & 0xff, number & 0xff);
|
|
56
|
+
case "uint32":
|
|
57
|
+
return Uint8Array.of(0xce, (number >>> 24) & 0xff, (number >>> 16) & 0xff, (number >>> 8) & 0xff, number & 0xff);
|
|
58
|
+
case "int8":
|
|
59
|
+
return Uint8Array.of(0xd0, number & 0xff);
|
|
60
|
+
case "int16":
|
|
61
|
+
return Uint8Array.of(0xd1, (number >> 8) & 0xff, number & 0xff);
|
|
62
|
+
case "int32":
|
|
63
|
+
return Uint8Array.of(0xd2, (number >> 24) & 0xff, (number >> 16) & 0xff, (number >> 8) & 0xff, number & 0xff);
|
|
64
|
+
case "float64": {
|
|
65
|
+
const out = new Uint8Array(9);
|
|
66
|
+
out[0] = 0xcb;
|
|
67
|
+
new DataView(out.buffer).setFloat64(1, number, false);
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
case "float32":
|
|
71
|
+
default: {
|
|
72
|
+
const out = new Uint8Array(5);
|
|
73
|
+
out[0] = 0xca;
|
|
74
|
+
new DataView(out.buffer).setFloat32(1, number, false);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function encodeMapHeader(length, originalHeaderBytes = null) {
|
|
81
|
+
if (originalHeaderBytes && originalHeaderBytes.length > 0) return originalHeaderBytes;
|
|
82
|
+
if (length <= 15) return Uint8Array.of(0x80 | length);
|
|
83
|
+
return Uint8Array.of(0xde, (length >>> 8) & 0xff, length & 0xff);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function encodeValue(key, value, entry) {
|
|
87
|
+
if (entry && valuesEqual(value, entry.valueNode.value)) return entry.valueNode.raw;
|
|
88
|
+
if (value == null) return Uint8Array.of(0xc0);
|
|
89
|
+
if (typeof value === "boolean") return Uint8Array.of(value ? 0xc3 : 0xc2);
|
|
90
|
+
if (typeof value === "number") {
|
|
91
|
+
const schemaFormat = COMMAND_DEFAULT_FORMATS[key];
|
|
92
|
+
let format = schemaFormat && schemaFormat !== "nil" ? schemaFormat : null;
|
|
93
|
+
if (!format) {
|
|
94
|
+
if (!Number.isInteger(value)) format = "float32";
|
|
95
|
+
else if (value >= 0) {
|
|
96
|
+
if (value <= 0x7f) format = "positive-fixint";
|
|
97
|
+
else if (value <= 0xff) format = "uint8";
|
|
98
|
+
else if (value <= 0xffff) format = "uint16";
|
|
99
|
+
else format = "uint32";
|
|
100
|
+
} else if (value >= -32) format = "negative-fixint";
|
|
101
|
+
else if (value >= -128) format = "int8";
|
|
102
|
+
else if (value >= -32768) format = "int16";
|
|
103
|
+
else format = "int32";
|
|
104
|
+
}
|
|
105
|
+
return encodeNumberWithFormat(value, format);
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === "string") return encodeStringBytes(value);
|
|
108
|
+
if (key === "drag" && typeof value === "object") {
|
|
109
|
+
const sourceEntry = entry?.valueNode?.entries?.find((item) => String(item.key) === "source")?.valueNode;
|
|
110
|
+
const targetEntry = entry?.valueNode?.entries?.find((item) => String(item.key) === "target")?.valueNode;
|
|
111
|
+
return concatBytes([
|
|
112
|
+
Uint8Array.of(0x83),
|
|
113
|
+
encodeStringBytes("source"),
|
|
114
|
+
encodeNumberWithFormat(Number(value.source || 0), sourceEntry?.format || "positive-fixint"),
|
|
115
|
+
encodeStringBytes("target"),
|
|
116
|
+
encodeNumberWithFormat(Number(value.target || 0), targetEntry?.format || "positive-fixint"),
|
|
117
|
+
encodeStringBytes("split"),
|
|
118
|
+
Uint8Array.of(Boolean(value.split) ? 0xc3 : 0xc2)
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Unsupported command field "${key}"`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function decodeValue(bytes, offset = 0) {
|
|
125
|
+
const head = bytes[offset++];
|
|
126
|
+
if (head <= 0x7f) return {value: head, format: "positive-fixint", offset, raw: bytes.slice(offset - 1, offset)};
|
|
127
|
+
if (head >= 0xe0) return {
|
|
128
|
+
value: (head << 24) >> 24,
|
|
129
|
+
format: "negative-fixint",
|
|
130
|
+
offset,
|
|
131
|
+
raw: bytes.slice(offset - 1, offset)
|
|
132
|
+
};
|
|
133
|
+
if ((head & 0xf0) === 0x80) {
|
|
134
|
+
const count = head & 0x0f;
|
|
135
|
+
const entries = [];
|
|
136
|
+
const obj = {};
|
|
137
|
+
for (let i = 0; i < count; i++) {
|
|
138
|
+
const keyNode = decodeValue(bytes, offset);
|
|
139
|
+
offset = keyNode.offset;
|
|
140
|
+
const valueNode = decodeValue(bytes, offset);
|
|
141
|
+
offset = valueNode.offset;
|
|
142
|
+
entries.push({key: keyNode.value, keyNode, valueNode});
|
|
143
|
+
obj[String(keyNode.value)] = valueNode.value;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
value: obj,
|
|
147
|
+
format: "fixmap",
|
|
148
|
+
entries,
|
|
149
|
+
headerBytes: bytes.slice(offset - 1 - (count * 2), offset),
|
|
150
|
+
offset,
|
|
151
|
+
raw: bytes.slice(offset - (offset - (offset - 1)), offset)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if ((head & 0xf0) === 0x90) {
|
|
155
|
+
const count = head & 0x0f;
|
|
156
|
+
const items = [];
|
|
157
|
+
for (let i = 0; i < count; i++) {
|
|
158
|
+
const item = decodeValue(bytes, offset);
|
|
159
|
+
offset = item.offset;
|
|
160
|
+
items.push(item);
|
|
161
|
+
}
|
|
162
|
+
return {value: items.map((item) => item.value), format: "fixarray", items, offset, raw: bytes.slice(0, offset)};
|
|
163
|
+
}
|
|
164
|
+
if ((head & 0xe0) === 0xa0) {
|
|
165
|
+
const len = head & 0x1f;
|
|
166
|
+
const start = offset;
|
|
167
|
+
const end = start + len;
|
|
168
|
+
return {
|
|
169
|
+
value: decoder.decode(bytes.slice(start, end)),
|
|
170
|
+
format: "fixstr",
|
|
171
|
+
offset: end,
|
|
172
|
+
raw: bytes.slice(offset - 1, end)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
switch (head) {
|
|
176
|
+
case 0xc0:
|
|
177
|
+
return {value: null, format: "nil", offset, raw: bytes.slice(offset - 1, offset)};
|
|
178
|
+
case 0xc2:
|
|
179
|
+
return {value: false, format: "bool", offset, raw: bytes.slice(offset - 1, offset)};
|
|
180
|
+
case 0xc3:
|
|
181
|
+
return {value: true, format: "bool", offset, raw: bytes.slice(offset - 1, offset)};
|
|
182
|
+
case 0xc4: {
|
|
183
|
+
const len = bytes[offset++];
|
|
184
|
+
const start = offset;
|
|
185
|
+
const end = start + len;
|
|
186
|
+
return {value: bytes.slice(start, end), format: "bin8", offset: end, raw: bytes.slice(start - 2, end)};
|
|
187
|
+
}
|
|
188
|
+
case 0xc5: {
|
|
189
|
+
const len = (bytes[offset] << 8) | bytes[offset + 1];
|
|
190
|
+
offset += 2;
|
|
191
|
+
const start = offset;
|
|
192
|
+
const end = start + len;
|
|
193
|
+
return {value: bytes.slice(start, end), format: "bin16", offset: end, raw: bytes.slice(start - 3, end)};
|
|
194
|
+
}
|
|
195
|
+
case 0xc6: {
|
|
196
|
+
const len = (bytes[offset] * 0x1000000) + ((bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]);
|
|
197
|
+
offset += 4;
|
|
198
|
+
const start = offset;
|
|
199
|
+
const end = start + len;
|
|
200
|
+
return {value: bytes.slice(start, end), format: "bin32", offset: end, raw: bytes.slice(start - 5, end)};
|
|
201
|
+
}
|
|
202
|
+
case 0xca:
|
|
203
|
+
return {
|
|
204
|
+
value: new DataView(bytes.buffer, bytes.byteOffset + offset, 4).getFloat32(0, false),
|
|
205
|
+
format: "float32",
|
|
206
|
+
offset: offset + 4,
|
|
207
|
+
raw: bytes.slice(offset - 1, offset + 4)
|
|
208
|
+
};
|
|
209
|
+
case 0xcb:
|
|
210
|
+
return {
|
|
211
|
+
value: new DataView(bytes.buffer, bytes.byteOffset + offset, 8).getFloat64(0, false),
|
|
212
|
+
format: "float64",
|
|
213
|
+
offset: offset + 8,
|
|
214
|
+
raw: bytes.slice(offset - 1, offset + 8)
|
|
215
|
+
};
|
|
216
|
+
case 0xcc:
|
|
217
|
+
return {
|
|
218
|
+
value: bytes[offset],
|
|
219
|
+
format: "uint8",
|
|
220
|
+
offset: offset + 1,
|
|
221
|
+
raw: bytes.slice(offset - 1, offset + 1)
|
|
222
|
+
};
|
|
223
|
+
case 0xcd:
|
|
224
|
+
return {
|
|
225
|
+
value: (bytes[offset] << 8) | bytes[offset + 1],
|
|
226
|
+
format: "uint16",
|
|
227
|
+
offset: offset + 2,
|
|
228
|
+
raw: bytes.slice(offset - 1, offset + 2)
|
|
229
|
+
};
|
|
230
|
+
case 0xce:
|
|
231
|
+
return {
|
|
232
|
+
value: (((bytes[offset] * 0x1000000) + ((bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3])) >>> 0),
|
|
233
|
+
format: "uint32",
|
|
234
|
+
offset: offset + 4,
|
|
235
|
+
raw: bytes.slice(offset - 1, offset + 4)
|
|
236
|
+
};
|
|
237
|
+
case 0xd0:
|
|
238
|
+
return {
|
|
239
|
+
value: (bytes[offset] << 24) >> 24,
|
|
240
|
+
format: "int8",
|
|
241
|
+
offset: offset + 1,
|
|
242
|
+
raw: bytes.slice(offset - 1, offset + 1)
|
|
243
|
+
};
|
|
244
|
+
case 0xd1:
|
|
245
|
+
return {
|
|
246
|
+
value: new DataView(bytes.buffer, bytes.byteOffset + offset, 2).getInt16(0, false),
|
|
247
|
+
format: "int16",
|
|
248
|
+
offset: offset + 2,
|
|
249
|
+
raw: bytes.slice(offset - 1, offset + 2)
|
|
250
|
+
};
|
|
251
|
+
case 0xd2:
|
|
252
|
+
return {
|
|
253
|
+
value: new DataView(bytes.buffer, bytes.byteOffset + offset, 4).getInt32(0, false),
|
|
254
|
+
format: "int32",
|
|
255
|
+
offset: offset + 4,
|
|
256
|
+
raw: bytes.slice(offset - 1, offset + 4)
|
|
257
|
+
};
|
|
258
|
+
case 0xd9: {
|
|
259
|
+
const len = bytes[offset++];
|
|
260
|
+
const start = offset;
|
|
261
|
+
const end = start + len;
|
|
262
|
+
return {
|
|
263
|
+
value: decoder.decode(bytes.slice(start, end)),
|
|
264
|
+
format: "str8",
|
|
265
|
+
offset: end,
|
|
266
|
+
raw: bytes.slice(start - 2, end)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
case 0xda: {
|
|
270
|
+
const len = (bytes[offset] << 8) | bytes[offset + 1];
|
|
271
|
+
offset += 2;
|
|
272
|
+
const start = offset;
|
|
273
|
+
const end = start + len;
|
|
274
|
+
return {
|
|
275
|
+
value: decoder.decode(bytes.slice(start, end)),
|
|
276
|
+
format: "str16",
|
|
277
|
+
offset: end,
|
|
278
|
+
raw: bytes.slice(start - 3, end)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
case 0xdc: {
|
|
282
|
+
const count = (bytes[offset] << 8) | bytes[offset + 1];
|
|
283
|
+
offset += 2;
|
|
284
|
+
const items = [];
|
|
285
|
+
for (let i = 0; i < count; i++) {
|
|
286
|
+
const item = decodeValue(bytes, offset);
|
|
287
|
+
offset = item.offset;
|
|
288
|
+
items.push(item);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
value: items.map((item) => item.value),
|
|
292
|
+
format: "array16",
|
|
293
|
+
items,
|
|
294
|
+
offset,
|
|
295
|
+
raw: bytes.slice(0, offset)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
case 0xde: {
|
|
299
|
+
const count = (bytes[offset] << 8) | bytes[offset + 1];
|
|
300
|
+
offset += 2;
|
|
301
|
+
const entries = [];
|
|
302
|
+
const obj = {};
|
|
303
|
+
for (let i = 0; i < count; i++) {
|
|
304
|
+
const keyNode = decodeValue(bytes, offset);
|
|
305
|
+
offset = keyNode.offset;
|
|
306
|
+
const valueNode = decodeValue(bytes, offset);
|
|
307
|
+
offset = valueNode.offset;
|
|
308
|
+
entries.push({key: keyNode.value, keyNode, valueNode});
|
|
309
|
+
obj[String(keyNode.value)] = valueNode.value;
|
|
310
|
+
}
|
|
311
|
+
return {value: obj, format: "map16", entries, offset, raw: bytes.slice(0, offset)};
|
|
312
|
+
}
|
|
313
|
+
default:
|
|
314
|
+
throw new Error(`Unsupported msgpack token 0x${head.toString(16)}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Value-only decoder used for all inbound traffic.
|
|
319
|
+
//
|
|
320
|
+
// `decodeValue` above annotates every node with its `raw` byte slice and format
|
|
321
|
+
// so outgoing commands can be re-encoded byte-for-byte. Containers slice from
|
|
322
|
+
// the start of the buffer, which makes decoding quadratic in packet size, and
|
|
323
|
+
// `decodeMsgpack` discarded all of it. This path allocates only the decoded
|
|
324
|
+
// values. The annotated decoder stays exported for the command-signing path and
|
|
325
|
+
// external tooling.
|
|
326
|
+
let cursor = 0;
|
|
327
|
+
|
|
328
|
+
function readFast(bytes) {
|
|
329
|
+
const head = bytes[cursor++];
|
|
330
|
+
|
|
331
|
+
if (head <= 0x7f) return head;
|
|
332
|
+
if (head >= 0xe0) return (head << 24) >> 24;
|
|
333
|
+
|
|
334
|
+
if ((head & 0xf0) === 0x80) return readMapFast(bytes, head & 0x0f);
|
|
335
|
+
if ((head & 0xf0) === 0x90) return readArrayFast(bytes, head & 0x0f);
|
|
336
|
+
if ((head & 0xe0) === 0xa0) return readStrFast(bytes, head & 0x1f);
|
|
337
|
+
|
|
338
|
+
switch (head) {
|
|
339
|
+
case 0xc0:
|
|
340
|
+
return null;
|
|
341
|
+
case 0xc2:
|
|
342
|
+
return false;
|
|
343
|
+
case 0xc3:
|
|
344
|
+
return true;
|
|
345
|
+
case 0xc4:
|
|
346
|
+
return readBinFast(bytes, bytes[cursor++]);
|
|
347
|
+
case 0xc5: {
|
|
348
|
+
const len = (bytes[cursor] << 8) | bytes[cursor + 1];
|
|
349
|
+
cursor += 2;
|
|
350
|
+
return readBinFast(bytes, len);
|
|
351
|
+
}
|
|
352
|
+
case 0xc6: {
|
|
353
|
+
const len = (bytes[cursor] * 0x1000000) + ((bytes[cursor + 1] << 16) | (bytes[cursor + 2] << 8) | bytes[cursor + 3]);
|
|
354
|
+
cursor += 4;
|
|
355
|
+
return readBinFast(bytes, len);
|
|
356
|
+
}
|
|
357
|
+
case 0xca: {
|
|
358
|
+
const value = new DataView(bytes.buffer, bytes.byteOffset + cursor, 4).getFloat32(0, false);
|
|
359
|
+
cursor += 4;
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
case 0xcb: {
|
|
363
|
+
const value = new DataView(bytes.buffer, bytes.byteOffset + cursor, 8).getFloat64(0, false);
|
|
364
|
+
cursor += 8;
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
case 0xcc:
|
|
368
|
+
return bytes[cursor++];
|
|
369
|
+
case 0xcd: {
|
|
370
|
+
const value = (bytes[cursor] << 8) | bytes[cursor + 1];
|
|
371
|
+
cursor += 2;
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
case 0xce: {
|
|
375
|
+
const value = (((bytes[cursor] * 0x1000000) + ((bytes[cursor + 1] << 16) | (bytes[cursor + 2] << 8) | bytes[cursor + 3])) >>> 0);
|
|
376
|
+
cursor += 4;
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
case 0xd0:
|
|
380
|
+
return (bytes[cursor++] << 24) >> 24;
|
|
381
|
+
case 0xd1: {
|
|
382
|
+
const value = ((((bytes[cursor] << 8) | bytes[cursor + 1]) << 16) >> 16);
|
|
383
|
+
cursor += 2;
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
case 0xd2: {
|
|
387
|
+
const value = ((bytes[cursor] << 24) | (bytes[cursor + 1] << 16) | (bytes[cursor + 2] << 8) | bytes[cursor + 3]) | 0;
|
|
388
|
+
cursor += 4;
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
case 0xd9:
|
|
392
|
+
return readStrFast(bytes, bytes[cursor++]);
|
|
393
|
+
case 0xda: {
|
|
394
|
+
const len = (bytes[cursor] << 8) | bytes[cursor + 1];
|
|
395
|
+
cursor += 2;
|
|
396
|
+
return readStrFast(bytes, len);
|
|
397
|
+
}
|
|
398
|
+
case 0xdc: {
|
|
399
|
+
const count = (bytes[cursor] << 8) | bytes[cursor + 1];
|
|
400
|
+
cursor += 2;
|
|
401
|
+
return readArrayFast(bytes, count);
|
|
402
|
+
}
|
|
403
|
+
case 0xde: {
|
|
404
|
+
const count = (bytes[cursor] << 8) | bytes[cursor + 1];
|
|
405
|
+
cursor += 2;
|
|
406
|
+
return readMapFast(bytes, count);
|
|
407
|
+
}
|
|
408
|
+
default:
|
|
409
|
+
throw new Error(`Unsupported msgpack token 0x${head.toString(16)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function readStrFast(bytes, len) {
|
|
414
|
+
const start = cursor;
|
|
415
|
+
cursor += len;
|
|
416
|
+
// subarray, not slice: TextDecoder copies into the result string anyway.
|
|
417
|
+
return decoder.decode(bytes.subarray(start, cursor));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function readBinFast(bytes, len) {
|
|
421
|
+
const start = cursor;
|
|
422
|
+
cursor += len;
|
|
423
|
+
// slice, not subarray: callers retain these payloads past the socket buffer.
|
|
424
|
+
return bytes.slice(start, cursor);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function readArrayFast(bytes, count) {
|
|
428
|
+
const items = new Array(count);
|
|
429
|
+
for (let i = 0; i < count; i++) items[i] = readFast(bytes);
|
|
430
|
+
return items;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function readMapFast(bytes, count) {
|
|
434
|
+
const obj = {};
|
|
435
|
+
for (let i = 0; i < count; i++) {
|
|
436
|
+
const key = readFast(bytes);
|
|
437
|
+
obj[typeof key === "string" ? key : String(key)] = readFast(bytes);
|
|
438
|
+
}
|
|
439
|
+
return obj;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function decodeMsgpack(bytes) {
|
|
443
|
+
const input = toUint8Array(bytes);
|
|
444
|
+
cursor = 0;
|
|
445
|
+
return readFast(input);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Growable output buffer. The recursive encoder used to build an array of small
|
|
449
|
+
// Uint8Array fragments per value and concatenate them at every nesting level.
|
|
450
|
+
class MsgpackWriter {
|
|
451
|
+
constructor(capacity = 256) {
|
|
452
|
+
this.bytes = new Uint8Array(capacity);
|
|
453
|
+
this.length = 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#reserve(extra) {
|
|
457
|
+
const needed = this.length + extra;
|
|
458
|
+
if (needed <= this.bytes.length) return;
|
|
459
|
+
let size = this.bytes.length || 64;
|
|
460
|
+
while (size < needed) size *= 2;
|
|
461
|
+
const grown = new Uint8Array(size);
|
|
462
|
+
grown.set(this.bytes.subarray(0, this.length));
|
|
463
|
+
this.bytes = grown;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
byte(value) {
|
|
467
|
+
this.#reserve(1);
|
|
468
|
+
this.bytes[this.length++] = value & 0xff;
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
raw(chunk) {
|
|
473
|
+
this.#reserve(chunk.length);
|
|
474
|
+
this.bytes.set(chunk, this.length);
|
|
475
|
+
this.length += chunk.length;
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
result() {
|
|
480
|
+
return this.bytes.slice(0, this.length);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function writeMsgpack(writer, value) {
|
|
485
|
+
if (value == null) return writer.byte(0xc0);
|
|
486
|
+
if (typeof value === "boolean") return writer.byte(value ? 0xc3 : 0xc2);
|
|
487
|
+
if (typeof value === "number") return writer.raw(encodeMsgpackNumber(value));
|
|
488
|
+
if (typeof value === "string") return writer.raw(encodeStringBytes(value));
|
|
489
|
+
|
|
490
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
491
|
+
const bytes = toUint8Array(value);
|
|
492
|
+
if (bytes.length <= 0xff) writer.byte(0xc4).byte(bytes.length);
|
|
493
|
+
else if (bytes.length <= 0xffff) writer.byte(0xc5).byte(bytes.length >>> 8).byte(bytes.length);
|
|
494
|
+
else writer.byte(0xc6).byte(bytes.length >>> 24).byte(bytes.length >>> 16).byte(bytes.length >>> 8).byte(bytes.length);
|
|
495
|
+
return writer.raw(bytes);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (Array.isArray(value)) {
|
|
499
|
+
const len = value.length;
|
|
500
|
+
if (len <= 15) writer.byte(0x90 | len);
|
|
501
|
+
else writer.byte(0xdc).byte(len >>> 8).byte(len);
|
|
502
|
+
for (const item of value) writeMsgpack(writer, item);
|
|
503
|
+
return writer;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (typeof value === "object") {
|
|
507
|
+
const entries = Object.entries(value);
|
|
508
|
+
if (entries.length <= 15) writer.byte(0x80 | entries.length);
|
|
509
|
+
else writer.byte(0xde).byte(entries.length >>> 8).byte(entries.length);
|
|
510
|
+
for (const [key, val] of entries) {
|
|
511
|
+
writer.raw(encodeStringBytes(key));
|
|
512
|
+
writeMsgpack(writer, val);
|
|
513
|
+
}
|
|
514
|
+
return writer;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
throw new Error(`Unsupported msgpack value: ${typeof value}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function encodeMsgpackNumber(value) {
|
|
521
|
+
if (!Number.isInteger(value)) return encodeNumberWithFormat(value, "float32");
|
|
522
|
+
if (value >= 0) {
|
|
523
|
+
if (value <= 0x7f) return encodeNumberWithFormat(value, "positive-fixint");
|
|
524
|
+
if (value <= 0xff) return encodeNumberWithFormat(value, "uint8");
|
|
525
|
+
if (value <= 0xffff) return encodeNumberWithFormat(value, "uint16");
|
|
526
|
+
return encodeNumberWithFormat(value, "uint32");
|
|
527
|
+
}
|
|
528
|
+
if (value >= -32) return encodeNumberWithFormat(value, "negative-fixint");
|
|
529
|
+
if (value >= -128) return encodeNumberWithFormat(value, "int8");
|
|
530
|
+
if (value >= -32768) return encodeNumberWithFormat(value, "int16");
|
|
531
|
+
return encodeNumberWithFormat(value, "int32");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function encodeMsgpack(value) {
|
|
535
|
+
// Scalars need no buffer at all.
|
|
536
|
+
if (value == null) return Uint8Array.of(0xc0);
|
|
537
|
+
if (typeof value === "boolean") return Uint8Array.of(value ? 0xc3 : 0xc2);
|
|
538
|
+
if (typeof value === "number") return encodeMsgpackNumber(value);
|
|
539
|
+
if (typeof value === "string") return encodeStringBytes(value);
|
|
540
|
+
const writer = new MsgpackWriter();
|
|
541
|
+
writeMsgpack(writer, value);
|
|
542
|
+
return writer.result();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export {
|
|
546
|
+
cloneDragValue,
|
|
547
|
+
cloneCommand,
|
|
548
|
+
encodeStringBytes,
|
|
549
|
+
encodeNumberWithFormat,
|
|
550
|
+
encodeMapHeader,
|
|
551
|
+
encodeValue,
|
|
552
|
+
decodeValue,
|
|
553
|
+
decodeMsgpack,
|
|
554
|
+
encodeMsgpack
|
|
555
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decodeIncomingFrame(data: string | Uint8Array | ArrayBuffer | number[]): unknown;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {decodeMsgpack} from "../codec/msgpack.js";
|
|
2
|
+
import {toUint8Array} from "../codec/binary.js";
|
|
3
|
+
|
|
4
|
+
// Convert a websocket payload into the transport-independent packet object
|
|
5
|
+
// consumed by the client and state reducers.
|
|
6
|
+
export function decodeIncomingFrame(data) {
|
|
7
|
+
return typeof data === "string" ? JSON.parse(data) : decodeMsgpack(toUint8Array(data));
|
|
8
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function decodeWorldPayload(data: Uint8Array | ArrayBuffer | number[], worldId: number, seed: number): unknown;
|
|
2
|
+
export function decodeModelPayload(data: Uint8Array | ArrayBuffer | number[], worldId: number, seed: number): Uint8Array;
|
|
3
|
+
export function decompressWorldChunk(data: Uint8Array | ArrayBuffer | number[]): Uint8Array;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {decompressLz4Frame} from "../../compression/lz4.js";
|
|
2
|
+
import {decryptPayload} from "../../crypto/chacha.js";
|
|
3
|
+
import {decodeMsgpack} from "../codec/msgpack.js";
|
|
4
|
+
|
|
5
|
+
export function decodeWorldPayload(data, worldId, seed) {
|
|
6
|
+
if (!data || seed == null) return null;
|
|
7
|
+
try {
|
|
8
|
+
return decodeMsgpack(decryptPayload(data, worldId, seed));
|
|
9
|
+
} catch (_) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function decodeModelPayload(data, worldId, seed) {
|
|
15
|
+
if (!data || seed == null) throw new Error("Encrypted model payload requires a world seed");
|
|
16
|
+
return decryptPayload(data, worldId, seed);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function decompressWorldChunk(data) {
|
|
20
|
+
return decompressLz4Frame(data);
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./codec/msgpack.js";
|
|
2
|
+
export * from "./codec/binary.js";
|
|
3
|
+
export * from "./inbound/frame.js";
|
|
4
|
+
export * from "./inbound/world-payload.js";
|
|
5
|
+
export * from "./outbound/blueprint.js";
|
|
6
|
+
export * from "./outbound/comms.js";
|
|
7
|
+
export * from "./outbound/commands.js";
|
|
8
|
+
export * from "./outbound/inventory.js";
|
|
9
|
+
export * from "./outbound/ship-management.js";
|
|
10
|
+
export * from "./outbound/sign.js";
|
|
11
|
+
export * from "./outbound/ui-config.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {decodeMsgpack, encodeMsgpack} from "./codec/msgpack.js";
|
|
2
|
+
export {toUint8Array, concatBytes, valuesEqual} from "./codec/binary.js";
|
|
3
|
+
export {decodeIncomingFrame} from "./inbound/frame.js";
|
|
4
|
+
export {decodeWorldPayload, decodeModelPayload, decompressWorldChunk} from "./inbound/world-payload.js";
|
|
5
|
+
export {buildBlueprintPlacementMessage} from "./outbound/blueprint.js";
|
|
6
|
+
export {buildCommsMessage, flattenRichText, normalizeCommsEvent} from "./outbound/comms.js";
|
|
7
|
+
export {buildSignedCommandPacket} from "./outbound/commands.js";
|
|
8
|
+
export {
|
|
9
|
+
buildEquipItemCommand,
|
|
10
|
+
buildInventoryDragCommand,
|
|
11
|
+
buildUnequipItemCommand,
|
|
12
|
+
equipmentSlotName,
|
|
13
|
+
normalizeEquipmentSlot,
|
|
14
|
+
normalizeInventoryEvent,
|
|
15
|
+
EquipmentSlot
|
|
16
|
+
} from "./outbound/inventory.js";
|
|
17
|
+
export * from "./outbound/ship-management.js";
|
|
18
|
+
export * from "./outbound/sign.js";
|
|
19
|
+
export * from "./outbound/ui-config.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface BlueprintPlacement {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
w?: number;
|
|
7
|
+
h?: number;
|
|
8
|
+
source: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BlueprintPlacementMessage {
|
|
12
|
+
type: 9;
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
w: number;
|
|
16
|
+
h: number;
|
|
17
|
+
source: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildBlueprintPlacementMessage(placement: BlueprintPlacement): BlueprintPlacementMessage;
|
|
21
|
+
|
|
22
|
+
export const BLUEPRINT_PLACEMENT_TYPE: 9;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const BLUEPRINT_PLACEMENT_TYPE = 9;
|
|
2
|
+
|
|
3
|
+
function requireFiniteNumber(value, name) {
|
|
4
|
+
const number = Number(value);
|
|
5
|
+
if (!Number.isFinite(number)) throw new TypeError(`${name} must be a finite number`);
|
|
6
|
+
return number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function requirePositiveInteger(value, name) {
|
|
10
|
+
const number = Number(value);
|
|
11
|
+
if (!Number.isInteger(number) || number <= 0) throw new RangeError(`${name} must be a positive integer`);
|
|
12
|
+
return number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeBlueprintDimension(placement, publicName, protocolName) {
|
|
16
|
+
const value = placement[publicName] ?? placement[protocolName];
|
|
17
|
+
return requirePositiveInteger(value, publicName);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildBlueprintPlacementMessage(placement = {}) {
|
|
21
|
+
const source = placement.source;
|
|
22
|
+
if (typeof source !== "string" || !source) throw new TypeError("source must be a non-empty blueprint string");
|
|
23
|
+
return {
|
|
24
|
+
type: BLUEPRINT_PLACEMENT_TYPE,
|
|
25
|
+
x: requireFiniteNumber(placement.x, "x"),
|
|
26
|
+
y: requireFiniteNumber(placement.y, "y"),
|
|
27
|
+
w: normalizeBlueprintDimension(placement, "width", "w"),
|
|
28
|
+
h: normalizeBlueprintDimension(placement, "height", "h"),
|
|
29
|
+
source
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
BLUEPRINT_PLACEMENT_TYPE,
|
|
35
|
+
buildBlueprintPlacementMessage
|
|
36
|
+
};
|