public static async Task HandlePlayPackets(Packet packet, Client client) { Server server = client.Server; Player player = client.Player; switch (packet.id) { case 0x00: // Teleport Confirm var confirm = PacketSerializer.FastDeserialize <TeleportConfirm>(packet.data); if (confirm.TeleportId == player.TeleportId) { break; } await player.KickAsync("Invalid teleport... cheater?"); //await player.TeleportAsync(player.LastLocation);//Teleport them back we didn't send this packet break; case 0x01: // Query Block NBT Logger.LogDebug("Received query block nbt"); break; case 0x02: //Set difficulty break; case 0x03: // Incoming chat message var message = await PacketSerializer.FastDeserializeAsync <IncomingChatMessage>(packet.data); await server.ParseMessageAsync(message.Message, client); break; case 0x04: // Client status break; case 0x05: // Client Settings client.ClientSettings = PacketSerializer.FastDeserialize <ClientSettings>(packet.data); Logger.LogDebug("Received client settings"); break; case 0x06: // Tab-Complete Logger.LogDebug("Received tab-complete"); break; case 0x07: //TODO look more into this // Window Confirmation (serverbound) var conf = PacketSerializer.FastDeserialize <WindowConfirmation>(packet.data); Logger.LogDebug("Window Confirmation (serverbound)"); break; case 0x08: // Click Window Button var clicked = PacketSerializer.FastDeserialize <ClickWindowButton>(packet.data); break; case 0x09: // Click Window { var window = PacketSerializer.FastDeserialize <ClickWindow>(packet.data); Logger.LogDebug("Click window"); if (window.WindowId == 0) { //This is the player inventory switch (window.Mode) { case InventoryOperationMode.MouseClick: //TODO InventoryClickEvent { if (window.Button == 0) { player.Inventory.RemoveItem(window.ClickedSlot, 64); } else { player.Inventory.RemoveItem(window.ClickedSlot, window.Item.Count / 2); } break; } case InventoryOperationMode.ShiftMouseClick: break; case InventoryOperationMode.NumberKeys: break; case InventoryOperationMode.MiddleMouseClick: break; case InventoryOperationMode.Drop: { //If clicked slot is -999 that means they clicked outside the inventory if (window.ClickedSlot != -999) { Logger.LogDebug("Dropped Item"); if (window.Button == 0) { player.Inventory.RemoveItem(window.ClickedSlot); } else { player.Inventory.RemoveItem(window.ClickedSlot, 64); } } break; } case InventoryOperationMode.MouseDrag: { if (window.ClickedSlot == -999) { if (window.Button == 0 || window.Button == 4 || window.Button == 8) { client.isDragging = true; } else if (window.Button == 2 || window.Button == 6 || window.Button == 10) { client.isDragging = false; } } else if (client.isDragging) { if (player.Gamemode == Gamemode.Creative) { if (window.Button != 9) { break; } //creative copy player.Inventory.SetItem(window.ClickedSlot, new ItemStack(window.Item.Id, window.Item.Count) { Nbt = window.Item.Nbt }); } else { if (window.Button != 1 || window.Button != 5) { break; } //survival painting player.Inventory.SetItem(window.ClickedSlot, new ItemStack(window.Item.Id, window.Item.Count) { Nbt = window.Item.Nbt }); } } else { //It shouldn't get here } break; } case InventoryOperationMode.DoubleClick: break; default: break; } } else { } break; } case 0x0A: // Close Window (serverbound) var closedWindow = PacketSerializer.FastDeserialize <CloseWindow>(packet.data); Logger.LogDebug("Received close window"); break; case 0x0B: // Plugin Message (serverbound) { var msg = await PacketSerializer.DeserializeAsync <PluginMessage>(packet.data); var result = await msg.HandleAsync(); switch (result.Type) { case PluginMessageType.Brand: client.Brand = result.Value.ToString(); break; case PluginMessageType.Register: { using var stream = new MinecraftStream(msg.PluginData); var len = await stream.ReadVarIntAsync(); //Idk how this would work so I'm assuming a length will be sent first for (int i = 0; i < len; i++) { server.RegisteredChannels.Add(await stream.ReadStringAsync()); } break; } case PluginMessageType.UnRegister: server.RegisteredChannels.RemoveWhere(x => x == msg.Channel.ToLower()); break; case PluginMessageType.Custom: //This can be ignored for now default: break; } Logger.LogDebug($"Received plugin message: {msg.Channel}"); } break; case 0x0C: // Edit Book Logger.LogDebug("Received edit book"); break; case 0x0E: //Interact Entity Logger.LogDebug("Interact entity"); break; case 0x0F: //Generate structure break; case 0x10: // Keep Alive (serverbound) var keepalive = PacketSerializer.FastDeserialize <KeepAlive>(packet.data); Logger.LogDebug($"Successfully kept alive player {player.Username} with ka id " + $"{keepalive.KeepAliveId} previously missed {client.missedKeepalives - 1} ka's"); // missed is 1 more bc we just handled one // Server is alive, reset missed keepalives. client.missedKeepalives = 0; break; case 0x11: //Lock Difficulty break; case 0x12: // Player Position var pos = PacketSerializer.FastDeserialize <PlayerPosition>(packet.data); await player.UpdateAsync(server, pos.Position, pos.OnGround); break; case 0x13: //Player Position And rotation (serverbound) var ppos = PacketSerializer.FastDeserialize <ServerPlayerPositionLook>(packet.data); await player.UpdateAsync(server, ppos.Position, ppos.Yaw, ppos.Pitch, ppos.OnGround); break; case 0x14: // Player rotation var look = PacketSerializer.FastDeserialize <PlayerRotation>(packet.data); await player.UpdateAsync(server, look.Yaw, look.Pitch, look.OnGround); break; case 0x15: //Player movement break; case 0x16: // Vehicle Move (serverbound) break; case 0x17: //Steer boat break; case 0x18: // Pick Item var item = PacketSerializer.FastDeserialize <PickItem>(packet.data); Logger.LogDebug("Received pick item"); break; case 0x19: // Craft Recipe Request Logger.LogDebug("Received craft recipe request"); break; case 0x1A: // Player Abilities (serverbound) Logger.LogDebug("Received player abilities"); break; case 0x1B: // Player Digging var digging = PacketSerializer.FastDeserialize <PlayerDigging>(packet.data); await server.BroadcastPlayerDigAsync(new PlayerDiggingStore { Player = player.Uuid, Packet = digging }); break; case 0x1C: //TODO Entity Action var action = PacketSerializer.FastDeserialize <EntityAction>(packet.data); switch (action.Action) { case EAction.StartSneaking: player.Sneaking = true; break; case EAction.StopSneaking: player.Sneaking = false; break; case EAction.LeaveBed: player.Sleeping = false; break; case EAction.StartSprinting: player.Sprinting = true; break; case EAction.StopSprinting: player.Sprinting = false; break; case EAction.StartJumpWithHorse: break; case EAction.StopJumpWithHorse: break; case EAction.OpenHorseInventory: player.InHorseInventory = true; break; case EAction.StartFlyingWithElytra: player.FlyingWithElytra = true; break; default: break; } Logger.LogDebug("Received entity action"); break; case 0x1D: // Steer Vehicle break; case 0x1E: // Set Displayed Recipe break; case 0x1F: //Set recipe book state break; case 0x20: // Name Item var nameItem = PacketSerializer.FastDeserialize <NameItem>(packet.data); break; case 0x21: //Resource pack status break; case 0x22: //Advancement tab break; case 0x23: //Select trade break; case 0x24: //Set beacon effect break; case 0x25: // Held Item Change (serverbound) var heldItemChange = PacketSerializer.FastDeserialize <ServerHeldItemChange>(packet.data); player.CurrentSlot = (short)(heldItemChange.Slot + 36); var heldItem = player.GetHeldItem(); await server.BroadcastPacketAsync(new EntityEquipment { EntityId = client.id, Slot = ESlot.MainHand, Item = new ItemStack { Present = heldItem.Present, Count = (sbyte)heldItem.Count, Id = heldItem.Id, Nbt = heldItem.Nbt } }, player); Logger.LogDebug("Held item change"); break; case 0x26: //Update command block break; case 0x27: //Update command block minecart break; case 0x28: // Creative Inventory Action in creative they send this to replace whatever item existed in the slot { var ca = PacketSerializer.FastDeserialize <CreativeInventoryAction>(packet.data); player.Inventory.SetItem(ca.ClickedSlot, new ItemStack(ca.ClickedItem.Id, ca.ClickedItem.Count) { Nbt = ca.ClickedItem.Nbt, Present = ca.ClickedItem.Present }); if (ca.ClickedSlot >= 36 && ca.ClickedSlot <= 44) { heldItem = player.GetHeldItem(); await server.BroadcastPacketAsync(new EntityEquipment { EntityId = client.id, Slot = ESlot.MainHand, Item = new ItemStack { Present = heldItem.Present, Count = (sbyte)heldItem.Count, Id = heldItem.Id, Nbt = heldItem.Nbt } }, player); } } break; case 0x29: //Update jigsaw block break; case 0x2A: //Update structure block break; case 0x2B: //Update sign break; case 0x2C: // Animation (serverbound) var serverAnim = PacketSerializer.FastDeserialize <Animation>(packet.data); //TODO broadcast entity animation to nearby players switch (serverAnim.Hand) { case Hand.MainHand: await server.BroadcastPacketAsync(new EntityAnimation { EntityId = client.id, Animation = EAnimation.SwingMainArm }, player); break; case Hand.OffHand: await server.BroadcastPacketAsync(new EntityAnimation { EntityId = client.id, Animation = EAnimation.SwingOffhand }, player); break; default: break; } break; case 0x2D: //Spectate break; case 0x2E: // Player Block Placement { var pbp = PacketSerializer.FastDeserialize <PlayerBlockPlacement>(packet.data); var currentItem = player.GetHeldItem(); var block = Registry.GetBlock(currentItem.Type); var location = pbp.Location; var interactedBlock = server.World.GetBlock(location); if (interactedBlock.CanInteract() && !player.Sneaking) { var arg = await server.Events.InvokeBlockInteractAsync(new BlockInteractEventArgs(player, block, pbp.Location)); if (arg.Cancel) { return; } //TODO open chests/Crafting inventory ^ ^ Logger.LogDebug($"Block Interact: {interactedBlock} - {location}"); return; } if (player.Gamemode != Gamemode.Creative) { player.Inventory.RemoveItem(player.CurrentSlot); } switch (pbp.Face) // TODO fix this for logs { case BlockFace.Bottom: location.Y -= 1; break; case BlockFace.Top: location.Y += 1; break; case BlockFace.North: location.Z -= 1; break; case BlockFace.South: location.Z += 1; break; case BlockFace.West: location.X -= 1; break; case BlockFace.East: location.X += 1; break; default: break; } block.Location = location; server.World.SetBlock(location, block); await server.BroadcastBlockPlacementAsync(player, block, location); break; } case 0x2F: //Use item Logger.LogDebug("Use item"); break; } }
public async Task StartConnectionAsync() { while (!Cancellation.IsCancellationRequested && this.tcp.Connected) { Packet packet = await this.GetNextPacketAsync(); if (this.State == ClientState.Play && packet.data.Length < 1) { this.Disconnect(); } switch (this.State) { case ClientState.Status: // Server ping/list switch (packet.id) { case 0x00: var status = new ServerStatus(Server); await this.SendPacketAsync(new RequestResponse(status)); break; case 0x01: await this.SendPacketAsync(new PingPong(packet.data)); this.Disconnect(); break; } break; case ClientState.Handshaking: if (packet.id == 0x00) { if (packet == null) { throw new InvalidOperationException(); } var handshake = await PacketSerializer.FastDeserializeAsync <Handshake>(packet.data); var nextState = handshake.NextState; if (nextState != ClientState.Status && nextState != ClientState.Login) { this.Logger.LogDebug($"Client sent unexpected state ({(int)nextState}), forcing it to disconnect"); await this.DisconnectAsync(ChatMessage.Simple("you seem suspicious")); } this.State = nextState; this.Logger.LogInformation($"Handshaking with client (protocol: {handshake.Version}, server: {handshake.ServerAddress}:{handshake.ServerPort})"); } else { // Handle legacy ping } break; case ClientState.Login: switch (packet.id) { default: this.Logger.LogError("Client in state Login tried to send an unimplemented packet. Forcing it to disconnect."); await this.DisconnectAsync("Unknown Packet Id."); break; case 0x00: var loginStart = await PacketSerializer.FastDeserializeAsync <LoginStart>(packet.data); string username = config.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; this.Logger.LogDebug($"Received login request from user {loginStart.Username}"); await this.Server.DisconnectIfConnectedAsync(username); if (this.config.OnlineMode) { var user = await MinecraftAPI.GetUserAsync(loginStart.Username); this.Player = new Player(Guid.Parse(user.Id), loginStart.Username, this) { World = this.Server.World }; this.packetCryptography.GenerateKeyPair(); var values = this.packetCryptography.GeneratePublicKeyAndToken(); this.randomToken = values.randomToken; await this.SendPacketAsync(new EncryptionRequest(values.publicKey, this.randomToken)); break; } this.Player = new Player(UUIDFactory.CreateUUID(3, 1, $"OfflinePlayer:{username}"), username, this) { World = this.Server.World }; //await this.SetCompression(); await this.ConnectAsync(); break; case 0x01: var encryptionResponse = PacketSerializer.FastDeserialize <EncryptionResponse>(packet.data); this.sharedKey = this.packetCryptography.Decrypt(encryptionResponse.SharedSecret); var decryptedToken = this.packetCryptography.Decrypt(encryptionResponse.VerifyToken); if (!decryptedToken.SequenceEqual(this.randomToken)) { await this.DisconnectAsync("Invalid token.."); break; } var serverId = sharedKey.Concat(this.packetCryptography.PublicKey).ToArray().MinecraftShaDigest(); JoinedResponse response = await MinecraftAPI.HasJoined(this.Player.Username, serverId); if (response is null) { this.Logger.LogWarning($"Failed to auth {this.Player.Username}"); await this.DisconnectAsync("Unable to authenticate.."); break; } this.encryptionEnabled = true; this.minecraftStream = new AesStream(this.debugStream ?? (Stream)this.tcp.GetStream(), this.sharedKey); //await this.SetCompression(); await ConnectAsync(); break; case 0x02: // Login Plugin Response break; } break; case ClientState.Play: //await this.Logger.LogDebugAsync($"Received Play packet with Packet ID 0x{packet.id.ToString("X")}"); await PacketHandler.HandlePlayPackets(packet, this); break; } //await Task.Delay(50); } Logger.LogInformation($"Disconnected client"); if (this.State == ClientState.Play) { await this.Server.Events.InvokePlayerLeaveAsync(new PlayerLeaveEventArgs(this.Player)); } if (tcp.Connected) { this.tcp.Close(); if (this.Player != null) { this.Server.OnlinePlayers.TryRemove(this.Player.Uuid, out var _); } } }