diff --git a/build.rs b/build.rs index a7886f1..1a9a0bc 100644 --- a/build.rs +++ b/build.rs @@ -24,7 +24,6 @@ fn main() { if !opcodes.is_empty() { // beginning output_str.push_str("#[binrw]\n"); - output_str.push_str("#[brw(repr = u16)]\n"); output_str.push_str("#[derive(Clone, PartialEq, Debug)]\n"); output_str.push_str(&format!("pub enum {key} {{\n")); @@ -33,14 +32,18 @@ fn main() { let name = opcode.get("name").unwrap().as_str().unwrap(); let opcode = opcode.get("opcode").unwrap().as_number().unwrap(); - output_str.push_str(&format!("{name} = {opcode},\n")); + output_str.push_str(&format!("#[brw(magic = {opcode}u16)]\n")); + output_str.push_str(&format!("{name},\n")); } + output_str.push_str(&format!("Unknown,\n")); + // end output_str.push_str("}\n\n"); - // sizes output_str.push_str(&format!("impl {key} {{\n")); + + // sizes output_str.push_str("/// Returns the expected size of the data segment of this IPC opcode, _without_ any headers.\n"); output_str.push_str("pub fn calc_size(&self) -> u32 {\n"); output_str.push_str("match self {\n"); @@ -53,8 +56,29 @@ fn main() { output_str.push_str(&format!("{key}::{name} => {size},\n")); } + output_str.push_str(&format!("{key}::Unknown => 0,\n")); + output_str.push_str("}\n\n"); output_str.push_str("}\n\n"); + + // names + output_str.push_str("/// Returns a human-readable name of the opcode.\n"); + output_str.push_str("pub fn get_name(&self) -> &'static str {\n"); + output_str.push_str("match self {\n"); + + for opcode in opcodes { + let opcode = opcode.as_object().unwrap(); + let name = opcode.get("name").unwrap().as_str().unwrap(); + + output_str.push_str(&format!("{key}::{name} => \"{name}\",\n")); + } + + output_str.push_str(&format!("{key}::Unknown => \"Unknown\",\n")); + + output_str.push_str("}\n\n"); + output_str.push_str("}\n\n"); + + // end impl output_str.push_str("}\n\n"); } } diff --git a/src/common/mod.rs b/src/common/mod.rs index 6a9755c..b011d40 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -61,8 +61,12 @@ pub(crate) fn write_bool_as>(x: &bool) -> T { } pub(crate) fn read_string(byte_stream: Vec) -> String { - let str = String::from_utf8(byte_stream).unwrap(); - str.trim_matches(char::from(0)).to_string() // trim \0 from the end of strings + // TODO: better error handling here + if let Ok(str) = String::from_utf8(byte_stream) { + str.trim_matches(char::from(0)).to_string() // trim \0 from the end of strings + } else { + String::default() + } } pub(crate) fn write_string(str: &String) -> Vec { diff --git a/src/ipc/chat/mod.rs b/src/ipc/chat/mod.rs index 3889e00..53c0adf 100644 --- a/src/ipc/chat/mod.rs +++ b/src/ipc/chat/mod.rs @@ -12,6 +12,10 @@ impl ReadWriteIpcSegment for ServerChatIpcSegment { // 16 is the size of the IPC header 16 + self.op_code.calc_size() } + + fn get_name(&self) -> &'static str { + self.op_code.get_name() + } } // TODO: make generic diff --git a/src/ipc/kawari/mod.rs b/src/ipc/kawari/mod.rs index b20c898..82057ad 100644 --- a/src/ipc/kawari/mod.rs +++ b/src/ipc/kawari/mod.rs @@ -27,6 +27,10 @@ impl ReadWriteIpcSegment for CustomIpcSegment { CustomIpcType::CharacterRemade => 8, } } + + fn get_name(&self) -> &'static str { + "" + } } impl Default for CustomIpcSegment { diff --git a/src/ipc/lobby/mod.rs b/src/ipc/lobby/mod.rs index 5248c6b..cee3663 100644 --- a/src/ipc/lobby/mod.rs +++ b/src/ipc/lobby/mod.rs @@ -25,6 +25,10 @@ impl ReadWriteIpcSegment for ClientLobbyIpcSegment { // 16 is the size of the IPC header 16 + self.op_code.calc_size() } + + fn get_name(&self) -> &'static str { + self.op_code.get_name() + } } // TODO: make generic @@ -52,6 +56,10 @@ impl ReadWriteIpcSegment for ServerLobbyIpcSegment { // 16 is the size of the IPC header 16 + self.op_code.calc_size() } + + fn get_name(&self) -> &'static str { + self.op_code.get_name() + } } // TODO: make generic diff --git a/src/ipc/zone/actor_control.rs b/src/ipc/zone/actor_control.rs index e3ff0d5..7322dd5 100644 --- a/src/ipc/zone/actor_control.rs +++ b/src/ipc/zone/actor_control.rs @@ -115,6 +115,7 @@ pub enum ActorControlCategory { actor_id: u32, unk1: u32, }, + Unknown {}, } #[binrw] diff --git a/src/ipc/zone/mod.rs b/src/ipc/zone/mod.rs index 5ceed0f..0ac104d 100644 --- a/src/ipc/zone/mod.rs +++ b/src/ipc/zone/mod.rs @@ -101,6 +101,10 @@ impl ReadWriteIpcSegment for ClientZoneIpcSegment { // 16 is the size of the IPC header 16 + self.op_code.calc_size() } + + fn get_name(&self) -> &'static str { + self.op_code.get_name() + } } // TODO: make generic @@ -124,6 +128,10 @@ impl ReadWriteIpcSegment for ServerZoneIpcSegment { // 16 is the size of the IPC header 16 + self.op_code.calc_size() } + + fn get_name(&self) -> &'static str { + self.op_code.get_name() + } } // TODO: make generic @@ -159,35 +167,45 @@ pub enum GameMasterCommandType { } #[binrw] -#[br(import(_magic: &ServerZoneIpcType))] +#[br(import(magic: &ServerZoneIpcType))] #[derive(Debug, Clone)] pub enum ServerZoneIpcData { /// Sent by the server as response to ZoneInitRequest. + #[br(pre_assert(*magic == ServerZoneIpcType::InitResponse))] InitResponse { unk1: u64, character_id: u32, unk2: u32, }, /// Sent by the server that tells the client which zone to load + #[br(pre_assert(*magic == ServerZoneIpcType::InitZone))] InitZone(InitZone), /// Sent by the server for... something + #[br(pre_assert(*magic == ServerZoneIpcType::ActorControlSelf))] ActorControlSelf(ActorControlSelf), /// Sent by the server containing character stats + #[br(pre_assert(*magic == ServerZoneIpcType::PlayerStats))] PlayerStats(PlayerStats), /// Sent by the server to setup the player on the client + #[br(pre_assert(*magic == ServerZoneIpcType::PlayerStatus))] PlayerStatus(PlayerStatus), /// Sent by the server to setup class info + #[br(pre_assert(*magic == ServerZoneIpcType::UpdateClassInfo))] UpdateClassInfo(UpdateClassInfo), /// Sent by the server to spawn the player in + #[br(pre_assert(*magic == ServerZoneIpcType::PlayerSpawn))] PlayerSpawn(PlayerSpawn), /// Sent by the server to indicate the log out is complete + #[br(pre_assert(*magic == ServerZoneIpcType::LogOutComplete))] LogOutComplete { // TODO: guessed unk: [u8; 8], }, /// Sent by the server to modify the client's position + #[br(pre_assert(*magic == ServerZoneIpcType::Warp))] Warp(Warp), /// Sent by the server when they send a chat message + #[br(pre_assert(*magic == ServerZoneIpcType::ServerChatMessage))] ServerChatMessage { /* * bits (properties will apply when set, but a final base 10 value of zero defaults to chat log only): @@ -210,46 +228,67 @@ pub enum ServerZoneIpcData { message: String, }, /// Unknown, but seems to contain information on cross-world linkshells - LinkShellInformation { unk: [u8; 456] }, + #[br(pre_assert(*magic == ServerZoneIpcType::LinkShellInformation))] + LinkShellInformation { + unk: [u8; 456], + }, /// Sent by the server when it wants the client to... prepare to zone? - PrepareZoning { unk: [u32; 4] }, + #[br(pre_assert(*magic == ServerZoneIpcType::PrepareZoning))] + PrepareZoning { + unk: [u32; 4], + }, /// Sent by the server + #[br(pre_assert(*magic == ServerZoneIpcType::ActorControl))] ActorControl(ActorControl), /// Sent by the server + #[br(pre_assert(*magic == ServerZoneIpcType::Move))] Move(Move), /// Sent by the server in response to SocialListRequest + #[br(pre_assert(*magic == ServerZoneIpcType::SocialList))] SocialList(SocialList), /// Sent by the server to spawn an NPC + #[br(pre_assert(*magic == ServerZoneIpcType::NpcSpawn))] NpcSpawn(NpcSpawn), /// Sent by the server to update an actor's status effect list + #[br(pre_assert(*magic == ServerZoneIpcType::StatusEffectList))] StatusEffectList(StatusEffectList), /// Sent by the server when it's time to change the weather + #[br(pre_assert(*magic == ServerZoneIpcType::WeatherId))] WeatherId(WeatherChange), /// Sent to inform the client of an inventory item + #[br(pre_assert(*magic == ServerZoneIpcType::UpdateItem))] UpdateItem(ItemInfo), /// Sent to inform the client of container status + #[br(pre_assert(*magic == ServerZoneIpcType::ContainerInfo))] ContainerInfo(ContainerInfo), /// Sent to tell the client to play a scene + #[br(pre_assert(*magic == ServerZoneIpcType::EventScene))] EventScene(EventScene), /// Sent to tell the client to load a scene, but not play it + #[br(pre_assert(*magic == ServerZoneIpcType::EventStart))] EventStart(EventStart), /// Sent to update an actor's hp & mp values + #[br(pre_assert(*magic == ServerZoneIpcType::UpdateHpMpTp))] UpdateHpMpTp { hp: u32, mp: u16, unk: u16, // it's filled with... something }, /// Sent to inform the client the consequences of their actions + #[br(pre_assert(*magic == ServerZoneIpcType::ActionResult))] ActionResult(ActionResult), /// Sent to to the client to update their appearance + #[br(pre_assert(*magic == ServerZoneIpcType::Equip))] Equip(Equip), /// Sent to the client to free up a spawn index + #[br(pre_assert(*magic == ServerZoneIpcType::Delete))] Delete { spawn_index: u8, #[brw(pad_before = 3)] // padding actor_id: u32, }, /// Sent to the client to stop their currently playing event. + #[br(pre_assert(*magic == ServerZoneIpcType::EventFinish))] EventFinish { handler_id: u32, event: u8, @@ -259,16 +298,21 @@ pub enum ServerZoneIpcData { arg: u32, }, /// Sent after EventFinish? it un-occupies the character lol + #[br(pre_assert(*magic == ServerZoneIpcType::Unk18))] Unk18 { unk: [u8; 16], // all zero... }, /// Used to control target information + #[br(pre_assert(*magic == ServerZoneIpcType::ActorControlTarget))] ActorControlTarget(ActorControlTarget), /// Used to update the player's currencies + #[br(pre_assert(*magic == ServerZoneIpcType::CurrencyCrystalInfo))] CurrencyCrystalInfo(CurrencyInfo), /// Used to update an actor's equip display flags + #[br(pre_assert(*magic == ServerZoneIpcType::Config))] Config(Config), /// Unknown, seen in haircut event + #[br(pre_assert(*magic == ServerZoneIpcType::EventUnkReply))] EventUnkReply { event_id: u32, unk1: u16, @@ -276,11 +320,13 @@ pub enum ServerZoneIpcData { #[brw(pad_after = 8)] unk3: u8, }, + #[br(pre_assert(*magic == ServerZoneIpcType::UnkCall))] UnkCall { unk1: u32, #[brw(pad_after = 26)] unk2: u16, }, + Unknown, } #[binrw] @@ -430,6 +476,7 @@ pub enum ClientZoneIpcData { #[brw(pad_after = 8)] unk3: u8, }, + Unknown, } #[cfg(test)] diff --git a/src/packet/ipc.rs b/src/packet/ipc.rs index f9d8060..0463d21 100644 --- a/src/packet/ipc.rs +++ b/src/packet/ipc.rs @@ -7,6 +7,9 @@ pub trait ReadWriteIpcSegment: /// Calculate the size of this Ipc segment *including* the 16 byte header. /// When implementing this, please use the size seen in retail instead of guessing. fn calc_size(&self) -> u32; + + /// Returns a human-readable name of the opcode. + fn get_name(&self) -> &'static str; } /// An IPC packet segment. diff --git a/src/packet/mod.rs b/src/packet/mod.rs index b2880a9..9a0bf3b 100644 --- a/src/packet/mod.rs +++ b/src/packet/mod.rs @@ -1,8 +1,7 @@ mod parsing; -use parsing::PacketHeader; pub use parsing::{ - ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, parse_packet, - send_keep_alive, send_packet, + ConnectionType, PacketHeader, PacketSegment, PacketState, SegmentData, SegmentType, + parse_packet, send_keep_alive, send_packet, }; mod compression;