From 87a33962545d765e3c9982381beb087090e5b33a Mon Sep 17 00:00:00 2001 From: bonanoo Date: Tue, 10 Dec 2024 22:18:06 +0200 Subject: [PATCH] Release: Fixes, 1.21.50 --- README.md | 12 ++- package.json | 11 +-- src/Client.ts | 42 +++++++-- src/Connection.ts | 10 ++- src/client/ClientData.ts | 1 + src/client/ClientOptions.ts | 9 +- src/tools/connect.ts | 13 ++- src/tools/connection.ts | 2 +- src/vendor/PacketEncryptor.ts | 22 ++--- src/vendor/PacketSorter.ts | 106 +++++----------------- src/vendor/packets/player-auth-input.ts | 111 ++++++++++++++++++++++++ tsconfig.json | 2 +- 12 files changed, 219 insertions(+), 122 deletions(-) create mode 100644 src/vendor/packets/player-auth-input.ts diff --git a/README.md b/README.md index 3daa558..704dd07 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🛠️ Minecraft Bedrock Edition Client Library -![Version](https://img.shields.io/badge/Supported%20Version-1.21.30-brightgreen) +![Version](https://img.shields.io/badge/Supported%20Version-1.21.50-brightgreen) ![npm](https://img.shields.io/npm/v/@sanctumterra/client) ![License](https://img.shields.io/badge/License-MIT-blue) @@ -30,6 +30,7 @@ npm i @sanctumterra/client | 2.0.10 | 712 | 1.21.20 | | 2.1.5 | 729 | 1.21.30 | | 2.1.12 | 748 | 1.21.40 | +| 2.1.13 | 766 | 1.21.50 | ## 🚀 Usage Example @@ -42,7 +43,7 @@ const client = new Client({ offline: true, username: "SanctumTerra", tokensFolder: "./cache/tokens", - version: "1.21.30", + version: "1.21.50", deviceOS: DeviceOS.Android }); @@ -75,7 +76,7 @@ client.on("spawn", () => { ### 🎛️ Client Configuration - **Required Parameters**: `host`, `port` -- **Optional Parameters**: `offline`, `username`, `tokensFolder`, `version`, `deviceOS` +- **Optional Parameters**: `offline`, `username`, `tokensFolder`, `version`, `deviceOS`, `viewDistance` ### 📡 Event Handling - Events allow you to listen to any implemented packet if it is not implemented you will receive a warning and it should not crash if there is a crash then make an issue on github. @@ -87,6 +88,11 @@ client.on("spawn", () => { ## 📜 Changelog +### 2.1.13 +- Added support for Minecraft 1.21.50. +- Fixed many bugs. +- Slightly Improved performance. + ### 2.1.12 - Added support for Minecraft 1.21.40. - New Raknet! It now uses NAPI as Rust is faster. diff --git a/package.json b/package.json index a6b65bd..4b2f8e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@sanctumterra/client", - "version": "2.1.12", - "minecraft": "1.21.40", + "version": "2.1.13", + "minecraft": "1.21.50", "description": "We'll be singing Baraye.", "main": "./dist/index.js", "type": "commonjs", @@ -35,15 +35,16 @@ "why-is-node-running": "^3.2.0" }, "dependencies": { - "@sanctumterra/raknet": "^1.3.57", + "@sanctumterra/raknet": "^1.3.60", "@sanctumterra/rs-rak-client": "^1.0.63", "@serenityjs/binarystream": "^2.6.6", "@serenityjs/block": "^0.4.4", "@serenityjs/network": "^0.4.4", - "@serenityjs/protocol": "^0.6.0", - "@serenityjs/raknet": "^0.4.4", + "@serenityjs/protocol": "^0.6.1-beta-20241119050127", + "@serenityjs/raknet": "^0.6.2-beta-20241128210118", "@serenityjs/world": "^0.4.4", "@types/node": "^22.7.5", + "crypto": "^1.0.1", "fast-json-stable-stringify": "^2.1.0", "lodash": "^4.17.21", "prismarine-auth": "github:AnyBananaGAME/prismarine-auth", diff --git a/src/Client.ts b/src/Client.ts index 29d3568..18869c2 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -16,7 +16,6 @@ import { NetworkItemStackDescriptor, PlayerActionPacket, PlayerAuthInputData, - PlayerAuthInputPacket, PlayMode, TextPacket, TextPacketType, @@ -33,6 +32,8 @@ import { Inventory } from "./client/inventory/Inventory"; import { Connection } from "./Connection"; import { Logger } from "./vendor/Logger"; import { Queue } from "./vendor/Queue"; +import { PlayerAuthInputPacket } from "./vendor/packets/player-auth-input"; +import type { PlayerAuthInputPacket as PAIP } from "@serenityjs/protocol"; class Client extends Connection { private sneaking = false; @@ -87,7 +88,7 @@ class Client extends Connection { packet.playMode = PlayMode.Screen; packet.interactionMode = InteractionMode.Touch; packet.interactRotation = new Vector2f(0, 0); - packet.inputTick = BigInt(this.tick); + packet.tick = BigInt(this.tick); packet.positionDelta = new Vector3f(0, 0, 0); packet.itemStackRequest = null; packet.blockActions = null; @@ -97,7 +98,7 @@ class Client extends Connection { const cancel = false; this.emit("PrePlayerAuthInputPacket", packet, cancel); if (!cancel) { - this.sendPacket(packet, Priority.Immediate); + // this.sendPacket(packet, Priority.Immediate); } }, 100); } @@ -208,7 +209,7 @@ class Client extends Connection { * @param position The position of the block * @param ticks The number of ticks to break the block */ - private async breakBlock(position: Vector3f, ticks = 5): Promise { + public async breakBlock(position: Vector3f, ticks = 5): Promise { const MAX_DISTANCE = 5; const TICK_INTERVAL = 100; @@ -229,7 +230,8 @@ class Client extends Connection { return new Promise((resolve) => { this.once( "PrePlayerAuthInputPacket", - (packet: PlayerAuthInputPacket) => { + // @ts-expect-error meh + (packet: PlayerAuthInputPacket, _cancel: boolean) => { modifier(packet); resolve(); }, @@ -285,9 +287,33 @@ class Client extends Connection { // Stop Break await modifyNextPacket((packet: PlayerAuthInputPacket) => { - // packet.inputData.setFlag(InputDataFlags.BlockAction, true); - // packet.inputData.setFlag(InputDataFlags.ItemInteract, true); - // this.lookAt(position.x, position.y, position.z); + packet.inputData.setFlag(InputData.PerformBlockActions, true); + packet.inputData.setFlag(InputData.StartUsingItem, true); + this.lookAt(position.x, position.y, position.z); + packet.blockActions = new PlayerBlockActions([ + new PlayerBlockActionData( + PlayerActionType.StopDestroyBlock, + position, + face, + ), + ]); + packet.inputTransaction = new InputTransaction( + new LegacyTransaction(0, []), + [], + new ItemUseInventoryTransaction( + ItemUseInventoryTransactionType.Destroy, + TriggerType.Unknown, + position, + face, + 0, + new NetworkItemStackDescriptor(0), + this.position, + new Vector3f(0, 0, 0), + 0, + false, + ), + ); + // packet.blockActions = new PlayerBlockActions([ // new PlayerBlockActionData(PlayerActionType.StopDestroyBlock, position, face), // ]); diff --git a/src/Connection.ts b/src/Connection.ts index 4699c79..1bac539 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -38,6 +38,7 @@ import { createPublicKey, KeyObject, } from "node:crypto"; +import * as crypto from "node:crypto"; import { measureExecutionTime } from "./vendor/debug-tools"; import { @@ -61,6 +62,7 @@ class Connection extends Listener { public position!: Vector3f; public tick = 0; public _encryption = false; + public compression = false; public options: ClientOptions; public data: ClientData; @@ -147,6 +149,7 @@ class Connection extends Listener { const networkSettingsPacket = new RequestNetworkSettingsPacket(); networkSettingsPacket.protocol = this.protocol; this.sendPacket(networkSettingsPacket); + this.compression = true; } @measureExecutionTime @@ -155,6 +158,8 @@ class Connection extends Listener { let Advertisement_: Advertisement; this.once("session", async () => { Advertisement_ = await this.handleSessionStart(); + console.timeEnd("RakConnect"); + // this.raknet.frameAndSend(Buffer.from([254, 0, 236, 151, 151, 151])) }); this.once("StartGamePacket", (packet: StartGamePacket) => { resolve([Advertisement_, packet]); @@ -173,6 +178,7 @@ class Connection extends Listener { if (this.options.debug) Logger.debug("S -> C NetworkSettingsPacket"); this.data.sendDeflated = true; this.data.compressionThreshold = instance.compressionThreshold; + this.data.compressionMethod = instance.compressionMethod; this.sendLoginPacket(); } @@ -207,6 +213,8 @@ class Connection extends Listener { pubKeyDer, ); + // this.data.sharedSecret = diffieHellman({ privateKey: this.data.loginData.ecdhKeyPair.privateKey, publicKey: pubKeyDer }) + this.setupEncryption(salt); this.sendClientToServerHandshake(); } @@ -283,7 +291,7 @@ class Connection extends Listener { ecdh.setPrivateKey(Buffer.from(privateKeyJwk.d, "base64")); const publicKeyBuffer = Buffer.concat([ - Buffer.from([0x04]), // Uncompressed point format + Buffer.from([0x04]), Buffer.from(publicKeyJwk.x, "base64"), Buffer.from(publicKeyJwk.y, "base64"), ]); diff --git a/src/client/ClientData.ts b/src/client/ClientData.ts index b76c9bc..36772ad 100644 --- a/src/client/ClientData.ts +++ b/src/client/ClientData.ts @@ -117,6 +117,7 @@ class ClientData { public accessToken!: string[]; public sendDeflated = false; public compressionThreshold!: number; + public compressionMethod = 0; public sharedSecret!: Buffer; public secretKeyBytes!: Buffer; private defaultPayload: Payload | null = null; diff --git a/src/client/ClientOptions.ts b/src/client/ClientOptions.ts index fffd567..d3b5a4a 100644 --- a/src/client/ClientOptions.ts +++ b/src/client/ClientOptions.ts @@ -1,9 +1,10 @@ -const Versions = ["1.21.20", "1.21.30", "1.21.40"] as const; +const Versions = ["1.21.20", "1.21.30", "1.21.40", "1.21.50"] as const; enum ProtocolList { "1.21.20" = 712, "1.21.30" = 729, "1.21.40" = 748, + "1.21.50" = 766, } enum DeviceOS { @@ -36,21 +37,23 @@ type ClientOptions = { port: number; skinData: object | null; debug: boolean; + compressionThreshold: number; tokensFolder: string; viewDistance: number; deviceOS: DeviceOS; }; const defaultOptions: ClientOptions = { - version: "1.21.30", + version: "1.21.50", offline: false, username: "defaultUser", host: "127.0.0.1", port: 19132, skinData: null, debug: false, + compressionThreshold: 512, tokensFolder: `${process.cwd()}/tokens`, - viewDistance: 6, + viewDistance: 4, deviceOS: DeviceOS.Win10, }; diff --git a/src/tools/connect.ts b/src/tools/connect.ts index 359f9f8..9267752 100644 --- a/src/tools/connect.ts +++ b/src/tools/connect.ts @@ -1,4 +1,5 @@ -import type { TextPacket } from "@serenityjs/protocol"; +import "reflect-metadata"; +import { Vector3f, type TextPacket } from "@serenityjs/protocol"; import { Client } from "../Client"; // import fs from "fs"; // import path from "path"; @@ -102,19 +103,25 @@ const client = new Client({ host: "127.0.0.1", offline: true, username: "SanctumTerra", - version: "1.21.40", + version: "1.21.50", port: 19132, - viewDistance: 1, + viewDistance: 4, // debug: true }); console.time("Connection"); +console.time("RakConnect"); // writeToLog("Starting connection..."); client.connect().then(([ad, packet]) => { console.timeEnd("Connection"); + // console.log(ad); // writeToLog(`Connected successfully: ${JSON.stringify(ad)}`); + setTimeout(() => { + const vec = new Vector3f(260, 65, 236); + // client.breakBlock(vec); + }, 1000); }); client.on("DisconnectPacket", (packet) => { diff --git a/src/tools/connection.ts b/src/tools/connection.ts index e2f7ce0..d5d85b0 100644 --- a/src/tools/connection.ts +++ b/src/tools/connection.ts @@ -4,7 +4,7 @@ const connection = new Connection({ host: "127.0.0.1", port: 19132, username: "SanctumTerra", - version: "1.21.30", + version: "1.21.50", offline: true, }); diff --git a/src/vendor/PacketEncryptor.ts b/src/vendor/PacketEncryptor.ts index 3bb35b2..47967af 100644 --- a/src/vendor/PacketEncryptor.ts +++ b/src/vendor/PacketEncryptor.ts @@ -1,9 +1,9 @@ +import { GAME_BYTE } from "@serenityjs/network"; import { CompressionMethod } from "@serenityjs/protocol"; import { Frame, Reliability } from "@serenityjs/raknet"; import * as crypto from "node:crypto"; import * as Zlib from "node:zlib"; import type { Connection } from "src/Connection"; -import { Logger } from "./Logger"; class PacketEncryptor { public secretKeyBytes: Buffer; @@ -12,6 +12,7 @@ class PacketEncryptor { public receiveCounter: bigint; public cipher: crypto.Cipher | null; public decipher: crypto.Decipher | null; + public client: Connection; constructor(client: Connection, secretKey: Buffer, compressionThreshold = 1) { this.secretKeyBytes = Buffer.from(secretKey); @@ -20,6 +21,7 @@ class PacketEncryptor { this.receiveCounter = 0n; this.cipher = null; this.decipher = null; + this.client = client; this.initializeCipher(client.data.iv); this.initializeDecipher(client.data.iv); @@ -66,11 +68,11 @@ class PacketEncryptor { encryptPacket(framed: Buffer): Frame { let deflated: Buffer; - - if (framed.byteLength > this.compressionThreshold) { + // framed.byteLength > this.client.data.compressionThreshold + if (framed.byteLength > this.client.data.compressionThreshold) { deflated = Buffer.from([ CompressionMethod.Zlib, - ...Zlib.deflateRawSync(framed), + ...Zlib.deflateRawSync(framed, { level: 7 }), ]); } else { deflated = Buffer.from([CompressionMethod.None, ...framed]); @@ -101,23 +103,17 @@ class PacketEncryptor { if (!this.decipher) { throw new Error("Decipher not initialized"); } - const old = this.receiveCounter; - this.receiveCounter++; const decrypted = this.decipher.update(encryptedPayload); const packet = decrypted.slice(0, decrypted.length - 8); const receivedChecksum = decrypted.slice(decrypted.length - 8); - const computedChecksum = this.computeCheckSum(packet, old); + const computedChecksum = this.computeCheckSum(packet, this.receiveCounter); + this.receiveCounter++; if (!receivedChecksum.equals(computedChecksum)) { - throw new Error( - `Checksum mismatch ${receivedChecksum.toString("hex")} != ${computedChecksum.toString("hex")}`, - ); + throw new Error("Checksum mismatch"); } - // else { - // Logger.debug(`Checksum matched ${receivedChecksum.toString('hex')} == ${computedChecksum.toString('hex')}`); - // } return packet; } diff --git a/src/vendor/PacketSorter.ts b/src/vendor/PacketSorter.ts index 7fa40d2..c7d6f13 100644 --- a/src/vendor/PacketSorter.ts +++ b/src/vendor/PacketSorter.ts @@ -32,6 +32,7 @@ import { AddItemActorPacket } from "./packets/add-item-actor"; import { LegacyTelemetryEventPacket } from "./packets/LegacyTelemetryEventPacket"; import { UpdateSubChunkBlocksPacket } from "./packets/UpdateSubChunkBlocksPacket"; import { Frame } from "@sanctumterra/raknet"; +import * as crypto from "node:crypto"; export class PacketSorter { private lastPacket: Buffer = Buffer.alloc(0); @@ -44,14 +45,15 @@ export class PacketSorter { const serialized = packet.serialize(); const framed = Framer.frame(serialized); const payload = this.preparePayload(framed); - if ("frameAndSend" in this.connection.raknet) { - this.connection.raknet.frameAndSend(payload); - } else if ("sendFrame" in this.connection.raknet) { + + if ("sendFrame" in this.connection.raknet) { const frame = new Frame(); frame.orderChannel = 0; frame.payload = payload; // @ts-expect-error 'sendFrame only exists in older versions. this.connection.raknet.sendFrame(frame, Priority.Immediate); + } else if ("frameAndSend" in this.connection.raknet) { + this.connection.raknet.frameAndSend(payload); } } catch (error) { Logger.error( @@ -102,13 +104,18 @@ export class PacketSorter { return this.connection._encryptor.encryptPacket(framed).payload; } - if (!this.connection.data.sendDeflated) { - return Buffer.concat([Buffer.from([254]), framed]); - } - const deflated = - framed.byteLength > 256 - ? Buffer.from([CompressionMethod.Zlib, ...deflateRawSync(framed)]) - : Buffer.from([CompressionMethod.None, ...framed]); + const shouldCompress = + framed.byteLength > this.connection.data.compressionThreshold && + this.connection.compression; + const deflated = shouldCompress + ? Buffer.concat([ + Buffer.from([this.connection.data.compressionMethod]), + deflateRawSync(framed), + ]) + : this.connection.compression + ? Buffer.concat([Buffer.from([CompressionMethod.None]), framed]) + : framed; + return Buffer.concat([Buffer.from([254]), deflated]); } @@ -180,79 +187,8 @@ export class PacketSorter { for (const frame of frames) { const id = getPacketId(frame); if (id === SetScorePacket.id) continue; - // Logger.debug(`Processing packet ${id}`); - let PacketClass = Packets[id]; - if (id === 52) { - PacketClass = CraftingDataPacket; - } - - if ((id as number) === 124) { - PacketClass = LevelEventGenericPacket; - } - - if ((id as number) === 314) { - PacketClass = CurrectStructureFeaturePacket; - } - - if ((id as number) === 302) { - PacketClass = TrimDataPacket; - } - - if ((id as number) === 160) { - PacketClass = PlayerFogPacket; - } - - if ((id as number) === 72) { - PacketClass = GameRulesChangedPacket; - } - - if ((id as number) === 60) { - PacketClass = SetDifficultyPacket; - } - - if ((id as number) === 43) { - PacketClass = SetSpawnPositionPacket; - } - if ((id as number) === 42) { - PacketClass = SetHealthPacket; - } - - if ((id as number) === 199) { - PacketClass = UnlockedRecipesPacket; - } - - if ((id as number) === 165) { - PacketClass = SyncActorPropertyPacket; - } - - if ((id as number) === 111) { - PacketClass = MoveActorDeltaPacket; - } - - if ((id as number) === 162) { - PacketClass = ItemComponentPacket; - } - - if ((id as number) === 39) { - PacketClass = SetActorDataPacket; - } - - if ((id as number) === 13) { - PacketClass = AddEntityPacket; - } - - if ((id as number) === 15) { - PacketClass = AddItemActorPacket; - } - - if ((id as number) === 65) { - PacketClass = LegacyTelemetryEventPacket; - } - - if ((id as number) === 172) { - PacketClass = UpdateSubChunkBlocksPacket; - } + const PacketClass = Packets[id]; if (!PacketClass) { Logger.warn(`Packet with ID ${id} not found`); @@ -260,8 +196,10 @@ export class PacketSorter { } try { - const instance = new PacketClass(frame).deserialize(); - this.connection.emit(PacketClass.name, instance); + if (this.connection.listenerCount(PacketClass.name) > 0) { + const instance = new PacketClass(frame).deserialize(); + this.connection.emit(PacketClass.name, instance); + } } catch (error) { Logger.warn( `Error processing packet ${id}: ${error instanceof Error ? error.message : String(error)}\n`, diff --git a/src/vendor/packets/player-auth-input.ts b/src/vendor/packets/player-auth-input.ts new file mode 100644 index 0000000..89eb68e --- /dev/null +++ b/src/vendor/packets/player-auth-input.ts @@ -0,0 +1,111 @@ +import { Endianness } from "@serenityjs/binarystream"; +import { + ClientPredictedVehicle, + DataPacket, + InputData, + type InputMode, + InputTransaction, + type InteractionMode, + ItemStackRequest, + Packet, + PlayerAuthInputData, + PlayerAuthItemStackRequest, + PlayerBlockActions, + PlayerInputTick, + type PlayMode, + Vector2f, + Vector3f, +} from "@serenityjs/protocol"; +import { Proto } from "@serenityjs/raknet"; + +@Proto(Packet.PlayerAuthInput) +export class PlayerAuthInputPacket extends DataPacket { + public rotation!: Vector2f; + public position!: Vector3f; + public motion!: Vector2f; + public headYaw!: number; + public inputData!: PlayerAuthInputData; + public inputMode!: InputMode; + public playMode!: PlayMode; + public interactionMode!: InteractionMode; + public interactRotation!: Vector2f; + public tick!: bigint; + public positionDelta!: Vector3f; + public inputTransaction!: InputTransaction | null; + public itemStackRequest!: PlayerAuthItemStackRequest | null; + public blockActions!: PlayerBlockActions | null; + public predictedVehicle!: ClientPredictedVehicle | null; + public analogueMotion!: Vector2f; + public cameraOrientation!: Vector3f; + + public override serialize(): Buffer { + this.writeVarInt(Packet.PlayerAuthInput); + Vector2f.write(this, this.rotation); + Vector3f.write(this, this.position); + Vector2f.write(this, this.motion); + this.writeFloat32(this.headYaw, Endianness.Little); + PlayerAuthInputData.write(this, this.inputData); + this.writeVarInt(this.inputMode); + this.writeVarInt(this.playMode); + this.writeVarInt(this.interactionMode); + Vector2f.write(this, this.interactRotation); + PlayerInputTick.write(this, this.tick); + Vector3f.write(this, this.positionDelta); + if (this.inputTransaction) { + InputTransaction.write(this, this.inputTransaction); + } + if (this.itemStackRequest) { + PlayerAuthItemStackRequest.write( + this, + this.itemStackRequest, + 0, + this.inputData, + ); + } + if (this.blockActions) { + PlayerBlockActions.write(this, this.blockActions, 0, this.inputData); + } + if (this.predictedVehicle) { + ClientPredictedVehicle.write( + this, + this.predictedVehicle, + 0, + this.inputData, + ); + } + Vector2f.write(this, this.analogueMotion); + Vector3f.write(this, this.cameraOrientation); + return this.getBuffer(); + } + public override deserialize(): this { + this.readUint8(); + this.rotation = Vector2f.read(this); + this.position = Vector3f.read(this); + this.motion = Vector2f.read(this); + this.headYaw = this.readFloat32(Endianness.Little); + this.inputData = PlayerAuthInputData.read(this); + this.inputMode = this.readVarInt(); + this.playMode = this.readVarInt(); + this.interactionMode = this.readVarInt(); + this.interactRotation = Vector2f.read(this); + this.tick = PlayerInputTick.read(this); + this.positionDelta = Vector3f.read(this); + if (this.inputData.hasFlag(InputData.PerformItemInteraction)) { + this.inputTransaction = InputTransaction.read(this); + } + this.itemStackRequest = PlayerAuthItemStackRequest.read( + this, + 0, + this.inputData, + ); + this.blockActions = PlayerBlockActions.read(this, 0, this.inputData); + this.predictedVehicle = ClientPredictedVehicle.read( + this, + 0, + this.inputData, + ); + this.analogueMotion = Vector2f.read(this); + this.cameraOrientation = Vector3f.read(this); + return this; + } +} diff --git a/tsconfig.json b/tsconfig.json index a39c63d..91ddae1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,8 +8,8 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "emitDecoratorMetadata": true, "declaration": true, + "emitDecoratorMetadata": true, "experimentalDecorators": true, "paths": { "*": ["./*"]