2025-03-08 22:03:28 -05:00
|
|
|
use binrw::binrw;
|
|
|
|
|
2025-03-12 17:47:58 -04:00
|
|
|
use crate::{
|
2025-03-13 00:18:00 -04:00
|
|
|
CHAR_NAME_MAX_LENGTH,
|
2025-03-12 17:47:58 -04:00
|
|
|
common::{read_string, write_string},
|
2025-03-12 18:05:41 -04:00
|
|
|
world::{
|
2025-03-15 20:36:39 -04:00
|
|
|
ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
2025-03-16 14:07:56 -04:00
|
|
|
SocialList, SocialListRequest, UpdateClassInfo,
|
2025-03-12 18:05:41 -04:00
|
|
|
},
|
2025-03-12 17:47:58 -04:00
|
|
|
};
|
2025-03-08 22:05:20 -05:00
|
|
|
|
2025-03-10 21:31:21 -04:00
|
|
|
// NOTE: See https://github.com/karashiiro/FFXIVOpcodes/blob/master/FFXIVOpcodes/Ipcs.cs for opcodes
|
|
|
|
|
2025-03-08 22:03:28 -05:00
|
|
|
#[binrw]
|
|
|
|
#[brw(repr = u16)]
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
|
|
pub enum IPCOpCode {
|
2025-03-10 21:31:21 -04:00
|
|
|
/// Sent by the server to Initialize something chat-related?
|
|
|
|
InitializeChat = 0x2,
|
2025-03-08 22:55:47 -05:00
|
|
|
/// Sent by the client when it requests the character list in the lobby.
|
|
|
|
RequestCharacterList = 0x3,
|
2025-03-09 11:01:06 -04:00
|
|
|
/// Sent by the client when it requests to enter a world.
|
|
|
|
RequestEnterWorld = 0x4,
|
2025-03-08 22:03:28 -05:00
|
|
|
/// Sent by the client after exchanging encryption information with the lobby server.
|
|
|
|
ClientVersionInfo = 0x5,
|
2025-03-09 00:20:41 -05:00
|
|
|
/// Sent by the client when they request something about the character (e.g. deletion.)
|
|
|
|
LobbyCharacterAction = 0xB,
|
2025-03-08 22:55:47 -05:00
|
|
|
/// Sent by the server to inform the client of their service accounts.
|
2025-03-08 22:03:28 -05:00
|
|
|
LobbyServiceAccountList = 0xC,
|
2025-03-08 22:55:47 -05:00
|
|
|
/// Sent by the server to inform the client of their characters.
|
|
|
|
LobbyCharacterList = 0xD,
|
2025-03-09 11:01:06 -04:00
|
|
|
/// Sent by the server to tell the client how to connect to the world server.
|
|
|
|
LobbyEnterWorld = 0xF,
|
2025-03-08 22:55:47 -05:00
|
|
|
/// 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,
|
2025-03-10 21:31:21 -04:00
|
|
|
|
|
|
|
/// Sent by the client when they successfully initialize with the server, and they need several bits of information (e.g. what zone to load)
|
|
|
|
InitRequest = 0x2ED,
|
|
|
|
/// Sent by the server that tells the client which zone to load
|
|
|
|
InitZone = 0x0311,
|
|
|
|
/// Sent by the server for... something
|
|
|
|
ActorControlSelf = 0x018C,
|
|
|
|
/// Sent by the server containing character stats
|
|
|
|
PlayerStats = 0x01FA,
|
|
|
|
/// Sent by the server to setup the player on the client
|
|
|
|
PlayerSetup = 0x006B,
|
|
|
|
// Sent by the server to setup class info
|
|
|
|
UpdateClassInfo = 0x006A,
|
2025-03-12 00:32:37 -04:00
|
|
|
// Sent by the client when they're done loading and they need to be spawned in
|
|
|
|
FinishLoading = 0x397, // TODO: assumed
|
|
|
|
// Sent by the server to spawn the player in
|
|
|
|
PlayerSpawn = 0x1AB,
|
|
|
|
|
|
|
|
// FIXME: 32 bytes of something from the client, not sure what yet
|
|
|
|
Unk1 = 0x37C,
|
|
|
|
// FIXME: 16 bytes of something from the client, not sure what yet
|
2025-03-16 14:07:56 -04:00
|
|
|
Unk2 = 0x2E5,
|
2025-03-12 00:32:37 -04:00
|
|
|
// FIXME: 8 bytes of something from the client, not sure what yet
|
|
|
|
Unk3 = 0x326,
|
|
|
|
// FIXME: 8 bytes of something from the client, not sure what yet
|
|
|
|
Unk4 = 0x143,
|
|
|
|
SetSearchInfoHandler = 0x3B2, // TODO: assumed,
|
|
|
|
// FIXME: 8 bytes of something from the client, not sure what yet
|
2025-03-16 14:07:56 -04:00
|
|
|
/// ALSO Sent by the server as response to ZoneInitRequest.
|
2025-03-12 00:32:37 -04:00
|
|
|
Unk5 = 0x2D0,
|
2025-03-16 14:07:56 -04:00
|
|
|
// Sent by the client when it requests the friends list and other related info
|
|
|
|
SocialListRequest = 0x1A1,
|
2025-03-12 00:32:37 -04:00
|
|
|
// FIXME: 32 bytes of something from the client, not sure what yet
|
|
|
|
Unk7 = 0x2B5,
|
|
|
|
UpdatePositionHandler = 0x249, // TODO: assumed
|
2025-03-12 18:44:05 -04:00
|
|
|
// Sent by the client when the user requests to log out
|
|
|
|
LogOut = 0x217,
|
|
|
|
// Sent by the server to indicate the log out is complete
|
|
|
|
LogOutComplete = 0x369,
|
|
|
|
// Sent by the client when it's actually disconnecting
|
|
|
|
Disconnected = 0x360,
|
2025-03-12 19:34:15 -04:00
|
|
|
// Sent by the client when they send a chat message
|
|
|
|
ChatMessage = 0xCA,
|
2025-03-13 00:41:16 -04:00
|
|
|
// Sent by the client when they send a GM command. This can only be sent by the client if they are sent a GM rank.
|
|
|
|
GameMasterCommand = 0x3B3,
|
2025-03-13 19:52:01 -04:00
|
|
|
// Sent by the server to modify the client's position
|
|
|
|
ActorSetPos = 0x223,
|
2025-03-13 21:11:20 -04:00
|
|
|
// Sent by the server when they send a chat message
|
|
|
|
ServerChatMessage = 0x196,
|
2025-03-13 21:49:03 -04:00
|
|
|
// Unknown, server sends to the client before player spawn
|
|
|
|
Unk8 = 0x134,
|
|
|
|
// Unknown, but seems to contain information on cross-world linkshells
|
|
|
|
LinkShellInformation = 0x234,
|
|
|
|
// Unknown, server sends to the client before player spawn
|
|
|
|
Unk9 = 0x189,
|
|
|
|
// Unknown, server sends to the client before player spawn.
|
|
|
|
// Seems to the same across two different characters?
|
|
|
|
Unk10 = 0x110,
|
|
|
|
// Unknown, server sends this in response to Unk7
|
|
|
|
Unk11 = 0x156,
|
2025-03-13 23:30:48 -04:00
|
|
|
// Assumed what this is, but probably incorrect
|
|
|
|
CharacterCreated = 0xE,
|
2025-03-14 00:30:37 -04:00
|
|
|
// Unknown, client sends this for ???
|
|
|
|
Unk12 = 0x0E9,
|
2025-03-15 19:34:29 -04:00
|
|
|
// Sent by the client when the character walks into a zone transistion
|
|
|
|
EnterZoneLine = 0x205,
|
|
|
|
// Sent by the client after we sent a InitZone in TravelToZone??
|
|
|
|
// TODO: Actually, I don't think is real...
|
|
|
|
Unk13 = 0x2EE,
|
|
|
|
// Sent by the server when it wants the client to... prepare to zone?
|
|
|
|
PrepareZoning = 0x308,
|
|
|
|
// Sent by the client for unknown reasons
|
|
|
|
Unk14 = 0x87,
|
2025-03-16 14:07:56 -04:00
|
|
|
// Sent by the server???
|
|
|
|
Unk15 = 0x28C,
|
|
|
|
// Sent by the server before init zone???
|
|
|
|
Unk16 = 0x3AB,
|
|
|
|
// Sent by the server
|
|
|
|
ActorControl = 0x1B9,
|
|
|
|
// Sent by the server
|
|
|
|
ActorMove = 0x3D8,
|
|
|
|
// Sent by the server
|
|
|
|
Unk17 = 0x2A1,
|
|
|
|
// Sent by the server in response to SocialListRequest
|
|
|
|
SocialList = 0x36C,
|
2025-03-08 22:03:28 -05:00
|
|
|
}
|
2025-03-08 22:05:20 -05:00
|
|
|
|
|
|
|
#[binrw]
|
2025-03-08 22:17:26 -05:00
|
|
|
#[derive(Debug, Clone, Default)]
|
2025-03-08 22:05:20 -05:00
|
|
|
pub struct ServiceAccount {
|
|
|
|
pub id: u32,
|
|
|
|
pub unk1: u32,
|
|
|
|
pub index: u32,
|
|
|
|
#[bw(pad_size_to = 0x44)]
|
|
|
|
#[br(count = 0x44)]
|
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
2025-03-08 22:55:47 -05:00
|
|
|
#[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,
|
2025-03-11 22:02:49 -04:00
|
|
|
#[bw(pad_size_to = 64)]
|
|
|
|
#[br(count = 64)]
|
2025-03-08 22:55:47 -05:00
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
2025-03-13 19:52:01 -04:00
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
pub struct ActorSetPos {
|
|
|
|
pub unk: u32,
|
|
|
|
pub layer_id: u32,
|
|
|
|
pub position: Position,
|
|
|
|
pub unk3: u32,
|
|
|
|
}
|
|
|
|
|
2025-03-08 22:55:47 -05:00
|
|
|
#[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,
|
2025-03-09 10:39:46 -04:00
|
|
|
pub origin_server_id: u16,
|
|
|
|
pub current_server_id: u16,
|
2025-03-08 22:55:47 -05:00
|
|
|
pub unk1: [u8; 16],
|
2025-03-13 00:18:00 -04:00
|
|
|
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
|
|
|
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
2025-03-08 22:55:47 -05:00
|
|
|
#[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)]
|
2025-03-09 10:39:46 -04:00
|
|
|
pub origin_server_name: String,
|
2025-03-08 22:55:47 -05:00
|
|
|
#[bw(pad_size_to = 32)]
|
|
|
|
#[br(count = 32)]
|
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
2025-03-09 10:39:46 -04:00
|
|
|
pub current_server_name: String,
|
2025-03-08 22:55:47 -05:00
|
|
|
#[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],
|
|
|
|
}
|
|
|
|
|
2025-03-09 00:20:41 -05:00
|
|
|
#[binrw]
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
|
|
pub enum LobbyCharacterAction {
|
2025-03-13 22:22:02 -04:00
|
|
|
#[brw(magic = 0x1u8)]
|
|
|
|
ReserveName,
|
|
|
|
#[brw(magic = 0x2u8)]
|
|
|
|
Create,
|
|
|
|
#[brw(magic = 0x3u8)]
|
|
|
|
Rename,
|
|
|
|
#[brw(magic = 0x4u8)]
|
|
|
|
Delete,
|
|
|
|
#[brw(magic = 0x5u8)]
|
|
|
|
Move,
|
|
|
|
#[brw(magic = 0x6u8)]
|
|
|
|
RemakeRetainer,
|
|
|
|
#[brw(magic = 0x7u8)]
|
|
|
|
RemakeChara,
|
|
|
|
#[brw(magic = 0x8u8)]
|
|
|
|
SettingsUploadBegin,
|
|
|
|
#[brw(magic = 0xCu8)]
|
|
|
|
SettingsUpload,
|
|
|
|
#[brw(magic = 0xEu8)]
|
|
|
|
WorldVisit,
|
|
|
|
#[brw(magic = 0xFu8)]
|
|
|
|
DataCenterToken,
|
|
|
|
#[brw(magic = 0x15u8)]
|
|
|
|
Request,
|
2025-03-09 00:20:41 -05:00
|
|
|
}
|
|
|
|
|
2025-03-13 00:41:16 -04:00
|
|
|
#[binrw]
|
|
|
|
#[brw(repr = u8)]
|
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
|
|
|
pub enum GameMasterCommandType {
|
|
|
|
ChangeTerritory = 0x58,
|
|
|
|
}
|
|
|
|
|
2025-03-08 22:05:20 -05:00
|
|
|
#[binrw]
|
|
|
|
#[br(import(magic: &IPCOpCode))]
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum IPCStructData {
|
|
|
|
// Client->Server IPC
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::ClientVersionInfo))]
|
|
|
|
ClientVersionInfo {
|
2025-03-16 15:20:55 -04:00
|
|
|
sequence: u64,
|
|
|
|
|
2025-03-16 15:39:44 -04:00
|
|
|
#[brw(pad_before = 10)] // full of nonsense i don't understand yet
|
2025-03-08 22:05:20 -05:00
|
|
|
#[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'
|
|
|
|
},
|
2025-03-08 22:55:47 -05:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::RequestCharacterList))]
|
|
|
|
RequestCharacterList {
|
|
|
|
#[brw(pad_before = 16)]
|
|
|
|
sequence: u64,
|
|
|
|
// TODO: what is in here?
|
|
|
|
},
|
2025-03-09 00:20:41 -05:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::LobbyCharacterAction))]
|
|
|
|
LobbyCharacterAction {
|
2025-03-11 21:31:52 -04:00
|
|
|
request_number: u32,
|
|
|
|
unk1: u32,
|
|
|
|
character_id: u64,
|
|
|
|
#[br(pad_before = 8)]
|
|
|
|
character_index: u8,
|
2025-03-09 00:20:41 -05:00
|
|
|
action: LobbyCharacterAction,
|
2025-03-11 21:31:52 -04:00
|
|
|
world_id: u16,
|
2025-03-13 00:18:00 -04:00
|
|
|
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
|
|
|
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
2025-03-09 00:06:54 -05:00
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
name: String,
|
2025-03-13 22:22:02 -04:00
|
|
|
#[bw(pad_size_to = 436)]
|
|
|
|
#[br(count = 436)]
|
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
json: String,
|
2025-03-09 00:06:54 -05:00
|
|
|
},
|
2025-03-09 11:01:06 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::RequestEnterWorld))]
|
|
|
|
RequestEnterWorld {
|
|
|
|
#[brw(pad_before = 16)]
|
|
|
|
sequence: u64,
|
|
|
|
lookup_id: u64,
|
|
|
|
// TODO: what else is in here?
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::InitRequest))]
|
|
|
|
InitRequest {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
#[br(dbg)]
|
|
|
|
unk: [u8; 105],
|
|
|
|
},
|
2025-03-12 00:32:37 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::FinishLoading))]
|
|
|
|
FinishLoading {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 72],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk1))]
|
|
|
|
Unk1 {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 32],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk2))]
|
|
|
|
Unk2 {
|
|
|
|
// TODO: full of possibly interesting information
|
2025-03-16 14:43:30 -04:00
|
|
|
unk: [u8; 8],
|
2025-03-12 00:32:37 -04:00
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk3))]
|
|
|
|
Unk3 {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk4))]
|
|
|
|
Unk4 {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::SetSearchInfoHandler))]
|
|
|
|
SetSearchInfoHandler {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk5))]
|
|
|
|
Unk5 {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
2025-03-16 14:07:56 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::SocialListRequest))]
|
|
|
|
SocialListRequest(SocialListRequest),
|
2025-03-12 00:32:37 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk7))]
|
|
|
|
Unk7 {
|
|
|
|
// TODO: full of possibly interesting information
|
2025-03-13 21:49:03 -04:00
|
|
|
timestamp: u32,
|
|
|
|
#[brw(pad_before = 8)] // empty bytes
|
|
|
|
#[brw(pad_after = 4)] // empty bytes
|
|
|
|
unk1: [u8; 16], // something
|
2025-03-12 00:32:37 -04:00
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::UpdatePositionHandler))]
|
|
|
|
UpdatePositionHandler {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 24],
|
|
|
|
},
|
2025-03-12 18:44:05 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::LogOut))]
|
|
|
|
LogOut {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Disconnected))]
|
|
|
|
Disconnected {
|
|
|
|
// TODO: full of possibly interesting information
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
2025-03-12 19:34:15 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::ChatMessage))]
|
2025-03-15 20:36:39 -04:00
|
|
|
ChatMessage(ChatMessage),
|
2025-03-13 00:41:16 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::GameMasterCommand))]
|
|
|
|
GameMasterCommand {
|
|
|
|
// TODO: incomplete
|
|
|
|
command: GameMasterCommandType,
|
|
|
|
#[br(pad_before = 3)] // idk, not empty though
|
|
|
|
arg: u32,
|
|
|
|
#[br(dbg)]
|
|
|
|
unk: [u8; 24],
|
|
|
|
},
|
2025-03-14 00:30:37 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk12))]
|
|
|
|
Unk12 {
|
|
|
|
unk: [u8; 8], // TODO: unknown
|
|
|
|
},
|
2025-03-15 19:34:29 -04:00
|
|
|
#[br(pre_assert(*magic == IPCOpCode::EnterZoneLine))]
|
|
|
|
EnterZoneLine {
|
|
|
|
exit_box_id: u32,
|
|
|
|
position: Position,
|
|
|
|
#[brw(pad_after = 4)] // empty
|
|
|
|
landset_index: i32,
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk13))]
|
|
|
|
Unk13 {
|
|
|
|
#[br(dbg)]
|
|
|
|
unk: [u8; 16], // TODO: unknown
|
|
|
|
},
|
|
|
|
#[br(pre_assert(*magic == IPCOpCode::Unk14))]
|
|
|
|
Unk14 {
|
|
|
|
#[br(dbg)]
|
|
|
|
unk: [u8; 8], // TODO: unknown
|
|
|
|
},
|
2025-03-08 22:05:20 -05:00
|
|
|
|
|
|
|
// Server->Client IPC
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-08 22:05:20 -05:00
|
|
|
LobbyServiceAccountList {
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(dbg)]
|
2025-03-08 22:05:20 -05:00
|
|
|
sequence: u64,
|
2025-03-08 22:17:26 -05:00
|
|
|
#[brw(pad_before = 1)]
|
2025-03-08 22:05:20 -05:00
|
|
|
num_service_accounts: u8,
|
|
|
|
unk1: u8,
|
|
|
|
#[brw(pad_after = 4)]
|
|
|
|
unk2: u8,
|
|
|
|
#[br(count = 8)]
|
|
|
|
service_accounts: Vec<ServiceAccount>,
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-08 22:55:47 -05:00
|
|
|
LobbyServerList {
|
|
|
|
sequence: u64,
|
|
|
|
unk1: u16,
|
|
|
|
offset: u16,
|
|
|
|
#[brw(pad_after = 8)]
|
|
|
|
num_servers: u32,
|
|
|
|
#[br(count = 6)]
|
2025-03-11 22:02:49 -04:00
|
|
|
#[brw(pad_size_to = 504)]
|
2025-03-08 22:55:47 -05:00
|
|
|
servers: Vec<Server>,
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-08 22:55:47 -05:00
|
|
|
LobbyRetainerList {
|
|
|
|
// TODO: what is in here?
|
|
|
|
#[brw(pad_before = 7)]
|
|
|
|
#[brw(pad_after = 202)]
|
|
|
|
unk1: u8,
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-08 22:55:47 -05:00
|
|
|
LobbyCharacterList {
|
|
|
|
sequence: u64,
|
|
|
|
counter: u8,
|
|
|
|
#[brw(pad_after = 2)]
|
|
|
|
num_in_packet: u8,
|
|
|
|
unk1: u8,
|
|
|
|
unk2: u8,
|
|
|
|
unk3: u8,
|
2025-03-09 09:40:11 -04:00
|
|
|
/// Set to 128 if legacy character
|
2025-03-08 22:55:47 -05:00
|
|
|
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,
|
2025-03-08 23:49:53 -05:00
|
|
|
max_characters_on_world: u16,
|
2025-03-08 22:55:47 -05:00
|
|
|
unk8: u16,
|
|
|
|
#[brw(pad_after = 12)]
|
|
|
|
entitled_expansion: u32,
|
|
|
|
#[br(count = 2)]
|
2025-03-11 22:02:49 -04:00
|
|
|
#[brw(pad_size_to = 2368)]
|
2025-03-08 22:55:47 -05:00
|
|
|
characters: Vec<CharacterDetails>,
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-09 11:01:06 -04:00
|
|
|
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,
|
|
|
|
},
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-11 23:52:55 -04:00
|
|
|
InitializeChat { unk: [u8; 8] },
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
InitResponse {
|
|
|
|
unk1: u64,
|
|
|
|
character_id: u32,
|
|
|
|
unk2: u32,
|
|
|
|
},
|
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
InitZone(InitZone),
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
ActorControlSelf(ActorControlSelf),
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
PlayerStats(PlayerStats),
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
PlayerSetup(PlayerSetup),
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
UpdateClassInfo(UpdateClassInfo),
|
2025-03-12 00:32:37 -04:00
|
|
|
#[br(pre_assert(false))]
|
2025-03-12 18:05:41 -04:00
|
|
|
PlayerSpawn(PlayerSpawn),
|
2025-03-12 18:44:05 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
LogOutComplete {
|
|
|
|
// TODO: guessed
|
|
|
|
unk: [u8; 8],
|
|
|
|
},
|
2025-03-13 19:52:01 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
ActorSetPos(ActorSetPos),
|
2025-03-13 21:11:20 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
ServerChatMessage {
|
|
|
|
unk: u8, // channel?
|
|
|
|
#[brw(pad_after = 775)]
|
|
|
|
#[br(count = 775)]
|
|
|
|
#[br(map = read_string)]
|
|
|
|
#[bw(map = write_string)]
|
|
|
|
message: String,
|
|
|
|
},
|
2025-03-13 21:49:03 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk8 { unk: [u8; 808] },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
LinkShellInformation { unk: [u8; 456] },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk9 { unk: [u8; 24] },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk10 { unk: u64 },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk11 {
|
|
|
|
timestamp: u32,
|
|
|
|
#[brw(pad_after = 24)] // empty bytes
|
|
|
|
unk: u32,
|
|
|
|
},
|
2025-03-13 22:22:02 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
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,
|
|
|
|
},
|
2025-03-13 23:30:48 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
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,
|
|
|
|
},
|
2025-03-15 19:34:29 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
PrepareZoning { unk: [u32; 4] },
|
2025-03-16 14:07:56 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk15 { unk: u32, player_id: u32 },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk16 { unk: [u8; 136] },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
ActorControl {
|
|
|
|
#[brw(pad_after = 20)] // empty
|
|
|
|
unk: u32,
|
|
|
|
},
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
ActorMove {
|
|
|
|
#[brw(pad_after = 4)] // empty
|
|
|
|
pos: Position,
|
|
|
|
},
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
Unk17 { unk: [u8; 104] },
|
|
|
|
#[br(pre_assert(false))]
|
|
|
|
SocialList(SocialList),
|
2025-03-16 15:20:55 -04:00
|
|
|
#[br(pre_assert(false))]
|
|
|
|
LobbyError {
|
|
|
|
sequence: u64,
|
|
|
|
error: u32,
|
|
|
|
value: u32,
|
|
|
|
exd_error_id: u16,
|
|
|
|
unk1: u16,
|
|
|
|
},
|
2025-03-08 22:05:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct IPCSegment {
|
|
|
|
pub unk1: u8,
|
|
|
|
pub unk2: u8,
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(dbg)]
|
2025-03-08 22:05:20 -05:00
|
|
|
pub op_code: IPCOpCode,
|
|
|
|
#[brw(pad_before = 2)] // empty
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(dbg)]
|
2025-03-08 22:05:20 -05:00
|
|
|
pub server_id: u16,
|
2025-03-10 21:31:21 -04:00
|
|
|
#[br(dbg)]
|
2025-03-08 22:05:20 -05:00
|
|
|
pub timestamp: u32,
|
|
|
|
#[brw(pad_before = 4)]
|
|
|
|
#[br(args(&op_code))]
|
|
|
|
pub data: IPCStructData,
|
|
|
|
}
|
|
|
|
|
2025-03-16 14:07:56 -04:00
|
|
|
impl Default for IPCSegment {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
unk1: 0x14,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitializeChat,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: IPCStructData::ClientVersionInfo {
|
|
|
|
session_id: String::new(),
|
|
|
|
version_info: String::new(),
|
2025-03-16 15:20:55 -04:00
|
|
|
sequence: 0,
|
2025-03-16 14:07:56 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-08 22:05:20 -05:00
|
|
|
impl IPCSegment {
|
|
|
|
pub fn calc_size(&self) -> u32 {
|
|
|
|
let header = 16;
|
|
|
|
header
|
2025-03-08 22:17:26 -05:00
|
|
|
+ match self.data {
|
|
|
|
IPCStructData::ClientVersionInfo { .. } => todo!(),
|
|
|
|
IPCStructData::LobbyServiceAccountList { .. } => 24 + (8 * 80),
|
2025-03-08 22:55:47 -05:00
|
|
|
IPCStructData::RequestCharacterList { .. } => todo!(),
|
|
|
|
IPCStructData::LobbyServerList { .. } => 24 + (6 * 84),
|
|
|
|
IPCStructData::LobbyRetainerList { .. } => 210,
|
2025-03-08 23:49:53 -05:00
|
|
|
IPCStructData::LobbyCharacterList { .. } => 80 + (2 * 1184),
|
2025-03-09 00:20:41 -05:00
|
|
|
IPCStructData::LobbyCharacterAction { .. } => todo!(),
|
2025-03-09 11:01:06 -04:00
|
|
|
IPCStructData::LobbyEnterWorld { .. } => 160,
|
|
|
|
IPCStructData::RequestEnterWorld { .. } => todo!(),
|
2025-03-11 23:52:55 -04:00
|
|
|
IPCStructData::InitializeChat { .. } => 8,
|
|
|
|
IPCStructData::InitRequest { .. } => 16,
|
2025-03-10 21:31:21 -04:00
|
|
|
IPCStructData::InitResponse { .. } => 16,
|
|
|
|
IPCStructData::InitZone { .. } => 103,
|
|
|
|
IPCStructData::ActorControlSelf { .. } => 32,
|
2025-03-12 18:17:22 -04:00
|
|
|
IPCStructData::PlayerStats { .. } => 224,
|
2025-03-16 14:07:56 -04:00
|
|
|
IPCStructData::PlayerSetup { .. } => 2784,
|
2025-03-10 21:31:21 -04:00
|
|
|
IPCStructData::UpdateClassInfo { .. } => 48,
|
2025-03-12 00:32:37 -04:00
|
|
|
IPCStructData::FinishLoading { .. } => todo!(),
|
|
|
|
IPCStructData::PlayerSpawn { .. } => 656,
|
|
|
|
IPCStructData::Unk1 { .. } => todo!(),
|
|
|
|
IPCStructData::Unk2 { .. } => todo!(),
|
|
|
|
IPCStructData::Unk3 { .. } => todo!(),
|
|
|
|
IPCStructData::Unk4 { .. } => todo!(),
|
|
|
|
IPCStructData::SetSearchInfoHandler { .. } => todo!(),
|
|
|
|
IPCStructData::Unk5 { .. } => todo!(),
|
2025-03-16 14:07:56 -04:00
|
|
|
IPCStructData::SocialListRequest { .. } => todo!(),
|
2025-03-12 00:32:37 -04:00
|
|
|
IPCStructData::Unk7 { .. } => todo!(),
|
|
|
|
IPCStructData::UpdatePositionHandler { .. } => todo!(),
|
2025-03-12 18:44:05 -04:00
|
|
|
IPCStructData::LogOut { .. } => todo!(),
|
|
|
|
IPCStructData::LogOutComplete { .. } => 8,
|
|
|
|
IPCStructData::Disconnected { .. } => todo!(),
|
2025-03-12 19:45:15 -04:00
|
|
|
IPCStructData::ChatMessage { .. } => 1056,
|
2025-03-13 00:41:16 -04:00
|
|
|
IPCStructData::GameMasterCommand { .. } => todo!(),
|
2025-03-13 19:52:01 -04:00
|
|
|
IPCStructData::ActorSetPos { .. } => 24,
|
2025-03-13 21:11:20 -04:00
|
|
|
IPCStructData::ServerChatMessage { .. } => 776,
|
2025-03-13 21:49:03 -04:00
|
|
|
IPCStructData::Unk8 { .. } => 808,
|
|
|
|
IPCStructData::LinkShellInformation { .. } => 456,
|
|
|
|
IPCStructData::Unk9 { .. } => 24,
|
|
|
|
IPCStructData::Unk10 { .. } => 8,
|
|
|
|
IPCStructData::Unk11 { .. } => 32,
|
2025-03-13 22:22:02 -04:00
|
|
|
IPCStructData::NameRejection { .. } => 536,
|
2025-03-13 23:30:48 -04:00
|
|
|
IPCStructData::CharacterCreated { .. } => 2568,
|
2025-03-14 00:30:37 -04:00
|
|
|
IPCStructData::Unk12 { .. } => todo!(),
|
2025-03-15 19:34:29 -04:00
|
|
|
IPCStructData::EnterZoneLine { .. } => todo!(),
|
|
|
|
IPCStructData::Unk13 { .. } => todo!(),
|
|
|
|
IPCStructData::PrepareZoning { .. } => 16,
|
|
|
|
IPCStructData::Unk14 { .. } => todo!(),
|
2025-03-16 14:07:56 -04:00
|
|
|
IPCStructData::Unk15 { .. } => 8,
|
|
|
|
IPCStructData::Unk16 { .. } => 136,
|
|
|
|
IPCStructData::ActorControl { .. } => 24,
|
|
|
|
IPCStructData::ActorMove { .. } => 16,
|
|
|
|
IPCStructData::Unk17 { .. } => 104,
|
|
|
|
IPCStructData::SocialList { .. } => 1136,
|
2025-03-16 15:20:55 -04:00
|
|
|
IPCStructData::LobbyError { .. } => 536,
|
2025-03-08 22:17:26 -05:00
|
|
|
}
|
2025-03-08 22:05:20 -05:00
|
|
|
}
|
|
|
|
}
|
2025-03-11 22:02:49 -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 test_ipc_sizes() {
|
|
|
|
let ipc_types = [
|
|
|
|
IPCStructData::LobbyServerList {
|
|
|
|
sequence: 0,
|
|
|
|
unk1: 0,
|
|
|
|
offset: 0,
|
|
|
|
num_servers: 0,
|
|
|
|
servers: Vec::new(),
|
|
|
|
},
|
|
|
|
IPCStructData::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(),
|
2025-03-11 23:52:55 -04:00
|
|
|
},
|
2025-03-12 18:05:41 -04:00
|
|
|
IPCStructData::ActorControlSelf(ActorControlSelf::default()),
|
2025-03-11 23:52:55 -04:00
|
|
|
IPCStructData::InitializeChat { unk: [0; 8] },
|
2025-03-12 18:17:22 -04:00
|
|
|
IPCStructData::PlayerStats(PlayerStats::default()),
|
|
|
|
IPCStructData::PlayerSetup(PlayerSetup::default()),
|
|
|
|
IPCStructData::UpdateClassInfo(UpdateClassInfo::default()),
|
|
|
|
IPCStructData::PlayerSpawn(PlayerSpawn::default()),
|
2025-03-13 19:52:01 -04:00
|
|
|
IPCStructData::ActorSetPos(ActorSetPos::default()),
|
2025-03-11 22:02:49 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
for ipc in &ipc_types {
|
|
|
|
let mut cursor = Cursor::new(Vec::new());
|
|
|
|
|
|
|
|
let ipc_segment = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitializeChat, // doesn't matter for this test
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
|
|
|
data: ipc.clone(),
|
|
|
|
};
|
|
|
|
ipc_segment.write_le(&mut cursor).unwrap();
|
|
|
|
|
|
|
|
let buffer = cursor.into_inner();
|
|
|
|
|
2025-03-12 18:17:22 -04:00
|
|
|
assert_eq!(
|
|
|
|
buffer.len(),
|
|
|
|
ipc_segment.calc_size() as usize,
|
|
|
|
"{:?} did not match size!",
|
|
|
|
ipc
|
|
|
|
);
|
2025-03-11 22:02:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|