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:
parent
e5d143d2c6
commit
51c66a4a14
5 changed files with 160 additions and 16 deletions
|
@ -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
40
src/compression.rs
Normal 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)
|
||||
}
|
|
@ -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
98
src/oodle.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue