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.
- package/.extra/logo.png +0 -0
- package/README.md +303 -0
- package/dist/bridge/bridge-options.d.ts +12 -3
- package/dist/bridge/bridge-options.js +1 -0
- package/dist/bridge/bridge-player.js +2 -7
- package/dist/bridge/bridge.js +126 -33
- package/dist/client/client-data.js +4 -1
- package/dist/client/client-options.d.ts +10 -12
- package/dist/client/client-options.js +7 -2
- package/dist/client/client.d.ts +12 -1
- package/dist/client/client.js +113 -76
- package/dist/client/types/payload.d.ts +23 -0
- package/dist/client/types/payload.js +4 -4
- package/dist/network/packets/level-chunk-packet.d.ts +6 -13
- package/dist/network/packets/level-chunk-packet.js +17 -25
- package/dist/server/server-options.d.ts +1 -1
- package/dist/server/server-options.js +1 -1
- package/dist/server/server.js +21 -3
- package/package.json +5 -3
package/.extra/logo.png
ADDED
|
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
|
-
|
|
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
|
-
|
|
16
|
+
eventStatus: {
|
|
17
|
+
cancelled: boolean;
|
|
18
|
+
modified: boolean;
|
|
19
|
+
}
|
|
14
20
|
];
|
|
15
21
|
} & {
|
|
16
22
|
"serverbound-ClientCacheStatusPacket": [
|
|
17
23
|
packet: ClientCacheStatusPacket,
|
|
18
|
-
|
|
24
|
+
eventStatus: {
|
|
25
|
+
cancelled: boolean;
|
|
26
|
+
modified: boolean;
|
|
27
|
+
}
|
|
19
28
|
];
|
|
20
29
|
};
|
|
21
30
|
export type BridgeOptions = ServerOptions & {
|
|
@@ -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
|
}
|
package/dist/bridge/bridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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:
|
|
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
|
|
188
|
-
|
|
189
|
-
player.player.send(
|
|
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
|
-
|
|
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:
|
|
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
|
|
83
|
-
|
|
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.
|
|
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: "
|
|
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 = {
|
package/dist/client/client.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/client/client.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
148
|
+
raknet_1.Logger.error(`Failed to process packet ${PacketClass?.name || id}`, error);
|
|
132
149
|
}
|
|
133
150
|
}
|
|
134
|
-
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
88
|
-
GraphicsMode:
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
20
|
-
if (this.
|
|
21
|
-
this.writeUint16(this.
|
|
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.
|
|
24
|
-
if (this.
|
|
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.
|
|
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.
|
|
41
|
-
if (this.
|
|
42
|
-
this.
|
|
43
|
-
if (this.
|
|
44
|
-
this.
|
|
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.
|
|
47
|
-
if (this.
|
|
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
|
-
|
|
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
|
} & {
|
package/dist/server/server.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
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(
|
|
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.
|
|
5
|
-
"minecraft": "1.21.
|
|
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/
|
|
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",
|