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 }