├── kittymc_lib ├── tests │ ├── packet_tests │ │ ├── client │ │ │ ├── mod.rs │ │ │ └── play.rs │ │ ├── mod.rs │ │ ├── server │ │ │ ├── mod.rs │ │ │ ├── login.rs │ │ │ └── handshake.rs │ │ └── utils.rs │ ├── mod.rs │ └── type_tests │ │ └── mod.rs ├── src │ ├── packets │ │ ├── client │ │ │ ├── mod.rs │ │ │ ├── status │ │ │ │ └── mod.rs │ │ │ ├── login │ │ │ │ ├── mod.rs │ │ │ │ ├── set_compression_03.rs │ │ │ │ ├── disconnect_login_00.rs │ │ │ │ └── success_02.rs │ │ │ └── play │ │ │ │ ├── server_held_item_change_3a.rs │ │ │ │ ├── time_update_47.rs │ │ │ │ ├── entity_status_1b.rs │ │ │ │ ├── keep_alive_1f.rs │ │ │ │ ├── map_chunk_bulk_26.rs │ │ │ │ ├── spawn_position_46.rs │ │ │ │ ├── server_difficulty_0d.rs │ │ │ │ ├── entity_head_look_36.rs │ │ │ │ ├── unload_chunk_1d.rs │ │ │ │ ├── entity_metadata_3c.rs │ │ │ │ ├── destroy_entities_32.rs │ │ │ │ ├── animation_06.rs │ │ │ │ ├── entity_relative_move_26.rs │ │ │ │ ├── server_plugin_message_18.rs │ │ │ │ ├── disconnect_1a.rs │ │ │ │ ├── spawn_player_05.rs │ │ │ │ ├── block_break_animation_08.rs │ │ │ │ ├── entity_look_28.rs │ │ │ │ ├── block_change_0b.rs │ │ │ │ ├── player_position_and_look_2f.rs │ │ │ │ ├── player_abilities_2c.rs │ │ │ │ ├── join_game_23.rs │ │ │ │ ├── unlock_recipes_31.rs │ │ │ │ ├── chat_message_0f.rs │ │ │ │ ├── window_items_14.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── player_list_item_2e.rs │ │ │ │ └── chunk_data_20.rs │ │ ├── server │ │ │ ├── mod.rs │ │ │ ├── login │ │ │ │ ├── mod.rs │ │ │ │ └── login_start_00.rs │ │ │ ├── status │ │ │ │ ├── mod.rs │ │ │ │ ├── request_00.rs │ │ │ │ └── ping_01.rs │ │ │ ├── play │ │ │ │ ├── client_held_item_change_a1.rs │ │ │ │ ├── teleport_confirm_00.rs │ │ │ │ ├── chat_message_02.rs │ │ │ │ ├── animation_1d.rs │ │ │ │ ├── client_keep_alive_0b.rs │ │ │ │ ├── player_look_0f.rs │ │ │ │ ├── creative_inventory_action_1b.rs │ │ │ │ ├── player_position_0d.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── client_player_position_and_look_0e.rs │ │ │ │ ├── client_plugin_message_09.rs │ │ │ │ ├── player_block_placement_1f.rs │ │ │ │ ├── entity_action_15.rs │ │ │ │ ├── client_settings_04.rs │ │ │ │ └── player_digging_14.rs │ │ │ └── handshake.rs │ │ ├── mod.rs │ │ └── packet_serialization.rs │ ├── lib.rs │ ├── subtypes │ │ ├── state.rs │ │ ├── components.rs │ │ ├── metadata.rs │ │ └── mod.rs │ ├── utils.rs │ └── error.rs ├── Cargo.lock └── Cargo.toml ├── .gitignore ├── kittymc_server ├── src │ ├── chunking │ │ ├── mod.rs │ │ ├── increasing_ticker.rs │ │ ├── chunk_unloader.rs │ │ ├── chunk_generator.rs │ │ └── chunk_manager.rs │ ├── main.rs │ ├── player.rs │ ├── inventory.rs │ └── client.rs └── Cargo.toml ├── Cargo.toml ├── kittymc_proxy ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── README.md ├── kittymc_macros ├── Cargo.toml └── src │ └── lib.rs └── .github └── workflows └── rust.yml /kittymc_lib/tests/packet_tests/client/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod play; 2 | -------------------------------------------------------------------------------- /kittymc_lib/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod packet_tests; 2 | mod type_tests; 3 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod server; 3 | mod utils; 4 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handshake; 2 | pub mod login; 3 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod login; 2 | pub mod play; 3 | pub mod status; 4 | -------------------------------------------------------------------------------- /kittymc_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod packets; 3 | pub mod subtypes; 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handshake; 2 | pub mod login; 3 | pub mod play; 4 | pub mod status; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /kittymc_lib/target 2 | /kittymc_proxy/target 3 | /kittymc_server/target 4 | /target 5 | /.idea 6 | /world/ 7 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/status/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod response_00; 2 | 3 | pub use response_00::StatusResponsePacket; 4 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/login/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod login_start_00; 2 | 3 | pub use login_start_00::LoginStartPacket; 4 | -------------------------------------------------------------------------------- /kittymc_server/src/chunking/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chunk_manager; 2 | pub mod chunk_generator; 3 | pub mod chunk_unloader; 4 | mod increasing_ticker; -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "kittymc_lib", "kittymc_macros", 5 | "kittymc_proxy", 6 | "kittymc_server" 7 | ] 8 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/status/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ping_01; 2 | pub mod request_00; 3 | 4 | pub use ping_01::StatusPingPongPacket; 5 | pub use request_00::StatusRequestPacket; 6 | -------------------------------------------------------------------------------- /kittymc_lib/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "kittymc-lib" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /kittymc_proxy/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "kittymc-proxy" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KittyMC 2 | 3 | This is a minecraft protocol implementation in Rust. 4 | 5 | *Currently implementing: MC 1.12.2 (Protocol Version 340)* 6 | 7 | ## Based on 8 | https://minecraft.wiki/w/Protocol?oldid=2772350 -------------------------------------------------------------------------------- /kittymc_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kittymc-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | syn = "2.0.90" 8 | quote = "1.0.37" 9 | proc-macro2 = "1.0.92" 10 | log = "0.4.22" 11 | 12 | [lib] 13 | proc-macro = true 14 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/login/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod disconnect_login_00; 2 | pub mod set_compression_03; 3 | pub mod success_02; 4 | 5 | pub use disconnect_login_00::DisconnectLoginPacket; 6 | pub use set_compression_03::SetCompressionPacket; 7 | pub use success_02::LoginSuccessPacket; 8 | -------------------------------------------------------------------------------- /kittymc_proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kittymc-proxy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | kittymc-lib = { path = "../kittymc_lib" } 8 | tokio = { version = "1.42.0", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"] } 9 | anyhow = "1.0.94" 10 | -------------------------------------------------------------------------------- /kittymc_server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kittymc-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | kittymc-lib = { path = "../kittymc_lib" } 8 | uuid = { version = "1.11.0", features = ["v3"] } 9 | tracing = "0.1.41" 10 | tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } 11 | log = "0.4.22" 12 | rand = "0.9.0-beta.1" 13 | ctrlc = "3.4.5" -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /kittymc_lib/src/subtypes/state.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq)] 2 | pub enum State { 3 | Handshake = 0, 4 | Status = 1, 5 | Login = 2, 6 | Transfer = 3, 7 | Play = 4, 8 | Undefined = 255, 9 | } 10 | 11 | impl From for State { 12 | fn from(value: u32) -> Self { 13 | match value { 14 | 1 => State::Status, 15 | 2 => State::Login, 16 | 3 => State::Transfer, 17 | 4 => State::Play, 18 | _ => State::Undefined, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/status/request_00.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::SerializablePacket; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Clone, Debug, Packet)] 7 | pub struct StatusRequestPacket; 8 | 9 | impl SerializablePacket for StatusRequestPacket { 10 | fn serialize(&self) -> Vec { 11 | vec![1, 0] 12 | } 13 | 14 | fn deserialize(_data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 15 | Ok((0, Packet::StatusRequest(StatusRequestPacket))) 16 | } 17 | 18 | fn id() -> u32 { 19 | 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/server_held_item_change_3a.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_u8, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Default, Packet)] 6 | pub struct ServerHeldItemChangePacket { 7 | slot: u8, 8 | } 9 | 10 | impl SerializablePacket for ServerHeldItemChangePacket { 11 | fn serialize(&self) -> Vec { 12 | let mut packet = vec![]; 13 | 14 | write_u8(&mut packet, self.slot); 15 | 16 | wrap_packet(&mut packet, Self::id()); 17 | 18 | packet 19 | } 20 | 21 | fn id() -> u32 { 22 | 0x3A 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/time_update_47.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_u64, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Default, Packet)] 6 | pub struct TimeUpdatePacket { 7 | world_age: u64, 8 | time_of_day: u64, 9 | } 10 | 11 | impl SerializablePacket for TimeUpdatePacket { 12 | fn serialize(&self) -> Vec { 13 | let mut packet = vec![]; 14 | 15 | write_u64(&mut packet, self.world_age); 16 | write_u64(&mut packet, self.time_of_day); 17 | 18 | wrap_packet(&mut packet, Self::id()); 19 | 20 | packet 21 | } 22 | 23 | fn id() -> u32 { 24 | 0x47 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/entity_status_1b.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_i32, write_u8, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Default, Packet)] 6 | pub struct EntityStatusPacket { 7 | pub entity_id: i32, 8 | pub entity_status: u8, 9 | } 10 | 11 | impl SerializablePacket for EntityStatusPacket { 12 | fn serialize(&self) -> Vec { 13 | let mut packet = vec![]; 14 | 15 | write_i32(&mut packet, self.entity_id); 16 | write_u8(&mut packet, self.entity_status); 17 | 18 | wrap_packet(&mut packet, Self::id()); 19 | 20 | packet 21 | } 22 | 23 | fn id() -> u32 { 24 | 0x1B 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/keep_alive_1f.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_u64, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Packet)] 6 | pub struct ServerKeepAlivePacket { 7 | pub id: u64, 8 | } 9 | 10 | impl ServerKeepAlivePacket { 11 | pub fn new(id: u64) -> Self { 12 | ServerKeepAlivePacket { id } 13 | } 14 | } 15 | 16 | impl SerializablePacket for ServerKeepAlivePacket { 17 | fn serialize(&self) -> Vec { 18 | let mut packet = vec![]; 19 | 20 | write_u64(&mut packet, self.id); 21 | 22 | wrap_packet(&mut packet, Self::id()); 23 | 24 | packet 25 | } 26 | 27 | fn id() -> u32 { 28 | 0x1F 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kittymc_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kittymc-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | thiserror = "2.0.6" 8 | proc-macro2 = "1.0.92" 9 | kittymc-macros = { path = "../kittymc_macros" } 10 | integer-encoding = "4.0.2" 11 | serde = { version = "1.0.216", features = ["derive"] } 12 | serde_json = "1.0.133" 13 | uuid = { version = "1.11.0", features = ["serde", "v4"] } 14 | md5 = "0.7.0" 15 | miniz_oxide = "0.8.0" 16 | num-traits = "0.2.19" 17 | nalgebra = "0.33.2" 18 | bitflags = "2.6.0" 19 | typed-builder = "0.20.0" 20 | rand = "0.9.0-beta.1" 21 | log = "0.4.22" 22 | lazy_static = "1.5.0" 23 | fastnbt = { version = "2.5.0" } 24 | paste = "1.0.15" 25 | savefile = { version = "0.18.5", features = ["bzip2"] } 26 | savefile-derive = "0.18.5" 27 | 28 | [dev-dependencies] 29 | anyhow = "1.0.94" -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/client_held_item_change_a1.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_i16, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct ClientHeldItemChangePacket { 8 | pub slot: i16, 9 | } 10 | 11 | impl SerializablePacket for ClientHeldItemChangePacket { 12 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 13 | let mut size = 0; 14 | 15 | let slot = read_i16(&mut data, &mut size)?; 16 | 17 | Ok(( 18 | size, 19 | Packet::ClientHeldItemChange(ClientHeldItemChangePacket { slot }), 20 | )) 21 | } 22 | 23 | fn id() -> u32 { 24 | 0xA1 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/teleport_confirm_00.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_varint_u32, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct TeleportConfirmPacket { 8 | teleport_id: u32, 9 | } 10 | 11 | impl SerializablePacket for TeleportConfirmPacket { 12 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 13 | let mut size = 0; 14 | 15 | let teleport_id = read_varint_u32(&mut data, &mut size)?; 16 | 17 | Ok(( 18 | size, 19 | Packet::TeleportConfirm(TeleportConfirmPacket { teleport_id }), 20 | )) 21 | } 22 | 23 | fn id() -> u32 { 24 | 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/chat_message_02.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_length_prefixed_string, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct ServerChatMessagePacket { 8 | pub message: String, 9 | } 10 | 11 | impl SerializablePacket for ServerChatMessagePacket { 12 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 13 | let mut size = 0; 14 | 15 | let message = read_length_prefixed_string(&mut data, &mut size)?; 16 | 17 | Ok(( 18 | size, 19 | Packet::ChatMessage(ServerChatMessagePacket { message }), 20 | )) 21 | } 22 | 23 | fn id() -> u32 { 24 | 2 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/animation_1d.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_varint_u32, SerializablePacket}; 3 | use crate::packets::server::play::client_settings_04::Hand; 4 | use crate::packets::Packet; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct ClientAnimationPacket { 9 | pub hand: Hand, 10 | } 11 | 12 | impl SerializablePacket for ClientAnimationPacket { 13 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 14 | let mut size = 0; 15 | 16 | let hand = read_varint_u32(&mut data, &mut size)?.into(); 17 | 18 | Ok(( 19 | size, 20 | Packet::ClientAnimation(ClientAnimationPacket { hand }), 21 | )) 22 | } 23 | 24 | fn id() -> u32 { 25 | 0x1D 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/client_keep_alive_0b.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_u64, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct ClientKeepAlivePacket { 8 | pub id: u64, 9 | } 10 | 11 | impl ClientKeepAlivePacket { 12 | pub fn new(id: u64) -> Self { 13 | ClientKeepAlivePacket { id } 14 | } 15 | } 16 | 17 | impl SerializablePacket for ClientKeepAlivePacket { 18 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 19 | let mut size = 0; 20 | 21 | let id = read_u64(&mut data, &mut size)?; 22 | 23 | Ok((size, Packet::KeepAlive(ClientKeepAlivePacket { id }))) 24 | } 25 | 26 | fn id() -> u32 { 27 | 0x0B 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/map_chunk_bulk_26.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::SerializablePacket; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[allow(dead_code)] 6 | pub struct ChunkMeta { 7 | pos_x: i32, 8 | pos_y: i32, 9 | primary_bit_mask: u16, 10 | } 11 | 12 | #[derive(PartialEq, Debug, Clone, Packet)] 13 | pub struct MapChunkBulkPacket { 14 | sky_light_sent: bool, 15 | } 16 | 17 | impl Default for MapChunkBulkPacket { 18 | fn default() -> Self { 19 | MapChunkBulkPacket { 20 | sky_light_sent: false, 21 | } 22 | } 23 | } 24 | 25 | impl SerializablePacket for MapChunkBulkPacket { 26 | fn serialize(&self) -> Vec { 27 | let mut packet = vec![]; 28 | 29 | wrap_packet(&mut packet, Self::id()); 30 | 31 | packet 32 | } 33 | 34 | fn id() -> u32 { 35 | 0x26 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/spawn_position_46.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_block_location, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::Location; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct SpawnPositionPacket { 8 | position: Location, 9 | } 10 | 11 | impl Default for SpawnPositionPacket { 12 | fn default() -> Self { 13 | SpawnPositionPacket { 14 | position: Location::new(0., 4., 0.), 15 | } 16 | } 17 | } 18 | 19 | impl SerializablePacket for SpawnPositionPacket { 20 | fn serialize(&self) -> Vec { 21 | let mut packet = vec![]; 22 | 23 | write_block_location(&mut packet, &self.position); 24 | 25 | wrap_packet(&mut packet, Self::id()); 26 | 27 | packet 28 | } 29 | 30 | fn id() -> u32 { 31 | 0x46 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/server_difficulty_0d.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::client::play::Difficulty; 2 | use crate::packets::packet_serialization::{write_u8, SerializablePacket}; 3 | use crate::packets::wrap_packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct ServerDifficultyPacket { 8 | difficulty: Difficulty, 9 | } 10 | 11 | impl Default for ServerDifficultyPacket { 12 | fn default() -> Self { 13 | ServerDifficultyPacket { 14 | difficulty: Difficulty::Normal, 15 | } 16 | } 17 | } 18 | 19 | impl SerializablePacket for ServerDifficultyPacket { 20 | fn serialize(&self) -> Vec { 21 | let mut packet = vec![]; 22 | 23 | write_u8(&mut packet, self.difficulty as u8); 24 | 25 | wrap_packet(&mut packet, Self::id()); 26 | 27 | packet 28 | } 29 | 30 | fn id() -> u32 { 31 | 0x0D 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/entity_head_look_36.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_angle, write_varint_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Packet)] 6 | pub struct EntityHeadLookPacket { 7 | pub entity_id: i32, 8 | pub yaw: f32, 9 | } 10 | 11 | impl SerializablePacket for EntityHeadLookPacket { 12 | fn serialize(&self) -> Vec { 13 | let mut packet = vec![]; 14 | 15 | let mut yaw = self.yaw; 16 | while yaw > 180. { 17 | yaw -= 360.; 18 | } 19 | while yaw < -180. { 20 | yaw += 360.; 21 | } 22 | 23 | write_varint_i32(&mut packet, self.entity_id); 24 | write_angle(&mut packet, yaw); 25 | 26 | wrap_packet(&mut packet, Self::id()); 27 | 28 | packet 29 | } 30 | 31 | fn id() -> u32 { 32 | 0x36 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/unload_chunk_1d.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::ChunkPosition; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct UnloadChunkPacket { 8 | x: i32, 9 | z: i32, 10 | } 11 | 12 | impl UnloadChunkPacket { 13 | pub fn new(pos: &ChunkPosition) -> UnloadChunkPacket { 14 | UnloadChunkPacket { 15 | x: pos.chunk_x() as i32, 16 | z: pos.chunk_z() as i32, 17 | } 18 | } 19 | } 20 | 21 | impl SerializablePacket for UnloadChunkPacket { 22 | fn serialize(&self) -> Vec { 23 | let mut packet = vec![]; 24 | 25 | write_i32(&mut packet, self.x); 26 | write_i32(&mut packet, self.z); 27 | 28 | wrap_packet(&mut packet, Self::id()); 29 | 30 | packet 31 | } 32 | 33 | fn id() -> u32 { 34 | 0x1D 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/entity_metadata_3c.rs: -------------------------------------------------------------------------------- 1 | use kittymc_macros::Packet; 2 | use crate::packets::packet_serialization::{write_varint_i32, SerializablePacket}; 3 | use crate::packets::wrap_packet; 4 | use crate::subtypes::metadata::MetadataObject; 5 | 6 | #[derive(Clone, PartialEq, Debug, Packet)] 7 | pub struct EntityMetadataPacket { 8 | entity_id: i32, 9 | metadata: M, 10 | } 11 | 12 | impl EntityMetadataPacket { 13 | pub fn new(entity_id: i32, metadata: M) -> Self { 14 | Self { entity_id, metadata } 15 | } 16 | } 17 | 18 | impl SerializablePacket for EntityMetadataPacket { 19 | fn serialize(&self) -> Vec { 20 | let mut packet = vec![]; 21 | 22 | write_varint_i32(&mut packet, self.entity_id); 23 | self.metadata.write_metadata(&mut packet); 24 | 25 | wrap_packet(&mut packet, Self::id()); 26 | 27 | packet 28 | } 29 | fn id() -> u32 { 30 | 0x3C 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/player_look_0f.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_bool, read_direction, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use crate::subtypes::Direction; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct PlayerLookPacket { 9 | pub direction: Direction, 10 | pub on_ground: bool, 11 | } 12 | 13 | impl SerializablePacket for PlayerLookPacket { 14 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 15 | let mut size = 0; 16 | 17 | let direction = read_direction(&mut data, &mut size)?; 18 | let on_ground = read_bool(&mut data, &mut size)?; 19 | 20 | Ok(( 21 | size, 22 | Packet::PlayerLook(PlayerLookPacket { 23 | direction, 24 | on_ground, 25 | }), 26 | )) 27 | } 28 | 29 | fn id() -> u32 { 30 | 0xF 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/destroy_entities_32.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_varint_i32, write_varint_u32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Default, Packet)] 6 | pub struct DestroyEntitiesPacket { 7 | entity_ids: Vec, 8 | } 9 | 10 | impl DestroyEntitiesPacket { 11 | pub fn new(entity_ids: Vec) -> Self { 12 | DestroyEntitiesPacket { entity_ids } 13 | } 14 | } 15 | 16 | impl SerializablePacket for DestroyEntitiesPacket { 17 | fn serialize(&self) -> Vec { 18 | let mut packet = vec![]; 19 | 20 | write_varint_u32(&mut packet, self.entity_ids.len() as u32); 21 | for entity_id in &self.entity_ids { 22 | write_varint_i32(&mut packet, *entity_id); 23 | } 24 | 25 | wrap_packet(&mut packet, Self::id()); 26 | 27 | packet 28 | } 29 | 30 | fn id() -> u32 { 31 | 0x32 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/creative_inventory_action_1b.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_i16, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | use crate::packets::client::play::window_items_14::SlotData; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct CreativeInventoryActionPacket { 9 | pub slot: i16, 10 | pub clicked_item: SlotData 11 | } 12 | 13 | impl SerializablePacket for CreativeInventoryActionPacket { 14 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 15 | let mut size = 0; 16 | 17 | let slot = read_i16(&mut data, &mut size)?; 18 | let clicked_item = SlotData::read(&mut data, &mut size)?; 19 | 20 | Ok((size, Packet::CreativeInventoryAction(CreativeInventoryActionPacket { 21 | slot, 22 | clicked_item 23 | }))) 24 | } 25 | 26 | fn id() -> u32 { 27 | 0x1B 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/animation_06.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_u8, write_varint_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone)] 6 | pub enum AnimationType { 7 | SwingMainArm = 0, 8 | TakeDamage = 1, 9 | LeaveBed = 2, 10 | SwingOffHand = 3, 11 | CriticalEffect = 4, 12 | MagicCriticalEffect = 5, 13 | } 14 | 15 | #[derive(PartialEq, Debug, Clone, Packet)] 16 | pub struct ServerAnimationPacket { 17 | pub entity_id: i32, 18 | pub animation: AnimationType, 19 | } 20 | 21 | impl SerializablePacket for ServerAnimationPacket { 22 | fn serialize(&self) -> Vec { 23 | let mut packet = vec![]; 24 | 25 | write_varint_i32(&mut packet, self.entity_id); 26 | write_u8(&mut packet, self.animation.clone() as u8); 27 | wrap_packet(&mut packet, Self::id()); 28 | 29 | packet 30 | } 31 | 32 | fn id() -> u32 { 33 | 6 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/player_position_0d.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_bool, read_location2, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use crate::subtypes::Location2; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct PlayerPositionPacket { 9 | pub location: Location2, // Feet 10 | pub on_ground: bool, 11 | } 12 | 13 | impl SerializablePacket for PlayerPositionPacket { 14 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 15 | let mut size = 0; 16 | 17 | let location = read_location2(&mut data, &mut size)?; 18 | let on_ground = read_bool(&mut data, &mut size)?; 19 | 20 | Ok(( 21 | size, 22 | Packet::PlayerPosition(PlayerPositionPacket { 23 | location, 24 | on_ground, 25 | }), 26 | )) 27 | } 28 | 29 | fn id() -> u32 { 30 | 0xD 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/entity_relative_move_26.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_bool, write_i16, write_varint_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[derive(PartialEq, Debug, Clone, Packet)] 6 | pub struct EntityRelativeMovePacket { 7 | pub entity_id: i32, 8 | pub delta_x: i16, 9 | pub delta_y: i16, 10 | pub delta_z: i16, 11 | pub on_ground: bool, 12 | } 13 | 14 | impl SerializablePacket for EntityRelativeMovePacket { 15 | fn serialize(&self) -> Vec { 16 | let mut packet = vec![]; 17 | 18 | write_varint_i32(&mut packet, self.entity_id); 19 | write_i16(&mut packet, self.delta_x); 20 | write_i16(&mut packet, self.delta_y); 21 | write_i16(&mut packet, self.delta_z); 22 | write_bool(&mut packet, self.on_ground); 23 | 24 | wrap_packet(&mut packet, Self::id()); 25 | 26 | packet 27 | } 28 | 29 | fn id() -> u32 { 30 | 0x26 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/server/login.rs: -------------------------------------------------------------------------------- 1 | use crate::packet_tests::utils::check_serialized_packet; 2 | use kittymc_lib::packets::packet_serialization::SerializablePacket; 3 | use kittymc_lib::packets::server::login::login_start_00::LoginStartPacket; 4 | use kittymc_lib::packets::Packet; 5 | 6 | #[test] 7 | fn test_00_login_serialize() { 8 | let packet = LoginStartPacket { 9 | name: "MeowHD".to_string(), 10 | }; 11 | 12 | let serialized = packet.serialize(); 13 | 14 | check_serialized_packet(&serialized, 8, 0, |data| { 15 | assert_eq!(data[0] as usize, b"MeowHD".len()); // Length 16 | assert_eq!(&data[1..7], b"MeowHD"); // Name 17 | }) 18 | .unwrap(); 19 | 20 | let (len, deserialized_res) = LoginStartPacket::deserialize(&serialized[2..]).unwrap(); 21 | assert_eq!( 22 | len, 23 | serialized.len() - 2, 24 | "Length of deserialized size didn't match with serialized packet" 25 | ); 26 | assert_eq!(deserialized_res, Packet::LoginStart(packet)); 27 | } 28 | -------------------------------------------------------------------------------- /kittymc_server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod player; 3 | mod server; 4 | mod inventory; 5 | mod chunking; 6 | 7 | use log::error; 8 | use crate::server::KittyMCServer; 9 | use tracing::metadata::LevelFilter; 10 | use tracing_subscriber::EnvFilter; 11 | 12 | fn main() { 13 | tracing_subscriber::fmt() 14 | .with_env_filter( 15 | EnvFilter::builder() 16 | .with_default_directive(LevelFilter::INFO.into()) 17 | .from_env_lossy(), 18 | ) 19 | .pretty() 20 | .compact() 21 | .with_target(false) 22 | .with_line_number(false) 23 | .with_file(false) 24 | .init(); 25 | 26 | let mut server = match KittyMCServer::new(25565) { 27 | Ok(server) => server, 28 | Err(e) => { 29 | error!("Error while trying to start the server: {e}"); 30 | return; 31 | } 32 | }; 33 | 34 | if let Err(e) = server.run() { 35 | error!("Error occurred while server was running: {e}"); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/status/ping_01.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_i64, write_i64, SerializablePacket}; 3 | use crate::packets::{wrap_packet, Packet}; 4 | use kittymc_macros::Packet; 5 | 6 | // Special Packet. Is being used for serializing the clientbound Ping and deserializing the serverbound Pong 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct StatusPingPongPacket { 9 | payload: i64, 10 | } 11 | 12 | impl SerializablePacket for StatusPingPongPacket { 13 | fn serialize(&self) -> Vec { 14 | let mut packet = vec![]; 15 | 16 | write_i64(&mut packet, self.payload); 17 | 18 | wrap_packet(&mut packet, Self::id()); 19 | 20 | packet 21 | } 22 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 23 | let mut size = 0; 24 | let payload = read_i64(&mut data, &mut size)?; 25 | 26 | Ok((size, Packet::StatusPing(StatusPingPongPacket { payload }))) 27 | } 28 | 29 | fn id() -> u32 { 30 | 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/login/login_start_00.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_length_prefixed_string, write_length_prefixed_string, SerializablePacket, 4 | }; 5 | use crate::packets::{wrap_packet, Packet}; 6 | use kittymc_macros::Packet; 7 | 8 | #[derive(Debug, Clone, PartialEq, Packet)] 9 | pub struct LoginStartPacket { 10 | pub name: String, 11 | } 12 | 13 | impl SerializablePacket for LoginStartPacket { 14 | fn serialize(&self) -> Vec { 15 | let mut packet = vec![]; 16 | 17 | write_length_prefixed_string(&mut packet, &self.name); 18 | 19 | wrap_packet(&mut packet, Self::id()); 20 | 21 | packet 22 | } 23 | 24 | // not including length or packet id 25 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 26 | let mut size = 0; 27 | let name = read_length_prefixed_string(&mut data, &mut size)?; 28 | 29 | Ok((size, Packet::LoginStart(LoginStartPacket { name }))) 30 | } 31 | 32 | fn id() -> u32 { 33 | 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/server_plugin_message_18.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{ 2 | write_length_prefixed_bytes, write_length_prefixed_string, SerializablePacket, 3 | }; 4 | use crate::packets::wrap_packet; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct ServerPluginMessagePacket { 9 | pub channel: String, 10 | pub data: Vec, 11 | } 12 | 13 | impl ServerPluginMessagePacket { 14 | pub fn default_brand() -> Self { 15 | ServerPluginMessagePacket { 16 | channel: "MC|Brand".to_string(), 17 | data: "vanilla".as_bytes().to_vec(), 18 | } 19 | } 20 | } 21 | 22 | impl SerializablePacket for ServerPluginMessagePacket { 23 | fn serialize(&self) -> Vec { 24 | let mut packet = vec![]; 25 | 26 | write_length_prefixed_string(&mut packet, &self.channel); 27 | write_length_prefixed_bytes(&mut packet, &self.data); 28 | 29 | wrap_packet(&mut packet, Self::id()); 30 | 31 | packet 32 | } 33 | 34 | fn id() -> u32 { 35 | 0x18 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/disconnect_1a.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::packets::packet_serialization::SerializablePacket; 3 | use crate::packets::wrap_packet; 4 | use crate::subtypes::components::Component; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct DisconnectPlayPacket { 9 | reason: Component, 10 | } 11 | 12 | impl DisconnectPlayPacket { 13 | pub fn default_restart() -> DisconnectPlayPacket { 14 | DisconnectPlayPacket { 15 | reason: Component::default_restart_disconnect(), 16 | } 17 | } 18 | 19 | pub fn default_error(e: &E) -> DisconnectPlayPacket { 20 | DisconnectPlayPacket { 21 | reason: Component::default_error(e), 22 | } 23 | } 24 | } 25 | 26 | impl SerializablePacket for DisconnectPlayPacket { 27 | fn serialize(&self) -> Vec { 28 | let mut packet = vec![]; 29 | 30 | self.reason.write(&mut packet); 31 | wrap_packet(&mut packet, Self::id()); 32 | 33 | packet 34 | } 35 | 36 | fn id() -> u32 { 37 | 0x1A 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kittymc_server/src/chunking/increasing_ticker.rs: -------------------------------------------------------------------------------- 1 | use std::thread::sleep; 2 | use std::time::Duration; 3 | 4 | const DEFAULT_MAX_IDLE_TIME_MS: u64 = 0; 5 | const DEFAULT_IDLE_TIME_INCREASE_NS: u64 = 10; 6 | 7 | pub struct IncreasingTicker { 8 | current_time_ns: u64, 9 | max_time_ns: u64, 10 | increment_ns: u64, 11 | } 12 | 13 | impl Default for IncreasingTicker { 14 | fn default() -> Self { 15 | Self::new(DEFAULT_MAX_IDLE_TIME_MS * 1_000, DEFAULT_IDLE_TIME_INCREASE_NS) 16 | } 17 | } 18 | 19 | impl IncreasingTicker { 20 | pub fn new(max_time_ns: u64, increment_ns: u64) -> IncreasingTicker { 21 | IncreasingTicker { 22 | current_time_ns: 0, 23 | max_time_ns, 24 | increment_ns, 25 | } 26 | } 27 | 28 | pub fn wait_for_next_tick(&mut self) { 29 | sleep(Duration::from_millis(self.current_time_ns)); 30 | self.current_time_ns = self 31 | .current_time_ns 32 | .saturating_add(self.increment_ns) 33 | .min(self.max_time_ns); 34 | } 35 | 36 | pub fn reset(&mut self) { 37 | self.current_time_ns = 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/client/play.rs: -------------------------------------------------------------------------------- 1 | use kittymc_lib::packets::client::login::set_compression_03::SetCompressionPacket; 2 | use kittymc_lib::packets::client::login::success_02::LoginSuccessPacket; 3 | use kittymc_lib::packets::packet_serialization::SerializablePacket; 4 | 5 | #[test] 6 | fn test_03_set_compression() { 7 | let packet = SetCompressionPacket { threshold: 256 }.serialize(); 8 | 9 | assert_eq!(&packet, &[03, 03, 0x80, 02]); 10 | } 11 | 12 | #[test] 13 | fn test_02_login_success() { 14 | let _actual_real_raw_packet = b"0\x00\x02$0e22d127-3477-35f9-a65a-6fb3611c78fb\x08will_owo"; 15 | let raw_packet = b"/\x02$0e22d127-3477-35f9-a65a-6fb3611c78fb\x08will_owo"; 16 | // The difference here is that the actual packet serialized its varints badly. It adds an extra 0 after the packet length 17 | // The raw packet is a slightly modified version since kittymc does not have this encoding error 18 | // clients accept it fine without the extra 0 19 | 20 | let packet = LoginSuccessPacket::deserialize(&raw_packet[2..]).unwrap(); 21 | assert_eq!(&packet.1.serialize(), raw_packet) // Here the implementation of integer-encodings varint encode is different from the minecraft one 22 | } 23 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/login/set_compression_03.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_varint_u32, write_varint_u32, SerializablePacket}; 3 | use crate::packets::{wrap_packet, Packet}; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct SetCompressionPacket { 8 | pub threshold: u32, 9 | } 10 | 11 | impl Default for SetCompressionPacket { 12 | fn default() -> Self { 13 | SetCompressionPacket { threshold: 256 } 14 | } 15 | } 16 | 17 | impl SerializablePacket for SetCompressionPacket { 18 | fn serialize(&self) -> Vec { 19 | let mut packet = vec![]; 20 | 21 | write_varint_u32(&mut packet, self.threshold); 22 | 23 | wrap_packet(&mut packet, Self::id()); 24 | 25 | packet 26 | } 27 | 28 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 29 | let mut size = 0; 30 | 31 | let threshold = read_varint_u32(&mut data, &mut size)?; 32 | 33 | Ok(( 34 | size, 35 | Packet::SetCompression(SetCompressionPacket { threshold }), 36 | )) 37 | } 38 | 39 | fn id() -> u32 { 40 | 3 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/spawn_player_05.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_direction_as_angles, write_location2, write_uuid, write_varint_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::metadata::{EntityMetadata, MetadataObject}; 4 | use crate::subtypes::{Direction, Location2}; 5 | use kittymc_macros::Packet; 6 | use uuid::Uuid; 7 | 8 | #[derive(PartialEq, Debug, Clone, Packet)] 9 | pub struct SpawnPlayerPacket { 10 | pub entity_id: i32, 11 | pub player_uuid: Uuid, 12 | pub location: Location2, 13 | pub direction: Direction, 14 | pub metadata: EntityMetadata, 15 | } 16 | 17 | impl SerializablePacket for SpawnPlayerPacket { 18 | fn serialize(&self) -> Vec { 19 | let mut packet = vec![]; 20 | 21 | write_varint_i32(&mut packet, self.entity_id); 22 | write_uuid(&mut packet, &self.player_uuid); 23 | write_location2(&mut packet, &self.location); 24 | write_direction_as_angles(&mut packet, &self.direction); 25 | self.metadata.write_metadata(&mut packet); 26 | 27 | wrap_packet(&mut packet, Self::id()); 28 | 29 | packet 30 | } 31 | 32 | fn id() -> u32 { 33 | 5 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::format_err; 2 | use integer_encoding::VarInt; 3 | 4 | pub fn check_serialized_packet( 5 | actual_data: &[u8], 6 | len: usize, 7 | packet_id: u32, 8 | other: fn(data: &[u8]), 9 | ) -> anyhow::Result<()> { 10 | let mut data = actual_data; 11 | let (actual_len, size): (usize, usize) = 12 | VarInt::decode_var(data).ok_or_else(|| format_err!("Packet Length not valid"))?; 13 | assert_eq!( 14 | actual_len, len, 15 | "Serialized Packet length doesn't match hardcoded Packet Length" 16 | ); 17 | assert_eq!( 18 | actual_data.len(), 19 | len + 1, 20 | "Actual Packet length doesn't match hardcoded Packet Length" 21 | ); 22 | assert_eq!( 23 | actual_len, 24 | actual_data.len() - 1, 25 | "Serialized Packet length doesn't match actual Packet length" 26 | ); 27 | data = &data[size..]; 28 | 29 | let (actual_packet_id, size): (u32, usize) = 30 | VarInt::decode_var(data).ok_or_else(|| format_err!("Packet ID not valid"))?; 31 | assert_eq!(actual_packet_id, packet_id, "Packet ID doesn't match"); 32 | data = &data[size..]; 33 | 34 | other(data); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/block_break_animation_08.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{ 2 | write_block_location, write_u8, write_varint_u32, SerializablePacket, 3 | }; 4 | use crate::packets::wrap_packet; 5 | use crate::subtypes::Location; 6 | use kittymc_macros::Packet; 7 | 8 | #[derive(PartialEq, Debug, Clone, Packet)] 9 | pub struct BlockBreakAnimationPacket { 10 | entity_id: u32, 11 | location: Location, 12 | destroy_stage: u8, 13 | } 14 | 15 | impl BlockBreakAnimationPacket { 16 | pub fn new(entity_id: u32, location: Location, destroy_stage: u8) -> BlockBreakAnimationPacket { 17 | BlockBreakAnimationPacket { 18 | entity_id, 19 | location, 20 | destroy_stage, 21 | } 22 | } 23 | } 24 | 25 | impl SerializablePacket for BlockBreakAnimationPacket { 26 | fn serialize(&self) -> Vec { 27 | let mut packet = vec![]; 28 | 29 | write_varint_u32(&mut packet, self.entity_id); 30 | write_block_location(&mut packet, &self.location); 31 | write_u8(&mut packet, self.destroy_stage); 32 | 33 | wrap_packet(&mut packet, Self::id()); 34 | 35 | packet 36 | } 37 | 38 | fn id() -> u32 { 39 | 8 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /kittymc_lib/tests/packet_tests/server/handshake.rs: -------------------------------------------------------------------------------- 1 | use crate::packet_tests::utils::check_serialized_packet; 2 | use kittymc_lib::packets::packet_serialization::SerializablePacket; 3 | use kittymc_lib::packets::server::handshake::HandshakePacket; 4 | use kittymc_lib::packets::Packet; 5 | use kittymc_lib::subtypes::state::State; 6 | 7 | #[test] 8 | fn test_00_handshake_serialize() { 9 | let handshake = HandshakePacket { 10 | protocol_version: 47, 11 | server_address: "meowmc.de".to_string(), 12 | server_port: 25565, 13 | next_state: State::Status, 14 | }; 15 | 16 | let serialized = handshake.serialize(); 17 | check_serialized_packet(&serialized, 15, 0, |data| { 18 | assert_eq!(data[0], 47); // Protocol Version 19 | assert_eq!(data[1] as usize, b"meowmc.de".len()); // Server Address Length 20 | assert_eq!(&data[2..11], b"meowmc.de"); // Server Address 21 | }) 22 | .unwrap(); 23 | 24 | let (len, deserialized_res) = HandshakePacket::deserialize(&serialized[2..]).unwrap(); 25 | assert_eq!( 26 | len, 27 | serialized.len() - 2, 28 | "Length of deserialized size didn't match with serialized packet" 29 | ); 30 | assert_eq!(deserialized_res, Packet::Handshake(handshake)); 31 | } 32 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod animation_1d; 2 | pub mod chat_message_02; 3 | pub mod client_held_item_change_a1; 4 | pub mod client_keep_alive_0b; 5 | pub mod client_player_position_and_look_0e; 6 | pub mod client_plugin_message_09; 7 | pub mod client_settings_04; 8 | pub mod entity_action_15; 9 | pub mod player_digging_14; 10 | pub mod player_look_0f; 11 | pub mod player_position_0d; 12 | pub mod teleport_confirm_00; 13 | pub mod creative_inventory_action_1b; 14 | pub mod player_block_placement_1f; 15 | 16 | pub use animation_1d::ClientAnimationPacket; 17 | pub use chat_message_02::ServerChatMessagePacket; 18 | pub use client_held_item_change_a1::ClientHeldItemChangePacket; 19 | pub use client_keep_alive_0b::ClientKeepAlivePacket; 20 | pub use client_player_position_and_look_0e::ClientPlayerPositionAndLookPacket; 21 | pub use client_plugin_message_09::ClientPluginMessagePacket; 22 | pub use client_settings_04::ClientSettingsPacket; 23 | pub use player_digging_14::PlayerDiggingPacket; 24 | pub use player_look_0f::PlayerLookPacket; 25 | pub use player_position_0d::PlayerPositionPacket; 26 | pub use teleport_confirm_00::TeleportConfirmPacket; 27 | pub use creative_inventory_action_1b::CreativeInventoryActionPacket; 28 | pub use player_block_placement_1f::PlayerBlockPlacementPacket; 29 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/entity_look_28.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_angle, write_u8, write_varint_i32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::Direction; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Packet)] 7 | pub struct EntityLookPacket { 8 | pub entity_id: i32, 9 | pub direction: Direction, 10 | pub on_ground: bool, 11 | } 12 | 13 | impl SerializablePacket for EntityLookPacket { 14 | fn serialize(&self) -> Vec { 15 | let mut packet = vec![]; 16 | 17 | let mut yaw = self.direction.x; 18 | while yaw > 180. { 19 | yaw -= 360.; 20 | } 21 | while yaw < -180. { 22 | yaw += 360.; 23 | } 24 | let mut pitch = self.direction.y; 25 | while pitch > 180. { 26 | pitch -= 360.; 27 | } 28 | while pitch < -180. { 29 | pitch += 360.; 30 | } 31 | 32 | write_varint_i32(&mut packet, self.entity_id); 33 | write_angle(&mut packet, yaw); 34 | write_angle(&mut packet, pitch); 35 | write_u8(&mut packet, self.on_ground as u8); 36 | 37 | wrap_packet(&mut packet, Self::id()); 38 | 39 | packet 40 | } 41 | 42 | fn id() -> u32 { 43 | 0x28 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/block_change_0b.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{ 2 | write_block_location, write_varint_u32, SerializablePacket, 3 | }; 4 | use crate::packets::wrap_packet; 5 | use crate::subtypes::Location; 6 | use kittymc_macros::Packet; 7 | use log::info; 8 | use crate::packets::client::play::chunk_data_20::BlockStateId; 9 | 10 | #[derive(PartialEq, Debug, Clone, Packet)] 11 | pub struct BlockChangePacket { 12 | pub location: Location, 13 | pub block_id: BlockStateId, 14 | } 15 | 16 | impl BlockChangePacket { 17 | pub fn new(location: Location, block_id: BlockStateId) -> BlockChangePacket { 18 | BlockChangePacket { location, block_id } 19 | } 20 | 21 | pub fn new_empty(location: Location) -> BlockChangePacket { 22 | BlockChangePacket { 23 | location, 24 | block_id: 0b0000_0000, 25 | } 26 | } 27 | } 28 | 29 | impl SerializablePacket for BlockChangePacket { 30 | fn serialize(&self) -> Vec { 31 | let mut packet = vec![]; 32 | info!("Chaging block to {:?}", self); 33 | 34 | write_block_location(&mut packet, &self.location); 35 | write_varint_u32(&mut packet, self.block_id); 36 | 37 | wrap_packet(&mut packet, Self::id()); 38 | 39 | packet 40 | } 41 | 42 | fn id() -> u32 { 43 | 0x0B 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/player_position_and_look_2f.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{ 2 | write_direction, write_location2, write_u8, write_varint_u32, SerializablePacket, 3 | }; 4 | use crate::packets::wrap_packet; 5 | use crate::subtypes::{Direction, Location2}; 6 | use kittymc_macros::Packet; 7 | use rand::random; 8 | 9 | #[derive(PartialEq, Debug, Clone, Packet)] 10 | pub struct ServerPlayerPositionAndLookPacket { 11 | location: Location2, // Feet 12 | direction: Direction, 13 | relative_flags: u8, 14 | teleport_id: u32, 15 | } 16 | 17 | impl Default for ServerPlayerPositionAndLookPacket { 18 | fn default() -> Self { 19 | ServerPlayerPositionAndLookPacket { 20 | location: Location2::new(0., 5., 0.), 21 | direction: Default::default(), 22 | relative_flags: 0, 23 | teleport_id: random(), 24 | } 25 | } 26 | } 27 | 28 | impl SerializablePacket for ServerPlayerPositionAndLookPacket { 29 | fn serialize(&self) -> Vec { 30 | let mut packet = vec![]; 31 | 32 | write_location2(&mut packet, &self.location); 33 | write_direction(&mut packet, &self.direction); 34 | write_u8(&mut packet, self.relative_flags); 35 | write_varint_u32(&mut packet, self.teleport_id); 36 | 37 | wrap_packet(&mut packet, Self::id()); 38 | 39 | packet 40 | } 41 | 42 | fn id() -> u32 { 43 | 0x2F 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/client_player_position_and_look_0e.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_bool, read_f32, read_f64, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use crate::subtypes::{Direction, Location2}; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(PartialEq, Debug, Clone, Packet)] 8 | pub struct ClientPlayerPositionAndLookPacket { 9 | pub location: Location2, // Feet 10 | pub direction: Direction, 11 | pub on_ground: bool, 12 | } 13 | 14 | impl SerializablePacket for ClientPlayerPositionAndLookPacket { 15 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 16 | let mut size = 0; 17 | 18 | let location_x = read_f64(&mut data, &mut size)?; 19 | let location_y = read_f64(&mut data, &mut size)?; 20 | let location_z = read_f64(&mut data, &mut size)?; 21 | let yaw = read_f32(&mut data, &mut size)?; 22 | let pitch = read_f32(&mut data, &mut size)?; 23 | let on_ground = read_bool(&mut data, &mut size)?; 24 | 25 | Ok(( 26 | size, 27 | Packet::PlayerPositionAndLook(ClientPlayerPositionAndLookPacket { 28 | location: Location2::new(location_x, location_y, location_z), 29 | direction: Direction::new(yaw, pitch), 30 | on_ground, 31 | }), 32 | )) 33 | } 34 | 35 | fn id() -> u32 { 36 | 0x0E 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/client_plugin_message_09.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_length_prefixed_bytes, read_length_prefixed_string, SerializablePacket, 4 | }; 5 | use crate::packets::Packet; 6 | use kittymc_macros::Packet; 7 | use std::fmt::{Debug, Formatter}; 8 | 9 | #[derive(PartialEq, Clone, Packet)] 10 | pub struct ClientPluginMessagePacket { 11 | pub channel: String, 12 | pub data: Vec, 13 | } 14 | 15 | impl Debug for ClientPluginMessagePacket { 16 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 17 | f.debug_struct("ClientPluginMessagePacket") 18 | .field("channel", &self.channel) 19 | .field( 20 | "data", 21 | &format!("{:?} ({})", self.data, String::from_utf8_lossy(&self.data)), 22 | ) 23 | .finish() 24 | } 25 | } 26 | 27 | impl SerializablePacket for ClientPluginMessagePacket { 28 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 29 | let mut total_size = 0; 30 | let channel = read_length_prefixed_string(&mut data, &mut total_size)?; 31 | let data = read_length_prefixed_bytes(&mut data, &mut total_size)?; 32 | 33 | Ok(( 34 | total_size, 35 | Packet::PluginMessage(ClientPluginMessagePacket { channel, data }), 36 | )) 37 | } 38 | 39 | fn id() -> u32 { 40 | 0x18 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/player_abilities_2c.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_f32, write_u8, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use bitflags::bitflags; 4 | use kittymc_macros::Packet; 5 | 6 | bitflags! { 7 | 8 | #[repr(transparent)] 9 | #[derive(PartialEq, Debug, Clone)] 10 | pub struct PlayerAbilitiesFlags: u8 { 11 | const invulnerable = 0b00000001; 12 | const flying = 0b00000010; 13 | const allow_flying = 0b00000100; 14 | const creative_mode = 0b00001000; 15 | } 16 | } 17 | 18 | #[derive(PartialEq, Debug, Clone, Packet)] 19 | pub struct PlayerAbilitiesPacket { 20 | flags: PlayerAbilitiesFlags, 21 | flying_speed: f32, 22 | field_of_view_modifier: f32, 23 | } 24 | 25 | impl Default for PlayerAbilitiesPacket { 26 | fn default() -> Self { 27 | PlayerAbilitiesPacket { 28 | flags: PlayerAbilitiesFlags::all() ^ PlayerAbilitiesFlags::flying, 29 | flying_speed: 0.1, 30 | field_of_view_modifier: 0.1, 31 | } 32 | } 33 | } 34 | 35 | impl SerializablePacket for PlayerAbilitiesPacket { 36 | fn serialize(&self) -> Vec { 37 | let mut packet = vec![]; 38 | 39 | write_u8(&mut packet, self.flags.bits()); 40 | write_f32(&mut packet, self.flying_speed); 41 | write_f32(&mut packet, self.field_of_view_modifier); 42 | 43 | wrap_packet(&mut packet, Self::id()); 44 | 45 | packet 46 | } 47 | 48 | fn id() -> u32 { 49 | 0x2C 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/login/disconnect_login_00.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::SerializablePacket; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::components::{Component, TextComponent}; 4 | use crate::utils::KITTYMC_TAG; 5 | use kittymc_macros::Packet; 6 | 7 | #[derive(Debug, Packet)] 8 | pub struct DisconnectLoginPacket { 9 | reason: Component, 10 | } 11 | 12 | impl Default for DisconnectLoginPacket { 13 | fn default() -> Self { 14 | DisconnectLoginPacket { 15 | reason: Component::Text( 16 | TextComponent::builder() 17 | .text(format!( 18 | "{KITTYMC_TAG} YOU'VE BEEN DISCONNECTED §b:<§r!\n\n§dS0RRYY, ITS OV3R" 19 | )) 20 | .build(), 21 | ), 22 | } 23 | } 24 | } 25 | 26 | impl DisconnectLoginPacket { 27 | pub fn wrong_version() -> Self { 28 | DisconnectLoginPacket { 29 | reason: Component::Text( 30 | TextComponent::builder() 31 | .text(format!( 32 | "{KITTYMC_TAG} BUUUH, WRONG VERSION. §b:<§r!\n§dHop on 1.12.2 :3" 33 | )) 34 | .build(), 35 | ), 36 | } 37 | } 38 | } 39 | 40 | impl SerializablePacket for DisconnectLoginPacket { 41 | fn serialize(&self) -> Vec { 42 | let mut packet = vec![]; 43 | 44 | self.reason.write(&mut packet); 45 | 46 | wrap_packet(&mut packet, Self::id()); 47 | 48 | packet 49 | } 50 | 51 | fn id() -> u32 { 52 | 0 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/player_block_placement_1f.rs: -------------------------------------------------------------------------------- 1 | use kittymc_macros::Packet; 2 | use crate::error::KittyMCError; 3 | use crate::packets::Packet; 4 | use crate::packets::packet_serialization::{read_block_location, read_f32, read_varint_u32, SerializablePacket}; 5 | use crate::packets::server::play::client_settings_04::Hand; 6 | use crate::packets::server::play::player_digging_14::BlockFace; 7 | use crate::subtypes::Location; 8 | 9 | #[derive(Debug, Clone, PartialEq, Packet)] 10 | pub struct PlayerBlockPlacementPacket { 11 | pub location: Location, 12 | pub face: BlockFace, 13 | pub hand: Hand, 14 | pub cursor_pos_x: f32, 15 | pub cursor_pos_y: f32, 16 | pub cursor_pos_z: f32, 17 | } 18 | 19 | impl SerializablePacket for PlayerBlockPlacementPacket { 20 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 21 | let mut size = 0usize; 22 | 23 | let location = read_block_location(&mut data, &mut size)?; 24 | let face = read_varint_u32(&mut data, &mut size)?.into(); 25 | let hand = read_varint_u32(&mut data, &mut size)?.into(); 26 | let cursor_x = read_f32(&mut data, &mut size)?; 27 | let cursor_y = read_f32(&mut data, &mut size)?; 28 | let cursor_z = read_f32(&mut data, &mut size)?; 29 | 30 | Ok((size, Packet::PlayerBlockPlacement(Self { 31 | location, 32 | face, 33 | hand, 34 | cursor_pos_x: cursor_x, 35 | cursor_pos_y: cursor_y, 36 | cursor_pos_z: cursor_z, 37 | }))) 38 | } 39 | 40 | fn id() -> u32 { 41 | 0x1B 42 | } 43 | } -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/login/success_02.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_length_prefixed_string, write_length_prefixed_string, SerializablePacket, 4 | }; 5 | use crate::packets::{wrap_packet, Packet}; 6 | use crate::utils::generate_cracked_uuid; 7 | use kittymc_macros::Packet; 8 | use std::str::FromStr; 9 | use uuid::Uuid; 10 | 11 | #[derive(PartialEq, Debug, Clone, Packet)] 12 | pub struct LoginSuccessPacket { 13 | pub uuid: Uuid, 14 | pub username: String, 15 | } 16 | 17 | impl LoginSuccessPacket { 18 | pub fn from_name_cracked(name: &str) -> Result { 19 | let uuid = generate_cracked_uuid(name)?; 20 | 21 | Ok(LoginSuccessPacket { 22 | uuid, 23 | username: name.to_string(), 24 | }) 25 | } 26 | } 27 | 28 | impl SerializablePacket for LoginSuccessPacket { 29 | fn serialize(&self) -> Vec { 30 | let mut packet = vec![]; 31 | 32 | write_length_prefixed_string(&mut packet, &self.uuid.hyphenated().to_string()); 33 | write_length_prefixed_string(&mut packet, &self.username); 34 | 35 | wrap_packet(&mut packet, Self::id()); 36 | 37 | packet 38 | } 39 | 40 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 41 | let mut size = 0; 42 | 43 | let uuid = read_length_prefixed_string(&mut data, &mut size)?; 44 | let uuid = Uuid::from_str(&uuid)?; 45 | 46 | let username = read_length_prefixed_string(&mut data, &mut size)?; 47 | 48 | Ok(( 49 | size, 50 | Packet::LoginSuccess(LoginSuccessPacket { uuid, username }), 51 | )) 52 | } 53 | 54 | fn id() -> u32 { 55 | 2 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/join_game_23.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::client::play::{Difficulty, Dimension, GameMode, LevelType}; 2 | use crate::packets::packet_serialization::{ 3 | write_bool, write_i32, write_length_prefixed_string, write_u8, SerializablePacket, 4 | }; 5 | use crate::packets::wrap_packet; 6 | use kittymc_macros::Packet; 7 | 8 | #[derive(Clone, Debug, Packet)] 9 | #[allow(dead_code)] 10 | pub struct JoinGamePacket { 11 | entity_id: i32, 12 | gamemode: GameMode, 13 | dimension: Dimension, 14 | difficulty: Difficulty, 15 | max_players: u8, 16 | level_type: LevelType, 17 | reduced_debug_info: bool, 18 | } 19 | 20 | impl JoinGamePacket { 21 | pub fn new(id: i32) -> Self { 22 | Self { 23 | entity_id: id, 24 | gamemode: GameMode::Creative, 25 | dimension: Dimension::Overworld, 26 | difficulty: Difficulty::Peaceful, 27 | max_players: 1, 28 | level_type: LevelType::Flat, 29 | reduced_debug_info: false, 30 | } 31 | } 32 | } 33 | 34 | impl SerializablePacket for JoinGamePacket { 35 | fn serialize(&self) -> Vec { 36 | let mut packet = vec![]; 37 | 38 | write_i32(&mut packet, self.entity_id); 39 | write_u8(&mut packet, self.gamemode as u8); 40 | write_i32(&mut packet, self.dimension as i32); 41 | write_u8(&mut packet, self.difficulty as u8); 42 | write_u8(&mut packet, 69); // TODO: Actual max players 43 | write_length_prefixed_string(&mut packet, self.level_type.as_str()); 44 | write_bool(&mut packet, self.reduced_debug_info); 45 | 46 | wrap_packet(&mut packet, Self::id()); 47 | 48 | packet 49 | } 50 | 51 | fn id() -> u32 { 52 | 0x23 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/unlock_recipes_31.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_bool, write_varint_u32, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | 5 | #[repr(u32)] 6 | #[derive(PartialEq, Debug, Clone, Copy, Packet)] 7 | pub enum UnlockAction { 8 | Init = 0, 9 | Add = 1, 10 | Remove = 2, 11 | } 12 | 13 | #[derive(PartialEq, Debug, Clone, Packet)] 14 | pub struct UnlockRecipesPacket { 15 | action: UnlockAction, 16 | crafting_book_open: bool, 17 | filtering_craftable: bool, 18 | recipe_ids: Vec, 19 | recipe_ids_2: Vec, 20 | } 21 | 22 | impl Default for UnlockRecipesPacket { 23 | fn default() -> Self { 24 | UnlockRecipesPacket { 25 | action: UnlockAction::Init, 26 | crafting_book_open: false, 27 | filtering_craftable: false, 28 | recipe_ids: vec![], 29 | recipe_ids_2: vec![], 30 | } 31 | } 32 | } 33 | 34 | impl SerializablePacket for UnlockRecipesPacket { 35 | fn serialize(&self) -> Vec { 36 | let mut packet = vec![]; 37 | 38 | write_varint_u32(&mut packet, self.action as u32); 39 | write_bool(&mut packet, self.crafting_book_open); 40 | write_bool(&mut packet, self.filtering_craftable); 41 | 42 | write_varint_u32(&mut packet, self.recipe_ids.len() as u32); 43 | for id in &self.recipe_ids { 44 | write_varint_u32(&mut packet, *id); 45 | } 46 | 47 | write_varint_u32(&mut packet, self.recipe_ids_2.len() as u32); 48 | for id in &self.recipe_ids_2 { 49 | write_varint_u32(&mut packet, *id); 50 | } 51 | 52 | wrap_packet(&mut packet, Self::id()); 53 | 54 | packet 55 | } 56 | 57 | fn id() -> u32 { 58 | 0x31 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/chat_message_0f.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{write_i8, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use crate::subtypes::components::Component; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone, Copy)] 7 | pub enum ChatPosition { 8 | Chat = 0, 9 | System = 1, 10 | Hotbar = 2, 11 | } 12 | 13 | #[derive(Debug, Packet)] 14 | pub struct ClientChatMessagePacket { 15 | text: Component, 16 | position: ChatPosition, 17 | } 18 | 19 | impl ClientChatMessagePacket { 20 | pub fn new_join_message(name: &str) -> Self { 21 | ClientChatMessagePacket { 22 | text: Component::default_join(name), 23 | position: ChatPosition::Chat, 24 | } 25 | } 26 | 27 | pub fn new_quit_message(name: &str) -> Self { 28 | ClientChatMessagePacket { 29 | text: Component::default_quit(name), 30 | position: ChatPosition::Chat, 31 | } 32 | } 33 | 34 | pub fn new_chat_message(name: &str, message: &str) -> Self { 35 | ClientChatMessagePacket { 36 | text: Component::default_chat(name, message), 37 | position: ChatPosition::Chat, 38 | } 39 | } 40 | } 41 | 42 | impl SerializablePacket for ClientChatMessagePacket { 43 | fn serialize(&self) -> Vec { 44 | let mut packet = vec![]; 45 | 46 | // write_length_prefixed_string( 47 | // &mut packet, 48 | // &serde_json::to_string(&json!({ 49 | // "text": self.text 50 | // })) 51 | // .unwrap(), 52 | // ); 53 | 54 | self.text.write(&mut packet); 55 | write_i8(&mut packet, self.position as i8); 56 | 57 | wrap_packet(&mut packet, Self::id()); 58 | 59 | packet 60 | } 61 | 62 | fn id() -> u32 { 63 | 0x0F 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/handshake.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_length_prefixed_string, read_u16, read_varint_u32, write_length_prefixed_string, 4 | write_u16, write_varint_u32, SerializablePacket, 5 | }; 6 | use crate::packets::{wrap_packet, Packet}; 7 | use crate::subtypes::state::State; 8 | use kittymc_macros::Packet; 9 | 10 | #[derive(Debug, Clone, PartialEq, Packet)] 11 | pub struct HandshakePacket { 12 | pub protocol_version: u32, 13 | pub server_address: String, 14 | pub server_port: u16, 15 | pub next_state: State, 16 | } 17 | 18 | impl SerializablePacket for HandshakePacket { 19 | fn serialize(&self) -> Vec { 20 | let mut packet = vec![]; 21 | 22 | write_varint_u32(&mut packet, self.protocol_version); 23 | write_length_prefixed_string(&mut packet, &self.server_address); 24 | write_u16(&mut packet, self.server_port); 25 | write_varint_u32(&mut packet, self.next_state as u32); 26 | 27 | wrap_packet(&mut packet, Self::id()); 28 | 29 | packet 30 | } 31 | 32 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 33 | let mut total_size = 0; 34 | 35 | let protocol_version = read_varint_u32(&mut data, &mut total_size)?; 36 | let server_address = read_length_prefixed_string(&mut data, &mut total_size)?; 37 | let server_port = read_u16(&mut data, &mut total_size)?; 38 | let next_state = State::from(read_varint_u32(&mut data, &mut total_size)?); 39 | 40 | Ok(( 41 | total_size, 42 | Packet::Handshake(HandshakePacket { 43 | protocol_version, 44 | server_address, 45 | server_port, 46 | next_state, 47 | }), 48 | )) 49 | } 50 | 51 | fn id() -> u32 { 52 | 0 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/entity_action_15.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{read_varint_u32, SerializablePacket}; 3 | use crate::packets::Packet; 4 | use kittymc_macros::Packet; 5 | 6 | #[derive(PartialEq, Debug, Clone)] 7 | pub enum EntityAction { 8 | StartSprinting, 9 | StopSprinting, 10 | LeaveBed, 11 | StartSneaking, 12 | StopSneaking, 13 | StartJumpingWithHorse, 14 | StopJumpingWithHorse, 15 | OpenHorseInventory, 16 | StartFlyingWithElytra, 17 | Unknown, 18 | } 19 | 20 | impl From for EntityAction { 21 | fn from(value: u32) -> Self { 22 | match value { 23 | 0 => EntityAction::StartSneaking, 24 | 1 => EntityAction::StopSneaking, 25 | 2 => EntityAction::LeaveBed, 26 | 3 => EntityAction::StartSprinting, 27 | 4 => EntityAction::StopSprinting, 28 | 5 => EntityAction::StartJumpingWithHorse, 29 | 6 => EntityAction::StopJumpingWithHorse, 30 | 7 => EntityAction::OpenHorseInventory, 31 | 8 => EntityAction::StartFlyingWithElytra, 32 | _ => EntityAction::Unknown, 33 | } 34 | } 35 | } 36 | 37 | #[derive(PartialEq, Debug, Clone, Packet)] 38 | pub struct EntityActionPacket { 39 | pub entity_id: u32, 40 | pub action: EntityAction, 41 | pub jump_boost_amount: u32, 42 | } 43 | 44 | impl SerializablePacket for EntityActionPacket { 45 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 46 | let mut size = 0; 47 | 48 | let entity_id = read_varint_u32(&mut data, &mut size)?; 49 | let action = read_varint_u32(&mut data, &mut size)?.into(); 50 | let jump_boost_amount = read_varint_u32(&mut data, &mut size)?; 51 | 52 | Ok(( 53 | size, 54 | Packet::EntityAction(EntityActionPacket { 55 | entity_id, 56 | action, 57 | jump_boost_amount, 58 | }), 59 | )) 60 | } 61 | 62 | fn id() -> u32 { 63 | 0x15 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /kittymc_lib/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::subtypes::components::TextComponent; 3 | use uuid::{Builder, Uuid}; 4 | 5 | pub const KITTYMC_TAG: &str = "§4[§5K§6I§eT§aT§bY §dMC§4]§r"; 6 | 7 | pub fn generate_cracked_uuid(name: &str) -> Result { 8 | if name.len() > 16 { 9 | return Err(KittyMCError::TooMuchData(name.len(), 16)); 10 | } 11 | 12 | let md5 = md5::compute(format!("OfflinePlayer:{name}")); 13 | 14 | Ok(Builder::from_md5_bytes(md5.0).into_uuid()) 15 | } 16 | 17 | pub fn is_cool(name: &str) -> bool { 18 | const COOL_PEOPLE: [&'static str; 3] = ["will_owo", "IT0NA31", "OnlyAfro"]; 19 | 20 | COOL_PEOPLE.contains(&name) 21 | } 22 | 23 | pub fn rainbowize_cool_people_textcomp(name: &str, bold: bool) -> Option { 24 | if !is_cool(name) { 25 | return None; 26 | } 27 | 28 | Some( 29 | TextComponent::builder() 30 | .text(rainbowize_cool_people(name, bold)) 31 | .build(), 32 | ) 33 | } 34 | 35 | pub fn rainbowize_cool_people(name: &str, bold: bool) -> String { 36 | if is_cool(&name) { 37 | to_mc_rainbow(name, bold) 38 | } else { 39 | name.to_string() 40 | } 41 | } 42 | 43 | pub fn to_mc_rainbow(text: &str, bold: bool) -> String { 44 | let colors = ["§c", "§6", "§e", "§a", "§b", "§9", "§d"]; 45 | 46 | let mut result = String::new(); 47 | for (i, ch) in text.chars().enumerate() { 48 | let color_code = colors[i % colors.len()]; 49 | result.push_str(color_code); 50 | if bold { 51 | result.push_str("§l"); 52 | } 53 | result.push(ch); 54 | } 55 | 56 | result 57 | } 58 | 59 | pub fn axis_to_angle(angle: f32) -> i8 { 60 | (angle / 360. * 256.) as i8 61 | } 62 | 63 | #[test] 64 | fn test_cracked_uuid() { 65 | use std::str::FromStr; 66 | 67 | assert_eq!( 68 | generate_cracked_uuid("will_owo").unwrap(), 69 | Uuid::from_str("0e22d127-3477-35f9-a65a-6fb3611c78fb").unwrap() 70 | ); 71 | assert_eq!( 72 | generate_cracked_uuid("meow").unwrap(), 73 | Uuid::from_str("dadfb5ef-c239-3cb3-b316-aec3a76dbc71").unwrap() 74 | ); 75 | assert_eq!( 76 | generate_cracked_uuid("IT0NA31").unwrap(), 77 | Uuid::from_str("fe86cee2-9d18-3100-bc41-6740712ec780").unwrap() 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /kittymc_server/src/chunking/chunk_unloader.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs; 3 | use std::sync::{Arc, RwLock}; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | use log::error; 7 | use kittymc_lib::error::KittyMCError; 8 | use kittymc_lib::subtypes::ChunkPosition; 9 | use crate::chunking::chunk_manager::{make_chunk_file_path, SharedChunk, SharedChunkAccessList, SharedChunkList}; 10 | 11 | const CHUNK_REMOVE_TIME_S: u64 = 30; 12 | 13 | pub struct ChunkUnloader { 14 | collection: SharedChunkList, 15 | access_list: SharedChunkAccessList, 16 | } 17 | 18 | impl ChunkUnloader { 19 | pub fn entry_thread(collection: Arc>>, access_list: SharedChunkAccessList) { 20 | let mut unloader = ChunkUnloader { 21 | collection, 22 | access_list, 23 | }; 24 | 25 | let _ = fs::create_dir("world"); 26 | unloader.run(); 27 | } 28 | 29 | fn run(&mut self) { 30 | loop { 31 | self.save_old(); 32 | sleep(Duration::from_secs(CHUNK_REMOVE_TIME_S)); 33 | } 34 | } 35 | 36 | fn save_old(&self) { 37 | let mut to_save = vec![]; 38 | 39 | for (chunk_pos, time) in self.access_list.write().unwrap().iter() { 40 | if time.elapsed() >= Duration::from_secs(CHUNK_REMOVE_TIME_S) { 41 | to_save.push(chunk_pos.clone()); 42 | } 43 | } 44 | 45 | let mut access_list_lock = self.access_list.write().unwrap(); 46 | for pos in to_save { 47 | if let Err(e) = self.save_chunk(&pos) { 48 | error!("LOST! Failed to save chunk: {}", e); 49 | } 50 | 51 | access_list_lock.remove(&pos); 52 | } 53 | drop(access_list_lock); 54 | } 55 | 56 | fn save_chunk(&self, chunk_pos: &ChunkPosition) -> Result<(), KittyMCError> { 57 | let mut collection_lock = self.collection.write().unwrap(); 58 | let chunk = collection_lock.remove(chunk_pos); 59 | drop(collection_lock); 60 | 61 | let Some(chunk) = chunk else { 62 | error!("Uhm.. chunk was so old it got dementia and forgot it existed."); 63 | return Err(KittyMCError::InvalidChunk(chunk_pos.block_location())); 64 | }; 65 | 66 | let chunk = chunk.read().unwrap(); 67 | chunk.save_to(&make_chunk_file_path(chunk_pos))?; 68 | 69 | Ok(()) 70 | } 71 | } -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/window_items_14.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::packet_serialization::{read_nbt, read_u16, read_u8, write_i16, write_u16, write_u8, SerializablePacket}; 2 | use crate::packets::wrap_packet; 3 | use kittymc_macros::Packet; 4 | use crate::error::KittyMCError; 5 | 6 | #[derive(PartialEq, Debug, Clone)] 7 | pub struct SlotData { 8 | pub id: u16, // 0xffff is empty 9 | pub item_count: u8, 10 | pub item_damage: u16, 11 | pub nbt: Option, 12 | } 13 | 14 | impl Default for SlotData { 15 | fn default() -> Self { 16 | SlotData { 17 | id: u16::MAX, 18 | item_count: 0, 19 | item_damage: 0, 20 | nbt: None, 21 | } 22 | } 23 | } 24 | 25 | impl SlotData { 26 | pub fn write(&self, data: &mut Vec) { 27 | write_u16(data, self.id); 28 | if self.id != u16::MAX { 29 | write_u8(data, self.item_count); 30 | write_u16(data, self.item_damage); 31 | } 32 | } 33 | 34 | pub fn read(data: &mut &[u8], size: &mut usize) -> Result { 35 | let block_id = read_u16(data, size)?; 36 | let mut item_count: u8 = 0; 37 | let mut item_damage: u16 = 0; 38 | if block_id != u16::MAX { 39 | item_count = read_u8(data, size)?; 40 | item_damage = read_u16(data, size)?; 41 | } 42 | let nbt = read_nbt(data, size).ok(); 43 | 44 | Ok(Self { 45 | id: block_id, 46 | item_count, 47 | item_damage, 48 | nbt, 49 | }) 50 | } 51 | } 52 | 53 | #[derive(PartialEq, Debug, Clone, Packet)] 54 | pub struct WindowItemsPacket { 55 | window_id: u8, 56 | slot_data: Vec, 57 | } 58 | 59 | impl Default for WindowItemsPacket { 60 | fn default() -> Self { 61 | WindowItemsPacket { 62 | window_id: 0, 63 | slot_data: vec![SlotData::default(); 45], 64 | } 65 | } 66 | } 67 | 68 | impl SerializablePacket for WindowItemsPacket { 69 | fn serialize(&self) -> Vec { 70 | let mut packet = vec![]; 71 | 72 | write_u8(&mut packet, self.window_id); 73 | write_i16(&mut packet, self.slot_data.len() as i16); 74 | for slot in &self.slot_data { 75 | slot.write(&mut packet); 76 | } 77 | 78 | wrap_packet(&mut packet, Self::id()); 79 | 80 | packet 81 | } 82 | 83 | fn id() -> u32 { 84 | 0x14 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /kittymc_lib/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::array::TryFromSliceError; 3 | use std::io; 4 | use std::string::FromUtf8Error; 5 | use savefile::SavefileError; 6 | use thiserror::Error; 7 | use crate::subtypes::Location; 8 | 9 | #[derive(Error, Debug)] 10 | pub enum KittyMCError { 11 | #[error("packet type hasn't been implemented")] 12 | NotImplemented(usize, usize), // packet_id packet_len 13 | #[error("failed to deserialize packet")] 14 | DeserializationError, 15 | #[error("failed to decode string in packet")] 16 | StringDecodeError(#[from] FromUtf8Error), 17 | #[error("not enough data: {0}<{1}")] 18 | NotEnoughData(usize, usize), // Actual, Required 19 | #[error("more data than was expected: {0}>{1}")] 20 | TooMuchData(usize, usize), // Actual, Required 21 | #[error("{0}")] 22 | IoError(#[from] io::Error), 23 | #[error("{0}")] 24 | JsonError(#[from] serde_json::Error), 25 | #[error("{0}")] 26 | ByteConversionError(#[from] TryFromSliceError), 27 | #[error("{0}")] 28 | UuidConversionError(#[from] uuid::Error), 29 | #[error("OOMFIE happened :< : {0}")] 30 | OomfieError(&'static str), 31 | #[error("The bridge between the client and the server had an interruption")] 32 | ServerBridgeError, 33 | #[error("Still polling. Wait.")] 34 | Waiting, 35 | #[error("The client has disconnected")] 36 | Disconnected, 37 | #[error("The client version mismatched the server version")] 38 | VersionMissmatch, 39 | #[error("Data couldn't be decompressed properly")] 40 | DecompressionError, // No #[from] because DecompressError is stupip 41 | #[error("Couldn't deserialize type \"{0}\". Needed {1} bytes, got {2}")] 42 | NotEnoughBytesToDeserialize(&'static str, usize, usize), 43 | #[error("Couldn't deserialize variable type \"{0}\"")] 44 | VarDeserializationError(&'static str), 45 | #[error("The packet length was smaller than the header")] 46 | PacketLengthTooSmall, 47 | #[error("The packet length was invalid")] 48 | InvalidPacketLength, 49 | #[error("Zlib Decompression failed with error: {0}")] 50 | ZlibDecompressionError(miniz_oxide::inflate::DecompressError), 51 | #[error("The decompressed packet size was different than previously announced. Assuming corruption. {0} != {1}" 52 | )] 53 | InvalidDecompressedPacketLength(usize, usize), // Announced, Actual 54 | #[error("Thread exited unexpectedly: {0:?}")] 55 | ThreadError(Box), 56 | #[error("The requested client was not found")] 57 | ClientNotFound, 58 | #[error("The lock couldn't be locked")] 59 | LockPoisonError, 60 | #[error("The requested chunk position at {0} is invalid.")] 61 | InvalidChunk(Location), 62 | #[error("The requested block position at {0} is invalid.")] 63 | InvalidBlock(Location), 64 | #[error("Couldn't parse NBT data")] 65 | NBTError(#[from] fastnbt::error::Error), 66 | #[error("The requested player could not be found")] 67 | PlayerNotFound, 68 | #[error("The requested inventory slot {0} was empty")] 69 | InventorySlotEmpty(i16), 70 | #[error("{0}")] 71 | SaveFileError(#[from] SavefileError), 72 | } 73 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/client_settings_04.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_bool, read_length_prefixed_string, read_u8, read_varint_u32, write_u8, SerializablePacket, 4 | }; 5 | use crate::packets::Packet; 6 | use bitflags::bitflags; 7 | use kittymc_macros::Packet; 8 | 9 | #[repr(u32)] 10 | #[derive(PartialEq, Debug, Clone)] 11 | pub enum ChatMode { 12 | Enabled = 0, 13 | CommandsOnly = 1, 14 | Hidden = 2, 15 | Unknown = 0xFFFF_FFFF, 16 | } 17 | 18 | impl From for ChatMode { 19 | fn from(value: u32) -> Self { 20 | match value { 21 | 0 => ChatMode::Enabled, 22 | 1 => ChatMode::CommandsOnly, 23 | 2 => ChatMode::Hidden, 24 | _ => ChatMode::Unknown, 25 | } 26 | } 27 | } 28 | 29 | bitflags! { 30 | #[repr(transparent)] 31 | #[derive(PartialEq, Debug, Clone, Packet)] 32 | pub struct DisplayedSkinParts : u8 { 33 | const cape = 0x01; 34 | const jacket = 0x02; 35 | const left_sleeve = 0x04; 36 | const right_sleeve = 0x08; 37 | const left_pants_leg = 0x10; 38 | const right_pants_leg = 0x20; 39 | const hat = 0x40; 40 | } 41 | } 42 | 43 | impl DisplayedSkinParts { 44 | pub fn write(&self, buffer: &mut Vec) { 45 | write_u8(buffer, self.bits()); 46 | } 47 | } 48 | 49 | #[repr(u32)] 50 | #[derive(PartialEq, Debug, Clone, Packet)] 51 | pub enum Hand { 52 | Left = 0, 53 | Right = 1, 54 | Unknown = 0xFFFF_FFFF, 55 | } 56 | 57 | impl From for Hand { 58 | fn from(value: u32) -> Self { 59 | match value { 60 | 0 => Hand::Left, 61 | 1 => Hand::Right, 62 | _ => Hand::Unknown, 63 | } 64 | } 65 | } 66 | 67 | #[derive(PartialEq, Debug, Clone, Packet)] 68 | pub struct ClientSettingsPacket { 69 | pub locale: String, 70 | pub view_distance: u8, 71 | pub chat_mode: ChatMode, 72 | pub chat_colors: bool, 73 | pub displayed_skin_parts: DisplayedSkinParts, 74 | pub main_hand: Hand, 75 | } 76 | 77 | impl SerializablePacket for ClientSettingsPacket { 78 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 79 | let mut size = 0; 80 | 81 | let locale = read_length_prefixed_string(&mut data, &mut size)?; 82 | let view_distance = read_u8(&mut data, &mut size)?; 83 | let chat_mode = read_varint_u32(&mut data, &mut size)?.into(); 84 | let chat_colors = read_bool(&mut data, &mut size)?; 85 | let displayed_skin_parts = DisplayedSkinParts::from_bits(read_u8(&mut data, &mut size)?) 86 | .ok_or(KittyMCError::DeserializationError)?; 87 | let main_hand = read_varint_u32(&mut data, &mut size)?.into(); 88 | 89 | Ok(( 90 | size, 91 | Packet::ClientSettings(ClientSettingsPacket { 92 | locale, 93 | view_distance, 94 | chat_mode, 95 | chat_colors, 96 | displayed_skin_parts, 97 | main_hand, 98 | }), 99 | )) 100 | } 101 | 102 | fn id() -> u32 { 103 | 4 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod animation_06; 2 | pub mod block_break_animation_08; 3 | pub mod block_change_0b; 4 | pub mod chat_message_0f; 5 | pub mod chunk_data_20; 6 | pub mod disconnect_1a; 7 | pub mod entity_head_look_36; 8 | pub mod entity_look_28; 9 | pub mod entity_relative_move_26; 10 | pub mod entity_status_1b; 11 | pub mod join_game_23; 12 | pub mod keep_alive_1f; 13 | pub mod map_chunk_bulk_26; 14 | pub mod player_abilities_2c; 15 | pub mod player_list_item_2e; 16 | pub mod player_position_and_look_2f; 17 | pub mod server_difficulty_0d; 18 | pub mod server_held_item_change_3a; 19 | pub mod server_plugin_message_18; 20 | pub mod spawn_player_05; 21 | pub mod spawn_position_46; 22 | pub mod time_update_47; 23 | pub mod unload_chunk_1d; 24 | pub mod unlock_recipes_31; 25 | pub mod window_items_14; 26 | pub mod entity_metadata_3c; 27 | pub mod destroy_entities_32; 28 | 29 | pub use block_break_animation_08::BlockBreakAnimationPacket; 30 | pub use block_change_0b::BlockChangePacket; 31 | pub use chat_message_0f::ClientChatMessagePacket; 32 | pub use chunk_data_20::ChunkDataPacket; 33 | pub use entity_head_look_36::EntityHeadLookPacket; 34 | pub use entity_look_28::EntityLookPacket; 35 | pub use entity_relative_move_26::EntityRelativeMovePacket; 36 | pub use entity_status_1b::EntityStatusPacket; 37 | pub use join_game_23::JoinGamePacket; 38 | pub use keep_alive_1f::ServerKeepAlivePacket; 39 | pub use map_chunk_bulk_26::MapChunkBulkPacket; 40 | pub use player_abilities_2c::PlayerAbilitiesPacket; 41 | pub use player_list_item_2e::PlayerListItemPacket; 42 | pub use player_position_and_look_2f::ServerPlayerPositionAndLookPacket; 43 | pub use server_difficulty_0d::ServerDifficultyPacket; 44 | pub use server_held_item_change_3a::ServerHeldItemChangePacket; 45 | pub use server_plugin_message_18::ServerPluginMessagePacket; 46 | pub use spawn_player_05::SpawnPlayerPacket; 47 | pub use spawn_position_46::SpawnPositionPacket; 48 | pub use time_update_47::TimeUpdatePacket; 49 | pub use unload_chunk_1d::UnloadChunkPacket; 50 | pub use unlock_recipes_31::UnlockRecipesPacket; 51 | pub use window_items_14::WindowItemsPacket; 52 | pub use entity_metadata_3c::EntityMetadataPacket; 53 | pub use destroy_entities_32::DestroyEntitiesPacket; 54 | 55 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 56 | pub enum GameMode { 57 | Survival = 0, 58 | Creative = 1, 59 | Adventure = 2, 60 | Spectator = 3, 61 | SurvivalH = 0 | 8, 62 | CreativeH = 1 | 8, 63 | AdventureH = 2 | 8, 64 | SpectatorH = 3 | 8, 65 | } 66 | 67 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 68 | pub enum Dimension { 69 | Nether = -1, 70 | Overworld = 0, 71 | End = 1, 72 | } 73 | 74 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 75 | pub enum Difficulty { 76 | Peaceful = 0, 77 | Easy = 1, 78 | Normal = 2, 79 | Hard = 3, 80 | } 81 | 82 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 83 | pub enum LevelType { 84 | Default, 85 | Flat, 86 | LargeBiomes, 87 | Amplified, 88 | Default11, 89 | } 90 | 91 | impl LevelType { 92 | pub fn as_str(&self) -> &'static str { 93 | match self { 94 | LevelType::Default => "default", 95 | LevelType::Flat => "flat", 96 | LevelType::LargeBiomes => "largeBiomes", 97 | LevelType::Amplified => "amplified", 98 | LevelType::Default11 => "default_1_1", 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /kittymc_server/src/chunking/chunk_generator.rs: -------------------------------------------------------------------------------- 1 | use crate::chunking::chunk_manager::{ 2 | make_chunk_file_path, ChunkPriority, SharedChunk, SharedChunkList, SharedQueue, 3 | }; 4 | use crate::chunking::increasing_ticker::IncreasingTicker; 5 | use kittymc_lib::error::KittyMCError; 6 | use kittymc_lib::packets::client::play::chunk_data_20::{Chunk, DEFAULT_FLAT_CHUNK, DEFAULT_FLAT_CHUNK_2}; 7 | use kittymc_lib::subtypes::ChunkPosition; 8 | use log::{debug, error}; 9 | use std::path::Path; 10 | use std::sync::RwLock; 11 | 12 | pub struct ChunkGenerator { 13 | collector: SharedChunkList, 14 | low_queue: SharedQueue, 15 | middle_queue: SharedQueue, 16 | high_queue: SharedQueue, 17 | ticker: IncreasingTicker, 18 | } 19 | 20 | impl ChunkGenerator { 21 | pub fn entry_thread( 22 | collector: SharedChunkList, 23 | low: SharedQueue, 24 | mid: SharedQueue, 25 | high: SharedQueue, 26 | ) { 27 | let mut gen = ChunkGenerator { 28 | collector, 29 | low_queue: low, 30 | middle_queue: mid, 31 | high_queue: high, 32 | ticker: IncreasingTicker::default(), 33 | }; 34 | 35 | gen.run(); 36 | } 37 | 38 | pub fn run(&mut self) { 39 | loop { 40 | self.bite_queue(); 41 | self.ticker.wait_for_next_tick(); 42 | } 43 | } 44 | 45 | fn bite_queue(&mut self) { 46 | if self.bite_specific_queue(ChunkPriority::HIGH) 47 | || self.bite_specific_queue(ChunkPriority::MID) 48 | || self.bite_specific_queue(ChunkPriority::LOW) 49 | { 50 | return; 51 | } 52 | } 53 | 54 | fn bite_specific_queue(&mut self, queue: ChunkPriority) -> bool { 55 | let mut queue = match queue { 56 | ChunkPriority::HIGH => &self.high_queue, 57 | ChunkPriority::MID => &self.middle_queue, 58 | ChunkPriority::LOW => &self.low_queue, 59 | } 60 | .write() 61 | .unwrap(); 62 | 63 | let chunk_pos = queue.pop_front(); 64 | 65 | drop(queue); 66 | 67 | if let Some(chunk_pos) = chunk_pos { 68 | let file_path = make_chunk_file_path(&chunk_pos); 69 | let exists = file_path.exists(); 70 | let chunk_res = match exists { 71 | true => self.load_chunk(&file_path), 72 | false => Ok(self.start_generation(&chunk_pos)), 73 | }; 74 | 75 | let chunk = match chunk_res { 76 | Ok(chunk) => chunk, 77 | Err(e) => { 78 | error!("Failed to load chunk: {e}"); 79 | return true; 80 | } 81 | }; 82 | 83 | self.collector 84 | .write() 85 | .unwrap() 86 | .insert(chunk_pos, SharedChunk::new(RwLock::new(chunk))); 87 | 88 | return true; 89 | } 90 | 91 | false 92 | } 93 | 94 | pub fn load_chunk(&mut self, file_path: &Path) -> Result, KittyMCError> { 95 | debug!("Loaded chunk from file"); 96 | Ok(Chunk::load_from(file_path)?) 97 | } 98 | 99 | pub fn start_generation(&mut self, chunk_pos: &ChunkPosition) -> Box { 100 | self.ticker.reset(); 101 | self.generate(chunk_pos) 102 | } 103 | 104 | pub fn generate(&mut self, chunk_pos: &ChunkPosition) -> Box { 105 | if chunk_pos.chunk_z().abs() % 2 == 1 { 106 | DEFAULT_FLAT_CHUNK.clone() 107 | } else { 108 | DEFAULT_FLAT_CHUNK_2.clone() 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/server/play/player_digging_14.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::packet_serialization::{ 3 | read_block_location, read_u8, read_varint_u32, write_block_location, write_u8, 4 | write_varint_u32, SerializablePacket, 5 | }; 6 | use crate::packets::{wrap_packet, Packet}; 7 | use crate::subtypes::Location; 8 | use kittymc_macros::Packet; 9 | 10 | #[derive(PartialEq, Debug, Clone, Copy)] 11 | pub enum PlayerDiggingStatus { 12 | StartedDigging, 13 | CancelledDigging, 14 | FinishedDigging, 15 | DropItemStack, 16 | DropItem, 17 | ShootArrowFishEating, 18 | SwapItemInHand, 19 | Unknown, 20 | } 21 | 22 | impl From for PlayerDiggingStatus { 23 | fn from(value: u32) -> Self { 24 | match value { 25 | 0 => PlayerDiggingStatus::StartedDigging, 26 | 1 => PlayerDiggingStatus::CancelledDigging, 27 | 2 => PlayerDiggingStatus::FinishedDigging, 28 | 3 => PlayerDiggingStatus::DropItemStack, 29 | 4 => PlayerDiggingStatus::DropItem, 30 | 5 => PlayerDiggingStatus::ShootArrowFishEating, 31 | 6 => PlayerDiggingStatus::SwapItemInHand, 32 | _ => PlayerDiggingStatus::Unknown, 33 | } 34 | } 35 | } 36 | 37 | #[derive(PartialEq, Debug, Clone, Copy)] 38 | pub enum BlockFace { 39 | Top, 40 | Bottom, 41 | North, 42 | South, 43 | West, 44 | East, 45 | Unknown, 46 | } 47 | 48 | impl BlockFace { 49 | pub fn as_offset(&self) -> Location { 50 | match self { 51 | BlockFace::Top => Location::new(0., 1., 0.), 52 | BlockFace::Bottom => Location::new(0., -1., 0.), 53 | BlockFace::North => Location::new(0., 0., 1.), 54 | BlockFace::South => Location::new(0., 0., -1.), 55 | BlockFace::West => Location::new(1., 0., 0.), 56 | BlockFace::East => Location::new(-1., 0., 0.), 57 | BlockFace::Unknown => Location::new(0., 0., 0.), 58 | } 59 | } 60 | } 61 | 62 | impl From for BlockFace { 63 | fn from(value: u8) -> Self { 64 | Self::from(value as u32) 65 | } 66 | } 67 | 68 | impl From for BlockFace { 69 | fn from(value: u32) -> Self { 70 | match value { 71 | 0 => BlockFace::Top, 72 | 1 => BlockFace::Bottom, 73 | 2 => BlockFace::North, 74 | 3 => BlockFace::South, 75 | 4 => BlockFace::West, 76 | 5 => BlockFace::East, 77 | _ => BlockFace::Unknown, 78 | } 79 | } 80 | } 81 | 82 | #[derive(PartialEq, Debug, Clone, Packet)] 83 | pub struct PlayerDiggingPacket { 84 | pub status: PlayerDiggingStatus, 85 | pub location: Location, 86 | pub face: BlockFace, 87 | } 88 | 89 | impl SerializablePacket for PlayerDiggingPacket { 90 | fn deserialize(mut data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 91 | let mut size = 0; 92 | 93 | let status = read_varint_u32(&mut data, &mut size)?.into(); 94 | let location = read_block_location(&mut data, &mut size)?; 95 | let face = read_u8(&mut data, &mut size)?.into(); 96 | 97 | Ok(( 98 | size, 99 | Packet::PlayerDigging(PlayerDiggingPacket { 100 | status, 101 | location, 102 | face, 103 | }), 104 | )) 105 | } 106 | 107 | fn serialize(&self) -> Vec { 108 | let mut packet = vec![]; 109 | 110 | write_varint_u32(&mut packet, self.status as u32); 111 | write_block_location(&mut packet, &self.location); 112 | write_u8(&mut packet, self.face as u8); 113 | 114 | wrap_packet(&mut packet, Self::id()); 115 | 116 | packet 117 | } 118 | 119 | fn id() -> u32 { 120 | 0x14 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /kittymc_server/src/player.rs: -------------------------------------------------------------------------------- 1 | use crate::client::ClientInfo; 2 | use kittymc_lib::packets::client::play::GameMode; 3 | use kittymc_lib::subtypes::{Direction, Location2}; 4 | use uuid::Uuid; 5 | use kittymc_lib::subtypes::metadata::{EntityMetaState, PlayerMetadata}; 6 | use kittymc_lib::utils::is_cool; 7 | use crate::inventory::Inventory; 8 | 9 | #[derive(Debug)] 10 | pub struct Player { 11 | uuid: Uuid, 12 | username: String, 13 | entity_id: i32, 14 | position: Location2, 15 | direction: Direction, 16 | last_position: Location2, 17 | last_direction: Direction, 18 | game_mode: GameMode, 19 | pub inventory: Inventory, 20 | current_slot: i16, 21 | state: PlayerMetadata, 22 | } 23 | 24 | #[allow(dead_code)] 25 | impl Player { 26 | pub fn from_client_info( 27 | client_info: ClientInfo, 28 | id: i32, 29 | position: &Location2, 30 | direction: &Direction, 31 | game_mode: GameMode, 32 | ) -> Self { 33 | Self::new( 34 | client_info.uuid, 35 | client_info.username, 36 | id, 37 | position, 38 | direction, 39 | game_mode, 40 | ) 41 | } 42 | 43 | pub fn new( 44 | uuid: Uuid, 45 | username: String, 46 | id: i32, 47 | position: &Location2, 48 | direction: &Direction, 49 | game_mode: GameMode, 50 | ) -> Self { 51 | Self { 52 | uuid, 53 | username, 54 | entity_id: id, 55 | position: *position, 56 | direction: *direction, 57 | last_position: *position, 58 | last_direction: *direction, 59 | game_mode, 60 | inventory: Inventory::new(), 61 | current_slot: 0, 62 | state: Default::default(), 63 | } 64 | } 65 | 66 | pub fn uuid(&self) -> &Uuid { 67 | &self.uuid 68 | } 69 | 70 | pub fn name(&self) -> &str { 71 | &self.username 72 | } 73 | 74 | pub fn id(&self) -> i32 { 75 | self.entity_id 76 | } 77 | 78 | pub fn position(&self) -> &Location2 { 79 | &self.position 80 | } 81 | 82 | pub fn set_position(&mut self, position: &Location2) { 83 | self.last_position = self.position; 84 | self.position = *position; 85 | } 86 | 87 | pub fn direction(&self) -> &Direction { 88 | &self.direction 89 | } 90 | 91 | pub fn set_direction(&mut self, direction: &Direction) { 92 | self.last_direction = self.direction; 93 | self.direction = *direction; 94 | } 95 | 96 | pub fn last_position(&self) -> &Location2 { 97 | &self.last_position 98 | } 99 | 100 | pub fn last_direction(&self) -> &Direction { 101 | &self.last_direction 102 | } 103 | 104 | pub fn game_mode(&self) -> GameMode { 105 | self.game_mode 106 | } 107 | 108 | pub fn set_game_mode(&mut self, game_mode: GameMode) { 109 | self.game_mode = game_mode; 110 | } 111 | 112 | pub fn set_current_slot(&mut self, slot: i16) { 113 | self.current_slot = slot; 114 | } 115 | 116 | pub fn current_slot(&self) -> i16 { 117 | self.current_slot 118 | } 119 | 120 | pub fn current_hotbar_slot(&self) -> i16 { 121 | self.current_slot + 36 122 | } 123 | 124 | pub fn is_cool(&self) -> bool { 125 | is_cool(&self.username) 126 | } 127 | 128 | pub fn get_state(&self) -> &PlayerMetadata { 129 | &self.state 130 | } 131 | 132 | pub fn set_crouching(&mut self, is_crouching: bool) { 133 | self.state.living.entity.meta_state.set(EntityMetaState::crouched, is_crouching); 134 | } 135 | 136 | pub fn set_sprinting(&mut self, is_sprinting: bool) { 137 | self.state.living.entity.meta_state.set(EntityMetaState::sprinting, is_sprinting); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /kittymc_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, TokenStreamExt}; 3 | use syn::{parse_macro_input, Data, DeriveInput, Fields}; 4 | 5 | #[proc_macro_derive(PacketHelperFuncs)] 6 | pub fn derive_packet_helper_funcs(input: TokenStream) -> TokenStream { 7 | let input: DeriveInput = parse_macro_input!(input); 8 | 9 | let name = input.ident; 10 | 11 | let data_enum = match input.data { 12 | Data::Enum(data_enum) => data_enum, 13 | _ => { 14 | return syn::Error::new_spanned( 15 | name, 16 | "SerializePacketFunc can only be derived for enums", 17 | ) 18 | .to_compile_error() 19 | .into(); 20 | } 21 | }; 22 | 23 | let variant_arms = data_enum.variants.iter().map(|variant| { 24 | let vname = &variant.ident; 25 | let unnamed_fields = match &variant.fields { 26 | Fields::Unnamed(fields) if !fields.unnamed.is_empty() => fields, 27 | _ => { 28 | return Err(syn::Error::new_spanned( 29 | vname, 30 | "Packet Enum Variant MUST have an inner struct that does serialization and deserialization.", 31 | ).to_compile_error().into()); 32 | } 33 | }; 34 | 35 | let inner_field_ty = (&unnamed_fields.unnamed[0]).ty.clone(); 36 | 37 | Ok((quote! { 38 | Self::#vname(inner) => inner.serialize(), 39 | }, 40 | quote! { 41 | Self::#vname(_) => #inner_field_ty::name(), 42 | }, 43 | quote! { 44 | Self::#vname(inner) => #inner_field_ty::id(), 45 | })) 46 | }); 47 | 48 | let results: Vec< 49 | Result< 50 | ( 51 | proc_macro2::TokenStream, 52 | proc_macro2::TokenStream, 53 | proc_macro2::TokenStream, 54 | ), 55 | proc_macro2::TokenStream, 56 | >, 57 | > = variant_arms.clone().collect(); 58 | 59 | if results.iter().any(|v| v.is_err()) { 60 | let mut error_collector = proc_macro2::TokenStream::new(); 61 | error_collector.append_all( 62 | results 63 | .into_iter() 64 | .filter(|res| res.is_err()) 65 | .map(|res| res.unwrap_err()), 66 | ); 67 | return error_collector.into(); 68 | } 69 | 70 | let results = results.into_iter().map(|res| res.unwrap()); 71 | 72 | let mut serializers = vec![]; 73 | let mut names = vec![]; 74 | let mut ids = vec![]; 75 | 76 | for (s, n, i) in results { 77 | serializers.push(s); 78 | names.push(n); 79 | ids.push(i); 80 | } 81 | 82 | let expanded = quote! { 83 | impl #name { 84 | pub fn serialize(&self) -> Vec { 85 | match self { 86 | #(#serializers)* 87 | } 88 | } 89 | 90 | pub fn name(&self) -> &'static str { 91 | match self { 92 | #(#names)* 93 | } 94 | } 95 | 96 | pub fn id(&self) -> u32 { 97 | match self { 98 | #(#ids)* 99 | } 100 | } 101 | } 102 | }; 103 | 104 | expanded.into() 105 | } 106 | 107 | #[proc_macro_derive(Packet)] 108 | pub fn derive_packet(input: TokenStream) -> TokenStream { 109 | let input: DeriveInput = parse_macro_input!(input); 110 | 111 | let name = input.ident; 112 | let generics = input.generics.params; 113 | 114 | let expanded = quote! { 115 | impl<#generics> crate::packets::packet_serialization::NamedPacket for #name<#generics> { 116 | fn name() -> &'static str { 117 | stringify!(#name) 118 | } 119 | } 120 | }; 121 | 122 | expanded.into() 123 | } 124 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/player_list_item_2e.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::client::play::GameMode; 2 | use crate::packets::packet_serialization::{ 3 | write_bool, write_length_prefixed_string, write_uuid, write_varint_u32, SerializablePacket, 4 | }; 5 | use crate::packets::wrap_packet; 6 | use crate::subtypes::components::TextComponent; 7 | use kittymc_macros::Packet; 8 | use log::warn; 9 | use uuid::Uuid; 10 | 11 | #[derive(PartialEq, Debug, Clone)] 12 | pub struct PlayerListItemProperties { 13 | pub name: String, 14 | pub value: String, 15 | // is_signed: bool, // determined by signature Optional 16 | pub signature: Option, 17 | } 18 | 19 | impl PlayerListItemProperties { 20 | pub fn write(&self, buffer: &mut Vec) { 21 | write_length_prefixed_string(buffer, &self.name); 22 | write_length_prefixed_string(buffer, &self.name); 23 | write_bool(buffer, self.signature.is_some()); 24 | if let Some(sig) = &self.signature { 25 | write_bool(buffer, true); 26 | write_length_prefixed_string(buffer, sig); 27 | } 28 | } 29 | } 30 | 31 | #[derive(PartialEq, Debug, Clone)] 32 | pub enum PlayerListItemAction { 33 | AddPlayer { 34 | name: String, 35 | properties: Vec, 36 | game_mode: GameMode, 37 | ping: u32, 38 | // has_display_name: bool, // determined by display_name Optional 39 | display_name: Option, 40 | }, 41 | UpdateGameMode(GameMode), 42 | UpdateLatency(u32), 43 | UpdateDisplayName(Option), 44 | RemovePlayer, 45 | } 46 | 47 | impl PlayerListItemAction { 48 | pub fn id(&self) -> u32 { 49 | match self { 50 | PlayerListItemAction::AddPlayer { .. } => 0, 51 | PlayerListItemAction::UpdateGameMode(_) => 1, 52 | PlayerListItemAction::UpdateLatency(_) => 2, 53 | PlayerListItemAction::UpdateDisplayName(_) => 3, 54 | PlayerListItemAction::RemovePlayer => 4, 55 | } 56 | } 57 | 58 | pub fn write(&self, buffer: &mut Vec) { 59 | match self { 60 | PlayerListItemAction::AddPlayer { 61 | name, 62 | properties, 63 | game_mode, 64 | ping, 65 | display_name, 66 | } => { 67 | write_length_prefixed_string(buffer, name); 68 | write_varint_u32(buffer, properties.len() as u32); 69 | for property in properties { 70 | property.write(buffer); 71 | } 72 | write_varint_u32(buffer, *game_mode as u32); 73 | write_varint_u32(buffer, *ping); 74 | write_bool(buffer, display_name.is_some()); 75 | if let Some(display) = display_name { 76 | display.write(buffer); 77 | } 78 | } 79 | PlayerListItemAction::UpdateGameMode(game_mode) => { 80 | write_varint_u32(buffer, *game_mode as u32); 81 | } 82 | PlayerListItemAction::UpdateLatency(latency) => { 83 | write_varint_u32(buffer, *latency); 84 | } 85 | PlayerListItemAction::UpdateDisplayName(display_name) => { 86 | write_bool(buffer, display_name.is_some()); 87 | if let Some(display) = display_name { 88 | display.write(buffer); 89 | } 90 | } 91 | PlayerListItemAction::RemovePlayer => (), 92 | } 93 | } 94 | } 95 | 96 | #[derive(PartialEq, Debug, Clone, Packet)] 97 | pub struct PlayerListItemPacket { 98 | pub actions: Vec<(Uuid, PlayerListItemAction)>, 99 | } 100 | 101 | impl Default for PlayerListItemPacket { 102 | fn default() -> Self { 103 | PlayerListItemPacket { 104 | actions: vec![( 105 | Uuid::new_v4(), 106 | PlayerListItemAction::AddPlayer { 107 | name: "meow".to_string(), 108 | properties: vec![], 109 | game_mode: GameMode::Creative, 110 | ping: 5, 111 | display_name: None, 112 | }, 113 | )], 114 | } 115 | } 116 | } 117 | 118 | impl SerializablePacket for PlayerListItemPacket { 119 | fn serialize(&self) -> Vec { 120 | let mut packet = vec![]; 121 | 122 | if !self.actions.is_empty() { 123 | let first = &self.actions[0]; 124 | for action in &self.actions { 125 | if first.1.id() != action.1.id() { 126 | warn!("Server tried to serialize a packet with different action types. This is not possible. Sending default packet"); 127 | return vec![3, 0x2E, 0, 0]; 128 | } 129 | } 130 | } else { 131 | warn!("Server tried sending an empty PlayerListItem Packet for some reason"); 132 | return vec![3, 0x2E, 0, 0]; 133 | } 134 | 135 | write_varint_u32(&mut packet, self.actions[0].1.id()); 136 | write_varint_u32(&mut packet, self.actions.len() as u32); 137 | for (uuid, action) in &self.actions { 138 | write_uuid(&mut packet, uuid); 139 | action.write(&mut packet); 140 | } 141 | 142 | wrap_packet(&mut packet, Self::id()); 143 | 144 | packet 145 | } 146 | 147 | fn id() -> u32 { 148 | 0x2E 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /kittymc_server/src/inventory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq)] 4 | pub struct ItemStack { 5 | pub item_id: u16, 6 | pub damage: u16, 7 | pub count: u8, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct Inventory { 12 | slots: HashMap, 13 | } 14 | 15 | #[allow(dead_code)] 16 | impl Inventory { 17 | pub fn new() -> Self { 18 | Inventory { 19 | slots: HashMap::new(), 20 | } 21 | } 22 | 23 | pub fn add_item(&mut self, item_id: u16, mut count: u8) -> u8 { 24 | // First pass: fill existing stacks 25 | for slot_num in 0..=35 { 26 | if let Some(existing) = self.slots.get_mut(&slot_num) { 27 | if existing.item_id == item_id { 28 | let available = 64 - existing.count; 29 | if available > 0 { 30 | let add = count.min(available); 31 | existing.count += add; 32 | count -= add; 33 | if count == 0 { 34 | return 0; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Second pass: fill empty slots 42 | for slot_num in 0..=35 { 43 | if !self.slots.contains_key(&slot_num) { 44 | let add = count.min(64); 45 | self.slots.insert(slot_num, ItemStack { item_id, count: add, damage: 0 }); 46 | count -= add; 47 | if count == 0 { 48 | return 0; 49 | } 50 | } 51 | } 52 | 53 | count 54 | } 55 | 56 | pub fn remove_item(&mut self, item_id: u16, mut count: u8) -> u8 { 57 | let mut remove_ids = vec![]; 58 | 59 | for (id, slot) in &mut self.slots.iter_mut() { 60 | if slot.item_id == item_id { 61 | if slot.count <= count { 62 | count -= slot.count; 63 | remove_ids.push(*id); 64 | } else { 65 | slot.count -= count; 66 | count = 0; 67 | } 68 | if count == 0 { 69 | break; 70 | } 71 | } 72 | } 73 | 74 | for id in remove_ids { 75 | self.slots.remove(&id); 76 | } 77 | 78 | count 79 | } 80 | 81 | pub fn get_item_count(&self, item_id: u16) -> u32 { 82 | self.slots 83 | .iter() 84 | .filter_map(|(_, slot)| { 85 | if slot.item_id == item_id { 86 | Some(slot.count as u32) 87 | } else { 88 | None 89 | } 90 | }) 91 | .sum() 92 | } 93 | 94 | pub fn get_slot(&self, index: i16) -> Option { 95 | self.slots.get(&index).cloned() 96 | } 97 | 98 | pub fn set_slot( 99 | &mut self, 100 | index: i16, 101 | item: Option, 102 | ) { 103 | // TODO: Maybe filter out invalid slots? 104 | match item { 105 | None => self.slots.remove(&index), 106 | Some(item) => self.slots.insert(index, item), 107 | }; 108 | } 109 | 110 | pub fn find_item_slot(&self, item_id: u16) -> Option { 111 | self.slots 112 | .iter() 113 | .find(|(_, slot)| slot.item_id == item_id) 114 | .map(|(i, _)| *i) 115 | } 116 | 117 | pub fn is_full(&self) -> bool { 118 | for slot_num in 0..=35 { 119 | if !self.slots.contains_key(&slot_num) { 120 | return false; 121 | } 122 | } 123 | 124 | true 125 | } 126 | 127 | pub fn is_empty(&self) -> bool { 128 | for slot_num in 0..=35 { 129 | if self.slots.contains_key(&slot_num) { 130 | return false; 131 | } 132 | } 133 | 134 | true 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use super::*; 141 | 142 | #[test] 143 | fn new_inventory_is_empty() { 144 | let inv = Inventory::new(); 145 | for i in 0..256 { 146 | assert_eq!(inv.get_slot(i), None); 147 | } 148 | } 149 | 150 | #[test] 151 | fn add_and_remove_items() { 152 | let mut inv = Inventory::new(); 153 | assert_eq!(inv.add_item(1, 64), 0); 154 | assert_eq!(inv.get_item_count(1), 64); 155 | assert_eq!(inv.add_item(1, 65), 0); 156 | assert_eq!(inv.remove_item(1, 60), 0); 157 | assert_eq!(inv.get_item_count(1), 64 + 65 - 60); 158 | } 159 | 160 | #[test] 161 | fn slot_management() { 162 | let mut inv = Inventory::new(); 163 | inv.set_slot(0, Some(ItemStack { item_id: 1, damage: 0, count: 10 })); 164 | inv.set_slot(255, Some(ItemStack { item_id: 2, damage: 0, count: 20 })); 165 | 166 | assert_eq!( 167 | inv.get_slot(0), 168 | Some(ItemStack { item_id: 1, damage: 0, count: 10 }) 169 | ); 170 | assert_eq!( 171 | inv.get_slot(255), 172 | Some(ItemStack { item_id: 2, damage: 0, count: 20 }) 173 | ); 174 | } 175 | 176 | #[test] 177 | fn inventory_capacity() { 178 | let mut inv = Inventory::new(); 179 | for i in 0..=35 { 180 | inv.set_slot(i, Some(ItemStack { item_id: 1, damage: 0, count: 64 })); 181 | } 182 | assert!(inv.is_full()); 183 | assert_eq!(inv.add_item(1, 1), 1); 184 | } 185 | } -------------------------------------------------------------------------------- /kittymc_proxy/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{format_err, Context}; 2 | use kittymc_lib::packets::packet_serialization::SerializablePacket; 3 | use kittymc_lib::packets::{CompressionInfo, Packet}; 4 | use kittymc_lib::subtypes::state::State; 5 | use std::net::SocketAddr; 6 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 7 | use tokio::net::{TcpListener, TcpStream}; 8 | use tokio::task::JoinHandle; 9 | 10 | const NO_COMPRESSION: CompressionInfo = CompressionInfo { 11 | enabled: false, 12 | compression_threshold: 0, 13 | }; 14 | 15 | fn modify_client_data(data: &mut Vec, mut n: usize) -> anyhow::Result { 16 | let result = Packet::deserialize(State::Handshake, &data, &NO_COMPRESSION); 17 | 18 | if let Ok((size, mut packet)) = result { 19 | match packet { 20 | Packet::Handshake(ref mut handshake) => { 21 | handshake.server_address = "gommehd.net".to_string(); 22 | let serialized = handshake.serialize(); 23 | let serialized_len = serialized.len(); 24 | data.splice(..n, serialized); 25 | n = serialized_len; 26 | } 27 | _ => (), 28 | } 29 | println!("Client -> Server: Packet of size {size}: {packet:?}"); 30 | return Ok(n); 31 | } 32 | 33 | let result = Packet::deserialize(State::Login, &data, &NO_COMPRESSION); 34 | 35 | if let Ok((size, packet)) = result { 36 | match packet { 37 | _ => (), 38 | } 39 | println!("Client -> Server: Packet of size {size}: {packet:?}"); 40 | return Ok(n); 41 | } 42 | 43 | println!("Couldn't parse packet"); 44 | 45 | Err(format_err!("meow")) 46 | } 47 | 48 | fn modify_server_data(data: &mut Vec, _n: usize) -> anyhow::Result { 49 | let result = Packet::deserialize(State::Handshake, &data, &NO_COMPRESSION); 50 | 51 | if let Ok((size, packet)) = result { 52 | println!("Server -> Client: Packet of size {size}: {packet:?}"); 53 | } 54 | 55 | let result = Packet::deserialize(State::Login, &data, &NO_COMPRESSION); 56 | 57 | if let Ok((size, packet)) = result { 58 | println!("Server -> Client: Packet of size {size}: {packet:?}"); 59 | } 60 | 61 | println!("Couldn't parse packet"); 62 | 63 | Err(format_err!("meow")) 64 | } 65 | 66 | async fn forward_data( 67 | mut reader: TcpStream, 68 | mut writer: TcpStream, 69 | is_client_to_server: bool, 70 | ) -> anyhow::Result<()> { 71 | let mut buffer = vec![0u8; 2048]; 72 | 73 | loop { 74 | let mut n = match reader.read(&mut buffer).await { 75 | Ok(0) => { 76 | // The other side closed the connection 77 | return Ok(()); 78 | } 79 | Ok(n) => n, 80 | Err(e) => return Err(e.into()), 81 | }; 82 | 83 | if is_client_to_server { 84 | n = match modify_client_data(&mut buffer, n) { 85 | Ok(new_size) => new_size, 86 | Err(_) => continue, 87 | } 88 | } else { 89 | n = match modify_server_data(&mut buffer, n) { 90 | Ok(new_size) => new_size, 91 | Err(_) => continue, 92 | } 93 | }; 94 | 95 | writer.write_all(&buffer[..n]).await?; 96 | 97 | buffer.drain(..n); 98 | } 99 | } 100 | 101 | fn get_server_address() -> String { 102 | "gommehd.net".to_string() 103 | } 104 | 105 | async fn client_loop(client: TcpStream, sockaddr: &SocketAddr) -> anyhow::Result<()> { 106 | let server_url = get_server_address(); 107 | let server_addr = (server_url.as_str(), 25565); 108 | let server = TcpStream::connect(server_addr) 109 | .await 110 | .with_context(|| format!("Failed to connect to server at {:?}", server_addr))?; 111 | 112 | println!( 113 | "Established proxy between client {} and server {}", 114 | sockaddr, server_url 115 | ); 116 | 117 | // ugly tokio::TcpStream::try_clone() https://github.com/tokio-rs/tokio-core/issues/198 118 | let std_client = client.into_std()?; 119 | let std_server = server.into_std()?; 120 | let client_for_server = TcpStream::from_std(std_client.try_clone()?)?; 121 | let server_for_client = TcpStream::from_std(std_server.try_clone()?)?; 122 | let client = TcpStream::from_std(std_client)?; 123 | let server = TcpStream::from_std(std_server)?; 124 | 125 | let client_to_server_task = tokio::spawn(async move { 126 | if let Err(e) = forward_data(client_for_server, server, true).await { 127 | eprintln!("Error forwarding client->server: {e}"); 128 | } 129 | }); 130 | 131 | let server_to_client_task = tokio::spawn(async move { 132 | if let Err(e) = forward_data(server_for_client, client, false).await { 133 | eprintln!("Error forwarding client->server: {e}"); 134 | } 135 | }); 136 | 137 | tokio::select! { 138 | _ = client_to_server_task => { Ok(()) } 139 | _ = server_to_client_task => { Ok(()) } 140 | } 141 | } 142 | 143 | async fn new_client_thread(client: TcpStream, sockaddr: SocketAddr) { 144 | match client_loop(client, &sockaddr).await { 145 | Err(e) => eprintln!("Fatal error in client {sockaddr}: {e}"), 146 | Ok(()) => println!("Client {sockaddr} disconnected."), 147 | } 148 | } 149 | 150 | async fn handle_new_client(server: &TcpListener) -> JoinHandle<()> { 151 | let (client, sockaddr) = server.accept().await.expect("Failed to accept"); 152 | println!("Client {sockaddr} connected"); 153 | tokio::spawn(async move { 154 | new_client_thread(client, sockaddr).await; 155 | }) 156 | } 157 | 158 | #[tokio::main] 159 | async fn main() { 160 | let server = TcpListener::bind(("0.0.0.0", 25565)) 161 | .await 162 | .expect("Failed to bind"); 163 | loop { 164 | handle_new_client(&server).await; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /kittymc_lib/tests/type_tests/mod.rs: -------------------------------------------------------------------------------- 1 | use kittymc_lib::subtypes::components::{ 2 | BaseComponent, ClickEvent, Component, HoverEvent, TextComponent, TranslationComponent, 3 | }; 4 | use kittymc_lib::subtypes::Color; 5 | use serde_json::Value; 6 | 7 | #[test] 8 | fn test_click_event_serialize_minimal() { 9 | // Create a minimal ClickEvent with just open_url 10 | let click_evt = ClickEvent::builder() 11 | .open_url("https://example.com") 12 | .build(); 13 | 14 | // Convert to JSON 15 | let json_str = serde_json::to_string(&click_evt).unwrap(); 16 | // Quick JSON check 17 | let as_json = serde_json::from_str::(&json_str).unwrap(); 18 | 19 | // Ensure field is present 20 | assert_eq!( 21 | as_json["open_url"], 22 | serde_json::Value::String("https://example.com".into()) 23 | ); 24 | // Ensure empty fields are omitted 25 | assert!(as_json.get("run_command").is_none()); 26 | assert!(as_json.get("suggest_command").is_none()); 27 | assert!(as_json.get("change_page").is_none()); 28 | } 29 | 30 | #[test] 31 | fn test_click_event_deserialize() { 32 | let incoming = r#" 33 | { 34 | "open_url": "https://example.com", 35 | "run_command": "/hello" 36 | } 37 | "#; 38 | let evt: ClickEvent = serde_json::from_str(incoming).unwrap(); 39 | assert_eq!(evt.open_url, "https://example.com"); 40 | assert_eq!(evt.run_command, "/hello"); 41 | // `change_page` was not provided => should be `None` 42 | assert!(evt.change_page.is_none()); 43 | } 44 | 45 | #[test] 46 | fn test_hover_event_round_trip() { 47 | let hover_evt = HoverEvent::builder().show_text("Hover here").build(); 48 | let serialized = serde_json::to_string(&hover_evt).unwrap(); 49 | let deserialized: HoverEvent = serde_json::from_str(&serialized).unwrap(); 50 | assert_eq!(deserialized.show_text, Some("Hover here".to_string())); 51 | } 52 | 53 | #[test] 54 | fn test_chat_style_default() { 55 | // By default, booleans should be false, and Option fields should be None 56 | let style = BaseComponent::default(); 57 | let serialized = serde_json::to_string(&style).unwrap(); 58 | // Should be an empty object because all fields are default/empty 59 | assert_eq!(serialized, "{}"); 60 | } 61 | 62 | #[test] 63 | fn test_chat_style_with_options() { 64 | let style = BaseComponent::builder() 65 | .bold(true) 66 | .italic(true) 67 | .color(Color::DarkPurple) 68 | .insertion("InsertMe".to_string()) 69 | .build(); 70 | 71 | let serialized = serde_json::to_string(&style).unwrap(); 72 | let as_json = serde_json::from_str::(&serialized).unwrap(); 73 | 74 | // Check presence of fields 75 | assert_eq!(as_json["bold"], true); 76 | assert_eq!(as_json["italic"], true); 77 | assert_eq!(as_json["color"], Value::String("dark_purple".to_string())); 78 | assert_eq!(as_json["insertion"], "InsertMe"); 79 | } 80 | 81 | #[test] 82 | fn test_text_component_serialize() { 83 | let text_comp = TextComponent::builder() 84 | .text("Hello, world!") 85 | .options( 86 | BaseComponent::builder() 87 | .bold(true) 88 | .color(Color::Gray) 89 | .build(), 90 | ) 91 | .build(); 92 | 93 | let serialized = serde_json::to_string(&text_comp).unwrap(); 94 | let as_json = serde_json::from_str::(&serialized).unwrap(); 95 | 96 | // "text" should be present 97 | assert_eq!(as_json["text"], "Hello, world!"); 98 | // Style fields should appear at the same level if flattened 99 | assert_eq!(as_json["bold"], true); 100 | assert_eq!(as_json["color"], Value::String("gray".to_string())); 101 | } 102 | 103 | #[test] 104 | fn test_translation_component_round_trip() { 105 | let trans_comp = TranslationComponent { 106 | translate: "chat.type.text".into(), 107 | with: vec![ 108 | Component::Text(TextComponent::builder().text("Player1").build()), 109 | Component::Text(TextComponent::builder().text("Hello!").build()), 110 | ], 111 | }; 112 | 113 | let serialized = serde_json::to_string(&trans_comp).unwrap(); 114 | let deserialized: TranslationComponent = serde_json::from_str(&serialized).unwrap(); 115 | 116 | assert_eq!(deserialized.translate, "chat.type.text"); 117 | assert_eq!(deserialized.with.len(), 2); 118 | } 119 | 120 | #[test] 121 | fn test_component_enum_serialization() { 122 | let comp_text = Component::Text(TextComponent::builder().text("Just Testing").build()); 123 | let json_text = serde_json::to_string(&comp_text).unwrap(); 124 | // Because of untagged enum, it should look like { "text": "...", "bold": false, ...} 125 | let as_json = serde_json::from_str::(&json_text).unwrap(); 126 | assert_eq!(as_json["text"], "Just Testing"); 127 | } 128 | 129 | #[test] 130 | fn test_component_write_length_prefixed_text() { 131 | let comp_text = Component::Text(TextComponent::builder().text("Prefixed?").build()); 132 | let mut buffer = Vec::new(); 133 | comp_text.write(&mut buffer); 134 | 135 | // The first byte(s) encode the varint length of the JSON, 136 | // then the JSON data in UTF-8. We won't parse the entire buffer, 137 | // but let's at least check it's non-empty and the final part contains "Prefixed?". 138 | assert!(!buffer.is_empty()); 139 | 140 | // A quick approach is to skip the varint (since we know its length is small) 141 | // and check the trailing bytes. The varint might be 1 or 2 bytes depending 142 | // on the JSON length. Let's just do a rough search: 143 | let full_str = String::from_utf8_lossy(&buffer); 144 | assert!(full_str.contains("Prefixed?")); 145 | } 146 | 147 | #[test] 148 | fn test_component_default_join() { 149 | let join_comp = Component::default_join("PlayerXYZ"); 150 | 151 | // Should produce a Text component with extra sub-text 152 | let json_str = serde_json::to_string(&join_comp).unwrap(); 153 | let json_val: serde_json::Value = serde_json::from_str(&json_str).unwrap(); 154 | 155 | // Because it's untagged, we expect top-level fields like "text", "bold", "italic", etc. 156 | // Something like: {"text":"PlayerXYZ","bold":true,"italic":true,"color":"DarkPurple","extra":[...]} 157 | assert_eq!(json_val["text"], "PlayerXYZ"); 158 | assert_eq!(json_val["bold"], true); 159 | assert_eq!(json_val["italic"], true); 160 | assert_eq!(json_val["color"], Value::String("dark_purple".to_string())); 161 | 162 | // Check that extra has at least 1 item 163 | let extra_array = json_val["extra"].as_array().unwrap(); 164 | assert_eq!(extra_array.len(), 1); 165 | 166 | // That item should be a text component with " joined the game" 167 | let joined_text = &extra_array[0]; 168 | assert_eq!(joined_text["text"], " joined the game"); 169 | assert_eq!(joined_text["color"], Value::String("gray".to_string())); 170 | } 171 | -------------------------------------------------------------------------------- /kittymc_server/src/chunking/chunk_manager.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use kittymc_lib::error::KittyMCError; 3 | use kittymc_lib::packets::client::play::chunk_data_20::{BlockStateId, Chunk}; 4 | use kittymc_lib::subtypes::{ChunkPosition, Location}; 5 | use std::collections::{HashMap, VecDeque}; 6 | use std::path::PathBuf; 7 | use std::sync::{Arc, RwLock}; 8 | use std::thread::JoinHandle; 9 | use std::time::Instant; 10 | use crate::chunking::chunk_generator::ChunkGenerator; 11 | use crate::chunking::chunk_unloader::ChunkUnloader; 12 | 13 | pub type SharedChunk = Arc>>; 14 | pub type SharedQueue = Arc>>; 15 | pub type SharedChunkList = Arc>>; 16 | pub type SharedChunkAccessList = Arc>>; 17 | 18 | const GENERATOR_THREADS: usize = 4; 19 | const UNLOADER_THREADS: usize = 1; 20 | 21 | pub enum ChunkPriority { 22 | HIGH, 23 | MID, 24 | LOW, 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct ChunkManager { 29 | loaded_chunks: SharedChunkList, 30 | access_list: SharedChunkAccessList, 31 | 32 | generator_threads: Vec>, 33 | unloader_threads: Vec>, 34 | 35 | high_priority_queue: SharedQueue, 36 | medium_priority_queue: SharedQueue, 37 | low_priority_queue: SharedQueue, 38 | } 39 | 40 | impl ChunkManager { 41 | pub fn new() -> ChunkManager { 42 | let mut manager = ChunkManager { 43 | loaded_chunks: Arc::new(Default::default()), 44 | access_list: Arc::new(Default::default()), 45 | 46 | generator_threads: Vec::new(), 47 | unloader_threads: Vec::new(), 48 | 49 | high_priority_queue: Arc::new(Default::default()), 50 | medium_priority_queue: Arc::new(Default::default()), 51 | low_priority_queue: Arc::new(Default::default()), 52 | }; 53 | 54 | manager.init_threads(); 55 | 56 | manager 57 | } 58 | 59 | fn init_threads(&mut self) { 60 | for _ in 0..GENERATOR_THREADS { 61 | let collector = self.loaded_chunks.clone(); 62 | let high_queue = self.high_priority_queue.clone(); 63 | let medium_queue = self.medium_priority_queue.clone(); 64 | let low_priority_queue = self.low_priority_queue.clone(); 65 | 66 | self.generator_threads.push(std::thread::spawn(|| ChunkGenerator::entry_thread(collector, high_queue, medium_queue, low_priority_queue))); 67 | } 68 | 69 | for _ in 0..UNLOADER_THREADS { 70 | let collector = self.loaded_chunks.clone(); 71 | let access_list = self.access_list.clone(); 72 | 73 | self.unloader_threads.push(std::thread::spawn(|| ChunkUnloader::entry_thread(collector, access_list))); 74 | } 75 | } 76 | 77 | #[allow(dead_code)] 78 | pub fn is_chunk_loaded(&self, pos: &ChunkPosition) -> bool { 79 | let mut pos = pos.clone(); 80 | pos.set_chunk_y(0); 81 | self.tap_chunk(&pos); 82 | self.loaded_chunks.read().unwrap().contains_key(&pos) 83 | } 84 | 85 | pub fn get_chunk_at(&mut self, pos: &ChunkPosition) -> Option { 86 | let mut pos = pos.clone(); 87 | pos.set_chunk_y(0); 88 | self.tap_chunk(&pos); 89 | self.loaded_chunks.read().unwrap().get(&pos).cloned() 90 | } 91 | 92 | #[allow(dead_code)] 93 | pub fn get_chunk_containing_block(&mut self, loc: &Location) -> Option { 94 | let mut pos: ChunkPosition = loc.into(); 95 | pos.set_chunk_y(0); 96 | self.tap_chunk(&pos); 97 | self.loaded_chunks.read().unwrap().get(&pos).cloned() 98 | } 99 | 100 | pub fn is_queued(&self, chunk_pos: &ChunkPosition) -> bool { 101 | let mut chunk_pos = chunk_pos.clone(); 102 | chunk_pos.set_chunk_y(0); 103 | 104 | self.high_priority_queue.read().unwrap().contains(&chunk_pos) || 105 | self.medium_priority_queue.read().unwrap().contains(&chunk_pos) || 106 | self.low_priority_queue.read().unwrap().contains(&chunk_pos) 107 | } 108 | 109 | pub fn request_chunk(&mut self, chunk_pos: &ChunkPosition) -> Option { 110 | let mut chunk_pos = chunk_pos.clone(); 111 | chunk_pos.set_chunk_y(0); 112 | 113 | match self.get_chunk_at(&chunk_pos) { 114 | Some(chunk) => return Some(chunk), 115 | _ => {} 116 | } 117 | if self.is_queued(&chunk_pos) { 118 | return None; 119 | } 120 | 121 | self.high_priority_queue.write().unwrap().push_back(chunk_pos); 122 | 123 | None 124 | } 125 | 126 | #[allow(dead_code)] 127 | pub fn request_chunks_bulk( 128 | &mut self, 129 | chunks: &[ChunkPosition], 130 | ) -> HashMap { 131 | let mut loaded = HashMap::new(); 132 | 133 | for pos in chunks { 134 | match self.request_chunk(pos) { 135 | None => continue, 136 | Some(chunk) => { 137 | loaded.insert(pos.clone(), chunk); 138 | } 139 | } 140 | } 141 | 142 | loaded 143 | } 144 | 145 | #[allow(dead_code)] 146 | pub fn poll_chunks_in_range( 147 | &mut self, 148 | loc: &Location, 149 | radius: u32, 150 | ) -> Option> { 151 | let mut loaded_chunks = HashMap::new(); 152 | let requested_chunks: Vec = 153 | ChunkPosition::iter_xz_circle_in_range(loc, radius as f32).collect(); 154 | let requested_count = requested_chunks.len(); 155 | 156 | for chunk_pos in requested_chunks { 157 | let Some(chunk) = self.request_chunk(&chunk_pos) else { 158 | continue; 159 | }; 160 | 161 | loaded_chunks.insert(chunk_pos, chunk); 162 | } 163 | 164 | if loaded_chunks.len() != requested_count { 165 | return None; 166 | } 167 | 168 | Some(loaded_chunks) 169 | } 170 | 171 | #[allow(dead_code)] 172 | pub fn request_chunks_in_range( 173 | &mut self, 174 | loc: &Location, 175 | radius: u32, 176 | ) -> Vec<(ChunkPosition, SharedChunk)> { 177 | let mut loaded_chunks = Vec::new(); 178 | let requested_chunks: Vec = 179 | ChunkPosition::iter_xz_circle_in_range(loc, radius as f32).collect(); 180 | 181 | for chunk_pos in requested_chunks { 182 | match self.request_chunk(&chunk_pos) { 183 | Some(chunk) => loaded_chunks.push((chunk_pos.clone(), chunk)), 184 | _ => {} 185 | } 186 | } 187 | 188 | loaded_chunks 189 | } 190 | 191 | // This function is to be called anywhere where a chunk should be tapped to keep it in memory. 192 | // This will refresh the access list that is used by the unloader so that it does not unload it 193 | // after a chunk might be cached for 30 seconds 194 | pub fn tap_chunk(&self, pos: &ChunkPosition) { 195 | self.access_list.write().unwrap().insert(pos.clone(), Instant::now()); 196 | } 197 | 198 | pub fn set_block(&mut self, loc: &Location, block_id: BlockStateId) -> Result<(), KittyMCError> { 199 | let chunk = self.get_chunk_containing_block(loc) 200 | .ok_or_else(|| KittyMCError::InvalidChunk(loc.clone()))?; 201 | let mut chunk_lock = chunk.write() 202 | .map_err(|_| KittyMCError::LockPoisonError)?; 203 | 204 | let chunk_pos = ChunkPosition::from(loc); 205 | 206 | let x = (loc.x - chunk_pos.block_x() as f32).floor() as usize; 207 | let y = loc.y.floor() as usize; 208 | let z = (loc.z - chunk_pos.block_z() as f32).floor() as usize; 209 | 210 | chunk_lock.set_block(x, y, z, block_id) 211 | } 212 | } 213 | 214 | pub fn make_chunk_file_path(chunk_pos: &ChunkPosition) -> PathBuf { 215 | format!("world/{}me{}ow{}.kitty", chunk_pos.chunk_x(), chunk_pos.chunk_y(), chunk_pos.chunk_z()).into() 216 | } -------------------------------------------------------------------------------- /kittymc_lib/src/subtypes/components.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::packets::packet_serialization::write_length_prefixed_string; 3 | use crate::subtypes::Color; 4 | use crate::utils::{rainbowize_cool_people, to_mc_rainbow, KITTYMC_TAG}; 5 | use serde::{Deserialize, Serialize}; 6 | use typed_builder::TypedBuilder; 7 | 8 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TypedBuilder)] 9 | pub struct ClickEvent { 10 | #[serde(skip_serializing_if = "String::is_empty", default)] 11 | #[builder(default, setter(into))] 12 | pub open_url: String, 13 | 14 | #[serde(skip_serializing_if = "String::is_empty", default)] 15 | #[builder(default, setter(into))] 16 | pub run_command: String, 17 | 18 | #[serde(skip_serializing_if = "String::is_empty", default)] 19 | #[builder(default, setter(into))] 20 | pub suggest_command: String, 21 | 22 | #[serde(skip_serializing_if = "Option::is_none", default)] 23 | #[builder(setter(strip_option), default)] 24 | pub change_page: Option, 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TypedBuilder)] 28 | pub struct HoverEvent { 29 | #[serde(skip_serializing_if = "Option::is_none", default)] 30 | #[builder(setter(into, strip_option), default)] 31 | pub show_text: Option, 32 | 33 | #[serde(skip_serializing_if = "Option::is_none", default)] 34 | #[builder(setter(strip_option), default)] 35 | pub show_item: Option<()>, // TODO: NBT 36 | 37 | #[serde(skip_serializing_if = "Option::is_none", default)] 38 | #[builder(setter(strip_option), default)] 39 | pub show_entity: Option<()>, // TODO: NBT 40 | } 41 | 42 | fn is_false(b: &bool) -> bool { 43 | !*b 44 | } 45 | 46 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TypedBuilder, Default)] 47 | pub struct BaseComponent { 48 | #[serde(skip_serializing_if = "is_false", default)] 49 | #[builder(default)] 50 | pub bold: bool, 51 | 52 | #[serde(skip_serializing_if = "is_false", default)] 53 | #[builder(default)] 54 | pub italic: bool, 55 | 56 | #[serde(skip_serializing_if = "is_false", default)] 57 | #[builder(default)] 58 | pub underlined: bool, 59 | 60 | #[serde(skip_serializing_if = "is_false", default)] 61 | #[builder(default)] 62 | pub strikethrough: bool, 63 | 64 | #[serde(skip_serializing_if = "is_false", default)] 65 | #[builder(default)] 66 | pub obfuscated: bool, 67 | 68 | #[serde(skip_serializing_if = "Option::is_none", default)] 69 | #[builder(setter(strip_option), default)] 70 | pub color: Option, 71 | 72 | #[serde(skip_serializing_if = "String::is_empty", default)] 73 | #[builder(default)] 74 | pub insertion: String, 75 | 76 | #[serde(skip_serializing_if = "Option::is_none", flatten, default)] 77 | #[builder(setter(strip_option), default)] 78 | pub click_event: Option, 79 | 80 | #[serde(skip_serializing_if = "Option::is_none", flatten, default)] 81 | #[builder(setter(strip_option), default)] 82 | pub hover_event: Option, 83 | 84 | #[serde(skip_serializing_if = "Vec::is_empty", default)] 85 | #[builder(default)] 86 | pub extra: Vec, 87 | } 88 | 89 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TypedBuilder)] 90 | pub struct TextComponent { 91 | #[builder(setter(into), default)] 92 | pub text: String, 93 | #[serde(flatten, default)] 94 | #[builder(default)] 95 | pub options: BaseComponent, 96 | } 97 | 98 | impl TextComponent { 99 | pub fn write(&self, buffer: &mut Vec) { 100 | write_length_prefixed_string( 101 | buffer, 102 | &serde_json::to_string(self).unwrap_or_else(|_| "INVALID".to_string()), 103 | ) 104 | } 105 | } 106 | 107 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TypedBuilder)] 108 | pub struct TranslationComponent { 109 | pub translate: String, 110 | pub with: Vec, 111 | } 112 | 113 | impl TranslationComponent { 114 | pub fn write(&self, buffer: &mut Vec) { 115 | write_length_prefixed_string( 116 | buffer, 117 | &serde_json::to_string(self).unwrap_or_else(|_| "INVALID".to_string()), 118 | ) 119 | } 120 | } 121 | 122 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 123 | #[serde(untagged)] 124 | pub enum Component { 125 | Text(TextComponent), 126 | Translation(TranslationComponent), 127 | KeyBind, // TODO 128 | Score, // TODO 129 | Selector, // TODO 130 | } 131 | 132 | const CHAT_TRANSLATION_TAG: &'static str = "chat.type.text"; 133 | 134 | impl Component { 135 | pub fn write(&self, buffer: &mut Vec) { 136 | write_length_prefixed_string( 137 | buffer, 138 | &serde_json::to_string(&self).unwrap_or_else(|_| "INVALID".to_string()), 139 | ); 140 | } 141 | 142 | pub fn default_join(player: &str) -> Self { 143 | Self::default_state_message(player, "joined") 144 | } 145 | 146 | pub fn default_quit(player: &str) -> Self { 147 | Self::default_state_message(player, "quit") 148 | } 149 | 150 | pub fn default_restart_disconnect() -> Self { 151 | Component::Text( 152 | TextComponent::builder() 153 | .text(format!( 154 | "{KITTYMC_TAG}\n\n§7The server is restarting.\n\n{}", 155 | to_mc_rainbow("Please wait :3", true) 156 | )) 157 | .options(BaseComponent::builder().color(Color::Gray).build()) 158 | .build(), 159 | ) 160 | } 161 | 162 | pub fn default_error(e: &E) -> Self { 163 | Component::Text( 164 | TextComponent::builder() 165 | .text(format!( 166 | "{KITTYMC_TAG}\n\n§7An error occurred during your connection.\n\n{e}\n\n{}\n\n", 167 | to_mc_rainbow("oofies...", true) 168 | )) 169 | .options(BaseComponent::builder().color(Color::Gray).build()) 170 | .build(), 171 | ) 172 | } 173 | 174 | pub fn default_state_message(player_name: &str, verb: &str) -> Self { 175 | let name = rainbowize_cool_people(player_name, true); 176 | Component::Text( 177 | TextComponent::builder() 178 | .text(name) 179 | .options( 180 | BaseComponent::builder() 181 | .bold(true) 182 | .italic(true) 183 | .color(Color::DarkPurple) 184 | .extra(vec![Component::Text( 185 | TextComponent::builder() 186 | .text(format!(" {verb} the game")) 187 | .options(BaseComponent::builder().color(Color::Gray).build()) 188 | .build(), 189 | )]) 190 | .build(), 191 | ) 192 | .build(), 193 | ) 194 | } 195 | 196 | pub fn default_chat(player: &str, message: &str) -> Self { 197 | let name = rainbowize_cool_people(player, true); 198 | Component::Translation( 199 | TranslationComponent::builder() 200 | .translate(CHAT_TRANSLATION_TAG.to_string()) 201 | .with(vec![ 202 | Component::Text( 203 | TextComponent::builder() 204 | .text(name) 205 | .options( 206 | BaseComponent::builder() 207 | .bold(true) 208 | .italic(true) 209 | .color(Color::DarkPurple) 210 | .build(), 211 | ) 212 | .build(), 213 | ), 214 | Component::Text( 215 | TextComponent::builder() 216 | .text(message) 217 | .options(BaseComponent::builder().color(Color::Gray).build()) 218 | .build(), 219 | ), 220 | ]) 221 | .build(), 222 | ) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::client::login::*; 3 | use crate::packets::client::status::*; 4 | use crate::packets::packet_serialization::NamedPacket; 5 | use crate::packets::packet_serialization::{ 6 | decompress_packet, read_varint_u32, write_varint_u32_splice, SerializablePacket, 7 | }; 8 | use crate::packets::server::handshake::*; 9 | use crate::packets::server::login::*; 10 | use crate::packets::server::play::entity_action_15::EntityActionPacket; 11 | use crate::packets::server::play::*; 12 | use crate::packets::server::status::*; 13 | use crate::subtypes::state::State; 14 | use integer_encoding::VarInt; 15 | use kittymc_macros::PacketHelperFuncs; 16 | use log::{trace, warn}; 17 | 18 | pub mod client; 19 | pub mod packet_serialization; 20 | pub mod server; 21 | 22 | #[derive(Debug, Default, Clone)] 23 | pub struct CompressionInfo { 24 | pub enabled: bool, 25 | pub compression_threshold: u32, 26 | } 27 | 28 | #[derive(PartialEq, Debug, Clone, PacketHelperFuncs)] 29 | pub enum Packet { 30 | Handshake(HandshakePacket), 31 | LoginStart(LoginStartPacket), 32 | LoginSuccess(LoginSuccessPacket), 33 | StatusRequest(StatusRequestPacket), 34 | StatusResponse(StatusResponsePacket), 35 | StatusPing(StatusPingPongPacket), 36 | StatusPong(StatusPingPongPacket), 37 | KeepAlive(ClientKeepAlivePacket), 38 | SetCompression(SetCompressionPacket), 39 | PluginMessage(ClientPluginMessagePacket), 40 | ClientSettings(ClientSettingsPacket), 41 | TeleportConfirm(TeleportConfirmPacket), 42 | PlayerPositionAndLook(ClientPlayerPositionAndLookPacket), 43 | ClientHeldItemChange(ClientHeldItemChangePacket), 44 | PlayerPosition(PlayerPositionPacket), 45 | PlayerLook(PlayerLookPacket), 46 | ClientAnimation(ClientAnimationPacket), 47 | ChatMessage(ServerChatMessagePacket), 48 | PlayerDigging(PlayerDiggingPacket), 49 | EntityAction(EntityActionPacket), 50 | CreativeInventoryAction(CreativeInventoryActionPacket), 51 | PlayerBlockPlacement(PlayerBlockPlacementPacket), 52 | } 53 | 54 | impl Packet { 55 | pub fn deserialize_compressed( 56 | state: State, 57 | rawr_data: &[u8], 58 | compression: &CompressionInfo, 59 | ) -> Result<(usize, Packet), KittyMCError> { 60 | let mut data_part = rawr_data; 61 | let mut compressed_packet_len; 62 | let compressed_packet_data_len; 63 | let mut compressed_packet_len_len = 0; 64 | let mut uncompressed_data_len_len = 0; 65 | let uncompressed_packet_data_len; 66 | let uncompressed_data_len; 67 | 68 | let is_packet_compressed = { 69 | compressed_packet_data_len = 70 | read_varint_u32(&mut data_part, &mut compressed_packet_len_len)? as usize; 71 | compressed_packet_len = compressed_packet_data_len + compressed_packet_len_len; 72 | 73 | uncompressed_data_len = 74 | read_varint_u32(&mut data_part, &mut uncompressed_data_len_len)? as usize; 75 | uncompressed_data_len != 0 76 | && uncompressed_data_len >= compression.compression_threshold as usize 77 | }; 78 | 79 | uncompressed_packet_data_len = match is_packet_compressed { 80 | true => uncompressed_data_len + uncompressed_data_len_len, 81 | false => compressed_packet_data_len - uncompressed_data_len_len, 82 | }; 83 | 84 | let mut b_packet; 85 | 86 | if is_packet_compressed { 87 | let (size, owned_data) = decompress_packet(&rawr_data)?; 88 | b_packet = owned_data; 89 | write_varint_u32_splice(&mut b_packet, uncompressed_data_len as u32, ..0); 90 | trace!("Complete Uncompressed Packet : {:?}", b_packet); 91 | 92 | if compressed_packet_len != size { 93 | warn!("Handled size of decompression function should be equal to deserialize_compressed functions size. Using decompression size."); 94 | compressed_packet_len = size; 95 | } 96 | } else { 97 | b_packet = match data_part.get(..uncompressed_packet_data_len) { 98 | None => { 99 | return Err(KittyMCError::NotEnoughData( 100 | data_part.len(), 101 | uncompressed_packet_data_len, 102 | )) 103 | } 104 | Some(packet) => packet.to_vec(), 105 | }; 106 | write_varint_u32_splice(&mut b_packet, uncompressed_packet_data_len as u32, ..0); 107 | } 108 | 109 | let (decompressed_size, packet) = match Self::deserialize_uncompressed(state, &b_packet) { 110 | Ok(p) => Ok(p), 111 | Err(KittyMCError::NotImplemented(id, _len)) => { 112 | Err(KittyMCError::NotImplemented(id, compressed_packet_len)) 113 | } // Replace the length with the compressed length 114 | Err(e) => Err(e), 115 | }?; 116 | 117 | if b_packet.len() != decompressed_size { 118 | return Err(KittyMCError::InvalidDecompressedPacketLength( 119 | b_packet.len(), 120 | decompressed_size, 121 | )); 122 | } 123 | 124 | Ok((compressed_packet_len, packet)) 125 | } 126 | 127 | pub fn deserialize_uncompressed( 128 | state: State, 129 | mut data: &[u8], 130 | ) -> Result<(usize, Packet), KittyMCError> { 131 | let mut header_size = 0; 132 | let packet_data_and_id_len = read_varint_u32(&mut data, &mut header_size)? as usize; 133 | let full_packet_len = packet_data_and_id_len + header_size; 134 | 135 | if packet_data_and_id_len > data.len() { 136 | trace!( 137 | "Not enough data yet. Packet length: {}. Current Data length: {}", 138 | packet_data_and_id_len, 139 | data.len() 140 | ); 141 | return Err(KittyMCError::NotEnoughData( 142 | data.len(), 143 | packet_data_and_id_len, 144 | )); 145 | } 146 | 147 | let packet_id = read_varint_u32(&mut data, &mut header_size)? as usize; 148 | let Some(packet_data_len) = full_packet_len.checked_sub(header_size) else { 149 | return Err(KittyMCError::InvalidPacketLength); 150 | }; 151 | 152 | let (packet_size, packet) = Self::deserialize_by_state( 153 | state, 154 | &mut &data[..packet_data_len], 155 | full_packet_len, 156 | packet_id, 157 | )?; 158 | 159 | let full_size = header_size + packet_size; 160 | Ok((full_size, packet)) 161 | } 162 | 163 | fn deserialize_by_state( 164 | state: State, 165 | data: &mut &[u8], 166 | full_packet_len: usize, 167 | packet_id: usize, 168 | ) -> Result<(usize, Packet), KittyMCError> { 169 | let (packet_size, packet) = match state { 170 | State::Handshake => match packet_id { 171 | 0 => HandshakePacket::deserialize(data)?, 172 | _ => return Err(KittyMCError::NotImplemented(packet_id, full_packet_len)), 173 | }, 174 | State::Status => match packet_id { 175 | 0 => StatusRequestPacket::deserialize(data)?, 176 | 1 => StatusPingPongPacket::deserialize(data)?, 177 | _ => return Err(KittyMCError::NotImplemented(packet_id, full_packet_len)), 178 | }, 179 | State::Login => match packet_id { 180 | 0 => LoginStartPacket::deserialize(data)?, 181 | _ => return Err(KittyMCError::NotImplemented(packet_id, full_packet_len)), 182 | }, 183 | State::Play => match packet_id { 184 | 0 => TeleportConfirmPacket::deserialize(data)?, 185 | 2 => ServerChatMessagePacket::deserialize(data)?, 186 | 4 => ClientSettingsPacket::deserialize(data)?, 187 | 9 => ClientPluginMessagePacket::deserialize(data)?, 188 | 0xB => ClientKeepAlivePacket::deserialize(data)?, 189 | 0xD => PlayerPositionPacket::deserialize(data)?, 190 | 0xE => ClientPlayerPositionAndLookPacket::deserialize(data)?, 191 | 0xF => PlayerLookPacket::deserialize(data)?, 192 | 0x14 => PlayerDiggingPacket::deserialize(data)?, 193 | 0x15 => EntityActionPacket::deserialize(data)?, 194 | 0x1A => ClientHeldItemChangePacket::deserialize(data)?, 195 | 0x1B => CreativeInventoryActionPacket::deserialize(data)?, 196 | 0x1D => ClientAnimationPacket::deserialize(data)?, 197 | 0x1F => PlayerBlockPlacementPacket::deserialize(data)?, 198 | _ => return Err(KittyMCError::NotImplemented(packet_id, full_packet_len)), 199 | }, 200 | _ => return Err(KittyMCError::NotImplemented(packet_id, full_packet_len)), 201 | }; 202 | Ok((packet_size, packet)) 203 | } 204 | 205 | pub fn deserialize( 206 | state: State, 207 | raw_data: &[u8], 208 | compression: &CompressionInfo, 209 | ) -> Result<(usize, Packet), KittyMCError> { 210 | if compression.enabled { 211 | Self::deserialize_compressed(state, raw_data, compression) 212 | } else { 213 | Self::deserialize_uncompressed(state, raw_data) 214 | } 215 | } 216 | } 217 | 218 | pub fn wrap_packet(packet: &mut Vec, id: u32) { 219 | // add packet id 220 | packet.splice(0..0, id.encode_var_vec()); 221 | 222 | // set total length 223 | let total_len = packet.len().encode_var_vec(); 224 | packet.splice(0..0, total_len); 225 | } 226 | -------------------------------------------------------------------------------- /kittymc_lib/src/subtypes/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::packets::client::play::window_items_14::SlotData; 2 | use crate::packets::packet_serialization::{ 3 | write_bool, write_direction, write_f32, write_length_prefixed_string, write_location2, 4 | write_nbt, write_rotation, write_u8, write_uuid, write_varint_u32, 5 | }; 6 | use crate::packets::server::play::client_settings_04::DisplayedSkinParts; 7 | use crate::subtypes::components::Component; 8 | use crate::subtypes::{Direction, Location2, Rotation}; 9 | use bitflags::bitflags; 10 | use std::collections::{BTreeMap, HashMap}; 11 | use typed_builder::TypedBuilder; 12 | use uuid::Uuid; 13 | 14 | pub enum MetaData { 15 | Byte(u8), 16 | VarInt(u32), 17 | Float(f32), 18 | String(String), 19 | Chat(Component), 20 | Slot(SlotData), 21 | Boolean(bool), 22 | Rotation(Rotation), 23 | Position(Location2), 24 | OptPosition(Option), // Boolean + Optional Position 25 | Direction(Direction), // varints 26 | OptUuid(Option), // Boolean + Optional Uuid 27 | OptBlockId(Option), // Boolean + Optional VarInt 28 | NBTTag(fastnbt::Value), 29 | } 30 | 31 | impl MetaData { 32 | pub fn type_id(&self) -> u8 { 33 | match self { 34 | MetaData::Byte(_) => 0, 35 | MetaData::VarInt(_) => 1, 36 | MetaData::Float(_) => 2, 37 | MetaData::String(_) => 3, 38 | MetaData::Chat(_) => 4, 39 | MetaData::Slot(_) => 5, 40 | MetaData::Boolean(_) => 6, 41 | MetaData::Rotation(_) => 7, 42 | MetaData::Position(_) => 8, 43 | MetaData::OptPosition(_) => 9, 44 | MetaData::Direction(_) => 10, 45 | MetaData::OptUuid(_) => 11, 46 | MetaData::OptBlockId(_) => 12, 47 | MetaData::NBTTag(_) => 13, 48 | } 49 | } 50 | 51 | pub fn write(&self, buffer: &mut Vec) { 52 | match self { 53 | MetaData::Byte(b) => write_u8(buffer, *b), 54 | MetaData::VarInt(i) => write_varint_u32(buffer, *i), 55 | MetaData::Float(f) => write_f32(buffer, *f), 56 | MetaData::String(s) => write_length_prefixed_string(buffer, s), 57 | MetaData::Chat(c) => c.write(buffer), 58 | MetaData::Slot(s) => s.write(buffer), 59 | MetaData::Boolean(b) => write_bool(buffer, *b), 60 | MetaData::Rotation(rotation) => write_rotation(buffer, rotation), 61 | MetaData::Position(pos) => write_location2(buffer, pos), 62 | MetaData::OptPosition(pos) => { 63 | write_bool(buffer, pos.is_some()); 64 | if let Some(pos) = pos { 65 | write_location2(buffer, pos); 66 | } 67 | } 68 | MetaData::Direction(dir) => write_direction(buffer, dir), 69 | MetaData::OptUuid(uuid) => { 70 | write_bool(buffer, uuid.is_some()); 71 | if let Some(uuid) = uuid { 72 | write_uuid(buffer, uuid); 73 | } 74 | } 75 | MetaData::OptBlockId(block_id) => { 76 | write_bool(buffer, block_id.is_some()); 77 | if let Some(block_id) = block_id { 78 | write_varint_u32(buffer, *block_id); 79 | } 80 | } 81 | MetaData::NBTTag(tag) => write_nbt(buffer, tag), 82 | } 83 | } 84 | } 85 | 86 | pub fn write_metadata(buffer: &mut Vec, meta_data: &BTreeMap) { 87 | for (index, value) in meta_data { 88 | write_u8(buffer, *index); 89 | write_u8(buffer, value.type_id()); 90 | value.write(buffer); 91 | } 92 | write_u8(buffer, 0xFF); 93 | } 94 | 95 | bitflags! { 96 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 97 | pub struct EntityMetaState : u8 { 98 | const on_fire = 0x01; 99 | const crouched = 0x02; 100 | const unused = 0x04; // previously riding 101 | const sprinting = 0x08; 102 | const unused_2 = 0x10; 103 | const invisible = 0x20; // previously eating/drinking/blocking (use hand state now) 104 | const glowing_effect = 0x40; 105 | const elytra_flying = 0x80; 106 | } 107 | 108 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 109 | pub struct LivingHandState : u8 { 110 | const is_hand_active = 0x01; 111 | const active_hand = 0x02; // 0 = main hand, 1 = offhand 112 | } 113 | } 114 | 115 | impl EntityMetaState { 116 | pub fn write_to_metadata(&self, meta_data: &mut BTreeMap, index: u8) { 117 | meta_data.insert(index, MetaData::Byte(self.bits())); 118 | } 119 | } 120 | 121 | impl LivingHandState { 122 | pub fn write_to_metadata(&self, meta_data: &mut BTreeMap, index: u8) { 123 | meta_data.insert(index, MetaData::Byte(self.bits())); 124 | } 125 | } 126 | 127 | pub trait MetadataObject { 128 | fn write_to_metadata(&self, meta_data: &mut BTreeMap); 129 | fn write_metadata(&self, buffer: &mut Vec); 130 | } 131 | 132 | #[derive(PartialEq, Debug, Clone, TypedBuilder)] 133 | pub struct EntityMetadata { 134 | pub meta_state: EntityMetaState, 135 | pub air: u32, 136 | pub custom_name: String, 137 | pub is_custom_name_visible: bool, 138 | pub is_silent: bool, 139 | pub no_gravity: bool, 140 | } 141 | 142 | impl Default for EntityMetadata { 143 | fn default() -> Self { 144 | EntityMetadata { 145 | meta_state: EntityMetaState::empty(), 146 | air: 300, 147 | custom_name: "".to_string(), 148 | is_custom_name_visible: false, 149 | is_silent: false, 150 | no_gravity: false, 151 | } 152 | } 153 | } 154 | 155 | impl MetadataObject for EntityMetadata { 156 | fn write_to_metadata(&self, mut meta_data: &mut BTreeMap) { 157 | self.meta_state.write_to_metadata(&mut meta_data, 0); 158 | meta_data.insert(1, MetaData::VarInt(self.air)); 159 | meta_data.insert(2, MetaData::String(self.custom_name.clone())); 160 | meta_data.insert(3, MetaData::Boolean(self.is_custom_name_visible)); 161 | meta_data.insert(4, MetaData::Boolean(self.is_silent)); 162 | meta_data.insert(5, MetaData::Boolean(self.no_gravity)); 163 | } 164 | 165 | fn write_metadata(&self, buffer: &mut Vec) { 166 | let mut metadata = BTreeMap::new(); 167 | self.write_to_metadata(&mut metadata); 168 | write_metadata(buffer, &metadata); 169 | } 170 | } 171 | 172 | #[derive(PartialEq, Debug, Clone, TypedBuilder)] 173 | pub struct LivingMetadata { 174 | pub entity: EntityMetadata, 175 | pub hand_state: LivingHandState, 176 | pub health: f32, 177 | pub potion_effect_color: u32, 178 | pub is_potion_effect_ambient: bool, 179 | pub number_of_arrows_in_entity: u32, 180 | } 181 | 182 | impl Default for LivingMetadata { 183 | fn default() -> Self { 184 | LivingMetadata { 185 | entity: EntityMetadata::default(), 186 | hand_state: LivingHandState::empty(), 187 | health: 1.0, 188 | potion_effect_color: 0, 189 | is_potion_effect_ambient: false, 190 | number_of_arrows_in_entity: 0, 191 | } 192 | } 193 | } 194 | 195 | impl MetadataObject for LivingMetadata { 196 | fn write_to_metadata(&self, mut meta_data: &mut BTreeMap) { 197 | self.entity.write_to_metadata(&mut meta_data); 198 | self.hand_state.write_to_metadata(&mut meta_data, 6); 199 | meta_data.insert(6, MetaData::Float(self.health)); 200 | meta_data.insert(7, MetaData::VarInt(self.potion_effect_color)); 201 | meta_data.insert(8, MetaData::Boolean(self.is_potion_effect_ambient)); 202 | meta_data.insert(9, MetaData::VarInt(self.number_of_arrows_in_entity)); 203 | } 204 | 205 | fn write_metadata(&self, buffer: &mut Vec) { 206 | let mut metadata = BTreeMap::new(); 207 | self.write_to_metadata(&mut metadata); 208 | write_metadata(buffer, &metadata); 209 | } 210 | } 211 | 212 | #[derive(PartialEq, Debug, Clone, TypedBuilder)] 213 | pub struct PlayerMetadata { 214 | pub living: LivingMetadata, 215 | pub additional_hearts: f32, 216 | pub score: u32, 217 | pub displayed_skin_parts: DisplayedSkinParts, 218 | pub main_hand: u8, 219 | pub left_shoulder_entity_data: fastnbt::Value, // for occupying parrot // TODO: NBT 220 | pub right_shoulder_entity_data: fastnbt::Value, // for occupying parrot // TODO: NBT 221 | } 222 | 223 | impl Default for PlayerMetadata { 224 | fn default() -> Self { 225 | PlayerMetadata { 226 | living: LivingMetadata::default(), 227 | additional_hearts: 0., 228 | score: 0, 229 | displayed_skin_parts: DisplayedSkinParts::empty(), 230 | main_hand: 1, 231 | left_shoulder_entity_data: fastnbt::Value::Compound(HashMap::new()), 232 | right_shoulder_entity_data: fastnbt::Value::Compound(HashMap::new()), 233 | } 234 | } 235 | } 236 | 237 | impl MetadataObject for PlayerMetadata { 238 | fn write_to_metadata(&self, mut meta_data: &mut BTreeMap) { 239 | self.living.write_to_metadata(&mut meta_data); 240 | meta_data.insert(11, MetaData::Float(self.additional_hearts)); 241 | meta_data.insert(12, MetaData::VarInt(self.score)); 242 | meta_data.insert(13, MetaData::Byte(self.displayed_skin_parts.bits())); 243 | meta_data.insert(14, MetaData::Byte(self.main_hand)); 244 | meta_data.insert(15, MetaData::NBTTag(self.left_shoulder_entity_data.clone())); 245 | meta_data.insert( 246 | 16, 247 | MetaData::NBTTag(self.right_shoulder_entity_data.clone()), 248 | ); 249 | } 250 | 251 | fn write_metadata(&self, buffer: &mut Vec) { 252 | let mut metadata = BTreeMap::new(); 253 | self.write_to_metadata(&mut metadata); 254 | write_metadata(buffer, &metadata); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /kittymc_lib/src/subtypes/mod.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Rotation3, Vector2, Vector3}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::fmt::Debug; 4 | use std::ops::{Add, AddAssign}; 5 | 6 | pub mod components; 7 | pub mod metadata; 8 | pub mod state; 9 | 10 | #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)] 11 | #[serde(rename_all = "snake_case")] 12 | pub enum Color { 13 | Reset, 14 | Black, 15 | DarkBlue, 16 | DarkGreen, 17 | DarkAqua, 18 | DarkRed, 19 | DarkPurple, 20 | Gold, 21 | Gray, 22 | DarkGray, 23 | Blue, 24 | Green, 25 | Aqua, 26 | Red, 27 | LightPurple, 28 | Yellow, 29 | White, 30 | Bold, 31 | Obfuscated, 32 | Strikethrough, 33 | Underline, 34 | Italic, 35 | } 36 | 37 | impl Color { 38 | pub fn as_str(&self) -> &'static str { 39 | match self { 40 | Color::Black => "black", 41 | Color::DarkBlue => "dark_blue", 42 | Color::DarkGreen => "dark_green", 43 | Color::DarkAqua => "dark_aqua", 44 | Color::DarkRed => "dark_red", 45 | Color::DarkPurple => "dark_purple", 46 | Color::Gold => "gold", 47 | Color::Gray => "gray", 48 | Color::DarkGray => "dark_gray", 49 | Color::Blue => "blue", 50 | Color::Green => "green", 51 | Color::Aqua => "aqua", 52 | Color::Red => "red", 53 | Color::LightPurple => "light_purple", 54 | Color::Yellow => "yellow", 55 | Color::White => "white", 56 | _ => "", 57 | } 58 | } 59 | 60 | pub fn as_color_code(&self) -> &'static str { 61 | match self { 62 | Color::Black => "§0", 63 | Color::DarkBlue => "§1", 64 | Color::DarkGreen => "§2", 65 | Color::DarkAqua => "§3", 66 | Color::DarkRed => "§4", 67 | Color::DarkPurple => "§5", 68 | Color::Gold => "§6", 69 | Color::Gray => "§7", 70 | Color::DarkGray => "§8", 71 | Color::Blue => "§9", 72 | Color::Green => "§a", 73 | Color::Aqua => "§b", 74 | Color::Red => "§c", 75 | Color::LightPurple => "§d", 76 | Color::Yellow => "§e", 77 | Color::White => "§f", 78 | Color::Reset => "§r", 79 | Color::Obfuscated => "§k", 80 | Color::Bold => "§l", 81 | Color::Strikethrough => "§m", 82 | Color::Underline => "§n", 83 | Color::Italic => "§o", 84 | } 85 | } 86 | } 87 | 88 | pub const CHUNK_WIDTH: isize = 16; 89 | pub const HALF_CHUNK_WIDTH: f32 = (CHUNK_WIDTH / 2) as f32; 90 | 91 | pub struct ChunkPositionIterator { 92 | positions: Vec, 93 | idx: usize, 94 | } 95 | 96 | impl ChunkPositionIterator { 97 | /// Return the min (lowest corner) of the chunk in block coords 98 | fn chunk_box_min(cpos: &ChunkPosition) -> Location { 99 | Location::new( 100 | cpos.block_x() as f32, 101 | cpos.block_y() as f32, 102 | cpos.block_z() as f32, 103 | ) 104 | } 105 | 106 | /// Return the max corner of the chunk in block coords 107 | fn chunk_box_max(cpos: &ChunkPosition) -> Location { 108 | Self::chunk_box_min(cpos) 109 | + Location::new(CHUNK_WIDTH as f32, CHUNK_WIDTH as f32, CHUNK_WIDTH as f32) 110 | } 111 | 112 | /// 3D bounding box distance 113 | fn distance_to_chunk_box_3d(cpos: &ChunkPosition, point: &Location) -> f32 { 114 | let bmin = Self::chunk_box_min(cpos); 115 | let bmax = Self::chunk_box_max(cpos); 116 | 117 | // For each dimension, compute how far `point` is outside bmin..bmax. 118 | let dx = if point.x < bmin.x { 119 | bmin.x - point.x 120 | } else if point.x > bmax.x { 121 | point.x - bmax.x 122 | } else { 123 | 0.0 124 | }; 125 | 126 | let dy = if point.y < bmin.y { 127 | bmin.y - point.y 128 | } else if point.y > bmax.y { 129 | point.y - bmax.y 130 | } else { 131 | 0.0 132 | }; 133 | 134 | let dz = if point.z < bmin.z { 135 | bmin.z - point.z 136 | } else if point.z > bmax.z { 137 | point.z - bmax.z 138 | } else { 139 | 0.0 140 | }; 141 | 142 | (dx * dx + dy * dy + dz * dz).sqrt() 143 | } 144 | 145 | /// 2D bounding box distance (XZ only), ignoring Y dimension 146 | fn distance_to_chunk_box_xz(cpos: &ChunkPosition, point: &Location) -> f32 { 147 | let bmin = Self::chunk_box_min(cpos); 148 | let bmax = Self::chunk_box_max(cpos); 149 | 150 | // 2D in XZ 151 | let dx = if point.x < bmin.x { 152 | bmin.x - point.x 153 | } else if point.x > bmax.x { 154 | point.x - bmax.x 155 | } else { 156 | 0.0 157 | }; 158 | 159 | let dz = if point.z < bmin.z { 160 | bmin.z - point.z 161 | } else if point.z > bmax.z { 162 | point.z - bmax.z 163 | } else { 164 | 0.0 165 | }; 166 | 167 | // In the XZ‑only approach, we just treat Y as “unchanged”. 168 | (dx * dx + dz * dz).sqrt() 169 | } 170 | 171 | pub fn new(center: &Location, radius: f32, xz_only: bool) -> Self { 172 | let center_chunk = ChunkPosition::from(center.clone()); 173 | 174 | let radius_in_chunks = (radius / CHUNK_WIDTH as f32).ceil() as isize; 175 | 176 | let mut positions = Vec::new(); 177 | 178 | let (min_y, max_y) = if xz_only { 179 | (0, 0) 180 | } else { 181 | ( 182 | center_chunk.chunk_y() - radius_in_chunks, 183 | center_chunk.chunk_y() + radius_in_chunks, 184 | ) 185 | }; 186 | 187 | for cy in min_y..=max_y { 188 | for cz in (center_chunk.chunk_z() - radius_in_chunks) 189 | ..=(center_chunk.chunk_z() + radius_in_chunks) 190 | { 191 | for cx in (center_chunk.chunk_x() - radius_in_chunks) 192 | ..=(center_chunk.chunk_x() + radius_in_chunks) 193 | { 194 | let cpos = ChunkPosition::new(cx, cy, cz); 195 | 196 | let dist = if xz_only { 197 | Self::distance_to_chunk_box_xz(&cpos, center) 198 | } else { 199 | Self::distance_to_chunk_box_3d(&cpos, center) 200 | }; 201 | 202 | if dist <= radius { 203 | positions.push(cpos); 204 | } 205 | } 206 | } 207 | } 208 | 209 | ChunkPositionIterator { positions, idx: 0 } 210 | } 211 | } 212 | 213 | impl Iterator for ChunkPositionIterator { 214 | type Item = ChunkPosition; 215 | 216 | fn next(&mut self) -> Option { 217 | if self.idx < self.positions.len() { 218 | let out = self.positions[self.idx].clone(); 219 | self.idx += 1; 220 | Some(out) 221 | } else { 222 | None 223 | } 224 | } 225 | } 226 | 227 | #[test] 228 | fn chunk_position_iterator_test() { 229 | let chunks: Vec<_> = 230 | ChunkPositionIterator::new(&Location::new(0., 5., 0.), CHUNK_WIDTH as f32 / 2., true) 231 | .collect(); 232 | 233 | assert_eq!(chunks.len(), 4); 234 | } 235 | 236 | #[derive(Hash, Eq, PartialEq, Debug, Clone)] 237 | pub struct ChunkPosition(Vector3); 238 | 239 | impl ChunkPosition { 240 | /// Constructs a new `ChunkPosition` from raw chunk indices. 241 | /// E.g. `ChunkPosition::new(2, 0, 4)` means chunk #2 on X, #0 on Y, #4 on Z. 242 | pub fn new(cx: isize, cy: isize, cz: isize) -> Self { 243 | ChunkPosition(Vector3::new(cx, cy, cz)) 244 | } 245 | 246 | pub fn chunk_x(&self) -> isize { 247 | self.0.x 248 | } 249 | 250 | pub fn chunk_y(&self) -> isize { 251 | self.0.y 252 | } 253 | 254 | pub fn chunk_z(&self) -> isize { 255 | self.0.z 256 | } 257 | 258 | /// Returns the lowest corner of this chunk *in block/world coordinates* 259 | pub fn block_x(&self) -> isize { 260 | self.0.x * CHUNK_WIDTH 261 | } 262 | 263 | pub fn block_y(&self) -> isize { 264 | self.0.y * CHUNK_WIDTH 265 | } 266 | 267 | pub fn block_z(&self) -> isize { 268 | self.0.z * CHUNK_WIDTH 269 | } 270 | 271 | pub fn set_chunk_x(&mut self, x: isize) { 272 | self.0.x = x; 273 | } 274 | 275 | pub fn set_chunk_y(&mut self, y: isize) { 276 | self.0.y = y; 277 | } 278 | 279 | pub fn set_chunk_z(&mut self, z: isize) { 280 | self.0.z = z; 281 | } 282 | 283 | pub fn block_location(&self) -> Location { 284 | Location::new( 285 | self.block_x() as f32, 286 | self.block_y() as f32, 287 | self.block_z() as f32, 288 | ) 289 | } 290 | 291 | pub fn center(&self) -> Location { 292 | Location::new( 293 | self.block_x() as f32 + HALF_CHUNK_WIDTH, 294 | self.block_y() as f32 + HALF_CHUNK_WIDTH, 295 | self.block_z() as f32 + HALF_CHUNK_WIDTH, 296 | ) 297 | } 298 | 299 | pub fn iter_sphere_in_range(location: &Location, radius: f32) -> ChunkPositionIterator { 300 | ChunkPositionIterator::new(location, radius, false) 301 | } 302 | 303 | pub fn iter_xz_circle_in_range(location: &Location, radius: f32) -> ChunkPositionIterator { 304 | ChunkPositionIterator::new(location, radius, true) 305 | } 306 | } 307 | 308 | impl Add for ChunkPosition { 309 | type Output = ChunkPosition; 310 | 311 | fn add(mut self, rhs: Self) -> Self::Output { 312 | self.0 += rhs.0; 313 | self 314 | } 315 | } 316 | 317 | impl Add for ChunkPosition { 318 | type Output = ChunkPosition; 319 | 320 | fn add(mut self, rhs: isize) -> Self::Output { 321 | self.0.x += rhs; 322 | self.0.y += rhs; 323 | self.0.z += rhs; 324 | self 325 | } 326 | } 327 | 328 | impl AddAssign for ChunkPosition { 329 | fn add_assign(&mut self, rhs: isize) { 330 | self.0.x += rhs; 331 | self.0.y += rhs; 332 | self.0.z += rhs; 333 | } 334 | } 335 | 336 | impl From for ChunkPosition { 337 | fn from(loc: Location) -> ChunkPosition { 338 | Self::from(&loc) 339 | } 340 | } 341 | 342 | impl From<&Location> for ChunkPosition { 343 | fn from(loc: &Location) -> ChunkPosition { 344 | let bx = (loc.x / CHUNK_WIDTH as f32).floor(); 345 | let by = (loc.y / CHUNK_WIDTH as f32).floor(); 346 | let bz = (loc.z / CHUNK_WIDTH as f32).floor(); 347 | 348 | let cx = bx as isize; 349 | let cy = by as isize; 350 | let cz = bz as isize; 351 | 352 | ChunkPosition(Vector3::new(cx, cy, cz)) 353 | } 354 | } 355 | 356 | pub type Location = Vector3; 357 | pub type Location2 = Vector3; 358 | pub type Direction = Vector2; 359 | pub type Rotation = Rotation3; 360 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/packet_serialization.rs: -------------------------------------------------------------------------------- 1 | use crate::error::KittyMCError; 2 | use crate::packets::Packet; 3 | use crate::subtypes::{Direction, Location, Location2, Rotation}; 4 | use crate::utils::axis_to_angle; 5 | use integer_encoding::VarInt; 6 | use log::warn; 7 | use miniz_oxide::deflate::compress_to_vec_zlib; 8 | use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit; 9 | use paste::paste; 10 | use std::mem; 11 | use std::ops::RangeBounds; 12 | use uuid::Uuid; 13 | 14 | pub trait NamedPacket { 15 | fn name() -> &'static str { 16 | todo!("Define a name for this packet or just derive Packet") 17 | } 18 | } 19 | 20 | pub trait SerializablePacket { 21 | fn serialize(&self) -> Vec { 22 | vec![0] 23 | } 24 | 25 | // not including length or packet id 26 | fn deserialize(data: &[u8]) -> Result<(usize, Packet), KittyMCError> { 27 | Err(KittyMCError::NotImplemented( 28 | Self::id() as usize, 29 | data.len(), 30 | )) 31 | } 32 | 33 | fn id() -> u32; 34 | } 35 | 36 | pub fn read_length_prefixed_string( 37 | data: &mut &[u8], 38 | total_size: &mut usize, 39 | ) -> Result { 40 | let len = read_varint_u32(data, total_size)? as usize; 41 | 42 | if data.len() < len { 43 | return Err(KittyMCError::NotEnoughBytesToDeserialize( 44 | "String", 45 | len, 46 | data.len(), 47 | )); 48 | } 49 | 50 | let raw_bytes = &data[..len]; 51 | let s = String::from_utf8(raw_bytes.to_vec())?; 52 | *data = &data[len..]; 53 | *total_size += len; 54 | 55 | Ok(s) 56 | } 57 | 58 | pub fn read_length_prefixed_bytes( 59 | data: &mut &[u8], 60 | total_size: &mut usize, 61 | ) -> Result, KittyMCError> { 62 | let len = read_varint_u32(data, total_size)? as usize; 63 | 64 | if data.len() < len { 65 | return Err(KittyMCError::NotEnoughBytesToDeserialize( 66 | "Byte Array", 67 | len, 68 | data.len(), 69 | )); 70 | } 71 | 72 | let bytes = data[..len].to_vec(); 73 | *data = &data[len..]; 74 | *total_size += len; 75 | 76 | Ok(bytes) 77 | } 78 | 79 | macro_rules! type_rw_impl { 80 | ($($ty:ty$(,)?)*) => { 81 | $( 82 | paste! { 83 | pub fn [](data: &mut &[u8], total_size: &mut usize) -> Result<$ty, KittyMCError> { 84 | let size = mem::size_of::<$ty>(); 85 | if data.len() < size { 86 | return Err(KittyMCError::NotEnoughBytesToDeserialize( 87 | stringify!($ty), 88 | mem::size_of::<$ty>(), 89 | data.len(), 90 | )); 91 | } 92 | 93 | let value = <$ty>::from_be_bytes( 94 | data[..size] 95 | .try_into() 96 | .map_err(|_| KittyMCError::DeserializationError)?, 97 | ); 98 | *data = &data[size..]; 99 | *total_size += size; 100 | Ok(value) 101 | } 102 | 103 | pub fn [](buffer: &mut Vec, value: $ty) { 104 | buffer.extend_from_slice(&value.to_be_bytes()); 105 | } 106 | } 107 | )*}; 108 | } 109 | 110 | macro_rules! type_rw_varint_impl { 111 | ($($ty:ty$(,)?)*) => { 112 | $( 113 | paste! { 114 | pub fn [](data: &mut &[u8], total_size: &mut usize) -> Result<$ty, KittyMCError> { 115 | let (value, size) = 116 | VarInt::decode_var(*data).ok_or(KittyMCError::VarDeserializationError(concat!("var_", stringify!($ty))))?; 117 | *data = &data[size..]; 118 | *total_size += size; 119 | Ok(value) 120 | } 121 | 122 | pub fn [](buffer: &mut Vec, value: $ty) { 123 | buffer.extend_from_slice(&value.encode_var_vec()); 124 | } 125 | 126 | pub fn []>(buffer: &mut Vec, value: $ty, at: R) { 127 | buffer.splice(at, value.encode_var_vec()); 128 | } 129 | } 130 | )*}; 131 | } 132 | 133 | type_rw_impl!(u128, u64, u32, u16, u8, i128, i64, i32, i16, i8, f64, f32); 134 | type_rw_varint_impl!(u64, u32, u16, u8, i64, i32, i16, i8); 135 | 136 | pub fn read_bool(data: &mut &[u8], total_size: &mut usize) -> Result { 137 | if data.len() < 1 { 138 | return Err(KittyMCError::NotEnoughBytesToDeserialize( 139 | "Bool", 140 | 1, 141 | data.len(), 142 | )); 143 | } 144 | let value = data[0] != 0; 145 | *data = &data[1..]; 146 | *total_size += 1; 147 | Ok(value) 148 | } 149 | 150 | pub fn read_location2(data: &mut &[u8], total_size: &mut usize) -> Result { 151 | let x = read_f64(data, total_size)?; 152 | let y = read_f64(data, total_size)?; 153 | let z = read_f64(data, total_size)?; 154 | 155 | Ok(Location2::new(x, y, z)) 156 | } 157 | 158 | // Position 159 | pub fn read_block_location( 160 | data: &mut &[u8], 161 | total_size: &mut usize, 162 | ) -> Result { 163 | // Ensure there are at least 8 bytes to read 164 | if data.len() < 8 { 165 | return Err(KittyMCError::NotEnoughBytesToDeserialize( 166 | "Position", 167 | 8, 168 | data.len(), 169 | )); 170 | } 171 | 172 | // Read the first 8 bytes as a u64 in big-endian order 173 | let encoded = u64::from_be_bytes( 174 | data[..8] 175 | .try_into() 176 | .map_err(|_| KittyMCError::DeserializationError)?, 177 | ); 178 | 179 | // Update the data slice and total_size 180 | *data = &data[8..]; 181 | *total_size += 8; 182 | 183 | // Extract bits for x, y, z 184 | let x_bits = (encoded >> 38) & 0x3FFFFFF; // 26 bits for x 185 | let y_bits = (encoded >> 26) & 0xFFF; // 12 bits for y 186 | let z_bits = encoded & 0x3FFFFFF; // 26 bits for z 187 | 188 | // Convert the extracted bits to signed integers using two's complement 189 | let x = if x_bits & (1 << 25) != 0 { 190 | (x_bits as i32) - (1 << 26) 191 | } else { 192 | x_bits as i32 193 | }; 194 | 195 | let y = if y_bits & (1 << 11) != 0 { 196 | (y_bits as i32) - (1 << 12) 197 | } else { 198 | y_bits as i32 199 | }; 200 | 201 | let z = if z_bits & (1 << 25) != 0 { 202 | (z_bits as i32) - (1 << 26) 203 | } else { 204 | z_bits as i32 205 | }; 206 | 207 | // Create and return the Location instance 208 | Ok(Location::new(x as f32, y as f32, z as f32)) 209 | } 210 | 211 | pub fn read_direction(data: &mut &[u8], total_size: &mut usize) -> Result { 212 | let yaw = read_f32(data, total_size)?; 213 | let pitch = read_f32(data, total_size)?; 214 | 215 | Ok(Direction::new(yaw, pitch)) 216 | } 217 | 218 | pub fn read_nbt(buffer: &mut &[u8], total_size: &mut usize) -> Result { 219 | let len_before = buffer.len(); 220 | let nbt = fastnbt::from_reader(&mut *buffer); 221 | 222 | *total_size += len_before - buffer.len(); 223 | 224 | Ok(nbt?) 225 | } 226 | 227 | pub fn write_bool(buffer: &mut Vec, value: bool) { 228 | let value = if value { 1 } else { 0 }; 229 | write_u8(buffer, value); 230 | } 231 | 232 | pub fn write_length_prefixed_string(buffer: &mut Vec, s: &str) { 233 | let bytes = s.as_bytes(); 234 | write_varint_u32(buffer, bytes.len() as u32); 235 | buffer.extend_from_slice(bytes); 236 | } 237 | 238 | pub fn write_length_prefixed_bytes(buffer: &mut Vec, s: &[u8]) { 239 | write_varint_u32(buffer, s.len() as u32); 240 | buffer.extend_from_slice(s); 241 | } 242 | 243 | pub fn write_uuid(buffer: &mut Vec, uuid: &Uuid) { 244 | write_u128(buffer, uuid.as_u128()) 245 | } 246 | 247 | pub fn write_location(buffer: &mut Vec, loc: &Location) { 248 | write_f32(buffer, loc.x); 249 | write_f32(buffer, loc.y); 250 | write_f32(buffer, loc.z); 251 | } 252 | 253 | pub fn write_block_location(buffer: &mut Vec, loc: &Location) { 254 | // Convert the float coordinates to integers. 255 | let x = loc.x.floor() as i64; 256 | let y = loc.y.floor() as i64; 257 | let z = loc.z.floor() as i64; 258 | 259 | // Mask to signed bit ranges 260 | // x, z in 26 bits: -33_554_432..=33_554_431 261 | // y in 12 bits: -2048..=2047 262 | let x_masked = (x & 0x3FFFFFF) as u64; // 26 bits 263 | let y_masked = (y & 0xFFF) as u64; // 12 bits 264 | let z_masked = (z & 0x3FFFFFF) as u64; // 26 bits 265 | 266 | // Bit layout: [ x: 26 bits ][ y: 12 bits ][ z: 26 bits ] 267 | // x -> bits 38..63, y -> bits 26..37, z -> bits 0..25 268 | let packed = (x_masked << 38) | (y_masked << 26) | z_masked; 269 | 270 | buffer.extend_from_slice(&packed.to_be_bytes()); 271 | } 272 | 273 | pub fn write_location2(buffer: &mut Vec, loc: &Location2) { 274 | write_f64(buffer, loc.x); 275 | write_f64(buffer, loc.y); 276 | write_f64(buffer, loc.z); 277 | } 278 | 279 | pub fn write_direction(buffer: &mut Vec, loc: &Direction) { 280 | write_f32(buffer, loc.x); 281 | write_f32(buffer, loc.y); 282 | } 283 | 284 | pub fn write_rotation(buffer: &mut Vec, loc: &Rotation) { 285 | let eulers = loc.euler_angles(); 286 | write_f32(buffer, eulers.0); 287 | write_f32(buffer, eulers.1); 288 | write_f32(buffer, eulers.2); 289 | } 290 | 291 | pub fn write_angle(buffer: &mut Vec, angle: f32) { 292 | write_i8(buffer, axis_to_angle(angle)); 293 | } 294 | 295 | pub fn write_direction_as_angles(buffer: &mut Vec, loc: &Direction) { 296 | write_angle(buffer, loc.x); 297 | write_angle(buffer, loc.y); 298 | } 299 | 300 | pub fn write_nbt(buffer: &mut Vec, value: &fastnbt::Value) { 301 | let bytes = match fastnbt::to_bytes(value) { 302 | Ok(data) => data, 303 | Err(e) => { 304 | warn!("Failed to serialize NBT data: {}", e); 305 | return; 306 | } 307 | }; 308 | buffer.extend_from_slice(&bytes); 309 | } 310 | 311 | pub fn compress_packet(mut packet: &[u8], threshold: u32) -> Result, KittyMCError> { 312 | let mut total_size = 0; 313 | let raw_packet_length = read_varint_u32(&mut packet, &mut total_size)?; 314 | 315 | let mut new_packet; 316 | if raw_packet_length >= threshold { 317 | new_packet = compress_to_vec_zlib(packet, 5); 318 | write_varint_u32_splice(&mut new_packet, raw_packet_length, ..0); 319 | } else { 320 | new_packet = packet.to_vec(); 321 | write_varint_u32_splice(&mut new_packet, 0, ..0); 322 | } 323 | 324 | let new_packet_len = new_packet.len() as u32; 325 | write_varint_u32_splice(&mut new_packet, new_packet_len, ..0); 326 | 327 | Ok(new_packet) 328 | } 329 | 330 | pub fn decompress_packet(mut compressed_packet: &[u8]) -> Result<(usize, Vec), KittyMCError> { 331 | let mut header_size = 0; 332 | let compressed_packet_length = 333 | read_varint_u32(&mut compressed_packet, &mut header_size)? as usize; 334 | let total_size = compressed_packet_length + header_size; 335 | 336 | if compressed_packet.len() < compressed_packet_length { 337 | return Err(KittyMCError::NotEnoughData( 338 | compressed_packet.len(), 339 | compressed_packet_length, 340 | )); 341 | } 342 | 343 | header_size = 0; 344 | let uncompressed_data_length = read_varint_u32(&mut compressed_packet, &mut header_size)?; 345 | 346 | let uncompressed_packet = 347 | decompress_to_vec_zlib_with_limit(compressed_packet, uncompressed_data_length as usize) 348 | .map_err(|e| KittyMCError::ZlibDecompressionError(e))?; 349 | 350 | Ok((total_size, uncompressed_packet)) 351 | } 352 | -------------------------------------------------------------------------------- /kittymc_lib/src/packets/client/play/chunk_data_20.rs: -------------------------------------------------------------------------------- 1 | use crate::subtypes::Location; 2 | use crate::packets::packet_serialization::{ 3 | write_bool, write_i32, write_u64, write_u8, write_varint_u32, write_varint_u32_splice, 4 | SerializablePacket, 5 | }; 6 | use crate::packets::wrap_packet; 7 | use kittymc_macros::Packet; 8 | use lazy_static::lazy_static; 9 | use std::collections::HashMap; 10 | use std::path::Path; 11 | use savefile::{load_file, save_file_compressed}; 12 | use savefile_derive::Savefile; 13 | use crate::error::KittyMCError; 14 | 15 | const NUM_SECTIONS_PER_CHUNK_COLUMN: usize = 16; 16 | 17 | const SECTION_WIDTH: usize = 16; 18 | const SECTION_HEIGHT: usize = 16; 19 | const SECTION_SIZE: usize = SECTION_WIDTH * SECTION_HEIGHT * SECTION_WIDTH; 20 | 21 | const MAX_PALETTE_BITS: u8 = 8; 22 | 23 | const GLOBAL_BITS_PER_BLOCK: u8 = 13; 24 | 25 | pub type BlockStateId = u32; 26 | 27 | #[derive(PartialEq, Debug, Clone)] 28 | pub struct ChunkSection { 29 | bits_per_block: u8, 30 | palette: Vec, 31 | data: Vec, 32 | block_light: Vec, 33 | sky_light: Vec, 34 | section_y: u32, 35 | } 36 | 37 | impl ChunkSection { 38 | pub fn new(bits_per_block: u8, section_y: u32) -> Self { 39 | ChunkSection { 40 | bits_per_block, 41 | palette: Vec::new(), 42 | data: Vec::new(), 43 | block_light: vec![16; SECTION_SIZE / 2], // 2048 bytes 44 | sky_light: vec![16; SECTION_SIZE / 2], // 2048 bytes 45 | section_y, 46 | } 47 | } 48 | 49 | pub fn write(&self, out: &mut Vec, has_sky_light: bool) { 50 | write_u8(out, self.bits_per_block); 51 | 52 | if self.bits_per_block <= MAX_PALETTE_BITS { 53 | write_varint_u32(out, self.palette.len() as u32); 54 | for &pal_entry in &self.palette { 55 | write_varint_u32(out, pal_entry); 56 | } 57 | } else { 58 | write_varint_u32(out, 0); 59 | } 60 | 61 | write_varint_u32(out, self.data.len() as u32); 62 | 63 | for &val in &self.data { 64 | write_u64(out, val); 65 | } 66 | 67 | assert_eq!(self.block_light.len(), 2048); 68 | assert_eq!(self.sky_light.len(), 2048); 69 | 70 | for &light_byte in &self.block_light { 71 | write_u8(out, light_byte); 72 | } 73 | 74 | if has_sky_light { 75 | for &light_byte in &self.sky_light { 76 | write_u8(out, light_byte); 77 | } 78 | } 79 | } 80 | 81 | pub fn section_y(&self) -> u32 { 82 | self.section_y 83 | } 84 | } 85 | 86 | #[derive(PartialEq, Debug, Clone, Savefile)] 87 | pub struct Chunk { 88 | pub blocks: Vec, 89 | pub biomes: Vec, 90 | } 91 | 92 | impl Default for Chunk { 93 | fn default() -> Self { 94 | Chunk { 95 | blocks: vec![0; SECTION_SIZE * NUM_SECTIONS_PER_CHUNK_COLUMN], 96 | biomes: vec![1; 16 * 16], 97 | } 98 | } 99 | } 100 | 101 | impl Chunk { 102 | pub fn get_block(&self, x: usize, y: usize, z: usize) -> Option { 103 | self.blocks.get(y * 16 * 16 + z * 16 + x).cloned() 104 | } 105 | 106 | pub fn set_block(&mut self, x: usize, y: usize, z: usize, state: BlockStateId) -> Result<(), KittyMCError> { 107 | let Some(block) = self.blocks.get_mut(y * 16 * 16 + z * 16 + x) else { 108 | return Err(KittyMCError::InvalidBlock(Location::new(x as f32, y as f32, z as f32))); 109 | }; 110 | 111 | *block = state; 112 | Ok(()) 113 | } 114 | 115 | fn is_section_empty(&self, section_y: usize) -> bool { 116 | let start_y = section_y * SECTION_HEIGHT; 117 | let end_y = start_y + SECTION_HEIGHT; 118 | for y in start_y..end_y { 119 | for z in 0..SECTION_WIDTH { 120 | for x in 0..SECTION_WIDTH { 121 | if self.get_block(x, y, z).is_some_and(|b| b != 0) { 122 | return false; 123 | } 124 | } 125 | } 126 | } 127 | true 128 | } 129 | 130 | pub fn to_chunk_sections(&self, primary_bit_mask_out: &mut u32) -> Vec { 131 | let mut sections = Vec::new(); 132 | 133 | for section_y in 0..NUM_SECTIONS_PER_CHUNK_COLUMN { 134 | if self.is_section_empty(section_y) { 135 | continue; 136 | } 137 | 138 | *primary_bit_mask_out |= 1 << section_y; 139 | 140 | let mut block_states = Vec::with_capacity(SECTION_SIZE); 141 | let base_y = section_y * SECTION_HEIGHT; 142 | for y in 0..SECTION_HEIGHT { 143 | for z in 0..SECTION_WIDTH { 144 | for x in 0..SECTION_WIDTH { 145 | let global_y = base_y + y; 146 | block_states.push(self.get_block(x, global_y, z).unwrap()); 147 | } 148 | } 149 | } 150 | 151 | let mut distinct_map = HashMap::new(); 152 | for &block_id in &block_states { 153 | distinct_map.entry(block_id).or_insert(true); 154 | } 155 | let distinct_count = distinct_map.len(); 156 | 157 | let bits_per_block = if distinct_count <= 1 { 158 | 4 159 | } else if distinct_count > 256 { 160 | GLOBAL_BITS_PER_BLOCK 161 | } else { 162 | let b = (64 - (distinct_count - 1).leading_zeros()) as u8; 163 | let b = b.max(4).min(MAX_PALETTE_BITS); 164 | b 165 | }; 166 | 167 | let mut section = ChunkSection::new(bits_per_block, section_y as u32); 168 | 169 | let mut palette_index_map = HashMap::new(); 170 | if bits_per_block <= MAX_PALETTE_BITS { 171 | let mut local_palette: Vec = distinct_map.keys().copied().collect(); 172 | local_palette.sort_unstable(); 173 | 174 | for (idx, &bs) in local_palette.iter().enumerate() { 175 | palette_index_map.insert(bs, idx as u64); 176 | } 177 | section.palette = local_palette; 178 | } 179 | 180 | let mut data_words = Vec::new(); 181 | let mut current_word = 0u64; 182 | let mut bits_filled = 0; 183 | 184 | for &block_id in &block_states { 185 | let index = if bits_per_block <= MAX_PALETTE_BITS { 186 | *palette_index_map.get(&block_id).unwrap() 187 | } else { 188 | block_id as u64 189 | }; 190 | 191 | // put index in current_word 192 | current_word |= index << bits_filled; 193 | bits_filled += bits_per_block; 194 | 195 | if bits_filled >= 64 { 196 | data_words.push(current_word); 197 | current_word = 0; 198 | bits_filled = 0; 199 | } 200 | } 201 | if bits_filled > 0 { 202 | data_words.push(current_word); 203 | } 204 | 205 | section.data = data_words; 206 | 207 | for byte in &mut section.block_light { 208 | *byte = 0x0; 209 | } 210 | for byte in &mut section.sky_light { 211 | *byte = 0xFF; 212 | } 213 | 214 | sections.push(section); 215 | } 216 | 217 | sections 218 | } 219 | 220 | pub fn write( 221 | &self, 222 | buf: &mut Vec, 223 | ground_up_continuous: bool, 224 | dimension_has_sky_light: bool, 225 | primary_bit_mask: &mut u32, 226 | ) { 227 | let start_len = buf.len(); 228 | 229 | let sections = self.to_chunk_sections(primary_bit_mask); 230 | 231 | for section_y in 0..NUM_SECTIONS_PER_CHUNK_COLUMN { 232 | if (*primary_bit_mask & (1 << section_y)) == 0 { 233 | continue; 234 | } 235 | let section = §ions 236 | .iter() 237 | .find(|s| s.section_y() == section_y as u32) 238 | .unwrap(); 239 | section.write(buf, dimension_has_sky_light); 240 | } 241 | 242 | if ground_up_continuous { 243 | buf.extend_from_slice(self.biomes.as_slice()); 244 | } 245 | 246 | let full_size = (buf.len() - start_len) as u32; 247 | write_varint_u32_splice(buf, full_size, start_len..start_len); 248 | } 249 | 250 | pub fn save_to(&self, path: &Path) -> Result<(), KittyMCError> { 251 | Ok(save_file_compressed(path, 0, self)?) 252 | } 253 | 254 | pub fn load_from(path: &Path) -> Result, KittyMCError> { 255 | Ok(load_file(path, 0)?) 256 | } 257 | } 258 | 259 | #[derive(PartialEq, Debug, Clone, Packet)] 260 | pub struct ChunkDataPacket<'a> { 261 | x: i32, 262 | z: i32, 263 | ground_up_continuous: bool, 264 | data: &'a Chunk, 265 | block_entities: Vec<()>, 266 | } 267 | 268 | lazy_static! { 269 | pub static ref DEFAULT_FLAT_CHUNK: Box = { 270 | let mut chunk = Chunk::default(); 271 | 272 | for x in 0..SECTION_WIDTH { 273 | for z in 0..SECTION_WIDTH { 274 | chunk.set_block(x, 0, z, 0b0111_0000).unwrap(); 275 | for y in 1..4 { 276 | chunk.set_block(x, y, z, 0b0001_0000).unwrap(); 277 | } 278 | chunk.set_block(x, 4, z, 5 << 4).unwrap(); 279 | } 280 | } 281 | 282 | for x in 0..16 { 283 | for z in 5..11 { 284 | let block: u32 = (35 << 4) 285 | | match z { 286 | 5 => 14, 287 | 6 => 1, 288 | 7 => 4, 289 | 8 => 5, 290 | n => n as u32, 291 | }; 292 | 293 | chunk.set_block(x, 4, z, block).unwrap(); 294 | } 295 | } 296 | 297 | Box::new(chunk) 298 | }; 299 | 300 | pub static ref DEFAULT_FLAT_CHUNK_2: Box = { 301 | let mut chunk = Chunk::default(); 302 | 303 | for x in 0..SECTION_WIDTH { 304 | for z in 0..SECTION_WIDTH { 305 | chunk.set_block(x, 0, z, 0b0111_0000).unwrap(); 306 | for y in 1..4 { 307 | chunk.set_block(x, y, z, 0b0001_0000).unwrap(); 308 | } 309 | chunk.set_block(x, 4, z, 5 << 4).unwrap(); 310 | } 311 | } 312 | 313 | for x in 0..16 { 314 | for z in 5..10 { 315 | let block: u32 = (35 << 4) 316 | | match z { 317 | 5 | 9 => 3, 318 | 6 | 8 => 6, 319 | 7 => 0, 320 | n => n as u32, 321 | }; 322 | 323 | chunk.set_block(x, 4, z, block).unwrap(); 324 | } 325 | } 326 | 327 | Box::new(chunk) 328 | }; 329 | } 330 | 331 | impl ChunkDataPacket<'_> { 332 | pub fn default_at(x: i32, z: i32) -> Self { 333 | ChunkDataPacket { 334 | x, 335 | z, 336 | ground_up_continuous: true, 337 | data: &DEFAULT_FLAT_CHUNK, 338 | block_entities: vec![], 339 | } 340 | } 341 | 342 | pub fn new(chunk: &Chunk, x: i32, z: i32) -> ChunkDataPacket { 343 | ChunkDataPacket { 344 | x, 345 | z, 346 | ground_up_continuous: true, 347 | data: chunk, 348 | block_entities: vec![], 349 | } 350 | } 351 | } 352 | 353 | impl SerializablePacket for ChunkDataPacket<'_> { 354 | fn serialize(&self) -> Vec { 355 | let mut packet = vec![]; 356 | 357 | write_i32(&mut packet, self.x); 358 | write_i32(&mut packet, self.z); 359 | write_bool(&mut packet, self.ground_up_continuous); 360 | 361 | let mask_pos = packet.len(); 362 | 363 | let mut primary_bit_mask = 0u32; 364 | self.data.write( 365 | &mut packet, 366 | self.ground_up_continuous, 367 | true, 368 | &mut primary_bit_mask, 369 | ); // TODO: Nobody knows if this is an overworld chunk or not yet 370 | 371 | self.data.to_chunk_sections(&mut primary_bit_mask); 372 | write_varint_u32_splice(&mut packet, primary_bit_mask, mask_pos..mask_pos); 373 | 374 | write_varint_u32(&mut packet, 0u32); // TODO: Implement Block Entitites 375 | 376 | wrap_packet(&mut packet, Self::id()); 377 | 378 | packet 379 | } 380 | 381 | fn id() -> u32 { 382 | 0x20 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /kittymc_server/src/client.rs: -------------------------------------------------------------------------------- 1 | use kittymc_lib::packets::packet_serialization::NamedPacket; 2 | use log::error; 3 | use std::collections::HashSet; 4 | use std::fmt::Debug; 5 | use std::io; 6 | use std::io::{ErrorKind, Read, Write}; 7 | use std::net::{SocketAddr, TcpListener, TcpStream}; 8 | use std::time::{Duration, Instant}; 9 | use uuid::Uuid; 10 | 11 | use tracing::{debug, info, instrument, trace, warn}; 12 | 13 | use crate::player::Player; 14 | use kittymc_lib::error::KittyMCError; 15 | use kittymc_lib::packets::client::play::keep_alive_1f::ServerKeepAlivePacket; 16 | use kittymc_lib::packets::client::play::player_list_item_2e::PlayerListItemAction; 17 | use kittymc_lib::packets::client::play::{ 18 | ChunkDataPacket, GameMode, PlayerListItemPacket, SpawnPlayerPacket, UnloadChunkPacket, 19 | }; 20 | use kittymc_lib::packets::packet_serialization::compress_packet; 21 | use kittymc_lib::packets::{packet_serialization::SerializablePacket, CompressionInfo, Packet}; 22 | use kittymc_lib::subtypes::metadata::EntityMetadata; 23 | use kittymc_lib::subtypes::state::State; 24 | use kittymc_lib::subtypes::{ChunkPosition, Location}; 25 | use kittymc_lib::utils::rainbowize_cool_people_textcomp; 26 | use crate::chunking::chunk_manager::ChunkManager; 27 | 28 | const DEFAULT_CHUNK_LOAD_RADIUS: u32 = 4; 29 | 30 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 31 | pub struct ClientInfo { 32 | pub uuid: Uuid, 33 | pub username: String, 34 | } 35 | 36 | #[derive(Debug)] 37 | #[allow(dead_code)] 38 | pub struct Client { 39 | connected_at: Instant, 40 | socket: TcpStream, 41 | addr: SocketAddr, 42 | current_state: State, 43 | uuid: Uuid, 44 | last_heartbeat: Instant, 45 | last_heartbeat_id: u64, 46 | last_backbeat: Instant, 47 | buffer: Vec, 48 | buffer_size: usize, 49 | fragmented: bool, 50 | compression: CompressionInfo, 51 | brand: Option, 52 | loaded_chunks: HashSet, 53 | view_distance: u32, 54 | pub(crate) load_initial_chunks: bool, 55 | } 56 | 57 | #[allow(dead_code)] 58 | impl Client { 59 | #[instrument(skip(server))] 60 | pub fn accept(server: &TcpListener) -> Result, KittyMCError> { 61 | let (socket, addr) = match server.accept() { 62 | Ok(socket) => socket, 63 | Err(e) if e.kind() == ErrorKind::WouldBlock => return Ok(None), 64 | Err(e) => Err(e)?, 65 | }; 66 | 67 | socket 68 | .set_nonblocking(true) 69 | .expect("Couldn't set socket to nonblocking"); 70 | 71 | Client::new(socket, addr).map(Some) 72 | } 73 | 74 | #[instrument(skip(socket, addr))] 75 | pub fn new(socket: TcpStream, addr: SocketAddr) -> Result { 76 | info!("[{}] Client connected", addr); 77 | 78 | Ok(Client { 79 | connected_at: Instant::now(), 80 | socket, 81 | addr, 82 | current_state: State::Handshake, 83 | uuid: Uuid::default(), 84 | last_heartbeat: Instant::now(), 85 | last_heartbeat_id: 0, 86 | last_backbeat: Instant::now(), 87 | buffer: vec![0; 2048], 88 | buffer_size: 0, 89 | fragmented: false, 90 | compression: CompressionInfo::default(), 91 | brand: None, 92 | loaded_chunks: HashSet::new(), 93 | view_distance: DEFAULT_CHUNK_LOAD_RADIUS, 94 | load_initial_chunks: true, 95 | }) 96 | } 97 | 98 | pub fn try_clone(&self) -> io::Result { 99 | Ok(Client { 100 | connected_at: self.connected_at, 101 | socket: self.socket.try_clone()?, 102 | addr: self.addr, 103 | current_state: self.current_state, 104 | uuid: Uuid::default(), 105 | last_heartbeat: self.last_heartbeat, 106 | last_heartbeat_id: self.last_heartbeat_id, 107 | last_backbeat: self.last_backbeat, 108 | buffer: vec![], 109 | buffer_size: 0, 110 | fragmented: false, 111 | compression: self.compression.clone(), 112 | brand: self.brand.clone(), 113 | loaded_chunks: self.loaded_chunks.clone(), 114 | view_distance: self.view_distance, 115 | load_initial_chunks: self.load_initial_chunks, 116 | }) 117 | } 118 | 119 | pub fn addr(&self) -> &SocketAddr { 120 | &self.addr 121 | } 122 | 123 | pub fn set_state(&mut self, state: State) { 124 | if state == State::Play { 125 | self.last_heartbeat = Instant::now(); 126 | self.last_backbeat = Instant::now(); 127 | } 128 | 129 | self.current_state = state; 130 | } 131 | 132 | pub fn set_uuid(&mut self, uuid: Uuid) { 133 | self.uuid = uuid; 134 | } 135 | 136 | pub fn set_compression(&mut self, compress: bool, threshold: u32) { 137 | self.compression.enabled = compress; 138 | self.compression.compression_threshold = threshold; 139 | } 140 | 141 | pub fn set_brand(&mut self, brand: String) { 142 | self.brand = Some(brand); 143 | } 144 | 145 | // TODO: Something is broken with compression 146 | #[instrument(skip(self, b_packet))] 147 | pub fn send_packet_raw(&mut self, b_packet: &[u8]) -> Result<(), KittyMCError> { 148 | if self.compression.enabled { 149 | let compressed = compress_packet(b_packet, self.compression.compression_threshold)?; 150 | self.socket.write_all(&compressed)?; 151 | } else { 152 | self.socket.write_all(b_packet)?; 153 | } 154 | 155 | Ok(()) 156 | } 157 | 158 | pub fn set_view_distance(&mut self, view_distance: u32) { 159 | self.view_distance = view_distance; 160 | } 161 | 162 | #[instrument(skip(self, packet))] 163 | pub fn send_packet( 164 | &mut self, 165 | packet: &P, 166 | ) -> Result<(), KittyMCError> { 167 | if !matches!(P::name(), "ChunkDataPacket" | "UnloadChunkPacket") { 168 | debug!( 169 | "[{}] OUT >>> {}(0x{:x?})({})", 170 | self.addr, 171 | P::name(), 172 | P::id(), 173 | P::id() 174 | ); 175 | } 176 | self.send_packet_raw(&packet.serialize())?; 177 | Ok(()) 178 | } 179 | 180 | pub fn add_player_to_player_list(&mut self, player: &Player) -> Result<(), KittyMCError> { 181 | let display_name = rainbowize_cool_people_textcomp(player.name(), true); 182 | self.send_packet(&PlayerListItemPacket { 183 | actions: vec![( 184 | *player.uuid(), 185 | PlayerListItemAction::AddPlayer { 186 | name: player.name().to_string(), 187 | properties: vec![], 188 | game_mode: GameMode::Survival, 189 | ping: 0, // TODO: fix ping 190 | display_name, 191 | }, 192 | )], 193 | }) 194 | } 195 | 196 | pub fn spawn_player(&mut self, player: &Player) -> Result<(), KittyMCError> { 197 | if &self.uuid == player.uuid() { 198 | return Ok(()); 199 | } 200 | self.send_packet(&SpawnPlayerPacket { 201 | entity_id: player.id(), 202 | player_uuid: *player.uuid(), 203 | location: *player.position(), 204 | direction: *player.direction(), 205 | metadata: EntityMetadata::default(), 206 | }) 207 | } 208 | 209 | pub fn do_heartbeat(&mut self) -> Result { 210 | if self.current_state != State::Play { 211 | return Ok(true); 212 | } 213 | 214 | if self.last_heartbeat.elapsed() >= Duration::from_secs(5) { 215 | self.last_heartbeat_id = rand::random(); 216 | self.send_packet(&ServerKeepAlivePacket::new(self.last_heartbeat_id))?; 217 | self.last_heartbeat = Instant::now(); 218 | } 219 | 220 | Ok(self.last_backbeat.elapsed() <= Duration::from_secs(30)) 221 | } 222 | 223 | pub fn register_backbeat(&mut self, _id: u64) { 224 | // TODO: Should probably store four heartbeat ids and then see if any matches 225 | //if self.last_heartbeat_id == id { 226 | self.last_backbeat = Instant::now(); 227 | //} 228 | } 229 | 230 | #[instrument(skip(self))] 231 | pub fn fetch_packet(&mut self) -> Result, KittyMCError> { 232 | let mut n = self.buffer_size; 233 | if n == self.buffer.len() { 234 | // buffer has not enough space to fit packet. so extend it. 235 | self.buffer.resize(n + 2048, 0); 236 | trace!("[{}] Increased buffer size to fit bigger packet", self.addr); 237 | } 238 | let max_len = self.buffer.len(); 239 | match self.socket.read(&mut self.buffer[n..max_len]) { 240 | Ok(0) => { 241 | // The other side closed the connection 242 | return Err(KittyMCError::Disconnected); 243 | } 244 | Ok(new_n) => { 245 | self.fragmented = false; 246 | n += new_n; 247 | } 248 | Err(e) if e.kind() == ErrorKind::WouldBlock => { 249 | if n == 0 || self.fragmented { 250 | return Ok(None); 251 | } 252 | } 253 | Err(e) => return Err(e.into()), 254 | } 255 | self.buffer_size = n; 256 | 257 | trace!( 258 | "[{}] Complete Received Data : {:?}", 259 | self.addr, 260 | &self.buffer[..n] 261 | ); 262 | 263 | let (packet_len, packet) = 264 | match Packet::deserialize(self.current_state, &self.buffer[..n], &self.compression) { 265 | Ok(packet) => { 266 | debug!( 267 | "[{}] IN <<< {}(0x{:x?})({})", 268 | self.addr, 269 | packet.1.name(), 270 | packet.1.id(), 271 | packet.1.id() 272 | ); 273 | (packet.0, Some(packet.1)) 274 | } 275 | Err(KittyMCError::NotEnoughData(_, _)) => { 276 | trace!("[{}] Not enough data. Waiting for more", self.addr); 277 | self.fragmented = true; 278 | return Ok(None); 279 | } 280 | Err(KittyMCError::NotImplemented(packet_id, packet_len)) => { 281 | warn!( 282 | "[{}] IN UNIMPLEMENTED <<< UNKNOWN(0x{:x?})({}) (len: {})", 283 | self.addr, packet_id, packet_id, packet_len 284 | ); 285 | (packet_len, None) 286 | } 287 | Err(e) => { 288 | warn!("[{}] Error when deserializing packet: {}", self.addr, e); 289 | warn!( 290 | "[{}] Packet started with : {:?}", 291 | self.addr, 292 | &self.buffer[..n] 293 | ); 294 | return Err(e); 295 | } 296 | }; 297 | 298 | self.buffer_size = n - packet_len; 299 | self.buffer.drain(0..packet_len); 300 | 301 | if self.buffer_size < 2048 { 302 | self.buffer.resize(2048, 0); // shouldn't be able to become smaller than 2048 303 | } 304 | 305 | Ok(packet) 306 | } 307 | 308 | /// Returns true if all of its chunks could be loaded 309 | pub fn load_chunks<'a, I>( 310 | &mut self, 311 | positions: I, 312 | chunk_manager: &mut ChunkManager, 313 | ) -> Result 314 | where 315 | I: Iterator, 316 | { 317 | let mut all_loaded = true; 318 | for pos in positions { 319 | if self.loaded_chunks.contains(pos) { 320 | chunk_manager.tap_chunk(pos); 321 | continue; 322 | } 323 | let Some(chunk) = chunk_manager.request_chunk(pos) else { 324 | all_loaded = false; 325 | continue; 326 | }; 327 | 328 | { 329 | let chunk = chunk.read().unwrap(); 330 | let packet = ChunkDataPacket::new( 331 | chunk.as_ref(), 332 | pos.chunk_x() as i32, 333 | pos.chunk_z() as i32, 334 | ); 335 | self.send_packet(&packet)?; 336 | } 337 | self.loaded_chunks.insert(pos.clone()); 338 | } 339 | 340 | Ok(all_loaded) 341 | } 342 | 343 | pub fn unload_chunks<'a, I>(&mut self, positions: I) 344 | where 345 | I: Iterator, 346 | { 347 | for pos in positions { 348 | let packet = UnloadChunkPacket::new(pos); 349 | match self.send_packet(&packet) { 350 | Ok(_) => { 351 | self.loaded_chunks.remove(pos); 352 | } 353 | _ => error!("Unloading chunk failed"), 354 | } 355 | } 356 | } 357 | 358 | /// Returns if all its chunks could be loaded 359 | #[instrument(skip(self, pos, chunk_manager))] 360 | pub fn update_chunks( 361 | &mut self, 362 | pos: &Location, 363 | chunk_manager: &mut ChunkManager, 364 | ) -> Result { 365 | let new: Vec<_> = 366 | ChunkPosition::iter_xz_circle_in_range(pos, self.view_distance as f32 * 16.).collect(); 367 | let chunks_to_unload = self 368 | .loaded_chunks 369 | .iter() 370 | .filter(|c| !new.contains(c)) 371 | .cloned() 372 | .collect::>(); 373 | 374 | let all_loaded = self.load_chunks(new.iter(), chunk_manager)?; 375 | self.unload_chunks(chunks_to_unload.iter()); 376 | 377 | Ok(all_loaded) 378 | } 379 | } 380 | --------------------------------------------------------------------------------