1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-27 17:07:46 +00:00

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.
This commit is contained in:
Joshua Goins 2025-03-16 14:07:56 -04:00
parent 1cbc5c72b9
commit 51f6ad6744
11 changed files with 311 additions and 134 deletions

View file

@ -7,8 +7,8 @@ use kawari::ipc::{GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
use kawari::oodle::FFXIVOodle; use kawari::oodle::FFXIVOodle;
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive}; use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
use kawari::world::{ use kawari::world::{
ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerSetup, PlayerSpawn, ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerEntry, PlayerSetup,
PlayerStats, Position, UpdateClassInfo, Zone, ZoneConnection, PlayerSpawn, PlayerStats, Position, SocialList, UpdateClassInfo, Zone, ZoneConnection,
}; };
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID, timestamp_secs}; use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID, timestamp_secs};
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
@ -38,6 +38,7 @@ async fn main() {
socket, socket,
state, state,
player_id: 0, player_id: 0,
spawn_index: 0,
zone: Zone::load(ZONE_ID), zone: Zone::load(ZONE_ID),
}; };
@ -91,6 +92,7 @@ async fn main() {
target_actor: 0, target_actor: 0,
segment_type: SegmentType::ZoneInitialize { segment_type: SegmentType::ZoneInitialize {
player_id: *player_id, player_id: *player_id,
timestamp: timestamp_secs(),
}, },
}) })
.await; .await;
@ -107,6 +109,7 @@ async fn main() {
target_actor: 0, target_actor: 0,
segment_type: SegmentType::ZoneInitialize { segment_type: SegmentType::ZoneInitialize {
player_id: *player_id, player_id: *player_id,
timestamp: timestamp_secs(),
}, },
}) })
.await; .await;
@ -114,12 +117,10 @@ async fn main() {
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::InitializeChat, op_code: IPCOpCode::InitializeChat,
server_id: 0, timestamp: timestamp_secs(),
timestamp: 0,
data: IPCStructData::InitializeChat { unk: [0; 8] }, data: IPCStructData::InitializeChat { unk: [0; 8] },
..Default::default()
}; };
connection connection
@ -146,16 +147,14 @@ async fn main() {
// IPC Init(?) // IPC Init(?)
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0, op_code: IPCOpCode::Unk5,
unk2: 0,
op_code: IPCOpCode::InitResponse,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::InitResponse { data: IPCStructData::InitResponse {
unk1: 0, unk1: 0,
character_id: connection.player_id, character_id: connection.player_id,
unk2: 0, unk2: 0,
}, },
..Default::default()
}; };
connection connection
@ -170,10 +169,7 @@ async fn main() {
// Control Data // Control Data
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::ActorControlSelf, op_code: IPCOpCode::ActorControlSelf,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::ActorControlSelf( data: IPCStructData::ActorControlSelf(
ActorControlSelf { ActorControlSelf {
@ -187,6 +183,7 @@ async fn main() {
param6: 0, param6: 0,
}, },
), ),
..Default::default()
}; };
connection connection
@ -201,10 +198,7 @@ async fn main() {
// Stats // Stats
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PlayerStats, op_code: IPCOpCode::PlayerStats,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PlayerStats(PlayerStats { data: IPCStructData::PlayerStats(PlayerStats {
strength: 1, strength: 1,
@ -212,6 +206,7 @@ async fn main() {
mp: 100, mp: 100,
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
connection connection
@ -226,18 +221,17 @@ async fn main() {
// Player Setup // Player Setup
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PlayerSetup, op_code: IPCOpCode::PlayerSetup,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PlayerSetup(PlayerSetup { data: IPCStructData::PlayerSetup(PlayerSetup {
content_id: CONTENT_ID, content_id: CONTENT_ID,
exp: [10000; 32], exp: [10000; 32],
levels: [100; 32], levels: [100; 32],
name: CHAR_NAME.to_string(), name: CHAR_NAME.to_string(),
char_id: connection.player_id,
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
connection connection
@ -259,15 +253,13 @@ async fn main() {
// send welcome message // send welcome message
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::ServerChatMessage, op_code: IPCOpCode::ServerChatMessage,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::ServerChatMessage { data: IPCStructData::ServerChatMessage {
message: "Welcome to Kawari!".to_string(), message: "Welcome to Kawari!".to_string(),
unk: 0, unk: 0,
}, },
..Default::default()
}; };
connection connection
@ -282,12 +274,10 @@ async fn main() {
// send player spawn // send player spawn
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PlayerSpawn, op_code: IPCOpCode::PlayerSpawn,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PlayerSpawn(PlayerSpawn { data: IPCStructData::PlayerSpawn(PlayerSpawn {
content_id: CONTENT_ID,
current_world_id: WORLD_ID, current_world_id: WORLD_ID,
home_world_id: WORLD_ID, home_world_id: WORLD_ID,
title: 1, title: 1,
@ -298,11 +288,11 @@ async fn main() {
mp_curr: 100, mp_curr: 100,
mp_max: 100, mp_max: 100,
model_type: 1, model_type: 1,
spawn_index: 1,
state: 1, state: 1,
gm_rank: 3, gm_rank: 3,
look: CUSTOMIZE_DATA, look: CUSTOMIZE_DATA,
fc_tag: "LOCAL".to_string(), fc_tag: "LOCAL".to_string(),
subtype: 4,
models: [ models: [
0, // head 0, // head
89, // body 89, // body
@ -319,6 +309,7 @@ async fn main() {
.unwrap_or(Position::default()), .unwrap_or(Position::default()),
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
connection connection
@ -333,14 +324,12 @@ async fn main() {
// fade in? // fade in?
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PrepareZoning, op_code: IPCOpCode::PrepareZoning,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PrepareZoning { data: IPCStructData::PrepareZoning {
unk: [0, 0, 0, 0], unk: [0, 0, 0, 0],
}, },
..Default::default()
}; };
connection connection
@ -373,8 +362,65 @@ async fn main() {
IPCStructData::Unk5 { .. } => { IPCStructData::Unk5 { .. } => {
tracing::info!("Recieved Unk5!"); tracing::info!("Recieved Unk5!");
} }
IPCStructData::Unk6 { .. } => { IPCStructData::SocialListRequest(request) => {
tracing::info!("Recieved Unk6!"); 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 { IPCStructData::Unk7 {
timestamp, unk1, .. timestamp, unk1, ..
@ -384,15 +430,13 @@ async fn main() {
// send unk11 in response // send unk11 in response
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::Unk11, op_code: IPCOpCode::Unk11,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::Unk11 { data: IPCStructData::Unk11 {
timestamp: *timestamp, timestamp: *timestamp,
unk: 333, unk: 333,
}, },
..Default::default()
}; };
connection connection
@ -413,12 +457,10 @@ async fn main() {
// tell the client to disconnect // tell the client to disconnect
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::LogOutComplete, op_code: IPCOpCode::LogOutComplete,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::LogOutComplete { unk: [0; 8] }, data: IPCStructData::LogOutComplete { unk: [0; 8] },
..Default::default()
}; };
connection connection
@ -488,14 +530,12 @@ async fn main() {
// fade out? // fade out?
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PrepareZoning, op_code: IPCOpCode::PrepareZoning,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PrepareZoning { data: IPCStructData::PrepareZoning {
unk: [0x01000000, 0, 0, 0], unk: [0x01000000, 0, 0, 0],
}, },
..Default::default()
}; };
connection connection
@ -510,14 +550,12 @@ async fn main() {
// fade out? x2 // fade out? x2
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::PrepareZoning, op_code: IPCOpCode::PrepareZoning,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::PrepareZoning { data: IPCStructData::PrepareZoning {
unk: [0, 0x00000085, 0x00030000, 0x000008ff], // last thing is probably a float? unk: [0, 0x00000085, 0x00030000, 0x000008ff], // last thing is probably a float?
}, },
..Default::default()
}; };
connection connection

View file

@ -5,7 +5,7 @@ use crate::{
common::{read_string, write_string}, common::{read_string, write_string},
world::{ world::{
ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position, 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) /// 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, InitRequest = 0x2ED,
/// Sent by the server as response to ZoneInitRequest.
InitResponse = 0x1EF,
/// Sent by the server that tells the client which zone to load /// Sent by the server that tells the client which zone to load
InitZone = 0x0311, InitZone = 0x0311,
/// Sent by the server for... something /// 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 // FIXME: 32 bytes of something from the client, not sure what yet
Unk1 = 0x37C, Unk1 = 0x37C,
// FIXME: 16 bytes of something from the client, not sure what yet // 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 // FIXME: 8 bytes of something from the client, not sure what yet
Unk3 = 0x326, Unk3 = 0x326,
// FIXME: 8 bytes of something from the client, not sure what yet // FIXME: 8 bytes of something from the client, not sure what yet
Unk4 = 0x143, Unk4 = 0x143,
SetSearchInfoHandler = 0x3B2, // TODO: assumed, SetSearchInfoHandler = 0x3B2, // TODO: assumed,
// FIXME: 8 bytes of something from the client, not sure what yet // FIXME: 8 bytes of something from the client, not sure what yet
/// ALSO Sent by the server as response to ZoneInitRequest.
Unk5 = 0x2D0, Unk5 = 0x2D0,
// FIXME: 8 bytes of something from the client, not sure what yet // Sent by the client when it requests the friends list and other related info
Unk6 = 0x2E5, SocialListRequest = 0x1A1,
// FIXME: 32 bytes of something from the client, not sure what yet // FIXME: 32 bytes of something from the client, not sure what yet
Unk7 = 0x2B5, Unk7 = 0x2B5,
UpdatePositionHandler = 0x249, // TODO: assumed UpdatePositionHandler = 0x249, // TODO: assumed
@ -109,6 +108,18 @@ pub enum IPCOpCode {
PrepareZoning = 0x308, PrepareZoning = 0x308,
// Sent by the client for unknown reasons // Sent by the client for unknown reasons
Unk14 = 0x87, 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] #[binrw]
@ -313,11 +324,8 @@ pub enum IPCStructData {
// TODO: full of possibly interesting information // TODO: full of possibly interesting information
unk: [u8; 8], unk: [u8; 8],
}, },
#[br(pre_assert(*magic == IPCOpCode::Unk6))] #[br(pre_assert(*magic == IPCOpCode::SocialListRequest))]
Unk6 { SocialListRequest(SocialListRequest),
// TODO: full of possibly interesting information
unk: [u8; 8],
},
#[br(pre_assert(*magic == IPCOpCode::Unk7))] #[br(pre_assert(*magic == IPCOpCode::Unk7))]
Unk7 { Unk7 {
// TODO: full of possibly interesting information // TODO: full of possibly interesting information
@ -523,6 +531,24 @@ pub enum IPCStructData {
}, },
#[br(pre_assert(false))] #[br(pre_assert(false))]
PrepareZoning { unk: [u32; 4] }, 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] #[binrw]
@ -542,6 +568,22 @@ pub struct IPCSegment {
pub data: IPCStructData, 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 { impl IPCSegment {
pub fn calc_size(&self) -> u32 { pub fn calc_size(&self) -> u32 {
let header = 16; let header = 16;
@ -562,7 +604,7 @@ impl IPCSegment {
IPCStructData::InitZone { .. } => 103, IPCStructData::InitZone { .. } => 103,
IPCStructData::ActorControlSelf { .. } => 32, IPCStructData::ActorControlSelf { .. } => 32,
IPCStructData::PlayerStats { .. } => 224, IPCStructData::PlayerStats { .. } => 224,
IPCStructData::PlayerSetup { .. } => 2545, IPCStructData::PlayerSetup { .. } => 2784,
IPCStructData::UpdateClassInfo { .. } => 48, IPCStructData::UpdateClassInfo { .. } => 48,
IPCStructData::FinishLoading { .. } => todo!(), IPCStructData::FinishLoading { .. } => todo!(),
IPCStructData::PlayerSpawn { .. } => 656, IPCStructData::PlayerSpawn { .. } => 656,
@ -572,7 +614,7 @@ impl IPCSegment {
IPCStructData::Unk4 { .. } => todo!(), IPCStructData::Unk4 { .. } => todo!(),
IPCStructData::SetSearchInfoHandler { .. } => todo!(), IPCStructData::SetSearchInfoHandler { .. } => todo!(),
IPCStructData::Unk5 { .. } => todo!(), IPCStructData::Unk5 { .. } => todo!(),
IPCStructData::Unk6 { .. } => todo!(), IPCStructData::SocialListRequest { .. } => todo!(),
IPCStructData::Unk7 { .. } => todo!(), IPCStructData::Unk7 { .. } => todo!(),
IPCStructData::UpdatePositionHandler { .. } => todo!(), IPCStructData::UpdatePositionHandler { .. } => todo!(),
IPCStructData::LogOut { .. } => todo!(), IPCStructData::LogOut { .. } => todo!(),
@ -594,6 +636,12 @@ impl IPCSegment {
IPCStructData::Unk13 { .. } => todo!(), IPCStructData::Unk13 { .. } => todo!(),
IPCStructData::PrepareZoning { .. } => 16, IPCStructData::PrepareZoning { .. } => 16,
IPCStructData::Unk14 { .. } => todo!(), IPCStructData::Unk14 { .. } => todo!(),
IPCStructData::Unk15 { .. } => 8,
IPCStructData::Unk16 { .. } => 136,
IPCStructData::ActorControl { .. } => 24,
IPCStructData::ActorMove { .. } => 16,
IPCStructData::Unk17 { .. } => 104,
IPCStructData::SocialList { .. } => 1136,
} }
} }
} }

View file

@ -69,8 +69,9 @@ pub enum SegmentType {
KeepAliveResponse { id: u32, timestamp: u32 }, KeepAliveResponse { id: u32, timestamp: u32 },
#[brw(magic = 0x2u32)] #[brw(magic = 0x2u32)]
ZoneInitialize { ZoneInitialize {
#[brw(pad_after = 36)]
player_id: u32, player_id: u32,
#[brw(pad_after = 32)]
timestamp: u32,
}, },
} }

View file

@ -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}; use super::{ChatMessage, Position, ZoneConnection};
pub struct ChatHandler {} pub struct ChatHandler {}
@ -21,6 +33,61 @@ impl ChatHandler {
}) })
.await; .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!"), _ => tracing::info!("Unrecognized debug command!"),
} }
} }

View file

@ -1,3 +1,6 @@
use std::io::Cursor;
use binrw::BinRead;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use crate::{ use crate::{
@ -19,6 +22,7 @@ pub struct ZoneConnection {
pub player_id: u32, pub player_id: u32,
pub zone: Zone, pub zone: Zone,
pub spawn_index: u8,
} }
impl ZoneConnection { impl ZoneConnection {
@ -40,16 +44,14 @@ impl ZoneConnection {
// set pos // set pos
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 14,
unk2: 0,
op_code: IPCOpCode::ActorSetPos, op_code: IPCOpCode::ActorSetPos,
server_id: WORLD_ID,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::ActorSetPos(ActorSetPos { data: IPCStructData::ActorSetPos(ActorSetPos {
unk: 0x020fa3b8, unk: 0x020fa3b8,
position, position,
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
let response_packet = PacketSegment { let response_packet = PacketSegment {
@ -73,10 +75,7 @@ impl ZoneConnection {
// Player Class Info // Player Class Info
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::UpdateClassInfo, op_code: IPCOpCode::UpdateClassInfo,
server_id: 69, // lol
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::UpdateClassInfo(UpdateClassInfo { data: IPCStructData::UpdateClassInfo(UpdateClassInfo {
class_id: 35, class_id: 35,
@ -85,6 +84,7 @@ impl ZoneConnection {
class_level: 90, class_level: 90,
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
self.send_segment(PacketSegment { self.send_segment(PacketSegment {
@ -95,56 +95,13 @@ impl ZoneConnection {
.await; .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 // link shell information
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::LinkShellInformation, op_code: IPCOpCode::LinkShellInformation,
server_id: 69, // lol
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::LinkShellInformation { unk: [0; 456] }, data: IPCStructData::LinkShellInformation { unk: [0; 456] },
..Default::default()
}; };
self.send_segment(PacketSegment { self.send_segment(PacketSegment {
@ -155,39 +112,20 @@ impl ZoneConnection {
.await; .await;
} }
// unk8 // TODO: send unk16?
{
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;
}
// Init Zone // Init Zone
{ {
let ipc = IPCSegment { let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::InitZone, op_code: IPCOpCode::InitZone,
server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: IPCStructData::InitZone(InitZone { data: IPCStructData::InitZone(InitZone {
server_id: WORLD_ID, server_id: 0,
zone_id: self.zone.id, zone_id: self.zone.id,
weather_id: 1, weather_id: 1,
..Default::default() ..Default::default()
}), }),
..Default::default()
}; };
self.send_segment(PacketSegment { self.send_segment(PacketSegment {
@ -198,4 +136,9 @@ impl ZoneConnection {
.await; .await;
} }
} }
pub fn get_free_spawn_index(&mut self) -> u8 {
self.spawn_index += 1;
return self.spawn_index;
}
} }

View file

@ -11,7 +11,6 @@ pub struct InitZone {
pub content_finder_condition_id: u16, pub content_finder_condition_id: u16,
pub layer_set_id: u32, pub layer_set_id: u32,
pub layout_id: u32, pub layout_id: u32,
#[br(dbg)]
pub weather_id: u16, // index into Weather sheet probably? pub weather_id: u16, // index into Weather sheet probably?
pub unk_really: u16, pub unk_really: u16,
pub unk_bitmask1: u8, pub unk_bitmask1: u8,

View file

@ -34,3 +34,9 @@ pub use connection::ZoneConnection;
mod chat_message; mod chat_message;
pub use chat_message::ChatMessage; 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;

View file

@ -82,7 +82,9 @@ pub struct PlayerSetup {
#[bw(pad_size_to = 33)] #[bw(pad_size_to = 33)]
pub mount_guide_mask: Vec<u8>, pub mount_guide_mask: Vec<u8>,
pub ornament_mask: [u8; 4], pub ornament_mask: [u8; 4],
pub unknown281: [u8; 23], #[br(count = 85)]
#[bw(pad_size_to = 85)]
pub unknown281: Vec<u8>,
#[br(count = CHAR_NAME_MAX_LENGTH)] #[br(count = CHAR_NAME_MAX_LENGTH)]
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)] #[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
#[br(map = read_string)] #[br(map = read_string)]

View file

@ -11,7 +11,11 @@ use super::status_effect::StatusEffect;
#[brw(little)] #[brw(little)]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct PlayerSpawn { 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 title: u16,
pub u1b: u16, pub u1b: u16,
@ -121,6 +125,7 @@ mod tests {
assert_eq!(player_spawn.mp_curr, 10000); assert_eq!(player_spawn.mp_curr, 10000);
assert_eq!(player_spawn.mp_max, 10000); assert_eq!(player_spawn.mp_max, 10000);
assert_eq!(player_spawn.state, 1); assert_eq!(player_spawn.state, 1);
assert_eq!(player_spawn.spawn_index, 0);
assert_eq!(player_spawn.level, 1); assert_eq!(player_spawn.level, 1);
assert_eq!(player_spawn.class_job, 1); // adventurer assert_eq!(player_spawn.class_job, 1); // adventurer
assert_eq!(player_spawn.scale, 36); assert_eq!(player_spawn.scale, 36);
@ -132,5 +137,6 @@ mod tests {
assert_eq!(player_spawn.look.gender, 1); assert_eq!(player_spawn.look.gender, 1);
assert_eq!(player_spawn.look.bust, 100); assert_eq!(player_spawn.look.bust, 100);
assert_eq!(player_spawn.fc_tag, ""); assert_eq!(player_spawn.fc_tag, "");
assert_eq!(player_spawn.subtype, 4);
} }
} }

62
src/world/social_list.rs Normal file
View file

@ -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<PlayerEntry>,
}

View file

@ -22,7 +22,9 @@ impl Zone {
GameData::from_existing(Platform::Win32, &config.game_location).unwrap(); GameData::from_existing(Platform::Win32, &config.game_location).unwrap();
let exh = game_data.read_excel_sheet_header("TerritoryType").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]; let territory_type_row = &exd.read_row(&exh, id as u32).unwrap()[0];
@ -31,7 +33,10 @@ impl Zone {
panic!("Unexpected type!"); 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 lgb = game_data.extract(&path).unwrap();
let layer_group = LayerGroup::from_existing(&lgb).unwrap(); let layer_group = LayerGroup::from_existing(&lgb).unwrap();
Self { id, layer_group } Self { id, layer_group }