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

Fix numerous issues with the client<->zone communication and Oodle

Yet again TemporalStasis is awesome, and it shows that we need two separate
compressors (one for clientbound packets, one for serverbound ones.) I also
updated some opcodes for the latest patch, and support for compressing with
Oodle when sending packets.
This commit is contained in:
Joshua Goins 2025-03-11 23:52:55 -04:00
parent 7cd5233598
commit 7b0c41a478
6 changed files with 207 additions and 59 deletions

View file

@ -7,7 +7,7 @@ use kawari::encryption::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,
CompressionType, 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};
@ -28,7 +28,8 @@ async fn main() {
let mut state = State {
client_key: None,
session_id: None,
oodle: FFXIVOodle::new(),
clientbound_oodle: FFXIVOodle::new(),
serverbound_oodle: FFXIVOodle::new(),
player_id: None,
};
@ -114,7 +115,13 @@ async fn initialize_encryption(
target_actor: 0,
segment_type: SegmentType::InitializationEncryptionResponse { data },
};
send_packet(socket, &[response_packet], state).await;
send_packet(
socket,
&[response_packet],
state,
CompressionType::Uncompressed,
)
.await;
}
async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &mut State) {
@ -156,7 +163,13 @@ async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &mut State)
target_actor: 0,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(socket, &[response_packet], state).await;
send_packet(
socket,
&[response_packet],
state,
CompressionType::Uncompressed,
)
.await;
}
async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &mut State, sequence: u64) {
@ -227,7 +240,7 @@ async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &mut State, s
packets.push(response_packet);
}
send_packet(socket, &packets, state).await;
send_packet(socket, &packets, state, CompressionType::Uncompressed).await;
// now send them the character list
{
@ -367,7 +380,13 @@ async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &mut State, s
target_actor: 0,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(socket, &[response_packet], state).await;
send_packet(
socket,
&[response_packet],
state,
CompressionType::Uncompressed,
)
.await;
}
}
}
@ -412,5 +431,11 @@ async fn send_enter_world(
target_actor: 0,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(socket, &[response_packet], state).await;
send_packet(
socket,
&[response_packet],
state,
CompressionType::Uncompressed,
)
.await;
}

View file

