1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-06-30 11:47:45 +00:00

Add various things useful for using Kawari downstream

Such as better unknown packet parsing, IPC opcode names and
more stuff exposed as public API.
This commit is contained in:
Joshua Goins 2025-06-26 20:59:46 -04:00
parent b560642978
commit db3166d8b3
9 changed files with 105 additions and 11 deletions

View file

@ -24,7 +24,6 @@ fn main() {
if !opcodes.is_empty() { if !opcodes.is_empty() {
// beginning // beginning
output_str.push_str("#[binrw]\n"); 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("#[derive(Clone, PartialEq, Debug)]\n");
output_str.push_str(&format!("pub enum {key} {{\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 name = opcode.get("name").unwrap().as_str().unwrap();
let opcode = opcode.get("opcode").unwrap().as_number().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 // end
output_str.push_str("}\n\n"); output_str.push_str("}\n\n");
// sizes
output_str.push_str(&format!("impl {key} {{\n")); 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("/// 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("pub fn calc_size(&self) -> u32 {\n");
output_str.push_str("match self {\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}::{name} => {size},\n"));
} }
output_str.push_str(&format!("{key}::Unknown => 0,\n"));
output_str.push_str("}\n\n"); output_str.push_str("}\n\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"); output_str.push_str("}\n\n");
} }
} }

View file

@ -61,8 +61,12 @@ pub(crate) fn write_bool_as<T: std::convert::From<u8>>(x: &bool) -> T {
} }
pub(crate) fn read_string(byte_stream: Vec<u8>) -> String { pub(crate) fn read_string(byte_stream: Vec<u8>) -> String {
let str = String::from_utf8(byte_stream).unwrap(); // TODO: better error handling here
str.trim_matches(char::from(0)).to_string() // trim \0 from the end of strings 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<u8> { pub(crate) fn write_string(str: &String) -> Vec<u8> {

View file

@ -12,6 +12,10 @@ impl ReadWriteIpcSegment for ServerChatIpcSegment {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + self.op_code.calc_size() 16 + self.op_code.calc_size()
} }
fn get_name(&self) -> &'static str {
self.op_code.get_name()
}
} }
// TODO: make generic // TODO: make generic

View file

@ -27,6 +27,10 @@ impl ReadWriteIpcSegment for CustomIpcSegment {
CustomIpcType::CharacterRemade => 8, CustomIpcType::CharacterRemade => 8,
} }
} }
fn get_name(&self) -> &'static str {
""
}
} }
impl Default for CustomIpcSegment { impl Default for CustomIpcSegment {

View file

@ -25,6 +25,10 @@ impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + self.op_code.calc_size() 16 + self.op_code.calc_size()
} }
fn get_name(&self) -> &'static str {
self.op_code.get_name()
}
} }
// TODO: make generic // TODO: make generic
@ -52,6 +56,10 @@ impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + self.op_code.calc_size() 16 + self.op_code.calc_size()
} }
fn get_name(&self) -> &'static str {
self.op_code.get_name()
}
} }
// TODO: make generic // TODO: make generic

View file

@ -115,6 +115,7 @@ pub enum ActorControlCategory {
actor_id: u32, actor_id: u32,
unk1: u32, unk1: u32,
}, },
Unknown {},
} }
#[binrw] #[binrw]

View file

