diff --git a/src/ipc.rs b/src/ipc.rs index a624429..ea60bef 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -6,10 +6,18 @@ use crate::common::{read_string, write_string}; #[brw(repr = u16)] #[derive(Clone, PartialEq, Debug)] pub enum IPCOpCode { + /// Sent by the client when it requests the character list in the lobby. + RequestCharacterList = 0x3, /// Sent by the client after exchanging encryption information with the lobby server. ClientVersionInfo = 0x5, - /// Sent by the server to inform the client of service accounts. + /// Sent by the server to inform the client of their service accounts. LobbyServiceAccountList = 0xC, + /// Sent by the server to inform the client of their characters. + LobbyCharacterList = 0xD, + /// Sent by the server to inform the client of their servers. + LobbyServerList = 0x15, + /// Sent by the server to inform the client of their retainers. + LobbyRetainerList = 0x17, } #[binrw] @@ -25,6 +33,57 @@ pub struct ServiceAccount { pub name: String, } +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct Server { + pub id: u16, + pub index: u16, + pub flags: u32, + #[brw(pad_before = 4)] + #[brw(pad_after = 4)] + pub icon: u32, + #[bw(pad_size_to = 0x40)] + #[br(count = 0x40)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub name: String, +} + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct CharacterDetails { + #[brw(pad_after = 4)] + pub id: u32, + pub content_id: u64, + #[brw(pad_after = 4)] + pub index: u32, + // TODO: lol? why is there server_id1? + pub server_id: u16, + pub server_id1: u16, + pub unk1: [u8; 16], + #[bw(pad_size_to = 16)] + #[br(count = 16)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub character_name: String, + #[bw(pad_size_to = 32)] + #[br(count = 32)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub character_server_name: String, + #[bw(pad_size_to = 32)] + #[br(count = 32)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub character_server_name1: String, + #[bw(pad_size_to = 1024)] + #[br(count = 1024)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub character_detail_json: String, + pub unk2: [u8; 20], +} + #[binrw] #[br(import(magic: &IPCOpCode))] #[derive(Debug, Clone)] @@ -45,6 +104,12 @@ pub enum IPCStructData { version_info: String, // unknown stuff at the end, it's not completely empty' }, + #[br(pre_assert(*magic == IPCOpCode::RequestCharacterList))] + RequestCharacterList { + #[brw(pad_before = 16)] + sequence: u64, + // TODO: what is in here? + }, // Server->Client IPC LobbyServiceAccountList { @@ -57,6 +122,45 @@ pub enum IPCStructData { #[br(count = 8)] service_accounts: Vec, }, + LobbyServerList { + sequence: u64, + unk1: u16, + offset: u16, + #[brw(pad_after = 8)] + num_servers: u32, + #[br(count = 6)] + servers: Vec, + }, + LobbyRetainerList { + // TODO: what is in here? + #[brw(pad_before = 7)] + #[brw(pad_after = 202)] + unk1: u8, + }, + LobbyCharacterList { + sequence: u64, + counter: u8, + #[brw(pad_after = 2)] + num_in_packet: u8, + unk1: u8, + unk2: u8, + unk3: u8, + unk4: u8, + unk5: [u32; 7], + unk6: u8, + veteran_rank: u8, + #[brw(pad_after = 1)] + unk7: u8, + days_subscribed: u32, + remaining_days: u32, + days_to_next_rank: u32, + max_characters_on_world: u32, + unk8: u16, + #[brw(pad_after = 12)] + entitled_expansion: u32, + #[br(count = 2)] + characters: Vec, + }, } #[binrw] @@ -80,6 +184,10 @@ impl IPCSegment { + match self.data { IPCStructData::ClientVersionInfo { .. } => todo!(), IPCStructData::LobbyServiceAccountList { .. } => 24 + (8 * 80), + IPCStructData::RequestCharacterList { .. } => todo!(), + IPCStructData::LobbyServerList { .. } => 24 + (6 * 84), + IPCStructData::LobbyRetainerList { .. } => 210, + IPCStructData::LobbyCharacterList { .. } => 82 + (2 * 1168), } } } diff --git a/src/packet.rs b/src/packet.rs index af4c75b..0630121 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,4 +1,5 @@ use std::{ + cmp::{max, min}, fs::write, io::Cursor, time::{SystemTime, UNIX_EPOCH}, @@ -13,7 +14,7 @@ use tokio::{ use crate::{ common::{read_bool_from, read_string, write_bool_as}, encryption::{blowfish_encode, decrypt, encrypt, generate_encryption_key}, - ipc::{IPCOpCode, IPCSegment, IPCStructData, ServiceAccount}, + ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, Server, ServiceAccount}, }; #[binrw] @@ -236,7 +237,8 @@ pub async fn parse_packet(socket: &mut WriteHalf, data: &[u8], state: unk1: 0, index: 0, name: "FINAL FANTASY XIV".to_string(), - }].to_vec(); + }] + .to_vec(); // add any empty boys service_accounts.resize(8, ServiceAccount::default()); @@ -264,6 +266,166 @@ pub async fn parse_packet(socket: &mut WriteHalf, data: &[u8], state: }; send_packet(socket, &[response_packet], state).await; } + IPCStructData::RequestCharacterList { sequence } => { + tracing::info!("Client is requesting character list..."); + + let timestamp: u32 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Failed to get UNIX timestamp!") + .as_secs() + .try_into() + .unwrap(); + + let mut packets = Vec::new(); + // send them the character list + { + let mut servers = [Server { + id: 21, + index: 0, + flags: 0, + icon: 0, + name: "KAWARI".to_string(), + }] + .to_vec(); + // add any empty boys + servers.resize(6, Server::default()); + + let lobby_server_list = IPCStructData::LobbyServerList { + sequence: 0, + unk1: 1, + offset: 0, + num_servers: 1, + servers, + }; + + let ipc = IPCSegment { + unk1: 0, + unk2: 0, + op_code: IPCOpCode::LobbyServerList, + server_id: 0, + timestamp, + data: lobby_server_list, + }; + + let response_packet = PacketSegment { + source_actor: 0, + target_actor: 0, + segment_type: SegmentType::IPC { data: ipc }, + }; + packets.push(response_packet); + } + + // send them the retainer list + { + let lobby_retainer_list = + IPCStructData::LobbyRetainerList { unk1: 1 }; + + let ipc = IPCSegment { + unk1: 0, + unk2: 0, + op_code: IPCOpCode::LobbyRetainerList, + server_id: 0, + timestamp, + data: lobby_retainer_list, + }; + + let response_packet = PacketSegment { + source_actor: 0, + target_actor: 0, + segment_type: SegmentType::IPC { data: ipc }, + }; + packets.push(response_packet); + } + + send_packet(socket, &packets, state).await; + + // now send them the character list + { + let mut characters = vec![CharacterDetails { + id: 0, + content_id: 11111111111111111, + index: 0, + server_id: 21, + server_id1: 21, + unk1: [0; 16], + character_name: "test".to_string(), + character_server_name: "test".to_string(), + character_server_name1: "test".to_string(), + character_detail_json: "test".to_string(), + unk2: [0; 20], + }]; + // add any empty boys + characters.resize(2, CharacterDetails::default()); + + for i in 0..4 { + let mut characters_in_packet = Vec::new(); + for _ in 0..min(characters.len(), 2) { + characters_in_packet.push(characters.swap_remove(0)); + } + + let lobby_character_list = if i == 3 { + // On the last packet, add the account-wide information + IPCStructData::LobbyCharacterList { + sequence: *sequence, + counter: (i * 4) + 1, // TODO: why the + 1 here? + num_in_packet: characters_in_packet.len() as u8, + unk1: 0, + unk2: 0, + unk3: 0, + unk4: 128, + unk5: [0; 7], + unk6: 0, + veteran_rank: 0, + unk7: 0, + days_subscribed: 0, + remaining_days: 0, + days_to_next_rank: 0, + max_characters_on_world: 20, + unk8: 8, + entitled_expansion: 4, + characters: characters_in_packet, + } + } else { + IPCStructData::LobbyCharacterList { + sequence: *sequence, + counter: i * 4, + num_in_packet: characters_in_packet.len() as u8, + unk1: 0, + unk2: 0, + unk3: 0, + unk4: 0, + unk5: [0; 7], + unk6: 0, + veteran_rank: 0, + unk7: 0, + days_subscribed: 0, + remaining_days: 0, + days_to_next_rank: 0, + max_characters_on_world: 0, + unk8: 0, + entitled_expansion: 0, + characters: characters_in_packet, + } + }; + + let ipc = IPCSegment { + unk1: 0, + unk2: 0, + op_code: IPCOpCode::LobbyCharacterList, + server_id: 0, + timestamp, + data: lobby_character_list, + }; + + let response_packet = PacketSegment { + source_actor: 0, + target_actor: 0, + segment_type: SegmentType::IPC { data: ipc }, + }; + send_packet(socket, &[response_packet], state).await; + } + } + } _ => { panic!("The server is recieving a IPC response packet!") }