mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-22 23:27:46 +00:00
Start implementing some world IPC
This doesn't work yet, but whatever it's a start.
This commit is contained in:
parent
e3886e69da
commit
660e12c597
9 changed files with 835 additions and 69 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -18,6 +18,13 @@ name = "kawari-patch"
|
|||
[[bin]]
|
||||
name = "kawari-web"
|
||||
|
||||
[[bin]]
|
||||
name = "kawari-lobby"
|
||||
|
||||
[[bin]]
|
||||
name = "kawari-world"
|
||||
required-features = ["oodle"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
|
@ -25,6 +32,10 @@ opt-level = "z"
|
|||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = ["oodle"]
|
||||
oodle = []
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
|
||||
use kawari::encryption::{blowfish_encode, generate_encryption_key};
|
||||
use kawari::ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, Server, ServiceAccount};
|
||||
use kawari::oodle::FFXIVOodle;
|
||||
use kawari::packet::{
|
||||
PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
||||
};
|
||||
use kawari::{CONTENT_ID, WORLD_ID, WORLD_NAME, ZONE_ID};
|
||||
use tokio::io::{AsyncReadExt, WriteHalf};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
|
@ -25,6 +27,8 @@ async fn main() {
|
|||
let mut state = State {
|
||||
client_key: None,
|
||||
session_id: None,
|
||||
oodle: FFXIVOodle::new(),
|
||||
player_id: None,
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -33,7 +37,7 @@ async fn main() {
|
|||
let n = read.read(&mut buf).await.expect("Failed to read data!");
|
||||
|
||||
if n != 0 {
|
||||
let segments = parse_packet(&buf[..n], &mut state).await;
|
||||
let (segments, _) = parse_packet(&buf[..n], &mut state).await;
|
||||
for segment in &segments {
|
||||
match &segment.segment_type {
|
||||
SegmentType::InitializeEncryption { phrase, key } => {
|
||||
|
@ -50,12 +54,12 @@ async fn main() {
|
|||
|
||||
state.session_id = Some(session_id.clone());
|
||||
|
||||
send_account_list(&mut write, &state).await;
|
||||
send_account_list(&mut write, &mut state).await;
|
||||
}
|
||||
IPCStructData::RequestCharacterList { sequence } => {
|
||||
tracing::info!("Client is requesting character list...");
|
||||
|
||||
send_lobby_info(&mut write, &state, *sequence).await;
|
||||
send_lobby_info(&mut write, &mut state, *sequence).await;
|
||||
}
|
||||
IPCStructData::LobbyCharacterAction {
|
||||
sequence,
|
||||
|
@ -79,7 +83,7 @@ async fn main() {
|
|||
} => {
|
||||
tracing::info!("Client is joining the world...");
|
||||
|
||||
send_enter_world(&mut write, &state, *sequence, *lookup_id)
|
||||
send_enter_world(&mut write, &mut state, *sequence, *lookup_id)
|
||||
.await;
|
||||
}
|
||||
_ => {
|
||||
|
@ -87,7 +91,7 @@ async fn main() {
|
|||
}
|
||||
},
|
||||
SegmentType::KeepAlive { id, timestamp } => {
|
||||
send_keep_alive(&mut write, &state, *id, *timestamp).await
|
||||
send_keep_alive(&mut write, &mut state, *id, *timestamp).await
|
||||
}
|
||||
_ => {
|
||||
panic!("The server is recieving a response packet!")
|
||||
|
@ -125,7 +129,7 @@ async fn initialize_encryption(
|
|||
send_packet(socket, &[response_packet], state).await;
|
||||
}
|
||||
|
||||
async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &State) {
|
||||
async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &mut State) {
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
|
@ -167,12 +171,7 @@ async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &State) {
|
|||
send_packet(socket, &[response_packet], state).await;
|
||||
}
|
||||
|
||||
// TODO: make this configurable
|
||||
// See https://ffxiv.consolegameswiki.com/wiki/Servers for a list of possible IDs
|
||||
const WORLD_ID: u16 = 63;
|
||||
const WORLD_NAME: &str = "KAWARI";
|
||||
|
||||
async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &State, sequence: u64) {
|
||||
async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &mut State, sequence: u64) {
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
|
@ -256,7 +255,7 @@ async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &State, seque
|
|||
guardian: 2,
|
||||
unk8: 0,
|
||||
unk9: 0,
|
||||
zone_id: 1000,
|
||||
zone_id: ZONE_ID as i32,
|
||||
unk11: 0,
|
||||
customize: ClientCustomizeData {
|
||||
race: 3,
|
||||
|
@ -301,7 +300,7 @@ async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &State, seque
|
|||
|
||||
let mut characters = vec![CharacterDetails {
|
||||
id: 0,
|
||||
content_id: 11111111111111111,
|
||||
content_id: CONTENT_ID,
|
||||
index: 0,
|
||||
unk1: [0; 16],
|
||||
origin_server_id: WORLD_ID,
|
||||
|
@ -387,7 +386,7 @@ async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &State, seque
|
|||
|
||||
async fn send_enter_world(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
state: &State,
|
||||
state: &mut State,
|
||||
sequence: u64,
|
||||
lookup_id: u64,
|
||||
) {
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
use kawari::packet::{SegmentType, State, parse_packet, send_keep_alive};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use kawari::ipc::{ActorControlType, IPCOpCode, IPCSegment, IPCStructData, Position};
|
||||
use kawari::oodle::FFXIVOodle;
|
||||
use kawari::packet::{
|
||||
PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
||||
};
|
||||
use kawari::{CONTENT_ID, WORLD_ID, ZONE_ID};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
|
@ -17,6 +24,8 @@ async fn main() {
|
|||
let mut state = State {
|
||||
client_key: None,
|
||||
session_id: None,
|
||||
oodle: FFXIVOodle::new(),
|
||||
player_id: None,
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -25,14 +34,435 @@ async fn main() {
|
|||
let n = read.read(&mut buf).await.expect("Failed to read data!");
|
||||
|
||||
if n != 0 {
|
||||
let segments = parse_packet(&buf[..n], &mut state).await;
|
||||
println!("recieved {n} bytes...");
|
||||
let (segments, connection_type) = parse_packet(&buf[..n], &mut state).await;
|
||||
for segment in &segments {
|
||||
match &segment.segment_type {
|
||||
SegmentType::InitializeSession { player_id } => {
|
||||
state.player_id = Some(*player_id);
|
||||
|
||||
// We have send THEM a keep alive
|
||||
{
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::KeepAlive {
|
||||
id: 0xE0037603u32,
|
||||
timestamp,
|
||||
},
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state).await;
|
||||
}
|
||||
|
||||
match connection_type {
|
||||
kawari::packet::ConnectionType::Zone => {
|
||||
tracing::info!(
|
||||
"Client {player_id} is initializing zone session..."
|
||||
);
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
kawari::packet::ConnectionType::Chat => {
|
||||
tracing::info!(
|
||||
"Client {player_id} is initializing chat session..."
|
||||
);
|
||||
|
||||
{
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::InitializeChat,
|
||||
server_id: 0,
|
||||
timestamp: 0,
|
||||
data: IPCStructData::InitializeChat {
|
||||
unk: [0; 24],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: *player_id,
|
||||
target_actor: *player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
"The client is trying to initialize the wrong connection?!"
|
||||
),
|
||||
}
|
||||
}
|
||||
SegmentType::Ipc { data } => {
|
||||
panic!("The server is recieving a IPC response or unknown packet!")
|
||||
match &data.data {
|
||||
IPCStructData::InitRequest { .. } => {
|
||||
tracing::info!(
|
||||
"Client is now requesting zone information. Sending!"
|
||||
);
|
||||
|
||||
let timestamp_secs = || {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// IPC Init(?)
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::InitResponse,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::InitResponse {
|
||||
unk1: 0,
|
||||
character_id: state.player_id.unwrap(),
|
||||
unk2: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Control Data
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::ActorControlSelf,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::ActorControlSelf {
|
||||
category: ActorControlType::SetCharaGearParamUI,
|
||||
param1: 1,
|
||||
param2: 1,
|
||||
param3: 0,
|
||||
param4: 0,
|
||||
param5: 0,
|
||||
param6: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Stats
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PlayerStats,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PlayerStats {
|
||||
strength: 1,
|
||||
dexterity: 0,
|
||||
vitality: 0,
|
||||
intelligence: 0,
|
||||
mind: 0,
|
||||
piety: 0,
|
||||
hp: 100,
|
||||
mp: 100,
|
||||
tp: 0,
|
||||
gp: 0,
|
||||
cp: 0,
|
||||
delay: 0,
|
||||
tenacity: 0,
|
||||
attack_power: 0,
|
||||
defense: 0,
|
||||
direct_hit_rate: 0,
|
||||
evasion: 0,
|
||||
magic_defense: 0,
|
||||
critical_hit: 0,
|
||||
attack_magic_potency: 0,
|
||||
healing_magic_potency: 0,
|
||||
elemental_bonus: 0,
|
||||
determination: 0,
|
||||
skill_speed: 0,
|
||||
spell_speed: 0,
|
||||
haste: 0,
|
||||
craftmanship: 0,
|
||||
control: 0,
|
||||
gathering: 0,
|
||||
perception: 0,
|
||||
unk1: [0; 26],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Player Setup
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PlayerSetup,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PlayerSetup {
|
||||
content_id: CONTENT_ID,
|
||||
crest: 0,
|
||||
unknown10: 0,
|
||||
char_id: 0,
|
||||
rested_exp: 0,
|
||||
companion_current_exp: 0,
|
||||
unknown1c: 0,
|
||||
fish_caught: 0,
|
||||
use_bait_catalog_id: 0,
|
||||
unknown28: 0,
|
||||
unknown_pvp2c: 0,
|
||||
unknown2e: 0,
|
||||
pvp_frontline_overall_campaigns: 0,
|
||||
unknown_timestamp34: 0,
|
||||
unknown_timestamp38: 0,
|
||||
unknown3c: 0,
|
||||
unknown40: 0,
|
||||
unknown44: 0,
|
||||
companion_time_passed: 0.0,
|
||||
unknown4c: 0,
|
||||
unknown50: 0,
|
||||
unknown_pvp52: [0; 4],
|
||||
pvp_series_exp: 0,
|
||||
player_commendations: 0,
|
||||
unknown64: [0; 8],
|
||||
pvp_rival_wings_total_matches: 0,
|
||||
pvp_rival_wings_total_victories: 0,
|
||||
pvp_rival_wings_weekly_matches: 0,
|
||||
pvp_rival_wings_weekly_victories: 0,
|
||||
max_level: 0,
|
||||
expansion: 0,
|
||||
unknown76: 0,
|
||||
unknown77: 0,
|
||||
unknown78: 0,
|
||||
race: 0,
|
||||
tribe: 0,
|
||||
gender: 0,
|
||||
current_job: 0,
|
||||
current_class: 0,
|
||||
deity: 0,
|
||||
nameday_month: 0,
|
||||
nameday_day: 0,
|
||||
city_state: 0,
|
||||
homepoint: 0,
|
||||
unknown8d: [0; 3],
|
||||
companion_rank: 0,
|
||||
companion_stars: 0,
|
||||
companion_sp: 0,
|
||||
companion_unk93: 0,
|
||||
companion_color: 0,
|
||||
companion_fav_feed: 0,
|
||||
fav_aetheryte_count: 0,
|
||||
unknown97: [0; 5],
|
||||
sightseeing21_to_80_unlock: 0,
|
||||
sightseeing_heavensward_unlock: 0,
|
||||
unknown9e: [0; 26],
|
||||
exp: [10000; 32],
|
||||
pvp_total_exp: 0,
|
||||
unknown_pvp124: 0,
|
||||
pvp_exp: 0,
|
||||
pvp_frontline_overall_ranks: [0; 3],
|
||||
unknown138: 0,
|
||||
levels: [100; 32],
|
||||
unknown194: [0; 218],
|
||||
companion_name: [0; 21],
|
||||
companion_def_rank: 0,
|
||||
companion_att_rank: 0,
|
||||
companion_heal_rank: 0,
|
||||
mount_guide_mask: [0; 33],
|
||||
ornament_mask: [0; 4],
|
||||
unknown281: [0; 23],
|
||||
name: "KAWARI".to_string(),
|
||||
unknown293: [0; 16],
|
||||
unknown2a3: 0,
|
||||
unlock_bitmask: [0; 64],
|
||||
aetheryte: [0; 26],
|
||||
favorite_aetheryte_ids: [0; 4],
|
||||
free_aetheryte_id: 0,
|
||||
ps_plus_free_aetheryte_id: 0,
|
||||
discovery: [0; 480],
|
||||
howto: [0; 36],
|
||||
unknown554: [0; 4],
|
||||
minions: [0; 60],
|
||||
chocobo_taxi_mask: [0; 12],
|
||||
watched_cutscenes: [0; 159],
|
||||
companion_barding_mask: [0; 12],
|
||||
companion_equipped_head: 0,
|
||||
companion_equipped_body: 0,
|
||||
companion_equipped_legs: 0,
|
||||
unknown_mask: [0; 287],
|
||||
pose: [0; 7],
|
||||
unknown6df: [0; 3],
|
||||
challenge_log_complete: [0; 13],
|
||||
secret_recipe_book_mask: [0; 12],
|
||||
unknown_mask6f7: [0; 29],
|
||||
relic_completion: [0; 12],
|
||||
sightseeing_mask: [0; 37],
|
||||
hunting_mark_mask: [0; 102],
|
||||
triple_triad_cards: [0; 45],
|
||||
unknown895: 0,
|
||||
unknown7d7: [0; 15],
|
||||
unknown7d8: 0,
|
||||
unknown7e6: [0; 49],
|
||||
regional_folklore_mask: [0; 6],
|
||||
orchestrion_mask: [0; 87],
|
||||
hall_of_novice_completion: [0; 3],
|
||||
anima_completion: [0; 11],
|
||||
unknown85e: [0; 41],
|
||||
unlocked_raids: [0; 28],
|
||||
unlocked_dungeons: [0; 18],
|
||||
unlocked_guildhests: [0; 10],
|
||||
unlocked_trials: [0; 12],
|
||||
unlocked_pvp: [0; 5],
|
||||
cleared_raids: [0; 28],
|
||||
cleared_dungeons: [0; 18],
|
||||
cleared_guildhests: [0; 10],
|
||||
cleared_trials: [0; 12],
|
||||
cleared_pvp: [0; 5],
|
||||
unknown948: [0; 15],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Player Class Info
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::UpdateClassInfo,
|
||||
server_id: 69, // lol
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::UpdateClassInfo {
|
||||
class_id: 35,
|
||||
unknown: 1,
|
||||
is_specialist: 0,
|
||||
synced_level: 90,
|
||||
class_level: 90,
|
||||
role_actions: [0; 10],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Init Zone
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::InitZone,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::InitZone {
|
||||
server_id: WORLD_ID,
|
||||
zone_id: ZONE_ID,
|
||||
zone_index: 0,
|
||||
content_finder_condition_id: 0,
|
||||
layer_set_id: 0,
|
||||
layout_id: 0,
|
||||
weather_id: 1,
|
||||
unk_bitmask1: 0x10,
|
||||
unk_bitmask2: 0,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
festival_id: 0,
|
||||
additional_festival_id: 0,
|
||||
unk3: 0,
|
||||
unk4: 0,
|
||||
unk5: 0,
|
||||
unk6: [0; 4],
|
||||
unk7: [0; 3],
|
||||
position: Position {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
},
|
||||
unk8: [0; 4],
|
||||
unk9: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(&mut write, &[response_packet], &mut state)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
"The server is recieving a IPC response or unknown packet!"
|
||||
),
|
||||
}
|
||||
}
|
||||
SegmentType::KeepAlive { id, timestamp } => {
|
||||
send_keep_alive(&mut write, &state, *id, *timestamp).await
|
||||
send_keep_alive(&mut write, &mut state, *id, *timestamp).await
|
||||
}
|
||||
SegmentType::KeepAliveResponse { .. } => {
|
||||
tracing::info!("Got keep alive response from client... cool...");
|
||||
}
|
||||
_ => {
|
||||
panic!("The server is recieving a response or unknown packet!")
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use std::fs::write;
|
||||
use std::io::Cursor;
|
||||
|
||||
use binrw::{BinRead, BinResult};
|
||||
|
||||
use crate::{
|
||||
oodle::FFXIVOodle,
|
||||
oodle::{FFXIVOodle, Oodle},
|
||||
packet::{PacketHeader, PacketSegment},
|
||||
};
|
||||
|
||||
#[binrw::parser(reader, endian)]
|
||||
pub(crate) fn decompress(
|
||||
oodle: &mut FFXIVOodle,
|
||||
header: &PacketHeader,
|
||||
encryption_key: Option<&[u8]>,
|
||||
) -> BinResult<Vec<PacketSegment>> {
|
||||
|
@ -16,16 +18,23 @@ pub(crate) fn decompress(
|
|||
|
||||
let size = header.size as usize - std::mem::size_of::<PacketHeader>();
|
||||
|
||||
println!(
|
||||
"known packet size: {} but decompressing {} bytes",
|
||||
header.size, size
|
||||
);
|
||||
|
||||
let mut data = vec![0; size];
|
||||
reader.read_exact(&mut data)?;
|
||||
reader.read_exact(&mut data).unwrap();
|
||||
|
||||
write("compressed.bin", &data).unwrap();
|
||||
|
||||
let data = match header.compressed {
|
||||
crate::packet::CompressionType::Uncompressed => data,
|
||||
crate::packet::CompressionType::Oodle => {
|
||||
FFXIVOodle::new().decode(data, header.oodle_decompressed_size)
|
||||
}
|
||||
crate::packet::CompressionType::Oodle => oodle.decode(data, header.oodle_decompressed_size),
|
||||
};
|
||||
|
||||
write("decompressed.bin", &data).unwrap();
|
||||
|
||||
let mut cursor = Cursor::new(&data);
|
||||
|
||||
for _ in 0..header.segment_count {
|
||||
|
|
|
@ -36,23 +36,26 @@ pub(crate) fn decrypt<T>(size: u32, encryption_key: Option<&[u8]>) -> BinResult<
|
|||
where
|
||||
for<'a> T: BinRead<Args<'a> = ()> + 'a,
|
||||
{
|
||||
let Some(encryption_key) = encryption_key else {
|
||||
panic!("This segment type is encrypted and no key was provided!");
|
||||
};
|
||||
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
|
||||
let mut data = vec![0; size as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
let mut data = vec![0; size as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
unsafe {
|
||||
let decryption_result =
|
||||
blowfish_decode(encryption_key.as_ptr(), 16, data.as_ptr(), size);
|
||||
let decrypted_data = slice::from_raw_parts(decryption_result, size as usize);
|
||||
|
||||
unsafe {
|
||||
let decryption_result = blowfish_decode(encryption_key.as_ptr(), 16, data.as_ptr(), size);
|
||||
let decrypted_data = slice::from_raw_parts(decryption_result, size as usize);
|
||||
write("decrypted.bin", decrypted_data).unwrap();
|
||||
|
||||
write("decrypted.bin", decrypted_data).unwrap();
|
||||
let mut cursor = Cursor::new(&decrypted_data);
|
||||
T::read_options(&mut cursor, endian, ())
|
||||
}
|
||||
} else {
|
||||
tracing::info!("NOTE: Not decrypting this IPC packet since no key was provided!");
|
||||
|
||||
let mut cursor = Cursor::new(&decrypted_data);
|
||||
T::read_options(&mut cursor, endian, ())
|
||||
T::read_options(reader, endian, ())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,29 +64,31 @@ pub(crate) fn encrypt<T>(value: &T, size: u32, encryption_key: Option<&[u8]>) ->
|
|||
where
|
||||
for<'a> T: BinWrite<Args<'a> = ()> + 'a,
|
||||
{
|
||||
let Some(encryption_key) = encryption_key else {
|
||||
panic!("This segment type needs to be encrypted and no key was provided!");
|
||||
};
|
||||
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
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
value.write_options(&mut cursor, endian, ())?;
|
||||
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
value.write_options(&mut cursor, endian, ())?;
|
||||
let mut buffer = cursor.into_inner();
|
||||
buffer.resize(size as usize, 0);
|
||||
|
||||
let mut buffer = cursor.into_inner();
|
||||
buffer.resize(size as usize, 0);
|
||||
unsafe {
|
||||
let encoded = blowfish_encode(
|
||||
encryption_key.as_ptr(),
|
||||
16,
|
||||
buffer.as_ptr(),
|
||||
buffer.len() as u32,
|
||||
);
|
||||
let encoded_data = slice::from_raw_parts(encoded, size as usize);
|
||||
writer.write_all(encoded_data)?;
|
||||
|
||||
unsafe {
|
||||
let encoded = blowfish_encode(
|
||||
encryption_key.as_ptr(),
|
||||
16,
|
||||
buffer.as_ptr(),
|
||||
buffer.len() as u32,
|
||||
);
|
||||
let encoded_data = slice::from_raw_parts(encoded, size as usize);
|
||||
writer.write_all(encoded_data)?;
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
tracing::info!("NOTE: Not encrypting this IPC packet since no key was provided!");
|
||||
|
||||
Ok(())
|
||||
value.write_options(writer, endian, ())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
270
src/ipc.rs
270
src/ipc.rs
|
@ -2,10 +2,14 @@ use binrw::binrw;
|
|||
|
||||
use crate::common::{read_string, write_string};
|
||||
|
||||
// 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.
|
||||
|
@ -24,6 +28,21 @@ pub enum IPCOpCode {
|
|||
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 as response to ZoneInitRequest.
|
||||
InitResponse = 280, // TODO: probably wrong!
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -97,6 +116,21 @@ pub enum LobbyCharacterAction {
|
|||
Request = 0x15,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Position {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
#[brw(repr = u16)]
|
||||
pub enum ActorControlType {
|
||||
SetCharaGearParamUI = 0x260,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[br(import(magic: &IPCOpCode))]
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -144,9 +178,17 @@ pub enum IPCStructData {
|
|||
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],
|
||||
},
|
||||
|
||||
// Server->Client IPC
|
||||
#[br(pre_assert(false))]
|
||||
LobbyServiceAccountList {
|
||||
#[br(dbg)]
|
||||
sequence: u64,
|
||||
#[brw(pad_before = 1)]
|
||||
num_service_accounts: u8,
|
||||
|
@ -156,6 +198,7 @@ pub enum IPCStructData {
|
|||
#[br(count = 8)]
|
||||
service_accounts: Vec<ServiceAccount>,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
LobbyServerList {
|
||||
sequence: u64,
|
||||
unk1: u16,
|
||||
|
@ -165,12 +208,14 @@ pub enum IPCStructData {
|
|||
#[br(count = 6)]
|
||||
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,
|
||||
|
@ -196,6 +241,7 @@ pub enum IPCStructData {
|
|||
#[br(count = 2)]
|
||||
characters: Vec<CharacterDetails>,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
LobbyEnterWorld {
|
||||
sequence: u64,
|
||||
character_id: u32,
|
||||
|
@ -214,6 +260,219 @@ pub enum IPCStructData {
|
|||
#[bw(map = write_string)]
|
||||
host: String,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
InitializeChat { unk: [u8; 24] },
|
||||
#[br(pre_assert(false))]
|
||||
InitResponse {
|
||||
unk1: u64,
|
||||
character_id: u32,
|
||||
unk2: u32,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
InitZone {
|
||||
server_id: u16,
|
||||
zone_id: u16,
|
||||
zone_index: u16,
|
||||
content_finder_condition_id: u16,
|
||||
layer_set_id: u32,
|
||||
layout_id: u32,
|
||||
weather_id: u32,
|
||||
unk_bitmask1: u8,
|
||||
unk_bitmask2: u8,
|
||||
unk1: u8,
|
||||
unk2: u32,
|
||||
festival_id: u16,
|
||||
additional_festival_id: u16,
|
||||
unk3: u32,
|
||||
unk4: u32,
|
||||
unk5: u32,
|
||||
unk6: [u32; 4],
|
||||
unk7: [u32; 3],
|
||||
position: Position,
|
||||
unk8: [u32; 4],
|
||||
unk9: u32,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
ActorControlSelf {
|
||||
#[brw(pad_after = 2)]
|
||||
category: ActorControlType,
|
||||
param1: u32,
|
||||
param2: u32,
|
||||
param3: u32,
|
||||
param4: u32,
|
||||
param5: u32,
|
||||
#[brw(pad_after = 4)]
|
||||
param6: u32,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
PlayerStats {
|
||||
strength: u32,
|
||||
dexterity: u32,
|
||||
vitality: u32,
|
||||
intelligence: u32,
|
||||
mind: u32,
|
||||
piety: u32,
|
||||
hp: u32,
|
||||
mp: u32,
|
||||
tp: u32,
|
||||
gp: u32,
|
||||
cp: u32,
|
||||
delay: u32,
|
||||
tenacity: u32,
|
||||
attack_power: u32,
|
||||
defense: u32,
|
||||
direct_hit_rate: u32,
|
||||
evasion: u32,
|
||||
magic_defense: u32,
|
||||
critical_hit: u32,
|
||||
attack_magic_potency: u32,
|
||||
healing_magic_potency: u32,
|
||||
elemental_bonus: u32,
|
||||
determination: u32,
|
||||
skill_speed: u32,
|
||||
spell_speed: u32,
|
||||
haste: u32,
|
||||
craftmanship: u32,
|
||||
control: u32,
|
||||
gathering: u32,
|
||||
perception: u32,
|
||||
unk1: [u32; 26],
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
PlayerSetup {
|
||||
content_id: u64,
|
||||
crest: u64,
|
||||
unknown10: u64,
|
||||
char_id: u32,
|
||||
rested_exp: u32,
|
||||
companion_current_exp: u32,
|
||||
unknown1c: u32,
|
||||
fish_caught: u32,
|
||||
use_bait_catalog_id: u32,
|
||||
unknown28: u32,
|
||||
unknown_pvp2c: u16,
|
||||
unknown2e: u16,
|
||||
pvp_frontline_overall_campaigns: u32,
|
||||
unknown_timestamp34: u32,
|
||||
unknown_timestamp38: u32,
|
||||
unknown3c: u32,
|
||||
unknown40: u32,
|
||||
unknown44: u32,
|
||||
companion_time_passed: f32,
|
||||
unknown4c: u32,
|
||||
unknown50: u16,
|
||||
unknown_pvp52: [u16; 4],
|
||||
pvp_series_exp: u16,
|
||||
player_commendations: u16,
|
||||
unknown64: [u16; 8],
|
||||
pvp_rival_wings_total_matches: u16,
|
||||
pvp_rival_wings_total_victories: u16,
|
||||
pvp_rival_wings_weekly_matches: u16,
|
||||
pvp_rival_wings_weekly_victories: u16,
|
||||
max_level: u8,
|
||||
expansion: u8,
|
||||
unknown76: u8,
|
||||
unknown77: u8,
|
||||
unknown78: u8,
|
||||
race: u8,
|
||||
tribe: u8,
|
||||
gender: u8,
|
||||
current_job: u8,
|
||||
current_class: u8,
|
||||
deity: u8,
|
||||
nameday_month: u8,
|
||||
nameday_day: u8,
|
||||
city_state: u8,
|
||||
homepoint: u8,
|
||||
unknown8d: [u8; 3],
|
||||
companion_rank: u8,
|
||||
companion_stars: u8,
|
||||
companion_sp: u8,
|
||||
companion_unk93: u8,
|
||||
companion_color: u8,
|
||||
companion_fav_feed: u8,
|
||||
fav_aetheryte_count: u8,
|
||||
unknown97: [u8; 5],
|
||||
sightseeing21_to_80_unlock: u8,
|
||||
sightseeing_heavensward_unlock: u8,
|
||||
unknown9e: [u8; 26],
|
||||
exp: [u32; 32],
|
||||
pvp_total_exp: u32,
|
||||
unknown_pvp124: u32,
|
||||
pvp_exp: u32,
|
||||
pvp_frontline_overall_ranks: [u32; 3],
|
||||
unknown138: u32,
|
||||
levels: [u16; 32],
|
||||
unknown194: [u8; 218],
|
||||
companion_name: [u8; 21],
|
||||
companion_def_rank: u8,
|
||||
companion_att_rank: u8,
|
||||
companion_heal_rank: u8,
|
||||
mount_guide_mask: [u8; 33],
|
||||
ornament_mask: [u8; 4],
|
||||
unknown281: [u8; 23],
|
||||
#[br(count = 32)]
|
||||
#[bw(pad_size_to = 32)]
|
||||
#[br(map = read_string)]
|
||||
#[bw(map = write_string)]
|
||||
name: String,
|
||||
unknown293: [u8; 16],
|
||||
unknown2a3: u8,
|
||||
unlock_bitmask: [u8; 64],
|
||||
aetheryte: [u8; 26],
|
||||
favorite_aetheryte_ids: [u16; 4],
|
||||
free_aetheryte_id: u16,
|
||||
ps_plus_free_aetheryte_id: u16,
|
||||
discovery: [u8; 480],
|
||||
howto: [u8; 36],
|
||||
unknown554: [u8; 4],
|
||||
minions: [u8; 60],
|
||||
chocobo_taxi_mask: [u8; 12],
|
||||
watched_cutscenes: [u8; 159],
|
||||
companion_barding_mask: [u8; 12],
|
||||
companion_equipped_head: u8,
|
||||
companion_equipped_body: u8,
|
||||
companion_equipped_legs: u8,
|
||||
unknown_mask: [u8; 287],
|
||||
pose: [u8; 7],
|
||||
unknown6df: [u8; 3],
|
||||
challenge_log_complete: [u8; 13],
|
||||
secret_recipe_book_mask: [u8; 12],
|
||||
unknown_mask6f7: [u8; 29],
|
||||
relic_completion: [u8; 12],
|
||||
sightseeing_mask: [u8; 37],
|
||||
hunting_mark_mask: [u8; 102],
|
||||
triple_triad_cards: [u8; 45],
|
||||
unknown895: u8,
|
||||
unknown7d7: [u8; 15],
|
||||
unknown7d8: u8,
|
||||
unknown7e6: [u8; 49],
|
||||
regional_folklore_mask: [u8; 6],
|
||||
orchestrion_mask: [u8; 87],
|
||||
hall_of_novice_completion: [u8; 3],
|
||||
anima_completion: [u8; 11],
|
||||
unknown85e: [u8; 41],
|
||||
unlocked_raids: [u8; 28],
|
||||
unlocked_dungeons: [u8; 18],
|
||||
unlocked_guildhests: [u8; 10],
|
||||
unlocked_trials: [u8; 12],
|
||||
unlocked_pvp: [u8; 5],
|
||||
cleared_raids: [u8; 28],
|
||||
cleared_dungeons: [u8; 18],
|
||||
cleared_guildhests: [u8; 10],
|
||||
cleared_trials: [u8; 12],
|
||||
cleared_pvp: [u8; 5],
|
||||
unknown948: [u8; 15],
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
UpdateClassInfo {
|
||||
class_id: u16,
|
||||
unknown: u8,
|
||||
is_specialist: u8,
|
||||
synced_level: u16,
|
||||
class_level: u16,
|
||||
role_actions: [u32; 10],
|
||||
},
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -221,9 +480,12 @@ pub enum IPCStructData {
|
|||
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))]
|
||||
|
@ -244,6 +506,14 @@ impl IPCSegment {
|
|||
IPCStructData::LobbyCharacterAction { .. } => todo!(),
|
||||
IPCStructData::LobbyEnterWorld { .. } => 160,
|
||||
IPCStructData::RequestEnterWorld { .. } => todo!(),
|
||||
IPCStructData::InitializeChat { .. } => 24,
|
||||
IPCStructData::InitRequest { .. } => todo!(),
|
||||
IPCStructData::InitResponse { .. } => 16,
|
||||
IPCStructData::InitZone { .. } => 103,
|
||||
IPCStructData::ActorControlSelf { .. } => 32,
|
||||
IPCStructData::PlayerStats { .. } => 228,
|
||||
IPCStructData::PlayerSetup { .. } => 2544,
|
||||
IPCStructData::UpdateClassInfo { .. } => 48,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -8,10 +8,19 @@ mod compression;
|
|||
pub mod config;
|
||||
pub mod encryption;
|
||||
pub mod ipc;
|
||||
mod oodle;
|
||||
pub mod oodle;
|
||||
pub mod packet;
|
||||
pub mod patchlist;
|
||||
|
||||
// TODO: make this configurable
|
||||
// See https://ffxiv.consolegameswiki.com/wiki/Servers for a list of possible IDs
|
||||
pub const WORLD_ID: u16 = 63;
|
||||
pub const WORLD_NAME: &str = "KAWARI";
|
||||
|
||||
pub const ZONE_ID: u16 = 1255;
|
||||
|
||||
pub const CONTENT_ID: u64 = 11111111111111111;
|
||||
|
||||
pub fn generate_sid() -> String {
|
||||
let random_id: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
|
|
|
@ -43,6 +43,10 @@ pub struct FFXIVOodle {
|
|||
window: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait Oodle {
|
||||
fn decode(&mut self, input: Vec<u8>, decompressed_size: u32) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl FFXIVOodle {
|
||||
pub fn new() -> FFXIVOodle {
|
||||
let htbits: i32 = 17;
|
||||
|
@ -74,8 +78,10 @@ impl FFXIVOodle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, input: Vec<u8>, decompressed_size: u32) -> Vec<u8> {
|
||||
impl Oodle for FFXIVOodle {
|
||||
fn decode(&mut self, input: Vec<u8>, decompressed_size: u32) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut out_buf: Vec<u8> = vec![0u8; decompressed_size.try_into().unwrap()];
|
||||
let mut in_buf = input.to_vec();
|
||||
|
|
|
@ -15,12 +15,13 @@ use crate::{
|
|||
compression::decompress,
|
||||
encryption::{decrypt, encrypt},
|
||||
ipc::IPCSegment,
|
||||
oodle::FFXIVOodle,
|
||||
};
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = u16)]
|
||||
#[derive(Debug)]
|
||||
enum ConnectionType {
|
||||
pub enum ConnectionType {
|
||||
None = 0x0,
|
||||
Zone = 0x1,
|
||||
Chat = 0x2,
|
||||
|
@ -32,6 +33,12 @@ enum ConnectionType {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum SegmentType {
|
||||
// Client->Server Packets
|
||||
#[brw(magic = 0x1u32)]
|
||||
InitializeSession {
|
||||
#[brw(pad_before = 4)]
|
||||
#[brw(pad_after = 48)] // TODO: probably not empty?
|
||||
player_id: u32,
|
||||
},
|
||||
#[brw(magic = 0x9u32)]
|
||||
InitializeEncryption {
|
||||
#[brw(pad_before = 36)] // empty
|
||||
|
@ -53,13 +60,18 @@ pub enum SegmentType {
|
|||
KeepAlive { id: u32, timestamp: u32 },
|
||||
|
||||
// Server->Client Packets
|
||||
#[brw(magic = 0x0Au32)]
|
||||
#[brw(magic = 0xAu32)]
|
||||
InitializationEncryptionResponse {
|
||||
#[br(count = 0x280)]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
#[brw(magic = 0x08u32)]
|
||||
#[brw(magic = 0x8u32)]
|
||||
KeepAliveResponse { id: u32, timestamp: u32 },
|
||||
#[brw(magic = 0x2u32)]
|
||||
ZoneInitialize {
|
||||
#[brw(pad_after = 36)]
|
||||
player_id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -90,10 +102,14 @@ pub struct PacketHeader {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PacketSegment {
|
||||
#[bw(calc = self.calc_size())]
|
||||
#[br(dbg)]
|
||||
pub size: u32,
|
||||
#[br(dbg)]
|
||||
pub source_actor: u32,
|
||||
#[br(dbg)]
|
||||
pub target_actor: u32,
|
||||
#[brw(args(size, encryption_key))]
|
||||
#[br(dbg)]
|
||||
pub segment_type: SegmentType,
|
||||
}
|
||||
|
||||
|
@ -105,20 +121,23 @@ impl PacketSegment {
|
|||
SegmentType::InitializeEncryption { .. } => 616,
|
||||
SegmentType::InitializationEncryptionResponse { .. } => 640,
|
||||
SegmentType::Ipc { data } => data.calc_size(),
|
||||
SegmentType::KeepAlive { .. } => todo!(),
|
||||
SegmentType::KeepAlive { .. } => 0x8,
|
||||
SegmentType::KeepAliveResponse { .. } => 0x8,
|
||||
SegmentType::ZoneInitialize { .. } => 40,
|
||||
SegmentType::InitializeSession { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(import(encryption_key: Option<&[u8]>))]
|
||||
#[brw(import(oodle: &mut FFXIVOodle, encryption_key: Option<&[u8]>))]
|
||||
#[derive(Debug)]
|
||||
struct Packet {
|
||||
#[br(dbg)]
|
||||
header: PacketHeader,
|
||||
#[bw(args(encryption_key))]
|
||||
#[br(parse_with = decompress, args(&header, encryption_key,))]
|
||||
#[br(parse_with = decompress, args(oodle, &header, encryption_key,))]
|
||||
#[br(dbg)]
|
||||
segments: Vec<PacketSegment>,
|
||||
}
|
||||
|
||||
|
@ -130,7 +149,7 @@ fn dump(msg: &str, data: &[u8]) {
|
|||
pub async fn send_packet(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
segments: &[PacketSegment],
|
||||
state: &State,
|
||||
state: &mut State,
|
||||
) {
|
||||
let timestamp: u64 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
@ -166,7 +185,10 @@ pub async fn send_packet(
|
|||
packet
|
||||
.write_le_args(
|
||||
&mut cursor,
|
||||
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
||||
(
|
||||
&mut state.oodle,
|
||||
state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -185,14 +207,19 @@ pub async fn send_packet(
|
|||
pub struct State {
|
||||
pub client_key: Option<[u8; 16]>,
|
||||
pub session_id: Option<String>,
|
||||
pub oodle: FFXIVOodle,
|
||||
pub player_id: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn parse_packet(data: &[u8], state: &mut State) -> Vec<PacketSegment> {
|
||||
pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>, ConnectionType) {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
match Packet::read_le_args(
|
||||
&mut cursor,
|
||||
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
|
||||
(
|
||||
&mut state.oodle,
|
||||
state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),
|
||||
),
|
||||
) {
|
||||
Ok(packet) => {
|
||||
println!("{:#?}", packet);
|
||||
|
@ -204,20 +231,20 @@ pub async fn parse_packet(data: &[u8], state: &mut State) -> Vec<PacketSegment>
|
|||
);
|
||||
}
|
||||
|
||||
packet.segments
|
||||
(packet.segments, packet.header.connection_type)
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{err}");
|
||||
dump("Failed to parse packet!", data);
|
||||
|
||||
Vec::new()
|
||||
(Vec::new(), ConnectionType::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_keep_alive(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
state: &State,
|
||||
state: &mut State,
|
||||
id: u32,
|
||||
timestamp: u32,
|
||||
) {
|
||||
|
|
Loading…
Add table
Reference in a new issue