From 51f6ad674451c0b8dce71ad7480636b738387434 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 16 Mar 2025 14:07:56 -0400 Subject: [PATCH] Grab bag of various fixes I have unsuccessfully tried to spawn another actor, the game recieves it (and adds it to the object table) but they are marked invisible. Besises, this also contains various field improvements and initial support for social lists. --- src/bin/kawari-world.rs | 124 +++++++++++++++++++++++++------------- src/ipc.rs | 74 +++++++++++++++++++---- src/packet.rs | 3 +- src/world/chat_handler.rs | 67 ++++++++++++++++++++ src/world/connection.rs | 87 +++++--------------------- src/world/init_zone.rs | 1 - src/world/mod.rs | 6 ++ src/world/player_setup.rs | 4 +- src/world/player_spawn.rs | 8 ++- src/world/social_list.rs | 62 +++++++++++++++++++ src/world/zone.rs | 9 ++- 11 files changed, 311 insertions(+), 134 deletions(-) create mode 100644 src/world/social_list.rs diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index a5d3e7e..e18561d 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -7,8 +7,8 @@ use kawari::ipc::{GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData}; use kawari::oodle::FFXIVOodle; use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive}; use kawari::world::{ - ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerSetup, PlayerSpawn, - PlayerStats, Position, UpdateClassInfo, Zone, ZoneConnection, + ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerEntry, PlayerSetup, + PlayerSpawn, PlayerStats, Position, SocialList, UpdateClassInfo, Zone, ZoneConnection, }; use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID, timestamp_secs}; use tokio::io::AsyncReadExt; @@ -38,6 +38,7 @@ async fn main() { socket, state, player_id: 0, + spawn_index: 0, zone: Zone::load(ZONE_ID), }; @@ -91,6 +92,7 @@ async fn main() { target_actor: 0, segment_type: SegmentType::ZoneInitialize { player_id: *player_id, + timestamp: timestamp_secs(), }, }) .await; @@ -107,6 +109,7 @@ async fn main() { target_actor: 0, segment_type: SegmentType::ZoneInitialize { player_id: *player_id, + timestamp: timestamp_secs(), }, }) .await; @@ -114,12 +117,10 @@ async fn main() { { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::InitializeChat, - server_id: 0, - timestamp: 0, + timestamp: timestamp_secs(), data: IPCStructData::InitializeChat { unk: [0; 8] }, + ..Default::default() }; connection @@ -146,16 +147,14 @@ async fn main() { // IPC Init(?) { let ipc = IPCSegment { - unk1: 0, - unk2: 0, - op_code: IPCOpCode::InitResponse, - server_id: 0, + op_code: IPCOpCode::Unk5, timestamp: timestamp_secs(), data: IPCStructData::InitResponse { unk1: 0, character_id: connection.player_id, unk2: 0, }, + ..Default::default() }; connection @@ -170,10 +169,7 @@ async fn main() { // Control Data { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::ActorControlSelf, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::ActorControlSelf( ActorControlSelf { @@ -187,6 +183,7 @@ async fn main() { param6: 0, }, ), + ..Default::default() }; connection @@ -201,10 +198,7 @@ async fn main() { // Stats { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PlayerStats, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PlayerStats(PlayerStats { strength: 1, @@ -212,6 +206,7 @@ async fn main() { mp: 100, ..Default::default() }), + ..Default::default() }; connection @@ -226,18 +221,17 @@ async fn main() { // Player Setup { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PlayerSetup, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PlayerSetup(PlayerSetup { content_id: CONTENT_ID, exp: [10000; 32], levels: [100; 32], name: CHAR_NAME.to_string(), + char_id: connection.player_id, ..Default::default() }), + ..Default::default() }; connection @@ -259,15 +253,13 @@ async fn main() { // send welcome message { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::ServerChatMessage, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::ServerChatMessage { message: "Welcome to Kawari!".to_string(), unk: 0, }, + ..Default::default() }; connection @@ -282,12 +274,10 @@ async fn main() { // send player spawn { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PlayerSpawn, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PlayerSpawn(PlayerSpawn { + content_id: CONTENT_ID, current_world_id: WORLD_ID, home_world_id: WORLD_ID, title: 1, @@ -298,11 +288,11 @@ async fn main() { mp_curr: 100, mp_max: 100, model_type: 1, - spawn_index: 1, state: 1, gm_rank: 3, look: CUSTOMIZE_DATA, fc_tag: "LOCAL".to_string(), + subtype: 4, models: [ 0, // head 89, // body @@ -319,6 +309,7 @@ async fn main() { .unwrap_or(Position::default()), ..Default::default() }), + ..Default::default() }; connection @@ -333,14 +324,12 @@ async fn main() { // fade in? { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PrepareZoning, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PrepareZoning { unk: [0, 0, 0, 0], }, + ..Default::default() }; connection @@ -373,8 +362,65 @@ async fn main() { IPCStructData::Unk5 { .. } => { tracing::info!("Recieved Unk5!"); } - IPCStructData::Unk6 { .. } => { - tracing::info!("Recieved Unk6!"); + IPCStructData::SocialListRequest(request) => { + tracing::info!("Recieved social list request!"); + + match &request.request_type { + kawari::world::SocialListRequestType::Party => { + let ipc = IPCSegment { + op_code: IPCOpCode::SocialList, + timestamp: timestamp_secs(), + data: IPCStructData::SocialList(SocialList { + request_type: request.request_type, + sequence: request.count, + entries: vec![PlayerEntry { + content_id: CONTENT_ID, + zone_id: connection.zone.id, + zone_id1: 0x0100, + class_job: 36, + level: 100, + one: 1, + name: CHAR_NAME.to_string(), + fc_tag: "LOCAL".to_string(), + ..Default::default() + }], + }), + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection.player_id, + target_actor: connection.player_id, + segment_type: SegmentType::Ipc { + data: ipc, + }, + }) + .await; + } + kawari::world::SocialListRequestType::Friends => { + let ipc = IPCSegment { + op_code: IPCOpCode::SocialList, + timestamp: timestamp_secs(), + data: IPCStructData::SocialList(SocialList { + request_type: request.request_type, + sequence: request.count, + entries: Default::default(), + }), + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection.player_id, + target_actor: connection.player_id, + segment_type: SegmentType::Ipc { + data: ipc, + }, + }) + .await; + } + } } IPCStructData::Unk7 { timestamp, unk1, .. @@ -384,15 +430,13 @@ async fn main() { // send unk11 in response { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::Unk11, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::Unk11 { timestamp: *timestamp, unk: 333, }, + ..Default::default() }; connection @@ -413,12 +457,10 @@ async fn main() { // tell the client to disconnect { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::LogOutComplete, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::LogOutComplete { unk: [0; 8] }, + ..Default::default() }; connection @@ -488,14 +530,12 @@ async fn main() { // fade out? { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PrepareZoning, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PrepareZoning { unk: [0x01000000, 0, 0, 0], }, + ..Default::default() }; connection @@ -510,14 +550,12 @@ async fn main() { // fade out? x2 { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::PrepareZoning, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::PrepareZoning { unk: [0, 0x00000085, 0x00030000, 0x000008ff], // last thing is probably a float? }, + ..Default::default() }; connection diff --git a/src/ipc.rs b/src/ipc.rs index 8fd9c83..03bdca2 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -5,7 +5,7 @@ use crate::{ common::{read_string, write_string}, world::{ ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position, - UpdateClassInfo, + SocialList, SocialListRequest, UpdateClassInfo, }, }; @@ -38,8 +38,6 @@ pub enum IPCOpCode { /// 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 as response to ZoneInitRequest. - InitResponse = 0x1EF, /// Sent by the server that tells the client which zone to load InitZone = 0x0311, /// Sent by the server for... something @@ -58,16 +56,17 @@ pub enum IPCOpCode { // 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 - Unk2 = 0x1A1, + Unk2 = 0x2E5, // 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 + /// ALSO Sent by the server as response to ZoneInitRequest. Unk5 = 0x2D0, - // FIXME: 8 bytes of something from the client, not sure what yet - Unk6 = 0x2E5, + // Sent by the client when it requests the friends list and other related info + SocialListRequest = 0x1A1, // FIXME: 32 bytes of something from the client, not sure what yet Unk7 = 0x2B5, UpdatePositionHandler = 0x249, // TODO: assumed @@ -109,6 +108,18 @@ pub enum IPCOpCode { PrepareZoning = 0x308, // Sent by the client for unknown reasons Unk14 = 0x87, + // 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, } #[binrw] @@ -313,11 +324,8 @@ pub enum IPCStructData { // TODO: full of possibly interesting information unk: [u8; 8], }, - #[br(pre_assert(*magic == IPCOpCode::Unk6))] - Unk6 { - // TODO: full of possibly interesting information - unk: [u8; 8], - }, + #[br(pre_assert(*magic == IPCOpCode::SocialListRequest))] + SocialListRequest(SocialListRequest), #[br(pre_assert(*magic == IPCOpCode::Unk7))] Unk7 { // TODO: full of possibly interesting information @@ -523,6 +531,24 @@ pub enum IPCStructData { }, #[br(pre_assert(false))] PrepareZoning { unk: [u32; 4] }, + #[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), } #[binrw] @@ -542,6 +568,22 @@ pub struct IPCSegment { pub data: IPCStructData, } +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(), + }, + } + } +} + impl IPCSegment { pub fn calc_size(&self) -> u32 { let header = 16; @@ -562,7 +604,7 @@ impl IPCSegment { IPCStructData::InitZone { .. } => 103, IPCStructData::ActorControlSelf { .. } => 32, IPCStructData::PlayerStats { .. } => 224, - IPCStructData::PlayerSetup { .. } => 2545, + IPCStructData::PlayerSetup { .. } => 2784, IPCStructData::UpdateClassInfo { .. } => 48, IPCStructData::FinishLoading { .. } => todo!(), IPCStructData::PlayerSpawn { .. } => 656, @@ -572,7 +614,7 @@ impl IPCSegment { IPCStructData::Unk4 { .. } => todo!(), IPCStructData::SetSearchInfoHandler { .. } => todo!(), IPCStructData::Unk5 { .. } => todo!(), - IPCStructData::Unk6 { .. } => todo!(), + IPCStructData::SocialListRequest { .. } => todo!(), IPCStructData::Unk7 { .. } => todo!(), IPCStructData::UpdatePositionHandler { .. } => todo!(), IPCStructData::LogOut { .. } => todo!(), @@ -594,6 +636,12 @@ impl IPCSegment { IPCStructData::Unk13 { .. } => todo!(), IPCStructData::PrepareZoning { .. } => 16, IPCStructData::Unk14 { .. } => todo!(), + IPCStructData::Unk15 { .. } => 8, + IPCStructData::Unk16 { .. } => 136, + IPCStructData::ActorControl { .. } => 24, + IPCStructData::ActorMove { .. } => 16, + IPCStructData::Unk17 { .. } => 104, + IPCStructData::SocialList { .. } => 1136, } } } diff --git a/src/packet.rs b/src/packet.rs index 99c6946..661ba3d 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -69,8 +69,9 @@ pub enum SegmentType { KeepAliveResponse { id: u32, timestamp: u32 }, #[brw(magic = 0x2u32)] ZoneInitialize { - #[brw(pad_after = 36)] player_id: u32, + #[brw(pad_after = 32)] + timestamp: u32, }, } diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index 70f013a..adfbe05 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -1,3 +1,15 @@ +use std::io::Cursor; + +use binrw::BinRead; + +use crate::{ + CHAR_NAME, CUSTOMIZE_DATA, WORLD_ID, + ipc::{IPCOpCode, IPCSegment, IPCStructData}, + packet::{PacketSegment, SegmentType}, + timestamp_secs, + world::PlayerSpawn, +}; + use super::{ChatMessage, Position, ZoneConnection}; pub struct ChatHandler {} @@ -21,6 +33,61 @@ impl ChatHandler { }) .await; } + "!spawnactor" => { + println!("Spawning actor..."); + + // send player spawn + { + let ipc = IPCSegment { + unk1: 20, + unk2: 0, + op_code: IPCOpCode::PlayerSpawn, + server_id: 0, + timestamp: timestamp_secs(), + data: IPCStructData::PlayerSpawn(PlayerSpawn { + some_unique_id: 1, + content_id: 1, + current_world_id: WORLD_ID, + home_world_id: WORLD_ID, + title: 1, + class_job: 35, + name: CHAR_NAME.to_string(), + hp_curr: 100, + hp_max: 100, + mp_curr: 100, + mp_max: 100, + model_type: 1, + state: 1, + gm_rank: 3, + spawn_index: connection.get_free_spawn_index(), + look: CUSTOMIZE_DATA, + fc_tag: "LOCAL".to_string(), + models: [ + 0, // head + 89, // body + 89, // hands + 89, // legs + 89, // feet + 0, // ears + 0, // neck + 0, // wrists + 0, // left finger + 0, // right finger + ], + pos: Position::default(), + ..Default::default() + }), + }; + + connection + .send_segment(PacketSegment { + source_actor: 0x106ad804, + target_actor: connection.player_id, + segment_type: SegmentType::Ipc { data: ipc }, + }) + .await; + } + } _ => tracing::info!("Unrecognized debug command!"), } } diff --git a/src/world/connection.rs b/src/world/connection.rs index b6b6b6b..b575149 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -1,3 +1,6 @@ +use std::io::Cursor; + +use binrw::BinRead; use tokio::net::TcpStream; use crate::{ @@ -19,6 +22,7 @@ pub struct ZoneConnection { pub player_id: u32, pub zone: Zone, + pub spawn_index: u8, } impl ZoneConnection { @@ -40,16 +44,14 @@ impl ZoneConnection { // set pos { let ipc = IPCSegment { - unk1: 14, - unk2: 0, op_code: IPCOpCode::ActorSetPos, - server_id: WORLD_ID, timestamp: timestamp_secs(), data: IPCStructData::ActorSetPos(ActorSetPos { unk: 0x020fa3b8, position, ..Default::default() }), + ..Default::default() }; let response_packet = PacketSegment { @@ -73,10 +75,7 @@ impl ZoneConnection { // Player Class Info { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::UpdateClassInfo, - server_id: 69, // lol timestamp: timestamp_secs(), data: IPCStructData::UpdateClassInfo(UpdateClassInfo { class_id: 35, @@ -85,6 +84,7 @@ impl ZoneConnection { class_level: 90, ..Default::default() }), + ..Default::default() }; self.send_segment(PacketSegment { @@ -95,56 +95,13 @@ impl ZoneConnection { .await; } - // unk10 - { - let ipc = IPCSegment { - unk1: 0, - unk2: 0, - op_code: IPCOpCode::Unk10, - server_id: 69, // lol - timestamp: timestamp_secs(), - data: IPCStructData::Unk10 { - unk: 0x41a0000000000002, - }, - }; - - self.send_segment(PacketSegment { - source_actor: self.player_id, - target_actor: self.player_id, - segment_type: SegmentType::Ipc { data: ipc }, - }) - .await; - } - - // unk9 - { - let ipc = IPCSegment { - unk1: 0, - unk2: 0, - op_code: IPCOpCode::Unk9, - server_id: 69, // lol - timestamp: timestamp_secs(), - data: IPCStructData::Unk9 { unk: [0; 24] }, - }; - - self.send_segment(PacketSegment { - source_actor: self.player_id, - target_actor: self.player_id, - segment_type: SegmentType::Ipc { data: ipc }, - }) - .await; - } - - // TODO: maybe only sent on initial login not every zone? // link shell information { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::LinkShellInformation, - server_id: 69, // lol timestamp: timestamp_secs(), data: IPCStructData::LinkShellInformation { unk: [0; 456] }, + ..Default::default() }; self.send_segment(PacketSegment { @@ -155,39 +112,20 @@ impl ZoneConnection { .await; } - // unk8 - { - let ipc = IPCSegment { - unk1: 0, - unk2: 0, - op_code: IPCOpCode::Unk8, - server_id: 69, // lol - timestamp: timestamp_secs(), - data: IPCStructData::Unk8 { unk: [0; 808] }, - }; - - self.send_segment(PacketSegment { - source_actor: self.player_id, - target_actor: self.player_id, - segment_type: SegmentType::Ipc { data: ipc }, - }) - .await; - } + // TODO: send unk16? // Init Zone { let ipc = IPCSegment { - unk1: 0, - unk2: 0, op_code: IPCOpCode::InitZone, - server_id: 0, timestamp: timestamp_secs(), data: IPCStructData::InitZone(InitZone { - server_id: WORLD_ID, + server_id: 0, zone_id: self.zone.id, weather_id: 1, ..Default::default() }), + ..Default::default() }; self.send_segment(PacketSegment { @@ -198,4 +136,9 @@ impl ZoneConnection { .await; } } + + pub fn get_free_spawn_index(&mut self) -> u8 { + self.spawn_index += 1; + return self.spawn_index; + } } diff --git a/src/world/init_zone.rs b/src/world/init_zone.rs index 447ccfd..5ebd019 100644 --- a/src/world/init_zone.rs +++ b/src/world/init_zone.rs @@ -11,7 +11,6 @@ pub struct InitZone { pub content_finder_condition_id: u16, pub layer_set_id: u32, pub layout_id: u32, - #[br(dbg)] pub weather_id: u16, // index into Weather sheet probably? pub unk_really: u16, pub unk_bitmask1: u8, diff --git a/src/world/mod.rs b/src/world/mod.rs index bc276f4..2711e4d 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -34,3 +34,9 @@ pub use connection::ZoneConnection; mod chat_message; 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; diff --git a/src/world/player_setup.rs b/src/world/player_setup.rs index bedd82f..0762167 100644 --- a/src/world/player_setup.rs +++ b/src/world/player_setup.rs @@ -82,7 +82,9 @@ pub struct PlayerSetup { #[bw(pad_size_to = 33)] pub mount_guide_mask: Vec, pub ornament_mask: [u8; 4], - pub unknown281: [u8; 23], + #[br(count = 85)] + #[bw(pad_size_to = 85)] + pub unknown281: Vec, #[br(count = CHAR_NAME_MAX_LENGTH)] #[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)] #[br(map = read_string)] diff --git a/src/world/player_spawn.rs b/src/world/player_spawn.rs index 023b526..54a2c80 100644 --- a/src/world/player_spawn.rs +++ b/src/world/player_spawn.rs @@ -11,7 +11,11 @@ use super::status_effect::StatusEffect; #[brw(little)] #[derive(Debug, Clone, Default)] pub struct PlayerSpawn { - pub aafafaf: [u8; 16], + // also shows up in the friends list. + pub some_unique_id: u32, + + #[brw(pad_before = 4)] // always empty? + pub content_id: u64, pub title: u16, pub u1b: u16, @@ -121,6 +125,7 @@ mod tests { assert_eq!(player_spawn.mp_curr, 10000); assert_eq!(player_spawn.mp_max, 10000); assert_eq!(player_spawn.state, 1); + assert_eq!(player_spawn.spawn_index, 0); assert_eq!(player_spawn.level, 1); assert_eq!(player_spawn.class_job, 1); // adventurer assert_eq!(player_spawn.scale, 36); @@ -132,5 +137,6 @@ mod tests { assert_eq!(player_spawn.look.gender, 1); assert_eq!(player_spawn.look.bust, 100); assert_eq!(player_spawn.fc_tag, ""); + assert_eq!(player_spawn.subtype, 4); } } diff --git a/src/world/social_list.rs b/src/world/social_list.rs new file mode 100644 index 0000000..86bbf48 --- /dev/null +++ b/src/world/social_list.rs @@ -0,0 +1,62 @@ +use binrw::binrw; + +use crate::{ + CHAR_NAME_MAX_LENGTH, + common::{read_string, write_string}, +}; + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, Clone, Copy)] +pub enum SocialListRequestType { + Party = 0x1, + Friends = 0x2, +} + +#[binrw] +#[derive(Debug, Clone)] +pub struct SocialListRequest { + #[brw(pad_before = 10)] // empty + pub request_type: SocialListRequestType, + #[brw(pad_after = 4)] // empty + pub count: u8, +} + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct PlayerEntry { + pub content_id: u64, + pub unk: [u8; 12], + pub zone_id: u16, + pub zone_id1: u16, + pub unk2: [u8; 8], + pub online_status_mask: u64, + pub class_job: u8, + pub padding: u8, + pub level: u8, + pub padding1: u8, + pub padding2: u16, + pub one: u32, + #[br(count = CHAR_NAME_MAX_LENGTH)] + #[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub name: String, + #[br(count = 6)] + #[bw(pad_size_to = 6)] + #[br(map = read_string)] + #[bw(map = write_string)] + pub fc_tag: String, +} + +#[binrw] +#[derive(Debug, Clone)] +pub struct SocialList { + #[brw(pad_before = 12)] // empty + pub request_type: SocialListRequestType, + pub sequence: u8, + #[brw(pad_before = 2)] // empty + #[br(count = 10)] + #[bw(pad_size_to = 112 * 10)] + pub entries: Vec, +} diff --git a/src/world/zone.rs b/src/world/zone.rs index 00b144a..4819061 100644 --- a/src/world/zone.rs +++ b/src/world/zone.rs @@ -22,7 +22,9 @@ impl Zone { GameData::from_existing(Platform::Win32, &config.game_location).unwrap(); let exh = game_data.read_excel_sheet_header("TerritoryType").unwrap(); - let exd = game_data.read_excel_sheet("TerritoryType", &exh, Language::None, 0).unwrap(); + let exd = game_data + .read_excel_sheet("TerritoryType", &exh, Language::None, 0) + .unwrap(); let territory_type_row = &exd.read_row(&exh, id as u32).unwrap()[0]; @@ -31,7 +33,10 @@ impl Zone { panic!("Unexpected type!"); }; - let path = format!("bg/{}/level/planmap.lgb", &bg_path[..bg_path.find("/level/").unwrap()]); + let path = format!( + "bg/{}/level/planmap.lgb", + &bg_path[..bg_path.find("/level/").unwrap()] + ); let lgb = game_data.extract(&path).unwrap(); let layer_group = LayerGroup::from_existing(&lgb).unwrap(); Self { id, layer_group }