@ -101,6 +101,10 @@ impl ReadWriteIpcSegment for ClientZoneIpcSegment {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + self.op_code.calc_size() 16 + self.op_code.calc_size()
} }
fn get_name(&self) -> &'static str {
self.op_code.get_name()
}
} }
// TODO: make generic // TODO: make generic
@ -124,6 +128,10 @@ impl ReadWriteIpcSegment for ServerZoneIpcSegment {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + self.op_code.calc_size() 16 + self.op_code.calc_size()
} }
fn get_name(&self) -> &'static str {
self.op_code.get_name()
}
} }
// TODO: make generic // TODO: make generic
@ -159,35 +167,45 @@ pub enum GameMasterCommandType {
} }
#[binrw] #[binrw]
#[br(import(_magic: &ServerZoneIpcType))] #[br(import(magic: &ServerZoneIpcType))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ServerZoneIpcData { pub enum ServerZoneIpcData {
/// Sent by the server as response to ZoneInitRequest. /// Sent by the server as response to ZoneInitRequest.
#[br(pre_assert(*magic == ServerZoneIpcType::InitResponse))]
InitResponse { InitResponse {
unk1: u64, unk1: u64,
character_id: u32, character_id: u32,
unk2: u32, unk2: u32,
}, },
/// Sent by the server that tells the client which zone to load /// Sent by the server that tells the client which zone to load
#[br(pre_assert(*magic == ServerZoneIpcType::InitZone))]
InitZone(InitZone), InitZone(InitZone),
/// Sent by the server for... something /// Sent by the server for... something
#[br(pre_assert(*magic == ServerZoneIpcType::ActorControlSelf))]
ActorControlSelf(ActorControlSelf), ActorControlSelf(ActorControlSelf),
/// Sent by the server containing character stats /// Sent by the server containing character stats
#[br(pre_assert(*magic == ServerZoneIpcType::PlayerStats))]
PlayerStats(PlayerStats), PlayerStats(PlayerStats),
/// Sent by the server to setup the player on the client /// Sent by the server to setup the player on the client
#[br(pre_assert(*magic == ServerZoneIpcType::PlayerStatus))]
PlayerStatus(PlayerStatus), PlayerStatus(PlayerStatus),
/// Sent by the server to setup class info /// Sent by the server to setup class info
#[br(pre_assert(*magic == ServerZoneIpcType::UpdateClassInfo))]
UpdateClassInfo(UpdateClassInfo), UpdateClassInfo(UpdateClassInfo),
/// Sent by the server to spawn the player in /// Sent by the server to spawn the player in
#[br(pre_assert(*magic == ServerZoneIpcType::PlayerSpawn))]
PlayerSpawn(PlayerSpawn), PlayerSpawn(PlayerSpawn),
/// Sent by the server to indicate the log out is complete /// Sent by the server to indicate the log out is complete
#[br(pre_assert(*magic == ServerZoneIpcType::LogOutComplete))]
LogOutComplete { LogOutComplete {
// TODO: guessed // TODO: guessed
unk: [u8; 8], unk: [u8; 8],
}, },
/// Sent by the server to modify the client's position /// Sent by the server to modify the client's position
#[br(pre_assert(*magic == ServerZoneIpcType::Warp))]
Warp(Warp), Warp(Warp),
/// Sent by the server when they send a chat message /// Sent by the server when they send a chat message
#[br(pre_assert(*magic == ServerZoneIpcType::ServerChatMessage))]
ServerChatMessage { ServerChatMessage {
/* /*
* bits (properties will apply when set, but a final base 10 value of zero defaults to chat log only): * 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, message: String,
}, },
/// Unknown, but seems to contain information on cross-world linkshells /// 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? /// 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 /// Sent by the server
#[br(pre_assert(*magic == ServerZoneIpcType::ActorControl))]
ActorControl(ActorControl), ActorControl(ActorControl),
/// Sent by the server /// Sent by the server
#[br(pre_assert(*magic == ServerZoneIpcType::Move))]
Move(Move), Move(Move),
/// Sent by the server in response to SocialListRequest /// Sent by the server in response to SocialListRequest
#[br(pre_assert(*magic == ServerZoneIpcType::SocialList))]
SocialList(SocialList), SocialList(SocialList),
/// Sent by the server to spawn an NPC /// Sent by the server to spawn an NPC
#[br(pre_assert(*magic == ServerZoneIpcType::NpcSpawn))]
NpcSpawn(NpcSpawn), NpcSpawn(NpcSpawn),
/// Sent by the server to update an actor's status effect list /// Sent by the server to update an actor's status effect list
#[br(pre_assert(*magic == ServerZoneIpcType::StatusEffectList))]
StatusEffectList(StatusEffectList), StatusEffectList(StatusEffectList),
/// Sent by the server when it's time to change the weather /// Sent by the server when it's time to change the weather
#[br(pre_assert(*magic == ServerZoneIpcType::WeatherId))]
WeatherId(WeatherChange), WeatherId(WeatherChange),
/// Sent to inform the client of an inventory item /// Sent to inform the client of an inventory item
#[br(pre_assert(*magic == ServerZoneIpcType::UpdateItem))]
UpdateItem(ItemInfo), UpdateItem(ItemInfo),
/// Sent to inform the client of container status /// Sent to inform the client of container status
#[br(pre_assert(*magic == ServerZoneIpcType::ContainerInfo))]
ContainerInfo(ContainerInfo), ContainerInfo(ContainerInfo),
/// Sent to tell the client to play a scene /// Sent to tell the client to play a scene
#[br(pre_assert(*magic == ServerZoneIpcType::EventScene))]
EventScene(EventScene), EventScene(EventScene),
/// Sent to tell the client to load a scene, but not play it /// Sent to tell the client to load a scene, but not play it
#[br(pre_assert(*magic == ServerZoneIpcType::EventStart))]
EventStart(EventStart), EventStart(EventStart),
/// Sent to update an actor's hp & mp values /// Sent to update an actor's hp & mp values
#[br(pre_assert(*magic == ServerZoneIpcType::UpdateHpMpTp))]
UpdateHpMpTp { UpdateHpMpTp {
hp: u32, hp: u32,
mp: u16, mp: u16,
unk: u16, // it's filled with... something unk: u16, // it's filled with... something
}, },
/// Sent to inform the client the consequences of their actions /// Sent to inform the client the consequences of their actions
#[br(pre_assert(*magic == ServerZoneIpcType::ActionResult))]
ActionResult(ActionResult), ActionResult(ActionResult),
/// Sent to to the client to update their appearance /// Sent to to the client to update their appearance
#[br(pre_assert(*magic == ServerZoneIpcType::Equip))]
Equip(Equip), Equip(Equip),
/// Sent to the client to free up a spawn index /// Sent to the client to free up a spawn index
#[br(pre_assert(*magic == ServerZoneIpcType::Delete))]
Delete { Delete {
spawn_index: u8, spawn_index: u8,
#[brw(pad_before = 3)] // padding #[brw(pad_before = 3)] // padding
actor_id: u32, actor_id: u32,
}, },
/// Sent to the client to stop their currently playing event. /// Sent to the client to stop their currently playing event.
#[br(pre_assert(*magic == ServerZoneIpcType::EventFinish))]
EventFinish { EventFinish {
handler_id: u32, handler_id: u32,
event: u8, event: u8,
@ -259,16 +298,21 @@ pub enum ServerZoneIpcData {
arg: u32, arg: u32,
}, },
/// Sent after EventFinish? it un-occupies the character lol /// Sent after EventFinish? it un-occupies the character lol
#[br(pre_assert(*magic == ServerZoneIpcType::Unk18))]
Unk18 { Unk18 {
unk: [u8; 16], // all zero... unk: [u8; 16], // all zero...
}, },
/// Used to control target information /// Used to control target information
#[br(pre_assert(*magic == ServerZoneIpcType::ActorControlTarget))]
ActorControlTarget(ActorControlTarget), ActorControlTarget(ActorControlTarget),
/// Used to update the player's currencies /// Used to update the player's currencies
#[br(pre_assert(*magic == ServerZoneIpcType::CurrencyCrystalInfo))]
CurrencyCrystalInfo(CurrencyInfo), CurrencyCrystalInfo(CurrencyInfo),
/// Used to update an actor's equip display flags /// Used to update an actor's equip display flags
#[br(pre_assert(*magic == ServerZoneIpcType::Config))]
Config(Config), Config(Config),
/// Unknown, seen in haircut event /// Unknown, seen in haircut event
#[br(pre_assert(*magic == ServerZoneIpcType::EventUnkReply))]
EventUnkReply { EventUnkReply {
event_id: u32, event_id: u32,
unk1: u16, unk1: u16,
@ -276,11 +320,13 @@ pub enum ServerZoneIpcData {
#[brw(pad_after = 8)] #[brw(pad_after = 8)]
unk3: u8, unk3: u8,
}, },
#[br(pre_assert(*magic == ServerZoneIpcType::UnkCall))]
UnkCall { UnkCall {
unk1: u32, unk1: u32,
#[brw(pad_after = 26)] #[brw(pad_after = 26)]
unk2: u16, unk2: u16,
}, },
Unknown,
} }
#[binrw] #[binrw]
@ -430,6 +476,7 @@ pub enum ClientZoneIpcData {
#[brw(pad_after = 8)] #[brw(pad_after = 8)]
unk3: u8, unk3: u8,
}, },
Unknown,
} }
#[cfg(test)] #[cfg(test)]

View file

@ -7,6 +7,9 @@ pub trait ReadWriteIpcSegment:
/// Calculate the size of this Ipc segment *including* the 16 byte header. /// 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. /// When implementing this, please use the size seen in retail instead of guessing.
fn calc_size(&self) -> u32; fn calc_size(&self) -> u32;
/// Returns a human-readable name of the opcode.
fn get_name(&self) -> &'static str;
} }
/// An IPC packet segment. /// An IPC packet segment.

View file

@ -1,8 +1,7 @@
mod parsing; mod parsing;
use parsing::PacketHeader;
pub use parsing::{ pub use parsing::{
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, parse_packet, ConnectionType, PacketHeader, PacketSegment, PacketState, SegmentData, SegmentType,
send_keep_alive, send_packet, parse_packet, send_keep_alive, send_packet,
}; };
mod compression; mod compression;