diff --git a/src/bin/kawari-lobby.rs b/src/bin/kawari-lobby.rs index c9934ef..bdd8a8b 100644 --- a/src/bin/kawari-lobby.rs +++ b/src/bin/kawari-lobby.rs @@ -1,3 +1,4 @@ +use kawari::RECEIVE_BUFFER_SIZE; use kawari::common::GameData; use kawari::common::custom_ipc::CustomIpcData; use kawari::common::custom_ipc::CustomIpcSegment; @@ -45,7 +46,7 @@ async fn main() { }; tokio::spawn(async move { - let mut buf = [0; 2056]; + let mut buf = vec![0; RECEIVE_BUFFER_SIZE]; loop { let n = connection .socket @@ -54,8 +55,6 @@ async fn main() { .expect("Failed to read data!"); if n != 0 { - tracing::info!("read {} bytes", n); - let (segments, _) = connection.parse_packet(&buf[..n]).await; for segment in &segments { match &segment.segment_type { diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index e5fd1a2..3ee51a9 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; +use kawari::RECEIVE_BUFFER_SIZE; use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType}; use kawari::common::{GameData, ObjectId, timestamp_secs}; use kawari::common::{Position, determine_initial_starting_zone}; @@ -28,6 +29,7 @@ use kawari::world::{ SocialList, }, }; + use mlua::{Function, Lua}; use tokio::io::AsyncReadExt; use tokio::join; @@ -162,18 +164,17 @@ pub fn spawn_client(info: ZoneConnection) { id: *id, ip: *ip, channel: send, - kill, + //kill, }; let _ = my_send.send(handle); } -async fn start_client(my_handle: oneshot::Receiver, mut data: ClientData) { +async fn start_client(my_handle: oneshot::Receiver, data: ClientData) { // Recieve client information from global let my_handle = match my_handle.await { Ok(my_handle) => my_handle, Err(_) => return, }; - data.handle.send(ToServer::NewClient(my_handle)).await; let connection = data.connection; let recv = data.recv; @@ -182,7 +183,7 @@ async fn start_client(my_handle: oneshot::Receiver, mut data: Clie let (internal_send, internal_recv) = unbounded_channel(); join! { - client_loop(connection, internal_recv), + client_loop(connection, internal_recv, my_handle), client_server_loop(recv, internal_send) }; } @@ -202,6 +203,7 @@ async fn client_server_loop( async fn client_loop( mut connection: ZoneConnection, mut internal_recv: UnboundedReceiver, + client_handle: ClientHandle, ) { let database = connection.database.clone(); let game_data = connection.gamedata.clone(); @@ -213,9 +215,10 @@ async fn client_loop( let mut lua_player = LuaPlayer::default(); - let mut buf = [0; 4096]; + let mut buf = vec![0; RECEIVE_BUFFER_SIZE]; loop { tokio::select! { + biased; // client data should always be prioritized Ok(n) = connection.socket.read(&mut buf) => { if n > 0 { let (segments, connection_type) = connection.parse_packet(&buf[..n]).await; @@ -225,11 +228,21 @@ async fn client_loop( // for some reason they send a string representation let actor_id = actor_id.parse::().unwrap(); + // initialize player data if it doesn't exist' + if connection.player_data.actor_id == 0 { + connection.player_data = database.find_player_data(actor_id); + } + // collect actor data - connection.player_data = database.find_player_data(actor_id); connection.initialize(&connection_type, actor_id).await; - exit_position = Some(connection.player_data.position); - exit_rotation = Some(connection.player_data.rotation); + + if connection_type == ConnectionType::Zone { + exit_position = Some(connection.player_data.position); + exit_rotation = Some(connection.player_data.rotation); + + // tell the server we exist, now that we confirmed we are a legitimate connection + connection.handle.send(ToServer::NewClient(client_handle.clone())).await; + } } SegmentType::Ipc { data } => { match &data.data { @@ -1040,14 +1053,17 @@ async fn client_loop( lua_player.status_effects = connection.status_effects.clone(); } } - msg = internal_recv.recv() => match msg { + /*msg = internal_recv.recv() => match msg { Some(msg) => match msg { FromServer::Message(msg)=>connection.send_message(&msg).await, - FromServer::ActorSpawn(actor) => connection.spawn_actor(actor).await, + FromServer::ActorSpawn(actor) => { + tracing::info!("connection {:?} is recieving an actorspawn!", connection.id); + connection.spawn_actor(actor).await + }, FromServer::ActorMove(actor_id, position) => connection.set_actor_position(actor_id, position).await, }, None => break, - } + }*/ } } } diff --git a/src/common/custom_ipc.rs b/src/common/custom_ipc.rs index 89f9f2f..9331db5 100644 --- a/src/common/custom_ipc.rs +++ b/src/common/custom_ipc.rs @@ -21,7 +21,7 @@ impl ReadWriteIpcSegment for CustomIpcSegment { CustomIpcType::CheckNameIsAvailable => CHAR_NAME_MAX_LENGTH as u32, CustomIpcType::NameIsAvailableResponse => 1, CustomIpcType::RequestCharacterList => 4, - CustomIpcType::RequestCharacterListRepsonse => 1184 * 8, + CustomIpcType::RequestCharacterListRepsonse => 1 + (1184 * 8), CustomIpcType::DeleteCharacter => 4, CustomIpcType::CharacterDeleted => 1, } @@ -95,6 +95,7 @@ pub enum CustomIpcData { #[bw(calc = characters.len() as u8)] num_characters: u8, #[br(count = num_characters)] + #[brw(pad_size_to = 1184 * 8)] characters: Vec, // TODO: maybe chunk this into 4 parts ala the lobby server? }, #[br(pre_assert(*magic == CustomIpcType::DeleteCharacter))] diff --git a/src/common/gamedata.rs b/src/common/gamedata.rs index 211ad58..6e77532 100644 --- a/src/common/gamedata.rs +++ b/src/common/gamedata.rs @@ -7,6 +7,12 @@ pub struct GameData { pub game_data: physis::gamedata::GameData, } +impl Default for GameData { + fn default() -> Self { + Self::new() + } +} + impl GameData { pub fn new() -> Self { let config = get_config(); diff --git a/src/common/mod.rs b/src/common/mod.rs index e3a2e06..2ba0286 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -82,7 +82,7 @@ pub(crate) fn read_packed_float(packed: u16) -> f32 { } pub(crate) fn write_packed_float(float: f32) -> u16 { - (((float + 1000.0) * 100.0) * 0.32767501) as u16 + (((float + 1000.0) * 100.0) * 0.327_675) as u16 } pub(crate) fn read_packed_position(packed: [u16; 3]) -> Position { diff --git a/src/lib.rs b/src/lib.rs index 9ac5c56..e6be6b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ pub mod opcodes; /// Used in the encryption key. const GAME_VERSION: u16 = 7000; +pub const RECEIVE_BUFFER_SIZE: usize = 32000; + /// Supported boot version. pub const SUPPORTED_BOOT_VERSION: Version = Version("2025.01.10.0000.0001"); diff --git a/src/lobby/connection.rs b/src/lobby/connection.rs index a562d74..d784bc8 100644 --- a/src/lobby/connection.rs +++ b/src/lobby/connection.rs @@ -3,6 +3,7 @@ use std::cmp::min; use tokio::{io::AsyncReadExt, net::TcpStream}; use crate::{ + RECEIVE_BUFFER_SIZE, blowfish::Blowfish, common::{ custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType}, @@ -589,11 +590,9 @@ pub async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option(&buf[..n], &mut packet_state).await; return match &segments[0].segment_type { diff --git a/src/oodle.rs b/src/oodle.rs index 2faf9b8..2c8bb9a 100644 --- a/src/oodle.rs +++ b/src/oodle.rs @@ -105,6 +105,7 @@ pub struct OodleNetwork { const HT_BITS: i32 = 0x11; const WINDOW_SIZE: usize = 0x100000; +const OODLENETWORK1_DECOMP_BUF_OVERREAD_LEN: usize = 5; impl OodleNetwork { pub fn new() -> OodleNetwork { @@ -137,13 +138,19 @@ impl OodleNetwork { } } - pub fn decode(&mut self, mut input: Vec, decompressed_size: u32) -> Vec { + pub fn decode(&mut self, input: Vec, decompressed_size: u32) -> Vec { + let mut padded_buffer = input.clone(); + padded_buffer.resize( + padded_buffer.len() + OODLENETWORK1_DECOMP_BUF_OVERREAD_LEN, + 0, + ); + unsafe { let mut out_buf: Vec = vec![0u8; decompressed_size.try_into().unwrap()]; let success = OodleNetwork1TCP_Decode( self.state.as_mut_ptr() as *mut c_void, - self.shared.as_mut_ptr() as *mut c_void, - input.as_mut_ptr() as *const c_void, + self.shared.as_mut_ptr() as *const c_void, + padded_buffer.as_mut_ptr() as *const c_void, input.len().try_into().unwrap(), out_buf.as_mut_ptr() as *mut c_void, out_buf.len().try_into().unwrap(), @@ -162,7 +169,7 @@ impl OodleNetwork { let mut out_buf: Vec = vec![0u8; input.len()]; let len = OodleNetwork1TCP_Encode( self.state.as_mut_ptr() as *mut c_void, - self.shared.as_mut_ptr() as *mut c_void, + self.shared.as_mut_ptr() as *const c_void, input.as_mut_ptr() as *const c_void, input.len().try_into().unwrap(), out_buf.as_mut_ptr() as *mut c_void, diff --git a/src/world/connection.rs b/src/world/connection.rs index 2c807e3..5dc8ebe 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -6,7 +6,7 @@ use std::{ }, }; -use tokio::{net::TcpStream, sync::mpsc::Sender, task::JoinHandle}; +use tokio::{net::TcpStream, sync::mpsc::Sender}; use crate::{ common::{GameData, ObjectId, Position, timestamp_secs}, @@ -61,12 +61,13 @@ pub enum FromServer { ActorMove(u32, Position), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ClientHandle { pub id: ClientId, pub ip: SocketAddr, pub channel: Sender, - pub kill: JoinHandle<()>, + // TODO: restore, i guess + //pub kill: JoinHandle<()>, } impl ClientHandle { @@ -161,6 +162,17 @@ impl ZoneConnection { .await; } + pub async fn send_chat_segment(&mut self, segment: PacketSegment) { + send_packet( + &mut self.socket, + &mut self.state, + ConnectionType::Chat, + CompressionType::Oodle, + &[segment], + ) + .await; + } + pub async fn initialize(&mut self, connection_type: &ConnectionType, actor_id: u32) { // some still hardcoded values self.player_data.classjob_id = 1; @@ -170,23 +182,23 @@ impl ZoneConnection { self.player_data.curr_mp = 10000; self.player_data.max_mp = 10000; - // We have send THEM a keep alive - { - self.send_segment(PacketSegment { - source_actor: 0, - target_actor: 0, - segment_type: SegmentType::KeepAlive { - id: 0xE0037603u32, - timestamp: timestamp_secs(), - }, - }) - .await; - } - match connection_type { ConnectionType::Zone => { tracing::info!("Client {actor_id} is initializing zone session..."); + // We have send THEM a keep alive + { + self.send_segment(PacketSegment { + source_actor: 0, + target_actor: 0, + segment_type: SegmentType::KeepAlive { + id: 0xE0037603u32, + timestamp: timestamp_secs(), + }, + }) + .await; + } + self.send_segment(PacketSegment { source_actor: 0, target_actor: 0, @@ -200,8 +212,21 @@ impl ZoneConnection { ConnectionType::Chat => { tracing::info!("Client {actor_id} is initializing chat session..."); + // We have send THEM a keep alive { - self.send_segment(PacketSegment { + self.send_chat_segment(PacketSegment { + source_actor: 0, + target_actor: 0, + segment_type: SegmentType::KeepAlive { + id: 0xE0037603u32, + timestamp: timestamp_secs(), + }, + }) + .await; + } + + { + self.send_chat_segment(PacketSegment { source_actor: 0, target_actor: 0, segment_type: SegmentType::ZoneInitialize { @@ -212,6 +237,9 @@ impl ZoneConnection { .await; } + // we need the actor id at this point! + assert!(self.player_data.actor_id != 0); + { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::InitializeChat, @@ -220,7 +248,7 @@ impl ZoneConnection { ..Default::default() }; - self.send_segment(PacketSegment { + self.send_chat_segment(PacketSegment { source_actor: self.player_data.actor_id, target_actor: self.player_data.actor_id, segment_type: SegmentType::Ipc { data: ipc }, @@ -276,6 +304,9 @@ impl ZoneConnection { } pub async fn spawn_actor(&mut self, actor: Actor) { + // There is no reason for us to spawn our own player again. It's probably a bug!' + assert!(actor.id.0 != self.player_data.actor_id); + let ipc = ServerZoneIpcSegment { unk1: 20, unk2: 0, @@ -351,7 +382,7 @@ impl ZoneConnection { } // link shell information - { + /*{ let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::LinkShellInformation, timestamp: timestamp_secs(), @@ -365,7 +396,7 @@ impl ZoneConnection { segment_type: SegmentType::Ipc { data: ipc }, }) .await; - } + }*/ // TODO: send unk16? @@ -573,7 +604,7 @@ impl ZoneConnection { } } - return None; + None } pub async fn actor_control_self(&mut self, actor_control: ActorControlSelf) { diff --git a/src/world/zone.rs b/src/world/zone.rs index 18561f7..5994269 100644 --- a/src/world/zone.rs +++ b/src/world/zone.rs @@ -1,13 +1,11 @@ use physis::{ - common::{Language, Platform}, + common::Language, gamedata::GameData, layer::{ ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject, }, }; -use crate::config::get_config; - /// Represents a loaded zone #[derive(Default)] pub struct Zone {