diff --git a/src/bin/kawari-lobby.rs b/src/bin/kawari-lobby.rs index a26a3cb..0d1ab95 100644 --- a/src/bin/kawari-lobby.rs +++ b/src/bin/kawari-lobby.rs @@ -280,7 +280,7 @@ async fn main() { connection.send_error(*sequence, 2002, 13006).await; } } - ClientLobbyIpcData::ServiceLogin { sequence } => { + ClientLobbyIpcData::ServiceLogin { sequence, .. } => { // TODO: support selecting a service account connection.selected_service_account = Some(connection.service_accounts[0].id); diff --git a/src/ipc/lobby/dist_retainer_info.rs b/src/ipc/lobby/dist_retainer_info.rs new file mode 100644 index 0000000..74d45ec --- /dev/null +++ b/src/ipc/lobby/dist_retainer_info.rs @@ -0,0 +1,43 @@ +use binrw::binrw; + +use crate::common::{CHAR_NAME_MAX_LENGTH, read_string, write_string}; + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct RetainerInfo { + id: u64, + owner_id: u64, + slot_id: u8, + param1: u8, + status: u16, + param2: u32, + #[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)] + #[br(count = CHAR_NAME_MAX_LENGTH)] + #[br(map = read_string)] + #[bw(map = write_string)] + name: String, +} + +impl RetainerInfo { + pub const SIZE: usize = 56; +} + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct DistRetainerInfo { + pub sequence: u64, + pub timestamp: u32, + pub index: u8, + pub count: u8, + pub option_param: u16, + pub option_arg: u32, + pub num_contracted: u16, + pub num_active: u16, + pub num_total: u16, + pub num_free_slots: u16, + pub total_retainers: u16, + pub active_retainers: u16, + #[br(count = 9)] + #[brw(pad_size_to = (9 * RetainerInfo::SIZE))] + pub characters: Vec, +} diff --git a/src/ipc/lobby/mod.rs b/src/ipc/lobby/mod.rs index ea3bcdb..54ced96 100644 --- a/src/ipc/lobby/mod.rs +++ b/src/ipc/lobby/mod.rs @@ -12,6 +12,12 @@ pub use server_list::{DistWorldInfo, Server}; mod login_reply; pub use login_reply::{LoginReply, ServiceAccount}; +mod dist_retainer_info; +pub use dist_retainer_info::{DistRetainerInfo, RetainerInfo}; + +mod nack_reply; +pub use nack_reply::NackReply; + use crate::{ common::{read_string, write_string}, opcodes::{ClientLobbyIpcType, ServerLobbyIpcType}, @@ -48,6 +54,8 @@ impl Default for ClientLobbyIpcSegment { session_id: String::new(), version_info: String::new(), unk1: 0, + timestamp: 0, + unk2: 0, }, } } @@ -78,13 +86,7 @@ impl Default for ServerLobbyIpcSegment { op_code: ServerLobbyIpcType::NackReply, option: 0, timestamp: 0, - data: ServerLobbyIpcData::NackReply { - sequence: 0, - error: 0, - value: 0, - exd_error_id: 0, - unk1: 0, - }, + data: ServerLobbyIpcData::NackReply(NackReply::default()), } } } @@ -96,9 +98,12 @@ pub enum ClientLobbyIpcData { /// Sent by the client when it requests the character list in the lobby. #[br(pre_assert(*magic == ClientLobbyIpcType::ServiceLogin))] ServiceLogin { - #[brw(pad_before = 16)] sequence: u64, - // TODO: what is in here? + account_index: u8, + unk1: u8, + unk2: u16, + unk3: u32, // TODO: probably multiple params + account_id: u64, }, /// Sent by the client when it requests to enter a world. #[br(pre_assert(*magic == ClientLobbyIpcType::GameLogin))] @@ -113,8 +118,9 @@ pub enum ClientLobbyIpcData { #[br(pre_assert(*magic == ClientLobbyIpcType::LoginEx))] LoginEx { sequence: u64, - - #[brw(pad_before = 10)] // full of nonsense i don't understand yet + timestamp: u32, + #[brw(pad_after = 2)] + unk1: u32, #[br(count = 64)] #[bw(pad_size_to = 64)] #[br(map = read_string)] @@ -128,7 +134,7 @@ pub enum ClientLobbyIpcData { version_info: String, #[brw(pad_before = 910)] // empty - unk1: u64, + unk2: u64, }, #[br(pre_assert(*magic == ClientLobbyIpcType::ShandaLogin))] ShandaLogin { @@ -151,14 +157,7 @@ pub enum ClientLobbyIpcData { pub enum ServerLobbyIpcData { /// Sent by the server to indicate an lobby error occured. #[br(pre_assert(*magic == ServerLobbyIpcType::NackReply))] - NackReply { - sequence: u64, - error: u32, - value: u32, - exd_error_id: u16, - #[brw(pad_after = 516)] // empty and garbage - unk1: u16, - }, + NackReply(NackReply), /// Sent by the server to inform the client of their service accounts. #[br(pre_assert(*magic == ServerLobbyIpcType::LoginReply))] LoginReply(LoginReply), @@ -203,12 +202,7 @@ pub enum ServerLobbyIpcData { DistWorldInfo(DistWorldInfo), /// Sent by the server to inform the client of their retainers. #[br(pre_assert(*magic == ServerLobbyIpcType::DistRetainerInfo))] - DistRetainerInfo { - // TODO: what is in here? - #[brw(pad_before = 7)] - #[brw(pad_after = 528)] - unk1: u8, - }, + DistRetainerInfo(DistRetainerInfo), Unknown { #[br(count = size - 32)] unk: Vec, @@ -229,13 +223,7 @@ mod tests { let ipc_types = [ ( ServerLobbyIpcType::NackReply, - ServerLobbyIpcData::NackReply { - sequence: 0, - error: 0, - value: 0, - exd_error_id: 0, - unk1: 0, - }, + ServerLobbyIpcData::NackReply(NackReply::default()), ), ( ServerLobbyIpcType::LoginReply, @@ -272,7 +260,7 @@ mod tests { ), ( ServerLobbyIpcType::DistRetainerInfo, - ServerLobbyIpcData::DistRetainerInfo { unk1: 0 }, + ServerLobbyIpcData::DistRetainerInfo(DistRetainerInfo::default()), ), ]; @@ -306,7 +294,14 @@ mod tests { let ipc_types = [ ( ClientLobbyIpcType::ServiceLogin, - ClientLobbyIpcData::ServiceLogin { sequence: 0 }, + ClientLobbyIpcData::ServiceLogin { + sequence: 0, + account_index: 0, + unk1: 0, + unk2: 0, + account_id: 0, + unk3: 0, + }, ), ( ClientLobbyIpcType::GameLogin, @@ -324,6 +319,8 @@ mod tests { session_id: String::default(), version_info: String::default(), unk1: 0, + timestamp: 0, + unk2: 0, }, ), ( diff --git a/src/ipc/lobby/nack_reply.rs b/src/ipc/lobby/nack_reply.rs new file mode 100644 index 0000000..7f55529 --- /dev/null +++ b/src/ipc/lobby/nack_reply.rs @@ -0,0 +1,27 @@ +#![allow(unused_variables)] // whoop binrw + +use binrw::binrw; + +use crate::common::{read_string, write_string}; + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct NackReply { + pub sequence: u64, + /// The error code shown in the client. + pub error: u32, + /// Value is specific to each error, e.g. your position in queue (i think) + pub value: u32, + /// The ID of a row in the Error Excel sheet. + pub exd_error_id: u16, + #[br(temp)] + #[bw(calc = message.len() as u16)] + pub message_size: u16, + /// Seems to be unused + #[brw(pad_after = 4)] // garbage + #[bw(pad_size_to = 512)] + #[br(count = 512)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub message: String, +} diff --git a/src/ipc/lobby/service_login_reply.rs b/src/ipc/lobby/service_login_reply.rs index 6fc7a94..21bf0a5 100644 --- a/src/ipc/lobby/service_login_reply.rs +++ b/src/ipc/lobby/service_login_reply.rs @@ -68,6 +68,10 @@ pub struct CharacterDetails { pub unk3: [u32; 5], } +impl CharacterDetails { + pub const SIZE: usize = 1196; +} + #[binrw] #[derive(Debug, Clone, Default)] pub struct ServiceLoginReply { @@ -93,6 +97,6 @@ pub struct ServiceLoginReply { #[brw(pad_after = 12)] pub entitled_expansion: u32, #[br(count = 2)] - #[brw(pad_size_to = (1196 * 2))] + #[brw(pad_size_to = (CharacterDetails::SIZE * 2))] pub characters: Vec, } diff --git a/src/lobby/connection.rs b/src/lobby/connection.rs index a9090fc..1c8f5d2 100644 --- a/src/lobby/connection.rs +++ b/src/lobby/connection.rs @@ -7,11 +7,11 @@ use crate::{ blowfish::Blowfish, common::timestamp_secs, config::get_config, + ipc::lobby::{DistRetainerInfo, NackReply}, opcodes::ServerLobbyIpcType, - packet::oodle::OodleNetwork, packet::{ CompressionType, ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, - generate_encryption_key, parse_packet, send_packet, + generate_encryption_key, oodle::OodleNetwork, parse_packet, send_packet, }, }; @@ -120,7 +120,7 @@ impl LobbyConnection { servers.resize(6, Server::default()); let lobby_server_list = ServerLobbyIpcData::DistWorldInfo(DistWorldInfo { - sequence: 0, + sequence, unk1: 1, num_servers: 1, servers, @@ -144,7 +144,8 @@ impl LobbyConnection { // send them the retainer list { - let lobby_retainer_list = ServerLobbyIpcData::DistRetainerInfo { unk1: 1 }; + let lobby_retainer_list = + ServerLobbyIpcData::DistRetainerInfo(DistRetainerInfo::default()); let ipc = ServerLobbyIpcSegment { op_code: ServerLobbyIpcType::DistRetainerInfo, @@ -272,13 +273,12 @@ impl LobbyConnection { /// Send a lobby error to the client. pub async fn send_error(&mut self, sequence: u64, error: u32, exd_error: u16) { - let lobby_error = ServerLobbyIpcData::NackReply { + let lobby_error = ServerLobbyIpcData::NackReply(NackReply { sequence, error, - value: 0, exd_error_id: exd_error, - unk1: 1, - }; + ..Default::default() + }); let ipc = ServerLobbyIpcSegment { op_code: ServerLobbyIpcType::NackReply, @@ -350,13 +350,12 @@ impl LobbyConnection { } else { let ipc = ServerLobbyIpcSegment { op_code: ServerLobbyIpcType::NackReply, - data: ServerLobbyIpcData::NackReply { + data: ServerLobbyIpcData::NackReply(NackReply { sequence: character_action.sequence, error: 0x00000bdb, exd_error_id: 0x32cc, - value: 0, - unk1: 0, - }, + ..Default::default() + }), ..Default::default() };