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 @@
|
|
|
1
|
+
export {DredlessClient, WrenchMode, TurretMode} from "./core.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decompressLz4Frame(bytes: Uint8Array | ArrayBuffer | number[]): Uint8Array;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {toUint8Array} from "../protocol/codec/binary.js";
|
|
2
|
+
|
|
3
|
+
// Growable typed-array sink. LZ4 block headers carry the compressed size, not
|
|
4
|
+
// the decompressed size, so the output buffer starts at a generous multiple and
|
|
5
|
+
// doubles on demand rather than pushing into a plain array one byte at a time.
|
|
6
|
+
function ensureCapacity(buffer, needed) {
|
|
7
|
+
if (needed <= buffer.length) return buffer;
|
|
8
|
+
let size = buffer.length || 64;
|
|
9
|
+
while (size < needed) size *= 2;
|
|
10
|
+
const grown = new Uint8Array(size);
|
|
11
|
+
grown.set(buffer);
|
|
12
|
+
return grown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function decompressLz4Block(block) {
|
|
16
|
+
let out = new Uint8Array(Math.max(64, block.length * 4));
|
|
17
|
+
let len = 0;
|
|
18
|
+
let pos = 0;
|
|
19
|
+
|
|
20
|
+
while (pos < block.length) {
|
|
21
|
+
const token = block[pos++];
|
|
22
|
+
|
|
23
|
+
let literalLen = token >>> 4;
|
|
24
|
+
if (literalLen === 15) {
|
|
25
|
+
let b;
|
|
26
|
+
do {
|
|
27
|
+
if (pos >= block.length) throw new Error("truncated lz4 literal length");
|
|
28
|
+
b = block[pos++];
|
|
29
|
+
literalLen += b;
|
|
30
|
+
} while (b === 255);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (pos + literalLen > block.length) throw new Error("truncated lz4 literals");
|
|
34
|
+
if (literalLen > 0) {
|
|
35
|
+
out = ensureCapacity(out, len + literalLen);
|
|
36
|
+
out.set(block.subarray(pos, pos + literalLen), len);
|
|
37
|
+
len += literalLen;
|
|
38
|
+
pos += literalLen;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (pos >= block.length) break;
|
|
42
|
+
if (pos + 2 > block.length) throw new Error("truncated lz4 offset");
|
|
43
|
+
const offset = block[pos] | (block[pos + 1] << 8);
|
|
44
|
+
pos += 2;
|
|
45
|
+
if (offset === 0 || offset > len) throw new Error(`invalid lz4 offset ${offset}`);
|
|
46
|
+
|
|
47
|
+
let matchLen = token & 0x0f;
|
|
48
|
+
if (matchLen === 15) {
|
|
49
|
+
let b;
|
|
50
|
+
do {
|
|
51
|
+
if (pos >= block.length) throw new Error("truncated lz4 match length");
|
|
52
|
+
b = block[pos++];
|
|
53
|
+
matchLen += b;
|
|
54
|
+
} while (b === 255);
|
|
55
|
+
}
|
|
56
|
+
matchLen += 4;
|
|
57
|
+
|
|
58
|
+
out = ensureCapacity(out, len + matchLen);
|
|
59
|
+
// Matches may overlap the region being written, so copy byte by byte rather
|
|
60
|
+
// than with copyWithin, which would read the pre-copy bytes.
|
|
61
|
+
let from = len - offset;
|
|
62
|
+
for (let i = 0; i < matchLen; i++) out[len++] = out[from++];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Return a compact standalone array, matching the previous contract: callers
|
|
66
|
+
// must not see a view over a larger backing buffer.
|
|
67
|
+
return out.length === len ? out : out.slice(0, len);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function decompressLz4Frame(bytes) {
|
|
71
|
+
const buf = toUint8Array(bytes);
|
|
72
|
+
if (buf.length < 7) return buf;
|
|
73
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
74
|
+
if (view.getUint32(0, true) !== 0x184d2204) return buf;
|
|
75
|
+
const flg = buf[4];
|
|
76
|
+
let pos = 6;
|
|
77
|
+
if (flg & 0x08) pos += 8;
|
|
78
|
+
if (flg & 0x01) pos += 4;
|
|
79
|
+
pos += 1;
|
|
80
|
+
const chunks = [];
|
|
81
|
+
let total = 0;
|
|
82
|
+
while (pos + 4 <= buf.length) {
|
|
83
|
+
const rawSize = view.getUint32(pos, true);
|
|
84
|
+
pos += 4;
|
|
85
|
+
if (rawSize === 0) break;
|
|
86
|
+
const uncompressed = (rawSize & 0x80000000) !== 0;
|
|
87
|
+
const size = rawSize & 0x7fffffff;
|
|
88
|
+
if (pos + size > buf.length) throw new Error("truncated lz4 block");
|
|
89
|
+
const block = buf.subarray(pos, pos + size);
|
|
90
|
+
const chunk = uncompressed ? block.slice() : decompressLz4Block(block);
|
|
91
|
+
chunks.push(chunk);
|
|
92
|
+
total += chunk.length;
|
|
93
|
+
pos += size;
|
|
94
|
+
if (flg & 0x10) pos += 4;
|
|
95
|
+
}
|
|
96
|
+
// Single-chunk frames are the common case and need no concatenation pass.
|
|
97
|
+
if (chunks.length === 1) return chunks[0];
|
|
98
|
+
if (chunks.length === 0) return new Uint8Array(0);
|
|
99
|
+
const out = new Uint8Array(total);
|
|
100
|
+
let offset = 0;
|
|
101
|
+
for (const chunk of chunks) {
|
|
102
|
+
out.set(chunk, offset);
|
|
103
|
+
offset += chunk.length;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
decompressLz4Block,
|
|
111
|
+
decompressLz4Frame
|
|
112
|
+
};
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = "https://drednot.io";
|
|
2
|
+
export const DEFAULT_GAME_VERSION = null;
|
|
3
|
+
export const DEFAULT_NOTICE_VERSION = 17;
|
|
4
|
+
|
|
5
|
+
export const encoder = new TextEncoder();
|
|
6
|
+
export const decoder = new TextDecoder("utf-8");
|
|
7
|
+
|
|
8
|
+
export const SHARED_CHACHA_MATERIAL = Uint8Array.from([
|
|
9
|
+
0x15, 0x85, 0x59, 0xc6, 0x26, 0xc4, 0x31, 0x32,
|
|
10
|
+
0xb8, 0xed, 0xb2, 0xfe, 0x5b, 0x87, 0x5e, 0x5e,
|
|
11
|
+
0x3d, 0xd9, 0xe3, 0xcc, 0xa3, 0x9b, 0xd6, 0x58,
|
|
12
|
+
0xe2, 0xdc, 0x0e, 0x8b, 0x96, 0xb7, 0x05, 0x88,
|
|
13
|
+
0x78, 0xd1, 0xd4, 0xa4, 0x19, 0x3c, 0xb9, 0x1e,
|
|
14
|
+
0xbe, 0x78, 0x18, 0x12
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const JOIN_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
|
|
18
|
+
export const KEEPALIVE_INTERVAL_MS = 15_000;
|
|
19
|
+
export const INITIAL_OUTFIT_MESSAGE = {
|
|
20
|
+
type: 7,
|
|
21
|
+
outfit: {
|
|
22
|
+
color_body: 4695355,
|
|
23
|
+
color_legs: 1393785,
|
|
24
|
+
color_skin: 13212550,
|
|
25
|
+
color_feet: 6242065,
|
|
26
|
+
color_hair: 1118481,
|
|
27
|
+
style_hair: 0,
|
|
28
|
+
color_hat: 16716049,
|
|
29
|
+
style_hat: 0,
|
|
30
|
+
color_face_1: 8421504,
|
|
31
|
+
style_face_1: 0,
|
|
32
|
+
color_face_2: 8421504,
|
|
33
|
+
style_face_2: 0
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const KEEPALIVE_MESSAGE = {type: 2, msg: "/"};
|
|
37
|
+
|
|
38
|
+
export const COMMAND_FIELDS = [
|
|
39
|
+
"type", "n", "x", "y", "mx", "my", "vx", "vy",
|
|
40
|
+
"jump", "jump_held", "drop", "act1", "act1_held", "exit", "act2", "act_alt", "act_alt_held",
|
|
41
|
+
"wrench_mode", "turret_mode", "scr_w", "scr_h", "motion",
|
|
42
|
+
"focus_ent", "config_ent", "tip_select", "inv_slot", "blur", "drag"
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const COMMAND_DEFAULT_FORMATS = {
|
|
46
|
+
type: "positive-fixint",
|
|
47
|
+
n: "uint16",
|
|
48
|
+
x: "float32",
|
|
49
|
+
y: "float32",
|
|
50
|
+
mx: "float32",
|
|
51
|
+
my: "float32",
|
|
52
|
+
vx: "float32",
|
|
53
|
+
vy: "float32",
|
|
54
|
+
jump: "bool",
|
|
55
|
+
jump_held: "bool",
|
|
56
|
+
drop: "bool",
|
|
57
|
+
act1: "bool",
|
|
58
|
+
act1_held: "bool",
|
|
59
|
+
exit: "bool",
|
|
60
|
+
act2: "bool",
|
|
61
|
+
act_alt: "bool",
|
|
62
|
+
act_alt_held: "bool",
|
|
63
|
+
wrench_mode: "positive-fixint",
|
|
64
|
+
turret_mode: "positive-fixint",
|
|
65
|
+
scr_w: "uint16",
|
|
66
|
+
scr_h: "uint16",
|
|
67
|
+
motion: "float32",
|
|
68
|
+
focus_ent: "nil",
|
|
69
|
+
config_ent: "nil",
|
|
70
|
+
tip_select: "nil",
|
|
71
|
+
inv_slot: "positive-fixint",
|
|
72
|
+
blur: "bool",
|
|
73
|
+
drag: "nil"
|
|
74
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {ChaCha20Backend} from "./chacha.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node-only. Installs OpenSSL's `chacha20` as the ChaCha20 backend when it is
|
|
5
|
+
* available. Returns false when the cipher is not present in this build.
|
|
6
|
+
*/
|
|
7
|
+
export function enableNativeChaCha20(): boolean;
|
|
8
|
+
|
|
9
|
+
export const nativeChaCha20: ChaCha20Backend;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Node-only ChaCha20 backend.
|
|
2
|
+
//
|
|
3
|
+
// OpenSSL exposes a raw `chacha20` stream cipher whose keystream matches
|
|
4
|
+
// src/crypto/chacha.js exactly, and it is roughly 27x faster on 64 KB payloads.
|
|
5
|
+
// This module is imported only from src/node.js so that browser bundles never
|
|
6
|
+
// encounter a `node:crypto` specifier.
|
|
7
|
+
import {createCipheriv, getCiphers} from "node:crypto";
|
|
8
|
+
|
|
9
|
+
import {installNativeChaCha20} from "./chacha.js";
|
|
10
|
+
|
|
11
|
+
// OpenSSL takes a 16-byte IV: a 32-bit little-endian block counter followed by
|
|
12
|
+
// the 12-byte nonce. The JS implementation starts its counter at zero.
|
|
13
|
+
function buildIv(nonce) {
|
|
14
|
+
const iv = new Uint8Array(16);
|
|
15
|
+
iv.set(nonce, 4);
|
|
16
|
+
return iv;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function nativeChaCha20(key, nonce, out, source) {
|
|
20
|
+
try {
|
|
21
|
+
const cipher = createCipheriv("chacha20", key, buildIv(nonce));
|
|
22
|
+
const input = source ?? new Uint8Array(out.length);
|
|
23
|
+
const head = cipher.update(input);
|
|
24
|
+
const tail = cipher.final();
|
|
25
|
+
if (head.length) out.set(head, 0);
|
|
26
|
+
if (tail.length) out.set(tail, head.length);
|
|
27
|
+
return out;
|
|
28
|
+
} catch (_) {
|
|
29
|
+
// Fall back to the portable implementation.
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function enableNativeChaCha20() {
|
|
35
|
+
try {
|
|
36
|
+
if (!getCiphers().includes("chacha20")) return false;
|
|
37
|
+
} catch (_) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
installNativeChaCha20(nativeChaCha20);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
enableNativeChaCha20,
|
|
47
|
+
nativeChaCha20
|
|
48
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function decryptPayload(wireBytes: Uint8Array | ArrayBuffer | number[], worldId: number, seed: number): Uint8Array;
|
|
2
|
+
|
|
3
|
+
export function chacha20(key: Uint8Array, nonce: Uint8Array, length: number): Uint8Array;
|
|
4
|
+
|
|
5
|
+
/** Generates the keystream and XORs it with `data` in a single pass. */
|
|
6
|
+
export function chacha20Xor(key: Uint8Array, nonce: Uint8Array, data: Uint8Array): Uint8Array;
|
|
7
|
+
|
|
8
|
+
export function rotl32(x: number, n: number): number;
|
|
9
|
+
|
|
10
|
+
export function rotr32(x: number, n: number): number;
|
|
11
|
+
|
|
12
|
+
export function quarterRound(state: Uint32Array, a: number, b: number, c: number, d: number): void;
|
|
13
|
+
|
|
14
|
+
export type ChaCha20Backend = (
|
|
15
|
+
key: Uint8Array,
|
|
16
|
+
nonce: Uint8Array,
|
|
17
|
+
out: Uint8Array,
|
|
18
|
+
source?: Uint8Array
|
|
19
|
+
) => Uint8Array | null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Installs a faster backend for large payloads. Node's entry point wires this to
|
|
23
|
+
* OpenSSL's `chacha20`; pass null to restore the portable implementation.
|
|
24
|
+
*/
|
|
25
|
+
export function installNativeChaCha20(cipherFactory: ChaCha20Backend | null): void;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {encoder, SHARED_CHACHA_MATERIAL} from "../constants.js";
|
|
2
|
+
import {toUint8Array} from "../protocol/codec/binary.js";
|
|
3
|
+
|
|
4
|
+
function rotl32(x, n) {
|
|
5
|
+
n &= 31;
|
|
6
|
+
return ((x << n) | (x >>> (32 - n))) >>> 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function rotr32(x, n) {
|
|
10
|
+
n &= 31;
|
|
11
|
+
return ((x >>> n) | (x << (32 - n))) >>> 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function quarterRound(state, a, b, c, d) {
|
|
15
|
+
state[a] = (state[a] + state[b]) >>> 0;
|
|
16
|
+
state[d] ^= state[a];
|
|
17
|
+
state[d] = rotl32(state[d], 16);
|
|
18
|
+
state[c] = (state[c] + state[d]) >>> 0;
|
|
19
|
+
state[b] ^= state[c];
|
|
20
|
+
state[b] = rotl32(state[b], 12);
|
|
21
|
+
state[a] = (state[a] + state[b]) >>> 0;
|
|
22
|
+
state[d] ^= state[a];
|
|
23
|
+
state[d] = rotl32(state[d], 8);
|
|
24
|
+
state[c] = (state[c] + state[d]) >>> 0;
|
|
25
|
+
state[b] ^= state[c];
|
|
26
|
+
state[b] = rotl32(state[b], 7);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Optional native backend. Node's OpenSSL exposes a raw `chacha20` stream cipher
|
|
30
|
+
// whose keystream is identical to this implementation, and it is ~27x faster on
|
|
31
|
+
// large payloads. It is installed by src/node.js rather than imported here so
|
|
32
|
+
// that browser bundles never see a `node:crypto` specifier.
|
|
33
|
+
let nativeBlockCipher = null;
|
|
34
|
+
|
|
35
|
+
function installNativeChaCha20(cipherFactory) {
|
|
36
|
+
nativeBlockCipher = typeof cipherFactory === "function" ? cipherFactory : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Native call overhead dominates below roughly this size; measured crossover is
|
|
40
|
+
// near 150 bytes, so stay in JS for the small per-tick payloads.
|
|
41
|
+
const NATIVE_MIN_BYTES = 256;
|
|
42
|
+
|
|
43
|
+
// Reused across blocks: the keystream words for one 64-byte block. Safe because
|
|
44
|
+
// generation is synchronous and single-threaded.
|
|
45
|
+
const SCRATCH_BLOCK = new Uint32Array(16);
|
|
46
|
+
|
|
47
|
+
function readLe32(bytes, offset) {
|
|
48
|
+
return (bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24)) >>> 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Writes `length` keystream bytes into `out` starting at 0, XORing against
|
|
52
|
+
// `source` when supplied. Keeping generation and XOR in one pass avoids
|
|
53
|
+
// allocating a full second buffer for the keystream.
|
|
54
|
+
function chacha20Into(out, key, nonce, length, source) {
|
|
55
|
+
const k0 = readLe32(key, 0), k1 = readLe32(key, 4), k2 = readLe32(key, 8), k3 = readLe32(key, 12);
|
|
56
|
+
const k4 = readLe32(key, 16), k5 = readLe32(key, 20), k6 = readLe32(key, 24), k7 = readLe32(key, 28);
|
|
57
|
+
const n0 = readLe32(nonce, 0), n1 = readLe32(nonce, 4), n2 = readLe32(nonce, 8);
|
|
58
|
+
|
|
59
|
+
let counter = 0;
|
|
60
|
+
let offset = 0;
|
|
61
|
+
const block = SCRATCH_BLOCK;
|
|
62
|
+
|
|
63
|
+
while (offset < length) {
|
|
64
|
+
let x0 = 0x61707865, x1 = 0x3320646e, x2 = 0x79622d32, x3 = 0x6b206574;
|
|
65
|
+
let x4 = k0, x5 = k1, x6 = k2, x7 = k3;
|
|
66
|
+
let x8 = k4, x9 = k5, x10 = k6, x11 = k7;
|
|
67
|
+
let x12 = counter, x13 = n0, x14 = n1, x15 = n2;
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < 10; i++) {
|
|
70
|
+
// column rounds
|
|
71
|
+
x0 = (x0 + x4) >>> 0;
|
|
72
|
+
x12 ^= x0;
|
|
73
|
+
x12 = (x12 << 16) | (x12 >>> 16);
|
|
74
|
+
x8 = (x8 + x12) >>> 0;
|
|
75
|
+
x4 ^= x8;
|
|
76
|
+
x4 = (x4 << 12) | (x4 >>> 20);
|
|
77
|
+
x0 = (x0 + x4) >>> 0;
|
|
78
|
+
x12 ^= x0;
|
|
79
|
+
x12 = (x12 << 8) | (x12 >>> 24);
|
|
80
|
+
x8 = (x8 + x12) >>> 0;
|
|
81
|
+
x4 ^= x8;
|
|
82
|
+
x4 = (x4 << 7) | (x4 >>> 25);
|
|
83
|
+
|
|
84
|
+
x1 = (x1 + x5) >>> 0;
|
|
85
|
+
x13 ^= x1;
|
|
86
|
+
x13 = (x13 << 16) | (x13 >>> 16);
|
|
87
|
+
x9 = (x9 + x13) >>> 0;
|
|
88
|
+
x5 ^= x9;
|
|
89
|
+
x5 = (x5 << 12) | (x5 >>> 20);
|
|
90
|
+
x1 = (x1 + x5) >>> 0;
|
|
91
|
+
x13 ^= x1;
|
|
92
|
+
x13 = (x13 << 8) | (x13 >>> 24);
|
|
93
|
+
x9 = (x9 + x13) >>> 0;
|
|
94
|
+
x5 ^= x9;
|
|
95
|
+
x5 = (x5 << 7) | (x5 >>> 25);
|
|
96
|
+
|
|
97
|
+
x2 = (x2 + x6) >>> 0;
|
|
98
|
+
x14 ^= x2;
|
|
99
|
+
x14 = (x14 << 16) | (x14 >>> 16);
|
|
100
|
+
x10 = (x10 + x14) >>> 0;
|
|
101
|
+
x6 ^= x10;
|
|
102
|
+
x6 = (x6 << 12) | (x6 >>> 20);
|
|
103
|
+
x2 = (x2 + x6) >>> 0;
|
|
104
|
+
x14 ^= x2;
|
|
105
|
+
x14 = (x14 << 8) | (x14 >>> 24);
|
|
106
|
+
x10 = (x10 + x14) >>> 0;
|
|
107
|
+
x6 ^= x10;
|
|
108
|
+
x6 = (x6 << 7) | (x6 >>> 25);
|
|
109
|
+
|
|
110
|
+
x3 = (x3 + x7) >>> 0;
|
|
111
|
+
x15 ^= x3;
|
|
112
|
+
x15 = (x15 << 16) | (x15 >>> 16);
|
|
113
|
+
x11 = (x11 + x15) >>> 0;
|
|
114
|
+
x7 ^= x11;
|
|
115
|
+
x7 = (x7 << 12) | (x7 >>> 20);
|
|
116
|
+
x3 = (x3 + x7) >>> 0;
|
|
117
|
+
x15 ^= x3;
|
|
118
|
+
x15 = (x15 << 8) | (x15 >>> 24);
|
|
119
|
+
x11 = (x11 + x15) >>> 0;
|
|
120
|
+
x7 ^= x11;
|
|
121
|
+
x7 = (x7 << 7) | (x7 >>> 25);
|
|
122
|
+
|
|
123
|
+
// diagonal rounds
|
|
124
|
+
x0 = (x0 + x5) >>> 0;
|
|
125
|
+
x15 ^= x0;
|
|
126
|
+
x15 = (x15 << 16) | (x15 >>> 16);
|
|
127
|
+
x10 = (x10 + x15) >>> 0;
|
|
128
|
+
x5 ^= x10;
|
|
129
|
+
x5 = (x5 << 12) | (x5 >>> 20);
|
|
130
|
+
x0 = (x0 + x5) >>> 0;
|
|
131
|
+
x15 ^= x0;
|
|
132
|
+
x15 = (x15 << 8) | (x15 >>> 24);
|
|
133
|
+
x10 = (x10 + x15) >>> 0;
|
|
134
|
+
x5 ^= x10;
|
|
135
|
+
x5 = (x5 << 7) | (x5 >>> 25);
|
|
136
|
+
|
|
137
|
+
x1 = (x1 + x6) >>> 0;
|
|
138
|
+
x12 ^= x1;
|
|
139
|
+
x12 = (x12 << 16) | (x12 >>> 16);
|
|
140
|
+
x11 = (x11 + x12) >>> 0;
|
|
141
|
+
x6 ^= x11;
|
|
142
|
+
x6 = (x6 << 12) | (x6 >>> 20);
|
|
143
|
+
x1 = (x1 + x6) >>> 0;
|
|
144
|
+
x12 ^= x1;
|
|
145
|
+
x12 = (x12 << 8) | (x12 >>> 24);
|
|
146
|
+
x11 = (x11 + x12) >>> 0;
|
|
147
|
+
x6 ^= x11;
|
|
148
|
+
x6 = (x6 << 7) | (x6 >>> 25);
|
|
149
|
+
|
|
150
|
+
x2 = (x2 + x7) >>> 0;
|
|
151
|
+
x13 ^= x2;
|
|
152
|
+
x13 = (x13 << 16) | (x13 >>> 16);
|
|
153
|
+
x8 = (x8 + x13) >>> 0;
|
|
154
|
+
x7 ^= x8;
|
|
155
|
+
x7 = (x7 << 12) | (x7 >>> 20);
|
|
156
|
+
x2 = (x2 + x7) >>> 0;
|
|
157
|
+
x13 ^= x2;
|
|
158
|
+
x13 = (x13 << 8) | (x13 >>> 24);
|
|
159
|
+
x8 = (x8 + x13) >>> 0;
|
|
160
|
+
x7 ^= x8;
|
|
161
|
+
x7 = (x7 << 7) | (x7 >>> 25);
|
|
162
|
+
|
|
163
|
+
x3 = (x3 + x4) >>> 0;
|
|
164
|
+
x14 ^= x3;
|
|
165
|
+
x14 = (x14 << 16) | (x14 >>> 16);
|
|
166
|
+
x9 = (x9 + x14) >>> 0;
|
|
167
|
+
x4 ^= x9;
|
|
168
|
+
x4 = (x4 << 12) | (x4 >>> 20);
|
|
169
|
+
x3 = (x3 + x4) >>> 0;
|
|
170
|
+
x14 ^= x3;
|
|
171
|
+
x14 = (x14 << 8) | (x14 >>> 24);
|
|
172
|
+
x9 = (x9 + x14) >>> 0;
|
|
173
|
+
x4 ^= x9;
|
|
174
|
+
x4 = (x4 << 7) | (x4 >>> 25);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
block[0] = (x0 + 0x61707865) >>> 0;
|
|
178
|
+
block[1] = (x1 + 0x3320646e) >>> 0;
|
|
179
|
+
block[2] = (x2 + 0x79622d32) >>> 0;
|
|
180
|
+
block[3] = (x3 + 0x6b206574) >>> 0;
|
|
181
|
+
block[4] = (x4 + k0) >>> 0;
|
|
182
|
+
block[5] = (x5 + k1) >>> 0;
|
|
183
|
+
block[6] = (x6 + k2) >>> 0;
|
|
184
|
+
block[7] = (x7 + k3) >>> 0;
|
|
185
|
+
block[8] = (x8 + k4) >>> 0;
|
|
186
|
+
block[9] = (x9 + k5) >>> 0;
|
|
187
|
+
block[10] = (x10 + k6) >>> 0;
|
|
188
|
+
block[11] = (x11 + k7) >>> 0;
|
|
189
|
+
block[12] = (x12 + counter) >>> 0;
|
|
190
|
+
block[13] = (x13 + n0) >>> 0;
|
|
191
|
+
block[14] = (x14 + n1) >>> 0;
|
|
192
|
+
block[15] = (x15 + n2) >>> 0;
|
|
193
|
+
|
|
194
|
+
const remaining = length - offset;
|
|
195
|
+
if (remaining >= 64) {
|
|
196
|
+
if (source) {
|
|
197
|
+
for (let w = 0; w < 16; w++) {
|
|
198
|
+
const word = block[w];
|
|
199
|
+
out[offset] = source[offset] ^ (word & 0xff);
|
|
200
|
+
out[offset + 1] = source[offset + 1] ^ ((word >>> 8) & 0xff);
|
|
201
|
+
out[offset + 2] = source[offset + 2] ^ ((word >>> 16) & 0xff);
|
|
202
|
+
out[offset + 3] = source[offset + 3] ^ ((word >>> 24) & 0xff);
|
|
203
|
+
offset += 4;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
for (let w = 0; w < 16; w++) {
|
|
207
|
+
const word = block[w];
|
|
208
|
+
out[offset] = word & 0xff;
|
|
209
|
+
out[offset + 1] = (word >>> 8) & 0xff;
|
|
210
|
+
out[offset + 2] = (word >>> 16) & 0xff;
|
|
211
|
+
out[offset + 3] = (word >>> 24) & 0xff;
|
|
212
|
+
offset += 4;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
for (let i = 0; i < remaining; i++, offset++) {
|
|
217
|
+
const stream = (block[i >> 2] >>> ((i & 3) * 8)) & 0xff;
|
|
218
|
+
out[offset] = source ? source[offset] ^ stream : stream;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
counter++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function chacha20(key, nonce, length) {
|
|
228
|
+
const out = new Uint8Array(length);
|
|
229
|
+
if (length <= 0) return out;
|
|
230
|
+
if (nativeBlockCipher && length >= NATIVE_MIN_BYTES) {
|
|
231
|
+
const native = nativeBlockCipher(key, nonce, out);
|
|
232
|
+
if (native) return native;
|
|
233
|
+
}
|
|
234
|
+
return chacha20Into(out, key, nonce, length, null);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// XORs `data` with the keystream in a single pass, without materialising the
|
|
238
|
+
// keystream in a separate buffer.
|
|
239
|
+
function chacha20Xor(key, nonce, data) {
|
|
240
|
+
const out = new Uint8Array(data.length);
|
|
241
|
+
if (data.length === 0) return out;
|
|
242
|
+
if (nativeBlockCipher && data.length >= NATIVE_MIN_BYTES) {
|
|
243
|
+
const native = nativeBlockCipher(key, nonce, out, data);
|
|
244
|
+
if (native) return native;
|
|
245
|
+
}
|
|
246
|
+
return chacha20Into(out, key, nonce, data.length, data);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function decryptPayload(wireBytes, worldId, seed) {
|
|
250
|
+
const wire = toUint8Array(wireBytes);
|
|
251
|
+
if (wire.length < 8) throw new Error("payload too short");
|
|
252
|
+
const view = new DataView(wire.buffer, wire.byteOffset, wire.byteLength);
|
|
253
|
+
const packetWordA = view.getUint32(0, true);
|
|
254
|
+
const packetWordB = view.getUint32(wire.length - 4, true);
|
|
255
|
+
const material = Uint8Array.from(SHARED_CHACHA_MATERIAL);
|
|
256
|
+
const worldKey = encoder.encode(`${worldId}${seed}`);
|
|
257
|
+
for (let i = 0, l = 0, r = 0; i < 44; i++, l += 7, r += 11) {
|
|
258
|
+
material[i] ^= worldKey[i % worldKey.length] ^ (rotl32(packetWordA, l) & 0xff) ^ (rotr32(packetWordB, r) & 0xff);
|
|
259
|
+
}
|
|
260
|
+
// subarray, not slice: the key and nonce are only read.
|
|
261
|
+
const ciphertext = wire.subarray(4, wire.length - 4);
|
|
262
|
+
return chacha20Xor(material.subarray(0, 32), material.subarray(32, 44), ciphertext);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
export {
|
|
267
|
+
rotl32,
|
|
268
|
+
rotr32,
|
|
269
|
+
quarterRound,
|
|
270
|
+
chacha20,
|
|
271
|
+
chacha20Xor,
|
|
272
|
+
decryptPayload,
|
|
273
|
+
installNativeChaCha20
|
|
274
|
+
};
|