2025-03-16 17:43:29 -04:00
|
|
|
use binrw::binrw;
|
|
|
|
|
|
|
|
mod character_action;
|
2025-03-16 18:22:15 -04:00
|
|
|
pub use character_action::{LobbyCharacterAction, LobbyCharacterActionKind};
|
2025-03-16 17:43:29 -04:00
|
|
|
|
|
|
|
mod character_list;
|
2025-03-30 08:32:46 -04:00
|
|
|
pub use character_list::{CharacterDetails, CharacterFlag, LobbyCharacterList};
|
2025-03-16 17:43:29 -04:00
|
|
|
|
|
|
|
mod client_version_info;
|
|
|
|
|
|
|
|
mod server_list;
|
2025-03-16 18:15:19 -04:00
|
|
|
pub use server_list::{LobbyServerList, Server};
|
2025-03-16 17:43:29 -04:00
|
|
|
|
|
|
|
mod service_account_list;
|
2025-03-16 18:15:19 -04:00
|
|
|
pub use service_account_list::{LobbyServiceAccountList, ServiceAccount};
|
2025-03-16 17:43:29 -04:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
common::{read_string, write_string},
|
2025-03-26 17:58:15 -04:00
|
|
|
opcodes::{ClientLobbyIpcType, ServerLobbyIpcType},
|
2025-03-17 17:12:40 -04:00
|
|
|
packet::{IpcSegment, ReadWriteIpcSegment},
|
2025-03-16 17:43:29 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
|
|
|
|
|
2025-03-25 18:15:41 -04:00
|
|
|
impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
|
|
|
|
fn calc_size(&self) -> u32 {
|
|
|
|
// 16 is the size of the IPC header
|
2025-03-26 17:58:15 -04:00
|
|
|
16 + self.op_code.calc_size()
|
2025-03-25 18:15:41 -04:00
|
|
|
}
|
|
|
|
}
|
2025-03-16 17:43:29 -04:00
|
|
|
|
|
|
|
// TODO: make generic
|
|
|
|
impl Default for ClientLobbyIpcSegment {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
unk1: 0x14,
|
|
|
|
unk2: 0,
|
2025-05-01 22:36:30 -04:00
|
|
|
op_code: ClientLobbyIpcType::LoginEx,
|
2025-03-16 17:43:29 -04:00
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
2025-05-01 22:36:30 -04:00
|
|
|
data: ClientLobbyIpcData::LoginEx {
|
2025-03-16 17:43:29 -04:00
|
|
|
sequence: 0,
|
|
|
|
session_id: String::new(),
|
|
|
|
version_info: String::new(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcData>;
|
|
|
|
|
2025-03-17 17:12:40 -04:00
|
|
|
impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
|
2025-03-16 17:43:29 -04:00
|
|
|
fn calc_size(&self) -> u32 {
|
|
|
|
// 16 is the size of the IPC header
|
2025-03-26 17:58:15 -04:00
|
|
|
16 + self.op_code.calc_size()
|
2025-03-16 17:43:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: make generic
|
|
|
|
impl Default for ServerLobbyIpcSegment {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
unk1: 0x14,
|
|
|
|
unk2: 0,
|
2025-05-01 22:45:49 -04:00
|
|
|
op_code: ServerLobbyIpcType::NackReply,
|
2025-03-16 17:43:29 -04:00
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
2025-05-01 22:45:49 -04:00
|
|
|
data: ServerLobbyIpcData::NackReply {
|
2025-03-16 17:43:29 -04:00
|
|
|
sequence: 0,
|
|
|
|
error: 0,
|
|
|
|
value: 0,
|
|
|
|
exd_error_id: 0,
|
|
|
|
unk1: 0,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[br(import(magic: &ClientLobbyIpcType))]
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum ClientLobbyIpcData {
|
2025-05-01 22:45:49 -04:00
|
|
|
/// 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?
|
|
|
|
},
|
|
|
|
/// Sent by the client when it requests to enter a world.
|
|
|
|
#[br(pre_assert(*magic == ClientLobbyIpcType::GameLogin))]
|
|
|
|
GameLogin {
|
|
|
|
sequence: u64,
|
|
|
|
content_id: u64,
|
|
|
|
// TODO: what else is in here?
|
|
|
|
},
|
2025-03-26 17:58:15 -04:00
|
|
|
/// Sent by the client after exchanging encryption information with the lobby server.
|
2025-05-01 22:36:30 -04:00
|
|
|
#[br(pre_assert(*magic == ClientLobbyIpcType::LoginEx))]
|
|
|
|
LoginEx {
|
2025-03-16 17:43:29 -04:00
|
|
|
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,
|
2025-03-26 17:58:15 -04:00
|
|
|
// unknown stuff at the end, it's not completely empty
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
2025-04-22 18:15:13 -04:00
|
|
|
#[br(pre_assert(*magic == ClientLobbyIpcType::ShandaLogin))]
|
|
|
|
ShandaLogin {
|
|
|
|
#[bw(pad_size_to = 1456)]
|
|
|
|
#[br(count = 1456)]
|
|
|
|
unk: Vec<u8>,
|
|
|
|
},
|
2025-05-01 22:45:49 -04:00
|
|
|
/// Sent by the client when they request something about the character (e.g. deletion.)
|
|
|
|
#[br(pre_assert(*magic == ClientLobbyIpcType::CharaMake))]
|
|
|
|
CharaMake(LobbyCharacterAction),
|
2025-03-16 17:43:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[br(import(_magic: &ServerLobbyIpcType))]
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum ServerLobbyIpcData {
|
2025-05-01 22:45:49 -04:00
|
|
|
/// Sent by the server to indicate an lobby error occured.
|
|
|
|
NackReply {
|
|
|
|
sequence: u64,
|
|
|
|
error: u32,
|
|
|
|
value: u32,
|
|
|
|
exd_error_id: u16,
|
|
|
|
#[brw(pad_after = 516)] // empty and garbage
|
|
|
|
unk1: u16,
|
|
|
|
},
|
2025-03-26 17:58:15 -04:00
|
|
|
/// Sent by the server to inform the client of their service accounts.
|
2025-05-01 22:45:49 -04:00
|
|
|
LoginReply(LobbyServiceAccountList),
|
|
|
|
/// Sent by the server to inform the client of their characters.
|
|
|
|
ServiceLoginReply(LobbyCharacterList),
|
|
|
|
// Assumed what this is, but probably incorrect
|
|
|
|
CharaMakeReply {
|
|
|
|
sequence: u64,
|
2025-03-16 17:43:29 -04:00
|
|
|
unk1: u8,
|
2025-05-01 22:45:49 -04:00
|
|
|
unk2: u8,
|
|
|
|
#[brw(pad_after = 1)] // empty
|
|
|
|
action: LobbyCharacterActionKind,
|
|
|
|
#[brw(pad_before = 36)] // empty
|
|
|
|
#[brw(pad_after = 1336)] // empty and garbage
|
|
|
|
details: CharacterDetails,
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
2025-03-26 17:58:15 -04:00
|
|
|
/// Sent by the server to tell the client how to connect to the world server.
|
2025-05-01 22:45:49 -04:00
|
|
|
GameLoginReply {
|
2025-03-16 17:43:29 -04:00
|
|
|
sequence: u64,
|
2025-03-21 19:56:16 -04:00
|
|
|
actor_id: u32,
|
2025-03-16 17:43:29 -04:00
|
|
|
#[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)]
|
2025-03-21 19:56:16 -04:00
|
|
|
token: String, // WHAT IS THIS FOR??
|
2025-03-16 17:43:29 -04:00
|
|
|
port: u16,
|
2025-03-16 18:15:19 -04:00
|
|
|
#[brw(pad_after = 16)] // garbage?
|
2025-03-16 17:43:29 -04:00
|
|
|
#[br(count = 48)]
|
2025-03-16 18:15:19 -04:00
|
|
|
#[brw(pad_size_to = 48)]
|
2025-03-16 17:43:29 -04:00
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
host: String,
|
|
|
|
},
|
2025-05-01 22:45:49 -04:00
|
|
|
/// Sent by the server to inform the client of their servers.
|
|
|
|
DistWorldInfo(LobbyServerList),
|
|
|
|
/// Sent by the server to inform the client of their retainers.
|
|
|
|
DistRetainerInfo {
|
|
|
|
// TODO: what is in here?
|
|
|
|
#[brw(pad_before = 7)]
|
|
|
|
#[brw(pad_after = 202)]
|
2025-03-22 17:38:34 -04:00
|
|
|
unk1: u8,
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 = [
|
2025-03-16 18:15:19 -04:00
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::LoginReply,
|
|
|
|
ServerLobbyIpcData::LoginReply(LobbyServiceAccountList::default()),
|
2025-03-16 18:15:19 -04:00
|
|
|
),
|
2025-03-16 17:43:29 -04:00
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::DistWorldInfo,
|
|
|
|
ServerLobbyIpcData::DistWorldInfo(LobbyServerList::default()),
|
2025-03-16 18:15:19 -04:00
|
|
|
),
|
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::DistRetainerInfo,
|
|
|
|
ServerLobbyIpcData::DistRetainerInfo { unk1: 0 },
|
2025-03-16 18:15:19 -04:00
|
|
|
),
|
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::ServiceLoginReply,
|
|
|
|
ServerLobbyIpcData::ServiceLoginReply(LobbyCharacterList::default()),
|
2025-03-16 18:15:19 -04:00
|
|
|
),
|
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::GameLoginReply,
|
|
|
|
ServerLobbyIpcData::GameLoginReply {
|
2025-03-16 17:43:29 -04:00
|
|
|
sequence: 0,
|
2025-03-21 19:56:16 -04:00
|
|
|
actor_id: 0,
|
2025-03-16 18:15:19 -04:00
|
|
|
content_id: 0,
|
2025-03-21 19:56:16 -04:00
|
|
|
token: String::new(),
|
2025-03-16 18:15:19 -04:00
|
|
|
port: 0,
|
|
|
|
host: String::new(),
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::NackReply,
|
|
|
|
ServerLobbyIpcData::NackReply {
|
2025-03-16 17:43:29 -04:00
|
|
|
sequence: 0,
|
2025-03-16 18:15:19 -04:00
|
|
|
error: 0,
|
|
|
|
value: 0,
|
|
|
|
exd_error_id: 0,
|
2025-03-16 17:43:29 -04:00
|
|
|
unk1: 0,
|
2025-03-16 18:15:19 -04:00
|
|
|
},
|
|
|
|
),
|
|
|
|
(
|
2025-05-01 22:45:49 -04:00
|
|
|
ServerLobbyIpcType::CharaMakeReply,
|
|
|
|
ServerLobbyIpcData::CharaMakeReply {
|
2025-03-16 18:15:19 -04:00
|
|
|
sequence: 0,
|
2025-03-22 17:38:34 -04:00
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
action: LobbyCharacterActionKind::ReserveName,
|
2025-03-16 18:15:19 -04:00
|
|
|
details: CharacterDetails::default(),
|
2025-03-16 17:43:29 -04:00
|
|
|
},
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|