1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-26 00:37:44 +00:00
kawari/src/lobby/ipc/mod.rs

350 lines
9.7 KiB
Rust
Raw Normal View History

use binrw::binrw;
mod character_action;
pub use character_action::LobbyCharacterAction;
mod character_list;
pub use character_list::CharacterDetails;
mod client_version_info;
mod server_list;
pub use server_list::Server;
mod service_account_list;
pub use service_account_list::ServiceAccount;
use crate::{
CHAR_NAME_MAX_LENGTH,
common::{read_string, write_string},
packet::{IpcSegment, IpcSegmentTrait},
};
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
impl IpcSegmentTrait for ClientLobbyIpcSegment {
fn calc_size(&self) -> u32 {
todo!()
}
}
// TODO: make generic
impl Default for ClientLobbyIpcSegment {
fn default() -> Self {
Self {
unk1: 0x14,
unk2: 0,
op_code: ClientLobbyIpcType::ClientVersionInfo,
server_id: 0,
timestamp: 0,
data: ClientLobbyIpcData::ClientVersionInfo {
sequence: 0,
session_id: String::new(),
version_info: String::new(),
},
}
}
}
pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcData>;
impl IpcSegmentTrait for ServerLobbyIpcSegment {
fn calc_size(&self) -> u32 {
// 16 is the size of the IPC header
16 + match self.op_code {
ServerLobbyIpcType::LobbyError => 536,
ServerLobbyIpcType::LobbyServiceAccountList => 24 + (8 * 80),
ServerLobbyIpcType::LobbyCharacterList => 80 + (2 * 1184),
ServerLobbyIpcType::LobbyEnterWorld => 160,
ServerLobbyIpcType::LobbyServerList => 24 + (6 * 84),
ServerLobbyIpcType::LobbyRetainerList => 210,
ServerLobbyIpcType::CharacterCreated => 2568,
}
}
}
// TODO: make generic
impl Default for ServerLobbyIpcSegment {
fn default() -> Self {
Self {
unk1: 0x14,
unk2: 0,
op_code: ServerLobbyIpcType::LobbyError,
server_id: 0,
timestamp: 0,
data: ServerLobbyIpcData::LobbyError {
sequence: 0,
error: 0,
value: 0,
exd_error_id: 0,
unk1: 0,
},
}
}
}
#[binrw]
#[brw(repr = u16)]
#[derive(Clone, PartialEq, Debug)]
pub enum ServerLobbyIpcType {
/// Sent by the server to indicate an lobby error occured
LobbyError = 0x2,
/// 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 tell the client how to connect to the world server.
LobbyEnterWorld = 0xF,
/// 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,
// Assumed what this is, but probably incorrect
CharacterCreated = 0xE,
}
#[binrw]
#[brw(repr = u16)]
#[derive(Clone, PartialEq, Debug)]
pub enum ClientLobbyIpcType {
/// Sent by the client when it requests the character list in the lobby.
RequestCharacterList = 0x3,
/// Sent by the client when it requests to enter a world.
RequestEnterWorld = 0x4,
/// Sent by the client after exchanging encryption information with the lobby server.
ClientVersionInfo = 0x5,
/// Sent by the client when they request something about the character (e.g. deletion.)
LobbyCharacterAction = 0xB,
}
#[binrw]
#[br(import(magic: &ClientLobbyIpcType))]
#[derive(Debug, Clone)]
pub enum ClientLobbyIpcData {
// Client->Server IPC
#[br(pre_assert(*magic == ClientLobbyIpcType::ClientVersionInfo))]
ClientVersionInfo {
sequence: u64,
#[brw(pad_before = 10)] // full of nonsense i don't understand yet
#[br(count = 64)]
#[br(map = read_string)]
#[bw(ignore)]
session_id: String,
#[brw(pad_before = 8)] // empty
#[br(count = 128)]
#[br(map = read_string)]
#[bw(ignore)]
version_info: String,
// unknown stuff at the end, it's not completely empty'
},
#[br(pre_assert(*magic == ClientLobbyIpcType::RequestCharacterList))]
RequestCharacterList {
#[brw(pad_before = 16)]
sequence: u64,
// TODO: what is in here?
},
#[br(pre_assert(*magic == ClientLobbyIpcType::LobbyCharacterAction))]
LobbyCharacterAction {
request_number: u32,
unk1: u32,
character_id: u64,
#[br(pad_before = 8)]
character_index: u8,
action: LobbyCharacterAction,
world_id: u16,
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
#[br(count = CHAR_NAME_MAX_LENGTH)]
#[br(map = read_string)]
#[bw(map = write_string)]
name: String,
#[bw(pad_size_to = 436)]
#[br(count = 436)]
#[br(map = read_string)]
#[bw(map = write_string)]
json: String,
},
#[br(pre_assert(*magic == ClientLobbyIpcType::RequestEnterWorld))]
RequestEnterWorld {
#[brw(pad_before = 16)]
sequence: u64,
lookup_id: u64,
// TODO: what else is in here?
},
}
#[binrw]
#[br(import(_magic: &ServerLobbyIpcType))]
#[derive(Debug, Clone)]
pub enum ServerLobbyIpcData {
LobbyServiceAccountList {
#[br(dbg)]
sequence: u64,
#[brw(pad_before = 1)]
num_service_accounts: u8,
unk1: u8,
#[brw(pad_after = 4)]
unk2: u8,
#[br(count = 8)]
service_accounts: Vec<ServiceAccount>,
},
LobbyServerList {
sequence: u64,
unk1: u16,
offset: u16,
#[brw(pad_after = 8)]
num_servers: u32,
#[br(count = 6)]
#[brw(pad_size_to = 504)]
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,
/// Set to 128 if legacy character
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: u16,
unk8: u16,
#[brw(pad_after = 12)]
entitled_expansion: u32,
#[br(count = 2)]
#[brw(pad_size_to = 2368)]
characters: Vec<CharacterDetails>,
},
LobbyEnterWorld {
sequence: u64,
character_id: u32,
#[brw(pad_before = 4)]
content_id: u64,
#[brw(pad_before = 4)]
#[bw(pad_size_to = 66)]
#[br(count = 66)]
#[br(map = read_string)]
#[bw(map = write_string)]
session_id: String,
port: u16,
#[brw(pad_after = 16)]
#[br(count = 48)]
#[br(map = read_string)]
#[bw(map = write_string)]
host: String,
},
LobbyError {
sequence: u64,
error: u32,
value: u32,
exd_error_id: u16,
unk1: u16,
},
NameRejection {
// FIXME: This is opcode 0x2, which is InitializeChat. We need to separate the lobby/zone IPC codes.
unk1: u8,
#[brw(pad_before = 7)] // empty
unk2: u16,
#[brw(pad_before = 6)] // empty
#[brw(pad_after = 516)] // mostly empty
unk3: u32,
},
CharacterCreated {
#[brw(pad_after = 4)] // empty
unk1: u32,
#[brw(pad_after = 4)] // empty
unk2: u32,
#[brw(pad_before = 32)] // empty
#[brw(pad_after = 1136)] // empty
details: CharacterDetails,
},
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use binrw::BinWrite;
use super::*;
/// Ensure that the IPC data size as reported matches up with what we write
#[test]
fn lobby_ipc_sizes() {
let ipc_types = [
(
ServerLobbyIpcType::LobbyServerList,
ServerLobbyIpcData::LobbyServerList {
sequence: 0,
unk1: 0,
offset: 0,
num_servers: 0,
servers: Vec::new(),
},
),
(
ServerLobbyIpcType::LobbyCharacterList,
ServerLobbyIpcData::LobbyCharacterList {
sequence: 0,
counter: 0,
num_in_packet: 0,
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: Vec::new(),
},
),
];
for (opcode, ipc) in &ipc_types {
let mut cursor = Cursor::new(Vec::new());
let ipc_segment = ServerLobbyIpcSegment {
unk1: 0,
unk2: 0,
op_code: opcode.clone(),
server_id: 0,
timestamp: 0,
data: ipc.clone(),
};
ipc_segment.write_le(&mut cursor).unwrap();
let buffer = cursor.into_inner();
assert_eq!(
buffer.len(),
ipc_segment.calc_size() as usize,
"{:#?} did not match size!",
opcode
);
}
}
}