1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-24 08:07:45 +00:00

Begin sending lobby information

This now puts the client onto the real lobby screen, not sure how much actually
works yet.
This commit is contained in:
Joshua Goins 2025-03-08 22:55:47 -05:00
parent 00c5f4a10e
commit 1f43318add
2 changed files with 273 additions and 3 deletions

View file

@ -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<ServiceAccount>,
},
LobbyServerList {
sequence: u64,
unk1: u16,
offset: u16,
#[brw(pad_after = 8)]
num_servers: u32,
#[br(count = 6)]
servers: Vec<Server>,
},
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<CharacterDetails>,
},
}
#[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),
}
}
}

View file

@ -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<TcpStream>, 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<TcpStream>, 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!")
}