1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-20 06:37:45 +00:00

Add support for decompressing Oodle-compressed packets

This commit is contained in:
Joshua Goins 2025-03-09 12:00:58 -04:00
parent e5d143d2c6
commit 51c66a4a14
5 changed files with 160 additions and 16 deletions

View file

@ -18,6 +18,8 @@ A substitute for a few official servers such as “ffxiv.com” and “square-en
* Handles checking if the client needs any patching.
* [Lobby](https://docs.xiv.zone/server/lobby/)
* Handles logging the client into the world server, displaying the character list and so on.
* [World](https://docs.xiv.zone/server/world/)
* Handles actual world operation, e.g. characters running around.
## Supported Game Versions
@ -27,6 +29,8 @@ Only the Windows version of the game is supported at the moment. Only the latest
Install [Rust](https://rust-lang.org) and then use the `run.sh` helper script in the repository. You can of course run each server individually.
In order to run the World server, you need a copy of Oodle networking. Fortunately it is easy to do, assuming you have an Epic Games account and a GitHub account. Use something like [get-oodle-lib](https://github.com/sehnryr/get-oodle-lib) or another tool to fetch "liboo2net" for your platform.
### Testing via launcher
Testing on a real launcher is not yet supported, the easiest way is through [Astra](https://github.com/redstrate/Astra) which allows you to plug in your own domains. Because of how the domains are set up, you can't simply plug them in though.

40
src/compression.rs Normal file
View file

@ -0,0 +1,40 @@
use std::io::Cursor;
use binrw::{BinRead, BinResult};
use crate::{
oodle::FFXIVOodle,
packet::{PacketHeader, PacketSegment},
};
#[binrw::parser(reader, endian)]
pub(crate) fn decompress(
header: &PacketHeader,
encryption_key: Option<&[u8]>,
) -> BinResult<Vec<PacketSegment>> {
let mut segments = Vec::new();
let size = header.size as usize - std::mem::size_of::<PacketHeader>();
let mut data = vec![0; size];
reader.read_exact(&mut data)?;
let data = match header.compressed {
crate::packet::CompressionType::Uncompressed => data,
crate::packet::CompressionType::Oodle => {
FFXIVOodle::new().decode(data, header.oodle_decompressed_size)
}
};
let mut cursor = Cursor::new(&data);
for _ in 0..header.segment_count {
segments.push(PacketSegment::read_options(
&mut cursor,
endian,
(encryption_key,),
)?);
}
Ok(segments)
}

View file

@ -4,9 +4,11 @@ use rand::distributions::Alphanumeric;
pub mod client_select_data;
mod common;
mod compression;
pub mod config;
pub mod encryption;
pub mod ipc;
mod oodle;
pub mod packet;
pub mod patchlist;

98
src/oodle.rs Normal file
View file

@ -0,0 +1,98 @@
use std::{ffi::c_void, ptr::null};
// TODO: add support for windows?
#[link(name = "oo2netlinux64")]
unsafe extern "C" {
fn OodleNetwork1TCP_State_Size() -> isize;
fn OodleNetwork1_Shared_Size(htbits: i32) -> isize;
fn OodleNetwork1_Shared_SetWindow(
shared: *mut c_void,
htbits: i32,
window: *const c_void,
window_size: i32,
) -> c_void;
fn OodleNetwork1TCP_Train(
state: *mut c_void,
shared: *const c_void,
training_packet_pointers: *const c_void,
training_packet_sizes: i32,
num_training_packets: i32,
) -> c_void;
fn OodleNetwork1TCP_Decode(
state: *mut c_void,
shared: *const c_void,
enc: *const c_void,
enc_size: isize,
dec: *mut c_void,
dec_size: isize,
) -> bool;
fn OodleNetwork1TCP_Encode(
state: *mut c_void,
shared: *const c_void,
dec: *const c_void,
dec_size: isize,
enc: *mut c_void,
) -> bool;
}
#[derive(Debug, Default)]
pub struct FFXIVOodle {
state: Vec<u8>,
shared: Vec<u8>,
#[allow(dead_code)] // unused in rust but required to still be available for low-level oodle
window: Vec<u8>,
}
impl FFXIVOodle {
pub fn new() -> FFXIVOodle {
let htbits: i32 = 17;
unsafe {
let oodle_state_size: usize = OodleNetwork1TCP_State_Size().try_into().unwrap();
let oodle_shared_size: usize = OodleNetwork1_Shared_Size(17).try_into().unwrap();
let mut oodle_state = vec![0u8; oodle_state_size];
let mut oodle_shared = vec![0u8; oodle_shared_size];
let mut oodle_window = [0u8; 0x100000].to_vec();
OodleNetwork1_Shared_SetWindow(
oodle_shared.as_mut_ptr() as *mut c_void,
htbits,
oodle_window.as_mut_ptr() as *mut c_void,
oodle_window.len().try_into().unwrap(),
);
OodleNetwork1TCP_Train(
oodle_state.as_mut_ptr() as *mut c_void,
oodle_shared.as_mut_ptr() as *mut c_void,
null(),
0,
0,
);
FFXIVOodle {
state: oodle_state,
shared: oodle_shared,
window: oodle_window,
}
}
}
pub 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();
let success = OodleNetwork1TCP_Decode(
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.len().try_into().unwrap(),
);
if !success {
panic!("Failed to oodle decode for an unknown reason.");
}
out_buf
}
}
}

View file

@ -12,6 +12,7 @@ use tokio::{
use crate::{
common::read_string,
compression::decompress,
encryption::{decrypt, encrypt},
ipc::IPCSegment,
};
@ -64,25 +65,24 @@ pub enum SegmentType {
#[binrw]
#[brw(repr = u8)]
#[derive(Debug)]
enum CompressionType {
pub enum CompressionType {
Uncompressed = 0,
ZLib = 1,
Oodle = 2,
}
#[binrw]
#[derive(Debug)]
struct PacketHeader {
unk1: u64,
unk2: u64,
timestamp: u64,
size: u32,
connection_type: ConnectionType,
segment_count: u16,
unk3: u8,
compressed: CompressionType,
unk4: u16,
unk5: u32, // iolite says the size after oodle decompression
pub struct PacketHeader {
pub unk1: u64,
pub unk2: u64,
pub timestamp: u64,
pub size: u32,
pub connection_type: ConnectionType,
pub segment_count: u16,
pub unk3: u8,
pub compressed: CompressionType,
pub unk4: u16,
pub oodle_decompressed_size: u32,
}
#[binrw]
@ -117,8 +117,8 @@ impl PacketSegment {
struct Packet {
#[br(dbg)]
header: PacketHeader,
#[br(count = header.segment_count, args { inner: (encryption_key,) })]
#[bw(args(encryption_key))]
#[br(parse_with = decompress, args(&header, encryption_key,))]
segments: Vec<PacketSegment>,
}
@ -154,7 +154,7 @@ pub async fn send_packet(
unk3: 0,
compressed: CompressionType::Uncompressed,
unk4: 0,
unk5: 0,
oodle_decompressed_size: 0,
};
let packet = Packet {