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:
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::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
|
||||||
|
|
74
src/ipc.rs
74
src/ipc.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
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();
|
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 }
|
||||||
|
|
Loading…
Add table
Reference in a new issue