diff --git a/.gitignore b/.gitignore index a638136..e7e600c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ config.json *.bin *.db config.yaml +src/opcodes.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2affaad..d3c86db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,10 @@ Here are the various things that should be checked when updating Kawari to a new * The IPC opcodes _will_ change and must all be replaced. * Check the game version used in the encryption key in `lib.rs`. +## IPC Opcodes + +Since the Zone IPC opcodes change every patch, it's extremely easy to change the opcodes in Kawari. Edit the values under `resources/opcodes.json` and recompile Kawari. You still have to change the structs themselves (located under `src//ipc`) if needed though. + ## Contributing Before making a pull request, make sure: diff --git a/Cargo.toml b/Cargo.toml index 4f29b44..673e2e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,11 @@ panic = "abort" default = [] oodle = [] +[build-dependencies] +# Serialization of IPC opcodes +serde = { version = "1.0", features = ["derive"], default-features = false } +serde_json = { version = "1.0", features = ["std"], default-features = false } + [dependencies] # Used for the web servers axum = { version = "0.8", features = ["json", "tokio", "http1", "form", "query"], default-features = false } diff --git a/build.rs b/build.rs index 0e51681..0ef1c05 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,60 @@ +use std::path::PathBuf; + +use serde_json::Value; + fn main() { + // Add link search directory for Oodle println!("cargo:rustc-link-search=./oodle"); + + // Generate IPC opcodes + { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/opcodes.json"); + + let mut output_str = "use binrw::binrw;\n".to_string(); + + let opcodes_buffer = std::fs::read_to_string(d).unwrap(); + let json: Value = serde_json::from_str(&opcodes_buffer).unwrap(); + for element in json.as_object().unwrap() { + let key = element.0; + + // beginning + output_str.push_str("#[binrw]\n"); + output_str.push_str("#[brw(repr = u16)]\n"); + output_str.push_str("#[derive(Clone, PartialEq, Debug)]\n"); + output_str.push_str(&format!("pub enum {key} {{\n")); + + let opcodes = element.1.as_array().unwrap(); + for opcode in opcodes { + let opcode = opcode.as_object().unwrap(); + let name = opcode.get("name").unwrap().as_str().unwrap(); + let opcode = opcode.get("opcode").unwrap().as_number().unwrap(); + + output_str.push_str(&format!("{name} = {opcode},\n")); + } + + // end + output_str.push_str("}\n\n"); + + // sizes + output_str.push_str(&format!("impl {key} {{\n")); + output_str.push_str("/// Returns the expected size of the data segment of this IPC opcode, _without_ any headers.\n"); + output_str.push_str("pub fn calc_size(&self) -> u32 {\n"); + output_str.push_str("match self {\n"); + + for opcode in opcodes { + let opcode = opcode.as_object().unwrap(); + let name = opcode.get("name").unwrap().as_str().unwrap(); + let size = opcode.get("size").unwrap().as_number().unwrap(); + + output_str.push_str(&format!("{key}::{name} => {size},\n")); + } + + output_str.push_str("}\n\n"); + output_str.push_str("}\n\n"); + output_str.push_str("}\n\n"); + } + + std::fs::write("src/opcodes.rs", output_str).expect("Failed to write opcodes file!"); + } } diff --git a/resources/opcodes.json b/resources/opcodes.json new file mode 100644 index 0000000..1acddaa --- /dev/null +++ b/resources/opcodes.json @@ -0,0 +1,61 @@ +{ + "ServerLobbyIpcType": [ + { + "name": "LobbyError", + "opcode": 2, + "size": 536 + }, + { + "name": "LobbyServiceAccountList", + "opcode": 12, + "size": 656 + }, + { + "name": "LobbyCharacterList", + "opcode": 13, + "size": 2472 + }, + { + "name": "LobbyEnterWorld", + "opcode": 15, + "size": 160 + }, + { + "name": "LobbyServerList", + "opcode": 21, + "size": 528 + }, + { + "name": "LobbyRetainerList", + "opcode": 23, + "size": 210 + }, + { + "name": "CharacterCreated", + "opcode": 14, + "size": 2568 + } + ], + "ClientLobbyIpcType": [ + { + "name": "RequestCharacterList", + "opcode": 3, + "size": 24 + }, + { + "name": "RequestEnterWorld", + "opcode": 4, + "size": 32 + }, + { + "name": "ClientVersionInfo", + "opcode": 5, + "size": 1144 + }, + { + "name": "LobbyCharacterAction", + "opcode": 11, + "size": 496 + } + ] +} diff --git a/src/lib.rs b/src/lib.rs index d80f718..9ac5c56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ pub mod login; /// Patch server-specific code. pub mod patch; +/// Opcodes, see `resources/opcodes.json` +pub mod opcodes; + /// Used in the encryption key. const GAME_VERSION: u16 = 7000; diff --git a/src/lobby/connection.rs b/src/lobby/connection.rs index a24b834..6e8c731 100644 --- a/src/lobby/connection.rs +++ b/src/lobby/connection.rs @@ -11,6 +11,7 @@ use crate::{ config::get_config, lobby::CharaMake, oodle::OodleNetwork, + opcodes::ServerLobbyIpcType, packet::{ CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, generate_encryption_key, parse_packet, send_packet, @@ -20,7 +21,7 @@ use crate::{ use super::ipc::{ CharacterDetails, LobbyCharacterAction, LobbyCharacterActionKind, LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData, ServerLobbyIpcSegment, - ServerLobbyIpcType, ServiceAccount, + ServiceAccount, }; use crate::lobby::ipc::ClientLobbyIpcSegment; diff --git a/src/lobby/ipc/mod.rs b/src/lobby/ipc/mod.rs index 1b10542..88cb5f8 100644 --- a/src/lobby/ipc/mod.rs +++ b/src/lobby/ipc/mod.rs @@ -16,6 +16,7 @@ pub use service_account_list::{LobbyServiceAccountList, ServiceAccount}; use crate::{ common::{read_string, write_string}, + opcodes::{ClientLobbyIpcType, ServerLobbyIpcType}, packet::{IpcSegment, ReadWriteIpcSegment}, }; @@ -24,12 +25,7 @@ pub type ClientLobbyIpcSegment = IpcSegment u32 { // 16 is the size of the IPC header - 16 + match self.op_code { - ClientLobbyIpcType::RequestCharacterList => 24, - ClientLobbyIpcType::RequestEnterWorld => 32, - ClientLobbyIpcType::ClientVersionInfo => 1144, - ClientLobbyIpcType::LobbyCharacterAction => 496, - } + 16 + self.op_code.calc_size() } } @@ -56,15 +52,7 @@ pub type ServerLobbyIpcSegment = IpcSegment u32 { // 16 is the size of the IPC header - 16 + match self.op_code { - ServerLobbyIpcType::LobbyError => 536, - ServerLobbyIpcType::LobbyServiceAccountList => 656, - ServerLobbyIpcType::LobbyCharacterList => 2472, - ServerLobbyIpcType::LobbyEnterWorld => 160, - ServerLobbyIpcType::LobbyServerList => 528, - ServerLobbyIpcType::LobbyRetainerList => 210, - ServerLobbyIpcType::CharacterCreated => 2568, - } + 16 + self.op_code.calc_size() } } @@ -88,45 +76,11 @@ impl Default for ServerLobbyIpcSegment { } } -#[binrw] -#[brw(repr = u16)] -#[derive(Clone, PartialEq, Debug)] -pub enum ServerLobbyIpcType { - /// Sent by the server to indicate an lobby error occured - LobbyError = 0x2, - /// Sent by the server to inform the client of their service accounts. - LobbyServiceAccountList = 0xC, - /// Sent by the server to inform the client of their characters. - LobbyCharacterList = 0xD, - /// Sent by the server to tell the client how to connect to the world server. - LobbyEnterWorld = 0xF, - /// Sent by the server to inform the client of their servers. - LobbyServerList = 0x15, - /// Sent by the server to inform the client of their retainers. - LobbyRetainerList = 0x17, - // Assumed what this is, but probably incorrect - CharacterCreated = 0xE, -} - -#[binrw] -#[brw(repr = u16)] -#[derive(Clone, PartialEq, Debug)] -pub enum ClientLobbyIpcType { - /// Sent by the client when it requests the character list in the lobby. - RequestCharacterList = 0x3, - /// Sent by the client when it requests to enter a world. - RequestEnterWorld = 0x4, - /// Sent by the client after exchanging encryption information with the lobby server. - ClientVersionInfo = 0x5, - /// Sent by the client when they request something about the character (e.g. deletion.) - LobbyCharacterAction = 0xB, -} - #[binrw] #[br(import(magic: &ClientLobbyIpcType))] #[derive(Debug, Clone)] pub enum ClientLobbyIpcData { - // Client->Server IPC + /// Sent by the client after exchanging encryption information with the lobby server. #[br(pre_assert(*magic == ClientLobbyIpcType::ClientVersionInfo))] ClientVersionInfo { sequence: u64, @@ -142,16 +96,19 @@ pub enum ClientLobbyIpcData { #[br(map = read_string)] #[bw(ignore)] version_info: String, - // unknown stuff at the end, it's not completely empty' + // unknown stuff at the end, it's not completely empty }, + /// Sent by the client when it requests the character list in the lobby. #[br(pre_assert(*magic == ClientLobbyIpcType::RequestCharacterList))] RequestCharacterList { #[brw(pad_before = 16)] sequence: u64, // TODO: what is in here? }, + /// Sent by the client when they request something about the character (e.g. deletion.) #[br(pre_assert(*magic == ClientLobbyIpcType::LobbyCharacterAction))] LobbyCharacterAction(LobbyCharacterAction), + /// Sent by the client when it requests to enter a world. #[br(pre_assert(*magic == ClientLobbyIpcType::RequestEnterWorld))] RequestEnterWorld { sequence: u64, @@ -164,15 +121,20 @@ pub enum ClientLobbyIpcData { #[br(import(_magic: &ServerLobbyIpcType))] #[derive(Debug, Clone)] pub enum ServerLobbyIpcData { + /// Sent by the server to inform the client of their service accounts. LobbyServiceAccountList(LobbyServiceAccountList), + /// Sent by the server to inform the client of their servers. LobbyServerList(LobbyServerList), + /// Sent by the server to inform the client of their retainers. LobbyRetainerList { // TODO: what is in here? #[brw(pad_before = 7)] #[brw(pad_after = 202)] unk1: u8, }, + /// Sent by the server to inform the client of their characters. LobbyCharacterList(LobbyCharacterList), + /// Sent by the server to tell the client how to connect to the world server. LobbyEnterWorld { sequence: u64, actor_id: u32, @@ -192,6 +154,7 @@ pub enum ServerLobbyIpcData { #[bw(map = write_string)] host: String, }, + /// Sent by the server to indicate an lobby error occured. LobbyError { sequence: u64, error: u32, @@ -200,6 +163,7 @@ pub enum ServerLobbyIpcData { #[brw(pad_after = 516)] // empty and garbage unk1: u16, }, + // Assumed what this is, but probably incorrect CharacterCreated { sequence: u64, unk1: u8,