baltica 2.0.9 → 2.0.11
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/dist/bridge/bridge.d.ts +2 -0
- package/dist/bridge/bridge.js +65 -0
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.js +25 -5
- package/dist/client/types/login/login-data.js +1 -0
- package/dist/server/player.d.ts +1 -0
- package/dist/server/player.js +23 -0
- package/dist/shared/serializer/packet-encryptor.js +36 -17
- package/dist/shared/types/protocol.d.ts +3 -2
- package/dist/shared/types/protocol.js +2 -1
- package/package.json +2 -2
package/dist/bridge/bridge.d.ts
CHANGED
package/dist/bridge/bridge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Bridge = void 0;
|
|
4
|
+
const node_dgram_1 = require("node:dgram");
|
|
4
5
|
const utils_1 = require("@baltica/utils");
|
|
5
6
|
const protocol_1 = require("@serenityjs/protocol");
|
|
6
7
|
const server_1 = require("../server");
|
|
@@ -18,6 +19,7 @@ class Bridge extends utils_1.Emitter {
|
|
|
18
19
|
}
|
|
19
20
|
async start() {
|
|
20
21
|
await this.server.start();
|
|
22
|
+
this.pollBackendMotd();
|
|
21
23
|
this.server.on("playerConnect", (player) => {
|
|
22
24
|
const bridgePlayer = new bridge_player_1.BridgePlayer(player, this);
|
|
23
25
|
const key = player.connection.identifier;
|
|
@@ -26,6 +28,69 @@ class Bridge extends utils_1.Emitter {
|
|
|
26
28
|
this.emit("connect", bridgePlayer);
|
|
27
29
|
});
|
|
28
30
|
}
|
|
31
|
+
pollBackendMotd() {
|
|
32
|
+
this.fetchBackendMotd().then((motd) => {
|
|
33
|
+
if (motd)
|
|
34
|
+
this.server.raknet.message = motd;
|
|
35
|
+
}).finally(() => {
|
|
36
|
+
setTimeout(() => this.pollBackendMotd(), 5000);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
fetchBackendMotd() {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const socket = (0, node_dgram_1.createSocket)("udp4");
|
|
42
|
+
const timeout = setTimeout(() => {
|
|
43
|
+
try {
|
|
44
|
+
socket.close();
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
resolve(null);
|
|
48
|
+
}, 2000);
|
|
49
|
+
socket.on("error", () => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
try {
|
|
52
|
+
socket.close();
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
resolve(null);
|
|
56
|
+
});
|
|
57
|
+
socket.on("message", (buffer) => {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
try {
|
|
60
|
+
socket.close();
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
try {
|
|
64
|
+
if (buffer[0] === 0x1c) {
|
|
65
|
+
const pong = new raknet_1.UnconnectedPong(buffer.subarray(1)).deserialize();
|
|
66
|
+
resolve(pong.message);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
resolve(null);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
resolve(null);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
socket.bind(0, () => {
|
|
77
|
+
try {
|
|
78
|
+
const ping = new raknet_1.UnconnectedPing();
|
|
79
|
+
ping.timestamp = BigInt(Date.now());
|
|
80
|
+
ping.guid = BigInt(Math.floor(Math.random() * 0xffffffff));
|
|
81
|
+
socket.send(ping.serialize(), this.options.destination.port, this.options.destination.address);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
try {
|
|
86
|
+
socket.close();
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
resolve(null);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
29
94
|
disconnect(player) {
|
|
30
95
|
utils_1.Logger.info(`Disconnecting player ${player.player.username}`);
|
|
31
96
|
try {
|
package/dist/client/client.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
13
13
|
loginData: LoginData;
|
|
14
14
|
stopPastLogin: boolean;
|
|
15
15
|
startGameData: StartGamePacket;
|
|
16
|
+
private isDisconnected;
|
|
16
17
|
constructor(options: Partial<ClientOptions>);
|
|
17
18
|
connect(): Promise<void>;
|
|
18
19
|
private registerHandshakeHandlers;
|
package/dist/client/client.js
CHANGED
|
@@ -20,6 +20,7 @@ class Client extends utils_1.Emitter {
|
|
|
20
20
|
loginData;
|
|
21
21
|
stopPastLogin = false;
|
|
22
22
|
startGameData;
|
|
23
|
+
isDisconnected = false;
|
|
23
24
|
constructor(options) {
|
|
24
25
|
super();
|
|
25
26
|
this.options = { ...types_1.defaultClientOptions, ...options };
|
|
@@ -37,12 +38,11 @@ class Client extends utils_1.Emitter {
|
|
|
37
38
|
await this.raknet.connect();
|
|
38
39
|
this.requestNetworkSettings();
|
|
39
40
|
return new Promise((resolve, reject) => {
|
|
40
|
-
this.
|
|
41
|
+
this.once("SetLocalPlayerAsInitializedPacket", () => {
|
|
41
42
|
resolve();
|
|
42
|
-
this.emit("spawn");
|
|
43
43
|
this.emit("connect");
|
|
44
44
|
});
|
|
45
|
-
this.
|
|
45
|
+
this.once("disconnect", (reason) => {
|
|
46
46
|
reject(new Error(`Disconnected during login: ${reason}`));
|
|
47
47
|
});
|
|
48
48
|
});
|
|
@@ -109,10 +109,15 @@ class Client extends utils_1.Emitter {
|
|
|
109
109
|
radius.radius = this.options.viewDistance;
|
|
110
110
|
radius.maxRadius = this.options.viewDistance;
|
|
111
111
|
this.send(radius.serialize());
|
|
112
|
+
const loadingScreen = new protocol_1.ServerboundLoadingScreenPacketPacket();
|
|
113
|
+
loadingScreen.type = protocol_1.ServerboundLoadingScreenType.StartLoadingScreen;
|
|
114
|
+
loadingScreen.hasScreenId = false;
|
|
115
|
+
this.send(loadingScreen.serialize());
|
|
112
116
|
}
|
|
113
117
|
onPlayStatus(packet) {
|
|
114
118
|
if (packet.status !== protocol_1.PlayStatus.PlayerSpawn)
|
|
115
119
|
return;
|
|
120
|
+
this.emit("spawn"); // Spawn is just spawning. not fully connected yet
|
|
116
121
|
const init = new protocol_1.SetLocalPlayerAsInitializedPacket();
|
|
117
122
|
init.runtimeEntityId = this.startGameData.runtimeEntityId;
|
|
118
123
|
const loadingScreen = new protocol_1.ServerboundLoadingScreenPacketPacket();
|
|
@@ -145,6 +150,9 @@ class Client extends utils_1.Emitter {
|
|
|
145
150
|
this.removeAllListeners();
|
|
146
151
|
}
|
|
147
152
|
disconnect(reason = "client disconnect") {
|
|
153
|
+
if (this.isDisconnected)
|
|
154
|
+
return;
|
|
155
|
+
this.isDisconnected = true;
|
|
148
156
|
this.emit("disconnect", reason);
|
|
149
157
|
setTimeout(() => {
|
|
150
158
|
if (this.raknet && typeof this.raknet.disconnect === "function") {
|
|
@@ -225,6 +233,8 @@ class Client extends utils_1.Emitter {
|
|
|
225
233
|
this.sendUncompressed(request.serialize(), raknet_1.Priority.High);
|
|
226
234
|
}
|
|
227
235
|
send(packet, priority = raknet_1.Priority.Medium, compressionMethod) {
|
|
236
|
+
if (this.isDisconnected)
|
|
237
|
+
return;
|
|
228
238
|
try {
|
|
229
239
|
const compressed = this.packetCompressor.compress(packet, compressionMethod);
|
|
230
240
|
this.raknet.sendReliable(compressed, priority);
|
|
@@ -237,7 +247,12 @@ class Client extends utils_1.Emitter {
|
|
|
237
247
|
try {
|
|
238
248
|
const decompressed = this.packetCompressor.decompress(packet);
|
|
239
249
|
for (const packet of decompressed) {
|
|
240
|
-
|
|
250
|
+
try {
|
|
251
|
+
this.handlePacket(packet);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
utils_2.Logger.error("Failed to handle packet", err);
|
|
255
|
+
}
|
|
241
256
|
}
|
|
242
257
|
}
|
|
243
258
|
catch (err) {
|
|
@@ -256,7 +271,12 @@ class Client extends utils_1.Emitter {
|
|
|
256
271
|
return;
|
|
257
272
|
if (this.listenerCount(PacketClass.name) > 0) {
|
|
258
273
|
const deserialized = new PacketClass(buffer).deserialize();
|
|
259
|
-
|
|
274
|
+
try {
|
|
275
|
+
this.emit(PacketClass.name, deserialized);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
utils_2.Logger.error(`Error in packet handler for ${PacketClass.name}:`, err);
|
|
279
|
+
}
|
|
260
280
|
}
|
|
261
281
|
}
|
|
262
282
|
sendUncompressed(packet, priority = raknet_1.Priority.Medium) {
|
package/dist/server/player.d.ts
CHANGED
package/dist/server/player.js
CHANGED
|
@@ -51,6 +51,7 @@ class Player extends utils_1.Emitter {
|
|
|
51
51
|
connection;
|
|
52
52
|
username = "";
|
|
53
53
|
xuid = "";
|
|
54
|
+
isDisconnected = false;
|
|
54
55
|
privateKey;
|
|
55
56
|
publicKeyDER;
|
|
56
57
|
clientX509;
|
|
@@ -114,6 +115,23 @@ class Player extends utils_1.Emitter {
|
|
|
114
115
|
}
|
|
115
116
|
catch { }
|
|
116
117
|
}
|
|
118
|
+
// New client versions use Token instead of Certificate chain.
|
|
119
|
+
// The Token JWT payload contains cpk (client public key), xname, and xid.
|
|
120
|
+
if (!identityPublicKey) {
|
|
121
|
+
const token = identity.Token ?? identity.token;
|
|
122
|
+
if (token) {
|
|
123
|
+
try {
|
|
124
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
125
|
+
if (payload?.cpk)
|
|
126
|
+
identityPublicKey = payload.cpk;
|
|
127
|
+
if (payload?.xname && !displayName)
|
|
128
|
+
displayName = payload.xname;
|
|
129
|
+
if (payload?.xid && !xuid)
|
|
130
|
+
xuid = payload.xid;
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
117
135
|
this.username = displayName;
|
|
118
136
|
this.xuid = xuid;
|
|
119
137
|
if (!identityPublicKey) {
|
|
@@ -184,6 +202,8 @@ class Player extends utils_1.Emitter {
|
|
|
184
202
|
}
|
|
185
203
|
}
|
|
186
204
|
send(packet, priority = raknet_1.Priority.High, compressionMethod) {
|
|
205
|
+
if (this.isDisconnected)
|
|
206
|
+
return;
|
|
187
207
|
try {
|
|
188
208
|
const compressed = this.packetCompressor.compress(packet, compressionMethod);
|
|
189
209
|
this.connection.send(compressed, priority);
|
|
@@ -203,6 +223,9 @@ class Player extends utils_1.Emitter {
|
|
|
203
223
|
this.connection.send(buf, priority);
|
|
204
224
|
}
|
|
205
225
|
disconnect(reason = "disconnected") {
|
|
226
|
+
if (this.isDisconnected)
|
|
227
|
+
return;
|
|
228
|
+
this.isDisconnected = true;
|
|
206
229
|
utils_1.Logger.info(`Disconnecting ${this.username || "player"}: ${reason}`);
|
|
207
230
|
this.connection.disconnect();
|
|
208
231
|
this.emit("disconnect");
|
|
@@ -67,26 +67,45 @@ class PacketEncryptor {
|
|
|
67
67
|
return Buffer.from(new Uint8Array(hash.subarray(0, 8)));
|
|
68
68
|
}
|
|
69
69
|
encryptPacket(payload) {
|
|
70
|
-
if (!this.cipher)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
if (!this.cipher) {
|
|
71
|
+
// Cipher destroyed - return unencrypted
|
|
72
|
+
return Buffer.concat([Buffer.from([0xfe]), payload]);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const checksum = this.computeCheckSum(payload, this.sendCounter);
|
|
76
|
+
const toEncrypt = Buffer.concat([payload, checksum]);
|
|
77
|
+
const encrypted = this.cipher.update(toEncrypt);
|
|
78
|
+
this.sendCounter++;
|
|
79
|
+
return Buffer.concat([Buffer.from([0xfe]), encrypted]);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Cipher in invalid state - return unencrypted
|
|
83
|
+
return Buffer.concat([Buffer.from([0xfe]), payload]);
|
|
84
|
+
}
|
|
77
85
|
}
|
|
78
86
|
decryptPacket(encryptedPayload) {
|
|
79
|
-
if (!this.decipher)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
if (!this.decipher) {
|
|
88
|
+
// Decipher destroyed - return as-is
|
|
89
|
+
return encryptedPayload;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const decrypted = this.decipher.update(encryptedPayload);
|
|
93
|
+
const packet = decrypted.subarray(0, decrypted.length - 8);
|
|
94
|
+
const receivedChecksum = decrypted.subarray(decrypted.length - 8);
|
|
95
|
+
const computedChecksum = this.computeCheckSum(packet, this.receiveCounter);
|
|
96
|
+
this.receiveCounter++;
|
|
97
|
+
if (!receivedChecksum.equals(computedChecksum)) {
|
|
98
|
+
throw new Error("Checksum mismatch");
|
|
99
|
+
}
|
|
100
|
+
return packet;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
// Re-throw checksum errors, ignore cipher state errors
|
|
104
|
+
if (err instanceof Error && err.message === "Checksum mismatch") {
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
return encryptedPayload;
|
|
88
108
|
}
|
|
89
|
-
return packet;
|
|
90
109
|
}
|
|
91
110
|
}
|
|
92
111
|
exports.PacketEncryptor = PacketEncryptor;
|
|
@@ -3,7 +3,8 @@ export type PacketNames = {
|
|
|
3
3
|
[K in keyof typeof Protocol]: K extends `${string}Packet` ? K extends "Packet" | "DataPacket" ? never : K : never;
|
|
4
4
|
}[keyof typeof Protocol];
|
|
5
5
|
export declare enum ProtocolList {
|
|
6
|
-
"1.26.1" = 924
|
|
6
|
+
"1.26.1" = 924,
|
|
7
|
+
"1.26.10" = 944
|
|
7
8
|
}
|
|
8
|
-
export type CurrentVersion = "1.26.
|
|
9
|
+
export type CurrentVersion = "1.26.10";
|
|
9
10
|
export declare const CurrentVersionConst: CurrentVersion;
|
|
@@ -4,5 +4,6 @@ exports.CurrentVersionConst = exports.ProtocolList = void 0;
|
|
|
4
4
|
var ProtocolList;
|
|
5
5
|
(function (ProtocolList) {
|
|
6
6
|
ProtocolList[ProtocolList["1.26.1"] = 924] = "1.26.1";
|
|
7
|
+
ProtocolList[ProtocolList["1.26.10"] = 944] = "1.26.10";
|
|
7
8
|
})(ProtocolList || (exports.ProtocolList = ProtocolList = {}));
|
|
8
|
-
exports.CurrentVersionConst = "1.26.
|
|
9
|
+
exports.CurrentVersionConst = "1.26.10";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baltica",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "Core baltica package",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@baltica/auth": "*",
|
|
14
14
|
"@baltica/raknet": "*",
|
|
15
15
|
"@baltica/utils": "*",
|
|
16
|
-
"@serenityjs/protocol": "^0.8.
|
|
16
|
+
"@serenityjs/protocol": "^0.8.19",
|
|
17
17
|
"jose": "^6.1.3"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|