baltica 0.0.8 → 0.0.10

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.
Binary file
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # Baltica
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/SanctumTerra/Baltica/master/.extra/logo.png" alt="Baltica Logo" width="200"/>
5
+ </p>
6
+
7
+ ---
8
+ **Supported Versions** `1.21.80` `1.21.70` `1.21.60` `1.21.50`
9
+ ---
10
+
11
+ Baltica is a high-performance Minecraft Bedrock Edition networking toolkit built with TypeScript. It serves three main purposes:
12
+ 1. A powerful bridge/proxy that can modify, intercept, and manipulate network traffic between Minecraft Bedrock clients and servers
13
+ 2. A robust client library for creating Minecraft Bedrock bots and automated players
14
+ 3. A flexible server implementation for creating custom Minecraft Bedrock servers
15
+
16
+ ## Features
17
+
18
+ - 🚀 High-performance packet handling and forwarding
19
+ - 🤖 Bot creation and automation
20
+ - 🔒 Support for encryption and compression
21
+ - 🎮 Multiple protocol version support (1.21.50 - 1.21.80)
22
+ - 🌐 Customizable packet manipulation
23
+ - 🔄 Packet caching for improved performance
24
+ - 📦 Resource pack handling
25
+ - 🎯 Event-driven architecture
26
+ - 🤝 Multi-client support for running multiple bots
27
+ - 🖥️ Custom server implementation
28
+ - ⚡ Bun runtime support for enhanced performance
29
+
30
+ ## Prerequisites
31
+
32
+ - Node.js (v16 or higher recommended) or Bun runtime
33
+ - TypeScript
34
+ - npm, yarn, or bun
35
+
36
+ ## Installation
37
+
38
+ ### Using Package Manager (Recommended)
39
+
40
+ ```bash
41
+ # Using npm
42
+ npm install baltica
43
+
44
+ # Using yarn
45
+ yarn add baltica
46
+
47
+ # Using bun
48
+ bun add baltica
49
+ ```
50
+
51
+ ### From Source
52
+
53
+ If you want to contribute or modify the source code:
54
+
55
+ 1. Clone the repository:
56
+ ```bash
57
+ git clone https://github.com/yourusername/baltica.git
58
+ cd baltica
59
+ ```
60
+
61
+ 2. Install dependencies:
62
+ ```bash
63
+ # Using npm
64
+ npm install
65
+
66
+ # Using yarn
67
+ yarn install
68
+
69
+ # Using bun
70
+ bun install
71
+ ```
72
+
73
+ 3. Build the project:
74
+ ```bash
75
+ # Using npm
76
+ npm run build
77
+
78
+ # Using yarn
79
+ yarn build
80
+
81
+ # Using bun
82
+ bun run build
83
+ ```
84
+
85
+ ## Quick Start
86
+
87
+ ```typescript
88
+ import { Client, Server, Bridge } from 'baltica';
89
+
90
+ // Create a bot
91
+ const bot = new Client({
92
+ host: 'server-address',
93
+ port: 19132,
94
+ version: '1.21.80',
95
+ username: 'MyBot'
96
+ });
97
+
98
+ // Create a server
99
+ const server = new Server({
100
+ host: '0.0.0.0',
101
+ port: 19132,
102
+ maxPlayers: 20
103
+ });
104
+
105
+ // Create a bridge
106
+ const bridge = new Bridge({
107
+ host: '0.0.0.0',
108
+ port: 19132,
109
+ destination: {
110
+ host: 'target-server.com',
111
+ port: 19132
112
+ }
113
+ });
114
+ ```
115
+
116
+ ## Usage
117
+
118
+ ### Creating a Custom Server
119
+
120
+ ```typescript
121
+ import { Server } from './src/server/server';
122
+
123
+ const server = new Server({
124
+ host: '0.0.0.0',
125
+ port: 19132,
126
+ version: '1.21.80',
127
+ maxPlayers: 20
128
+ });
129
+
130
+ // Handle player connections
131
+ server.on('playerConnect', (player) => {
132
+ console.log(`Player ${player.profile.name} connected!`);
133
+
134
+ // Handle player packets
135
+ player.on('TextPacket', (packet) => {
136
+ // Broadcast chat messages
137
+ if (packet.type === TextPacketType.Chat) {
138
+ server.broadcast(`${player.profile.name}: ${packet.message}`);
139
+ }
140
+ });
141
+ });
142
+
143
+ // Handle disconnections
144
+ server.on('disconnect', (displayName, player) => {
145
+ console.log(`Player ${displayName} disconnected`);
146
+ });
147
+
148
+ server.start();
149
+ ```
150
+
151
+ ### Creating Minecraft Bots
152
+
153
+ ```typescript
154
+ import { Client } from './src/client/client';
155
+
156
+ // Create a basic bot
157
+ const bot = new Client({
158
+ host: 'server-address',
159
+ port: 19132,
160
+ version: '1.21.80',
161
+ username: 'MyBot',
162
+ tokensFolder: 'tokens',
163
+ viewDistance: 10
164
+ });
165
+
166
+ // Connect and handle events
167
+ await bot.connect();
168
+
169
+ // Listen for chat messages
170
+ bot.on('TextPacket', (packet) => {
171
+ if (packet.type === TextPacketType.Chat) {
172
+ console.log(`${packet.source}: ${packet.message}`);
173
+
174
+ // Respond to messages
175
+ if (packet.message.startsWith('!hello')) {
176
+ bot.sendMessage('Hello there!');
177
+ }
178
+ }
179
+ });
180
+
181
+ // Handle player movement
182
+ bot.on('MovePlayerPacket', (packet) => {
183
+ // React to player movements
184
+ console.log(`Player ${packet.runtimeEntityId} moved to ${packet.position}`);
185
+ });
186
+ ```
187
+
188
+ ### Running Multiple Bots
189
+
190
+ ```typescript
191
+ async function createBots(count: number) {
192
+ const bots = [];
193
+
194
+ for (let i = 0; i < count; i++) {
195
+ const bot = new Client({
196
+ host: 'server-address',
197
+ port: 19132,
198
+ version: '1.21.80',
199
+ username: `Bot${i}`,
200
+ tokensFolder: 'tokens',
201
+ viewDistance: 2, // Lower view distance for better performance
202
+ worker: true // Enable worker mode for better performance
203
+ });
204
+
205
+ await bot.connect();
206
+ bots.push(bot);
207
+ }
208
+
209
+ return bots;
210
+ }
211
+
212
+ // Create 5 bots
213
+ const myBots = await createBots(5);
214
+ ```
215
+
216
+ ### Basic Bridge Setup
217
+
218
+ ```typescript
219
+ import { Bridge } from './src/bridge/bridge';
220
+
221
+ const bridge = new Bridge({
222
+ host: '0.0.0.0',
223
+ port: 19132,
224
+ destination: {
225
+ host: 'target-server.com',
226
+ port: 19132
227
+ },
228
+ version: '1.21.80',
229
+ maxPlayers: 20
230
+ });
231
+
232
+ bridge.start();
233
+ ```
234
+
235
+ ## Configuration Options
236
+
237
+ ### Server Options
238
+ - `host`: The IP address to bind the server to
239
+ - `port`: The port to listen on
240
+ - `version`: Minecraft protocol version
241
+ - `maxPlayers`: Maximum number of concurrent players
242
+ - `motd`: Message of the day
243
+
244
+ ### Client/Bot Options
245
+ - `host`: Target server address
246
+ - `port`: Target server port
247
+ - `version`: Protocol version
248
+ - `username`: Bot username
249
+ - `tokensFolder`: Directory for authentication tokens
250
+ - `viewDistance`: Render distance (lower values improve performance)
251
+ - `offline`: Enable offline mode
252
+ - `worker`: Enable worker mode for improved performance with multiple bots
253
+ - `deviceOS`: Device OS to emulate (defaults to Nintendo Switch)
254
+ - `skinData`: Custom skin data for the bot
255
+
256
+ ### Bridge Options
257
+ - `host`: The IP address to bind the bridge server to
258
+ - `port`: The port to listen on
259
+ - `destination`: Target server configuration (host and port)
260
+ - `version`: Minecraft protocol version
261
+ - `maxPlayers`: Maximum number of concurrent players
262
+
263
+ ## Events
264
+
265
+ Baltica uses an event-driven architecture across all its components. Each component (Server, Client/Bot, Bridge) emits events that you can listen to for various game events and packet handling.
266
+
267
+ ```typescript
268
+ // Example of event handling
269
+ client.on('packet', (packet) => {
270
+ // Handle any packet
271
+ });
272
+
273
+ bridge.on('connect', (player) => {
274
+ // Handle player connection
275
+ });
276
+
277
+ server.on('playerConnect', (player) => {
278
+ // Handle new player
279
+ });
280
+ ```
281
+
282
+ For detailed event documentation, please refer to our [API Documentation](https://github.com/SanctumTerra/Baltica/wiki).
283
+
284
+ ## Performance
285
+
286
+ Baltica supports both Node.js and Bun runtimes, with Bun offering significant performance improvements:
287
+ - Faster startup times
288
+ - Lower memory usage
289
+ - Better packet processing performance
290
+ - Improved concurrent connections handling
291
+
292
+ ## Contributing
293
+
294
+ Contributions are welcome! Please feel free to submit a Pull Request.
295
+
296
+ ## License
297
+
298
+ This project is licensed under the MIT License - see the LICENSE file for details.
299
+
300
+ ## Acknowledgments
301
+
302
+ - Built with [@serenityjs/protocol](https://github.com/SerenityJS/protocol) for Minecraft protocol implementation
303
+ - Uses [@sanctumterra/raknet](https://github.com/sanctumterra/raknet) for RakNet networking
@@ -5,17 +5,26 @@ import { type ServerOptions } from "../server/server-options";
5
5
  export type BridgePlayerEvents = {
6
6
  [K in PacketNames as `clientbound-${K}`]: [
7
7
  packet: InstanceType<(typeof Protocol)[K]>,
8
- cancelled: boolean
8
+ eventStatus: {
9
+ cancelled: boolean;
10
+ modified: boolean;
11
+ }
9
12
  ];
10
13
  } & {
11
14
  [K in PacketNames as `serverbound-${K}`]: [
12
15
  packet: InstanceType<(typeof Protocol)[K]>,
13
- cancelled: boolean
16
+ eventStatus: {
17
+ cancelled: boolean;
18
+ modified: boolean;
19
+ }
14
20
  ];
15
21
  } & {
16
22
  "serverbound-ClientCacheStatusPacket": [
17
23
  packet: ClientCacheStatusPacket,
18
- cancelled: boolean
24
+ eventStatus: {
25
+ cancelled: boolean;
26
+ modified: boolean;
27
+ }
19
28
  ];
20
29
  };
21
30
  export type BridgeOptions = ServerOptions & {
@@ -8,5 +8,6 @@ exports.defaultBridgeOptions = {
8
8
  host: "127.0.0.1",
9
9
  port: 19132,
10
10
  },
11
+ levelName: "SanctumTerra Server",
11
12
  offline: false,
12
13
  };
@@ -10,16 +10,11 @@ class BridgePlayer extends libs_1.Emitter {
10
10
  this.player = player;
11
11
  this.postStartGame = false;
12
12
  this.levelChunkQueue = [];
13
- this.once("clientbound-StartGamePacket", (packet) => {
13
+ this.once("clientbound-StartGamePacket", (packet, eventStatus) => {
14
14
  this.postStartGame = true;
15
15
  for (const chunk of this.levelChunkQueue) {
16
16
  const eventName = "clientbound-LevelChunkPacket";
17
- this.emit(eventName, chunk, false);
18
- if ("binary" in packet) {
19
- packet.binary = [];
20
- }
21
- const newBuffer = chunk.serialize();
22
- this.player.send(newBuffer);
17
+ this.emit(eventName, chunk, { cancelled: false, modified: false });
23
18
  }
24
19
  });
25
20
  }
@@ -77,52 +77,110 @@ class Bridge extends server_1.Server {
77
77
  const id = (0, protocol_1.getPacketId)(buffer);
78
78
  const PacketClass = protocol_1.Packets[id];
79
79
  const packetName = PacketClass?.name ?? "ClientCacheStatusPacket";
80
+ console.log(packetName);
80
81
  const eventName = `${isClientbound ? "clientbound" : "serverbound"}-${packetName}`;
81
82
  if (!PacketClass && id !== 129) {
83
+ if (this.debugLog) {
84
+ raknet_1.Logger.info(`Passing through unknown packet ID: ${id}`);
85
+ }
82
86
  sender.send(buffer);
83
87
  return;
84
88
  }
85
89
  if (packetName === "LevelChunkPacket" && !player.postStartGame) {
86
- player.levelChunkQueue.push(new level_chunk_packet_1.LevelChunkPacket(buffer).deserialize());
90
+ try {
91
+ player.levelChunkQueue.push(new level_chunk_packet_1.LevelChunkPacket(buffer).deserialize());
92
+ }
93
+ catch (e) {
94
+ raknet_1.Logger.error("Failed to deserialize LevelChunkPacket for queueing", e);
95
+ sender.send(buffer);
96
+ }
97
+ return;
98
+ }
99
+ if (packetName === "ItemStackRequestPacket") {
100
+ if (this.debugLog) {
101
+ raknet_1.Logger.info(`Passing through ItemStackRequestPacket (ID: ${id}) without processing.`);
102
+ }
103
+ sender.send(buffer);
104
+ return;
105
+ }
106
+ const hasListeners = player.hasListeners(eventName);
107
+ const isClientCachePacket = packetName === "ClientCacheStatusPacket";
108
+ const needsProcessing = hasListeners || isClientCachePacket;
109
+ if (!needsProcessing) {
110
+ if (this.debugLog) {
111
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (No listeners/special handling, sending original)`);
112
+ }
113
+ sender.send(buffer);
87
114
  return;
88
115
  }
89
- if (!player.hasListeners(eventName) &&
90
- packetName !== "ClientCacheStatusPacket") {
116
+ const cacheKey = `${id}-${buffer.toString("hex")}`;
117
+ const cachedSerializedBuffer = this.packetSerializationCache.get(cacheKey);
118
+ if (cachedSerializedBuffer) {
119
+ if (this.debugLog) {
120
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (Sending cached serialized)`);
121
+ }
122
+ sender.send(cachedSerializedBuffer);
123
+ return;
124
+ }
125
+ const CachedPacketClass = this.getPacketClass(id, PacketClass);
126
+ if (!CachedPacketClass) {
127
+ raknet_1.Logger.warn(`Could not get packet class for ${packetName} (ID: ${id}) despite needing processing. Sending original.`);
91
128
  sender.send(buffer);
92
129
  return;
93
130
  }
131
+ let packet;
94
132
  try {
95
- const CachedPacketClass = this.getPacketClass(id, PacketClass);
96
- if (!CachedPacketClass) {
97
- sender.send(buffer);
133
+ packet = new CachedPacketClass(buffer).deserialize();
134
+ }
135
+ catch (e) {
136
+ raknet_1.Logger.error(`Failed to deserialize ${packetName} (ID: ${id}). Sending original buffer.`, e);
137
+ sender.send(buffer);
138
+ return;
139
+ }
140
+ if (packet instanceof protocol_1.ClientCacheStatusPacket) {
141
+ packet.enabled = false;
142
+ raknet_1.Logger.warn(`Modified and dropping ClientCacheStatusPacket (ID: ${id})`);
143
+ return;
144
+ }
145
+ const eventStatus = { cancelled: false, modified: false };
146
+ if (hasListeners) {
147
+ if (this.debugLog) {
148
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (Emitting event)`);
149
+ }
150
+ player.emit(eventName, packet, eventStatus);
151
+ if (eventStatus.cancelled) {
152
+ if (this.debugLog) {
153
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (Cancelled by listener)`);
154
+ }
98
155
  return;
99
156
  }
157
+ }
158
+ let bridgeMadeModifications = false;
159
+ if ("binary" in packet && packet.binary !== undefined) {
160
+ if (!Array.isArray(packet.binary) || packet.binary.length > 0) {
161
+ packet.binary = [];
162
+ bridgeMadeModifications = true;
163
+ }
164
+ }
165
+ const requiresSerialization = eventStatus.modified || bridgeMadeModifications;
166
+ if (requiresSerialization) {
100
167
  if (this.debugLog) {
101
- raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName}`);
168
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (Re-serializing after modification)`);
102
169
  }
103
- const cacheKey = `${id}-${buffer.toString("hex")}`;
104
- let newBuffer = this.packetSerializationCache.get(cacheKey);
105
- if (!newBuffer) {
106
- const packet = new CachedPacketClass(buffer).deserialize();
107
- if (packet instanceof protocol_1.ClientCacheStatusPacket) {
108
- packet.enabled = false;
109
- raknet_1.Logger.warn("Ignoring ClientCacheStatusPacket");
110
- return;
111
- }
112
- const cancelled = false;
113
- player.emit(eventName, packet, cancelled);
114
- if (cancelled)
115
- return;
116
- if ("binary" in packet) {
117
- packet.binary = [];
118
- }
119
- newBuffer = packet.serialize();
120
- this.packetSerializationCache.set(cacheKey, newBuffer);
170
+ try {
171
+ const newSerializedBuffer = packet.serialize();
172
+ this.packetSerializationCache.set(cacheKey, newSerializedBuffer);
173
+ sender.send(newSerializedBuffer);
174
+ }
175
+ catch (e) {
176
+ raknet_1.Logger.error(`Failed to serialize modified ${packetName} (ID: ${id}). Sending original buffer.`, e);
177
+ sender.send(buffer);
121
178
  }
122
- sender.send(newBuffer);
123
179
  }
124
- catch (e) {
125
- raknet_1.Logger.error(`Failed to process ${packetName}`, e);
180
+ else {
181
+ if (this.debugLog) {
182
+ raknet_1.Logger.info(`${isClientbound ? "Client -> BridgePlayer" : "BridgePlayer -> Client"} : ${packetName} (ID: ${id}) (Processed, not modified, sending original)`);
183
+ }
126
184
  sender.send(buffer);
127
185
  }
128
186
  }
@@ -163,14 +221,49 @@ class Bridge extends server_1.Server {
163
221
  });
164
222
  }
165
223
  onLogin(player) {
224
+ console.log("Creating Client");
225
+ const payload = player.player.data.payload;
166
226
  const client = new client_1.Client({
167
227
  host: this.options.destination.host,
168
228
  port: this.options.destination.port,
169
229
  version: this.options.version,
170
230
  tokensFolder: "tokens",
171
- viewDistance: 2,
231
+ viewDistance: payload.MaxViewDistance,
172
232
  worker: true,
173
233
  offline: this.options.offline,
234
+ deviceOS: payload.DeviceOS,
235
+ skinData: {
236
+ AnimatedImageData: payload.AnimatedImageData,
237
+ ArmSize: payload.ArmSize,
238
+ SkinData: payload.SkinData,
239
+ TrustedSkin: payload.TrustedSkin,
240
+ CapeData: payload.CapeData,
241
+ CapeId: payload.CapeId,
242
+ CapeImageHeight: payload.CapeImageHeight,
243
+ CapeImageWidth: payload.CapeImageWidth,
244
+ CapeOnClassicSkin: payload.CapeOnClassicSkin,
245
+ PersonaPieces: payload.PersonaPieces,
246
+ PieceTintColors: payload.PieceTintColors,
247
+ PersonaSkin: payload.PersonaSkin,
248
+ PremiumSkin: payload.PremiumSkin,
249
+ SkinAnimationData: payload.SkinAnimationData,
250
+ SkinColor: payload.SkinColor,
251
+ SkinGeometryData: payload.SkinGeometryData,
252
+ SkinGeometryDataEngineVersion: payload.SkinGeometryDataEngineVersion,
253
+ SkinId: payload.SkinId,
254
+ SkinImageHeight: payload.SkinImageHeight,
255
+ SkinImageWidth: payload.SkinImageWidth,
256
+ SkinResourcePatch: payload.SkinResourcePatch,
257
+ },
258
+ loginOptions: {
259
+ CurrentInputMode: payload.CurrentInputMode,
260
+ DefaultInputMode: payload.DefaultInputMode,
261
+ DeviceModel: payload.DeviceModel,
262
+ },
263
+ platformType: payload.PlatformType,
264
+ memoryTier: payload.MemoryTier,
265
+ uiProfile: payload.UIProfile,
266
+ graphicsMode: payload.GraphicsMode,
174
267
  });
175
268
  console.log(this.options);
176
269
  player.client = client;
@@ -183,10 +276,10 @@ class Bridge extends server_1.Server {
183
276
  packet.enabled = false;
184
277
  client.send(packet.serialize());
185
278
  });
186
- player.once("serverbound-ResourcePackClientResponsePacket", () => {
187
- const packet = new protocol_1.ClientCacheStatusPacket();
188
- packet.enabled = false;
189
- player.player.send(packet.serialize());
279
+ player.once("serverbound-ResourcePackClientResponsePacket", (packet, eventStatus) => {
280
+ const responsePacket = new protocol_1.ClientCacheStatusPacket();
281
+ responsePacket.enabled = false;
282
+ player.player.send(responsePacket.serialize());
190
283
  });
191
284
  client.once("PlayStatusPacket", (packet) => {
192
285
  if (packet.status !== protocol_1.PlayStatus.LoginSuccess) {
@@ -105,7 +105,10 @@ class ClientData {
105
105
  }
106
106
  async createClientUserChain(privateKey) {
107
107
  const { clientX509 } = this.loginData;
108
- const customPayload = this.client.options.skinData || {};
108
+ // Partial Payload
109
+ const customPayload = {
110
+ ...this.client.options.skinData,
111
+ };
109
112
  const payload = {
110
113
  ...this.payload,
111
114
  ...customPayload,
@@ -4,10 +4,12 @@ import type { ClientCacheStatusPacket } from "../network/packets/client-cache-st
4
4
  import type { Advertisement } from "@sanctumterra/raknet";
5
5
  import { AddPaintingPacket, UpdateSubchunkBlocksPacket, MotionPredictHintsPacket, SetLastHurtByPacket, SetDefaultGamemodePacket, UpdatePlayerGameTypePacket, UpdateBlockSyncPacket } from "../network/packets";
6
6
  import { LevelChunkPacket } from "../network/packets/level-chunk-packet";
7
+ import type { SkinData } from "./types/payload";
7
8
  export declare enum ProtocolList {
8
9
  "1.21.50" = 766,
9
10
  "1.21.60" = 776,
10
- "1.21.70" = 786
11
+ "1.21.70" = 786,
12
+ "1.21.80" = 800
11
13
  }
12
14
  export declare enum DeviceOS {
13
15
  Undefined = 0,
@@ -43,11 +45,15 @@ type ClientOptions = {
43
45
  username: string;
44
46
  tokensFolder: string;
45
47
  viewDistance: number;
46
- skinData: object | undefined;
48
+ skinData: SkinData | undefined;
47
49
  offline: boolean;
48
50
  worker: boolean;
49
51
  loginOptions: LoginPacketOptions;
50
52
  betaAuth: boolean;
53
+ platformType: number;
54
+ memoryTier: number;
55
+ uiProfile: number;
56
+ graphicsMode: number;
51
57
  };
52
58
  declare const defaultClientOptions: ClientOptions;
53
59
  type PacketNames = {
@@ -79,13 +85,5 @@ type ClientEvents = {
79
85
  connect: [packet: Advertisement];
80
86
  };
81
87
  export { type ClientOptions, defaultClientOptions, type ClientEvents, type PacketNames, };
82
- export declare const ExtraPackets: {
83
- 58: typeof LevelChunkPacket;
84
- 22: typeof AddPaintingPacket;
85
- 172: typeof UpdateSubchunkBlocksPacket;
86
- 157: typeof MotionPredictHintsPacket;
87
- 96: typeof SetLastHurtByPacket;
88
- 105: typeof SetDefaultGamemodePacket;
89
- 151: typeof UpdatePlayerGameTypePacket;
90
- 110: typeof UpdateBlockSyncPacket;
91
- };
88
+ export type ExtraPacketType = typeof LevelChunkPacket | typeof AddPaintingPacket | typeof UpdateSubchunkBlocksPacket | typeof MotionPredictHintsPacket | typeof SetLastHurtByPacket | typeof SetDefaultGamemodePacket | typeof UpdatePlayerGameTypePacket | typeof UpdateBlockSyncPacket;
89
+ export declare const ExtraPackets: Record<number, ExtraPacketType>;
@@ -9,6 +9,7 @@ var ProtocolList;
9
9
  ProtocolList[ProtocolList["1.21.50"] = 766] = "1.21.50";
10
10
  ProtocolList[ProtocolList["1.21.60"] = 776] = "1.21.60";
11
11
  ProtocolList[ProtocolList["1.21.70"] = 786] = "1.21.70";
12
+ ProtocolList[ProtocolList["1.21.80"] = 800] = "1.21.80";
12
13
  })(ProtocolList || (exports.ProtocolList = ProtocolList = {}));
13
14
  var DeviceOS;
14
15
  (function (DeviceOS) {
@@ -36,7 +37,7 @@ const defaultClientOptions = {
36
37
  compressionMethod: protocol_1.CompressionMethod.Zlib,
37
38
  compressionLevel: 7,
38
39
  deviceOS: DeviceOS.NintendoSwitch,
39
- version: "1.21.70",
40
+ version: "1.21.80",
40
41
  username: "SanctumTerra",
41
42
  tokensFolder: "tokens",
42
43
  viewDistance: 10,
@@ -44,11 +45,15 @@ const defaultClientOptions = {
44
45
  offline: false,
45
46
  worker: false,
46
47
  loginOptions: {
47
- DeviceModel: "SwimmingPool",
48
+ DeviceModel: "Beans something something",
48
49
  CurrentInputMode: protocol_1.InputMode.GamePad,
49
50
  DefaultInputMode: protocol_1.InputMode.GamePad,
50
51
  },
51
52
  betaAuth: false,
53
+ platformType: 2,
54
+ memoryTier: 2,
55
+ uiProfile: 0,
56
+ graphicsMode: 0,
52
57
  };
53
58
  exports.defaultClientOptions = defaultClientOptions;
54
59
  exports.ExtraPackets = {
@@ -25,19 +25,30 @@ declare class Client extends Emitter<ClientEvents> {
25
25
  runtimeEntityId: bigint;
26
26
  cancelPastLogin: boolean;
27
27
  constructor(options: Partial<ClientOptions>);
28
+ private initializeSession;
28
29
  connect(): Promise<[Advertisement, StartGamePacket]>;
30
+ private waitForSessionReady;
31
+ private waitForConnection;
29
32
  private handleStartGamePacket;
33
+ private requestChunkRadius;
30
34
  private handleEncapsulated;
31
35
  private sendPacket;
32
36
  send(packet: DataPacket | Buffer): void;
33
37
  queue(packet: DataPacket | Buffer): void;
34
38
  /** Already decompressed packets */
35
39
  processPacket(buffer: Buffer): void;
36
- listen(): void;
40
+ private setupEventListeners;
41
+ private setupConnectionListeners;
42
+ private handleRaknetConnect;
43
+ private setupPacketListeners;
44
+ private handleNetworkSettingsPacket;
45
+ private handleServerHandshake;
37
46
  private handlePlayStatusPacket;
47
+ private completePlayerSpawn;
38
48
  private handleResourcePacksInfoPacket;
39
49
  startEncryption(iv: Buffer): void;
40
50
  disconnect(): void;
51
+ private cleanup;
41
52
  sendMessage(text: string): void;
42
53
  }
43
54
  export { Client };
@@ -12,6 +12,9 @@ const packet_encryptor_1 = require("../network/packet-encryptor");
12
12
  const client_data_1 = require("./client-data");
13
13
  const client_options_1 = require("./client-options");
14
14
  const worker_1 = require("./worker");
15
+ const RETRY_INTERVAL_MS = 50;
16
+ const CONNECTION_CHECK_INTERVAL_MS = 50;
17
+ const NETWORK_SETTINGS_RETRY_INTERVAL_MS = 50;
15
18
  class Client extends emitter_1.Emitter {
16
19
  constructor(options) {
17
20
  super();
@@ -35,17 +38,26 @@ class Client extends emitter_1.Emitter {
35
38
  this.once("session", () => {
36
39
  this.sessionReady = true;
37
40
  });
41
+ this.initializeSession();
42
+ }
43
+ initializeSession() {
38
44
  this.options.offline ? (0, network_1.createOfflineSession)(this) : (0, network_1.authenticate)(this);
39
45
  }
40
46
  async connect() {
41
- while (!this.sessionReady) {
42
- await new Promise((resolve) => setTimeout(resolve, 10));
43
- }
47
+ await this.waitForSessionReady();
44
48
  this.status = raknet_1.Status.Connecting;
45
49
  this.packetCompressor = new network_1.PacketCompressor(this);
46
- this.listen();
50
+ this.setupEventListeners();
47
51
  const advertisement = await this.raknet.connect();
48
52
  this.raknet.on("encapsulated", this.handleEncapsulated.bind(this));
53
+ return this.waitForConnection(advertisement);
54
+ }
55
+ async waitForSessionReady() {
56
+ while (!this.sessionReady) {
57
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL_MS));
58
+ }
59
+ }
60
+ async waitForConnection(advertisement) {
49
61
  return new Promise((resolve, reject) => {
50
62
  this.once("StartGamePacket", this.handleStartGamePacket.bind(this));
51
63
  this.once("SetLocalPlayerAsInitializedPacket", () => {
@@ -59,7 +71,7 @@ class Client extends emitter_1.Emitter {
59
71
  this.emit("connect", advertisement);
60
72
  resolve([advertisement, this.startGamePacket]);
61
73
  }
62
- }, 50);
74
+ }, CONNECTION_CHECK_INTERVAL_MS);
63
75
  });
64
76
  }
65
77
  handleStartGamePacket(packet) {
@@ -67,6 +79,9 @@ class Client extends emitter_1.Emitter {
67
79
  this.runtimeEntityId = packet.runtimeEntityId;
68
80
  if (this.cancelPastLogin)
69
81
  return;
82
+ this.requestChunkRadius();
83
+ }
84
+ requestChunkRadius() {
70
85
  const radius = new protocol_1.RequestChunkRadiusPacket();
71
86
  radius.radius = this.options.viewDistance;
72
87
  radius.maxRadius = this.options.viewDistance;
@@ -95,103 +110,115 @@ class Client extends emitter_1.Emitter {
95
110
  this.raknet.sendFrame(frame, priority);
96
111
  }
97
112
  catch (error) {
98
- raknet_1.Logger.error(`Failed to send packet ${packet.constructor.name}`, error);
113
+ raknet_1.Logger.error(`Failed to send packet ${packet instanceof protocol_1.DataPacket ? packet.constructor.name : "Buffer"}`, error);
99
114
  }
100
115
  }
101
116
  send(packet) {
102
117
  this.sendPacket(packet, raknet_1.Priority.Immediate);
103
118
  }
104
119
  queue(packet) {
105
- raknet_1.Logger.debug(`Queueing packet ${packet.constructor.name}`);
120
+ raknet_1.Logger.debug(`Queueing packet ${packet instanceof protocol_1.DataPacket ? packet.constructor.name : "Buffer"}`);
106
121
  this.sendPacket(packet, raknet_1.Priority.Normal);
107
122
  }
108
123
  /** Already decompressed packets */
109
124
  processPacket(buffer) {
110
125
  const id = (0, protocol_1.getPacketId)(buffer);
111
126
  let PacketClass = protocol_1.Packets[id];
112
- // Logger.info(`Processing packet ${PacketClass.name ?? "unknown"} - ${id}`);
113
127
  try {
114
- // @ts-ignore
115
- if (id in client_options_1.ExtraPackets)
128
+ if (id in client_options_1.ExtraPackets) {
116
129
  PacketClass = client_options_1.ExtraPackets[id];
117
- if ((!protocol_1.Packets && !protocol_1.Packets[id]) || !PacketClass || !PacketClass.name)
118
- return raknet_1.Logger.warn(`Unknown Game packet ${id}`);
130
+ }
131
+ if (!PacketClass || !PacketClass.name) {
132
+ raknet_1.Logger.warn(`Unknown Game packet ${id}`);
133
+ return;
134
+ }
119
135
  const hasSpecificListener = this.hasListeners(PacketClass.name);
120
136
  const hasGenericListener = this.hasListeners("packet");
121
- let deserializedPacket;
122
137
  if (hasSpecificListener || hasGenericListener) {
123
- deserializedPacket = new PacketClass(buffer).deserialize();
124
- if (hasSpecificListener)
138
+ const deserializedPacket = new PacketClass(buffer).deserialize();
139
+ if (hasSpecificListener) {
125
140
  this.emit(PacketClass.name, deserializedPacket);
126
- if (hasGenericListener)
141
+ }
142
+ if (hasGenericListener) {
127
143
  this.emit("packet", deserializedPacket);
144
+ }
128
145
  }
129
146
  }
130
147
  catch (error) {
131
- raknet_1.Logger.error(`Failed to process packet ${PacketClass.name}`, error);
148
+ raknet_1.Logger.error(`Failed to process packet ${PacketClass?.name || id}`, error);
132
149
  }
133
150
  }
134
- listen() {
135
- this.raknet.once("connect", () => {
136
- const timer = setInterval(() => {
137
- if (!this.sessionReady)
138
- return;
139
- const request = new protocol_1.RequestNetworkSettingsPacket();
140
- request.protocol = this.protocol;
141
- this.send(request);
142
- clearInterval(timer);
143
- }, 50);
144
- });
151
+ setupEventListeners() {
152
+ this.setupConnectionListeners();
153
+ this.setupPacketListeners();
154
+ }
155
+ setupConnectionListeners() {
156
+ this.raknet.once("connect", this.handleRaknetConnect.bind(this));
145
157
  this.raknet.on("close", () => this.disconnect());
146
- this.once("NetworkSettingsPacket", (packet) => {
147
- this._compressionEnabled = true;
148
- this.options.compressionMethod = this.packetCompressor.getMethod(packet.compressionMethod);
149
- this.options.compressionThreshold = packet.compressionThreshold;
150
- const loginPacket = this.data.createLoginPacket();
151
- this.send(loginPacket);
152
- });
153
- this.once("ServerToClientHandshakePacket", (packet) => {
154
- const [header, payload] = packet.token
155
- .split(".")
156
- .map((k) => Buffer.from(k, "base64"));
157
- const { x5u } = JSON.parse(header.toString());
158
- const { salt } = JSON.parse(payload.toString());
159
- const pubKeyDer = (0, node_crypto_2.createPublicKey)({
160
- key: Buffer.from(x5u, "base64"),
161
- type: "spki",
162
- format: "der",
163
- });
164
- this.data.sharedSecret = this.data.createSharedSecret(this.data.loginData.ecdhKeyPair.privateKey, pubKeyDer);
165
- const secretHash = (0, node_crypto_1.createHash)("sha256")
166
- .update(new Uint8Array(Buffer.from(salt, "base64")))
167
- .update(new Uint8Array(this.data.sharedSecret))
168
- .digest();
169
- this.secretKeyBytes = secretHash;
170
- this.iv = secretHash.slice(0, 16);
171
- this.startEncryption(this.iv);
172
- const handshake = new protocol_1.ClientToServerHandshakePacket();
173
- this.send(handshake);
174
- });
158
+ }
159
+ handleRaknetConnect() {
160
+ const timer = setInterval(() => {
161
+ if (!this.sessionReady)
162
+ return;
163
+ const request = new protocol_1.RequestNetworkSettingsPacket();
164
+ request.protocol = this.protocol;
165
+ this.send(request);
166
+ clearInterval(timer);
167
+ }, NETWORK_SETTINGS_RETRY_INTERVAL_MS);
168
+ }
169
+ setupPacketListeners() {
170
+ this.once("NetworkSettingsPacket", this.handleNetworkSettingsPacket.bind(this));
171
+ this.once("ServerToClientHandshakePacket", this.handleServerHandshake.bind(this));
175
172
  this.once("ResourcePacksInfoPacket", this.handleResourcePacksInfoPacket.bind(this));
176
173
  this.on("PlayStatusPacket", this.handlePlayStatusPacket.bind(this));
177
174
  }
175
+ handleNetworkSettingsPacket(packet) {
176
+ this._compressionEnabled = true;
177
+ this.options.compressionMethod = this.packetCompressor.getMethod(packet.compressionMethod);
178
+ this.options.compressionThreshold = packet.compressionThreshold;
179
+ const loginPacket = this.data.createLoginPacket();
180
+ this.send(loginPacket);
181
+ }
182
+ handleServerHandshake(packet) {
183
+ const [header, payload] = packet.token
184
+ .split(".")
185
+ .map((k) => Buffer.from(k, "base64"));
186
+ const { x5u } = JSON.parse(header.toString());
187
+ const { salt } = JSON.parse(payload.toString());
188
+ const pubKeyDer = (0, node_crypto_2.createPublicKey)({
189
+ key: Buffer.from(x5u, "base64"),
190
+ type: "spki",
191
+ format: "der",
192
+ });
193
+ this.data.sharedSecret = this.data.createSharedSecret(this.data.loginData.ecdhKeyPair.privateKey, pubKeyDer);
194
+ const secretHash = (0, node_crypto_1.createHash)("sha256")
195
+ .update(new Uint8Array(Buffer.from(salt, "base64")))
196
+ .update(new Uint8Array(this.data.sharedSecret))
197
+ .digest();
198
+ this.secretKeyBytes = secretHash;
199
+ this.iv = secretHash.slice(0, 16);
200
+ this.startEncryption(this.iv);
201
+ const handshake = new protocol_1.ClientToServerHandshakePacket();
202
+ this.send(handshake);
203
+ }
178
204
  handlePlayStatusPacket(packet) {
179
205
  if (packet.status === protocol_1.PlayStatus.LoginSuccess) {
180
206
  }
181
- if (packet.status === protocol_1.PlayStatus.PlayerSpawn) {
182
- if (this.cancelPastLogin)
183
- return;
184
- const init = new protocol_1.SetLocalPlayerAsInitializedPacket();
185
- init.runtimeEntityId = this.runtimeEntityId;
186
- const ServerBoundLoadingScreen = new protocol_1.ServerboundLoadingScreenPacketPacket();
187
- ServerBoundLoadingScreen.type =
188
- protocol_1.ServerboundLoadingScreenType.EndLoadingScreen;
189
- ServerBoundLoadingScreen.hasScreenId = false;
190
- this.send(init);
191
- this.send(ServerBoundLoadingScreen);
192
- this.emit("SetLocalPlayerAsInitializedPacket", init);
207
+ if (packet.status === protocol_1.PlayStatus.PlayerSpawn && !this.cancelPastLogin) {
208
+ this.completePlayerSpawn();
193
209
  }
194
210
  }
211
+ completePlayerSpawn() {
212
+ const init = new protocol_1.SetLocalPlayerAsInitializedPacket();
213
+ init.runtimeEntityId = this.runtimeEntityId;
214
+ const serverBoundLoadingScreen = new protocol_1.ServerboundLoadingScreenPacketPacket();
215
+ serverBoundLoadingScreen.type =
216
+ protocol_1.ServerboundLoadingScreenType.EndLoadingScreen;
217
+ serverBoundLoadingScreen.hasScreenId = false;
218
+ this.send(init);
219
+ this.send(serverBoundLoadingScreen);
220
+ this.emit("SetLocalPlayerAsInitializedPacket", init);
221
+ }
195
222
  handleResourcePacksInfoPacket(packet) {
196
223
  if (this.cancelPastLogin)
197
224
  return;
@@ -199,27 +226,37 @@ class Client extends emitter_1.Emitter {
199
226
  response.packs = [];
200
227
  response.response = protocol_1.ResourcePackResponse.Completed;
201
228
  this.send(response);
202
- if (packet instanceof protocol_1.ResourcePacksInfoPacket)
229
+ if (packet instanceof protocol_1.ResourcePacksInfoPacket) {
203
230
  this.send(client_cache_status_1.ClientCacheStatusPacket.create(false));
231
+ }
204
232
  }
205
233
  startEncryption(iv) {
206
234
  this.packetEncryptor = new packet_encryptor_1.PacketEncryptor(this, iv);
207
235
  this._encryptionEnabled = true;
208
236
  }
209
237
  disconnect() {
238
+ if (this.status === raknet_1.Status.Disconnected)
239
+ return;
210
240
  this.status = raknet_1.Status.Disconnected;
211
241
  try {
212
- this.removeAllListeners();
213
- this.destroy();
214
- this.raknet.disconnect();
215
- this.packetEncryptor.destroy();
216
- this._encryptionEnabled = false;
217
- raknet_1.Logger.cleanup();
242
+ this.cleanup();
218
243
  }
219
244
  catch (error) {
220
245
  raknet_1.Logger.error("Error during disconnect:", error);
221
246
  }
222
247
  }
248
+ cleanup() {
249
+ this.removeAllListeners();
250
+ this.destroy();
251
+ if (this.raknet) {
252
+ this.raknet.disconnect();
253
+ }
254
+ if (this.packetEncryptor) {
255
+ this.packetEncryptor.destroy();
256
+ this._encryptionEnabled = false;
257
+ }
258
+ raknet_1.Logger.cleanup();
259
+ }
223
260
  sendMessage(text) {
224
261
  const textPacket = new protocol_1.TextPacket();
225
262
  textPacket.filtered = "";
@@ -48,4 +48,27 @@ export type Payload = {
48
48
  UIProfile: number;
49
49
  GraphicsMode: number;
50
50
  };
51
+ export type SkinData = {
52
+ AnimatedImageData: AnimatedImageData[];
53
+ ArmSize: string;
54
+ CapeData: string;
55
+ CapeId: string;
56
+ CapeImageHeight: number;
57
+ CapeImageWidth: number;
58
+ CapeOnClassicSkin: boolean;
59
+ PieceTintColors: PieceTintColors[];
60
+ PersonaPieces: PersonaPieces[];
61
+ PersonaSkin: boolean;
62
+ PremiumSkin: boolean;
63
+ SkinAnimationData: string;
64
+ SkinColor: string;
65
+ SkinData: string;
66
+ SkinGeometryData: string;
67
+ SkinGeometryDataEngineVersion: string;
68
+ SkinId: string;
69
+ SkinImageHeight: number;
70
+ SkinImageWidth: number;
71
+ SkinResourcePatch: string;
72
+ TrustedSkin: boolean;
73
+ };
51
74
  export declare const createDefaultPayload: (client: Client | Player) => Payload;
@@ -59,14 +59,14 @@ const createDefaultPayload = (client) => {
59
59
  IsEditorMode: false,
60
60
  LanguageCode: "en_US",
61
61
  MaxViewDistance: client.options?.viewDistance,
62
- MemoryTier: 2,
62
+ MemoryTier: client.options.memoryTier,
63
63
  OverrideSkin: false,
64
64
  PersonaPieces: skin.skinData.PersonaPieces,
65
65
  PersonaSkin: skin.skinData.PersonaSkin,
66
66
  PieceTintColors: skin.skinData.PieceTintColors,
67
67
  PlatformOfflineId: client_data_1.ClientData.nextUUID(username).replace(/-/g, ""),
68
68
  PlatformOnlineId: client_data_1.ClientData.OnlineId(),
69
- PlatformType: 2,
69
+ PlatformType: client.options.platformType,
70
70
  // PlatformUserId: "",
71
71
  PlayFabId: client_data_1.ClientData.nextUUID(username).replace(/-/g, "").slice(0, 16),
72
72
  PremiumSkin: skin.skinData.PremiumSkin,
@@ -84,8 +84,8 @@ const createDefaultPayload = (client) => {
84
84
  ThirdPartyName: username,
85
85
  ThirdPartyNameOnly: false,
86
86
  TrustedSkin: skin.skinData.TrustedSkin,
87
- UIProfile: 0,
88
- GraphicsMode: 0,
87
+ UIProfile: client.options.uiProfile,
88
+ GraphicsMode: client.options.graphicsMode,
89
89
  };
90
90
  return payload;
91
91
  };
@@ -1,23 +1,16 @@
1
+ import type { Buffer } from "node:buffer";
1
2
  import { DataPacket, type DimensionType } from "@serenityjs/protocol";
2
3
  declare class LevelChunkPacket extends DataPacket {
3
4
  private static readonly MAX_BLOB_HASHES;
4
- private static readonly CLIENT_REQUEST_FULL_COLUMN_FAKE_COUNT;
5
- private static readonly CLIENT_REQUEST_TRUNCATED_COLUMN_FAKE_COUNT;
6
5
  x: number;
7
6
  z: number;
8
7
  dimension: DimensionType;
9
- client_needs_to_request_subchunks: boolean;
10
- client_request_subchunk_limit?: number;
11
- sub_chunk_count: number;
12
- partial_subchunk_count_when_requesting?: number;
13
- subchunk_count_when_requesting?: number;
14
- highest_subchunk_count?: number;
15
- cache_enabled: boolean;
16
- blobs?: Array<bigint>;
17
- payload: Buffer;
8
+ highestSubChunkCount: number;
9
+ subChunkCount: number;
10
+ cacheEnabled: boolean;
11
+ blobs: Array<bigint>;
12
+ data: Buffer;
18
13
  serialize(): Buffer;
19
14
  deserialize(): this;
20
- private writeByteArray;
21
- private readByteArray;
22
15
  }
23
16
  export { LevelChunkPacket };
@@ -8,20 +8,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var LevelChunkPacket_1;
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.LevelChunkPacket = void 0;
11
- const protocol_1 = require("@serenityjs/protocol");
12
11
  const raknet_1 = require("@serenityjs/raknet");
12
+ const protocol_1 = require("@serenityjs/protocol");
13
13
  let LevelChunkPacket = LevelChunkPacket_1 = class LevelChunkPacket extends protocol_1.DataPacket {
14
14
  serialize() {
15
15
  this.writeVarInt(protocol_1.Packet.LevelChunk);
16
16
  this.writeZigZag(this.x);
17
17
  this.writeZigZag(this.z);
18
18
  this.writeZigZag(this.dimension);
19
- this.writeVarInt(this.sub_chunk_count);
20
- if (this.sub_chunk_count === -2) {
21
- this.writeUint16(this.highest_subchunk_count ?? 0, 1 /* Endianness.Little */);
19
+ this.writeVarInt(this.subChunkCount);
20
+ if (this.subChunkCount === -2) {
21
+ this.writeUint16(this.highestSubChunkCount ?? 0, 1 /* Endianness.Little */);
22
22
  }
23
- this.writeBool(this.cache_enabled);
24
- if (this.cache_enabled) {
23
+ this.writeBool(this.cacheEnabled);
24
+ if (this.cacheEnabled) {
25
25
  if (!this.blobs)
26
26
  throw new Error("Blobs required when cache_enabled is true");
27
27
  this.writeVarInt(this.blobs.length);
@@ -29,7 +29,8 @@ let LevelChunkPacket = LevelChunkPacket_1 = class LevelChunkPacket extends proto
29
29
  this.writeUint64(hash, 1 /* Endianness.Little */);
30
30
  }
31
31
  }
32
- this.writeByteArray(this.payload);
32
+ this.writeVarInt(this.data.length);
33
+ this.writeBuffer(this.data);
33
34
  return this.getBuffer();
34
35
  }
35
36
  deserialize() {
@@ -37,14 +38,14 @@ let LevelChunkPacket = LevelChunkPacket_1 = class LevelChunkPacket extends proto
37
38
  this.x = this.readZigZag();
38
39
  this.z = this.readZigZag();
39
40
  this.dimension = this.readZigZag();
40
- this.sub_chunk_count = this.readVarInt();
41
- if (this.sub_chunk_count === 4294967294)
42
- this.sub_chunk_count = -2;
43
- if (this.sub_chunk_count === -2) {
44
- this.highest_subchunk_count = this.readUint16(1 /* Endianness.Little */);
41
+ this.subChunkCount = this.readVarInt();
42
+ if (this.subChunkCount === 4294967294)
43
+ this.subChunkCount = -2;
44
+ if (this.subChunkCount === -2) {
45
+ this.highestSubChunkCount = this.readUint16(1 /* Endianness.Little */);
45
46
  }
46
- this.cache_enabled = this.readBool();
47
- if (this.cache_enabled) {
47
+ this.cacheEnabled = this.readBool();
48
+ if (this.cacheEnabled) {
48
49
  const blobCount = this.readVarInt();
49
50
  if (blobCount > LevelChunkPacket_1.MAX_BLOB_HASHES) {
50
51
  throw new Error(`Too many blob hashes: ${blobCount}`);
@@ -54,22 +55,13 @@ let LevelChunkPacket = LevelChunkPacket_1 = class LevelChunkPacket extends proto
54
55
  this.blobs.push(this.readLong(1 /* Endianness.Little */));
55
56
  }
56
57
  }
57
- this.payload = this.readByteArray();
58
+ const dataLength = this.readVarInt();
59
+ this.data = this.readBuffer(dataLength);
58
60
  return this;
59
61
  }
60
- writeByteArray(buffer) {
61
- this.writeVarInt(buffer.length);
62
- this.writeBuffer(buffer);
63
- }
64
- readByteArray() {
65
- const length = this.readVarInt();
66
- return this.readBuffer(length);
67
- }
68
62
  };
69
63
  exports.LevelChunkPacket = LevelChunkPacket;
70
64
  LevelChunkPacket.MAX_BLOB_HASHES = 64;
71
- LevelChunkPacket.CLIENT_REQUEST_FULL_COLUMN_FAKE_COUNT = 0xffffffff;
72
- LevelChunkPacket.CLIENT_REQUEST_TRUNCATED_COLUMN_FAKE_COUNT = 0xfffffffe;
73
65
  exports.LevelChunkPacket = LevelChunkPacket = LevelChunkPacket_1 = __decorate([
74
66
  (0, raknet_1.Proto)(protocol_1.Packet.LevelChunk)
75
67
  ], LevelChunkPacket);
@@ -2,7 +2,7 @@ import { CompressionMethod, type DataPacket } from "@serenityjs/protocol";
2
2
  import type * as Protocol from "@serenityjs/protocol";
3
3
  import type { PacketNames } from "../client";
4
4
  import type { ClientCacheStatusPacket } from "../network/packets/client-cache-status";
5
- type Version = "1.21.50" | "1.21.60" | "1.21.70";
5
+ type Version = "1.21.50" | "1.21.60" | "1.21.70" | "1.21.80";
6
6
  export type PlayerEvents = {
7
7
  [K in PacketNames]: [packet: InstanceType<(typeof Protocol)[K]>];
8
8
  } & {
@@ -6,7 +6,7 @@ exports.defaultServerOptions = {
6
6
  host: "127.0.0.1",
7
7
  port: 19132,
8
8
  offline: true,
9
- version: "1.21.70",
9
+ version: "1.21.80",
10
10
  worker: true,
11
11
  maxPlayers: 100,
12
12
  compressionMethod: protocol_1.CompressionMethod.Zlib,
@@ -5,6 +5,7 @@ const raknet_1 = require("@sanctumterra/raknet");
5
5
  const libs_1 = require("../libs");
6
6
  const player_1 = require("./player");
7
7
  const server_options_1 = require("./server-options");
8
+ const protocol_1 = require("@serenityjs/protocol");
8
9
  class Server extends libs_1.Emitter {
9
10
  constructor(options) {
10
11
  super();
@@ -29,13 +30,30 @@ class Server extends libs_1.Emitter {
29
30
  if (!(connection instanceof raknet_1.Connection))
30
31
  throw new Error("Connection is not instance of Connection");
31
32
  const connectionKey = this.getConnectionKey(connection);
32
- if (this.connections.has(connectionKey))
33
- return;
33
+ const existingPlayer = this.connections.get(connectionKey);
34
+ if (existingPlayer) {
35
+ raknet_1.Logger.warn(`Disconnecting existing player ${existingPlayer.profile?.name ?? connectionKey} due to new connection from the same address.`);
36
+ // Send a disconnect packet to the existing player
37
+ const disconnectPacket = new protocol_1.DisconnectPacket();
38
+ disconnectPacket.message = new protocol_1.DisconnectMessage("You were disconnected because a new connection was made from your IP address.");
39
+ disconnectPacket.reason = protocol_1.DisconnectReason.Kicked;
40
+ disconnectPacket.hideDisconnectScreen = false;
41
+ try {
42
+ existingPlayer.send(disconnectPacket); // Assuming Player class has a send method for Protocol packets
43
+ // Optionally, also force close RakNet connection if send isn't enough or player is unresponsive
44
+ existingPlayer.connection.disconnect();
45
+ }
46
+ catch (error) {
47
+ raknet_1.Logger.error(`Error sending disconnect to existing player ${connectionKey}:`, error);
48
+ }
49
+ this.onDisconnect(existingPlayer); // Ensure cleanup and event emission
50
+ }
51
+ // Proceed to create and connect the new player
34
52
  const player = new player_1.Player(this, connection);
35
53
  this.connections.set(connectionKey, player);
36
54
  this.emit("playerConnect", player);
37
55
  const { address, port } = connection.getAddress();
38
- raknet_1.Logger.info("Player connected from: ", `${address}:${port}`);
56
+ raknet_1.Logger.info(`Player connected from: ${address}:${port}`);
39
57
  });
40
58
  this.raknet.start();
41
59
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "baltica",
3
3
  "description": "A MCBE Utility Library",
4
- "version": "0.0.08",
5
- "minecraft": "1.21.70",
4
+ "version": "0.0.10",
5
+ "minecraft": "1.21.80",
6
6
  "type": "commonjs",
7
7
  "main": "dist/index.js",
8
8
  "license": "MIT",
@@ -23,7 +23,9 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@sanctumterra/raknet": "^1.3.77",
26
- "@serenityjs/protocol": "^0.7.0",
26
+ "@serenityjs/binarystream": "^2.7.0",
27
+ "@serenityjs/core": "^0.8.2",
28
+ "@serenityjs/protocol": "0.8.2",
27
29
  "axios": "^1.7.9",
28
30
  "elliptic": "^6.6.1",
29
31
  "jose": "^5.2.3",