From d0bb7f4ba96f5cf154e5db2e35c4b07e86fe2d26 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 26 Mar 2025 17:58:15 -0400 Subject: [PATCH] Start defining IPC opcodes in JSON This is to make it easier to interpolate with other tools and projects, and to reduce the mental barrier (of me and other contributors) of changing these. This only ports the Lobby connection to use JSON so far, the World connection is next. --- .gitignore | 1 + CONTRIBUTING.md | 4 +++ Cargo.toml | 5 ++++ build.rs | 57 +++++++++++++++++++++++++++++++++++ resources/opcodes.json | 61 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 ++ src/lobby/connection.rs | 3 +- src/lobby/ipc/mod.rs | 66 ++++++++++------------------------------- 8 files changed, 148 insertions(+), 52 deletions(-) create mode 100644 resources/opcodes.json 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,