mirror of
https://github.com/redstrate/Kawari.git
synced 2025-06-08 17:37:46 +00:00
Re-organize the IPC structures so they live in their own server-specific module
My old setup of throwing *all* of the IPC types and opcodes into *one* enum was becoming unbearable. Now that we have multiple things using the same opcodes (because they can overlap) I think it's time to repay this technical debt. This overhauls the structure of the project to move IPC structs into their own modules, and separate the opcode data/lists into separate ones depending on if it's clientbound and serverbound. Nothing has changed functionall, but this is going to make it way easier to add more IPC in the future.
This commit is contained in:
parent
726d351f8b
commit
f5d75301b2
31 changed files with 1217 additions and 983 deletions
|
@ -1,9 +1,12 @@
|
||||||
use kawari::CONTENT_ID;
|
use kawari::CONTENT_ID;
|
||||||
use kawari::ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, LobbyCharacterAction};
|
|
||||||
use kawari::lobby::chara_make::CharaMake;
|
use kawari::lobby::chara_make::CharaMake;
|
||||||
use kawari::lobby::connection::LobbyConnection;
|
use kawari::lobby::connection::LobbyConnection;
|
||||||
|
use kawari::lobby::ipc::{
|
||||||
|
CharacterDetails, ClientLobbyIpcData, LobbyCharacterAction, ServerLobbyIpcData,
|
||||||
|
ServerLobbyIpcSegment, ServerLobbyIpcType,
|
||||||
|
};
|
||||||
use kawari::oodle::FFXIVOodle;
|
use kawari::oodle::FFXIVOodle;
|
||||||
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
|
use kawari::packet::{PacketSegment, PacketState, SegmentType, send_keep_alive};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
@ -18,7 +21,7 @@ async fn main() {
|
||||||
loop {
|
loop {
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
let (socket, _) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
let state = State {
|
let state = PacketState {
|
||||||
client_key: None,
|
client_key: None,
|
||||||
session_id: None,
|
session_id: None,
|
||||||
clientbound_oodle: FFXIVOodle::new(),
|
clientbound_oodle: FFXIVOodle::new(),
|
||||||
|
@ -46,10 +49,10 @@ async fn main() {
|
||||||
connection.initialize_encryption(phrase, key).await;
|
connection.initialize_encryption(phrase, key).await;
|
||||||
}
|
}
|
||||||
SegmentType::Ipc { data } => match &data.data {
|
SegmentType::Ipc { data } => match &data.data {
|
||||||
IPCStructData::ClientVersionInfo {
|
ClientLobbyIpcData::ClientVersionInfo {
|
||||||
sequence,
|
|
||||||
session_id,
|
session_id,
|
||||||
version_info,
|
version_info,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Client {session_id} ({version_info}) logging in!"
|
"Client {session_id} ({version_info}) logging in!"
|
||||||
|
@ -62,13 +65,16 @@ async fn main() {
|
||||||
// request an update
|
// request an update
|
||||||
//connection.send_error(*sequence, 1012, 13101).await;
|
//connection.send_error(*sequence, 1012, 13101).await;
|
||||||
}
|
}
|
||||||
IPCStructData::RequestCharacterList { sequence } => {
|
ClientLobbyIpcData::RequestCharacterList { sequence } => {
|
||||||
tracing::info!("Client is requesting character list...");
|
tracing::info!("Client is requesting character list...");
|
||||||
|
|
||||||
connection.send_lobby_info(*sequence).await;
|
connection.send_lobby_info(*sequence).await;
|
||||||
}
|
}
|
||||||
IPCStructData::LobbyCharacterAction {
|
ClientLobbyIpcData::LobbyCharacterAction {
|
||||||
action, name, json, ..
|
action,
|
||||||
|
name,
|
||||||
|
json,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
match action {
|
match action {
|
||||||
LobbyCharacterAction::ReserveName => {
|
LobbyCharacterAction::ReserveName => {
|
||||||
|
@ -84,7 +90,7 @@ async fn main() {
|
||||||
op_code: IPCOpCode::InitializeChat, // wrong but technically right
|
op_code: IPCOpCode::InitializeChat, // wrong but technically right
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
data: IPCStructData::NameRejection {
|
data: ClientLobbyIpcType::NameRejection {
|
||||||
unk1: 0x03,
|
unk1: 0x03,
|
||||||
unk2: 0x0bdb,
|
unk2: 0x0bdb,
|
||||||
unk3: 0x000132cc,
|
unk3: 0x000132cc,
|
||||||
|
@ -107,13 +113,13 @@ async fn main() {
|
||||||
|
|
||||||
// accept
|
// accept
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::CharacterCreated,
|
op_code: ServerLobbyIpcType::CharacterCreated,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
data: IPCStructData::CharacterCreated {
|
data: ServerLobbyIpcData::CharacterCreated {
|
||||||
unk1: 0x4,
|
unk1: 0x4,
|
||||||
unk2: 0x00010101,
|
unk2: 0x00010101,
|
||||||
details: CharacterDetails {
|
details: CharacterDetails {
|
||||||
|
@ -147,13 +153,13 @@ async fn main() {
|
||||||
|
|
||||||
// a slightly different character created packet now
|
// a slightly different character created packet now
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::CharacterCreated,
|
op_code: ServerLobbyIpcType::CharacterCreated,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
data: IPCStructData::CharacterCreated {
|
data: ServerLobbyIpcData::CharacterCreated {
|
||||||
unk1: 0x5,
|
unk1: 0x5,
|
||||||
unk2: 0x00020101,
|
unk2: 0x00020101,
|
||||||
details: CharacterDetails {
|
details: CharacterDetails {
|
||||||
|
@ -192,7 +198,7 @@ async fn main() {
|
||||||
LobbyCharacterAction::Request => todo!(),
|
LobbyCharacterAction::Request => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::RequestEnterWorld {
|
ClientLobbyIpcData::RequestEnterWorld {
|
||||||
sequence,
|
sequence,
|
||||||
lookup_id,
|
lookup_id,
|
||||||
} => {
|
} => {
|
||||||
|
@ -200,12 +206,9 @@ async fn main() {
|
||||||
|
|
||||||
connection.send_enter_world(*sequence, *lookup_id).await;
|
connection.send_enter_world(*sequence, *lookup_id).await;
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
panic!("The server is recieving a IPC response packet!")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
SegmentType::KeepAlive { id, timestamp } => {
|
SegmentType::KeepAlive { id, timestamp } => {
|
||||||
send_keep_alive(
|
send_keep_alive::<ServerLobbyIpcSegment>(
|
||||||
&mut connection.socket,
|
&mut connection.socket,
|
||||||
&mut connection.state,
|
&mut connection.state,
|
||||||
*id,
|
*id,
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
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, PacketState, SegmentType, send_keep_alive};
|
||||||
|
use kawari::world::ipc::{
|
||||||
|
ClientZoneIpcData, GameMasterCommandType, ServerZoneIpcData, ServerZoneIpcSegment,
|
||||||
|
ServerZoneIpcType, SocialListRequestType,
|
||||||
|
};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ActorControlSelf, ActorControlType, ChatHandler, PlayerEntry, PlayerSetup, PlayerSpawn,
|
ChatHandler, Zone, ZoneConnection,
|
||||||
PlayerStats, Position, SocialList, Zone, ZoneConnection,
|
ipc::{
|
||||||
|
ActorControlSelf, ActorControlType, PlayerEntry, PlayerSetup, PlayerSpawn, PlayerStats,
|
||||||
|
Position, SocialList,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use kawari::{
|
use kawari::{
|
||||||
CHAR_NAME, CITY_STATE, CONTENT_ID, CUSTOMIZE_DATA, DEITY, NAMEDAY_DAY, NAMEDAY_MONTH, WORLD_ID,
|
CHAR_NAME, CITY_STATE, CONTENT_ID, CUSTOMIZE_DATA, DEITY, NAMEDAY_DAY, NAMEDAY_MONTH, WORLD_ID,
|
||||||
|
@ -25,7 +31,7 @@ async fn main() {
|
||||||
loop {
|
loop {
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
let (socket, _) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
let state = State {
|
let state = PacketState {
|
||||||
client_key: None,
|
client_key: None,
|
||||||
session_id: None,
|
session_id: None,
|
||||||
clientbound_oodle: FFXIVOodle::new(),
|
clientbound_oodle: FFXIVOodle::new(),
|
||||||
|
@ -116,10 +122,12 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::InitializeChat,
|
op_code: ServerZoneIpcType::InitializeChat,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::InitializeChat { unk: [0; 8] },
|
data: ServerZoneIpcData::InitializeChat {
|
||||||
|
unk: [0; 8],
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -139,17 +147,17 @@ async fn main() {
|
||||||
}
|
}
|
||||||
SegmentType::Ipc { data } => {
|
SegmentType::Ipc { data } => {
|
||||||
match &data.data {
|
match &data.data {
|
||||||
IPCStructData::InitRequest { .. } => {
|
ClientZoneIpcData::InitRequest { .. } => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Client is now requesting zone information. Sending!"
|
"Client is now requesting zone information. Sending!"
|
||||||
);
|
);
|
||||||
|
|
||||||
// IPC Init(?)
|
// IPC Init(?)
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::Unk5,
|
op_code: ServerZoneIpcType::InitResponse,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::InitResponse {
|
data: ServerZoneIpcData::InitResponse {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
character_id: connection.player_id,
|
character_id: connection.player_id,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
|
@ -168,10 +176,10 @@ async fn main() {
|
||||||
|
|
||||||
// Control Data
|
// Control Data
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::ActorControlSelf,
|
op_code: ServerZoneIpcType::ActorControlSelf,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::ActorControlSelf(
|
data: ServerZoneIpcData::ActorControlSelf(
|
||||||
ActorControlSelf {
|
ActorControlSelf {
|
||||||
category:
|
category:
|
||||||
ActorControlType::SetCharaGearParamUI,
|
ActorControlType::SetCharaGearParamUI,
|
||||||
|
@ -197,10 +205,10 @@ async fn main() {
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PlayerStats,
|
op_code: ServerZoneIpcType::PlayerStats,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PlayerStats(PlayerStats {
|
data: ServerZoneIpcData::PlayerStats(PlayerStats {
|
||||||
strength: 1,
|
strength: 1,
|
||||||
hp: 100,
|
hp: 100,
|
||||||
mp: 100,
|
mp: 100,
|
||||||
|
@ -220,10 +228,10 @@ async fn main() {
|
||||||
|
|
||||||
// Player Setup
|
// Player Setup
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PlayerSetup,
|
op_code: ServerZoneIpcType::PlayerSetup,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PlayerSetup(PlayerSetup {
|
data: ServerZoneIpcData::PlayerSetup(PlayerSetup {
|
||||||
content_id: CONTENT_ID,
|
content_id: CONTENT_ID,
|
||||||
exp: [10000; 32],
|
exp: [10000; 32],
|
||||||
levels: [100; 32],
|
levels: [100; 32],
|
||||||
|
@ -254,10 +262,10 @@ async fn main() {
|
||||||
|
|
||||||
// send welcome message
|
// send welcome message
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::ServerChatMessage,
|
op_code: ServerZoneIpcType::ServerChatMessage,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::ServerChatMessage {
|
data: ServerZoneIpcData::ServerChatMessage {
|
||||||
message: "Welcome to Kawari!".to_string(),
|
message: "Welcome to Kawari!".to_string(),
|
||||||
unk: 0,
|
unk: 0,
|
||||||
},
|
},
|
||||||
|
@ -273,17 +281,17 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::FinishLoading { .. } => {
|
ClientZoneIpcData::FinishLoading { .. } => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Client has finished loading... spawning in!"
|
"Client has finished loading... spawning in!"
|
||||||
);
|
);
|
||||||
|
|
||||||
// send player spawn
|
// send player spawn
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PlayerSpawn,
|
op_code: ServerZoneIpcType::PlayerSpawn,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PlayerSpawn(PlayerSpawn {
|
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
||||||
content_id: CONTENT_ID,
|
content_id: CONTENT_ID,
|
||||||
current_world_id: WORLD_ID,
|
current_world_id: WORLD_ID,
|
||||||
home_world_id: WORLD_ID,
|
home_world_id: WORLD_ID,
|
||||||
|
@ -329,10 +337,10 @@ async fn main() {
|
||||||
|
|
||||||
// fade in?
|
// fade in?
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PrepareZoning,
|
op_code: ServerZoneIpcType::PrepareZoning,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PrepareZoning {
|
data: ServerZoneIpcData::PrepareZoning {
|
||||||
unk: [0, 0, 0, 0],
|
unk: [0, 0, 0, 0],
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -350,33 +358,34 @@ async fn main() {
|
||||||
// wipe any exit position so it isn't accidentally reused
|
// wipe any exit position so it isn't accidentally reused
|
||||||
exit_position = None;
|
exit_position = None;
|
||||||
}
|
}
|
||||||
IPCStructData::Unk1 { .. } => {
|
ClientZoneIpcData::Unk1 { .. } => {
|
||||||
tracing::info!("Recieved Unk1!");
|
tracing::info!("Recieved Unk1!");
|
||||||
}
|
}
|
||||||
IPCStructData::Unk2 { .. } => {
|
ClientZoneIpcData::Unk2 { .. } => {
|
||||||
tracing::info!("Recieved Unk2!");
|
tracing::info!("Recieved Unk2!");
|
||||||
}
|
}
|
||||||
IPCStructData::Unk3 { .. } => {
|
ClientZoneIpcData::Unk3 { .. } => {
|
||||||
tracing::info!("Recieved Unk3!");
|
tracing::info!("Recieved Unk3!");
|
||||||
}
|
}
|
||||||
IPCStructData::Unk4 { .. } => {
|
ClientZoneIpcData::Unk4 { .. } => {
|
||||||
tracing::info!("Recieved Unk4!");
|
tracing::info!("Recieved Unk4!");
|
||||||
}
|
}
|
||||||
IPCStructData::SetSearchInfoHandler { .. } => {
|
ClientZoneIpcData::SetSearchInfoHandler { .. } => {
|
||||||
tracing::info!("Recieved SetSearchInfoHandler!");
|
tracing::info!("Recieved SetSearchInfoHandler!");
|
||||||
}
|
}
|
||||||
IPCStructData::Unk5 { .. } => {
|
ClientZoneIpcData::Unk5 { .. } => {
|
||||||
tracing::info!("Recieved Unk5!");
|
tracing::info!("Recieved Unk5!");
|
||||||
}
|
}
|
||||||
IPCStructData::SocialListRequest(request) => {
|
ClientZoneIpcData::SocialListRequest(request) => {
|
||||||
tracing::info!("Recieved social list request!");
|
tracing::info!("Recieved social list request!");
|
||||||
|
|
||||||
match &request.request_type {
|
match &request.request_type {
|
||||||
kawari::world::SocialListRequestType::Party => {
|
SocialListRequestType::Party => {
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::SocialList,
|
op_code: ServerZoneIpcType::SocialList,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::SocialList(SocialList {
|
data: ServerZoneIpcData::SocialList(
|
||||||
|
SocialList {
|
||||||
request_type: request.request_type,
|
request_type: request.request_type,
|
||||||
sequence: request.count,
|
sequence: request.count,
|
||||||
entries: vec![PlayerEntry {
|
entries: vec![PlayerEntry {
|
||||||
|
@ -390,7 +399,8 @@ async fn main() {
|
||||||
fc_tag: "LOCAL".to_string(),
|
fc_tag: "LOCAL".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -404,15 +414,17 @@ async fn main() {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
kawari::world::SocialListRequestType::Friends => {
|
SocialListRequestType::Friends => {
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::SocialList,
|
op_code: ServerZoneIpcType::SocialList,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::SocialList(SocialList {
|
data: ServerZoneIpcData::SocialList(
|
||||||
|
SocialList {
|
||||||
request_type: request.request_type,
|
request_type: request.request_type,
|
||||||
sequence: request.count,
|
sequence: request.count,
|
||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -428,17 +440,17 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::Unk7 {
|
ClientZoneIpcData::Unk7 {
|
||||||
timestamp, unk1, ..
|
timestamp, unk1, ..
|
||||||
} => {
|
} => {
|
||||||
tracing::info!("Recieved Unk7! {:#?}", unk1);
|
tracing::info!("Recieved Unk7! {:#?}", unk1);
|
||||||
|
|
||||||
// send unk11 in response
|
// send unk11 in response
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::Unk11,
|
op_code: ServerZoneIpcType::Unk11,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::Unk11 {
|
data: ServerZoneIpcData::Unk11 {
|
||||||
timestamp: *timestamp,
|
timestamp: *timestamp,
|
||||||
unk: 333,
|
unk: 333,
|
||||||
},
|
},
|
||||||
|
@ -454,18 +466,20 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::UpdatePositionHandler { .. } => {
|
ClientZoneIpcData::UpdatePositionHandler { .. } => {
|
||||||
tracing::info!("Recieved UpdatePositionHandler!");
|
tracing::info!("Recieved UpdatePositionHandler!");
|
||||||
}
|
}
|
||||||
IPCStructData::LogOut { .. } => {
|
ClientZoneIpcData::LogOut { .. } => {
|
||||||
tracing::info!("Recieved log out from client!");
|
tracing::info!("Recieved log out from client!");
|
||||||
|
|
||||||
// tell the client to disconnect
|
// tell the client to disconnect
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::LogOutComplete,
|
op_code: ServerZoneIpcType::LogOutComplete,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::LogOutComplete { unk: [0; 8] },
|
data: ServerZoneIpcData::LogOutComplete {
|
||||||
|
unk: [0; 8],
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -478,17 +492,19 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::Disconnected { .. } => {
|
ClientZoneIpcData::Disconnected { .. } => {
|
||||||
tracing::info!("Client disconnected!");
|
tracing::info!("Client disconnected!");
|
||||||
}
|
}
|
||||||
IPCStructData::ChatMessage(chat_message) => {
|
ClientZoneIpcData::ChatMessage(chat_message) => {
|
||||||
ChatHandler::handle_chat_message(
|
ChatHandler::handle_chat_message(
|
||||||
&mut connection,
|
&mut connection,
|
||||||
chat_message,
|
chat_message,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
IPCStructData::GameMasterCommand { command, arg, .. } => {
|
ClientZoneIpcData::GameMasterCommand {
|
||||||
|
command, arg, ..
|
||||||
|
} => {
|
||||||
tracing::info!("Got a game master command!");
|
tracing::info!("Got a game master command!");
|
||||||
|
|
||||||
match &command {
|
match &command {
|
||||||
|
@ -497,10 +513,10 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::Unk12 { .. } => {
|
ClientZoneIpcData::Unk12 { .. } => {
|
||||||
tracing::info!("Recieved Unk12!");
|
tracing::info!("Recieved Unk12!");
|
||||||
}
|
}
|
||||||
IPCStructData::EnterZoneLine {
|
ClientZoneIpcData::EnterZoneLine {
|
||||||
exit_box_id,
|
exit_box_id,
|
||||||
position,
|
position,
|
||||||
..
|
..
|
||||||
|
@ -535,10 +551,10 @@ async fn main() {
|
||||||
|
|
||||||
// fade out?
|
// fade out?
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PrepareZoning,
|
op_code: ServerZoneIpcType::PrepareZoning,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PrepareZoning {
|
data: ServerZoneIpcData::PrepareZoning {
|
||||||
unk: [0x01000000, 0, 0, 0],
|
unk: [0x01000000, 0, 0, 0],
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -555,10 +571,10 @@ async fn main() {
|
||||||
|
|
||||||
// fade out? x2
|
// fade out? x2
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::PrepareZoning,
|
op_code: ServerZoneIpcType::PrepareZoning,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PrepareZoning {
|
data: ServerZoneIpcData::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()
|
..Default::default()
|
||||||
|
@ -577,19 +593,16 @@ async fn main() {
|
||||||
|
|
||||||
connection.change_zone(new_territory).await;
|
connection.change_zone(new_territory).await;
|
||||||
}
|
}
|
||||||
IPCStructData::Unk13 { .. } => {
|
ClientZoneIpcData::Unk13 { .. } => {
|
||||||
tracing::info!("Recieved Unk13!");
|
tracing::info!("Recieved Unk13!");
|
||||||
}
|
}
|
||||||
IPCStructData::Unk14 { .. } => {
|
ClientZoneIpcData::Unk14 { .. } => {
|
||||||
tracing::info!("Recieved Unk14!");
|
tracing::info!("Recieved Unk14!");
|
||||||
}
|
}
|
||||||
_ => panic!(
|
|
||||||
"The server is recieving a IPC response or unknown packet!"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SegmentType::KeepAlive { id, timestamp } => {
|
SegmentType::KeepAlive { id, timestamp } => {
|
||||||
send_keep_alive(
|
send_keep_alive::<ServerZoneIpcSegment>(
|
||||||
&mut connection.socket,
|
&mut connection.socket,
|
||||||
&mut connection.state,
|
&mut connection.state,
|
||||||
*id,
|
*id,
|
||||||
|
|
732
src/ipc.rs
732
src/ipc.rs
|
@ -1,732 +0,0 @@
|
||||||
use binrw::binrw;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
CHAR_NAME_MAX_LENGTH,
|
|
||||||
common::{read_string, write_string},
|
|
||||||
world::{
|
|
||||||
ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
|
||||||
SocialList, SocialListRequest, UpdateClassInfo,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOTE: See https://github.com/karashiiro/FFXIVOpcodes/blob/master/FFXIVOpcodes/Ipcs.cs for opcodes
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(repr = u16)]
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum IPCOpCode {
|
|
||||||
/// Sent by the server to Initialize something chat-related?
|
|
||||||
InitializeChat = 0x2,
|
|
||||||
/// Sent by the client when it requests the character list in the lobby.
|
|
||||||
RequestCharacterList = 0x3,
|
|
||||||
/// Sent by the client when it requests to enter a world.
|
|
||||||
RequestEnterWorld = 0x4,
|
|
||||||
/// Sent by the client after exchanging encryption information with the lobby server.
|
|
||||||
ClientVersionInfo = 0x5,
|
|
||||||
/// Sent by the client when they request something about the character (e.g. deletion.)
|
|
||||||
LobbyCharacterAction = 0xB,
|
|
||||||
/// Sent by the server to inform the client of their service accounts.
|
|
||||||
LobbyServiceAccountList = 0xC,
|
|
||||||
/// Sent by the server to inform the client of their characters.
|
|
||||||
LobbyCharacterList = 0xD,
|
|
||||||
/// Sent by the server to tell the client how to connect to the world server.
|
|
||||||
LobbyEnterWorld = 0xF,
|
|
||||||
/// Sent by the server to inform the client of their servers.
|
|
||||||
LobbyServerList = 0x15,
|
|
||||||
/// Sent by the server to inform the client of their retainers.
|
|
||||||
LobbyRetainerList = 0x17,
|
|
||||||
|
|
||||||
/// 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 that tells the client which zone to load
|
|
||||||
InitZone = 0x0311,
|
|
||||||
/// Sent by the server for... something
|
|
||||||
ActorControlSelf = 0x018C,
|
|
||||||
/// Sent by the server containing character stats
|
|
||||||
PlayerStats = 0x01FA,
|
|
||||||
/// Sent by the server to setup the player on the client
|
|
||||||
PlayerSetup = 0x006B,
|
|
||||||
// Sent by the server to setup class info
|
|
||||||
UpdateClassInfo = 0x006A,
|
|
||||||
// Sent by the client when they're done loading and they need to be spawned in
|
|
||||||
FinishLoading = 0x397, // TODO: assumed
|
|
||||||
// Sent by the server to spawn the player in
|
|
||||||
PlayerSpawn = 0x1AB,
|
|
||||||
|
|
||||||
// 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 = 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,
|
|
||||||
// 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
|
|
||||||
// Sent by the client when the user requests to log out
|
|
||||||
LogOut = 0x217,
|
|
||||||
// Sent by the server to indicate the log out is complete
|
|
||||||
LogOutComplete = 0x369,
|
|
||||||
// Sent by the client when it's actually disconnecting
|
|
||||||
Disconnected = 0x360,
|
|
||||||
// Sent by the client when they send a chat message
|
|
||||||
ChatMessage = 0xCA,
|
|
||||||
// Sent by the client when they send a GM command. This can only be sent by the client if they are sent a GM rank.
|
|
||||||
GameMasterCommand = 0x3B3,
|
|
||||||
// Sent by the server to modify the client's position
|
|
||||||
ActorSetPos = 0x223,
|
|
||||||
// Sent by the server when they send a chat message
|
|
||||||
ServerChatMessage = 0x196,
|
|
||||||
// Unknown, server sends to the client before player spawn
|
|
||||||
Unk8 = 0x134,
|
|
||||||
// Unknown, but seems to contain information on cross-world linkshells
|
|
||||||
LinkShellInformation = 0x234,
|
|
||||||
// Unknown, server sends to the client before player spawn
|
|
||||||
Unk9 = 0x189,
|
|
||||||
// Unknown, server sends to the client before player spawn.
|
|
||||||
// Seems to the same across two different characters?
|
|
||||||
Unk10 = 0x110,
|
|
||||||
// Unknown, server sends this in response to Unk7
|
|
||||||
Unk11 = 0x156,
|
|
||||||
// Assumed what this is, but probably incorrect
|
|
||||||
CharacterCreated = 0xE,
|
|
||||||
// Unknown, client sends this for ???
|
|
||||||
Unk12 = 0x0E9,
|
|
||||||
// Sent by the client when the character walks into a zone transistion
|
|
||||||
EnterZoneLine = 0x205,
|
|
||||||
// Sent by the client after we sent a InitZone in TravelToZone??
|
|
||||||
// TODO: Actually, I don't think is real...
|
|
||||||
Unk13 = 0x2EE,
|
|
||||||
// Sent by the server when it wants the client to... prepare to zone?
|
|
||||||
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]
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct ServiceAccount {
|
|
||||||
pub id: u32,
|
|
||||||
pub unk1: u32,
|
|
||||||
pub index: u32,
|
|
||||||
#[bw(pad_size_to = 0x44)]
|
|
||||||
#[br(count = 0x44)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct Server {
|
|
||||||
pub id: u16,
|
|
||||||
pub index: u16,
|
|
||||||
pub flags: u32,
|
|
||||||
#[brw(pad_before = 4)]
|
|
||||||
#[brw(pad_after = 4)]
|
|
||||||
pub icon: u32,
|
|
||||||
#[bw(pad_size_to = 64)]
|
|
||||||
#[br(count = 64)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct ActorSetPos {
|
|
||||||
pub unk: u32,
|
|
||||||
pub layer_id: u32,
|
|
||||||
pub position: Position,
|
|
||||||
pub unk3: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct CharacterDetails {
|
|
||||||
#[brw(pad_after = 4)]
|
|
||||||
pub id: u32,
|
|
||||||
pub content_id: u64,
|
|
||||||
#[brw(pad_after = 4)]
|
|
||||||
pub index: u32,
|
|
||||||
pub origin_server_id: u16,
|
|
||||||
pub current_server_id: u16,
|
|
||||||
pub unk1: [u8; 16],
|
|
||||||
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub character_name: String,
|
|
||||||
#[bw(pad_size_to = 32)]
|
|
||||||
#[br(count = 32)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub origin_server_name: String,
|
|
||||||
#[bw(pad_size_to = 32)]
|
|
||||||
#[br(count = 32)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub current_server_name: String,
|
|
||||||
#[bw(pad_size_to = 1024)]
|
|
||||||
#[br(count = 1024)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub character_detail_json: String,
|
|
||||||
pub unk2: [u8; 20],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum LobbyCharacterAction {
|
|
||||||
#[brw(magic = 0x1u8)]
|
|
||||||
ReserveName,
|
|
||||||
#[brw(magic = 0x2u8)]
|
|
||||||
Create,
|
|
||||||
#[brw(magic = 0x3u8)]
|
|
||||||
Rename,
|
|
||||||
#[brw(magic = 0x4u8)]
|
|
||||||
Delete,
|
|
||||||
#[brw(magic = 0x5u8)]
|
|
||||||
Move,
|
|
||||||
#[brw(magic = 0x6u8)]
|
|
||||||
RemakeRetainer,
|
|
||||||
#[brw(magic = 0x7u8)]
|
|
||||||
RemakeChara,
|
|
||||||
#[brw(magic = 0x8u8)]
|
|
||||||
SettingsUploadBegin,
|
|
||||||
#[brw(magic = 0xCu8)]
|
|
||||||
SettingsUpload,
|
|
||||||
#[brw(magic = 0xEu8)]
|
|
||||||
WorldVisit,
|
|
||||||
#[brw(magic = 0xFu8)]
|
|
||||||
DataCenterToken,
|
|
||||||
#[brw(magic = 0x15u8)]
|
|
||||||
Request,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(repr = u8)]
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum GameMasterCommandType {
|
|
||||||
ChangeTerritory = 0x58,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[br(import(magic: &IPCOpCode))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum IPCStructData {
|
|
||||||
// Client->Server IPC
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::ClientVersionInfo))]
|
|
||||||
ClientVersionInfo {
|
|
||||||
sequence: u64,
|
|
||||||
|
|
||||||
#[brw(pad_before = 10)] // full of nonsense i don't understand yet
|
|
||||||
#[br(count = 64)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(ignore)]
|
|
||||||
session_id: String,
|
|
||||||
|
|
||||||
#[brw(pad_before = 8)] // empty
|
|
||||||
#[br(count = 128)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(ignore)]
|
|
||||||
version_info: String,
|
|
||||||
// unknown stuff at the end, it's not completely empty'
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::RequestCharacterList))]
|
|
||||||
RequestCharacterList {
|
|
||||||
#[brw(pad_before = 16)]
|
|
||||||
sequence: u64,
|
|
||||||
// TODO: what is in here?
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::LobbyCharacterAction))]
|
|
||||||
LobbyCharacterAction {
|
|
||||||
request_number: u32,
|
|
||||||
unk1: u32,
|
|
||||||
character_id: u64,
|
|
||||||
#[br(pad_before = 8)]
|
|
||||||
character_index: u8,
|
|
||||||
action: LobbyCharacterAction,
|
|
||||||
world_id: u16,
|
|
||||||
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
name: String,
|
|
||||||
#[bw(pad_size_to = 436)]
|
|
||||||
#[br(count = 436)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
json: String,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::RequestEnterWorld))]
|
|
||||||
RequestEnterWorld {
|
|
||||||
#[brw(pad_before = 16)]
|
|
||||||
sequence: u64,
|
|
||||||
lookup_id: u64,
|
|
||||||
// TODO: what else is in here?
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::InitRequest))]
|
|
||||||
InitRequest {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
#[br(dbg)]
|
|
||||||
unk: [u8; 105],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::FinishLoading))]
|
|
||||||
FinishLoading {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 72],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk1))]
|
|
||||||
Unk1 {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 32],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk2))]
|
|
||||||
Unk2 {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk3))]
|
|
||||||
Unk3 {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk4))]
|
|
||||||
Unk4 {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::SetSearchInfoHandler))]
|
|
||||||
SetSearchInfoHandler {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk5))]
|
|
||||||
Unk5 {
|
|
||||||
// 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
|
|
||||||
timestamp: u32,
|
|
||||||
#[brw(pad_before = 8)] // empty bytes
|
|
||||||
#[brw(pad_after = 4)] // empty bytes
|
|
||||||
unk1: [u8; 16], // something
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::UpdatePositionHandler))]
|
|
||||||
UpdatePositionHandler {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 24],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::LogOut))]
|
|
||||||
LogOut {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Disconnected))]
|
|
||||||
Disconnected {
|
|
||||||
// TODO: full of possibly interesting information
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::ChatMessage))]
|
|
||||||
ChatMessage(ChatMessage),
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::GameMasterCommand))]
|
|
||||||
GameMasterCommand {
|
|
||||||
// TODO: incomplete
|
|
||||||
command: GameMasterCommandType,
|
|
||||||
#[br(pad_before = 3)] // idk, not empty though
|
|
||||||
arg: u32,
|
|
||||||
#[br(dbg)]
|
|
||||||
unk: [u8; 24],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk12))]
|
|
||||||
Unk12 {
|
|
||||||
unk: [u8; 8], // TODO: unknown
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::EnterZoneLine))]
|
|
||||||
EnterZoneLine {
|
|
||||||
exit_box_id: u32,
|
|
||||||
position: Position,
|
|
||||||
#[brw(pad_after = 4)] // empty
|
|
||||||
landset_index: i32,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk13))]
|
|
||||||
Unk13 {
|
|
||||||
#[br(dbg)]
|
|
||||||
unk: [u8; 16], // TODO: unknown
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::Unk14))]
|
|
||||||
Unk14 {
|
|
||||||
#[br(dbg)]
|
|
||||||
unk: [u8; 8], // TODO: unknown
|
|
||||||
},
|
|
||||||
|
|
||||||
// Server->Client IPC
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyServiceAccountList {
|
|
||||||
#[br(dbg)]
|
|
||||||
sequence: u64,
|
|
||||||
#[brw(pad_before = 1)]
|
|
||||||
num_service_accounts: u8,
|
|
||||||
unk1: u8,
|
|
||||||
#[brw(pad_after = 4)]
|
|
||||||
unk2: u8,
|
|
||||||
#[br(count = 8)]
|
|
||||||
service_accounts: Vec<ServiceAccount>,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyServerList {
|
|
||||||
sequence: u64,
|
|
||||||
unk1: u16,
|
|
||||||
offset: u16,
|
|
||||||
#[brw(pad_after = 8)]
|
|
||||||
num_servers: u32,
|
|
||||||
#[br(count = 6)]
|
|
||||||
#[brw(pad_size_to = 504)]
|
|
||||||
servers: Vec<Server>,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyRetainerList {
|
|
||||||
// TODO: what is in here?
|
|
||||||
#[brw(pad_before = 7)]
|
|
||||||
#[brw(pad_after = 202)]
|
|
||||||
unk1: u8,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyCharacterList {
|
|
||||||
sequence: u64,
|
|
||||||
counter: u8,
|
|
||||||
#[brw(pad_after = 2)]
|
|
||||||
num_in_packet: u8,
|
|
||||||
unk1: u8,
|
|
||||||
unk2: u8,
|
|
||||||
unk3: u8,
|
|
||||||
/// Set to 128 if legacy character
|
|
||||||
unk4: u8,
|
|
||||||
unk5: [u32; 7],
|
|
||||||
unk6: u8,
|
|
||||||
veteran_rank: u8,
|
|
||||||
#[brw(pad_after = 1)]
|
|
||||||
unk7: u8,
|
|
||||||
days_subscribed: u32,
|
|
||||||
remaining_days: u32,
|
|
||||||
days_to_next_rank: u32,
|
|
||||||
max_characters_on_world: u16,
|
|
||||||
unk8: u16,
|
|
||||||
#[brw(pad_after = 12)]
|
|
||||||
entitled_expansion: u32,
|
|
||||||
#[br(count = 2)]
|
|
||||||
#[brw(pad_size_to = 2368)]
|
|
||||||
characters: Vec<CharacterDetails>,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyEnterWorld {
|
|
||||||
sequence: u64,
|
|
||||||
character_id: u32,
|
|
||||||
#[brw(pad_before = 4)]
|
|
||||||
content_id: u64,
|
|
||||||
#[brw(pad_before = 4)]
|
|
||||||
#[bw(pad_size_to = 66)]
|
|
||||||
#[br(count = 66)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
session_id: String,
|
|
||||||
port: u16,
|
|
||||||
#[brw(pad_after = 16)]
|
|
||||||
#[br(count = 48)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
host: String,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
InitializeChat { unk: [u8; 8] },
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
InitResponse {
|
|
||||||
unk1: u64,
|
|
||||||
character_id: u32,
|
|
||||||
unk2: u32,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
InitZone(InitZone),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
ActorControlSelf(ActorControlSelf),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
PlayerStats(PlayerStats),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
PlayerSetup(PlayerSetup),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
UpdateClassInfo(UpdateClassInfo),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
PlayerSpawn(PlayerSpawn),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LogOutComplete {
|
|
||||||
// TODO: guessed
|
|
||||||
unk: [u8; 8],
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
ActorSetPos(ActorSetPos),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
ServerChatMessage {
|
|
||||||
unk: u8, // channel?
|
|
||||||
#[brw(pad_after = 775)]
|
|
||||||
#[br(count = 775)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
Unk8 { unk: [u8; 808] },
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LinkShellInformation { unk: [u8; 456] },
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
Unk9 { unk: [u8; 24] },
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
Unk10 { unk: u64 },
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
Unk11 {
|
|
||||||
timestamp: u32,
|
|
||||||
#[brw(pad_after = 24)] // empty bytes
|
|
||||||
unk: u32,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
NameRejection {
|
|
||||||
// FIXME: This is opcode 0x2, which is InitializeChat. We need to separate the lobby/zone IPC codes.
|
|
||||||
unk1: u8,
|
|
||||||
#[brw(pad_before = 7)] // empty
|
|
||||||
unk2: u16,
|
|
||||||
#[brw(pad_before = 6)] // empty
|
|
||||||
#[brw(pad_after = 516)] // mostly empty
|
|
||||||
unk3: u32,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
CharacterCreated {
|
|
||||||
#[brw(pad_after = 4)] // empty
|
|
||||||
unk1: u32,
|
|
||||||
#[brw(pad_after = 4)] // empty
|
|
||||||
unk2: u32,
|
|
||||||
#[brw(pad_before = 32)] // empty
|
|
||||||
#[brw(pad_after = 1136)] // empty
|
|
||||||
details: CharacterDetails,
|
|
||||||
},
|
|
||||||
#[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),
|
|
||||||
#[br(pre_assert(false))]
|
|
||||||
LobbyError {
|
|
||||||
sequence: u64,
|
|
||||||
error: u32,
|
|
||||||
value: u32,
|
|
||||||
exd_error_id: u16,
|
|
||||||
unk1: u16,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct IPCSegment {
|
|
||||||
pub unk1: u8,
|
|
||||||
pub unk2: u8,
|
|
||||||
#[br(dbg)]
|
|
||||||
pub op_code: IPCOpCode,
|
|
||||||
#[brw(pad_before = 2)] // empty
|
|
||||||
#[br(dbg)]
|
|
||||||
pub server_id: u16,
|
|
||||||
#[br(dbg)]
|
|
||||||
pub timestamp: u32,
|
|
||||||
#[brw(pad_before = 4)]
|
|
||||||
#[br(args(&op_code))]
|
|
||||||
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(),
|
|
||||||
sequence: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IPCSegment {
|
|
||||||
pub fn calc_size(&self) -> u32 {
|
|
||||||
let header = 16;
|
|
||||||
header
|
|
||||||
+ match self.data {
|
|
||||||
IPCStructData::ClientVersionInfo { .. } => todo!(),
|
|
||||||
IPCStructData::LobbyServiceAccountList { .. } => 24 + (8 * 80),
|
|
||||||
IPCStructData::RequestCharacterList { .. } => todo!(),
|
|
||||||
IPCStructData::LobbyServerList { .. } => 24 + (6 * 84),
|
|
||||||
IPCStructData::LobbyRetainerList { .. } => 210,
|
|
||||||
IPCStructData::LobbyCharacterList { .. } => 80 + (2 * 1184),
|
|
||||||
IPCStructData::LobbyCharacterAction { .. } => todo!(),
|
|
||||||
IPCStructData::LobbyEnterWorld { .. } => 160,
|
|
||||||
IPCStructData::RequestEnterWorld { .. } => todo!(),
|
|
||||||
IPCStructData::InitializeChat { .. } => 8,
|
|
||||||
IPCStructData::InitRequest { .. } => 16,
|
|
||||||
IPCStructData::InitResponse { .. } => 16,
|
|
||||||
IPCStructData::InitZone { .. } => 103,
|
|
||||||
IPCStructData::ActorControlSelf { .. } => 32,
|
|
||||||
IPCStructData::PlayerStats { .. } => 224,
|
|
||||||
IPCStructData::PlayerSetup { .. } => 2784,
|
|
||||||
IPCStructData::UpdateClassInfo { .. } => 48,
|
|
||||||
IPCStructData::FinishLoading { .. } => todo!(),
|
|
||||||
IPCStructData::PlayerSpawn { .. } => 656,
|
|
||||||
IPCStructData::Unk1 { .. } => todo!(),
|
|
||||||
IPCStructData::Unk2 { .. } => todo!(),
|
|
||||||
IPCStructData::Unk3 { .. } => todo!(),
|
|
||||||
IPCStructData::Unk4 { .. } => todo!(),
|
|
||||||
IPCStructData::SetSearchInfoHandler { .. } => todo!(),
|
|
||||||
IPCStructData::Unk5 { .. } => todo!(),
|
|
||||||
IPCStructData::SocialListRequest { .. } => todo!(),
|
|
||||||
IPCStructData::Unk7 { .. } => todo!(),
|
|
||||||
IPCStructData::UpdatePositionHandler { .. } => todo!(),
|
|
||||||
IPCStructData::LogOut { .. } => todo!(),
|
|
||||||
IPCStructData::LogOutComplete { .. } => 8,
|
|
||||||
IPCStructData::Disconnected { .. } => todo!(),
|
|
||||||
IPCStructData::ChatMessage { .. } => 1056,
|
|
||||||
IPCStructData::GameMasterCommand { .. } => todo!(),
|
|
||||||
IPCStructData::ActorSetPos { .. } => 24,
|
|
||||||
IPCStructData::ServerChatMessage { .. } => 776,
|
|
||||||
IPCStructData::Unk8 { .. } => 808,
|
|
||||||
IPCStructData::LinkShellInformation { .. } => 456,
|
|
||||||
IPCStructData::Unk9 { .. } => 24,
|
|
||||||
IPCStructData::Unk10 { .. } => 8,
|
|
||||||
IPCStructData::Unk11 { .. } => 32,
|
|
||||||
IPCStructData::NameRejection { .. } => 536,
|
|
||||||
IPCStructData::CharacterCreated { .. } => 2568,
|
|
||||||
IPCStructData::Unk12 { .. } => todo!(),
|
|
||||||
IPCStructData::EnterZoneLine { .. } => todo!(),
|
|
||||||
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,
|
|
||||||
IPCStructData::LobbyError { .. } => 536,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use binrw::BinWrite;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Ensure that the IPC data size as reported matches up with what we write
|
|
||||||
#[test]
|
|
||||||
fn test_ipc_sizes() {
|
|
||||||
let ipc_types = [
|
|
||||||
IPCStructData::LobbyServerList {
|
|
||||||
sequence: 0,
|
|
||||||
unk1: 0,
|
|
||||||
offset: 0,
|
|
||||||
num_servers: 0,
|
|
||||||
servers: Vec::new(),
|
|
||||||
},
|
|
||||||
IPCStructData::LobbyCharacterList {
|
|
||||||
sequence: 0,
|
|
||||||
counter: 0,
|
|
||||||
num_in_packet: 0,
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
unk3: 0,
|
|
||||||
unk4: 0,
|
|
||||||
unk5: [0; 7],
|
|
||||||
unk6: 0,
|
|
||||||
veteran_rank: 0,
|
|
||||||
unk7: 0,
|
|
||||||
days_subscribed: 0,
|
|
||||||
remaining_days: 0,
|
|
||||||
days_to_next_rank: 0,
|
|
||||||
max_characters_on_world: 0,
|
|
||||||
unk8: 0,
|
|
||||||
entitled_expansion: 0,
|
|
||||||
characters: Vec::new(),
|
|
||||||
},
|
|
||||||
IPCStructData::ActorControlSelf(ActorControlSelf::default()),
|
|
||||||
IPCStructData::InitializeChat { unk: [0; 8] },
|
|
||||||
IPCStructData::PlayerStats(PlayerStats::default()),
|
|
||||||
IPCStructData::PlayerSetup(PlayerSetup::default()),
|
|
||||||
IPCStructData::UpdateClassInfo(UpdateClassInfo::default()),
|
|
||||||
IPCStructData::PlayerSpawn(PlayerSpawn::default()),
|
|
||||||
IPCStructData::ActorSetPos(ActorSetPos::default()),
|
|
||||||
];
|
|
||||||
|
|
||||||
for ipc in &ipc_types {
|
|
||||||
let mut cursor = Cursor::new(Vec::new());
|
|
||||||
|
|
||||||
let ipc_segment = IPCSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: IPCOpCode::InitializeChat, // doesn't matter for this test
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: ipc.clone(),
|
|
||||||
};
|
|
||||||
ipc_segment.write_le(&mut cursor).unwrap();
|
|
||||||
|
|
||||||
let buffer = cursor.into_inner();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
buffer.len(),
|
|
||||||
ipc_segment.calc_size() as usize,
|
|
||||||
"{:?} did not match size!",
|
|
||||||
ipc
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,14 +9,9 @@ pub mod blowfish;
|
||||||
/// Common functions, structures used between all servers.
|
/// Common functions, structures used between all servers.
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
||||||
mod compression;
|
|
||||||
|
|
||||||
/// Config management.
|
/// Config management.
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod encryption;
|
|
||||||
pub mod ipc;
|
|
||||||
pub mod oodle;
|
pub mod oodle;
|
||||||
pub mod packet;
|
|
||||||
|
|
||||||
/// Patch server-specific code.
|
/// Patch server-specific code.
|
||||||
pub mod patch;
|
pub mod patch;
|
||||||
|
@ -27,6 +22,9 @@ pub mod lobby;
|
||||||
/// World server-specific code.
|
/// World server-specific code.
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
|
||||||
|
/// Everything packet parsing related.
|
||||||
|
pub mod packet;
|
||||||
|
|
||||||
// TODO: make this configurable
|
// TODO: make this configurable
|
||||||
// See https://ffxiv.consolegameswiki.com/wiki/Servers for a list of possible IDs
|
// See https://ffxiv.consolegameswiki.com/wiki/Servers for a list of possible IDs
|
||||||
pub const WORLD_ID: u16 = 63;
|
pub const WORLD_ID: u16 = 63;
|
||||||
|
|
|
@ -7,28 +7,36 @@ use crate::{
|
||||||
ZONE_ID,
|
ZONE_ID,
|
||||||
blowfish::Blowfish,
|
blowfish::Blowfish,
|
||||||
common::timestamp_secs,
|
common::timestamp_secs,
|
||||||
encryption::generate_encryption_key,
|
|
||||||
ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, Server, ServiceAccount},
|
|
||||||
packet::{
|
packet::{
|
||||||
CompressionType, ConnectionType, PacketSegment, SegmentType, State, parse_packet,
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType,
|
||||||
send_packet,
|
generate_encryption_key, parse_packet, send_packet,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::client_select_data::ClientSelectData;
|
use super::{
|
||||||
|
client_select_data::ClientSelectData,
|
||||||
|
ipc::{
|
||||||
|
CharacterDetails, Server, ServerLobbyIpcData, ServerLobbyIpcSegment, ServerLobbyIpcType,
|
||||||
|
ServiceAccount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
||||||
|
|
||||||
pub struct LobbyConnection {
|
pub struct LobbyConnection {
|
||||||
pub socket: TcpStream,
|
pub socket: TcpStream,
|
||||||
|
|
||||||
pub state: State,
|
pub state: PacketState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LobbyConnection {
|
impl LobbyConnection {
|
||||||
pub async fn parse_packet(&mut self, data: &[u8]) -> (Vec<PacketSegment>, ConnectionType) {
|
pub async fn parse_packet(
|
||||||
|
&mut self,
|
||||||
|
data: &[u8],
|
||||||
|
) -> (Vec<PacketSegment<ClientLobbyIpcSegment>>, ConnectionType) {
|
||||||
parse_packet(data, &mut self.state).await
|
parse_packet(data, &mut self.state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_segment(&mut self, segment: PacketSegment) {
|
pub async fn send_segment(&mut self, segment: PacketSegment<ServerLobbyIpcSegment>) {
|
||||||
send_packet(
|
send_packet(
|
||||||
&mut self.socket,
|
&mut self.socket,
|
||||||
&[segment],
|
&[segment],
|
||||||
|
@ -66,7 +74,7 @@ impl LobbyConnection {
|
||||||
}]
|
}]
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let service_account_list = IPCStructData::LobbyServiceAccountList {
|
let service_account_list = ServerLobbyIpcData::LobbyServiceAccountList {
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
num_service_accounts: service_accounts.len() as u8,
|
num_service_accounts: service_accounts.len() as u8,
|
||||||
unk1: 3,
|
unk1: 3,
|
||||||
|
@ -74,10 +82,10 @@ impl LobbyConnection {
|
||||||
service_accounts: service_accounts.to_vec(),
|
service_accounts: service_accounts.to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::LobbyServiceAccountList,
|
op_code: ServerLobbyIpcType::LobbyServiceAccountList,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: service_account_list,
|
data: service_account_list,
|
||||||
|
@ -106,7 +114,7 @@ impl LobbyConnection {
|
||||||
// add any empty boys
|
// add any empty boys
|
||||||
servers.resize(6, Server::default());
|
servers.resize(6, Server::default());
|
||||||
|
|
||||||
let lobby_server_list = IPCStructData::LobbyServerList {
|
let lobby_server_list = ServerLobbyIpcData::LobbyServerList {
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
unk1: 1,
|
unk1: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -114,10 +122,10 @@ impl LobbyConnection {
|
||||||
servers,
|
servers,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::LobbyServerList,
|
op_code: ServerLobbyIpcType::LobbyServerList,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: lobby_server_list,
|
data: lobby_server_list,
|
||||||
|
@ -133,12 +141,12 @@ impl LobbyConnection {
|
||||||
|
|
||||||
// send them the retainer list
|
// send them the retainer list
|
||||||
{
|
{
|
||||||
let lobby_retainer_list = IPCStructData::LobbyRetainerList { unk1: 1 };
|
let lobby_retainer_list = ServerLobbyIpcData::LobbyRetainerList { unk1: 1 };
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::LobbyRetainerList,
|
op_code: ServerLobbyIpcType::LobbyRetainerList,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: lobby_retainer_list,
|
data: lobby_retainer_list,
|
||||||
|
@ -215,7 +223,7 @@ impl LobbyConnection {
|
||||||
|
|
||||||
let lobby_character_list = if i == 3 {
|
let lobby_character_list = if i == 3 {
|
||||||
// On the last packet, add the account-wide information
|
// On the last packet, add the account-wide information
|
||||||
IPCStructData::LobbyCharacterList {
|
ServerLobbyIpcData::LobbyCharacterList {
|
||||||
sequence,
|
sequence,
|
||||||
counter: (i * 4) + 1, // TODO: why the + 1 here?
|
counter: (i * 4) + 1, // TODO: why the + 1 here?
|
||||||
num_in_packet: characters_in_packet.len() as u8,
|
num_in_packet: characters_in_packet.len() as u8,
|
||||||
|
@ -236,7 +244,7 @@ impl LobbyConnection {
|
||||||
characters: characters_in_packet,
|
characters: characters_in_packet,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
IPCStructData::LobbyCharacterList {
|
ServerLobbyIpcData::LobbyCharacterList {
|
||||||
sequence,
|
sequence,
|
||||||
counter: i * 4,
|
counter: i * 4,
|
||||||
num_in_packet: characters_in_packet.len() as u8,
|
num_in_packet: characters_in_packet.len() as u8,
|
||||||
|
@ -258,10 +266,10 @@ impl LobbyConnection {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::LobbyCharacterList,
|
op_code: ServerLobbyIpcType::LobbyCharacterList,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: lobby_character_list,
|
data: lobby_character_list,
|
||||||
|
@ -282,7 +290,7 @@ impl LobbyConnection {
|
||||||
panic!("Missing session id!");
|
panic!("Missing session id!");
|
||||||
};
|
};
|
||||||
|
|
||||||
let enter_world = IPCStructData::LobbyEnterWorld {
|
let enter_world = ServerLobbyIpcData::LobbyEnterWorld {
|
||||||
sequence,
|
sequence,
|
||||||
character_id: 0,
|
character_id: 0,
|
||||||
content_id: lookup_id, // TODO: shouldn't these be named the same then?
|
content_id: lookup_id, // TODO: shouldn't these be named the same then?
|
||||||
|
@ -291,10 +299,10 @@ impl LobbyConnection {
|
||||||
host: "127.0.0.1".to_string(),
|
host: "127.0.0.1".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::LobbyEnterWorld,
|
op_code: ServerLobbyIpcType::LobbyEnterWorld,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: enter_world,
|
data: enter_world,
|
||||||
|
@ -309,7 +317,7 @@ impl LobbyConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_error(&mut self, sequence: u64, error: u32, exd_error: u16) {
|
pub async fn send_error(&mut self, sequence: u64, error: u32, exd_error: u16) {
|
||||||
let lobby_error = IPCStructData::LobbyError {
|
let lobby_error = ServerLobbyIpcData::LobbyError {
|
||||||
sequence,
|
sequence,
|
||||||
error,
|
error,
|
||||||
value: 0,
|
value: 0,
|
||||||
|
@ -317,10 +325,10 @@ impl LobbyConnection {
|
||||||
unk1: 1,
|
unk1: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerLobbyIpcSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::InitializeChat,
|
op_code: ServerLobbyIpcType::LobbyError,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: lobby_error,
|
data: lobby_error,
|
||||||
|
|
30
src/lobby/ipc/character_action.rs
Normal file
30
src/lobby/ipc/character_action.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum LobbyCharacterAction {
|
||||||
|
#[brw(magic = 0x1u8)]
|
||||||
|
ReserveName,
|
||||||
|
#[brw(magic = 0x2u8)]
|
||||||
|
Create,
|
||||||
|
#[brw(magic = 0x3u8)]
|
||||||
|
Rename,
|
||||||
|
#[brw(magic = 0x4u8)]
|
||||||
|
Delete,
|
||||||
|
#[brw(magic = 0x5u8)]
|
||||||
|
Move,
|
||||||
|
#[brw(magic = 0x6u8)]
|
||||||
|
RemakeRetainer,
|
||||||
|
#[brw(magic = 0x7u8)]
|
||||||
|
RemakeChara,
|
||||||
|
#[brw(magic = 0x8u8)]
|
||||||
|
SettingsUploadBegin,
|
||||||
|
#[brw(magic = 0xCu8)]
|
||||||
|
SettingsUpload,
|
||||||
|
#[brw(magic = 0xEu8)]
|
||||||
|
WorldVisit,
|
||||||
|
#[brw(magic = 0xFu8)]
|
||||||
|
DataCenterToken,
|
||||||
|
#[brw(magic = 0x15u8)]
|
||||||
|
Request,
|
||||||
|
}
|
39
src/lobby/ipc/character_list.rs
Normal file
39
src/lobby/ipc/character_list.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use crate::CHAR_NAME_MAX_LENGTH;
|
||||||
|
|
||||||
|
use super::{read_string, write_string};
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct CharacterDetails {
|
||||||
|
#[brw(pad_after = 4)]
|
||||||
|
pub id: u32,
|
||||||
|
pub content_id: u64,
|
||||||
|
#[brw(pad_after = 4)]
|
||||||
|
pub index: u32,
|
||||||
|
pub origin_server_id: u16,
|
||||||
|
pub current_server_id: u16,
|
||||||
|
pub unk1: [u8; 16],
|
||||||
|
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub character_name: String,
|
||||||
|
#[bw(pad_size_to = 32)]
|
||||||
|
#[br(count = 32)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub origin_server_name: String,
|
||||||
|
#[bw(pad_size_to = 32)]
|
||||||
|
#[br(count = 32)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub current_server_name: String,
|
||||||
|
#[bw(pad_size_to = 1024)]
|
||||||
|
#[br(count = 1024)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub character_detail_json: String,
|
||||||
|
pub unk2: [u8; 20],
|
||||||
|
}
|
1
src/lobby/ipc/client_version_info.rs
Normal file
1
src/lobby/ipc/client_version_info.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
349
src/lobby/ipc/mod.rs
Normal file
349
src/lobby/ipc/mod.rs
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
mod character_action;
|
||||||
|
pub use character_action::LobbyCharacterAction;
|
||||||
|
|
||||||
|
mod character_list;
|
||||||
|
pub use character_list::CharacterDetails;
|
||||||
|
|
||||||
|
mod client_version_info;
|
||||||
|
|
||||||
|
mod server_list;
|
||||||
|
pub use server_list::Server;
|
||||||
|
|
||||||
|
mod service_account_list;
|
||||||
|
pub use service_account_list::ServiceAccount;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
CHAR_NAME_MAX_LENGTH,
|
||||||
|
common::{read_string, write_string},
|
||||||
|
packet::{IpcSegment, IpcSegmentTrait},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcData>;
|
||||||
|
|
||||||
|
impl IpcSegmentTrait for ClientLobbyIpcSegment {
|
||||||
|
fn calc_size(&self) -> u32 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make generic
|
||||||
|
impl Default for ClientLobbyIpcSegment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unk1: 0x14,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ClientLobbyIpcType::ClientVersionInfo,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ClientLobbyIpcData::ClientVersionInfo {
|
||||||
|
sequence: 0,
|
||||||
|
session_id: String::new(),
|
||||||
|
version_info: String::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcData>;
|
||||||
|
|
||||||
|
impl IpcSegmentTrait for ServerLobbyIpcSegment {
|
||||||
|
fn calc_size(&self) -> u32 {
|
||||||
|
// 16 is the size of the IPC header
|
||||||
|
16 + match self.op_code {
|
||||||
|
ServerLobbyIpcType::LobbyError => 536,
|
||||||
|
ServerLobbyIpcType::LobbyServiceAccountList => 24 + (8 * 80),
|
||||||
|
ServerLobbyIpcType::LobbyCharacterList => 80 + (2 * 1184),
|
||||||
|
ServerLobbyIpcType::LobbyEnterWorld => 160,
|
||||||
|
ServerLobbyIpcType::LobbyServerList => 24 + (6 * 84),
|
||||||
|
ServerLobbyIpcType::LobbyRetainerList => 210,
|
||||||
|
ServerLobbyIpcType::CharacterCreated => 2568,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make generic
|
||||||
|
impl Default for ServerLobbyIpcSegment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unk1: 0x14,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerLobbyIpcType::LobbyError,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerLobbyIpcData::LobbyError {
|
||||||
|
sequence: 0,
|
||||||
|
error: 0,
|
||||||
|
value: 0,
|
||||||
|
exd_error_id: 0,
|
||||||
|
unk1: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u16)]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum ServerLobbyIpcType {
|
||||||
|
/// Sent by the server to indicate an lobby error occured
|
||||||
|
LobbyError = 0x2,
|
||||||
|
/// Sent by the server to inform the client of their service accounts.
|
||||||
|
LobbyServiceAccountList = 0xC,
|
||||||
|
/// Sent by the server to inform the client of their characters.
|
||||||
|
LobbyCharacterList = 0xD,
|
||||||
|
/// Sent by the server to tell the client how to connect to the world server.
|
||||||
|
LobbyEnterWorld = 0xF,
|
||||||
|
/// Sent by the server to inform the client of their servers.
|
||||||
|
LobbyServerList = 0x15,
|
||||||
|
/// Sent by the server to inform the client of their retainers.
|
||||||
|
LobbyRetainerList = 0x17,
|
||||||
|
// Assumed what this is, but probably incorrect
|
||||||
|
CharacterCreated = 0xE,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u16)]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum ClientLobbyIpcType {
|
||||||
|
/// Sent by the client when it requests the character list in the lobby.
|
||||||
|
RequestCharacterList = 0x3,
|
||||||
|
/// Sent by the client when it requests to enter a world.
|
||||||
|
RequestEnterWorld = 0x4,
|
||||||
|
/// Sent by the client after exchanging encryption information with the lobby server.
|
||||||
|
ClientVersionInfo = 0x5,
|
||||||
|
/// Sent by the client when they request something about the character (e.g. deletion.)
|
||||||
|
LobbyCharacterAction = 0xB,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[br(import(magic: &ClientLobbyIpcType))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ClientLobbyIpcData {
|
||||||
|
// Client->Server IPC
|
||||||
|
#[br(pre_assert(*magic == ClientLobbyIpcType::ClientVersionInfo))]
|
||||||
|
ClientVersionInfo {
|
||||||
|
sequence: u64,
|
||||||
|
|
||||||
|
#[brw(pad_before = 10)] // full of nonsense i don't understand yet
|
||||||
|
#[br(count = 64)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(ignore)]
|
||||||
|
session_id: String,
|
||||||
|
|
||||||
|
#[brw(pad_before = 8)] // empty
|
||||||
|
#[br(count = 128)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(ignore)]
|
||||||
|
version_info: String,
|
||||||
|
// unknown stuff at the end, it's not completely empty'
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientLobbyIpcType::RequestCharacterList))]
|
||||||
|
RequestCharacterList {
|
||||||
|
#[brw(pad_before = 16)]
|
||||||
|
sequence: u64,
|
||||||
|
// TODO: what is in here?
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientLobbyIpcType::LobbyCharacterAction))]
|
||||||
|
LobbyCharacterAction {
|
||||||
|
request_number: u32,
|
||||||
|
unk1: u32,
|
||||||
|
character_id: u64,
|
||||||
|
#[br(pad_before = 8)]
|
||||||
|
character_index: u8,
|
||||||
|
action: LobbyCharacterAction,
|
||||||
|
world_id: u16,
|
||||||
|
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
name: String,
|
||||||
|
#[bw(pad_size_to = 436)]
|
||||||
|
#[br(count = 436)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
json: String,
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientLobbyIpcType::RequestEnterWorld))]
|
||||||
|
RequestEnterWorld {
|
||||||
|
#[brw(pad_before = 16)]
|
||||||
|
sequence: u64,
|
||||||
|
lookup_id: u64,
|
||||||
|
// TODO: what else is in here?
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[br(import(_magic: &ServerLobbyIpcType))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ServerLobbyIpcData {
|
||||||
|
LobbyServiceAccountList {
|
||||||
|
#[br(dbg)]
|
||||||
|
sequence: u64,
|
||||||
|
#[brw(pad_before = 1)]
|
||||||
|
num_service_accounts: u8,
|
||||||
|
unk1: u8,
|
||||||
|
#[brw(pad_after = 4)]
|
||||||
|
unk2: u8,
|
||||||
|
#[br(count = 8)]
|
||||||
|
service_accounts: Vec<ServiceAccount>,
|
||||||
|
},
|
||||||
|
LobbyServerList {
|
||||||
|
sequence: u64,
|
||||||
|
unk1: u16,
|
||||||
|
offset: u16,
|
||||||
|
#[brw(pad_after = 8)]
|
||||||
|
num_servers: u32,
|
||||||
|
#[br(count = 6)]
|
||||||
|
#[brw(pad_size_to = 504)]
|
||||||
|
servers: Vec<Server>,
|
||||||
|
},
|
||||||
|
LobbyRetainerList {
|
||||||
|
// TODO: what is in here?
|
||||||
|
#[brw(pad_before = 7)]
|
||||||
|
#[brw(pad_after = 202)]
|
||||||
|
unk1: u8,
|
||||||
|
},
|
||||||
|
LobbyCharacterList {
|
||||||
|
sequence: u64,
|
||||||
|
counter: u8,
|
||||||
|
#[brw(pad_after = 2)]
|
||||||
|
num_in_packet: u8,
|
||||||
|
unk1: u8,
|
||||||
|
unk2: u8,
|
||||||
|
unk3: u8,
|
||||||
|
/// Set to 128 if legacy character
|
||||||
|
unk4: u8,
|
||||||
|
unk5: [u32; 7],
|
||||||
|
unk6: u8,
|
||||||
|
veteran_rank: u8,
|
||||||
|
#[brw(pad_after = 1)]
|
||||||
|
unk7: u8,
|
||||||
|
days_subscribed: u32,
|
||||||
|
remaining_days: u32,
|
||||||
|
days_to_next_rank: u32,
|
||||||
|
max_characters_on_world: u16,
|
||||||
|
unk8: u16,
|
||||||
|
#[brw(pad_after = 12)]
|
||||||
|
entitled_expansion: u32,
|
||||||
|
#[br(count = 2)]
|
||||||
|
#[brw(pad_size_to = 2368)]
|
||||||
|
characters: Vec<CharacterDetails>,
|
||||||
|
},
|
||||||
|
LobbyEnterWorld {
|
||||||
|
sequence: u64,
|
||||||
|
character_id: u32,
|
||||||
|
#[brw(pad_before = 4)]
|
||||||
|
content_id: u64,
|
||||||
|
#[brw(pad_before = 4)]
|
||||||
|
#[bw(pad_size_to = 66)]
|
||||||
|
#[br(count = 66)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
session_id: String,
|
||||||
|
port: u16,
|
||||||
|
#[brw(pad_after = 16)]
|
||||||
|
#[br(count = 48)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
host: String,
|
||||||
|
},
|
||||||
|
LobbyError {
|
||||||
|
sequence: u64,
|
||||||
|
error: u32,
|
||||||
|
value: u32,
|
||||||
|
exd_error_id: u16,
|
||||||
|
unk1: u16,
|
||||||
|
},
|
||||||
|
NameRejection {
|
||||||
|
// FIXME: This is opcode 0x2, which is InitializeChat. We need to separate the lobby/zone IPC codes.
|
||||||
|
unk1: u8,
|
||||||
|
#[brw(pad_before = 7)] // empty
|
||||||
|
unk2: u16,
|
||||||
|
#[brw(pad_before = 6)] // empty
|
||||||
|
#[brw(pad_after = 516)] // mostly empty
|
||||||
|
unk3: u32,
|
||||||
|
},
|
||||||
|
CharacterCreated {
|
||||||
|
#[brw(pad_after = 4)] // empty
|
||||||
|
unk1: u32,
|
||||||
|
#[brw(pad_after = 4)] // empty
|
||||||
|
unk2: u32,
|
||||||
|
#[brw(pad_before = 32)] // empty
|
||||||
|
#[brw(pad_after = 1136)] // empty
|
||||||
|
details: CharacterDetails,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use binrw::BinWrite;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Ensure that the IPC data size as reported matches up with what we write
|
||||||
|
#[test]
|
||||||
|
fn lobby_ipc_sizes() {
|
||||||
|
let ipc_types = [
|
||||||
|
(
|
||||||
|
ServerLobbyIpcType::LobbyServerList,
|
||||||
|
ServerLobbyIpcData::LobbyServerList {
|
||||||
|
sequence: 0,
|
||||||
|
unk1: 0,
|
||||||
|
offset: 0,
|
||||||
|
num_servers: 0,
|
||||||
|
servers: Vec::new(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerLobbyIpcType::LobbyCharacterList,
|
||||||
|
ServerLobbyIpcData::LobbyCharacterList {
|
||||||
|
sequence: 0,
|
||||||
|
counter: 0,
|
||||||
|
num_in_packet: 0,
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
unk3: 0,
|
||||||
|
unk4: 0,
|
||||||
|
unk5: [0; 7],
|
||||||
|
unk6: 0,
|
||||||
|
veteran_rank: 0,
|
||||||
|
unk7: 0,
|
||||||
|
days_subscribed: 0,
|
||||||
|
remaining_days: 0,
|
||||||
|
days_to_next_rank: 0,
|
||||||
|
max_characters_on_world: 0,
|
||||||
|
unk8: 0,
|
||||||
|
entitled_expansion: 0,
|
||||||
|
characters: Vec::new(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, ipc) in &ipc_types {
|
||||||
|
let mut cursor = Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
let ipc_segment = ServerLobbyIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: opcode.clone(),
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ipc.clone(),
|
||||||
|
};
|
||||||
|
ipc_segment.write_le(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let buffer = cursor.into_inner();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buffer.len(),
|
||||||
|
ipc_segment.calc_size() as usize,
|
||||||
|
"{:#?} did not match size!",
|
||||||
|
opcode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/lobby/ipc/server_list.rs
Normal file
19
src/lobby/ipc/server_list.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use crate::common::{read_string, write_string};
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Server {
|
||||||
|
pub id: u16,
|
||||||
|
pub index: u16,
|
||||||
|
pub flags: u32,
|
||||||
|
#[brw(pad_before = 4)]
|
||||||
|
#[brw(pad_after = 4)]
|
||||||
|
pub icon: u32,
|
||||||
|
#[bw(pad_size_to = 64)]
|
||||||
|
#[br(count = 64)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
16
src/lobby/ipc/service_account_list.rs
Normal file
16
src/lobby/ipc/service_account_list.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use crate::common::{read_string, write_string};
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ServiceAccount {
|
||||||
|
pub id: u32,
|
||||||
|
pub unk1: u32,
|
||||||
|
pub index: u32,
|
||||||
|
#[bw(pad_size_to = 0x44)]
|
||||||
|
#[br(count = 0x44)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod chara_make;
|
pub mod chara_make;
|
||||||
mod client_select_data;
|
mod client_select_data;
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
|
pub mod ipc;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use binrw::binrw;
|
||||||
use std::fs::write;
|
use std::fs::write;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
@ -8,12 +9,22 @@ use crate::{
|
||||||
packet::{PacketHeader, PacketSegment},
|
packet::{PacketHeader, PacketSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::IpcSegmentTrait;
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum CompressionType {
|
||||||
|
Uncompressed = 0,
|
||||||
|
Oodle = 2,
|
||||||
|
}
|
||||||
|
|
||||||
#[binrw::parser(reader, endian)]
|
#[binrw::parser(reader, endian)]
|
||||||
pub(crate) fn decompress(
|
pub(crate) fn decompress<T: IpcSegmentTrait>(
|
||||||
oodle: &mut FFXIVOodle,
|
oodle: &mut FFXIVOodle,
|
||||||
header: &PacketHeader,
|
header: &PacketHeader,
|
||||||
encryption_key: Option<&[u8]>,
|
encryption_key: Option<&[u8]>,
|
||||||
) -> BinResult<Vec<PacketSegment>> {
|
) -> BinResult<Vec<PacketSegment<T>>> {
|
||||||
let mut segments = Vec::new();
|
let mut segments = Vec::new();
|
||||||
|
|
||||||
let size = header.size as usize - std::mem::size_of::<PacketHeader>();
|
let size = header.size as usize - std::mem::size_of::<PacketHeader>();
|
|
@ -1,10 +1,12 @@
|
||||||
use std::fs::write;
|
use std::fs::write;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use binrw::{BinRead, BinResult, BinWrite};
|
use binrw::BinResult;
|
||||||
|
|
||||||
use crate::blowfish::Blowfish;
|
use crate::blowfish::Blowfish;
|
||||||
|
|
||||||
|
use super::IpcSegmentTrait;
|
||||||
|
|
||||||
const GAME_VERSION: u16 = 7000;
|
const GAME_VERSION: u16 = 7000;
|
||||||
|
|
||||||
pub fn generate_encryption_key(key: &[u8], phrase: &str) -> [u8; 16] {
|
pub fn generate_encryption_key(key: &[u8], phrase: &str) -> [u8; 16] {
|
||||||
|
@ -18,10 +20,10 @@ pub fn generate_encryption_key(key: &[u8], phrase: &str) -> [u8; 16] {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw::parser(reader, endian)]
|
#[binrw::parser(reader, endian)]
|
||||||
pub(crate) fn decrypt<T>(size: u32, encryption_key: Option<&[u8]>) -> BinResult<T>
|
pub(crate) fn decrypt<T: IpcSegmentTrait>(
|
||||||
where
|
size: u32,
|
||||||
for<'a> T: BinRead<Args<'a> = ()> + 'a,
|
encryption_key: Option<&[u8]>,
|
||||||
{
|
) -> BinResult<T> {
|
||||||
if let Some(encryption_key) = encryption_key {
|
if let Some(encryption_key) = encryption_key {
|
||||||
let size = size - (std::mem::size_of::<u32>() * 4) as u32; // 16 = header size
|
let size = size - (std::mem::size_of::<u32>() * 4) as u32; // 16 = header size
|
||||||
|
|
||||||
|
@ -43,10 +45,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw::writer(writer, endian)]
|
#[binrw::writer(writer, endian)]
|
||||||
pub(crate) fn encrypt<T>(value: &T, size: u32, encryption_key: Option<&[u8]>) -> BinResult<()>
|
pub(crate) fn encrypt<T: IpcSegmentTrait>(
|
||||||
where
|
value: &T,
|
||||||
for<'a> T: BinWrite<Args<'a> = ()> + 'a,
|
size: u32,
|
||||||
{
|
encryption_key: Option<&[u8]>,
|
||||||
|
) -> BinResult<()> {
|
||||||
if let Some(encryption_key) = encryption_key {
|
if let Some(encryption_key) = encryption_key {
|
||||||
let size = size - (std::mem::size_of::<u32>() * 4) as u32; // 16 = header size
|
let size = size - (std::mem::size_of::<u32>() * 4) as u32; // 16 = header size
|
||||||
|
|
30
src/packet/ipc.rs
Normal file
30
src/packet/ipc.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use binrw::{BinRead, BinWrite, binrw};
|
||||||
|
|
||||||
|
pub trait IpcSegmentTrait:
|
||||||
|
for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()> + std::fmt::Debug + 'static
|
||||||
|
{
|
||||||
|
fn calc_size(&self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IpcSegment<OpCode, Data>
|
||||||
|
where
|
||||||
|
for<'a> OpCode: BinRead<Args<'a> = ()> + 'a + std::fmt::Debug,
|
||||||
|
for<'a> OpCode: BinWrite<Args<'a> = ()> + 'a + std::fmt::Debug,
|
||||||
|
for<'a> Data: BinRead<Args<'a> = (&'a OpCode,)> + 'a + std::fmt::Debug,
|
||||||
|
for<'a> Data: BinWrite<Args<'a> = ()> + 'a + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
pub unk1: u8,
|
||||||
|
pub unk2: u8,
|
||||||
|
#[br(dbg)]
|
||||||
|
pub op_code: OpCode,
|
||||||
|
#[brw(pad_before = 2)] // empty
|
||||||
|
#[br(dbg)]
|
||||||
|
pub server_id: u16,
|
||||||
|
#[br(dbg)]
|
||||||
|
pub timestamp: u32,
|
||||||
|
#[brw(pad_before = 4)]
|
||||||
|
#[br(args(&op_code))]
|
||||||
|
pub data: Data,
|
||||||
|
}
|
15
src/packet/mod.rs
Normal file
15
src/packet/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
mod packet;
|
||||||
|
use packet::PacketHeader;
|
||||||
|
pub use packet::{
|
||||||
|
ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet, send_keep_alive,
|
||||||
|
send_packet,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod compression;
|
||||||
|
pub use compression::CompressionType;
|
||||||
|
|
||||||
|
mod encryption;
|
||||||
|
pub use encryption::generate_encryption_key;
|
||||||
|
|
||||||
|
mod ipc;
|
||||||
|
pub use ipc::{IpcSegment, IpcSegmentTrait};
|
|
@ -7,13 +7,9 @@ use std::{
|
||||||
use binrw::{BinRead, BinWrite, binrw};
|
use binrw::{BinRead, BinWrite, binrw};
|
||||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||||
|
|
||||||
use crate::{
|
use crate::{common::read_string, oodle::FFXIVOodle, packet::encryption::decrypt};
|
||||||
common::read_string,
|
|
||||||
compression::decompress,
|
use super::{CompressionType, compression::decompress, encryption::encrypt, ipc::IpcSegmentTrait};
|
||||||
encryption::{decrypt, encrypt},
|
|
||||||
ipc::IPCSegment,
|
|
||||||
oodle::FFXIVOodle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(repr = u16)]
|
#[brw(repr = u16)]
|
||||||
|
@ -28,7 +24,7 @@ pub enum ConnectionType {
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(import(size: u32, encryption_key: Option<&[u8]>))]
|
#[brw(import(size: u32, encryption_key: Option<&[u8]>))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SegmentType {
|
pub enum SegmentType<T: IpcSegmentTrait> {
|
||||||
// Client->Server Packets
|
// Client->Server Packets
|
||||||
#[brw(magic = 0x1u32)]
|
#[brw(magic = 0x1u32)]
|
||||||
InitializeSession {
|
InitializeSession {
|
||||||
|
@ -53,7 +49,7 @@ pub enum SegmentType {
|
||||||
Ipc {
|
Ipc {
|
||||||
#[br(parse_with = decrypt, args(size, encryption_key))]
|
#[br(parse_with = decrypt, args(size, encryption_key))]
|
||||||
#[bw(write_with = encrypt, args(size, encryption_key))]
|
#[bw(write_with = encrypt, args(size, encryption_key))]
|
||||||
data: IPCSegment,
|
data: T,
|
||||||
},
|
},
|
||||||
#[brw(magic = 0x7u32)]
|
#[brw(magic = 0x7u32)]
|
||||||
KeepAlive { id: u32, timestamp: u32 },
|
KeepAlive { id: u32, timestamp: u32 },
|
||||||
|
@ -75,14 +71,6 @@ pub enum SegmentType {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(repr = u8)]
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum CompressionType {
|
|
||||||
Uncompressed = 0,
|
|
||||||
Oodle = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PacketHeader {
|
pub struct PacketHeader {
|
||||||
|
@ -101,7 +89,7 @@ pub struct PacketHeader {
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(import(encryption_key: Option<&[u8]>))]
|
#[brw(import(encryption_key: Option<&[u8]>))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PacketSegment {
|
pub struct PacketSegment<T: IpcSegmentTrait> {
|
||||||
#[bw(calc = self.calc_size())]
|
#[bw(calc = self.calc_size())]
|
||||||
#[br(dbg)]
|
#[br(dbg)]
|
||||||
pub size: u32,
|
pub size: u32,
|
||||||
|
@ -111,10 +99,10 @@ pub struct PacketSegment {
|
||||||
pub target_actor: u32,
|
pub target_actor: u32,
|
||||||
#[brw(args(size, encryption_key))]
|
#[brw(args(size, encryption_key))]
|
||||||
#[br(dbg)]
|
#[br(dbg)]
|
||||||
pub segment_type: SegmentType,
|
pub segment_type: SegmentType<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketSegment {
|
impl<T: IpcSegmentTrait> PacketSegment<T> {
|
||||||
fn calc_size(&self) -> u32 {
|
fn calc_size(&self) -> u32 {
|
||||||
let header = std::mem::size_of::<u32>() * 4;
|
let header = std::mem::size_of::<u32>() * 4;
|
||||||
header as u32
|
header as u32
|
||||||
|
@ -133,13 +121,13 @@ impl PacketSegment {
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(import(oodle: &mut FFXIVOodle, encryption_key: Option<&[u8]>))]
|
#[brw(import(oodle: &mut FFXIVOodle, encryption_key: Option<&[u8]>))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Packet {
|
struct Packet<T: IpcSegmentTrait> {
|
||||||
#[br(dbg)]
|
#[br(dbg)]
|
||||||
header: PacketHeader,
|
header: PacketHeader,
|
||||||
#[bw(args(encryption_key))]
|
#[bw(args(encryption_key))]
|
||||||
#[br(parse_with = decompress, args(oodle, &header, encryption_key,))]
|
#[br(parse_with = decompress, args(oodle, &header, encryption_key,))]
|
||||||
#[br(dbg)]
|
#[br(dbg)]
|
||||||
segments: Vec<PacketSegment>,
|
segments: Vec<PacketSegment<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump(msg: &str, data: &[u8]) {
|
fn dump(msg: &str, data: &[u8]) {
|
||||||
|
@ -147,10 +135,10 @@ fn dump(msg: &str, data: &[u8]) {
|
||||||
panic!("{msg} Dumped to packet.bin.");
|
panic!("{msg} Dumped to packet.bin.");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_packet(
|
pub async fn send_packet<T: IpcSegmentTrait>(
|
||||||
socket: &mut TcpStream,
|
socket: &mut TcpStream,
|
||||||
segments: &[PacketSegment],
|
segments: &[PacketSegment<T>],
|
||||||
state: &mut State,
|
state: &mut PacketState,
|
||||||
compression_type: CompressionType,
|
compression_type: CompressionType,
|
||||||
) {
|
) {
|
||||||
let timestamp: u64 = SystemTime::now()
|
let timestamp: u64 = SystemTime::now()
|
||||||
|
@ -212,14 +200,17 @@ pub async fn send_packet(
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary
|
// temporary
|
||||||
pub struct State {
|
pub struct PacketState {
|
||||||
pub client_key: Option<[u8; 16]>,
|
pub client_key: Option<[u8; 16]>,
|
||||||
pub session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
pub serverbound_oodle: FFXIVOodle,
|
pub serverbound_oodle: FFXIVOodle,
|
||||||
pub clientbound_oodle: FFXIVOodle,
|
pub clientbound_oodle: FFXIVOodle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>, ConnectionType) {
|
pub async fn parse_packet<T: IpcSegmentTrait>(
|
||||||
|
data: &[u8],
|
||||||
|
state: &mut PacketState,
|
||||||
|
) -> (Vec<PacketSegment<T>>, ConnectionType) {
|
||||||
let mut cursor = Cursor::new(data);
|
let mut cursor = Cursor::new(data);
|
||||||
|
|
||||||
match Packet::read_le_args(
|
match Packet::read_le_args(
|
||||||
|
@ -231,15 +222,6 @@ pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>
|
||||||
) {
|
) {
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
println!("{:#?}", packet);
|
println!("{:#?}", packet);
|
||||||
|
|
||||||
// don't really think this works like I think it does'
|
|
||||||
/*if packet.header.size as usize != data.len() {
|
|
||||||
dump(
|
|
||||||
"Packet size mismatch between what we're given and the header!",
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
(packet.segments, packet.header.connection_type)
|
(packet.segments, packet.header.connection_type)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -251,8 +233,13 @@ pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_keep_alive(socket: &mut TcpStream, state: &mut State, id: u32, timestamp: u32) {
|
pub async fn send_keep_alive<T: IpcSegmentTrait>(
|
||||||
let response_packet = PacketSegment {
|
socket: &mut TcpStream,
|
||||||
|
state: &mut PacketState,
|
||||||
|
id: u32,
|
||||||
|
timestamp: u32,
|
||||||
|
) {
|
||||||
|
let response_packet: PacketSegment<T> = PacketSegment {
|
||||||
source_actor: 0,
|
source_actor: 0,
|
||||||
target_actor: 0,
|
target_actor: 0,
|
||||||
segment_type: SegmentType::KeepAliveResponse { id, timestamp },
|
segment_type: SegmentType::KeepAliveResponse { id, timestamp },
|
||||||
|
@ -268,8 +255,9 @@ pub async fn send_keep_alive(socket: &mut TcpStream, state: &mut State, id: u32,
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
// TODO: Restore this test
|
||||||
|
/*
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Ensure that the packet size as reported matches up with what we write
|
/// Ensure that the packet size as reported matches up with what we write
|
||||||
#[test]
|
#[test]
|
||||||
fn test_packet_sizes() {
|
fn test_packet_sizes() {
|
||||||
|
@ -303,5 +291,5 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(buffer.len(), packet_segment.calc_size() as usize);
|
assert_eq!(buffer.len(), packet_segment.calc_size() as usize);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
CHAR_NAME, CUSTOMIZE_DATA, WORLD_ID,
|
CHAR_NAME, CUSTOMIZE_DATA, WORLD_ID,
|
||||||
common::timestamp_secs,
|
common::timestamp_secs,
|
||||||
ipc::{IPCOpCode, IPCSegment, IPCStructData},
|
|
||||||
packet::{PacketSegment, SegmentType},
|
packet::{PacketSegment, SegmentType},
|
||||||
world::PlayerSpawn,
|
world::ipc::{PlayerSpawn, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ChatMessage, Position, ZoneConnection};
|
use super::{
|
||||||
|
ZoneConnection,
|
||||||
|
ipc::{ChatMessage, Position},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ChatHandler {}
|
pub struct ChatHandler {}
|
||||||
|
|
||||||
|
@ -34,13 +36,13 @@ impl ChatHandler {
|
||||||
|
|
||||||
// send player spawn
|
// send player spawn
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
unk1: 20,
|
unk1: 20,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
op_code: IPCOpCode::PlayerSpawn,
|
op_code: ServerZoneIpcType::PlayerSpawn,
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::PlayerSpawn(PlayerSpawn {
|
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
||||||
some_unique_id: 1,
|
some_unique_id: 1,
|
||||||
content_id: 1,
|
content_id: 1,
|
||||||
current_world_id: WORLD_ID,
|
current_world_id: WORLD_ID,
|
||||||
|
|
|
@ -2,19 +2,24 @@ use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::timestamp_secs,
|
common::timestamp_secs,
|
||||||
ipc::{ActorSetPos, IPCOpCode, IPCSegment, IPCStructData},
|
|
||||||
packet::{
|
packet::{
|
||||||
CompressionType, ConnectionType, PacketSegment, SegmentType, State, parse_packet,
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, parse_packet,
|
||||||
send_packet,
|
send_packet,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{InitZone, Position, UpdateClassInfo, Zone};
|
use super::{
|
||||||
|
Zone,
|
||||||
|
ipc::{
|
||||||
|
ActorSetPos, ClientZoneIpcSegment, InitZone, Position, ServerZoneIpcData,
|
||||||
|
ServerZoneIpcSegment, ServerZoneIpcType, UpdateClassInfo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ZoneConnection {
|
pub struct ZoneConnection {
|
||||||
pub socket: TcpStream,
|
pub socket: TcpStream,
|
||||||
|
|
||||||
pub state: State,
|
pub state: PacketState,
|
||||||
pub player_id: u32,
|
pub player_id: u32,
|
||||||
|
|
||||||
pub zone: Zone,
|
pub zone: Zone,
|
||||||
|
@ -22,11 +27,14 @@ pub struct ZoneConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZoneConnection {
|
impl ZoneConnection {
|
||||||
pub async fn parse_packet(&mut self, data: &[u8]) -> (Vec<PacketSegment>, ConnectionType) {
|
pub async fn parse_packet(
|
||||||
|
&mut self,
|
||||||
|
data: &[u8],
|
||||||
|
) -> (Vec<PacketSegment<ClientZoneIpcSegment>>, ConnectionType) {
|
||||||
parse_packet(data, &mut self.state).await
|
parse_packet(data, &mut self.state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_segment(&mut self, segment: PacketSegment) {
|
pub async fn send_segment(&mut self, segment: PacketSegment<ServerZoneIpcSegment>) {
|
||||||
send_packet(
|
send_packet(
|
||||||
&mut self.socket,
|
&mut self.socket,
|
||||||
&[segment],
|
&[segment],
|
||||||
|
@ -39,10 +47,10 @@ impl ZoneConnection {
|
||||||
pub async fn set_player_position(&mut self, position: Position) {
|
pub async fn set_player_position(&mut self, position: Position) {
|
||||||
// set pos
|
// set pos
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::ActorSetPos,
|
op_code: ServerZoneIpcType::ActorSetPos,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::ActorSetPos(ActorSetPos {
|
data: ServerZoneIpcData::ActorSetPos(ActorSetPos {
|
||||||
unk: 0x020fa3b8,
|
unk: 0x020fa3b8,
|
||||||
position,
|
position,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -70,10 +78,10 @@ impl ZoneConnection {
|
||||||
|
|
||||||
// Player Class Info
|
// Player Class Info
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::UpdateClassInfo,
|
op_code: ServerZoneIpcType::UpdateClassInfo,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::UpdateClassInfo(UpdateClassInfo {
|
data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo {
|
||||||
class_id: 35,
|
class_id: 35,
|
||||||
unknown: 1,
|
unknown: 1,
|
||||||
synced_level: 90,
|
synced_level: 90,
|
||||||
|
@ -93,10 +101,10 @@ impl ZoneConnection {
|
||||||
|
|
||||||
// link shell information
|
// link shell information
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::LinkShellInformation,
|
op_code: ServerZoneIpcType::LinkShellInformation,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::LinkShellInformation { unk: [0; 456] },
|
data: ServerZoneIpcData::LinkShellInformation { unk: [0; 456] },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,10 +120,10 @@ impl ZoneConnection {
|
||||||
|
|
||||||
// Init Zone
|
// Init Zone
|
||||||
{
|
{
|
||||||
let ipc = IPCSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: IPCOpCode::InitZone,
|
op_code: ServerZoneIpcType::InitZone,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::InitZone(InitZone {
|
data: ServerZoneIpcData::InitZone(InitZone {
|
||||||
server_id: 0,
|
server_id: 0,
|
||||||
zone_id: self.zone.id,
|
zone_id: self.zone.id,
|
||||||
weather_id: 1,
|
weather_id: 1,
|
||||||
|
|
465
src/world/ipc/mod.rs
Normal file
465
src/world/ipc/mod.rs
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
mod chat_message;
|
||||||
|
use binrw::binrw;
|
||||||
|
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;
|
||||||
|
|
||||||
|
mod player_spawn;
|
||||||
|
pub use player_spawn::CharacterMode;
|
||||||
|
pub use player_spawn::PlayerSpawn;
|
||||||
|
|
||||||
|
mod position;
|
||||||
|
pub use position::Position;
|
||||||
|
|
||||||
|
mod status_effect;
|
||||||
|
pub use status_effect::StatusEffect;
|
||||||
|
|
||||||
|
mod update_class_info;
|
||||||
|
pub use update_class_info::UpdateClassInfo;
|
||||||
|
|
||||||
|
mod player_setup;
|
||||||
|
pub use player_setup::PlayerSetup;
|
||||||
|
|
||||||
|
mod player_stats;
|
||||||
|
pub use player_stats::PlayerStats;
|
||||||
|
|
||||||
|
mod actor_control_self;
|
||||||
|
pub use actor_control_self::ActorControlSelf;
|
||||||
|
pub use actor_control_self::ActorControlType;
|
||||||
|
|
||||||
|
mod init_zone;
|
||||||
|
pub use init_zone::InitZone;
|
||||||
|
|
||||||
|
use crate::common::read_string;
|
||||||
|
use crate::common::write_string;
|
||||||
|
use crate::packet::IpcSegment;
|
||||||
|
use crate::packet::IpcSegmentTrait;
|
||||||
|
|
||||||
|
pub type ClientZoneIpcSegment = IpcSegment<ClientZoneIpcType, ClientZoneIpcData>;
|
||||||
|
|
||||||
|
impl IpcSegmentTrait for ClientZoneIpcSegment {
|
||||||
|
fn calc_size(&self) -> u32 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make generic
|
||||||
|
impl Default for ClientZoneIpcSegment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unk1: 0x14,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ClientZoneIpcType::InitRequest,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ClientZoneIpcData::InitRequest { unk: [0; 105] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ServerZoneIpcSegment = IpcSegment<ServerZoneIpcType, ServerZoneIpcData>;
|
||||||
|
|
||||||
|
impl IpcSegmentTrait for ServerZoneIpcSegment {
|
||||||
|
fn calc_size(&self) -> u32 {
|
||||||
|
// 16 is the size of the IPC header
|
||||||
|
16 + match self.op_code {
|
||||||
|
ServerZoneIpcType::InitializeChat => 8,
|
||||||
|
ServerZoneIpcType::InitZone => 103,
|
||||||
|
ServerZoneIpcType::ActorControlSelf => 32,
|
||||||
|
ServerZoneIpcType::PlayerStats => 224,
|
||||||
|
ServerZoneIpcType::PlayerSetup => 2784,
|
||||||
|
ServerZoneIpcType::UpdateClassInfo => 48,
|
||||||
|
ServerZoneIpcType::PlayerSpawn => 656,
|
||||||
|
ServerZoneIpcType::InitResponse => 16,
|
||||||
|
ServerZoneIpcType::LogOutComplete => 8,
|
||||||
|
ServerZoneIpcType::ActorSetPos => 24,
|
||||||
|
ServerZoneIpcType::ServerChatMessage => 776,
|
||||||
|
ServerZoneIpcType::Unk8 => 808,
|
||||||
|
ServerZoneIpcType::LinkShellInformation => 456,
|
||||||
|
ServerZoneIpcType::Unk9 => 24,
|
||||||
|
ServerZoneIpcType::Unk10 => 8,
|
||||||
|
ServerZoneIpcType::Unk11 => 32,
|
||||||
|
ServerZoneIpcType::Unk15 => 8,
|
||||||
|
ServerZoneIpcType::Unk16 => 136,
|
||||||
|
ServerZoneIpcType::ActorControl => 24,
|
||||||
|
ServerZoneIpcType::ActorMove => 16,
|
||||||
|
ServerZoneIpcType::Unk17 => 104,
|
||||||
|
ServerZoneIpcType::SocialList => 1136,
|
||||||
|
ServerZoneIpcType::PrepareZoning => 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make generic
|
||||||
|
impl Default for ServerZoneIpcSegment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unk1: 0x14,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerZoneIpcType::InitializeChat,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerZoneIpcData::InitializeChat { unk: [0; 8] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to their own files
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ActorSetPos {
|
||||||
|
pub unk: u32,
|
||||||
|
pub layer_id: u32,
|
||||||
|
pub position: Position,
|
||||||
|
pub unk3: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum GameMasterCommandType {
|
||||||
|
ChangeTerritory = 0x58,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u16)]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum ServerZoneIpcType {
|
||||||
|
/// Sent by the server to Initialize something chat-related?
|
||||||
|
InitializeChat = 0x2,
|
||||||
|
/// Sent by the server that tells the client which zone to load
|
||||||
|
InitZone = 0x0311,
|
||||||
|
/// Sent by the server for... something
|
||||||
|
ActorControlSelf = 0x018C,
|
||||||
|
/// Sent by the server containing character stats
|
||||||
|
PlayerStats = 0x01FA,
|
||||||
|
/// Sent by the server to setup the player on the client
|
||||||
|
PlayerSetup = 0x006B,
|
||||||
|
// Sent by the server to setup class info
|
||||||
|
UpdateClassInfo = 0x006A,
|
||||||
|
// Sent by the server to spawn the player in
|
||||||
|
PlayerSpawn = 0x1AB,
|
||||||
|
/// Sent by the server as response to ZoneInitRequest.
|
||||||
|
InitResponse = 0x2D0,
|
||||||
|
// Sent by the server to indicate the log out is complete
|
||||||
|
LogOutComplete = 0x369,
|
||||||
|
// Sent by the server to modify the client's position
|
||||||
|
ActorSetPos = 0x223,
|
||||||
|
// Sent by the server when they send a chat message
|
||||||
|
ServerChatMessage = 0x196,
|
||||||
|
// Unknown, server sends to the client before player spawn
|
||||||
|
Unk8 = 0x134,
|
||||||
|
// Unknown, but seems to contain information on cross-world linkshells
|
||||||
|
LinkShellInformation = 0x234,
|
||||||
|
// Unknown, server sends to the client before player spawn
|
||||||
|
Unk9 = 0x189,
|
||||||
|
// Unknown, server sends to the client before player spawn.
|
||||||
|
// Seems to the same across two different characters?
|
||||||
|
Unk10 = 0x110,
|
||||||
|
// Unknown, server sends this in response to Unk7
|
||||||
|
Unk11 = 0x156,
|
||||||
|
// Sent by the server when it wants the client to... prepare to zone?
|
||||||
|
PrepareZoning = 0x308,
|
||||||
|
// 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]
|
||||||
|
#[brw(repr = u16)]
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum ClientZoneIpcType {
|
||||||
|
/// 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 client when they're done loading and they need to be spawned in
|
||||||
|
FinishLoading = 0x397, // TODO: assumed
|
||||||
|
// 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 = 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
|
||||||
|
Unk5 = 0x2D0,
|
||||||
|
// 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
|
||||||
|
// Sent by the client when the user requests to log out
|
||||||
|
LogOut = 0x217,
|
||||||
|
// Sent by the client when it's actually disconnecting
|
||||||
|
Disconnected = 0x360,
|
||||||
|
// Sent by the client when they send a chat message
|
||||||
|
ChatMessage = 0xCA,
|
||||||
|
// Sent by the client when they send a GM command. This can only be sent by the client if they are sent a GM rank.
|
||||||
|
GameMasterCommand = 0x3B3,
|
||||||
|
// Unknown, client sends this for ???
|
||||||
|
Unk12 = 0x0E9,
|
||||||
|
// Sent by the client when the character walks into a zone transistion
|
||||||
|
EnterZoneLine = 0x205,
|
||||||
|
// Sent by the client after we sent a InitZone in TravelToZone??
|
||||||
|
// TODO: Actually, I don't think is real...
|
||||||
|
Unk13 = 0x2EE,
|
||||||
|
// Sent by the client for unknown reasons
|
||||||
|
Unk14 = 0x87,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[br(import(_magic: &ServerZoneIpcType))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ServerZoneIpcData {
|
||||||
|
InitializeChat {
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
InitResponse {
|
||||||
|
unk1: u64,
|
||||||
|
character_id: u32,
|
||||||
|
unk2: u32,
|
||||||
|
},
|
||||||
|
InitZone(InitZone),
|
||||||
|
ActorControlSelf(ActorControlSelf),
|
||||||
|
PlayerStats(PlayerStats),
|
||||||
|
PlayerSetup(PlayerSetup),
|
||||||
|
UpdateClassInfo(UpdateClassInfo),
|
||||||
|
PlayerSpawn(PlayerSpawn),
|
||||||
|
LogOutComplete {
|
||||||
|
// TODO: guessed
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
ActorSetPos(ActorSetPos),
|
||||||
|
ServerChatMessage {
|
||||||
|
unk: u8, // channel?
|
||||||
|
#[brw(pad_after = 775)]
|
||||||
|
#[br(count = 775)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
Unk8 {
|
||||||
|
unk: [u8; 808],
|
||||||
|
},
|
||||||
|
LinkShellInformation {
|
||||||
|
unk: [u8; 456],
|
||||||
|
},
|
||||||
|
Unk9 {
|
||||||
|
unk: [u8; 24],
|
||||||
|
},
|
||||||
|
Unk10 {
|
||||||
|
unk: u64,
|
||||||
|
},
|
||||||
|
Unk11 {
|
||||||
|
timestamp: u32,
|
||||||
|
#[brw(pad_after = 24)] // empty bytes
|
||||||
|
unk: u32,
|
||||||
|
},
|
||||||
|
PrepareZoning {
|
||||||
|
unk: [u32; 4],
|
||||||
|
},
|
||||||
|
Unk15 {
|
||||||
|
unk: u32,
|
||||||
|
player_id: u32,
|
||||||
|
},
|
||||||
|
Unk16 {
|
||||||
|
unk: [u8; 136],
|
||||||
|
},
|
||||||
|
ActorControl {
|
||||||
|
#[brw(pad_after = 20)] // empty
|
||||||
|
unk: u32,
|
||||||
|
},
|
||||||
|
ActorMove {
|
||||||
|
#[brw(pad_after = 4)] // empty
|
||||||
|
pos: Position,
|
||||||
|
},
|
||||||
|
Unk17 {
|
||||||
|
unk: [u8; 104],
|
||||||
|
},
|
||||||
|
SocialList(SocialList),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[br(import(magic: &ClientZoneIpcType))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ClientZoneIpcData {
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::InitRequest))]
|
||||||
|
InitRequest {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
#[br(dbg)]
|
||||||
|
unk: [u8; 105],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::FinishLoading))]
|
||||||
|
FinishLoading {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 72],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk1))]
|
||||||
|
Unk1 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 32],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk2))]
|
||||||
|
Unk2 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk3))]
|
||||||
|
Unk3 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk4))]
|
||||||
|
Unk4 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::SetSearchInfoHandler))]
|
||||||
|
SetSearchInfoHandler {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk5))]
|
||||||
|
Unk5 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::SocialListRequest))]
|
||||||
|
SocialListRequest(SocialListRequest),
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk7))]
|
||||||
|
Unk7 {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
timestamp: u32,
|
||||||
|
#[brw(pad_before = 8)] // empty bytes
|
||||||
|
#[brw(pad_after = 4)] // empty bytes
|
||||||
|
unk1: [u8; 16], // something
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::UpdatePositionHandler))]
|
||||||
|
UpdatePositionHandler {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 24],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::LogOut))]
|
||||||
|
LogOut {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Disconnected))]
|
||||||
|
Disconnected {
|
||||||
|
// TODO: full of possibly interesting information
|
||||||
|
unk: [u8; 8],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::ChatMessage))]
|
||||||
|
ChatMessage(ChatMessage),
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::GameMasterCommand))]
|
||||||
|
GameMasterCommand {
|
||||||
|
// TODO: incomplete
|
||||||
|
command: GameMasterCommandType,
|
||||||
|
#[br(pad_before = 3)] // idk, not empty though
|
||||||
|
arg: u32,
|
||||||
|
#[br(dbg)]
|
||||||
|
unk: [u8; 24],
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk12))]
|
||||||
|
Unk12 {
|
||||||
|
unk: [u8; 8], // TODO: unknown
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::EnterZoneLine))]
|
||||||
|
EnterZoneLine {
|
||||||
|
exit_box_id: u32,
|
||||||
|
position: Position,
|
||||||
|
#[brw(pad_after = 4)] // empty
|
||||||
|
landset_index: i32,
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk13))]
|
||||||
|
Unk13 {
|
||||||
|
#[br(dbg)]
|
||||||
|
unk: [u8; 16], // TODO: unknown
|
||||||
|
},
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk14))]
|
||||||
|
Unk14 {
|
||||||
|
#[br(dbg)]
|
||||||
|
unk: [u8; 8], // TODO: unknown
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use binrw::BinWrite;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Ensure that the IPC data size as reported matches up with what we write
|
||||||
|
#[test]
|
||||||
|
fn world_ipc_sizes() {
|
||||||
|
let ipc_types = [
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::ActorControlSelf,
|
||||||
|
ServerZoneIpcData::ActorControlSelf(ActorControlSelf::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::InitializeChat,
|
||||||
|
ServerZoneIpcData::InitializeChat { unk: [0; 8] },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::PlayerStats,
|
||||||
|
ServerZoneIpcData::PlayerStats(PlayerStats::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::PlayerSetup,
|
||||||
|
ServerZoneIpcData::PlayerSetup(PlayerSetup::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::UpdateClassInfo,
|
||||||
|
ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::PlayerSpawn,
|
||||||
|
ServerZoneIpcData::PlayerSpawn(PlayerSpawn::default()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::ActorSetPos,
|
||||||
|
ServerZoneIpcData::ActorSetPos(ActorSetPos::default()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, data) in &ipc_types {
|
||||||
|
let mut cursor = Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
let ipc_segment = ServerZoneIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: opcode.clone(), // doesn't matter for this test
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: data.clone(),
|
||||||
|
};
|
||||||
|
ipc_segment.write_le(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let buffer = cursor.into_inner();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buffer.len(),
|
||||||
|
ipc_segment.calc_size() as usize,
|
||||||
|
"{:#?} did not match size!",
|
||||||
|
opcode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,4 @@
|
||||||
mod player_spawn;
|
pub mod ipc;
|
||||||
pub use player_spawn::CharacterMode;
|
|
||||||
pub use player_spawn::PlayerSpawn;
|
|
||||||
|
|
||||||
mod position;
|
|
||||||
pub use position::Position;
|
|
||||||
|
|
||||||
mod status_effect;
|
|
||||||
pub use status_effect::StatusEffect;
|
|
||||||
|
|
||||||
mod update_class_info;
|
|
||||||
pub use update_class_info::UpdateClassInfo;
|
|
||||||
|
|
||||||
mod player_setup;
|
|
||||||
pub use player_setup::PlayerSetup;
|
|
||||||
|
|
||||||
mod player_stats;
|
|
||||||
pub use player_stats::PlayerStats;
|
|
||||||
|
|
||||||
mod actor_control_self;
|
|
||||||
pub use actor_control_self::ActorControlSelf;
|
|
||||||
pub use actor_control_self::ActorControlType;
|
|
||||||
|
|
||||||
mod init_zone;
|
|
||||||
pub use init_zone::InitZone;
|
|
||||||
|
|
||||||
mod zone;
|
mod zone;
|
||||||
pub use zone::Zone;
|
pub use zone::Zone;
|
||||||
|
@ -32,12 +8,3 @@ pub use chat_handler::ChatHandler;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
pub use connection::ZoneConnection;
|
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;
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue