From 0cdc71ab8a14f8b8d8e5cbbd4bbf7a30876ae261 Mon Sep 17 00:00:00 2001 From: TriMill Date: Fri, 2 Dec 2022 18:27:53 -0500 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 230 +++++++++++++++++++++++++ Cargo.toml | 13 ++ plugins/example_plugin/main.lua | 23 +++ src/main.rs | 30 ++++ src/network/mod.rs | 243 ++++++++++++++++++++++++++ src/plugins.rs | 128 ++++++++++++++ src/protocol/clientbound.rs | 204 ++++++++++++++++++++++ src/protocol/data.rs | 284 +++++++++++++++++++++++++++++++ src/protocol/mod.rs | 18 ++ src/protocol/serverbound.rs | 118 +++++++++++++ src/resources/registry_codec.nbt | Bin 0 -> 24946 bytes 12 files changed, 1292 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 plugins/example_plugin/main.lua create mode 100644 src/main.rs create mode 100644 src/network/mod.rs create mode 100644 src/plugins.rs create mode 100644 src/protocol/clientbound.rs create mode 100644 src/protocol/data.rs create mode 100644 src/protocol/mod.rs create mode 100644 src/protocol/serverbound.rs create mode 100644 src/resources/registry_codec.nbt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3d154c0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,230 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "hematite-nbt" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670d0784ee67cfb57393dc1837867d2951f9a59ca7db99a653499c854f745739" +dependencies = [ + "byteorder", + "cesu8", + "flate2", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mlua" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4351dbcc863fb6249c81b3bd0c8001214e9d4d44d22cabda17026353a77fe612" +dependencies = [ + "bstr", + "cc", + "num-traits", + "once_cell", + "pkg-config", + "rustc-hash", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quectocraft" +version = "0.1.0" +dependencies = [ + "hematite-nbt", + "mlua", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "serde" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a34df41 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "quectocraft" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hematite-nbt = "0.5" +serde_json = "1.0" +serde = "1.0" +mlua = { version = "0.8", features = ["lua54"] } +uuid = "1.2" diff --git a/plugins/example_plugin/main.lua b/plugins/example_plugin/main.lua new file mode 100644 index 0000000..5097c82 --- /dev/null +++ b/plugins/example_plugin/main.lua @@ -0,0 +1,23 @@ +local plugin = { + id = "example_plugin", + name = "Example Plugin", + version = "0.1.0", +} + +function plugin.init() + print("PLUGIN init") +end + +function plugin.playerJoin(name, uuid) + print("PLUGIN player joined: " .. name .. " uuid " .. uuid) +end + +function plugin.playerLeave(name, uuid) + print("PLUGIN player left: " .. name .. " uuid " .. uuid) +end + +function plugin.chatMessage(message, author, authorUuid) + print("PLUGIN message from " .. author .. ": " .. message) +end + +return plugin diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..069ff2a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,30 @@ +use std::time::Duration; + +use mlua::Lua; +use network::NetworkServer; +use plugins::{Plugin, Plugins}; + +mod plugins; +mod protocol; +mod network; + +fn main() { + let lua = Lua::new(); + let plugin = Plugin::load("plugins/example_plugin/main.lua".into(), &lua).unwrap(); + let mut plugins = Plugins::new(&lua).unwrap(); + plugins.add_plugin(plugin); + + let mut server = NetworkServer::new("127.0.0.1:25565".to_owned(), plugins); + let sleep_dur = Duration::from_millis(5); + let mut i = 0; + loop { + server.get_new_clients(); + server.handle_connections(); + std::thread::sleep(sleep_dur); + if i % 1024 == 0 { + server.send_keep_alive(); + i = 0; + } + i += 1; + } +} diff --git a/src/network/mod.rs b/src/network/mod.rs new file mode 100644 index 0000000..ed6533e --- /dev/null +++ b/src/network/mod.rs @@ -0,0 +1,243 @@ +use std::{net::{TcpStream, TcpListener, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, channel, TryRecvError}, time::Duration}; + +use serde_json::json; +use uuid::Uuid; + +use crate::{protocol::{data::{PacketDecoder, PacketEncoder}, serverbound::*, clientbound::*, NetworkState}, plugins::Plugins}; + +pub struct NetworkServer<'lua> { + plugins: Plugins<'lua>, + new_clients: Receiver, + clients: Vec, +} + +impl <'lua> NetworkServer<'lua> { + pub fn new(addr: String, plugins: Plugins<'lua>) -> Self { + let (send, recv) = channel(); + thread::spawn(move || Self::listen(&addr, send)); + plugins.init(); + Self { + plugins, + new_clients: recv, + clients: Vec::new(), + } + } + + fn listen(addr: &str, send_clients: Sender) { + println!("listening for connections"); + let listener = TcpListener::bind(addr).unwrap(); + for (id, stream) in listener.incoming().enumerate() { + let stream = stream.unwrap(); + println!("got connection from {} (id {})", stream.peer_addr().unwrap(), id); + let stream_2 = stream.try_clone().unwrap(); + let (send, recv) = channel(); + thread::spawn(|| NetworkClient::listen(stream_2, send)); + let client = NetworkClient { + id: id as i32, + play: false, + closed: false, + stream, + serverbound: recv, + player: None, + }; + send_clients.send(client).unwrap(); + } + } + + pub fn get_new_clients(&mut self) { + while let Ok(client) = self.new_clients.try_recv() { + self.clients.push(client); + } + } + + pub fn send_keep_alive(&mut self) { + let mut closed = Vec::new(); + for client in self.clients.iter_mut() { + if client.play { + if let Err(_) = client.send_packet(ClientBoundPacket::KeepAlive(0)) { + client.close(); + if let Some(pl) = &client.player { + self.plugins.player_leave(pl); + } + closed.push(client.id); + } + } + } + self.clients.retain(|x| !closed.contains(&x.id)); + } + + pub fn handle_connections(&mut self) { + let mut closed = Vec::new(); + for i in 0..self.clients.len() { + let client: &mut NetworkClient = unsafe { + &mut *(self.clients.get_unchecked_mut(i) as *mut _) + }; + let mut alive = true; + while let Some(packet) = client.recv_packet(&mut alive) { + if let Err(_) = self.handle_packet(client, packet) { + alive = false; + break; + } + } + if !alive && !client.closed { + closed.push(client.id); + if let Some(pl) = &client.player { + self.plugins.player_leave(pl); + } + client.close(); + } + } + self.clients.retain(|x| !closed.contains(&x.id)); + } + + fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> { + match packet { + ServerBoundPacket::Ignored(_) => (), + ServerBoundPacket::Unknown(id) => println!("unknown: {}", id), + ServerBoundPacket::Handshake(_) => (), + ServerBoundPacket::StatusRequest() + => client.send_packet(ClientBoundPacket::StatusResponse( + r#"{"version":{"name":"1.19.2","protocol":760}}"#.to_owned() + ))?, + ServerBoundPacket::PingRequest(n) => { + client.send_packet(ClientBoundPacket::PingResponse(n))?; + client.close(); + } + ServerBoundPacket::LoginStart(login_start) => { + client.player = Some(Player { + name: login_start.name.clone(), + uuid: login_start.uuid.unwrap(), + }); + client.play = true; + client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess { + name: login_start.name, + uuid: login_start.uuid.unwrap(), + }))?; + self.plugins.player_join(client.player.as_ref().unwrap()); + self.post_login(client)?; + } + ServerBoundPacket::ChatMessage(msg) => { + self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message); + } + } + Ok(()) + } + + fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { + client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay { + eid: client.id, + is_hardcore: false, + gamemode: 1, + prev_gamemode: 1, + dimensions: vec![ + "minecraft:world".to_owned(), + ], + registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(), + dimension_type: "minecraft:the_end".to_owned(), + dimension_name: "minecraft:world".to_owned(), + seed_hash: 0, + max_players: 0, + view_distance: 8, + sim_distance: 8, + reduced_debug_info: false, + respawn_screen: false, + is_debug: false, + is_flat: false, + death_location: None, + }))?; + client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage { + channel: "minecraft:brand".to_owned(), + data: { + let mut data = Vec::new(); + data.write_string(32767, "QuectoCraft"); + data + } + }))?; + client.send_packet(ClientBoundPacket::PlayerAbilities(0x0d, 0.05, 0.1))?; + let mut chunk_data: Vec = Vec::new(); + for _ in 0..(384 / 16) { + // number of non-air blocks + chunk_data.write_short(0); + // block states + chunk_data.write_ubyte(0); + chunk_data.write_varint(0); + chunk_data.write_varint(0); + // biomes + chunk_data.write_ubyte(0); + chunk_data.write_varint(0); + chunk_data.write_varint(0); + } + let hmdata = vec![0i64; 37]; + let mut heightmap = nbt::Blob::new(); + heightmap.insert("MOTION_BLOCKING", hmdata).unwrap(); + client.send_packet(ClientBoundPacket::ChunkData(ChunkData { + x: 0, + z: 0, + heightmap, + chunk_data, + }))?; + client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition { + x: 0.0, + y: 64.0, + z: 0.0, + yaw: 0.0, + pitch: 0.0, + flags: 0, + teleport_id: 0, + dismount: false + }))?; + Ok(()) + } + +} + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub uuid: Uuid, +} + +struct NetworkClient { + pub id: i32, + pub play: bool, + pub closed: bool, + stream: TcpStream, + serverbound: Receiver, + player: Option, +} + +impl NetworkClient { + pub fn listen(mut stream: TcpStream, send: Sender) { + let mut state = NetworkState::Handshake; + let dur = Duration::from_millis(5); + loop { + if let Some(decoder) = PacketDecoder::decode(&mut stream) { + let packet = ServerBoundPacket::decode(&mut state, decoder); + send.send(packet).unwrap(); + } + thread::sleep(dur) + } + } + + pub fn recv_packet(&mut self, alive: &mut bool) -> Option { + match self.serverbound.try_recv() { + Ok(packet) => Some(packet), + Err(TryRecvError::Empty) => None, + Err(TryRecvError::Disconnected) => { + *alive = false; + None + } + } + } + + pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> { + self.stream.write_all(&packet.encode())?; + Ok(()) + } + + pub fn close(&mut self) { + println!("closed connection with {}", self.id); + let _ = self.stream.shutdown(Shutdown::Both); + self.closed = true; + } +} diff --git a/src/plugins.rs b/src/plugins.rs new file mode 100644 index 0000000..0e383fe --- /dev/null +++ b/src/plugins.rs @@ -0,0 +1,128 @@ +use std::{path::{Path, PathBuf}, str::FromStr}; + +use mlua::{Function, Lua, Error, Table}; +use uuid::Uuid; + +use crate::network::Player; + +pub struct EventHandlers<'lua> { + init: Option>, + player_join: Option>, + player_leave: Option>, + chat_message: Option>, +} + +pub struct Plugin<'lua> { + pub id: String, + pub name: String, + pub version: String, + pub event_handlers: EventHandlers<'lua>, +} + +impl <'lua> Plugin<'lua> { + pub fn load(path: &str, lua: &'lua Lua) -> Result> { + let path = PathBuf::from_str(path).unwrap(); + let chunk = lua.load(&path); + let module: Table = chunk.eval()?; + + let id: String = module.get("id")?; + let name: String = module.get("name").unwrap_or_else(|_| id.clone()); + let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned()); + + let init: Option> = module.get("init").ok(); + let player_join: Option> = module.get("playerJoin").ok(); + let player_leave: Option> = module.get("playerLeave").ok(); + let chat_message: Option> = module.get("chatMessage").ok(); + + let event_handlers = EventHandlers { init, player_join, player_leave, chat_message }; + Ok(Plugin { id, name, version, event_handlers }) + } +} + +pub struct Plugins<'lua> { + lua: &'lua Lua, + plugins: Vec> +} + +impl <'lua> Plugins<'lua> { + pub fn new(lua: &'lua Lua) -> Result { + let server = lua.create_table()?; + let players = lua.create_table()?; + server.set("players", players)?; + let fn_send = lua.create_function(|_, (uuid, message): (String, String)| { + Ok(()) + })?; + server.set("send", fn_send)?; + lua.globals().set("server", server)?; + Ok(Self { + lua, + plugins: Vec::new(), + }) + } + + pub fn add_plugin(&mut self, pl: Plugin<'lua>) { + self.plugins.push(pl); + } + + pub fn init(&self) { + for pl in &self.plugins { + if let Some(init) = &pl.event_handlers.init { + if let Err(e) = init.call::<_, ()>(()) { + println!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + pub fn player_join(&self, player: &Player) { + if let Err(e) = self.add_player(player) { + println!("Error adding player: {}", e); + return + } + for pl in &self.plugins { + if let Some(init) = &pl.event_handlers.player_join { + if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { + println!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + fn add_player(&self, player: &Player) -> Result<(), mlua::Error> { + let server: Table = self.lua.globals().get("server")?; + let players: Table = server.get("players")?; + players.set(player.uuid.to_string(), player.name.as_str())?; + Ok(()) + } + + pub fn player_leave(&self, player: &Player) { + if let Err(e) = self.remove_player(player.uuid) { + println!("Error removing player: {}", e); + return + } + for pl in &self.plugins { + if let Some(func) = &pl.event_handlers.player_leave { + if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { + println!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> { + let server: Table = self.lua.globals().get("server")?; + let players: Table = server.get("players")?; + players.set(uuid.to_string(), mlua::Nil)?; + Ok(()) + } + + pub fn chat_message(&self, player: &Player, message: &str) { + for pl in &self.plugins { + if let Some(func) = &pl.event_handlers.chat_message { + if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) { + println!("Error in plugin {}: {}", pl.name, e); + } + } + } + } +} diff --git a/src/protocol/clientbound.rs b/src/protocol/clientbound.rs new file mode 100644 index 0000000..2b3f24f --- /dev/null +++ b/src/protocol/clientbound.rs @@ -0,0 +1,204 @@ +use uuid::Uuid; + +use super::{data::{PacketEncoder, finalize_packet}, Position}; + +#[derive(Debug)] +pub struct LoginSuccess { + pub uuid: Uuid, + pub name: String, +} + +impl LoginSuccess { + pub fn encode(self, encoder: &mut impl PacketEncoder) { + encoder.write_uuid(self.uuid); + encoder.write_string(16, &self.name); + encoder.write_varint(0); + } +} + +#[derive(Debug)] +pub struct LoginPlay { + pub eid: i32, + pub is_hardcore: bool, + pub gamemode: u8, + pub prev_gamemode: u8, + pub dimensions: Vec, + pub registry_codec: Vec, + pub dimension_type: String, + pub dimension_name: String, + pub seed_hash: i64, + pub max_players: i32, + pub view_distance: i32, + pub sim_distance: i32, + pub reduced_debug_info: bool, + pub respawn_screen: bool, + pub is_debug: bool, + pub is_flat: bool, + pub death_location: Option<(String, Position)> +} + +impl LoginPlay { + pub fn encode(self, encoder: &mut impl PacketEncoder) { + encoder.write_int(self.eid); + encoder.write_bool(self.is_hardcore); + encoder.write_ubyte(self.gamemode); + encoder.write_ubyte(self.prev_gamemode); + encoder.write_varint(self.dimensions.len() as i32); + for dim in self.dimensions { + encoder.write_string(32767, &dim); + } + encoder.write_bytes(&self.registry_codec); + encoder.write_string(32767, &self.dimension_type); + encoder.write_string(32767, &self.dimension_name); + encoder.write_long(self.seed_hash); + encoder.write_varint(self.max_players); + encoder.write_varint(self.view_distance); + encoder.write_varint(self.sim_distance); + encoder.write_bool(self.reduced_debug_info); + encoder.write_bool(self.respawn_screen); + encoder.write_bool(self.is_debug); + encoder.write_bool(self.is_flat); + encoder.write_bool(self.death_location.is_some()); + if let Some(dl) = self.death_location { + encoder.write_string(32767, &dl.0); + encoder.write_position(dl.1); + } + } +} + +#[derive(Debug)] +pub struct PluginMessage { + pub channel: String, + pub data: Vec, +} + +impl PluginMessage { + pub fn encode(self, encoder: &mut impl PacketEncoder) { + encoder.write_string(32767, &self.channel); + encoder.write_bytes(&self.data); + } +} + +#[derive(Debug)] +pub struct SyncPlayerPosition { + pub x: f64, + pub y: f64, + pub z: f64, + pub yaw: f32, + pub pitch: f32, + pub flags: i8, + pub teleport_id: i32, + pub dismount: bool, +} + +impl SyncPlayerPosition { + pub fn encode(self, encoder: &mut impl PacketEncoder) { + encoder.write_double(self.x); + encoder.write_double(self.y); + encoder.write_double(self.z); + encoder.write_float(self.yaw); + encoder.write_float(self.pitch); + encoder.write_byte(self.flags); + encoder.write_varint(self.teleport_id); + encoder.write_bool(self.dismount); + } +} + +#[derive(Debug)] +pub struct ChunkData { + pub x: i32, + pub z: i32, + pub heightmap: nbt::Blob, + pub chunk_data: Vec, +} + +impl ChunkData { + pub fn encode(self, encoder: &mut impl PacketEncoder) { + encoder.write_int(self.x); + encoder.write_int(self.z); + self.heightmap.to_writer(encoder).unwrap(); + encoder.write_varint(self.chunk_data.len() as i32); + encoder.write_bytes(&self.chunk_data); + // number of block entities + encoder.write_varint(0); + // trust edges + encoder.write_bool(true); + // light masks + encoder.write_varint(0); + encoder.write_varint(0); + encoder.write_varint(0); + encoder.write_varint(0); + // sky light array + encoder.write_varint(0); + // block light array + encoder.write_varint(0); + } +} + +#[derive(Debug)] +pub enum ClientBoundPacket { + // status + StatusResponse(String), + PingResponse(i64), + // login + LoginSuccess(LoginSuccess), + // play + LoginPlay(LoginPlay), + PluginMessage(PluginMessage), + SyncPlayerPosition(SyncPlayerPosition), + ChunkData(ChunkData), + KeepAlive(i64), + PlayerAbilities(i8, f32, f32), + SystemChatMessage(serde_json::Value, bool), +} + +impl ClientBoundPacket { + pub fn encode(self) -> Vec { + let mut packet = Vec::new(); + match self { + Self::StatusResponse(status) => { + packet.write_string(32767, &status); + finalize_packet(packet, 0) + }, + Self::PingResponse(n) => { + packet.write_long(n); + finalize_packet(packet, 1) + }, + Self::LoginSuccess(login_success) => { + login_success.encode(&mut packet); + finalize_packet(packet, 2) + } + Self::PluginMessage(plugin_message) => { + plugin_message.encode(&mut packet); + finalize_packet(packet, 22) + } + Self::SyncPlayerPosition(sync_player_position) => { + sync_player_position.encode(&mut packet); + finalize_packet(packet, 57) + } + Self::LoginPlay(login_play) => { + login_play.encode(&mut packet); + finalize_packet(packet, 37) + } + Self::ChunkData(chunk_data) => { + chunk_data.encode(&mut packet); + finalize_packet(packet, 33) + } + Self::KeepAlive(n) => { + packet.write_long(n); + finalize_packet(packet, 32) + } + Self::PlayerAbilities(flags, speed, view) => { + packet.write_byte(flags); + packet.write_float(speed); + packet.write_float(view); + finalize_packet(packet, 49) + } + Self::SystemChatMessage(msg, overlay) => { + packet.write_string(262144, &msg.to_string()); + packet.write_bool(overlay); + finalize_packet(packet, 98) + } + } + } +} diff --git a/src/protocol/data.rs b/src/protocol/data.rs new file mode 100644 index 0000000..be14c5c --- /dev/null +++ b/src/protocol/data.rs @@ -0,0 +1,284 @@ +use std::io::{Write, Read}; + +use serde::Serialize; +use uuid::Uuid; + +use super::Position; + +pub trait PacketEncoder: Write { + fn write_bytes(&mut self, data: &[u8]) { + self.write_all(data).unwrap(); + } + + fn write_bool(&mut self, data: bool) { + self.write_all(&[data as u8]).unwrap(); + } + + fn write_byte(&mut self, data: i8) { + self.write_all(&[data as u8]).unwrap(); + } + + fn write_ubyte(&mut self, data: u8) { + self.write_all(&[data]).unwrap(); + } + + fn write_short(&mut self, data: i16) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_ushort(&mut self, data: u16) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_int(&mut self, data: i32) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_long(&mut self, data: i64) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_uuid(&mut self, data: Uuid) { + self.write_all(&data.as_u128().to_be_bytes()).unwrap(); + } + + fn write_float(&mut self, data: f32) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_double(&mut self, data: f64) { + self.write_all(&data.to_be_bytes()).unwrap(); + } + + fn write_varint(&mut self, mut data: i32) { + loop { + let mut byte = (data & 0b11111111) as u8; + data = data >> 7; + if data != 0 { + byte |= 0b10000000; + } + self.write_all(&[byte]).unwrap(); + if data == 0 { + break + } + } + } + + fn write_varlong(&mut self, mut data: i64) { + loop { + let mut byte = (data & 0b11111111) as u8; + data = data >> 7; + if data != 0 { + byte |= 0b10000000; + } + self.write_all(&[byte]).unwrap(); + if data == 0 { + break + } + } + } + + fn write_position(&mut self, position: Position) { + self.write_long( + (((position.x & 0x3ffffff) as i64) << 38) + | (((position.z & 0x3ffffff) as i64) << 12) + | (position.y & 0xfff) as i64 + ) + } + + fn write_nbt(&mut self, nbt: &impl Serialize) { + nbt::to_writer(self, nbt, None).unwrap(); + } + + fn write_string(&mut self, max_len: usize, val: &str) { + if val.len() > max_len * 4 + 3 { + panic!("exceeded max string length") + } + self.write_varint(val.len() as i32); + self.write_all(val.as_bytes()).unwrap(); + } + + fn to_data(self) -> Vec; +} + +impl PacketEncoder for Vec { + fn to_data(self) -> Vec { self } +} + +fn encode_varint(mut data: i32) -> Vec { + let mut res = Vec::new(); + loop { + let mut byte = (data & 0b11111111) as u8; + data = data >> 7; + if data != 0 { + byte |= 0b10000000; + } + res.push(byte); + if data == 0 { + break + } + } + res +} + +pub fn finalize_packet(packet: impl PacketEncoder, packet_id: i32) -> Vec { + let mut id = encode_varint(packet_id); + let mut data = packet.to_data(); + let mut result = encode_varint((id.len() + data.len()) as i32); + result.append(&mut id); + result.append(&mut data); + result +} + +pub struct PacketDecoder { + data: Vec, + idx: usize, + packet_id: i32, +} + +impl PacketDecoder { + pub fn decode(read: &mut impl Read) -> Option { + let size = read_varint(read)? as usize; + let mut data = vec![0; size]; + read.read_exact(&mut data).unwrap(); + let mut decoder = PacketDecoder { + data, + idx: 0, + packet_id: 0 + }; + decoder.packet_id = decoder.read_varint(); + return Some(decoder); + } + + pub fn packet_id(&self) -> i32 { + return self.packet_id; + } + + pub fn read_bytes(&mut self, n: usize) -> &[u8] { + let ret = &self.data[self.idx..self.idx+n]; + self.idx += n; + ret + } + + pub fn read_bool(&mut self) -> bool { + self.idx += 1; + self.data[self.idx-1] != 0 + } + + pub fn read_byte(&mut self) -> i8 { + self.idx += 1; + self.data[self.idx-1] as i8 + } + + pub fn read_ubyte(&mut self) -> u8 { + self.idx += 1; + self.data[self.idx-1] + } + + pub fn read_short(&mut self) -> i16 { + let mut buf = [0; 2]; + buf.copy_from_slice(&self.data[self.idx..self.idx+2]); + self.idx += 2; + i16::from_be_bytes(buf) + } + + pub fn read_ushort(&mut self) -> u16 { + let mut buf = [0; 2]; + buf.copy_from_slice(&self.data[self.idx..self.idx+2]); + self.idx += 2; + u16::from_be_bytes(buf) + } + + pub fn read_int(&mut self) -> i32 { + let mut buf = [0; 4]; + buf.copy_from_slice(&self.data[self.idx..self.idx+4]); + self.idx += 4; + i32::from_be_bytes(buf) + } + + pub fn read_long(&mut self) -> i64 { + let mut buf = [0; 8]; + buf.copy_from_slice(&self.data[self.idx..self.idx+8]); + self.idx += 8; + i64::from_be_bytes(buf) + } + + pub fn read_uuid(&mut self) -> Uuid { + let mut buf = [0; 16]; + buf.copy_from_slice(&self.data[self.idx..self.idx+16]); + self.idx += 16; + Uuid::from_u128(u128::from_be_bytes(buf)) + } + + pub fn read_float(&mut self) -> f32 { + let mut buf = [0; 4]; + buf.copy_from_slice(&self.data[self.idx..self.idx+4]); + self.idx += 4; + f32::from_be_bytes(buf) + } + + pub fn read_double(&mut self) -> f64 { + let mut buf = [0; 8]; + buf.copy_from_slice(&self.data[self.idx..self.idx+8]); + self.idx += 8; + f64::from_be_bytes(buf) + } + + pub fn read_varint(&mut self) -> i32 { + let mut result = 0; + let mut count = 0; + loop { + let byte = self.read_ubyte(); + result |= ((byte & 0x7f) as i32) << (7 * count); + count += 1; + if count > 5 { + panic!("varint too long") + } + if byte & 0x80 == 0 { + break + } + } + result + } + + pub fn read_varlong(&mut self) -> i64 { + let mut result = 0; + let mut count = 0; + loop { + let byte = self.read_ubyte(); + result |= ((byte & 0x7f) as i64) << (7 * count); + count += 1; + if count > 10 { + panic!("varint too long") + } + if byte & 0x80 == 0 { + break + } + } + result + } + + pub fn read_string(&mut self) -> String { + let len = self.read_varint() as usize; + String::from_utf8(self.read_bytes(len).to_vec()).unwrap() + } +} + +fn read_varint(read: &mut impl Read) -> Option { + let mut result = 0; + let mut count = 0; + loop { + let mut byte = [0]; + read.read_exact(&mut byte).ok()?; + let byte = byte[0]; + result |= ((byte & 0x7f) as i32) << (7 * count); + count += 1; + if count > 5 { + panic!("varint too long") + } + if byte & 0x80 == 0 { + break + } + } + Some(result) +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..be49b58 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,18 @@ +pub mod data; +pub mod serverbound; +pub mod clientbound; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NetworkState { + Handshake, + Status, + Login, + Play +} + +#[derive(Clone, Copy, Debug)] +pub struct Position { + x: i32, + y: i16, + z: i32 +} diff --git a/src/protocol/serverbound.rs b/src/protocol/serverbound.rs new file mode 100644 index 0000000..ce6f599 --- /dev/null +++ b/src/protocol/serverbound.rs @@ -0,0 +1,118 @@ +use uuid::Uuid; + +use super::{data::PacketDecoder, NetworkState}; + +#[derive(Debug)] +pub struct Handshake { + pub version: i32, + pub addr: String, + pub port: u16, + pub next_state: i32, +} + +impl Handshake { + pub fn decode(mut decoder: PacketDecoder) -> Self { + let version = decoder.read_varint(); + let addr = decoder.read_string(); + let port = decoder.read_ushort(); + let next_state = decoder.read_varint(); + Self { version, addr, port, next_state } + } +} + +#[derive(Debug)] +pub struct SigData { + pub timestamp: i64, + pub pubkey: Vec, + pub sig: Vec, +} + +#[derive(Debug)] +pub struct LoginStart { + pub name: String, + pub sig_data: Option, + pub uuid: Option, +} + +impl LoginStart { + pub fn decode(mut decoder: PacketDecoder) -> Self { + let name = decoder.read_string(); + let has_sig_data = decoder.read_bool(); + let sig_data = if has_sig_data { + let timestamp = decoder.read_long(); + let pubkey_len = decoder.read_varint(); + let pubkey = decoder.read_bytes(pubkey_len as usize).to_vec(); + let sig_len = decoder.read_varint(); + let sig = decoder.read_bytes(sig_len as usize).to_vec(); + Some(SigData { timestamp, pubkey, sig }) + } else { + None + }; + let has_uuid = decoder.read_bool(); + let uuid = if has_uuid { + Some(decoder.read_uuid()) + } else { + None + }; + Self { name, sig_data, uuid } + } +} + +#[derive(Debug)] +pub struct ChatMessage { + pub message: String, + pub timestamp: i64, +} + +impl ChatMessage { + pub fn decode(mut decoder: PacketDecoder) -> Self { + let message = decoder.read_string(); + let timestamp = decoder.read_long(); + // TODO read rest of packet + Self { message, timestamp } + } +} + +#[derive(Debug)] +pub enum ServerBoundPacket { + Unknown(i32), + Ignored(i32), + // handshake + Handshake(Handshake), + // status + StatusRequest(), + PingRequest(i64), + // login + LoginStart(LoginStart), + // play + ChatMessage(ChatMessage), +} + +impl ServerBoundPacket { + pub fn decode(state: &mut NetworkState, mut decoder: PacketDecoder) -> ServerBoundPacket { + use NetworkState as NS; + match (*state, decoder.packet_id()) { + (NS::Handshake, 0) => { + let hs = Handshake::decode(decoder); + match hs.next_state { + 1 => *state = NS::Status, + 2 => *state = NS::Login, + state => panic!("invalid next state: {}", state) + } + ServerBoundPacket::Handshake(hs) + }, + (NS::Status, 0) + => ServerBoundPacket::StatusRequest(), + (NS::Status, 1) + => ServerBoundPacket::PingRequest(decoder.read_long()), + (NS::Login, 0) => { + *state = NS::Play; + ServerBoundPacket::LoginStart(LoginStart::decode(decoder)) + }, + (NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)), + (NS::Play, id @ (18 | 20 | 21 | 22 | 30)) => ServerBoundPacket::Ignored(id), + (_, id) => ServerBoundPacket::Unknown(id), + } + } +} + diff --git a/src/resources/registry_codec.nbt b/src/resources/registry_codec.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b27371a82fbf053e9650cd4e6e8b4633d2154e14 GIT binary patch literal 24946 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