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:
parent
7cd5233598
commit
7b0c41a478
6 changed files with 207 additions and 59 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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!"
|
||||
|
|
|
@ -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);
|
||||
|
|
20
src/ipc.rs
20
src/ipc.rs
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue