diff --git a/README.md b/README.md index 81846ed..1d9376c 100644 --- a/README.md +++ b/README.md @@ -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 @@ -25,7 +27,9 @@ Only the Windows version of the game is supported at the moment. Only the latest ## Running -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 diff --git a/src/compression.rs b/src/compression.rs new file mode 100644 index 0000000..c6467b5 --- /dev/null +++ b/src/compression.rs @@ -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> { + let mut segments = Vec::new(); + + let size = header.size as usize - std::mem::size_of::(); + + 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) +} diff --git a/src/lib.rs b/src/lib.rs index 628df18..43bc553 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/oodle.rs b/src/oodle.rs new file mode 100644 index 0000000..eee05a5 --- /dev/null +++ b/src/oodle.rs @@ -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, + shared: Vec, + #[allow(dead_code)] // unused in rust but required to still be available for low-level oodle + window: Vec, +} + +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, decompressed_size: u32) -> Vec { + unsafe { + let mut out_buf: Vec = 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 + } + } +} diff --git a/src/packet.rs b/src/packet.rs index be75c17..70415b6 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -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, } @@ -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 {