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.
@@ -9,5 +9,7 @@ export declare class Bridge extends Emitter<BridgeEvents> {
9
9
  private clients;
10
10
  constructor(options: Partial<BridgeOptions>);
11
11
  start(): Promise<void>;
12
+ private pollBackendMotd;
13
+ private fetchBackendMotd;
12
14
  disconnect(player: BridgePlayer): void;
13
15
  }
@@ -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 {
@@ -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;
@@ -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.on("SetLocalPlayerAsInitializedPacket", () => {
41
+ this.once("SetLocalPlayerAsInitializedPacket", () => {
41
42
  resolve();
42
- this.emit("spawn");
43
43
  this.emit("connect");
44
44
  });
45
- this.on("disconnect", (reason) => {
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
- this.handlePacket(packet);
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
- this.emit(PacketClass.name, deserialized);
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) {
@@ -156,6 +156,7 @@ class LoginData {
156
156
  displayName: profile.name,
157
157
  identity: profile.uuid,
158
158
  titleId: "89692877",
159
+ sandboxId: "",
159
160
  XUID: "",
160
161
  },
161
162
  certificateAuthority: true,
@@ -11,6 +11,7 @@ export declare class Player extends Emitter<PlayerEvents> {
11
11
  connection: Connection;
12
12
  username: string;
13
13
  xuid: string;
14
+ private isDisconnected;
14
15
  private privateKey;
15
16
  private publicKeyDER;
16
17
  private clientX509;
@@ -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
- throw new Error("Cipher not initialized");
72
- const checksum = this.computeCheckSum(payload, this.sendCounter);
73
- const toEncrypt = Buffer.concat([payload, checksum]);
74
- const encrypted = this.cipher.update(toEncrypt);
75
- this.sendCounter++;
76
- return Buffer.concat([Buffer.from([0xfe]), encrypted]);
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
- throw new Error("Decipher not initialized");
81
- const decrypted = this.decipher.update(encryptedPayload);
82
- const packet = decrypted.subarray(0, decrypted.length - 8);
83
- const receivedChecksum = decrypted.subarray(decrypted.length - 8);
84
- const computedChecksum = this.computeCheckSum(packet, this.receiveCounter);
85
- this.receiveCounter++;
86
- if (!receivedChecksum.equals(computedChecksum)) {
87
- throw new Error("Checksum mismatch");
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.1";
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.1";
9
+ exports.CurrentVersionConst = "1.26.10";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baltica",
3
- "version": "2.0.9",
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.18",
16
+ "@serenityjs/protocol": "^0.8.19",
17
17
  "jose": "^6.1.3"
18
18
  },
19
19
  "files": [