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:
parent
7e6b493240
commit
d0bb7f4ba9
8 changed files with 148 additions and 52 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ config.json
|
||||||
*.bin
|
*.bin
|
||||||
*.db
|
*.db
|
||||||
config.yaml
|
config.yaml
|
||||||
|
src/opcodes.rs
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
57
build.rs
57
build.rs
|
@ -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
61
resources/opcodes.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue