From 1a24799dc7d4ad0c656ea04ceae6422a1e0c07a0 Mon Sep 17 00:00:00 2001 From: yourfriend Date: Sat, 4 Jun 2022 22:11:35 +0300 Subject: [PATCH 1/2] tt From 105234d766e2460fc8bb1b1424c379b4cf8360ec Mon Sep 17 00:00:00 2001 From: yourfriend Date: Sun, 10 Jul 2022 17:30:26 +0300 Subject: [PATCH 2/2] fix tcp packet splitting + maxUsers --- classes/Player.ts | 17 +++++---- classes/Server.ts | 93 ++++++++++++++++++++++++++++++++++------------- readme.md | 17 +++++---- 3 files changed, 86 insertions(+), 41 deletions(-) diff --git a/classes/Player.ts b/classes/Player.ts index 4d3aabd..75f9562 100644 --- a/classes/Player.ts +++ b/classes/Player.ts @@ -8,6 +8,7 @@ export class Player { private server: Server; username: string; + ip: string; id: number; world = "main"; position: Position; @@ -23,15 +24,15 @@ export class Player { this.username = username; this.position = position; this.server = server; - - let id = Math.floor(Math.random() * 255); + this.ip = (this.socket.remoteAddr as Deno.NetAddr).hostname; + + let id = Math.floor(Math.random() * server.maxUsers); // reassigns ID until finds available one - // if we reach 255 players this will loop forever - while (server.players.find((e) => e.id == id)) { - id = Math.floor(Math.random() * 255); - } + while (server.players.find((e) => e.id == id)) { + id = Math.floor(Math.random() * server.maxUsers); + } this.id = id; } @@ -54,7 +55,7 @@ export class Player { }); } - async toWorld(world: World) { + toWorld(world: World) { this.server.broadcastPacket( (e) => PacketDefinitions.despawn(this.id, e), this, @@ -75,6 +76,6 @@ export class Player { this.message("You have been moved."); - await world.save(); + //await world.save(); TODO: this causes way too many issues } } diff --git a/classes/Server.ts b/classes/Server.ts index 89efb6d..6994bfc 100644 --- a/classes/Server.ts +++ b/classes/Server.ts @@ -20,10 +20,16 @@ export class Server { players: Player[] = []; plugins: Map = new Map(); - lengthMap: Map = new Map([[0, 131], [5, 9], [8, 10], [ - 13, - 66, - ]]); + + lengthMap: Map = new Map([ + [0, 130], + [5, 8], + [8, 9], + [13, 65], + ]); + + maxUsers = 69 + worlds: World[] = [new World({ x: 64, y: 64, z: 64 }, "main")]; async start(port: number) { @@ -60,9 +66,10 @@ export class Server { await fetch( "https://www.classicube.net/heartbeat.jsp" + `?port=${config.port}` + - "&max=255" + + `&max=${this.maxUsers}` + "&name=Cla66ic" + "&public=True" + + "&software=Cla66ic" + `&version=7&salt=${config.hash}` + `&users=${this.players.length}`, ); @@ -136,8 +143,13 @@ export class Server { this.players = this.players.filter((e) => e != player); + try { + conn.close(); + } catch { + // whatever + } + this.broadcast(`${player.username} has &cleft`); - this.worlds.find((e) => e.name == player.world)!.save(); this.broadcastPacket( (e) => PacketDefinitions.despawn(player.id, e), @@ -145,14 +157,25 @@ export class Server { ); } - async handlePacket(packet: PacketReader, connection: Deno.Conn) { - const packetType = packet.readByte(); + async handlePacket( + buffer: Uint8Array, + packetType: number, + connection: Deno.Conn, + ) { + const packet = new PacketReader(buffer); if (packetType == 0x00) { if (this.players.find((e) => e.socket == connection)) return; packet.readByte(); const username = packet.readString(); const verification = packet.readString(); + + if(this.players.length >= this.maxUsers) { + + connection.close(); + + return; + } const player = new Player( connection, username, @@ -281,33 +304,51 @@ export class Server { player, ); } - - if (packet.buffer.length - 1 >= packet.pos) { // TODO: This logic is wrong! Sometimes, TCP packets are still dropped. Need to rewrite this properly. - this.handlePacket(packet, connection); - - packet.pos += packet.totalPacketSize; - packet.totalPacketSize = 0; - } } async startSocket(connection: Deno.Conn) { - const buffer = new Uint8Array(2048); - while (true) { - let count; + const packetID = new Uint8Array(1); + let packetIDReadAttempt; + try { - count = await connection.read(buffer); - } catch (e) { - count = 0; - log.critical(e); + packetIDReadAttempt = await connection.read(packetID); + } catch { + this.removeUser(connection); // TODO: add a reason to this + break; } - if (!count) { + if (packetIDReadAttempt) { + const packetLength = this.lengthMap.get(packetID[0]); + + if (!packetLength) { + log.critical("Unknown Packet: " + packetID[0]); + this.removeUser(connection); // TODO: add a reason to this + break; + } + + let rawPacket = new Uint8Array(packetLength); + let packetReadAttempt; + + try { + packetReadAttempt = await connection.read(rawPacket); + } catch { + this.removeUser(connection); // TODO: add a reason to this + break; + } + let fullRead = packetReadAttempt!; + + while (fullRead < packetLength) { + const halfPacket = new Uint8Array(packetLength - fullRead); + rawPacket = new Uint8Array([...rawPacket, ...halfPacket]); + + fullRead += (await connection.read(halfPacket))!; + } + + this.handlePacket(rawPacket, packetID[0], connection); + } else { this.removeUser(connection); break; - } else { - const packet = new PacketReader(buffer.subarray(0, count)); - this.handlePacket(packet, connection); } } } diff --git a/readme.md b/readme.md index 2aaf067..171dfaf 100644 --- a/readme.md +++ b/readme.md @@ -33,15 +33,18 @@ environment variables ### insipration taken from: -1. mcgalaxy (obviuouuusly!!) -2. https://github.com/Patbox/Cobblestone-Classic (some protocol information and +1. https://github.com/Patbox/Cobblestone-Classic (some protocol information and worldhandling) -3. cla55ic (world data too) +2. cla55ic (world data too) ### issues: -1. tcp packet splitting fails sometimes -2. no cpe support! i want to get all of the above issues fixed before + +1. Properly queue up map saves instead of just blantantly saving whenever possible +2. massive performance issues, running more than 100 something accounts makes the server instead insane amounts of cpu (most likely multithreading needed) +3. no cpe support! i want to get all of the above issues fixed before implementing CPE support -3. proper rank support (implemented as plugin) -4. no discord bridge (implemented as plugin) +4. no IP cooldown connections (no block cooldown either), no anticheat, no unique IP heartbeats +5. proper rank support (implemented as plugin) +6. no discord bridge (implemented as plugin) +7. no cla66ic/plugins repository