From 34aa046b219fa2df419f00e5ed3d0dc6c03886f4 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 13 Mar 2025 00:02:37 -0400 Subject: [PATCH] Fix padding in Player Spawn IPC data I spent HOURS fixing this, only to realize that it was all offset by 16 bytes. I hate myself. --- src/bin/kawari-world.rs | 34 +++++++++++++++- src/client_select_data.rs | 3 ++ src/world/mod.rs | 1 + src/world/player_spawn.rs | 86 +++++++++++++++++++++++++++++++++++---- 4 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 5191a44..ba38443 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -1,13 +1,14 @@ use std::time::{SystemTime, UNIX_EPOCH}; +use kawari::client_select_data::ClientCustomizeData; use kawari::ipc::{IPCOpCode, IPCSegment, IPCStructData}; use kawari::oodle::FFXIVOodle; use kawari::packet::{ CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet, }; use kawari::world::{ - ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, - UpdateClassInfo, + ActorControlSelf, ActorControlType, CustomizeData, InitZone, PlayerSetup, PlayerSpawn, + PlayerStats, UpdateClassInfo, }; use kawari::{CONTENT_ID, WORLD_ID, ZONE_ID}; use tokio::io::AsyncReadExt; @@ -374,6 +375,35 @@ async fn main() { model_type: 1, spawn_index: 1, state: 1, + look: CustomizeData { + race: 3, + age: 0, + gender: 1, + height: 5, + subrace: 0, + face: 1, + hair: 2, + enable_highlights: 0, + skin_tone: 4, + right_eye_color: 5, + hair_tone: 2, + highlights: 7, + facial_features: 1, + facial_feature_color: 1, + eyebrows: 2, + left_eye_color: 1, + eyes: 1, + nose: 0, + jaw: 1, + mouth: 0, + lips_tone_fur_pattern: 1, + race_feature_size: 1, + race_feature_type: 1, + bust: 0, + face_paint: 1, + face_paint_color: 1, + }, + fc_tag: "LOCAL".to_string(), ..Default::default() }), }; diff --git a/src/client_select_data.rs b/src/client_select_data.rs index 8ee7498..6ea1e15 100644 --- a/src/client_select_data.rs +++ b/src/client_select_data.rs @@ -1,5 +1,8 @@ +use binrw::binrw; use serde_json::{Value, json}; +#[binrw] +#[derive(Debug, Clone, Default)] pub struct ClientCustomizeData { pub race: i32, pub gender: i32, diff --git a/src/world/mod.rs b/src/world/mod.rs index 7535a14..4494798 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1,4 +1,5 @@ mod player_spawn; +pub use player_spawn::CustomizeData; pub use player_spawn::PlayerSpawn; mod position; diff --git a/src/world/player_spawn.rs b/src/world/player_spawn.rs index f324dc2..4660f67 100644 --- a/src/world/player_spawn.rs +++ b/src/world/player_spawn.rs @@ -1,13 +1,17 @@ use binrw::binrw; +use crate::client_select_data::ClientCustomizeData; use crate::common::{read_string, write_string}; use super::position::Position; use super::status_effect::StatusEffect; #[binrw] +#[brw(little)] #[derive(Debug, Clone, Default)] pub struct PlayerSpawn { + pub aafafaf: [u8; 16], + pub title: u16, pub u1b: u16, pub current_world_id: u16, @@ -39,7 +43,6 @@ pub struct PlayerSpawn { pub director_id: u32, pub owner_id: u32, pub u22: u32, - pub padding4: [u8; 16], pub hp_max: u32, pub hp_curr: u32, pub display_flags: u32, @@ -67,25 +70,94 @@ pub struct PlayerSpawn { pub class_job: u8, pub unk28: u8, pub unk29: u8, - pub unk30: u8, pub mount_head: u8, pub mount_body: u8, pub mount_feet: u8, pub mount_color: u8, pub scale: u8, pub element_data: [u8; 6], - pub padding2: [u8; 12], + pub padding2: [u8; 1], pub effect: [StatusEffect; 30], pub pos: Position, pub models: [u32; 10], pub unknown6_58: [u8; 10], - pub padding3: [u8; 7], + pub padding3: [u8; 4], #[br(count = 32)] #[bw(pad_size_to = 32)] #[br(map = read_string)] #[bw(map = write_string)] pub name: String, - pub look: [u8; 26], - pub fc_tag: [u8; 6], - pub padding: [u8; 26], + pub look: CustomizeData, + #[br(count = 6)] + #[bw(pad_size_to = 6)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub fc_tag: String, + pub padding: [u8; 2], +} + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct CustomizeData { + pub race: u8, + pub gender: u8, + pub age: u8, + pub height: u8, + pub subrace: u8, + pub face: u8, + pub hair: u8, + pub enable_highlights: u8, + pub skin_tone: u8, + pub right_eye_color: u8, + pub hair_tone: u8, + pub highlights: u8, + pub facial_features: u8, + pub facial_feature_color: u8, + pub eyebrows: u8, + pub left_eye_color: u8, + pub eyes: u8, + pub nose: u8, + pub jaw: u8, + pub mouth: u8, + pub lips_tone_fur_pattern: u8, + pub race_feature_size: u8, + pub race_feature_type: u8, + pub bust: u8, + pub face_paint: u8, + pub face_paint_color: u8, +} + +#[cfg(test)] +mod tests { + use std::{fs::read, io::Cursor}; + + use binrw::BinRead; + + use super::*; + + #[test] + fn read_playerspawn() { + let buffer = read("/home/josh/Downloads/myfile(1).dat").unwrap(); + let mut buffer = Cursor::new(&buffer); + + let player_spawn = PlayerSpawn::read_le(&mut buffer).unwrap(); + assert_eq!(player_spawn.current_world_id, 0x4F); + assert_eq!(player_spawn.home_world_id, 0x4F); + assert_eq!(player_spawn.hp_curr, 159); + assert_eq!(player_spawn.hp_max, 159); + assert_eq!(player_spawn.mp_curr, 10000); + assert_eq!(player_spawn.mp_max, 10000); + assert_eq!(player_spawn.state, 1); + assert_eq!(player_spawn.level, 1); + assert_eq!(player_spawn.class_job, 1); // adventurer + assert_eq!(player_spawn.scale, 36); + assert_eq!(player_spawn.pos.x, 40.519722); + assert_eq!(player_spawn.pos.y, 4.0); + assert_eq!(player_spawn.pos.z, -150.33124); + assert_eq!(player_spawn.name, "Lavenaa Warren"); + assert_eq!(player_spawn.look.race, 1); + assert_eq!(player_spawn.look.gender, 1); + assert_eq!(player_spawn.look.bust, 100); + assert_eq!(player_spawn.fc_tag, ""); + } }