@ -3,7 +3,7 @@ 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,
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
};
use kawari::{CONTENT_ID, WORLD_ID, ZONE_ID};
use tokio::io::AsyncReadExt;
@ -24,7 +24,8 @@ async fn main() {
let mut state = State {
client_key: None,
session_id: None,
oodle: FFXIVOodle::new(),
clientbound_oodle: FFXIVOodle::new(),
serverbound_oodle: FFXIVOodle::new(),
player_id: None,
};
@ -58,7 +59,13 @@ async fn main() {
timestamp,
},
};
send_packet(&mut write, &[response_packet], &mut state).await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
match connection_type {
@ -74,8 +81,13 @@ async fn main() {
player_id: *player_id,
},
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
kawari::packet::ConnectionType::Chat => {
tracing::info!(
@ -90,8 +102,13 @@ async fn main() {
player_id: *player_id,
},
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
{
@ -101,9 +118,7 @@ async fn main() {
op_code: IPCOpCode::InitializeChat,
server_id: 0,
timestamp: 0,
data: IPCStructData::InitializeChat {
unk: [0; 24],
},
data: IPCStructData::InitializeChat { unk: [0; 8] },
};
let response_packet = PacketSegment {
@ -111,8 +126,13 @@ async fn main() {
target_actor: *player_id,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
}
_ => panic!(
@ -156,8 +176,13 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// Control Data
@ -184,8 +209,13 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// Stats
@ -236,8 +266,13 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// Player Setup
@ -376,8 +411,13 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// Player Class Info
@ -403,8 +443,13 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// Init Zone
@ -449,9 +494,38 @@ async fn main() {
target_actor: state.player_id.unwrap(),
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(&mut write, &[response_packet], &mut state)
.await;
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Oodle,
)
.await;
}
// ?????
/*{
let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::InitRequest,
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, CompressionType::Oodle)
.await;
}*/
}
_ => panic!(
"The server is recieving a IPC response or unknown packet!"

View file

@ -33,6 +33,14 @@ pub(crate) fn decompress(
crate::packet::CompressionType::Oodle => oodle.decode(data, header.uncompressed_size),
};
if header.compression_type == crate::packet::CompressionType::Oodle {
assert_eq!(
data.len(),
header.uncompressed_size as usize,
"Decompressed data does not match the expected length!"
);
}
write("decompressed.bin", &data).unwrap();
let mut cursor = Cursor::new(&data);

View file

@ -32,7 +32,7 @@ pub enum IPCOpCode {
/// Sent by the client when they successfully initialize with the server, and they need several bits of information (e.g. what zone to load)
InitRequest = 0x2ED,
/// Sent by the server as response to ZoneInitRequest.
InitResponse = 280, // TODO: probably wrong!
InitResponse = 0x1EF,
/// Sent by the server that tells the client which zone to load
InitZone = 0x0311,
/// Sent by the server for... something
@ -276,7 +276,7 @@ pub enum IPCStructData {
host: String,
},
#[br(pre_assert(false))]
InitializeChat { unk: [u8; 24] },
InitializeChat { unk: [u8; 8] },
#[br(pre_assert(false))]
InitResponse {
unk1: u64,
@ -521,8 +521,8 @@ impl IPCSegment {
IPCStructData::LobbyCharacterAction { .. } => todo!(),
IPCStructData::LobbyEnterWorld { .. } => 160,
IPCStructData::RequestEnterWorld { .. } => todo!(),
IPCStructData::InitializeChat { .. } => 24,
IPCStructData::InitRequest { .. } => todo!(),
IPCStructData::InitializeChat { .. } => 8,
IPCStructData::InitRequest { .. } => 16,
IPCStructData::InitResponse { .. } => 16,
IPCStructData::InitZone { .. } => 103,
IPCStructData::ActorControlSelf { .. } => 32,
@ -571,7 +571,17 @@ mod tests {
unk8: 0,
entitled_expansion: 0,
characters: Vec::new(),
}
},
IPCStructData::ActorControlSelf {
category: ActorControlType::SetCharaGearParamUI,
param1: 0,
param2: 0,
param3: 0,
param4: 0,
param5: 0,
param6: 0,
},
IPCStructData::InitializeChat { unk: [0; 8] },
];
for ipc in &ipc_types {

View file

@ -151,4 +151,21 @@ impl FFXIVOodle {
out_buf
}
}
pub fn encode(&mut self, input: Vec<u8>) -> Vec<u8> {
unsafe {
let mut out_buf: Vec<u8> = vec![0u8; input.len()];
let mut in_buf = input.to_vec();
let len = OodleNetwork1TCP_Encode(
self.state.as_mut_ptr() as *mut c_void,
self.shared.as_mut_ptr() as *mut c_void,
in_buf.as_mut_ptr() as *const c_void,
in_buf.len().try_into().unwrap(),
out_buf.as_mut_ptr() as *mut c_void,
);
out_buf.truncate(len as usize);
out_buf
}
}
}

View file

@ -79,7 +79,7 @@ pub enum SegmentType {
#[binrw]
#[brw(repr = u8)]
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum CompressionType {
Uncompressed = 0,
Oodle = 2,
@ -153,6 +153,7 @@ pub async fn send_packet(
socket: &mut WriteHalf<TcpStream>,
segments: &[PacketSegment],
state: &mut State,
compression_type: CompressionType,
) {
let timestamp: u64 = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -161,39 +162,45 @@ pub async fn send_packet(
.try_into()
.unwrap();
let mut total_segment_size = 0;
let mut segments_buffer = Cursor::new(Vec::new());
for segment in segments {
total_segment_size += segment.calc_size();
segment
.write_le_args(
&mut segments_buffer,
(state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),),
)
.unwrap();
}
let segments_buffer = segments_buffer.into_inner();
let mut uncompressed_size = 0;
let data = match compression_type {
CompressionType::Uncompressed => segments_buffer,
CompressionType::Oodle => {
uncompressed_size = segments_buffer.len();
state.clientbound_oodle.encode(segments_buffer)
}
};
let size = std::mem::size_of::<PacketHeader>() + data.len();
let header = PacketHeader {
unk1: 0xE2465DFF41A05252, // wtf?
unk2: 0x75C4997B4D642A7F, // wtf? x2
timestamp,
size: std::mem::size_of::<PacketHeader>() as u32 + total_segment_size,
size: size as u32,
connection_type: ConnectionType::Lobby,
segment_count: segments.len() as u16,
unk3: 0,
compression_type: CompressionType::Uncompressed,
compression_type,
unk4: 0,
uncompressed_size: 0,
};
let packet = Packet {
header,
segments: segments.to_vec(),
uncompressed_size: uncompressed_size as u32,
};
let mut cursor = Cursor::new(Vec::new());
packet
.write_le_args(
&mut cursor,
(
&mut state.oodle,
state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),
),
)
.unwrap();
header.write_le(&mut cursor).unwrap();
std::io::Write::write_all(&mut cursor, &data).unwrap();
let buffer = cursor.into_inner();
@ -210,7 +217,8 @@ pub async fn send_packet(
pub struct State {
pub client_key: Option<[u8; 16]>,
pub session_id: Option<String>,
pub oodle: FFXIVOodle,
pub serverbound_oodle: FFXIVOodle,
pub clientbound_oodle: FFXIVOodle,
pub player_id: Option<u32>,
}
@ -220,7 +228,7 @@ pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>
match Packet::read_le_args(
&mut cursor,
(
&mut state.oodle,
&mut state.serverbound_oodle,
state.client_key.as_ref().map(|s: &[u8; 16]| s.as_slice()),
),
) {
@ -256,7 +264,13 @@ pub async fn send_keep_alive(
target_actor: 0,
segment_type: SegmentType::KeepAliveResponse { id, timestamp },
};
send_packet(socket, &[response_packet], state).await;
send_packet(
socket,
&[response_packet],
state,
CompressionType::Uncompressed,
)
.await;
}
#[cfg(test)]