1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-21 15:07:45 +00:00
kawari/src/world/ipc/mod.rs

425 lines
13 KiB
Rust
Raw Normal View History

mod chat_message;
use binrw::binrw;
pub use chat_message::ChatMessage;
mod social_list;
pub use social_list::PlayerEntry;
pub use social_list::SocialList;
pub use social_list::SocialListRequest;
pub use social_list::SocialListRequestType;
mod player_spawn;
pub use player_spawn::PlayerSpawn;
mod status_effect;
pub use status_effect::StatusEffect;
mod update_class_info;
pub use update_class_info::UpdateClassInfo;
mod player_setup;
pub use player_setup::PlayerSetup;
mod player_stats;
pub use player_stats::PlayerStats;
mod actor_control;
pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf};
mod init_zone;
pub use init_zone::InitZone;
mod npc_spawn;
pub use npc_spawn::NpcSpawn;
mod common_spawn;
pub use common_spawn::{
BattleNpcSubKind, CharacterMode, CommonSpawn, DisplayFlag, GameMasterRank, ObjectKind,
OnlineStatus, PlayerSubKind,
};
mod status_effect_list;
pub use status_effect_list::StatusEffectList;
mod weather_change;
pub use weather_change::WeatherChange;
mod action_request;
pub use action_request::ActionRequest;
mod container_info;
2025-03-23 16:49:48 -04:00
pub use container_info::{ContainerInfo, ContainerType};
mod item_info;
pub use item_info::ItemInfo;
2025-03-22 22:01:32 -04:00
use crate::common::Position;
use crate::common::read_string;
use crate::common::write_string;
use crate::opcodes::ClientZoneIpcType;
use crate::opcodes::ServerZoneIpcType;
use crate::packet::IpcSegment;
use crate::packet::ReadWriteIpcSegment;
pub type ClientZoneIpcSegment = IpcSegment<ClientZoneIpcType, ClientZoneIpcData>;
impl ReadWriteIpcSegment for ClientZoneIpcSegment {
fn calc_size(&self) -> u32 {
// 16 is the size of the IPC header
16 + self.op_code.calc_size()
}
}
// TODO: make generic
impl Default for ClientZoneIpcSegment {
fn default() -> Self {
Self {
unk1: 0x14,
unk2: 0,
op_code: ClientZoneIpcType::InitRequest,
server_id: 0,
timestamp: 0,
data: ClientZoneIpcData::InitRequest { unk: [0; 120] },
}
}
}
pub type ServerZoneIpcSegment = IpcSegment<ServerZoneIpcType, ServerZoneIpcData>;
impl ReadWriteIpcSegment for ServerZoneIpcSegment {
fn calc_size(&self) -> u32 {
// 16 is the size of the IPC header
16 + self.op_code.calc_size()
}
}
// TODO: make generic
impl Default for ServerZoneIpcSegment {
fn default() -> Self {
Self {
unk1: 0x14,
unk2: 0,
op_code: ServerZoneIpcType::InitializeChat,
server_id: 0,
timestamp: 0,
data: ServerZoneIpcData::InitializeChat { unk: [0; 8] },
}
}
}
// TODO: move to their own files
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct ActorSetPos {
pub unk: u32,
pub layer_id: u32,
pub position: Position,
pub unk3: u32,
}
#[binrw]
#[brw(repr = u8)]
#[derive(Clone, PartialEq, Debug)]
pub enum GameMasterCommandType {
ChangeWeather = 0x6,
ToggleInvisibility = 0xD,
ChangeTerritory = 0x58,
}
#[binrw]
#[br(import(_magic: &ServerZoneIpcType))]
#[derive(Debug, Clone)]
pub enum ServerZoneIpcData {
/// Sent by the server to Initialize something chat-related?
InitializeChat { unk: [u8; 8] },
/// Sent by the server as response to ZoneInitRequest.
InitResponse {
unk1: u64,
character_id: u32,
unk2: u32,
},
/// Sent by the server that tells the client which zone to load
InitZone(InitZone),
/// Sent by the server for... something
ActorControlSelf(ActorControlSelf),
/// Sent by the server containing character stats
PlayerStats(PlayerStats),
/// Sent by the server to setup the player on the client
PlayerSetup(PlayerSetup),
/// Sent by the server to setup class info
UpdateClassInfo(UpdateClassInfo),
/// Sent by the server to spawn the player in
PlayerSpawn(PlayerSpawn),
/// Sent by the server to indicate the log out is complete
LogOutComplete {
// TODO: guessed
unk: [u8; 8],
},
/// Sent by the server to modify the client's position
ActorSetPos(ActorSetPos),
/// Sent by the server when they send a chat message
ServerChatMessage {
unk: u8, // channel?
#[brw(pad_after = 775)]
#[br(count = 775)]
#[br(map = read_string)]
#[bw(map = write_string)]
message: String,
},
/// Unknown, server sends to the client before player spawn
Unk8 { unk: [u8; 808] },
/// Unknown, but seems to contain information on cross-world linkshells
LinkShellInformation { unk: [u8; 456] },
/// Unknown, server sends to the client before player spawn
Unk9 { unk: [u8; 24] },
/// Unknown, server sends this in response to Unk7
Unk11 {
timestamp: u32,
#[brw(pad_after = 24)] // empty bytes
unk: u32,
},
/// Sent by the server when it wants the client to... prepare to zone?
PrepareZoning { unk: [u32; 4] },
/// Sent by the server???
Unk15 { unk: u32, player_id: u32 },
/// Sent by the server before init zone???
Unk16 { unk: [u8; 136] },
/// Sent by the server
ActorControl(ActorControl),
/// Sent by the server
ActorMove {
#[brw(pad_after = 4)] // empty
pos: Position,
},
/// Sent by the server
Unk17 { unk: [u8; 104] },
/// Sent by the server in response to SocialListRequest
SocialList(SocialList),
/// Sent by the server to spawn an NPC
NpcSpawn(NpcSpawn),
/// Sent by the server to update an actor's status effect list
StatusEffectList(StatusEffectList),
/// Sent by the server when it's time to change the weather
WeatherChange(WeatherChange),
/// Sent to inform the client of an inventory item
ItemInfo(ItemInfo),
/// Sent to inform the client of container status
ContainerInfo(ContainerInfo),
}
#[binrw]
#[br(import(magic: &ClientZoneIpcType))]
#[derive(Debug, Clone)]
pub enum ClientZoneIpcData {
/// Sent by the client when they successfully initialize with the server, and they need several bits of information (e.g. what zone to load)
#[br(pre_assert(*magic == ClientZoneIpcType::InitRequest))]
InitRequest {
// TODO: full of possibly interesting information
unk: [u8; 120],
},
/// Sent by the client when they're done loading and they need to be spawned in
#[br(pre_assert(*magic == ClientZoneIpcType::FinishLoading))]
FinishLoading {
// TODO: full of possibly interesting information
unk: [u8; 72],
},
/// FIXME: 32 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk1))]
Unk1 {
// TODO: full of possibly interesting information
unk: [u8; 32],
},
/// FIXME: 16 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk2))]
Unk2 {
// TODO: full of possibly interesting information
unk: [u8; 16],
},
/// FIXME: 8 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk3))]
Unk3 {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
/// FIXME: 8 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk4))]
Unk4 {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
#[br(pre_assert(*magic == ClientZoneIpcType::SetSearchInfoHandler))]
SetSearchInfoHandler {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
/// FIXME: 8 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk5))]
Unk5 {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
/// Sent by the client when it requests the friends list and other related info
#[br(pre_assert(*magic == ClientZoneIpcType::SocialListRequest))]
SocialListRequest(SocialListRequest),
/// FIXME: 32 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk7))]
Unk7 {
// TODO: full of possibly interesting information
timestamp: u32,
#[brw(pad_before = 8)] // empty bytes
#[brw(pad_after = 4)] // empty bytes
unk1: [u8; 16], // something
},
#[br(pre_assert(*magic == ClientZoneIpcType::UpdatePositionHandler))]
UpdatePositionHandler {
// TODO: full of possibly interesting information
unk: [u8; 8], // not empty
#[brw(pad_after = 4)] // empty
position: Position,
},
/// Sent by the client when the user requests to log out
#[br(pre_assert(*magic == ClientZoneIpcType::LogOut))]
LogOut {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
/// Sent by the client when it's actually disconnecting
#[br(pre_assert(*magic == ClientZoneIpcType::Disconnected))]
Disconnected {
// TODO: full of possibly interesting information
unk: [u8; 8],
},
/// Sent by the client when they send a chat message
#[br(pre_assert(*magic == ClientZoneIpcType::ChatMessage))]
ChatMessage(ChatMessage),
/// 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.
#[br(pre_assert(*magic == ClientZoneIpcType::GameMasterCommand))]
GameMasterCommand {
// TODO: incomplete
command: GameMasterCommandType,
#[br(pad_before = 3)] // idk, not empty though
arg: u32,
unk: [u8; 24],
},
/// Sent by the client when the character walks into a zone transistion
#[br(pre_assert(*magic == ClientZoneIpcType::EnterZoneLine))]
EnterZoneLine {
exit_box_id: u32,
position: Position,
#[brw(pad_after = 4)] // empty
landset_index: i32,
},
/// Sent by the client when a character performs an action
#[br(pre_assert(*magic == ClientZoneIpcType::ActionRequest))]
ActionRequest(ActionRequest),
#[br(pre_assert(*magic == ClientZoneIpcType::Unk16))]
Unk16 {
unk: [u8; 8], // TODO: unknown
},
#[br(pre_assert(*magic == ClientZoneIpcType::Unk17))]
Unk17 {
unk: [u8; 32], // TODO: unknown
},
#[br(pre_assert(*magic == ClientZoneIpcType::Unk18))]
Unk18 {
unk: [u8; 8], // TODO: unknown
},
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use binrw::BinWrite;
use crate::opcodes::ServerZoneIpcType;
use super::*;
/// Ensure that the IPC data size as reported matches up with what we write
#[test]
fn world_ipc_sizes() {
let ipc_types = [
(
ServerZoneIpcType::ActorControlSelf,
ServerZoneIpcData::ActorControlSelf(ActorControlSelf::default()),
),
(
ServerZoneIpcType::InitializeChat,
ServerZoneIpcData::InitializeChat { unk: [0; 8] },
),
(
ServerZoneIpcType::PlayerStats,
ServerZoneIpcData::PlayerStats(PlayerStats::default()),
),
(
ServerZoneIpcType::PlayerSetup,
ServerZoneIpcData::PlayerSetup(PlayerSetup::default()),
),
(
ServerZoneIpcType::UpdateClassInfo,
ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo::default()),
),
(
ServerZoneIpcType::PlayerSpawn,
ServerZoneIpcData::PlayerSpawn(PlayerSpawn::default()),
),
(
ServerZoneIpcType::ActorSetPos,
ServerZoneIpcData::ActorSetPos(ActorSetPos::default()),
),
(
ServerZoneIpcType::NpcSpawn,
ServerZoneIpcData::NpcSpawn(NpcSpawn::default()),
),
(
ServerZoneIpcType::ActorControl,
ServerZoneIpcData::ActorControl(ActorControl::default()),
),
(
ServerZoneIpcType::StatusEffectList,
ServerZoneIpcData::StatusEffectList(StatusEffectList::default()),
),
(
ServerZoneIpcType::WeatherChange,
ServerZoneIpcData::WeatherChange(WeatherChange::default()),
),
(
ServerZoneIpcType::ActorControl,
ServerZoneIpcData::ActorControl(ActorControl::default()),
),
(
ServerZoneIpcType::ItemInfo,
ServerZoneIpcData::ItemInfo(ItemInfo::default()),
),
(
ServerZoneIpcType::ContainerInfo,
ServerZoneIpcData::ContainerInfo(ContainerInfo::default()),
),
];
for (opcode, data) in &ipc_types {
let mut cursor = Cursor::new(Vec::new());
let ipc_segment = ServerZoneIpcSegment {
unk1: 0,
unk2: 0,
op_code: opcode.clone(), // doesn't matter for this test
server_id: 0,
timestamp: 0,
data: data.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
);
}
}
}