diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..3eaa6c2 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,22 @@ +use std::fmt; + +#[derive(Debug)] +pub enum AppError { + IoError(std::io::Error), + InvalidWorldFile, +} + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AppError::IoError(err) => write!(f, "IO Error: {}", err), + AppError::InvalidWorldFile => write!(f, "Invalid world file"), + } + } +} + +impl From for AppError { + fn from(err: std::io::Error) -> Self { + AppError::IoError(err) + } +} diff --git a/src/main.rs b/src/main.rs index 9ef9efb..af426e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,731 +1,45 @@ -use flate2::write::GzEncoder; -use flate2::Compression; -use log::{error, info, warn}; +use log::{error, info}; use simple_logger::SimpleLogger; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::net::{SocketAddr, TcpListener}; use std::sync::{Arc, Mutex}; -use std::thread; -use std::thread::sleep; -use std::time::Duration; -impl Default for Player { - fn default() -> Self { - Player { - id: SpecialPlayers::SelfPlayer as u8, - username: "".to_string(), - verification_key: [0; 64], - unused: 0x00, - position_x: 0, - position_y: 0, - position_z: 0, - yaw: 0, - pitch: 0, - operator: false, - outgoing_data: Vec::new(), - } - } -} +mod error; +mod network; +mod player; +mod utils; +mod world; -struct Player { - // Struct `Player` is never constructed `#[warn(fuck_you)]` on by default - pub id: u8, - pub username: String, - pub verification_key: [u8; 64], - pub unused: u8, - pub position_x: i16, - pub position_y: i16, - pub position_z: i16, - pub yaw: u8, - pub pitch: u8, - pub operator: bool, - pub outgoing_data: Vec, -} +use error::AppError; +use network::handle_client; +use player::{Player, SpecialPlayers}; +use world::World; -enum SpecialPlayers { - SelfPlayer = 0xFF, -} - -#[derive(Copy, Clone, PartialEq)] -enum PlayerStatus { - Disconnected, - ConnectedSelf, - Connected, -} - -struct World { - pub size_x: i16, - pub size_y: i16, - pub size_z: i16, - pub data: Vec, -} - -fn build_world(size_x: i16, size_y: i16, size_z: i16) -> Vec { - let mut world_dat: Vec = Vec::new(); - - for y in 0..size_y { - for _z in 0..size_z { - for _x in 0..size_x { - if y < 15 { - world_dat.push(3); // Dirt - } else if y == 15 { - world_dat.push(2); // Grass - } else { - world_dat.push(0x00); // Air - } - } - } - } - - return world_dat; -} - -const SIZE_X: i16 = 64; -const SIZE_Y: i16 = 32; -const SIZE_Z: i16 = 64; - -fn handle_client( - mut stream: TcpStream, - client_number: u8, - players_arc_clone: Arc>, - world_arc_clone: Arc>, -) { - thread::spawn(move || { - info!("Thread initialized with player ID: {}", client_number); - - let mut player_statuses = [PlayerStatus::Disconnected; 255]; - let mut immediate_join = [false; 255]; - { - let mut players = players_arc_clone.lock().unwrap(); - for i in 0..players.len() { - let current_player = &mut players[i]; - match current_player.id { - 255 => { - continue; - } - _ => { - player_statuses[i] = PlayerStatus::Connected; - immediate_join[i] = true; - } - } - } - } - player_statuses[client_number as usize] = PlayerStatus::ConnectedSelf; - - loop { - let mut buffer = [0; 1]; - if stream.read(&mut buffer).unwrap() == 0 { - break; - } - - match buffer[0] { - 0x00 => { - let mut payload_buffer = [0; 130]; // Byte + String + String + Byte - let _ = stream.read(&mut payload_buffer); - - if payload_buffer[0] != 7 { - // Shit pant - let _ = &mut stream.write(&client_disconnect( - "Something went wrong (Type 0x00 expected ver 7, something else was received, contact server admin)", - )); - warn!("Something went wrong, packet 0x00 received but second byte was not 0x07: received {:#04X} instead.", payload_buffer[0]); - break; - } - - let mut username = String::new(); - - for i in 0..64 { - username.push(payload_buffer[i + 1] as char); - } - - let mut verif_key = [0; 64]; - - for i in 0..64 { - verif_key[i] = payload_buffer[i + 65]; - } - - let mut verif_key_formatted = String::new(); - use std::fmt::Write; - for &byte in &verif_key { - write!(&mut verif_key_formatted, "{:X}", byte).expect("Piss"); - } - { - let mut players = players_arc_clone.lock().unwrap(); - let current_player = &mut players[client_number as usize]; - - current_player.id = client_number; - current_player.username = username.trim().to_string(); - current_player.verification_key = verif_key; - current_player.unused = payload_buffer[129]; - current_player.position_x = 0; - current_player.position_y = 128; - current_player.position_z = 0; - current_player.yaw = 0; - current_player.pitch = 0; - current_player.operator = true; - - bomb_server_details(&mut stream, ¤t_player, &world_arc_clone); - - for i in 0..immediate_join.len() { - if immediate_join[i] { - //println!("Immediately joining {}", i); - let _ = &mut stream.write(&spawn_player( - players[i].id, - &players[i].username, - players[i].position_x, - players[i].position_y, - players[i].position_z, - players[i].yaw, - players[i].pitch, - )); - } - } - } - } - 0x05 => { - let mut payload_buffer = [0; 8]; // Short (2) + Short (2) + Short (2) + Byte (1) + Byte (1) - let _ = stream.read(&mut payload_buffer); - - let position_x = - ((payload_buffer[0] as i16) << (8 as i16)) + payload_buffer[1] as i16; - let position_y = - ((payload_buffer[2] as i16) << (8 as i16)) + payload_buffer[3] as i16; - let position_z = - ((payload_buffer[4] as i16) << (8 as i16)) + payload_buffer[5] as i16; - - let mode = payload_buffer[6]; - let mut block_type = payload_buffer[7]; - { - let mut world_dat = world_arc_clone.lock().unwrap(); - - // Sanity check (Stop losers from losing) - if position_x > world_dat.size_x - || position_y > world_dat.size_y - || position_z > world_dat.size_z - { - // Fuck you! - let _ = &mut stream.write(&client_disconnect( - "Block position was not within world bounds, naughty boy", - )); - break; - } - - if mode == 0x00 { - block_type = 0x00; // Air - } - - let world_offset: u32 = position_x as u32 - + (position_z as u32 * world_dat.size_x as u32) - + (position_y as u32 - * world_dat.size_x as u32 - * world_dat.size_z as u32); - world_dat.data[world_offset as usize] = block_type; - } - - let mut update_block_bytes: Vec = Vec::new(); - update_block_bytes.push(0x06); - update_block_bytes.extend_from_slice(&stream_write_short(position_x)); - update_block_bytes.extend_from_slice(&stream_write_short(position_y)); - update_block_bytes.extend_from_slice(&stream_write_short(position_z)); - update_block_bytes.push(block_type); - - let mut players = players_arc_clone.lock().unwrap(); - for i in 0..players.len() { - if players[i].id != 255 && players[i].id != client_number { - players[i] - .outgoing_data - .extend_from_slice(&update_block_bytes); - } - } - } - 0x08 => { - let mut payload_buffer = [0; 9]; // SByte + FShort (2B) + FShort + FShort + - // Byte + Byte - let _ = stream.read(&mut payload_buffer); - - if payload_buffer[0] != SpecialPlayers::SelfPlayer as u8 { - let _ = &mut stream.write(&client_disconnect("Evil bit level hacking")); - break; - } - { - let mut players = players_arc_clone.lock().unwrap(); - let current_player = &mut players[client_number as usize]; - current_player.position_x = - ((payload_buffer[1] as i16) << (8 as i16)) + payload_buffer[2] as i16; - current_player.position_y = - ((payload_buffer[3] as i16) << (8 as i16)) + payload_buffer[4] as i16; - current_player.position_z = - ((payload_buffer[5] as i16) << (8 as i16)) + payload_buffer[6] as i16; - - current_player.yaw = payload_buffer[7]; - current_player.pitch = payload_buffer[8]; - } - } - 0x0D => { - let mut payload_buffer = [0; 65]; // Byte + String - let _ = stream.read(&mut payload_buffer); - - if payload_buffer[0] != SpecialPlayers::SelfPlayer as u8 { - let _ = &mut stream.write(&client_disconnect("Evil bit level hacking")); - break; - } - - let mut message = [' '; 64]; - for i in 0..64 { - message[i] = payload_buffer[i + 1] as char; - } - - let message_string = String::from_iter(message); - - if message[0] == '/' { - // Uh oh, command time - info!("{}", message_string); - let remaning_command = String::from_iter(&message[1..message.len()]); - let vectorized_command = remaning_command.split(" ").collect::>(); - match vectorized_command[0] { - "kick" => { - let mut players = players_arc_clone.lock().unwrap(); - for i in 0..players.len() { - if players[i].id != 255 { - if players[i].username == vectorized_command[1] { - let _ = &mut players[i] - .outgoing_data - .extend_from_slice(&client_disconnect("KICKED!")); - players[i].id = 255; - break; - } - } - } - } - "tp" => { - let players = players_arc_clone.lock().unwrap(); - for i in 0..players.len() { - if players[i].id != 255 { - if players[i].username == vectorized_command[1] { - let _ = - &mut stream.write(&set_position_and_orientation( - SpecialPlayers::SelfPlayer as u8, - players[i].position_x, - players[i].position_y, - players[i].position_z, - players[i].yaw, - players[i].pitch, - )); - break; - } - } - } - } - _ => { - let _ = &mut stream.write(&send_chat_message( - SpecialPlayers::SelfPlayer as u8, - "".to_string(), - "&cUnkown command!".to_string(), - )); - } - } - } else { - let mut players = players_arc_clone.lock().unwrap(); - let sender: u8 = players[client_number as usize].id; - let sender_name: String = players[client_number as usize].username.clone(); - for i in 0..players.len() { - if players[i].id != 255 && players[i].id != client_number { - players[i] - .outgoing_data - .extend_from_slice(&send_chat_message( - sender, - sender_name.clone(), - message_string.clone(), - )); - } - } - - let _ = &mut stream.write(&send_chat_message( - SpecialPlayers::SelfPlayer as u8, - sender_name.clone(), - message_string.clone(), - )); - info!("[{}]: {}", sender_name, message_string); - } - } - _ => warn!("Packet {} not implemented!", buffer[0]), - } - let is_kill = &mut stream.write(&ping()); // Ping that MF - - if is_kill.is_err() { - break; - } - - sleep(Duration::from_millis(1000 / 1000)); // 1000 TPS TODO: Delta time - { - let mut players = players_arc_clone.lock().unwrap(); - if players[client_number as usize].outgoing_data.len() > 0 { - let _ = stream.write(&players[client_number as usize].outgoing_data); - players[client_number as usize].outgoing_data.clear(); - } - for i in 0..players.len() { - if players[i].id != 255 { - if player_statuses[i] == PlayerStatus::Disconnected { - let _ = stream.write(&spawn_player( - players[i].id, - &players[i].username, - players[i].position_x, - players[i].position_y, - players[i].position_z, - players[i].yaw, - players[i].pitch, - )); - player_statuses[i] = PlayerStatus::Connected; - let _ = stream.write(&send_chat_message( - players[i].id, - "".to_string(), - format!("{} has joined the game!", &players[i].username), - )); - } - } else { - if player_statuses[i] == PlayerStatus::Connected { - let _ = stream.write(&despawn_player(i.try_into().unwrap())); - let _ = stream.write(&send_chat_message( - i.try_into().unwrap(), - "".to_string(), - format!("{} has left the game!", &players[i].username), - )); - player_statuses[i] = PlayerStatus::Disconnected; - } - } - if player_statuses[i] == PlayerStatus::Connected { - let _ = stream.write(&set_position_and_orientation( - players[i].id, - players[i].position_x, - players[i].position_y, - players[i].position_z, - players[i].yaw, - players[i].pitch, - )); - } - } - } - } - { - let mut players = players_arc_clone.lock().unwrap(); - let current_player = &mut players[client_number as usize]; - - current_player.id = SpecialPlayers::SelfPlayer as u8; - } - info!( - "Client {} disconnected, thread shutting down!", - client_number - ); - }); -} - -fn to_mc_string(text: &str) -> [u8; 64] { - let text_vec: Vec = text.chars().take(64).collect(); - let mut balls = [0; 64]; - - for i in 0..64 { - balls[i] = 0x20; - } - - for i in 0..text_vec.len() { - balls[i] = text_vec[i] as u8; - } - - return balls; -} - -fn stream_write_short(data: i16) -> Vec { - return [(data >> 0x08) as u8, (data & 0x00FF) as u8].to_vec(); -} - -fn client_disconnect(text: &str) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x0E); - ret_val.append(&mut to_mc_string(text).to_vec()); - return ret_val; -} - -fn server_identification(is_op: bool) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x00); - ret_val.push(0x07); - - let server_name = "Erm... what the sigma?"; - ret_val.append(&mut to_mc_string(server_name).to_vec()); - - let server_motd = "Pragmatism not idealism"; - ret_val.append(&mut to_mc_string(server_motd).to_vec()); - - if is_op { - ret_val.push(0x64); - } else { - ret_val.push(0x00); - } - - return ret_val; -} - -fn ping() -> Vec { - return vec![0x01]; -} - -fn init_level() -> Vec { - return vec![0x02]; -} - -fn finalize_level(world_arc_clone: &Arc>) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x04); - - let world_dat = world_arc_clone.lock().unwrap(); - - ret_val.append(&mut stream_write_short(world_dat.size_x).to_vec()); - ret_val.append(&mut stream_write_short(world_dat.size_y).to_vec()); - ret_val.append(&mut stream_write_short(world_dat.size_z).to_vec()); - - return ret_val; -} - -fn spawn_player( - player_id: u8, - name: &String, - pos_x: i16, - pos_y: i16, - pos_z: i16, - yaw: u8, - pitch: u8, -) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x07); - - ret_val.push(player_id); - ret_val.append(&mut to_mc_string(name).to_vec()); - ret_val.append(&mut stream_write_short(pos_x << 5).to_vec()); // FShort - ret_val.append(&mut stream_write_short(pos_y << 5).to_vec()); - ret_val.append(&mut stream_write_short(pos_z << 5).to_vec()); - ret_val.push(yaw); - ret_val.push(pitch); - - return ret_val; -} - -fn despawn_player(player_id: u8) -> Vec { - return [0x0C, player_id].to_vec(); -} - -fn send_chat_message(source_id: u8, mut source_username: String, mut message: String) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x0D); - - if source_username.len() != 0 { - source_username.push_str(": "); - } - message.insert_str(0, &source_username); - - ret_val.push(source_id); - ret_val.append(&mut to_mc_string(&message).to_vec()); - - return ret_val; -} - -fn set_position_and_orientation( - player_id: u8, - pos_x: i16, - pos_y: i16, - pos_z: i16, - yaw: u8, - pitch: u8, -) -> Vec { - let mut ret_val: Vec = vec![]; - ret_val.push(0x08); - - ret_val.push(player_id); - ret_val.append(&mut stream_write_short(pos_x).to_vec()); - ret_val.append(&mut stream_write_short(pos_y).to_vec()); - ret_val.append(&mut stream_write_short(pos_z).to_vec()); - - ret_val.push(yaw); - ret_val.push(pitch); - - return ret_val; -} - -fn send_level_data(world_arc_clone: &Arc>) -> Vec { - let mut ret_val: Vec = vec![]; - let mut world_dat = world_arc_clone.lock().unwrap().data.clone(); - - // Big endian fold lmao - world_dat.insert(0, ((world_dat.len() & 0xFF) >> 0) as u8); - world_dat.insert(0, ((world_dat.len() & 0xFF00) >> 8) as u8); - world_dat.insert(0, ((world_dat.len() & 0xFF0000) >> 16) as u8); - world_dat.insert(0, ((world_dat.len() & 0xFF000000) >> 24) as u8); - - // TODO: Stream GZIP straight onto the network - - let mut world_dat_compressor = GzEncoder::new(Vec::new(), Compression::fast()); - for i in 0..world_dat.len() { - let _ = world_dat_compressor.write(&[world_dat[i]]); - } - let world_dat_gzipped = world_dat_compressor.finish().unwrap(); - - let number_of_chunks = ((world_dat_gzipped.len() as f32) / 1024.0_f32).ceil() as usize; - let mut current_chunk = 0; - - if number_of_chunks != 1 { - while current_chunk + 1 != number_of_chunks { - ret_val.push(0x03); - - ret_val.append(&mut stream_write_short(0x400)); - - let mut chunk_data_buffer = [0u8; 1024]; - for i in 0..1024 { - chunk_data_buffer[i] = world_dat_gzipped[current_chunk * 1024 + i]; - } - ret_val.append(&mut chunk_data_buffer.to_vec()); - - let mut percentage = current_chunk / number_of_chunks * 100; - - if percentage > 100 { - percentage = 100; - } - - ret_val.push(percentage.try_into().unwrap()); - - current_chunk += 1; - } - } - - let remaining_chunk_size = world_dat_gzipped.len() - (current_chunk * 1024); - - if remaining_chunk_size > 0 { - ret_val.push(0x03); - - ret_val.append(&mut stream_write_short( - remaining_chunk_size.try_into().unwrap(), - )); - - let mut remaining_data_buffer = [0u8; 1024]; - for i in 0..remaining_chunk_size { - remaining_data_buffer[i] = world_dat_gzipped[current_chunk * 1024 + i]; - } - - ret_val.append(&mut remaining_data_buffer.to_vec()); - ret_val.push(100); - } - - info!( - "World transmission size: {}KiB", - ret_val.len() as f32 / 1024.0 - ); - return ret_val; -} - -fn save_world(world_arc_clone: Arc>) -> std::io::Result<()> { - let mut to_write: Vec = Vec::new(); - { - let mut world_dat = world_arc_clone.lock().unwrap(); - to_write.push((world_dat.size_x >> 8) as u8); - to_write.push((world_dat.size_x & 0xFF) as u8); - to_write.push((world_dat.size_y >> 8) as u8); - to_write.push((world_dat.size_y & 0xFF) as u8); - to_write.push((world_dat.size_z >> 8) as u8); - to_write.push((world_dat.size_z & 0xFF) as u8); - to_write.append(&mut world_dat.data); - } - - let mut file = File::create("world.wrld")?; - return file.write_all(&to_write); -} - -fn load_world() -> World { - if fs::metadata("world.wrld").is_ok() { - let mut world: World = World { - size_x: 0, - size_y: 0, - size_z: 0, - data: Vec::new(), - }; - let world_data_raw = fs::read("world.wrld").unwrap(); - if world_data_raw.len() < 6 { - error!("INVALID WORLD!"); - std::process::exit(1); - } - world.size_x = ((world_data_raw[0] as i16) << 8) + (world_data_raw[1] as i16); - world.size_y = ((world_data_raw[2] as i16) << 8) + (world_data_raw[3] as i16); - world.size_z = ((world_data_raw[4] as i16) << 8) + (world_data_raw[5] as i16); - - if world_data_raw.len() - != (world.size_x as i32 * world.size_y as i32 * world.size_z as i32 + 6 as i32) as usize - { - error!( - "Expected more bytes in world contents: {} (expected) != {} (actual)", - world.size_x * world.size_y * world.size_z + 6, - world_data_raw.len() - ); - std::process::exit(1); - } - - world.data = world_data_raw[6..world_data_raw.len()].to_vec(); - - return world; - } else { - return World { - size_x: SIZE_X, - size_y: SIZE_Y, - size_z: SIZE_Z, - data: build_world(SIZE_X, SIZE_Y, SIZE_Z), - }; - } -} - -fn bomb_server_details( - stream: &mut TcpStream, - current_player: &Player, - world_arc_clone: &Arc>, -) { - let mut compound_data: Vec = vec![]; - compound_data.append(&mut server_identification(current_player.operator)); - - compound_data.append(&mut init_level()); - - // info!("Send level data"); - compound_data.append(&mut send_level_data(&world_arc_clone)); // Approaching Nirvana - Maw of the beast - - compound_data.append(&mut finalize_level(&world_arc_clone)); - - info!("Spawning player: {}", ¤t_player.username); - compound_data.append(&mut spawn_player( - SpecialPlayers::SelfPlayer as u8, - ¤t_player.username, - 32, - 17, - 32, - 0, - 0, - )); - - let _ = stream.write(&compound_data); -} - -fn main() -> std::io::Result<()> { +fn main() { SimpleLogger::new().with_threads(true).init().unwrap(); + if let Err(err) = run() { + error!("FATAL: {}", err); + std::process::exit(1); + } +} + +fn run() -> Result<(), AppError> { let players: [Player; 255] = core::array::from_fn(|_| Player::default()); let players_arc = Arc::new(Mutex::new(players)); - let world_instance: World = load_world(); + let world_instance: World = World::load()?; let world_arc = Arc::new(Mutex::new(world_instance)); let addr = SocketAddr::from(([0, 0, 0, 0], 25565)); - let listener = TcpListener::bind(addr)?; let mut thread_number: u8 = 0; let world_arc_clone_main_thread = Arc::clone(&world_arc); ctrlc::set_handler(move || { - let _ = save_world(world_arc_clone_main_thread.clone()); // Fortnite save the world + println!(""); + info!("SAVING"); + let _ = World::save(world_arc_clone_main_thread.clone()); // Fortnite save the world std::process::exit(0); }) .expect("Error handling control C, save on exit will not work"); @@ -734,11 +48,7 @@ fn main() -> std::io::Result<()> { let players_arc_clone = Arc::clone(&players_arc); let world_arc_clone = Arc::clone(&world_arc); handle_client(stream?, thread_number, players_arc_clone, world_arc_clone); - if thread_number < 255 { - thread_number += 1; - } else { - thread_number = 0; - } + thread_number = thread_number.wrapping_add(1); } Ok(()) } diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 0000000..41a4d30 --- /dev/null +++ b/src/network.rs @@ -0,0 +1,347 @@ +use std::net::TcpStream; +use std::io::prelude::*; +use std::sync::{Arc, Mutex}; +use log::{info, warn}; +use std::thread; +use std::thread::sleep; +use std::time::Duration; + +use crate::player::{Player, SpecialPlayers, PlayerStatus}; +use crate::world::World; +use crate::utils::*; + +pub fn handle_client( + mut stream: TcpStream, + client_number: u8, + players_arc_clone: Arc>, + world_arc_clone: Arc>, +) { + thread::spawn(move || { + info!("Thread initialized with player ID: {}", client_number); + + let mut player_statuses = [PlayerStatus::Disconnected; 255]; + let mut immediate_join = [false; 255]; + { + let mut players = players_arc_clone.lock().unwrap(); + for i in 0..players.len() { + let current_player = &mut players[i]; + match current_player.id { + 255 => { + continue; + } + _ => { + player_statuses[i] = PlayerStatus::Connected; + immediate_join[i] = true; + } + } + } + } + player_statuses[client_number as usize] = PlayerStatus::ConnectedSelf; + + loop { + let mut buffer = [0; 1]; + if stream.read(&mut buffer).unwrap() == 0 { + break; + } + + match buffer[0] { + 0x00 => { + let mut payload_buffer = [0; 130]; // Byte + String + String + Byte + let _ = stream.read(&mut payload_buffer); + + if payload_buffer[0] != 7 { + // Shit pant + let _ = &mut stream.write(&client_disconnect( + "Something went wrong (Type 0x00 expected ver 7, something else was received, contact server admin)", + )); + warn!("Something went wrong, packet 0x00 received but second byte was not 0x07: received {:#04X} instead.", payload_buffer[0]); + break; + } + + let mut username = String::new(); + + for i in 0..64 { + username.push(payload_buffer[i + 1] as char); + } + + let mut verif_key = [0; 64]; + + for i in 0..64 { + verif_key[i] = payload_buffer[i + 65]; + } + + let mut verif_key_formatted = String::new(); + use std::fmt::Write; + for &byte in &verif_key { + write!(&mut verif_key_formatted, "{:X}", byte).expect("Piss"); + } + { + let mut players = players_arc_clone.lock().unwrap(); + let current_player = &mut players[client_number as usize]; + + current_player.id = client_number; + current_player.username = username.trim().to_string(); + current_player.verification_key = verif_key; + current_player.unused = payload_buffer[129]; + current_player.position_x = 0; + current_player.position_y = 128; + current_player.position_z = 0; + current_player.yaw = 0; + current_player.pitch = 0; + current_player.operator = true; + + bomb_server_details(&mut stream, ¤t_player, &world_arc_clone); + + for i in 0..immediate_join.len() { + if immediate_join[i] { + //println!("Immediately joining {}", i); + let _ = &mut stream.write(&spawn_player( + players[i].id, + &players[i].username, + players[i].position_x, + players[i].position_y, + players[i].position_z, + players[i].yaw, + players[i].pitch, + )); + } + } + } + } + 0x05 => { + let mut payload_buffer = [0; 8]; // Short (2) + Short (2) + Short (2) + Byte (1) + Byte (1) + let _ = stream.read(&mut payload_buffer); + + let position_x = + ((payload_buffer[0] as i16) << (8 as i16)) + payload_buffer[1] as i16; + let position_y = + ((payload_buffer[2] as i16) << (8 as i16)) + payload_buffer[3] as i16; + let position_z = + ((payload_buffer[4] as i16) << (8 as i16)) + payload_buffer[5] as i16; + + let mode = payload_buffer[6]; + let mut block_type = payload_buffer[7]; + { + let mut world_dat = world_arc_clone.lock().unwrap(); + + // Sanity check (Stop losers from losing) + if position_x > world_dat.size_x + || position_y > world_dat.size_y + || position_z > world_dat.size_z + { + // Fuck you! + let _ = &mut stream.write(&client_disconnect( + "Block position was not within world bounds, naughty boy", + )); + break; + } + + if mode == 0x00 { + block_type = 0x00; // Air + } + + let world_offset: u32 = position_x as u32 + + (position_z as u32 * world_dat.size_x as u32) + + (position_y as u32 + * world_dat.size_x as u32 + * world_dat.size_z as u32); + world_dat.data[world_offset as usize] = block_type; + } + + let mut update_block_bytes: Vec = Vec::new(); + update_block_bytes.push(0x06); + update_block_bytes.extend_from_slice(&stream_write_short(position_x)); + update_block_bytes.extend_from_slice(&stream_write_short(position_y)); + update_block_bytes.extend_from_slice(&stream_write_short(position_z)); + update_block_bytes.push(block_type); + + let mut players = players_arc_clone.lock().unwrap(); + for i in 0..players.len() { + if players[i].id != 255 && players[i].id != client_number { + players[i] + .outgoing_data + .extend_from_slice(&update_block_bytes); + } + } + } + 0x08 => { + let mut payload_buffer = [0; 9]; // SByte + FShort (2B) + FShort + FShort + + // Byte + Byte + let _ = stream.read(&mut payload_buffer); + + if payload_buffer[0] != SpecialPlayers::SelfPlayer as u8 { + let _ = &mut stream.write(&client_disconnect("Evil bit level hacking")); + break; + } + { + let mut players = players_arc_clone.lock().unwrap(); + let current_player = &mut players[client_number as usize]; + current_player.position_x = + ((payload_buffer[1] as i16) << (8 as i16)) + payload_buffer[2] as i16; + current_player.position_y = + ((payload_buffer[3] as i16) << (8 as i16)) + payload_buffer[4] as i16; + current_player.position_z = + ((payload_buffer[5] as i16) << (8 as i16)) + payload_buffer[6] as i16; + + current_player.yaw = payload_buffer[7]; + current_player.pitch = payload_buffer[8]; + } + } + 0x0D => { + let mut payload_buffer = [0; 65]; // Byte + String + let _ = stream.read(&mut payload_buffer); + + if payload_buffer[0] != SpecialPlayers::SelfPlayer as u8 { + let _ = &mut stream.write(&client_disconnect("Evil bit level hacking")); + break; + } + + let mut message = [' '; 64]; + for i in 0..64 { + message[i] = payload_buffer[i + 1] as char; + } + + let message_string = String::from_iter(message); + + if message[0] == '/' { + // Uh oh, command time + info!("{}", message_string); + let remaning_command = String::from_iter(&message[1..message.len()]); + let vectorized_command = remaning_command.split(" ").collect::>(); + match vectorized_command[0] { + "kick" => { + let mut players = players_arc_clone.lock().unwrap(); + for i in 0..players.len() { + if players[i].id != 255 { + if players[i].username == vectorized_command[1] { + let _ = &mut players[i] + .outgoing_data + .extend_from_slice(&client_disconnect("KICKED!")); + players[i].id = 255; + break; + } + } + } + } + "tp" => { + let players = players_arc_clone.lock().unwrap(); + for i in 0..players.len() { + if players[i].id != 255 { + if players[i].username == vectorized_command[1] { + let _ = + &mut stream.write(&set_position_and_orientation( + SpecialPlayers::SelfPlayer as u8, + players[i].position_x, + players[i].position_y, + players[i].position_z, + players[i].yaw, + players[i].pitch, + )); + break; + } + } + } + } + _ => { + let _ = &mut stream.write(&send_chat_message( + SpecialPlayers::SelfPlayer as u8, + "".to_string(), + "&cUnkown command!".to_string(), + )); + } + } + } else { + let mut players = players_arc_clone.lock().unwrap(); + let sender: u8 = players[client_number as usize].id; + let sender_name: String = players[client_number as usize].username.clone(); + for i in 0..players.len() { + if players[i].id != 255 && players[i].id != client_number { + players[i] + .outgoing_data + .extend_from_slice(&send_chat_message( + sender, + sender_name.clone(), + message_string.clone(), + )); + } + } + + let _ = &mut stream.write(&send_chat_message( + SpecialPlayers::SelfPlayer as u8, + sender_name.clone(), + message_string.clone(), + )); + info!("[{}]: {}", sender_name, message_string); + } + } + _ => warn!("Packet {} not implemented!", buffer[0]), + } + let is_kill = &mut stream.write(&ping()); // Ping that MF + + if is_kill.is_err() { + break; + } + + sleep(Duration::from_millis(1000 / 1000)); // 1000 TPS TODO: Delta time + { + let mut players = players_arc_clone.lock().unwrap(); + if players[client_number as usize].outgoing_data.len() > 0 { + let _ = stream.write(&players[client_number as usize].outgoing_data); + players[client_number as usize].outgoing_data.clear(); + } + for i in 0..players.len() { + if players[i].id != 255 { + if player_statuses[i] == PlayerStatus::Disconnected { + let _ = stream.write(&spawn_player( + players[i].id, + &players[i].username, + players[i].position_x, + players[i].position_y, + players[i].position_z, + players[i].yaw, + players[i].pitch, + )); + player_statuses[i] = PlayerStatus::Connected; + let _ = stream.write(&send_chat_message( + players[i].id, + "".to_string(), + format!("{} has joined the game!", &players[i].username), + )); + } + } else { + if player_statuses[i] == PlayerStatus::Connected { + let _ = stream.write(&despawn_player(i.try_into().unwrap())); + let _ = stream.write(&send_chat_message( + i.try_into().unwrap(), + "".to_string(), + format!("{} has left the game!", &players[i].username), + )); + player_statuses[i] = PlayerStatus::Disconnected; + } + } + if player_statuses[i] == PlayerStatus::Connected { + let _ = stream.write(&set_position_and_orientation( + players[i].id, + players[i].position_x, + players[i].position_y, + players[i].position_z, + players[i].yaw, + players[i].pitch, + )); + } + } + } + } + { + let mut players = players_arc_clone.lock().unwrap(); + let current_player = &mut players[client_number as usize]; + + current_player.id = SpecialPlayers::SelfPlayer as u8; + } + info!( + "Client {} disconnected, thread shutting down!", + client_number + ); + }); +} + diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..8686265 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,45 @@ +#[derive(Debug)] +pub struct Player { + // Struct `Player` is never constructed `#[warn(fuck_you)]` on by default + pub id: u8, + pub username: String, + pub verification_key: [u8; 64], + pub unused: u8, + pub position_x: i16, + pub position_y: i16, + pub position_z: i16, + pub yaw: u8, + pub pitch: u8, + pub operator: bool, + pub outgoing_data: Vec, +} + +impl Default for Player { + fn default() -> Self { + Player { + id: SpecialPlayers::SelfPlayer as u8, + username: "".to_string(), + verification_key: [0; 64], + unused: 0x00, + position_x: 0, + position_y: 0, + position_z: 0, + yaw: 0, + pitch: 0, + operator: false, + outgoing_data: Vec::new(), + } + } +} + +#[derive(Debug)] +pub enum SpecialPlayers { + SelfPlayer = 0xFF, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum PlayerStatus { + Disconnected, + ConnectedSelf, + Connected, +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..5ceb169 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,239 @@ +use flate2::write::GzEncoder; +use flate2::Compression; +use log::info; +use std::io::prelude::*; +use std::net::TcpStream; +use std::sync::{Arc, Mutex}; + +use crate::World; +use crate::SpecialPlayers; +use crate::Player; + +pub fn to_mc_string(text: &str) -> [u8; 64] { + let text_vec: Vec = text.chars().take(64).collect(); + let mut balls = [0; 64]; + + for i in 0..64 { + balls[i] = 0x20; + } + + for i in 0..text_vec.len() { + balls[i] = text_vec[i] as u8; + } + + balls +} + +pub fn stream_write_short(data: i16) -> Vec { + [(data >> 0x08) as u8, (data & 0x00FF) as u8].to_vec() +} + +pub fn client_disconnect(text: &str) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x0E); + ret_val.append(&mut to_mc_string(text).to_vec()); + ret_val +} + +pub fn server_identification(is_op: bool) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x00); + ret_val.push(0x07); + + let server_name = "Erm... what the sigma?"; + ret_val.append(&mut to_mc_string(server_name).to_vec()); + + let server_motd = "Pragmatism not idealism"; + ret_val.append(&mut to_mc_string(server_motd).to_vec()); + + if is_op { + ret_val.push(0x64); + } else { + ret_val.push(0x00); + } + + ret_val +} + +pub fn ping() -> Vec { + vec![0x01] +} + +pub fn init_level() -> Vec { + vec![0x02] +} + +pub fn finalize_level(world_arc_clone: &Arc>) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x04); + + let world_dat = world_arc_clone.lock().unwrap(); + + ret_val.append(&mut stream_write_short(world_dat.size_x).to_vec()); + ret_val.append(&mut stream_write_short(world_dat.size_y).to_vec()); + ret_val.append(&mut stream_write_short(world_dat.size_z).to_vec()); + + ret_val +} + +pub fn spawn_player( + player_id: u8, + name: &String, + pos_x: i16, + pos_y: i16, + pos_z: i16, + yaw: u8, + pitch: u8, +) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x07); + + ret_val.push(player_id); + ret_val.append(&mut to_mc_string(name).to_vec()); + ret_val.append(&mut stream_write_short(pos_x << 5).to_vec()); // FShort + ret_val.append(&mut stream_write_short(pos_y << 5).to_vec()); + ret_val.append(&mut stream_write_short(pos_z << 5).to_vec()); + ret_val.push(yaw); + ret_val.push(pitch); + + ret_val +} + +pub fn despawn_player(player_id: u8) -> Vec { + [0x0C, player_id].to_vec() +} + +pub fn send_chat_message(source_id: u8, mut source_username: String, mut message: String) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x0D); + + if source_username.len() != 0 { + source_username.push_str(": "); + } + message.insert_str(0, &source_username); + + ret_val.push(source_id); + ret_val.append(&mut to_mc_string(&message).to_vec()); + + ret_val +} + +pub fn set_position_and_orientation( + player_id: u8, + pos_x: i16, + pos_y: i16, + pos_z: i16, + yaw: u8, + pitch: u8, +) -> Vec { + let mut ret_val: Vec = vec![]; + ret_val.push(0x08); + + ret_val.push(player_id); + ret_val.append(&mut stream_write_short(pos_x).to_vec()); + ret_val.append(&mut stream_write_short(pos_y).to_vec()); + ret_val.append(&mut stream_write_short(pos_z).to_vec()); + + ret_val.push(yaw); + ret_val.push(pitch); + + ret_val +} + +pub fn send_level_data(world_arc_clone: &Arc>) -> Vec { + let mut ret_val: Vec = vec![]; + let mut world_dat = world_arc_clone.lock().unwrap().data.clone(); + + // Big endian fold lmao + world_dat.insert(0, ((world_dat.len() & 0xFF) >> 0) as u8); + world_dat.insert(0, ((world_dat.len() & 0xFF00) >> 8) as u8); + world_dat.insert(0, ((world_dat.len() & 0xFF0000) >> 16) as u8); + world_dat.insert(0, ((world_dat.len() & 0xFF000000) >> 24) as u8); + + // TODO: Stream GZIP straight onto the network + + let mut world_dat_compressor = GzEncoder::new(Vec::new(), Compression::fast()); + let _ = world_dat_compressor.write_all(&world_dat); + let world_dat_gzipped = world_dat_compressor.finish().unwrap(); + + let number_of_chunks = ((world_dat_gzipped.len() as f32) / 1024.0_f32).ceil() as usize; + let mut current_chunk = 0; + + if number_of_chunks != 1 { + while current_chunk + 1 != number_of_chunks { + ret_val.push(0x03); + + ret_val.append(&mut stream_write_short(0x400)); + + let mut chunk_data_buffer = [0u8; 1024]; + for i in 0..1024 { + chunk_data_buffer[i] = world_dat_gzipped[current_chunk * 1024 + i]; + } + ret_val.append(&mut chunk_data_buffer.to_vec()); + + let mut percentage = current_chunk / number_of_chunks * 100; + + if percentage > 100 { + percentage = 100; + } + + ret_val.push(percentage.try_into().unwrap()); + + current_chunk += 1; + } + } + + let remaining_chunk_size = world_dat_gzipped.len() - (current_chunk * 1024); + + if remaining_chunk_size > 0 { + ret_val.push(0x03); + + ret_val.append(&mut stream_write_short( + remaining_chunk_size.try_into().unwrap(), + )); + + let mut remaining_data_buffer = [0u8; 1024]; + for i in 0..remaining_chunk_size { + remaining_data_buffer[i] = world_dat_gzipped[current_chunk * 1024 + i]; + } + + ret_val.append(&mut remaining_data_buffer.to_vec()); + ret_val.push(100); + } + + info!( + "World transmission size: {}KiB", + ret_val.len() as f32 / 1024.0 + ); + ret_val +} + +pub fn bomb_server_details( + stream: &mut TcpStream, + current_player: &Player, + world_arc_clone: &Arc>, +) { + let mut compound_data: Vec = vec![]; + compound_data.append(&mut server_identification(current_player.operator)); + + compound_data.append(&mut init_level()); + + // info!("Send level data"); + compound_data.append(&mut send_level_data(&world_arc_clone)); // Approaching Nirvana - Maw of the beast + + compound_data.append(&mut finalize_level(&world_arc_clone)); + + info!("Spawning player: {}", ¤t_player.username); + compound_data.append(&mut spawn_player( + SpecialPlayers::SelfPlayer as u8, + ¤t_player.username, + 32, + 17, + 32, + 0, + 0, + )); + + let _ = stream.write(&compound_data); +} + diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..67dd8bf --- /dev/null +++ b/src/world.rs @@ -0,0 +1,91 @@ +use std::fs::{self, File}; +use std::io::Write; +use std::sync::{Arc, Mutex}; + +use crate::error::AppError; + +#[derive(Debug)] +pub struct World { + pub size_x: i16, + pub size_y: i16, + pub size_z: i16, + pub data: Vec, +} + +impl World { + fn build(size_x: i16, size_y: i16, size_z: i16) -> Vec { + let mut world_dat: Vec = Vec::new(); + + for y in 0..size_y { + for _z in 0..size_z { + for _x in 0..size_x { + if y < 15 { + world_dat.push(3); // Dirt + } else if y == 15 { + world_dat.push(2); // Grass + } else { + world_dat.push(0x00); // Air + } + } + } + } + + world_dat + } + pub fn load() -> Result { + if fs::metadata("world.wrld").is_ok() { + let mut world: World = World { + size_x: 0, + size_y: 0, + size_z: 0, + data: Vec::new(), + }; + + let world_data_raw = fs::read("world.wrld")?; + if world_data_raw.len() < 6 { + return Err(AppError::InvalidWorldFile); + } + world.size_x = ((world_data_raw[0] as i16) << 8) + (world_data_raw[1] as i16); + world.size_y = ((world_data_raw[2] as i16) << 8) + (world_data_raw[3] as i16); + world.size_z = ((world_data_raw[4] as i16) << 8) + (world_data_raw[5] as i16); + + if world.size_x > 512 || world.size_y > 256 || world.size_z > 512 { + return Err(AppError::InvalidWorldFile); + } + + if world_data_raw.len() + != (world.size_x as i32 * world.size_y as i32 * world.size_z as i32 + 6 as i32) + as usize + { + return Err(AppError::InvalidWorldFile); + } + + world.data = world_data_raw[6..].to_vec(); + Ok(world) + } else { + Ok(World { + size_x: 64, + size_y: 32, + size_z: 64, + data: World::build(64, 32, 64), + }) + } + } + + pub fn save(world_arc_clone: Arc>) -> std::io::Result<()> { + let mut to_write: Vec = Vec::new(); + { + let mut world_dat = world_arc_clone.lock().unwrap(); + to_write.push((world_dat.size_x >> 8) as u8); + to_write.push((world_dat.size_x & 0xFF) as u8); + to_write.push((world_dat.size_y >> 8) as u8); + to_write.push((world_dat.size_y & 0xFF) as u8); + to_write.push((world_dat.size_z >> 8) as u8); + to_write.push((world_dat.size_z & 0xFF) as u8); + to_write.append(&mut world_dat.data); + } + + let mut file = File::create("world.wrld")?; + file.write_all(&to_write) + } +}