mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-27 08:57:45 +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:
parent
1cbc5c72b9
commit
51f6ad6744
11 changed files with 311 additions and 134 deletions
|
@ -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
|
||||
|
|
74
src/ipc.rs
74
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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!"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -82,7 +82,9 @@ pub struct PlayerSetup {
|
|||
#[bw(pad_size_to = 33)]
|
||||
pub mount_guide_mask: Vec<u8>,
|
||||
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)]
|
||||
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
||||
#[br(map = read_string)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
62
src/world/social_list.rs
Normal file
62
src/world/social_list.rs
Normal 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>,
|
||||
}
|
|
@ -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 }
|
||||
|
|
Loading…
Add table
Reference in a new issue