mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-20 14:47: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.
|
* Handles checking if the client needs any patching.
|
||||||
* [Lobby](https://docs.xiv.zone/server/lobby/)
|
* [Lobby](https://docs.xiv.zone/server/lobby/)
|
||||||
* Handles logging the client into the world server, displaying the character list and so on.
|
* 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
|
## 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.
|
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 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.
|
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;
|
pub mod client_select_data;
|
||||||
mod common;
|
mod common;
|
||||||
|
mod compression;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod encryption;
|
pub mod encryption;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
mod oodle;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod patchlist;
|
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::{
|
use crate::{
|
||||||
common::read_string,
|
common::read_string,
|
||||||
|
compression::decompress,
|
||||||
encryption::{decrypt, encrypt},
|
encryption::{decrypt, encrypt},
|
||||||
ipc::IPCSegment,
|
ipc::IPCSegment,
|
||||||
};
|
};
|
||||||
|
@ -64,25 +65,24 @@ pub enum SegmentType {
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(repr = u8)]
|
#[brw(repr = u8)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum CompressionType {
|
pub enum CompressionType {
|
||||||
Uncompressed = 0,
|
Uncompressed = 0,
|
||||||
ZLib = 1,
|
|
||||||
Oodle = 2,
|
Oodle = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PacketHeader {
|
pub struct PacketHeader {
|
||||||
unk1: u64,
|
pub unk1: u64,
|
||||||
unk2: u64,
|
pub unk2: u64,
|
||||||
timestamp: u64,
|
pub timestamp: u64,
|
||||||
size: u32,
|
pub size: u32,
|
||||||
connection_type: ConnectionType,
|
pub connection_type: ConnectionType,
|
||||||
segment_count: u16,
|
pub segment_count: u16,
|
||||||
unk3: u8,
|
pub unk3: u8,
|
||||||
compressed: CompressionType,
|
pub compressed: CompressionType,
|
||||||
unk4: u16,
|
pub unk4: u16,
|
||||||
unk5: u32, // iolite says the size after oodle decompression
|
pub oodle_decompressed_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -117,8 +117,8 @@ impl PacketSegment {
|
||||||
struct Packet {
|
struct Packet {
|
||||||
#[br(dbg)]
|
#[br(dbg)]
|
||||||
header: PacketHeader,
|
header: PacketHeader,
|
||||||
#[br(count = header.segment_count, args { inner: (encryption_key,) })]
|
|
||||||
#[bw(args(encryption_key))]
|
#[bw(args(encryption_key))]
|
||||||
|
#[br(parse_with = decompress, args(&header, encryption_key,))]
|
||||||
segments: Vec<PacketSegment>,
|
segments: Vec<PacketSegment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ pub async fn send_packet(
|
||||||
unk3: 0,
|
unk3: 0,
|
||||||
compressed: CompressionType::Uncompressed,
|
compressed: CompressionType::Uncompressed,
|
||||||
unk4: 0,
|
unk4: 0,
|
||||||
unk5: 0,
|
oodle_decompressed_size: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let packet = Packet {
|
let packet = Packet {
|
||||||
|
|
Loading…
Add table
Reference in a new issue