diff --git a/src/game_cache.rs b/src/game_cache.rs index 5d38f10..4b0ef3d 100644 --- a/src/game_cache.rs +++ b/src/game_cache.rs @@ -4,14 +4,14 @@ const CACHE_SIZE: usize = 256; /// Represents a cache for storing game data packets. #[derive(Debug)] -struct GameCache { +pub struct GameCache { /// A deque to hold cached game data. cache: VecDeque>, } impl GameCache { /// Creates a new GameCache with a fixed size. - fn new() -> Self { + pub fn new() -> Self { Self { cache: VecDeque::with_capacity(CACHE_SIZE), } @@ -19,7 +19,7 @@ impl GameCache { /// Searches for the game data in the cache. /// Returns `Some(position)` if found, otherwise `None`. - fn find(&self, data: &Vec) -> Option { + pub fn find(&self, data: &Vec) -> Option { self.cache .iter() .position(|cached_data| cached_data == data) @@ -29,7 +29,7 @@ impl GameCache { /// Adds new game data to the cache. /// If the cache is full, it removes the oldest entry. /// Returns the position where the data was added. - fn add(&mut self, data: Vec) -> u8 { + pub fn add(&mut self, data: Vec) -> u8 { if self.cache.len() == CACHE_SIZE { self.cache.pop_front(); } @@ -38,15 +38,15 @@ impl GameCache { } /// Retrieves game data from the cache by position. - fn get(&self, position: u8) -> Option<&Vec> { + pub fn get(&self, position: u8) -> Option<&Vec> { self.cache.get(position as usize) } } /// Represents a connected client. #[derive(Debug)] -struct Client { - id: u32, +pub struct Client { + pub id: u32, /// Cache for received data (client's send cache). receive_cache: GameCache, /// Queue of pending inputs. @@ -55,7 +55,7 @@ struct Client { impl Client { /// Creates a new client with empty caches. - fn new(id: u32) -> Self { + pub fn new(id: u32) -> Self { Self { id, receive_cache: GameCache::new(), @@ -64,29 +64,29 @@ impl Client { } /// Adds an input to the pending inputs queue. - fn add_input(&mut self, data: Vec) { + pub fn add_input(&mut self, data: Vec) { self.pending_inputs.push_back(data); } /// Retrieves and removes the next input from the pending queue. - fn get_next_input(&mut self) -> Option> { + pub fn get_next_input(&mut self) -> Option> { self.pending_inputs.pop_front() } } /// Represents the result of processing a frame. #[derive(Debug)] -struct FrameResult { - frame_index: usize, - use_cache: bool, - cache_pos: u8, - data_to_send: Vec, - aggregated_data: Vec, +pub struct FrameResult { + pub frame_index: usize, + pub use_cache: bool, + pub cache_pos: u8, + pub data_to_send: Vec, + pub aggregated_data: Vec, } /// Represents the game data processor for handling inputs and outputs. #[derive(Debug)] -struct GameDataProcessor { +pub struct GameDataProcessor { /// Map of connected clients. clients: HashMap, /// Global cache for aggregated game data. @@ -99,7 +99,7 @@ struct GameDataProcessor { impl GameDataProcessor { /// Creates a new GameDataProcessor with no clients. - fn new() -> Self { + pub fn new() -> Self { Self { clients: HashMap::new(), aggregated_cache: GameCache::new(), @@ -109,78 +109,58 @@ impl GameDataProcessor { } /// Adds a new client to the processor. - fn add_client(&mut self, client_id: u32) { + pub fn add_client(&mut self, client_id: u32) { self.clients.insert(client_id, Client::new(client_id)); } /// Processes incoming game data from a client. /// Stores the data in the client's pending inputs. - fn process_incoming(&mut self, client_id: u32, data: Vec) { + pub fn process_incoming(&mut self, client_id: u32, data: Vec) { let client = self.clients.get_mut(&client_id).expect("Client not found"); - // Check if data is a cache position (1 byte). let actual_data = if data.len() == 1 { let position = data[0]; if let Some(cached_data) = client.receive_cache.get(position) { - // Data found in client's cache. cached_data.clone() } else { - // Invalid cache position; handle as needed. panic!( "Invalid cache position {} received from client {}", position, client_id ); } } else { - // Add data to client's receive cache. client.receive_cache.add(data.clone()); data }; - // Add the actual data to the client's pending inputs. client.add_input(actual_data); } /// Processes a frame if inputs from all clients are available. /// Returns Some(FrameResult) if the frame was processed. - fn process_frame(&mut self) -> Option { + pub fn process_frame(&mut self) -> Option { let mut frame_data = Vec::new(); - // First, check if all clients have inputs available without popping them let all_clients_have_input = self .clients .values() .all(|client| !client.pending_inputs.is_empty()); if !all_clients_have_input { - // Cannot process frame yet return None; } - // All clients have provided input; now proceed to pop inputs and process the frame. for (&client_id, client) in &mut self.clients { - // Now safe to pop inputs let input = client.get_next_input().expect("Input should be available"); frame_data.extend(input.clone()); - // Store the input per frame. self.frame_inputs .entry(self.frame_index) .or_default() .insert(client_id, input); } - // Proceed with frame processing - println!("\n--- Processing Frame {} ---", self.frame_index + 1); - - // Prepare outgoing data for clients. let (use_cache, cache_pos, data_to_send) = self.prepare_outgoing(frame_data.clone()); - println!( - "server->clients: use_cache_pos={}, cache_pos={}, data_to_send={:?}", - use_cache, cache_pos, data_to_send - ); - println!("Aggregated data: {:?}", frame_data); - // Create a FrameResult to return. let frame_result = FrameResult { frame_index: self.frame_index, use_cache, @@ -189,20 +169,15 @@ impl GameDataProcessor { aggregated_data: frame_data, }; - // Increment frame index. self.frame_index += 1; Some(frame_result) } /// Prepares outgoing game data to send to clients. - /// Returns a tuple indicating whether to use cache, cache position, and data to send. - fn prepare_outgoing(&mut self, data: Vec) -> (bool, u8, Vec) { - // Check if the aggregated data is already in the global cache. + pub fn prepare_outgoing(&mut self, data: Vec) -> (bool, u8, Vec) { if let Some(pos) = self.aggregated_cache.find(&data) { - // Data found in cache; send cache position. (true, pos, Vec::new()) } else { - // Data not found; add to cache and send data. let pos = self.aggregated_cache.add(data.clone()); (false, pos, data) } @@ -222,13 +197,11 @@ mod tests { processor.add_client(2); // Client ID 2 // Expected cache usage and positions per frame. - let expected_results = vec![ - (false, 0), // Frame 1: New data, cache position 0 + let expected_results = [(false, 0), // Frame 1: New data, cache position 0 (true, 0), // Frame 2: Using cache position 0 (false, 1), // Frame 3: New data, cache position 1 (false, 2), // Frame 4: New data, cache position 2 - (true, 1), // Frame 5: Using cache position 1 - ]; + (true, 1)]; // Test inputs from clients at different times. let inputs = vec![ diff --git a/src/handlers.rs b/src/handlers.rs deleted file mode 100644 index f49e5fd..0000000 --- a/src/handlers.rs +++ /dev/null @@ -1,1294 +0,0 @@ -use bytes::{Buf, BufMut, BytesMut}; -use chrono::Local; -use core::num; -use std::collections::{HashMap, HashSet}; -use std::error::Error; -use std::sync::Arc; -use std::time::Instant; -use tokio::io; -use tokio::net::UdpSocket; -use tokio::sync::{broadcast, mpsc, Mutex}; - -use crate::packet_history::PacketHistory; -use crate::{ - ClientInfo, GameInfo, Message, PLAYER_STATUS_IDLE, PLAYER_STATUS_NET_SYNC, - PLAYER_STATUS_PLAYING, -}; - -pub async fn handle_control_socket(control_socket: Arc) -> io::Result<()> { - let mut buf = [0u8; 4096]; - loop { - let (len, src) = control_socket.recv_from(&mut buf).await?; - let data = &buf[..len]; - - // Handle the HELLO0.83 message - if data == b"HELLO0.83\x00" { - let response = format!("HELLOD00D{}\0", crate::MAIN_PORT).into_bytes(); - control_socket.send_to(&response, src).await?; - } - // Handle the PING message - else if data == b"PING\x00" { - let response = b"PONG\x00".to_vec(); - control_socket.send_to(&response, src).await?; - } else { - let ascii_string: String = data - .iter() - .map(|&b| if b.is_ascii() { b as char } else { '.' }) // Replace invalid with '.' - .collect(); - eprintln!( - "Unknown message on control socket from {}: {:?}, {}", - src, data, ascii_string - ); - } - } -} - -pub fn parse_packet(data: &[u8]) -> Result, String> { - let mut buf = BytesMut::from(data); - if buf.is_empty() { - return Err("Packet is empty.".to_string()); - } - let num_messages = buf.get_u8(); - - let mut messages = Vec::new(); - - for _ in 0..num_messages { - if buf.len() < 5 { - println!("Current buffer content: {:02X?}", buf); - return Err("Incomplete message header.".to_string()); - } - - let message_number = buf.get_u16_le(); - let message_length = buf.get_u16_le(); - let message_type = buf.get_u8(); - - if buf.len() < (message_length - 1) as usize { - return Err("Incomplete message data.".to_string()); - } - - let message_data = buf.split_to((message_length - 1) as usize); - - messages.push(ParsedMessage { - message_number, - message_length, - message_type, - data: message_data.to_vec(), - }); - } - - Ok(messages) -} - -pub struct ParsedMessage { - pub message_number: u16, - pub message_length: u16, - pub message_type: u8, - pub data: Vec, -} - -pub async fn handle_message( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - next_game_id: Arc>, - next_user_id: Arc>, - packet_history: Arc, -) -> Result<(), Box> { - match message.message_type { - 0x01 => handle_user_quit(message, src, tx, clients, games, packet_history).await?, - 0x03 => { - handle_user_login( - message, - src, - tx, - clients, - next_user_id, - games, - packet_history, - ) - .await? - } - // 0x04 => handle_server_status(src, tx, clients, games, packet_history).await, // Corrected line - // 0x05 => handle_server_to_client_ack(message, src, tx).await, - 0x06 => handle_client_to_server_ack(src, tx, games, clients, packet_history).await?, - 0x07 => handle_global_chat(message, src, tx, clients, packet_history).await?, - 0x08 => handle_game_chat(message, src, tx, clients, games, packet_history).await?, - 0x09 => handle_client_keep_alive(message, src, tx).await?, - 0x0A => { - handle_create_game( - message, - src, - tx, - clients, - games, - next_game_id, - packet_history, - ) - .await? - } - 0x0B => handle_quit_game(message.data, src, tx, clients, games, packet_history).await?, - 0x0C => handle_join_game(message, src, tx, clients, games, packet_history).await?, - 0x0F => { - handle_kick_user(message, src, tx, clients, games, packet_history).await?; - } - 0x11 => handle_start_game(message, src, tx, clients, games, packet_history).await?, - 0x12 => { - println!( - "[{}] Received 0x12: Game Sync Request", - Local::now().format("%Y-%m-%d %H:%M:%S%.3f") - ); - handle_game_data(message, src, tx, clients, games, packet_history).await?; - } - 0x13 => { - println!( - "[{}] Received Game Cache", - Local::now().format("%Y-%m-%d %H:%M:%S%.3f") - ); - } - 0x15 => { - handle_ready_to_play_signal(message, src, tx, clients, games, packet_history).await? - } - - _ => { - println!("Unknown message type: 0x{:02X}", message.message_type); - // Err("Unknown message type".to_string())? - } - } - Ok(()) -} - -/* -' Server Notification: -' NB : Username -' 2B : UserID -' NB : Message - */ -pub async fn handle_user_quit( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - - // NB: Empty String - let _empty = read_string(&mut buf); - // 2B: 0xFF - let _code = if buf.len() >= 2 { buf.get_u16_le() } else { 0 }; - // NB: Message - let user_message = read_string(&mut buf); - - handle_quit_game( - vec![0x00, 0xFF, 0xFF], - src, - tx.clone(), - clients.clone(), - games.clone(), - packet_history.clone(), - ) - .await?; - // Remove client from list - let mut clients_lock = clients.lock().await; - if let Some(client_info) = clients_lock.remove(src) { - println!( - "User quit: username='{}', addr={}, message='{}'", - client_info.username, src, user_message - ); - let mut data = BytesMut::new(); - data.put(client_info.username.as_bytes()); - data.put_u8(0); - data.put_u16_le(client_info.user_id); - data.put(user_message.as_bytes()); - data.put_u8(0); - broadcast_packet(&tx, &packet_history, &clients_lock, 0x01, data.to_vec()).await?; - } else { - println!( - "Unknown client quit: addr={}, message='{}'", - src, user_message - ); - } - drop(clients_lock); - // Remove from packet history - packet_history.remove_client(src).await; - Ok(()) -} - -pub async fn make_user_joined( - src: &std::net::SocketAddr, - clients: Arc>>, -) -> Result, Box> { - let mut clients_lock = clients.lock().await; - let client_info_clone = if let Some(client_info) = clients_lock.get_mut(src) { - client_info.clone() - } else { - return Err("Client not found.".into()); - }; - drop(clients_lock); - let mut data = BytesMut::new(); - data.put(client_info_clone.username.as_bytes()); - data.put_u8(0); - data.put_u16_le(client_info_clone.user_id); - data.put_u32_le(client_info_clone.ping); - data.put_u8(client_info_clone.conn_type); - Ok(data.to_vec()) -} - -// Helper functions -async fn send_packet( - tx: &mpsc::Sender, - packet_history: &Arc, - addr: &std::net::SocketAddr, - packet_type: u8, - data: Vec, -) -> Result<(), Box> { - let response_packet = packet_history - .make_send_packet(*addr, packet_type, data) - .await; - tx.send(Message { - data: response_packet, - addr: *addr, - }) - .await?; - Ok(()) -} - -async fn broadcast_packet( - tx: &mpsc::Sender, - packet_history: &Arc, - clients: &HashMap, - packet_type: u8, - data: Vec, -) -> Result<(), Box> { - for (client_addr, _) in clients.iter() { - let response_packet = packet_history - .make_send_packet(*client_addr, packet_type, data.clone()) - .await; - tx.send(Message { - data: response_packet, - addr: *client_addr, - }) - .await?; - } - Ok(()) -} - -fn build_join_game_response(user: &ClientInfo) -> Vec { - let mut data = Vec::new(); - data.put_u8(0); // Empty string [00] - data.put_u32_le(0); // Pointer to Game on Server Side - data.put(user.username.as_bytes()); - data.put_u8(0); - data.put_u32_le(user.ping); - data.put_u16_le(user.user_id); - data.put_u8(user.conn_type); - data -} - -fn build_new_game_notification( - username: &str, - game_name: &str, - emulator_name: &str, - game_id: u32, -) -> Vec { - let mut data = Vec::new(); - data.put(username.as_bytes()); - data.put_u8(0); - data.put(game_name.as_bytes()); - data.put_u8(0); - data.put(emulator_name.as_bytes()); - data.put_u8(0); - data.put_u32_le(game_id); - data -} - -pub async fn handle_user_login( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - next_user_id: Arc>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - packet_history.remove_client(src).await; - let mut buf = BytesMut::from(&message.data[..]); - - // NB: Username - let username = read_string(&mut buf); - // NB: Emulator Name - let emulator_name = read_string(&mut buf); - // 1B: Connection Type - let conn_type = if !buf.is_empty() { buf.get_u8() } else { 0 }; - - println!( - "User login info: username='{}', emulator='{}', conn_type={}, addr={}", - username, emulator_name, conn_type, src - ); - - // Assign a user ID - let user_id = { - let mut next_user_id = next_user_id.lock().await; - let id = *next_user_id; - *next_user_id += 1; - id - }; - - // Add client to clients list - let mut clients_lock = clients.lock().await; - clients_lock.insert( - *src, - ClientInfo { - username: username.clone(), - emulator_name: emulator_name.clone(), - conn_type, - user_id, - ping: 0, - player_status: PLAYER_STATUS_IDLE, // Idle - game_id: None, - last_ping_time: None, - ack_count: 0, - }, - ); - drop(clients_lock); - - // Now, send Server to Client ACK [0x05], multiple times - let mut data = BytesMut::new(); - data.put_u8(0); // Empty string [00] - data.put_u32_le(0); - data.put_u32_le(1); - data.put_u32_le(2); - data.put_u32_le(3); - - let response_packet = packet_history - .make_send_packet(*src, 0x05, data.to_vec()) - .await; - - tx.send(Message { - data: response_packet, - addr: *src, - }) - .await?; - - // Record the time sent for ping calculation - let mut clients_lock = clients.lock().await; - if let Some(client_info) = clients_lock.get_mut(src) { - client_info.last_ping_time = Some(Instant::now()); - } - drop(clients_lock); - - Ok(()) -} - -pub async fn handle_global_chat( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - - // NB: Empty String - let _empty = read_string(&mut buf); - // NB: Message - let chat_message = read_string(&mut buf); - - // Get username from clients list - let clients_lock = clients.lock().await; - let username = if let Some(client_info) = clients_lock.get(src) { - client_info.username.clone() - } else { - "Unknown".to_string() - }; - drop(clients_lock); - - println!( - "Global chat: username='{}', message='{}'", - username, chat_message - ); - - // Server notification creation - let mut data = BytesMut::new(); - data.put(username.as_bytes()); - data.put_u8(0); // NULL terminator - data.put(chat_message.as_bytes()); - data.put_u8(0); // NULL terminator - - // Send message to all clients - let clients_lock = clients.lock().await; - for (client_addr, _) in clients_lock.iter() { - let response_packet = packet_history - .make_send_packet(*client_addr, 0x07, data.to_vec()) - .await; - tx.send(Message { - data: response_packet, - addr: *client_addr, - }) - .await?; - } - - Ok(()) -} - -pub fn read_string(buf: &mut BytesMut) -> String { - let mut s = Vec::new(); - while let Some(&b) = buf.first() { - buf.advance(1); - if b == 0 { - break; - } - s.push(b); - } - String::from_utf8_lossy(&s).to_string() -} - -pub async fn make_server_status( - src: &std::net::SocketAddr, - clients: Arc>>, - games: Arc>>, -) -> Result, Box> { - let clients_lock = clients.lock().await; - let games_lock = games.lock().await; - - // Prepare response data - let mut data = BytesMut::new(); - data.put_u8(0); // Empty string [00] - - // Number of users (excluding self) - let num_users = (clients_lock.len() - 1) as u32; - data.put_u32_le(num_users); - - // Number of games - let num_games = games_lock.len() as u32; - data.put_u32_le(num_games); - - // User list - for (addr, client_info) in clients_lock.iter() { - if addr != src { - data.put(client_info.username.as_bytes()); - data.put_u8(0); // NULL terminator - data.put_u32_le(client_info.ping); - data.put_u8(client_info.player_status); - data.put_u16_le(client_info.user_id); - data.put_u8(client_info.conn_type); - } - } - - // Game list - for game_info in games_lock.values() { - data.put(game_info.game_name.as_bytes()); - data.put_u8(0); // NULL terminator - data.put_u32_le(game_info.game_id); - data.put(game_info.emulator_name.as_bytes()); - data.put_u8(0); // NULL terminator - data.put(game_info.owner.as_bytes()); - data.put_u8(0); // NULL terminator - data.put(format!("{}/{}\0", game_info.num_players, game_info.max_players).as_bytes()); - data.put_u8(game_status_to_byte(game_info.game_status)); - } - Ok(data.to_vec()) -} - -pub fn make_server_information() -> Result, Box> { - // Prepare response data - // ' NB : "Server\0" - // ' NB : Message - let mut data = BytesMut::new(); - data.put("Server\0".as_bytes()); - data.put("Welcome to the Kaillera server!\0".as_bytes()); - Ok(data.to_vec()) -} - -fn game_status_to_byte(status: u8) -> u8 { - match status { - 0 => 0, // Waiting - 1 => 1, // Playing - 2 => 2, // Netsync - _ => 0, // Default to Waiting - } -} - -pub async fn handle_client_to_server_ack( - src: &std::net::SocketAddr, - tx: mpsc::Sender, - games: Arc>>, - clients: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - // Client to Server ACK [0x06] - // Calculate ping - let mut ack_count: u16 = 0; - let mut clients_lock = clients.lock().await; - if let Some(client_info) = clients_lock.get_mut(src) { - if let Some(last_ping_time) = client_info.last_ping_time { - let ping = last_ping_time.elapsed().as_millis() as u32; - client_info.ping = ping; - client_info.last_ping_time = Some(Instant::now()); - client_info.ack_count += 1; - ack_count = client_info.ack_count; - } - } - drop(clients_lock); - - if ack_count >= 3 { - let data = make_server_status(src, clients.clone(), games).await?; - let response_packet = packet_history.make_send_packet(*src, 0x04, data).await; - tx.send(Message { - data: response_packet, - addr: *src, - }) - .await?; - let data = make_user_joined(src, clients.clone()).await?; - for (client_addr, _) in clients.lock().await.iter() { - let response_packet = packet_history - .make_send_packet(*client_addr, 0x02, data.clone()) - .await; - tx.send(Message { - data: response_packet, - addr: *client_addr, - }) - .await?; - } - let data = make_server_information()?; - tx.send(Message { - data: packet_history.make_send_packet(*src, 0x17, data).await, - addr: *src, - }) - .await?; - } else { - // Server notification creation - let mut data = BytesMut::new(); - data.put_u8(0); - data.put_u32_le(0); - data.put_u32_le(1); - data.put_u32_le(2); - data.put_u32_le(3); - tx.send(Message { - data: packet_history - .make_send_packet(*src, 0x05, data.to_vec()) - .await, - addr: *src, - }) - .await?; - } - - Ok(()) -} - -pub async fn handle_game_chat( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - - // NB: Empty String - let _empty = read_string(&mut buf); - // NB: Message - let chat_message = read_string(&mut buf); - - // Get username and game ID - let clients_lock = clients.lock().await; - let (username, game_id) = if let Some(client_info) = clients_lock.get(src) { - (client_info.username.clone(), client_info.game_id) - } else { - ("Unknown".to_string(), None) - }; - drop(clients_lock); - - if let Some(game_id) = game_id { - println!( - "Game chat: '{}' (Game ID {}): '{}'", - username, game_id, chat_message - ); - - // Response creation - let mut data = BytesMut::new(); - data.put(username.as_bytes()); - data.put_u8(0); - data.put(chat_message.as_bytes()); - data.put_u8(0); - - // Send to all clients in the same game - let clients_lock = clients.lock().await; - for (client_addr, client_info) in clients_lock.iter() { - if client_info.game_id == Some(game_id) { - let response_packet = packet_history - .make_send_packet(*client_addr, 0x08, data.to_vec()) - .await; - tx.send(Message { - data: response_packet.clone(), - addr: *client_addr, - }) - .await?; - } - } - } else { - println!("Client '{}' is not in a game.", username); - } - - Ok(()) -} - -pub async fn handle_client_keep_alive( - _message: ParsedMessage, - _src: &std::net::SocketAddr, - _tx: mpsc::Sender, -) -> Result<(), Box> { - // No additional handling needed - Ok(()) -} - -// Refactored handle_create_game function -pub async fn handle_create_game( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - next_game_id: Arc>, - packet_history: Arc, -) -> Result<(), Box> { - // Parse the message to extract game_name - let mut buf = BytesMut::from(&message.data[..]); - let _ = read_string(&mut buf); // Empty String - let game_name = read_string(&mut buf); // Game Name - let _ = read_string(&mut buf); // Empty String - let _ = if buf.len() >= 4 { buf.get_u32_le() } else { 0 }; // 4B: 0xFF - - // Get client_info - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => client_info, - None => { - eprintln!("Client not found during game creation: addr={}", src); - return Ok(()); - } - }; - - // Clone user info - let user = client_info.clone(); - - // Allocate new game_id - let game_id = { - let mut game_id_lock = next_game_id.lock().await; - let id = *game_id_lock; - *game_id_lock += 1; - id - }; - - // Create new game - let mut players = HashSet::new(); - players.insert(*src); - let game_info = GameInfo { - game_id, - game_name: game_name.clone(), - emulator_name: client_info.emulator_name.clone(), - owner: client_info.username.clone(), - num_players: 1, - max_players: 4, - game_status: 0, // Waiting - players, - }; - - // Add game to games list - games.lock().await.insert(game_id, game_info.clone()); - - // Update client_info - client_info.game_id = Some(game_id); - - println!( - "Game created: '{}', Game ID: {}, Game Name: '{}'", - client_info.username, game_id, game_name - ); - - // Build data for new game notification - let data = build_new_game_notification( - &client_info.username, - &game_name, - &client_info.emulator_name, - game_id, - ); - - // Broadcast new game notification to all clients - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0A, data).await?; - - // Send game status update to the client - let status_data = make_update_game_status(&game_info).await?; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; - - // Build and send join game response to the client - let response_data = build_join_game_response(&user); - send_packet(&tx, &packet_history, src, 0x0C, response_data).await?; - - Ok(()) -} - -pub async fn handle_join_game( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - // Parse the message to extract game_id and conn_type - let mut buf = BytesMut::from(&message.data[..]); - let _ = read_string(&mut buf); - let game_id = buf.get_u32_le(); - let _ = read_string(&mut buf); - let _ = buf.get_u32_le(); - let _ = buf.get_u16_le(); - let _conn_type = buf.get_u8(); - - // Update client_info and game_info - let (client_info_clone, game_info_clone) = { - // Lock clients - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => client_info, - None => { - eprintln!("Client not found during game join: addr={}", src); - return Ok(()); - } - }; - - client_info.game_id = Some(game_id); - let client_info_clone = client_info.clone(); - - // Lock games - let mut games_lock = games.lock().await; - let game_info = match games_lock.get_mut(&game_id) { - Some(game_info) => game_info, - None => { - eprintln!("Game not found: game_id={}", game_id); - return Ok(()); - } - }; - - game_info.players.insert(*src); - game_info.num_players += 1; - let game_info_clone = game_info.clone(); // Clone game_info to use later - - (client_info_clone, game_info_clone) - }; // Locks are dropped here - - // Build status data without holding locks - let status_data = make_update_game_status(&game_info_clone).await?; - println!("Game status updated for game_id={}", game_id); - - // Get a list of client addresses to broadcast - let client_addresses = { - let clients_lock = clients.lock().await; - clients_lock.keys().cloned().collect::>() - }; - - // Broadcast game status update to all clients - for addr in client_addresses { - send_packet(&tx, &packet_history, &addr, 0x0E, status_data.clone()).await?; - } - - // Build player information data - let players_info = { - let clients_lock = clients.lock().await; - make_player_information(src, &clients_lock, &game_info_clone)? - }; - println!("Player info: {:?}", players_info); - - // Send player information to the joining client - send_packet(&tx, &packet_history, src, 0x0D, players_info.clone()).await?; - - // Build join game response data - let response_data = build_join_game_response(&client_info_clone); - - // Send join game response to existing players in the game (excluding the joining client) - for player_addr in &game_info_clone.players { - send_packet( - &tx, - &packet_history, - player_addr, - 0x0C, - response_data.clone(), - ) - .await?; - } - - Ok(()) -} - -/* - **Client to Server**: - - Empty String - - `2B`: UserID -*/ -pub async fn handle_kick_user( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - let _ = read_string(&mut buf); // Empty String - let user_id = buf.get_u16_le(); // UserID - - let (mut client_info_clone, client_addr) = { - let clients_lock = clients.lock().await; - let client_info = clients_lock.iter().find(|(_, c)| c.user_id == user_id); - match client_info { - Some((addr, client_info)) => (client_info.clone(), *addr), - None => { - eprintln!("Client not found during kick user: user_id={}", user_id); - return Ok(()); - } - } - }; - let game_id = match client_info_clone.game_id { - Some(game_id) => game_id, - None => { - eprintln!( - "Game ID not found during kick user: user_id={}", - client_info_clone.user_id - ); - return Ok(()); - } - }; - let mut game_info_clone = { - let mut games_lock = games.lock().await; - let game_info = games_lock.get_mut(&game_id); - match game_info { - Some(game_info) => game_info.clone(), - None => { - eprintln!("Game not found during kick user: game_id={}", game_id,); - return Ok(()); - } - } - }; - - client_info_clone.game_id = None; - - // Update game status - game_info_clone.num_players -= 1; - let status_data = make_update_game_status(&game_info_clone).await?; - let clients_lock = clients.lock().await; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; - for player_addr in game_info_clone.players.iter() { - let mut data = BytesMut::new(); - data.put(client_info_clone.username.as_bytes()); - data.put_u8(0); - data.put_u16_le(client_info_clone.user_id); - send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; - } - game_info_clone.players.remove(&client_addr); - - Ok(()) -} -/* - -'Quit Game State -'Client: Quit Game Request [0x0B] -'Server: Update Game Status [0x0E] -'Server: Quit Game Notification [0x0B] -' -'Close Game State -'Client: Quit Game Request [0x0B] -'Server: Close Game Notification [0x10] -'Server: Quit Game Notification [0x0B] -' 0x0B = Quit Game -' Client Request: -' NB : Empty String [00] -' 2B : 0xFF -' -' Server Notification: -' NB : Username -' 2B : UserID - -' 0x10 = Close game -' Server Notification: -' NB : Empty String [00] -' 4B : GameID - */ -pub async fn handle_quit_game( - message: Vec, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message[..]); - let _ = read_string(&mut buf); // Empty String - let _ = if buf.len() >= 2 { buf.get_u16_le() } else { 0 }; // 2B: 0xFF - // if last player, close game - // if not last player, remove player from game - let (client_info_clone, game_info_clone) = { - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => client_info, - None => { - eprintln!("Client not found during game quit: addr={}", src); - return Ok(()); - } - }; - let client_info_clone = client_info.clone(); - - let mut games_lock = games.lock().await; - let game_id = match client_info.game_id { - Some(game_id) => game_id, - None => { - eprintln!("Game ID not found during game quit: addr={}", src); - return Ok(()); - } - }; - let game_info = match games_lock.get_mut(&game_id) { - Some(game_info) => game_info, - None => { - eprintln!("Game not found during game quit: game_id={}", game_id,); - return Ok(()); - } - }; - game_info.players.remove(src); - game_info.num_players -= 1; - let game_info_clone = game_info.clone(); - - (client_info_clone, game_info_clone) - }; - if game_info_clone.owner == client_info_clone.username { - // Close the game - let game_id = game_info_clone.game_id; - let game_name = game_info_clone.game_name.clone(); - let emulator_name = game_info_clone.emulator_name.clone(); - let owner = game_info_clone.owner.clone(); - - // Remove game from games list - games.lock().await.remove(&game_id); - - // make close game notification - let mut data = BytesMut::new(); - data.put_u8(0x00); - // game id - data.put_u32_le(game_id); - let clients_lock = clients.lock().await; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x10, data.to_vec()).await?; - // quit game notification - for player_addr in game_info_clone.players.iter() { - let mut data = BytesMut::new(); - data.put(client_info_clone.username.as_bytes()); - data.put_u8(0); - data.put_u16_le(client_info_clone.user_id); - send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; - } - } else { - // Update game status - let status_data = make_update_game_status(&game_info_clone).await?; - let clients_lock = clients.lock().await; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; - for player_addr in game_info_clone.players.iter() { - let mut data = BytesMut::new(); - data.put(client_info_clone.username.as_bytes()); - data.put_u8(0); - data.put_u16_le(client_info_clone.user_id); - send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; - } - } - Ok(()) -} - -/* -' 0x11 = Start Game -' Client Request: -' NB : Empty String [00] -' 2B : 0xFF -' 1B : 0xFF -' 1B : 0xFF -' -' Server Notification: -' NB : Empty String [00] -' 2B : Frame Delay (eg. (connectionType * (frameDelay + 1) <-Block on that frame -' 1B : Your Player Number (eg. if you're player 1 or 2...) -' 1B : Total Players -- **Client**: Sends **Start Game Request** `[0x11]` -- **Server**: Sends **Update Game Status** `[0x0E]` -- **Server**: Sends **Start Game Notification** `[0x11]` -- **Client**: Enters **Netsync Mode** and waits for all players to send **Ready to Play Signal** `[0x15]` -- **Server**: Sends **Update Game Status** for whole server players`[0x0E]` -- **Server**: Enters **Playing Mode** after receiving **Ready to Play Signal Notification** `[0x15]` from all players in room -- **Client(s)**: Exchange data using **Game Data Send** `[0x12]` or **Game Cache Send** `[0x13]` -- **Server**: Sends data accordingly using **Game Data Notify** `[0x12]` or **Game Cache Notify** `[0x13]` - - */ -pub async fn handle_start_game( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - let _ = read_string(&mut buf); // Empty String - let _ = buf.get_u32_le(); // 0xFFFF 0xFF 0xFF - - let (client_info_clone, game_info_clone) = { - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => client_info, - None => { - eprintln!("Client not found during game start: addr={}", src); - return Ok(()); - } - }; - let client_info_clone = client_info.clone(); - - let mut games_lock = games.lock().await; - let game_id = match client_info.game_id { - Some(game_id) => game_id, - None => { - eprintln!("Game ID not found during game start: addr={}", src); - return Ok(()); - } - }; - let game_info = match games_lock.get_mut(&game_id) { - Some(game_info) => game_info, - None => { - eprintln!("Game not found during game start: game_id={}", game_id,); - return Ok(()); - } - }; - game_info.game_status = 1; // Playing - let game_info_clone = game_info.clone(); - - (client_info_clone, game_info_clone) - }; - - // Update game status - let status_data = make_update_game_status(&game_info_clone).await?; - let clients_lock = clients.lock().await; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; - - // Send start game notification - for (i, player_addr) in game_info_clone.players.iter().enumerate() { - let mut data = BytesMut::new(); - data.put_u8(0); - data.put_u16_le(4); // Frame Delay - data.put_u8((i + 1) as u8); // Player Number - data.put_u8(game_info_clone.players.len() as u8); // Total Players - send_packet(&tx, &packet_history, player_addr, 0x11, data.to_vec()).await?; - } - Ok(()) -} - -pub async fn handle_ready_to_play_signal( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - let _ = buf.get_u8(); // Empty String - - let (mut client_info_clone, game_info_clone) = { - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => { - client_info.player_status = PLAYER_STATUS_NET_SYNC; // Ready to play - client_info - } - None => { - eprintln!("Client not found during ready to play signal: addr={}", src); - return Ok(()); - } - }; - let client_info_clone = client_info.clone(); - - let mut games_lock = games.lock().await; - let game_id = match client_info.game_id { - Some(game_id) => game_id, - None => { - eprintln!( - "Game ID not found during ready to play signal: addr={}", - src - ); - return Ok(()); - } - }; - let game_info = match games_lock.get_mut(&game_id) { - Some(game_info) => game_info, - None => { - eprintln!( - "Game not found during ready to play signal: game_id={}", - game_id, - ); - return Ok(()); - } - }; - let game_info_clone = game_info.clone(); - - (client_info_clone, game_info_clone) - }; - - // Update game status - { - let status_data = make_update_game_status(&game_info_clone).await?; - let clients_lock = clients.lock().await; - broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; - } - - let all_user_ready_to_signal = { - let clients_lock = clients.lock().await; - game_info_clone.players.iter().all(|player_addr| { - if let Some(client_info) = clients_lock.get(player_addr) { - println!("client_info.player_status: {}", client_info.player_status); - client_info.player_status == PLAYER_STATUS_NET_SYNC - } else { - println!("None client_info"); - false - } - }) - }; - println!("12"); - // Send ready to play signal notification - if all_user_ready_to_signal { - println!("all user ready to signal"); - for player_addr in game_info_clone.players.iter() { - let mut data = BytesMut::new(); - data.put_u8(0); - send_packet(&tx, &packet_history, player_addr, 0x15, data.to_vec()).await?; - } - println!("13"); - // Make user play status - { - let mut clients_lock = clients.lock().await; - for player_addr in game_info_clone.players.iter() { - if let Some(client_info) = clients_lock.get_mut(player_addr) { - client_info.player_status = PLAYER_STATUS_PLAYING; // Playing - } - } - } - } - println!("14"); - Ok(()) -} - -/* -- **NB**: Empty String `[00]` -- **2B**: Length of Game Data -- **NB**: Game Data - */ -pub async fn handle_game_data( - message: ParsedMessage, - src: &std::net::SocketAddr, - tx: mpsc::Sender, - clients: Arc>>, - games: Arc>>, - packet_history: Arc, -) -> Result<(), Box> { - let mut buf = BytesMut::from(&message.data[..]); - let _ = buf.get_u8(); // Empty String - let length_of_game_data = buf.get_u16_le(); - let game_data = buf.split_to(length_of_game_data as usize).to_vec(); - - let (mut client_info_clone, game_info_clone) = { - let mut clients_lock = clients.lock().await; - let client_info = match clients_lock.get_mut(src) { - Some(client_info) => client_info, - None => { - eprintln!("Client not found during ready to play signal: addr={}", src); - return Ok(()); - } - }; - let client_info_clone = client_info.clone(); - - let mut games_lock = games.lock().await; - let game_id = match client_info.game_id { - Some(game_id) => game_id, - None => { - eprintln!( - "Game ID not found during ready to play signal: addr={}", - src - ); - return Ok(()); - } - }; - let game_info = match games_lock.get_mut(&game_id) { - Some(game_info) => game_info, - None => { - eprintln!( - "Game not found during ready to play signal: game_id={}", - game_id, - ); - return Ok(()); - } - }; - let game_info_clone = game_info.clone(); - - (client_info_clone, game_info_clone) - }; - for player_addr in game_info_clone.players.iter() { - let mut data = BytesMut::new(); - data.put_u8(0); - data.put_u16_le(length_of_game_data); - data.put(&game_data[..]); - send_packet(&tx, &packet_history, player_addr, 0x12, data.to_vec()).await?; - } - Ok(()) -} - -// ' 0x0D = Player Information -// ' Server Notification: -// ' NB : Empty String [00] -// ' 4B : Number of Users in Room [not including you] -// ' NB : Username -// ' 4B : Ping -// ' 2B : UserID -// ' 1B : Connection Type (6=Bad, 5=Low, 4=Average, 3=Good, 2=Excellent, & 1=LAN) -pub fn make_player_information( - src: &std::net::SocketAddr, - clients: &HashMap, - game_info: &GameInfo, -) -> Result, Box> { - // Prepare response data - let mut data = BytesMut::new(); - data.put_u8(0); // Empty string [00] - data.put_u32_le((game_info.players.len() - 1) as u32); - println!("Player count: {}", game_info.players.len()); - for player_addr in game_info.players.iter() { - if player_addr != src { - if let Some(client_info) = clients.get(player_addr) { - data.put(client_info.username.as_bytes()); - data.put_u8(0); // NULL terminator - data.put_u32_le(client_info.ping); - data.put_u16_le(client_info.user_id); - data.put_u8(client_info.conn_type); - } - } - } - Ok(data.to_vec()) -} - -// ' 0x0E = Update Game Status -// ' Server Notification: -// ' NB : Empty String [00] -// ' 4B : GameID -// ' 1B : Game Status (0=Waiting, 1=Playing, 2=Netsync) -// ' 1B : Number of Players in Room -// ' 1B : Maximum Players -pub async fn make_update_game_status(game_info: &GameInfo) -> Result, Box> { - let mut data = BytesMut::new(); - data.put_u8(0); // Empty string [00] - data.put_u32_le(game_info.game_id); - data.put_u8(game_info.game_status); - data.put_u8(game_info.num_players); - data.put_u8(game_info.max_players); - Ok(data.to_vec()) -} diff --git a/src/handlers/create_game.rs b/src/handlers/create_game.rs new file mode 100644 index 0000000..5cdce89 --- /dev/null +++ b/src/handlers/create_game.rs @@ -0,0 +1,91 @@ +use crate::packet_history::PacketHistory; +use crate::*; +use bytes::{Buf, BytesMut}; +use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +// Refactored handle_create_game function +pub async fn handle_create_game( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + next_game_id: Arc>, + packet_history: Arc, +) -> Result<(), Box> { + // Parse the message to extract game_name + let mut buf = BytesMut::from(&message.data[..]); + let _ = util::read_string(&mut buf); // Empty String + let game_name = util::read_string(&mut buf); // Game Name + let _ = util::read_string(&mut buf); // Empty String + let _ = if buf.len() >= 4 { buf.get_u32_le() } else { 0 }; // 4B: 0xFF + + // Get client_info + let mut clients_lock = clients.lock().await; + let client_info = match clients_lock.get_mut(src) { + Some(client_info) => client_info, + None => { + eprintln!("Client not found during game creation: addr={}", src); + return Ok(()); + } + }; + + // Clone user info + let user = client_info.clone(); + + // Allocate new game_id + let game_id = { + let mut game_id_lock = next_game_id.lock().await; + let id = *game_id_lock; + *game_id_lock += 1; + id + }; + + // Create new game + let mut players = HashSet::new(); + players.insert(*src); + let game_info = GameInfo { + game_id, + game_name: game_name.clone(), + emulator_name: client_info.emulator_name.clone(), + owner: client_info.username.clone(), + num_players: 1, + max_players: 4, + game_status: 0, // Waiting + players, + }; + + // Add game to games list + // games.lock().await.insert(game_id, game_i.clone(n)fo); + + // Update client_info + client_info.game_id = Some(game_id); + + println!( + "Game created: '{}', Game ID: {}, Game Name: '{}'", + client_info.username, game_id, game_name + ); + + // Build data for new game notification + let data = util::build_new_game_notification( + &client_info.username, + &game_name, + &client_info.emulator_name, + game_id, + ); + + // Broadcast new game notification to all clients + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0A, data).await?; + + // Send game status update to the client + let status_data = util::make_update_game_status(&game_info)?; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; + + // Build and send join game response to the client + let response_data = util::build_join_game_response(&user); + util::send_packet(&tx, &packet_history, src, 0x0C, response_data).await?; + + Ok(()) +} diff --git a/src/handlers/data.rs b/src/handlers/data.rs new file mode 100644 index 0000000..5da6bbf --- /dev/null +++ b/src/handlers/data.rs @@ -0,0 +1,31 @@ +use std::{collections::HashSet, time::Instant}; + +type PlayerStatus = u8; +pub const PLAYER_STATUS_PLAYING: PlayerStatus = 0; +pub const PLAYER_STATUS_IDLE: PlayerStatus = 1; +pub const PLAYER_STATUS_NET_SYNC: PlayerStatus = 2; +// ClientInfo and GameInfo structs need to be accessible in both files +#[derive(Clone)] +pub struct ClientInfo { + pub username: String, + pub emulator_name: String, + pub conn_type: u8, + pub user_id: u16, + pub ping: u32, + pub player_status: PlayerStatus, + pub game_id: Option, + pub last_ping_time: Option, + pub ack_count: u16, +} + +#[derive(Clone)] +pub struct GameInfo { + pub game_id: u32, + pub game_name: String, + pub emulator_name: String, + pub owner: String, + pub num_players: u8, + pub max_players: u8, + pub game_status: u8, // 0=Waiting, 1=Playing, 2=Netsync + pub players: HashSet, // 이 필드 추가 +} diff --git a/src/handlers/game_chat.rs b/src/handlers/game_chat.rs new file mode 100644 index 0000000..1325c5a --- /dev/null +++ b/src/handlers/game_chat.rs @@ -0,0 +1,64 @@ +use crate::packet_history::PacketHistory; +use crate::*; +use bytes::{BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +pub async fn handle_game_chat( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + + // NB: Empty String + let _empty = util::read_string(&mut buf); + // NB: Message + let chat_message = util::read_string(&mut buf); + + // Get username and game ID + let clients_lock = clients.lock().await; + let (username, game_id) = if let Some(client_info) = clients_lock.get(src) { + (client_info.username.clone(), client_info.game_id) + } else { + ("Unknown".to_string(), None) + }; + drop(clients_lock); + + if let Some(game_id) = game_id { + println!( + "Game chat: '{}' (Game ID {}): '{}'", + username, game_id, chat_message + ); + + // Response creation + let mut data = BytesMut::new(); + data.put(username.as_bytes()); + data.put_u8(0); + data.put(chat_message.as_bytes()); + data.put_u8(0); + + // Send to all clients in the same game + let clients_lock = clients.lock().await; + for (client_addr, client_info) in clients_lock.iter() { + if client_info.game_id == Some(game_id) { + let response_packet = packet_history + .make_send_packet(*client_addr, 0x08, data.to_vec()) + .await; + tx.send(Message { + data: response_packet.clone(), + addr: *client_addr, + }) + .await?; + } + } + } else { + println!("Client '{}' is not in a game.", username); + } + + Ok(()) +} diff --git a/src/handlers/global_chat.rs b/src/handlers/global_chat.rs new file mode 100644 index 0000000..eaf1c80 --- /dev/null +++ b/src/handlers/global_chat.rs @@ -0,0 +1,57 @@ +use crate::packet_history::PacketHistory; +use crate::*; +use bytes::{BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +pub async fn handle_global_chat( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + + // NB: Empty String + let _empty = util::read_string(&mut buf); + // NB: Message + let chat_message = util::read_string(&mut buf); + + // Get username from clients list + let clients_lock = clients.lock().await; + let username = if let Some(client_info) = clients_lock.get(src) { + client_info.username.clone() + } else { + "Unknown".to_string() + }; + drop(clients_lock); + + println!( + "Global chat: username='{}', message='{}'", + username, chat_message + ); + + // Server notification creation + let mut data = BytesMut::new(); + data.put(username.as_bytes()); + data.put_u8(0); // NULL terminator + data.put(chat_message.as_bytes()); + data.put_u8(0); // NULL terminator + + // Send message to all clients + let clients_lock = clients.lock().await; + for (client_addr, _) in clients_lock.iter() { + let response_packet = packet_history + .make_send_packet(*client_addr, 0x07, data.to_vec()) + .await; + tx.send(Message { + data: response_packet, + addr: *client_addr, + }) + .await?; + } + + Ok(()) +} diff --git a/src/handlers/handlerf.rs b/src/handlers/handlerf.rs new file mode 100644 index 0000000..06c20e3 --- /dev/null +++ b/src/handlers/handlerf.rs @@ -0,0 +1,335 @@ +use bytes::{Buf, BufMut, BytesMut}; +use chrono::Local; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::{mpsc, Mutex}; + +use crate::packet_history::PacketHistory; +use crate::*; + +pub async fn handle_message( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + next_game_id: Arc>, + next_user_id: Arc>, + packet_history: Arc, +) -> Result<(), Box> { + match message.message_type { + 0x01 => { + user_quit::handle_user_quit(message, src, tx, clients, games, packet_history).await? + } + 0x03 => { + user_login::handle_user_login( + message, + src, + tx, + clients, + next_user_id, + games, + packet_history, + ) + .await? + } + // 0x04 => handle_server_status(src, tx, clients, games, packet_history).await, // Corrected line + // 0x05 => handle_server_to_client_ack(message, src, tx).await, + 0x06 => handle_client_to_server_ack(src, tx, games, clients, packet_history).await?, + 0x07 => global_chat::handle_global_chat(message, src, tx, clients, packet_history).await?, + 0x08 => { + game_chat::handle_game_chat(message, src, tx, clients, games, packet_history).await? + } + 0x09 => handle_client_keep_alive(message, src, tx).await?, + 0x0A => { + create_game::handle_create_game( + message, + src, + tx, + clients, + games, + next_game_id, + packet_history, + ) + .await? + } + 0x0B => { + handlers::quit_game::handle_quit_game( + message.data, + src, + tx, + clients, + games, + packet_history, + ) + .await? + } + 0x0C => { + join_game::handle_join_game(message, src, tx, clients, games, packet_history).await? + } + 0x0F => { + kick_user::handle_kick_user(message, src, tx, clients, games, packet_history).await?; + } + 0x11 => { + start_game::handle_start_game(message, src, tx, clients, games, packet_history).await? + } + 0x12 => { + println!( + "[{}] Received 0x12: Game Sync Request", + Local::now().format("%Y-%m-%d %H:%M:%S%.3f") + ); + handle_game_data(message, src, tx, clients, games, packet_history).await?; + } + 0x13 => { + println!( + "[{}] Received Game Cache", + Local::now().format("%Y-%m-%d %H:%M:%S%.3f") + ); + } + 0x15 => { + handle_ready_to_play_signal(message, src, tx, clients, games, packet_history).await? + } + + _ => { + println!("Unknown message type: 0x{:02X}", message.message_type); + // Err("Unknown message type".to_string())? + } + } + Ok(()) +} + +pub async fn handle_client_to_server_ack( + src: &std::net::SocketAddr, + tx: mpsc::Sender, + games: Arc>>, + clients: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + // Client to Server ACK [0x06] + // Calculate ping + let mut ack_count: u16 = 0; + let mut clients_lock = clients.lock().await; + if let Some(client_info) = clients_lock.get_mut(src) { + if let Some(last_ping_time) = client_info.last_ping_time { + let ping = last_ping_time.elapsed().as_millis() as u32; + client_info.ping = ping; + client_info.last_ping_time = Some(Instant::now()); + client_info.ack_count += 1; + ack_count = client_info.ack_count; + } + } + drop(clients_lock); + + if ack_count >= 3 { + let data = util::make_server_status(src, clients.clone(), games).await?; + let response_packet = packet_history.make_send_packet(*src, 0x04, data).await; + tx.send(Message { + data: response_packet, + addr: *src, + }) + .await?; + let data = util::make_user_joined(src, clients.clone()).await?; + for (client_addr, _) in clients.lock().await.iter() { + let response_packet = packet_history + .make_send_packet(*client_addr, 0x02, data.clone()) + .await; + tx.send(Message { + data: response_packet, + addr: *client_addr, + }) + .await?; + } + let data = util::make_server_information()?; + tx.send(Message { + data: packet_history.make_send_packet(*src, 0x17, data).await, + addr: *src, + }) + .await?; + } else { + // Server notification creation + let mut data = BytesMut::new(); + data.put_u8(0); + data.put_u32_le(0); + data.put_u32_le(1); + data.put_u32_le(2); + data.put_u32_le(3); + tx.send(Message { + data: packet_history + .make_send_packet(*src, 0x05, data.to_vec()) + .await, + addr: *src, + }) + .await?; + } + + Ok(()) +} + +pub async fn handle_client_keep_alive( + _message: kaillera::protocol::ParsedMessage, + _src: &std::net::SocketAddr, + _tx: mpsc::Sender, +) -> Result<(), Box> { + // No additional handling needed + Ok(()) +} + +pub async fn handle_ready_to_play_signal( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + let _ = buf.get_u8(); // Empty String + + let (client_info_clone, game_info_clone) = { + let mut clients_lock = clients.lock().await; + let client_info = match clients_lock.get_mut(src) { + Some(client_info) => { + client_info.player_status = PLAYER_STATUS_NET_SYNC; // Ready to play + client_info + } + None => { + eprintln!("Client not found during ready to play signal: addr={}", src); + return Ok(()); + } + }; + let client_info_clone = client_info.clone(); + + let mut games_lock = games.lock().await; + let game_id = match client_info.game_id { + Some(game_id) => game_id, + None => { + eprintln!( + "Game ID not found during ready to play signal: addr={}", + src + ); + return Ok(()); + } + }; + let game_info = match games_lock.get_mut(&game_id) { + Some(game_info) => game_info, + None => { + eprintln!( + "Game not found during ready to play signal: game_id={}", + game_id, + ); + return Ok(()); + } + }; + let game_info_clone = game_info.clone(); + + (client_info_clone, game_info_clone) + }; + + // Update game status + { + let status_data = util::make_update_game_status(&game_info_clone)?; + let clients_lock = clients.lock().await; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; + } + + let all_user_ready_to_signal = { + let clients_lock = clients.lock().await; + game_info_clone.players.iter().all(|player_addr| { + if let Some(client_info) = clients_lock.get(player_addr) { + println!("client_info.player_status: {}", client_info.player_status); + client_info.player_status == PLAYER_STATUS_NET_SYNC + } else { + println!("None client_info"); + false + } + }) + }; + println!("12"); + // Send ready to play signal notification + if all_user_ready_to_signal { + println!("all user ready to signal"); + for player_addr in game_info_clone.players.iter() { + let mut data = BytesMut::new(); + data.put_u8(0); + util::send_packet(&tx, &packet_history, player_addr, 0x15, data.to_vec()).await?; + } + println!("13"); + // Make user play status + { + let mut clients_lock = clients.lock().await; + for player_addr in game_info_clone.players.iter() { + if let Some(client_info) = clients_lock.get_mut(player_addr) { + client_info.player_status = PLAYER_STATUS_PLAYING; // Playing + } + } + } + } + println!("14"); + Ok(()) +} + +/* +- **NB**: Empty String `[00]` +- **2B**: Length of Game Data +- **NB**: Game Data + */ +pub async fn handle_game_data( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + let _ = buf.get_u8(); // Empty String + let length_of_game_data = buf.get_u16_le(); + let game_data = buf.split_to(length_of_game_data as usize).to_vec(); + + let (client_info_clone, game_info_clone) = { + let mut clients_lock = clients.lock().await; + let client_info = match clients_lock.get_mut(src) { + Some(client_info) => client_info, + None => { + eprintln!("Client not found during ready to play signal: addr={}", src); + return Ok(()); + } + }; + let client_info_clone = client_info.clone(); + + let mut games_lock = games.lock().await; + let game_id = match client_info.game_id { + Some(game_id) => game_id, + None => { + eprintln!( + "Game ID not found during ready to play signal: addr={}", + src + ); + return Ok(()); + } + }; + let game_info = match games_lock.get_mut(&game_id) { + Some(game_info) => game_info, + None => { + eprintln!( + "Game not found during ready to play signal: game_id={}", + game_id, + ); + return Ok(()); + } + }; + let game_info_clone = game_info.clone(); + + (client_info_clone, game_info_clone) + }; + for player_addr in game_info_clone.players.iter() { + let mut data = BytesMut::new(); + data.put_u8(0); + data.put_u16_le(length_of_game_data); + data.put(&game_data[..]); + util::send_packet(&tx, &packet_history, player_addr, 0x12, data.to_vec()).await?; + } + Ok(()) +} diff --git a/src/handlers/join_game.rs b/src/handlers/join_game.rs new file mode 100644 index 0000000..0256bce --- /dev/null +++ b/src/handlers/join_game.rs @@ -0,0 +1,134 @@ +use bytes::{Buf, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; + +// use crate::handlers::util::*; +use crate::packet_history::PacketHistory; +use crate::util::*; +use crate::*; +// use crate::{ +// util::build_join_game_response, util::make_player_information, util::make_update_game_status, read_string, +// util::send_packet, ClientInfo, GameInfo, Message, ParsedMessage, +// }; +pub async fn with_client_info( + clients: &Arc>>, + src: &std::net::SocketAddr, + func: F, +) -> Result +where + F: FnOnce(&mut ClientInfo) -> R, +{ + let mut clients_lock = clients.lock().await; + if let Some(client_info) = clients_lock.get_mut(src) { + Ok(func(client_info)) + } else { + Err("Client not found") + } +} + +pub async fn with_game_info( + games: &Arc>>, + game_id: u32, + func: F, +) -> Result +where + F: FnOnce(&mut GameInfo) -> R, +{ + let mut games_lock = games.lock().await; + if let Some(game_info) = games_lock.get_mut(&game_id) { + Ok(func(game_info)) + } else { + Err("Game not found") + } +} + +pub async fn handle_join_game( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + // 메시지 파싱 + let mut buf = BytesMut::from(&message.data[..]); + let _ = read_string(&mut buf); + let game_id = buf.get_u32_le(); + let _ = read_string(&mut buf); + let _ = buf.get_u32_le(); + let _ = buf.get_u16_le(); + let _conn_type = buf.get_u8(); + + // 클라이언트 정보 업데이트 + with_client_info(&clients, src, |client_info| { + client_info.game_id = Some(game_id); + }) + .await?; + + // 게임 정보 업데이트 + with_game_info(&games, game_id, |game_info| { + game_info.players.insert(*src); + game_info.num_players += 1; + }) + .await?; + + // 잠금 해제 후 게임 상태 데이터 생성 + let status_data = { + let games_lock = games.lock().await; + let game_info = games_lock.get(&game_id).ok_or("Game not found")?; + util::make_update_game_status(game_info)? + }; + + println!("Game status updated for game_id={}", game_id); + + // 모든 클라이언트에게 게임 상태 업데이트 브로드캐스트 + let client_addresses = { + let clients_lock = clients.lock().await; + clients_lock.keys().cloned().collect::>() + }; + + for addr in client_addresses { + util::send_packet(&tx, &packet_history, &addr, 0x0E, status_data.clone()).await?; + } + + // 플레이어 정보 생성 및 조인한 클라이언트에게 전송 + let players_info = { + let clients_lock = clients.lock().await; + let games_lock = games.lock().await; + let game_info = games_lock.get(&game_id).ok_or("Game not found")?; + util::make_player_information(src, &clients_lock, game_info)? + }; + + util::send_packet(&tx, &packet_history, src, 0x0D, players_info.clone()).await?; + + // 조인 게임 응답 데이터 생성 + let response_data = { + let clients_lock = clients.lock().await; + let client_info = clients_lock.get(src).ok_or("Client not found")?; + util::build_join_game_response(client_info) + }; + + // 기존 플레이어에게 조인 게임 응답 전송 + let game_players = { + let games_lock = games.lock().await; + let game_info = games_lock.get(&game_id).ok_or("Game not found")?; + game_info.players.clone() + }; + + for player_addr in game_players { + if &player_addr != src { + util::send_packet( + &tx, + &packet_history, + &player_addr, + 0x0C, + response_data.clone(), + ) + .await?; + } + } + + Ok(()) +} diff --git a/src/handlers/kick_user.rs b/src/handlers/kick_user.rs new file mode 100644 index 0000000..772dafd --- /dev/null +++ b/src/handlers/kick_user.rs @@ -0,0 +1,76 @@ +use bytes::{Buf, BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; + +use crate::packet_history::PacketHistory; +use crate::*; +/* + **Client to Server**: + - Empty String + - `2B`: UserID +*/ +pub async fn handle_kick_user( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + let _ = util::read_string(&mut buf); // Empty String + let user_id = buf.get_u16_le(); // UserID + + let (mut client_info_clone, client_addr) = { + let clients_lock = clients.lock().await; + let client_info = clients_lock.iter().find(|(_, c)| c.user_id == user_id); + match client_info { + Some((addr, client_info)) => (client_info.clone(), *addr), + None => { + eprintln!("Client not found during kick user: user_id={}", user_id); + return Ok(()); + } + } + }; + let game_id = match client_info_clone.game_id { + Some(game_id) => game_id, + None => { + eprintln!( + "Game ID not found during kick user: user_id={}", + client_info_clone.user_id + ); + return Ok(()); + } + }; + let mut game_info_clone = { + let mut games_lock = games.lock().await; + let game_info = games_lock.get_mut(&game_id); + match game_info { + Some(game_info) => game_info.clone(), + None => { + eprintln!("Game not found during kick user: game_id={}", game_id,); + return Ok(()); + } + } + }; + + client_info_clone.game_id = None; + + // Update game status + game_info_clone.num_players -= 1; + let status_data = util::make_update_game_status(&game_info_clone)?; + let clients_lock = clients.lock().await; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; + for player_addr in game_info_clone.players.iter() { + let mut data = BytesMut::new(); + data.put(client_info_clone.username.as_bytes()); + data.put_u8(0); + data.put_u16_le(client_info_clone.user_id); + util::send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; + } + game_info_clone.players.remove(&client_addr); + + Ok(()) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..ea1226a --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,13 @@ +pub mod create_game; +pub mod data; +pub mod game_chat; +pub mod global_chat; +pub mod handlerf; +pub mod join_game; +pub mod kick_user; +mod quit_game; +pub mod start_game; +pub mod user_login; +pub mod user_quit; + +pub mod util; diff --git a/src/handlers/quit_game.rs b/src/handlers/quit_game.rs new file mode 100644 index 0000000..2ed61a9 --- /dev/null +++ b/src/handlers/quit_game.rs @@ -0,0 +1,118 @@ +use bytes::{Buf, BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; + +use crate::packet_history::PacketHistory; +use crate::*; +/* + +'Quit Game State +'Client: Quit Game Request [0x0B] +'Server: Update Game Status [0x0E] +'Server: Quit Game Notification [0x0B] +' +'Close Game State +'Client: Quit Game Request [0x0B] +'Server: Close Game Notification [0x10] +'Server: Quit Game Notification [0x0B] +' 0x0B = Quit Game +' Client Request: +' NB : Empty String [00] +' 2B : 0xFF +' +' Server Notification: +' NB : Username +' 2B : UserID + +' 0x10 = Close game +' Server Notification: +' NB : Empty String [00] +' 4B : GameID + */ +pub async fn handle_quit_game( + message: Vec, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message[..]); + let _ = util::read_string(&mut buf); // Empty String + let _ = if buf.len() >= 2 { buf.get_u16_le() } else { 0 }; // 2B: 0xFF + // if last player, close game + // if not last player, remove player from game + let (client_info_clone, game_info_clone) = { + let mut clients_lock = clients.lock().await; + let client_info = match clients_lock.get_mut(src) { + Some(client_info) => client_info, + None => { + eprintln!("Client not found during game quit: addr={}", src); + return Ok(()); + } + }; + let client_info_clone = client_info.clone(); + + let mut games_lock = games.lock().await; + let game_id = match client_info.game_id { + Some(game_id) => game_id, + None => { + eprintln!("Game ID not found during game quit: addr={}", src); + return Ok(()); + } + }; + let game_info = match games_lock.get_mut(&game_id) { + Some(game_info) => game_info, + None => { + eprintln!("Game not found during game quit: game_id={}", game_id,); + return Ok(()); + } + }; + game_info.players.remove(src); + game_info.num_players -= 1; + let game_info_clone = game_info.clone(); + + (client_info_clone, game_info_clone) + }; + if game_info_clone.owner == client_info_clone.username { + // Close the game + let game_id = game_info_clone.game_id; + let game_name = game_info_clone.game_name.clone(); + let emulator_name = game_info_clone.emulator_name.clone(); + let owner = game_info_clone.owner.clone(); + + // Remove game from games list + games.lock().await.remove(&game_id); + + // make close game notification + let mut data = BytesMut::new(); + data.put_u8(0x00); + // game id + data.put_u32_le(game_id); + let clients_lock = clients.lock().await; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x10, data.to_vec()).await?; + // quit game notification + for player_addr in game_info_clone.players.iter() { + let mut data = BytesMut::new(); + data.put(client_info_clone.username.as_bytes()); + data.put_u8(0); + data.put_u16_le(client_info_clone.user_id); + util::send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; + } + } else { + // Update game status + let status_data = util::make_update_game_status(&game_info_clone)?; + let clients_lock = clients.lock().await; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; + for player_addr in game_info_clone.players.iter() { + let mut data = BytesMut::new(); + data.put(client_info_clone.username.as_bytes()); + data.put_u8(0); + data.put_u16_le(client_info_clone.user_id); + util::send_packet(&tx, &packet_history, player_addr, 0x0B, data.to_vec()).await?; + } + } + Ok(()) +} diff --git a/src/handlers/start_game.rs b/src/handlers/start_game.rs new file mode 100644 index 0000000..fb2ad45 --- /dev/null +++ b/src/handlers/start_game.rs @@ -0,0 +1,90 @@ +use crate::packet_history::PacketHistory; +use crate::*; +use bytes::{Buf, BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +/* +' 0x11 = Start Game +' Client Request: +' NB : Empty String [00] +' 2B : 0xFF +' 1B : 0xFF +' 1B : 0xFF +' +' Server Notification: +' NB : Empty String [00] +' 2B : Frame Delay (eg. (connectionType * (frameDelay + 1) <-Block on that frame +' 1B : Your Player Number (eg. if you're player 1 or 2...) +' 1B : Total Players +- **Client**: Sends **Start Game Request** `[0x11]` +- **Server**: Sends **Update Game Status** `[0x0E]` +- **Server**: Sends **Start Game Notification** `[0x11]` +- **Client**: Enters **Netsync Mode** and waits for all players to send **Ready to Play Signal** `[0x15]` +- **Server**: Sends **Update Game Status** for whole server players`[0x0E]` +- **Server**: Enters **Playing Mode** after receiving **Ready to Play Signal Notification** `[0x15]` from all players in room +- **Client(s)**: Exchange data using **Game Data Send** `[0x12]` or **Game Cache Send** `[0x13]` +- **Server**: Sends data accordingly using **Game Data Notify** `[0x12]` or **Game Cache Notify** `[0x13]` + + */ +pub async fn handle_start_game( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + let _ = util::read_string(&mut buf); // Empty String + let _ = buf.get_u32_le(); // 0xFFFF 0xFF 0xFF + + let (client_info_clone, game_info_clone) = { + let mut clients_lock = clients.lock().await; + let client_info = match clients_lock.get_mut(src) { + Some(client_info) => client_info, + None => { + eprintln!("Client not found during game start: addr={}", src); + return Ok(()); + } + }; + let client_info_clone = client_info.clone(); + + let mut games_lock = games.lock().await; + let game_id = match client_info.game_id { + Some(game_id) => game_id, + None => { + eprintln!("Game ID not found during game start: addr={}", src); + return Ok(()); + } + }; + let game_info = match games_lock.get_mut(&game_id) { + Some(game_info) => game_info, + None => { + eprintln!("Game not found during game start: game_id={}", game_id,); + return Ok(()); + } + }; + game_info.game_status = 1; // Playing + let game_info_clone = game_info.clone(); + + (client_info_clone, game_info_clone) + }; + + // Update game status + let status_data = util::make_update_game_status(&game_info_clone)?; + let clients_lock = clients.lock().await; + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x0E, status_data).await?; + + // Send start game notification + for (i, player_addr) in game_info_clone.players.iter().enumerate() { + let mut data = BytesMut::new(); + data.put_u8(0); + data.put_u16_le(4); // Frame Delay + data.put_u8((i + 1) as u8); // Player Number + data.put_u8(game_info_clone.players.len() as u8); // Total Players + util::send_packet(&tx, &packet_history, player_addr, 0x11, data.to_vec()).await?; + } + Ok(()) +} diff --git a/src/handlers/user_login.rs b/src/handlers/user_login.rs new file mode 100644 index 0000000..b105a22 --- /dev/null +++ b/src/handlers/user_login.rs @@ -0,0 +1,87 @@ +use bytes::{Buf, BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; +use util::read_string; + +// use crate::handlers::util::*; +use crate::packet_history::PacketHistory; +use crate::*; +pub async fn handle_user_login( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + next_user_id: Arc>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + packet_history.remove_client(src).await; + let mut buf = BytesMut::from(&message.data[..]); + + // NB: Username + let username = read_string(&mut buf); + // NB: Emulator Name + let emulator_name = read_string(&mut buf); + // 1B: Connection Type + let conn_type = if !buf.is_empty() { buf.get_u8() } else { 0 }; + + println!( + "User login info: username='{}', emulator='{}', conn_type={}, addr={}", + username, emulator_name, conn_type, src + ); + + // Assign a user ID + let user_id = { + let mut next_user_id = next_user_id.lock().await; + let id = *next_user_id; + *next_user_id += 1; + id + }; + + // Add client to clients list + let mut clients_lock = clients.lock().await; + clients_lock.insert( + *src, + ClientInfo { + username: username.clone(), + emulator_name: emulator_name.clone(), + conn_type, + user_id, + ping: 0, + player_status: PLAYER_STATUS_IDLE, // Idle + game_id: None, + last_ping_time: None, + ack_count: 0, + }, + ); + drop(clients_lock); + + // Now, send Server to Client ACK [0x05], multiple times + let mut data = BytesMut::new(); + data.put_u8(0); // Empty string [00] + data.put_u32_le(0); + data.put_u32_le(1); + data.put_u32_le(2); + data.put_u32_le(3); + + let response_packet = packet_history + .make_send_packet(*src, 0x05, data.to_vec()) + .await; + + tx.send(Message { + data: response_packet, + addr: *src, + }) + .await?; + + // Record the time sent for ping calculation + let mut clients_lock = clients.lock().await; + if let Some(client_info) = clients_lock.get_mut(src) { + client_info.last_ping_time = Some(Instant::now()); + } + drop(clients_lock); + + Ok(()) +} diff --git a/src/handlers/user_quit.rs b/src/handlers/user_quit.rs new file mode 100644 index 0000000..fdca6c2 --- /dev/null +++ b/src/handlers/user_quit.rs @@ -0,0 +1,65 @@ +use bytes::{Buf, BufMut, BytesMut}; +use std::collections::HashMap; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::{mpsc, Mutex}; + +use crate::packet_history::PacketHistory; +use crate::*; +/* +' Server Notification: +' NB : Username +' 2B : UserID +' NB : Message + */ +pub async fn handle_user_quit( + message: kaillera::protocol::ParsedMessage, + src: &std::net::SocketAddr, + tx: mpsc::Sender, + clients: Arc>>, + games: Arc>>, + packet_history: Arc, +) -> Result<(), Box> { + let mut buf = BytesMut::from(&message.data[..]); + + // NB: Empty String + let _empty = util::read_string(&mut buf); + // 2B: 0xFF + let _code = if buf.len() >= 2 { buf.get_u16_le() } else { 0 }; + // NB: Message + let user_message = util::read_string(&mut buf); + + handlers::quit_game::handle_quit_game( + vec![0x00, 0xFF, 0xFF], + src, + tx.clone(), + clients.clone(), + games.clone(), + packet_history.clone(), + ) + .await?; + // Remove client from list + let mut clients_lock = clients.lock().await; + if let Some(client_info) = clients_lock.remove(src) { + println!( + "User quit: username='{}', addr={}, message='{}'", + client_info.username, src, user_message + ); + let mut data = BytesMut::new(); + data.put(client_info.username.as_bytes()); + data.put_u8(0); + data.put_u16_le(client_info.user_id); + data.put(user_message.as_bytes()); + data.put_u8(0); + util::broadcast_packet(&tx, &packet_history, &clients_lock, 0x01, data.to_vec()).await?; + } else { + println!( + "Unknown client quit: addr={}, message='{}'", + src, user_message + ); + } + drop(clients_lock); + // Remove from packet history + packet_history.remove_client(src).await; + Ok(()) +} diff --git a/src/handlers/util.rs b/src/handlers/util.rs new file mode 100644 index 0000000..6a50c13 --- /dev/null +++ b/src/handlers/util.rs @@ -0,0 +1,222 @@ +use std::{collections::HashMap, error::Error, sync::Arc}; + +use bytes::{Buf, BufMut, BytesMut}; +use tokio::sync::{mpsc, Mutex}; + +use crate::{packet_history::PacketHistory, GameInfo, Message}; + +use super::data::ClientInfo; + +pub fn build_join_game_response(user: &ClientInfo) -> Vec { + let mut data = Vec::new(); + data.put_u8(0); // Empty string [00] + data.put_u32_le(0); // Pointer to Game on Server Side + data.put(user.username.as_bytes()); + data.put_u8(0); + data.put_u32_le(user.ping); + data.put_u16_le(user.user_id); + data.put_u8(user.conn_type); + data +} + +pub fn build_new_game_notification( + username: &str, + game_name: &str, + emulator_name: &str, + game_id: u32, +) -> Vec { + let mut data = Vec::new(); + data.put(username.as_bytes()); + data.put_u8(0); + data.put(game_name.as_bytes()); + data.put_u8(0); + data.put(emulator_name.as_bytes()); + data.put_u8(0); + data.put_u32_le(game_id); + data +} + +// ' 0x0D = Player Information +// ' Server Notification: +// ' NB : Empty String [00] +// ' 4B : Number of Users in Room [not including you] +// ' NB : Username +// ' 4B : Ping +// ' 2B : UserID +// ' 1B : Connection Type (6=Bad, 5=Low, 4=Average, 3=Good, 2=Excellent, & 1=LAN) +pub fn make_player_information( + src: &std::net::SocketAddr, + clients: &HashMap, + game_info: &GameInfo, +) -> Result, Box> { + // Prepare response data + let mut data = BytesMut::new(); + data.put_u8(0); // Empty string [00] + data.put_u32_le((game_info.players.len() - 1) as u32); + println!("Player count: {}", game_info.players.len()); + for player_addr in game_info.players.iter() { + if player_addr != src { + if let Some(client_info) = clients.get(player_addr) { + data.put(client_info.username.as_bytes()); + data.put_u8(0); // NULL terminator + data.put_u32_le(client_info.ping); + data.put_u16_le(client_info.user_id); + data.put_u8(client_info.conn_type); + } + } + } + Ok(data.to_vec()) +} + +// ' 0x0E = Update Game Status +// ' Server Notification: +// ' NB : Empty String [00] +// ' 4B : GameID +// ' 1B : Game Status (0=Waiting, 1=Playing, 2=Netsync) +// ' 1B : Number of Players in Room +// ' 1B : Maximum Players +pub fn make_update_game_status(game_info: &GameInfo) -> Result, Box> { + let mut data = BytesMut::new(); + data.put_u8(0); // Empty string [00] + data.put_u32_le(game_info.game_id); + data.put_u8(game_info.game_status); + data.put_u8(game_info.num_players); + data.put_u8(game_info.max_players); + Ok(data.to_vec()) +} + +pub async fn make_user_joined( + src: &std::net::SocketAddr, + clients: Arc>>, +) -> Result, Box> { + let mut clients_lock = clients.lock().await; + let client_info_clone = if let Some(client_info) = clients_lock.get_mut(src) { + client_info.clone() + } else { + return Err("Client not found.".into()); + }; + drop(clients_lock); + let mut data = BytesMut::new(); + data.put(client_info_clone.username.as_bytes()); + data.put_u8(0); + data.put_u16_le(client_info_clone.user_id); + data.put_u32_le(client_info_clone.ping); + data.put_u8(client_info_clone.conn_type); + Ok(data.to_vec()) +} + +// Helper functions +pub async fn send_packet( + tx: &mpsc::Sender, + packet_history: &Arc, + addr: &std::net::SocketAddr, + packet_type: u8, + data: Vec, +) -> Result<(), Box> { + let response_packet = packet_history + .make_send_packet(*addr, packet_type, data) + .await; + tx.send(Message { + data: response_packet, + addr: *addr, + }) + .await?; + Ok(()) +} + +pub async fn broadcast_packet( + tx: &mpsc::Sender, + packet_history: &Arc, + clients: &HashMap, + packet_type: u8, + data: Vec, +) -> Result<(), Box> { + for (client_addr, _) in clients.iter() { + let response_packet = packet_history + .make_send_packet(*client_addr, packet_type, data.clone()) + .await; + tx.send(Message { + data: response_packet, + addr: *client_addr, + }) + .await?; + } + Ok(()) +} + +pub fn read_string(buf: &mut BytesMut) -> String { + let mut s = Vec::new(); + while let Some(&b) = buf.first() { + buf.advance(1); + if b == 0 { + break; + } + s.push(b); + } + String::from_utf8_lossy(&s).to_string() +} + +pub fn make_server_information() -> Result, Box> { + // Prepare response data + // ' NB : "Server\0" + // ' NB : Message + let mut data = BytesMut::new(); + data.put("Server\0".as_bytes()); + data.put("Welcome to the Kaillera server!\0".as_bytes()); + Ok(data.to_vec()) +} +pub async fn make_server_status( + src: &std::net::SocketAddr, + clients: Arc>>, + games: Arc>>, +) -> Result, Box> { + let clients_lock = clients.lock().await; + let games_lock = games.lock().await; + + // Prepare response data + let mut data = BytesMut::new(); + data.put_u8(0); // Empty string [00] + + // Number of users (excluding self) + let num_users = (clients_lock.len() - 1) as u32; + data.put_u32_le(num_users); + + // Number of games + let num_games = games_lock.len() as u32; + data.put_u32_le(num_games); + + // User list + for (addr, client_info) in clients_lock.iter() { + if addr != src { + data.put(client_info.username.as_bytes()); + data.put_u8(0); // NULL terminator + data.put_u32_le(client_info.ping); + data.put_u8(client_info.player_status); + data.put_u16_le(client_info.user_id); + data.put_u8(client_info.conn_type); + } + } + + // Game list + for game_info in games_lock.values() { + data.put(game_info.game_name.as_bytes()); + data.put_u8(0); // NULL terminator + data.put_u32_le(game_info.game_id); + data.put(game_info.emulator_name.as_bytes()); + data.put_u8(0); // NULL terminator + data.put(game_info.owner.as_bytes()); + data.put_u8(0); // NULL terminator + data.put(format!("{}/{}\0", game_info.num_players, game_info.max_players).as_bytes()); + data.put_u8(game_status_to_byte(game_info.game_status)); + } + Ok(data.to_vec()) +} + +fn game_status_to_byte(status: u8) -> u8 { + match status { + 0 => 0, // Waiting + 1 => 1, // Playing + 2 => 2, // Netsync + _ => 0, // Default to Waiting + } +} diff --git a/src/kaillera/mod.rs b/src/kaillera/mod.rs new file mode 100644 index 0000000..1b800ec --- /dev/null +++ b/src/kaillera/mod.rs @@ -0,0 +1 @@ +pub mod protocol; diff --git a/src/protocol.rs b/src/kaillera/protocol.rs similarity index 95% rename from src/protocol.rs rename to src/kaillera/protocol.rs index 7be3ec7..c2f3449 100644 --- a/src/protocol.rs +++ b/src/kaillera/protocol.rs @@ -1,15 +1,8 @@ use std::cmp; -#[derive(Debug, Clone)] -pub struct SeqKailleraMessage { - pub seq: u16, - pub length: u16, - pub message_type: u8, - pub data: Vec, -} - -pub struct KailleraMessage { - pub length: u16, +pub struct ParsedMessage { + pub message_number: u16, + pub message_length: u16, pub message_type: u8, pub data: Vec, } @@ -68,9 +61,6 @@ impl UDPPacketGenerator { #[cfg(test)] mod tests { use super::*; - - - #[test] fn test_make_packet() { diff --git a/src/main.rs b/src/main.rs index 2ec7219..26f3e4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ -use std::collections::{HashMap, HashSet}; +use handlerf::*; +use packet_util::*; +use std::collections::HashMap; use std::error::Error; use std::sync::Arc; use std::time::Instant; use tokio::net::UdpSocket; use tokio::sync::{mpsc, Mutex}; +mod kaillera; mod packet_history; mod packet_util; use packet_history::PacketHistory; @@ -13,7 +16,7 @@ mod handlers; use handlers::*; mod game_cache; -mod protocol; +use handlers::data::*; const MAIN_PORT: u16 = 8080; const CONTROL_PORT: u16 = 27888; @@ -106,36 +109,6 @@ async fn main() -> Result<(), Box> { } } -type PlayerStatus = u8; -const PLAYER_STATUS_PLAYING: PlayerStatus = 0; -const PLAYER_STATUS_IDLE: PlayerStatus = 1; -const PLAYER_STATUS_NET_SYNC: PlayerStatus = 2; -// ClientInfo and GameInfo structs need to be accessible in both files -#[derive(Clone)] -pub struct ClientInfo { - pub username: String, - pub emulator_name: String, - pub conn_type: u8, - pub user_id: u16, - pub ping: u32, - pub player_status: PlayerStatus, - pub game_id: Option, - pub last_ping_time: Option, - pub ack_count: u16, -} - -#[derive(Clone)] -pub struct GameInfo { - pub game_id: u32, - pub game_name: String, - pub emulator_name: String, - pub owner: String, - pub num_players: u8, - pub max_players: u8, - pub game_status: u8, // 0=Waiting, 1=Playing, 2=Netsync - pub players: HashSet, // 이 필드 추가 -} - // Message struct needs to be accessible in both files pub struct Message { pub data: Vec, diff --git a/src/packet_history.rs b/src/packet_history.rs index ac62161..6100e89 100644 --- a/src/packet_history.rs +++ b/src/packet_history.rs @@ -1,11 +1,12 @@ // packet_history.rs -use crate::protocol; use std::collections::HashMap; use std::net::SocketAddr; use tokio::sync::Mutex; +use crate::kaillera::protocol::*; + pub struct PacketHistory { - histories: Mutex>, + histories: Mutex>, } impl PacketHistory { @@ -24,7 +25,7 @@ impl PacketHistory { let mut histories = self.histories.lock().await; let history = histories .entry(addr) - .or_insert_with(protocol::UDPPacketGenerator::new); + .or_insert_with(UDPPacketGenerator::new); history.make_send_packet(message_type, data) } diff --git a/src/packet_util.rs b/src/packet_util.rs index 8b13789..c9eaea6 100644 --- a/src/packet_util.rs +++ b/src/packet_util.rs @@ -1 +1,70 @@ +use std::sync::Arc; +use crate::*; +use bytes::{Buf, BytesMut}; +use tokio::io; +use tokio::net::UdpSocket; + +pub async fn handle_control_socket(control_socket: Arc) -> io::Result<()> { + let mut buf = [0u8; 4096]; + loop { + let (len, src) = control_socket.recv_from(&mut buf).await?; + let data = &buf[..len]; + + // Handle the HELLO0.83 message + if data == b"HELLO0.83\x00" { + let response = format!("HELLOD00D{}\0", crate::MAIN_PORT).into_bytes(); + control_socket.send_to(&response, src).await?; + } + // Handle the PING message + else if data == b"PING\x00" { + let response = b"PONG\x00".to_vec(); + control_socket.send_to(&response, src).await?; + } else { + let ascii_string: String = data + .iter() + .map(|&b| if b.is_ascii() { b as char } else { '.' }) // Replace invalid with '.' + .collect(); + eprintln!( + "Unknown message on control socket from {}: {:?}, {}", + src, data, ascii_string + ); + } + } +} + +pub fn parse_packet(data: &[u8]) -> Result, String> { + let mut buf = BytesMut::from(data); + if buf.is_empty() { + return Err("Packet is empty.".to_string()); + } + let num_messages = buf.get_u8(); + + let mut messages = Vec::new(); + + for _ in 0..num_messages { + if buf.len() < 5 { + println!("Current buffer content: {:02X?}", buf); + return Err("Incomplete message header.".to_string()); + } + + let message_number = buf.get_u16_le(); + let message_length = buf.get_u16_le(); + let message_type = buf.get_u8(); + + if buf.len() < (message_length - 1) as usize { + return Err("Incomplete message data.".to_string()); + } + + let message_data = buf.split_to((message_length - 1) as usize); + + messages.push(kaillera::protocol::ParsedMessage { + message_number, + message_length, + message_type, + data: message_data.to_vec(), + }); + } + + Ok(messages) +}