1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-19 22:36:49 +00:00

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.
This commit is contained in:
Joshua Goins 2025-03-26 17:58:15 -04:00
parent 7e6b493240
commit d0bb7f4ba9
8 changed files with 148 additions and 52 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ config.json
*.bin *.bin
*.db *.db
config.yaml config.yaml
src/opcodes.rs

View file

@ -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. * The IPC opcodes _will_ change and must all be replaced.
* Check the game version used in the encryption key in `lib.rs`. * 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/<connection>/ipc`) if needed though.
## Contributing ## Contributing
Before making a pull request, make sure: Before making a pull request, make sure:

View file

@ -42,6 +42,11 @@ panic = "abort"
default = [] default = []
oodle = [] 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] [dependencies]
# Used for the web servers # Used for the web servers
axum = { version = "0.8", features = ["json", "tokio", "http1", "form", "query"], default-features = false } axum = { version = "0.8", features = ["json", "tokio", "http1", "form", "query"], default-features = false }

View file

@ -1,3 +1,60 @@
use std::path::PathBuf;
use serde_json::Value;
fn main() { fn main() {
// Add link search directory for Oodle
println!("cargo:rustc-link-search=./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!");
}
} }

61
resources/opcodes.json Normal file
View file

@ -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
}
]
}

View file

@ -34,6 +34,9 @@ pub mod login;
/// Patch server-specific code. /// Patch server-specific code.
pub mod patch; pub mod patch;
/// Opcodes, see `resources/opcodes.json`
pub mod opcodes;
/// Used in the encryption key. /// Used in the encryption key.
const GAME_VERSION: u16 = 7000; const GAME_VERSION: u16 = 7000;

View file

@ -11,6 +11,7 @@ use crate::{
config::get_config, config::get_config,
lobby::CharaMake, lobby::CharaMake,
oodle::OodleNetwork, oodle::OodleNetwork,
opcodes::ServerLobbyIpcType,
packet::{ packet::{
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType,
generate_encryption_key, parse_packet, send_packet, generate_encryption_key, parse_packet, send_packet,
@ -20,7 +21,7 @@ use crate::{
use super::ipc::{ use super::ipc::{
CharacterDetails, LobbyCharacterAction, LobbyCharacterActionKind, LobbyCharacterList, CharacterDetails, LobbyCharacterAction, LobbyCharacterActionKind, LobbyCharacterList,
LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData, ServerLobbyIpcSegment, LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData, ServerLobbyIpcSegment,
ServerLobbyIpcType, ServiceAccount, ServiceAccount,
}; };
use crate::lobby::ipc::ClientLobbyIpcSegment; use crate::lobby::ipc::ClientLobbyIpcSegment;

View file

@ -16,6 +16,7 @@ pub use service_account_list::{LobbyServiceAccountList, ServiceAccount};
use crate::{ use crate::{
common::{read_string, write_string}, common::{read_string, write_string},
opcodes::{ClientLobbyIpcType, ServerLobbyIpcType},
packet::{IpcSegment, ReadWriteIpcSegment}, packet::{IpcSegment, ReadWriteIpcSegment},
}; };
@ -24,12 +25,7 @@ pub type ClientLobbyIpcSegment = IpcSegment<ClientLobbyIpcType, ClientLobbyIpcDa
impl ReadWriteIpcSegment for ClientLobbyIpcSegment { impl ReadWriteIpcSegment for ClientLobbyIpcSegment {
fn calc_size(&self) -> u32 { fn calc_size(&self) -> u32 {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + match self.op_code { 16 + self.op_code.calc_size()
ClientLobbyIpcType::RequestCharacterList => 24,
ClientLobbyIpcType::RequestEnterWorld => 32,
ClientLobbyIpcType::ClientVersionInfo => 1144,
ClientLobbyIpcType::LobbyCharacterAction => 496,
}
} }
} }
@ -56,15 +52,7 @@ pub type ServerLobbyIpcSegment = IpcSegment<ServerLobbyIpcType, ServerLobbyIpcDa
impl ReadWriteIpcSegment for ServerLobbyIpcSegment { impl ReadWriteIpcSegment for ServerLobbyIpcSegment {
fn calc_size(&self) -> u32 { fn calc_size(&self) -> u32 {
// 16 is the size of the IPC header // 16 is the size of the IPC header
16 + match self.op_code { 16 + self.op_code.calc_size()
ServerLobbyIpcType::LobbyError => 536,
ServerLobbyIpcType::LobbyServiceAccountList => 656,
ServerLobbyIpcType::LobbyCharacterList => 2472,
ServerLobbyIpcType::LobbyEnterWorld => 160,
ServerLobbyIpcType::LobbyServerList => 528,
ServerLobbyIpcType::LobbyRetainerList => 210,
ServerLobbyIpcType::CharacterCreated => 2568,
}
} }
} }
@ -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] #[binrw]
#[br(import(magic: &ClientLobbyIpcType))] #[br(import(magic: &ClientLobbyIpcType))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ClientLobbyIpcData { pub enum ClientLobbyIpcData {
// Client->Server IPC /// Sent by the client after exchanging encryption information with the lobby server.
#[br(pre_assert(*magic == ClientLobbyIpcType::ClientVersionInfo))] #[br(pre_assert(*magic == ClientLobbyIpcType::ClientVersionInfo))]
ClientVersionInfo { ClientVersionInfo {
sequence: u64, sequence: u64,
@ -142,16 +96,19 @@ pub enum ClientLobbyIpcData {
#[br(map = read_string)] #[br(map = read_string)]
#[bw(ignore)] #[bw(ignore)]
version_info: String, 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))] #[br(pre_assert(*magic == ClientLobbyIpcType::RequestCharacterList))]
RequestCharacterList { RequestCharacterList {
#[brw(pad_before = 16)] #[brw(pad_before = 16)]
sequence: u64, sequence: u64,
// TODO: what is in here? // 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))] #[br(pre_assert(*magic == ClientLobbyIpcType::LobbyCharacterAction))]
LobbyCharacterAction(LobbyCharacterAction), LobbyCharacterAction(LobbyCharacterAction),
/// Sent by the client when it requests to enter a world.
#[br(pre_assert(*magic == ClientLobbyIpcType::RequestEnterWorld))] #[br(pre_assert(*magic == ClientLobbyIpcType::RequestEnterWorld))]
RequestEnterWorld { RequestEnterWorld {
sequence: u64, sequence: u64,
@ -164,15 +121,20 @@ pub enum ClientLobbyIpcData {
#[br(import(_magic: &ServerLobbyIpcType))] #[br(import(_magic: &ServerLobbyIpcType))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ServerLobbyIpcData { pub enum ServerLobbyIpcData {
/// Sent by the server to inform the client of their service accounts.
LobbyServiceAccountList(LobbyServiceAccountList), LobbyServiceAccountList(LobbyServiceAccountList),
/// Sent by the server to inform the client of their servers.
LobbyServerList(LobbyServerList), LobbyServerList(LobbyServerList),
/// Sent by the server to inform the client of their retainers.
LobbyRetainerList { LobbyRetainerList {
// TODO: what is in here? // TODO: what is in here?
#[brw(pad_before = 7)] #[brw(pad_before = 7)]
#[brw(pad_after = 202)] #[brw(pad_after = 202)]
unk1: u8, unk1: u8,
}, },
/// Sent by the server to inform the client of their characters.
LobbyCharacterList(LobbyCharacterList), LobbyCharacterList(LobbyCharacterList),
/// Sent by the server to tell the client how to connect to the world server.
LobbyEnterWorld { LobbyEnterWorld {
sequence: u64, sequence: u64,
actor_id: u32, actor_id: u32,
@ -192,6 +154,7 @@ pub enum ServerLobbyIpcData {
#[bw(map = write_string)] #[bw(map = write_string)]
host: String, host: String,
}, },
/// Sent by the server to indicate an lobby error occured.
LobbyError { LobbyError {
sequence: u64, sequence: u64,
error: u32, error: u32,
@ -200,6 +163,7 @@ pub enum ServerLobbyIpcData {
#[brw(pad_after = 516)] // empty and garbage #[brw(pad_after = 516)] // empty and garbage
unk1: u16, unk1: u16,
}, },
// Assumed what this is, but probably incorrect
CharacterCreated { CharacterCreated {
sequence: u64, sequence: u64,
unk1: u8, unk1: u8,