1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-23 15:47:45 +00:00

Add Lobby IPC packets for character creation

This adds the packets required for name confirmation, character creation etc so
you can actually go through the process now!
This commit is contained in:
Joshua Goins 2025-03-13 23:30:48 -04:00
parent 4d4720b192
commit 5c22532474
5 changed files with 176 additions and 2 deletions

View file

@ -2,6 +2,7 @@ use std::cmp::min;
use std::time::{SystemTime, UNIX_EPOCH};
use kawari::blowfish::Blowfish;
use kawari::chara_make::CharaMake;
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
use kawari::encryption::generate_encryption_key;
use kawari::ipc::{
@ -74,6 +75,7 @@ async fn main() {
action,
world_id,
name,
json,
..
} => {
match action {
@ -83,7 +85,7 @@ async fn main() {
);
// reject
{
/*{
let ipc = IPCSegment {
unk1: 0,
unk2: 0,
@ -97,6 +99,87 @@ async fn main() {
},
};
let response_packet = PacketSegment {
source_actor: 0x0,
target_actor: 0x0,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Uncompressed,
)
.await;
}*/
// accept
{
let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::CharacterCreated,
server_id: 0,
timestamp: 0,
data: IPCStructData::CharacterCreated {
unk1: 0x4,
unk2: 0x00010101,
details: CharacterDetails {
content_id: CONTENT_ID,
character_name: name.clone(),
origin_server_name: "KAWARI"
.to_string(),
current_server_name: "KAWARI"
.to_string(),
..Default::default()
},
},
};
let response_packet = PacketSegment {
source_actor: 0x0,
target_actor: 0x0,
segment_type: SegmentType::Ipc { data: ipc },
};
send_packet(
&mut write,
&[response_packet],
&mut state,
CompressionType::Uncompressed,
)
.await;
}
}
LobbyCharacterAction::Create => {
tracing::info!("Player is creating a new character!");
let chara_make = CharaMake::from_json(json);
println!("charamake: {:#?}", chara_make);
// a slightly different character created packet now
{
let ipc = IPCSegment {
unk1: 0,
unk2: 0,
op_code: IPCOpCode::CharacterCreated,
server_id: 0,
timestamp: 0,
data: IPCStructData::CharacterCreated {
unk1: 0x5,
unk2: 0x00020101,
details: CharacterDetails {
id: 0x07369f3a, // notice that we give them an id now
content_id: CONTENT_ID,
character_name: name.clone(),
origin_server_name: "KAWARI"
.to_string(),
current_server_name: "KAWARI"
.to_string(),
..Default::default()
},
},
};
let response_packet = PacketSegment {
source_actor: 0x0,
target_actor: 0x0,
@ -111,7 +194,6 @@ async fn main() {
.await;
}
}
LobbyCharacterAction::Create => todo!(),
LobbyCharacterAction::Rename => todo!(),
LobbyCharacterAction::Delete => todo!(),
LobbyCharacterAction::Move => todo!(),

48
src/chara_make.rs Normal file
View file

@ -0,0 +1,48 @@
use binrw::binrw;
use serde_json::{Value, json};
use crate::client_select_data::ClientCustomizeData;
#[derive(Debug)]
pub struct CharaMake {
pub customize: ClientCustomizeData,
pub unk1: i32, // always 1?
pub guardian: i32,
pub birth_month: i32,
pub classjob: i32,
pub birth_day: i32,
pub unk6: i32, // always 1?
}
impl CharaMake {
pub fn from_json(json: &str) -> Self {
let v: Value = serde_json::from_str(json).unwrap();
let content = &v["content"];
Self {
customize: ClientCustomizeData::from_json(&content[0]),
unk1: content[1].as_str().unwrap().parse::<i32>().unwrap(),
guardian: content[2].as_str().unwrap().parse::<i32>().unwrap(),
birth_month: content[3].as_str().unwrap().parse::<i32>().unwrap(),
classjob: content[4].as_str().unwrap().parse::<i32>().unwrap(),
birth_day: content[5].as_str().unwrap().parse::<i32>().unwrap(),
unk6: content[6].as_str().unwrap().parse::<i32>().unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_charamake() {
let json = "{\"classid\":118,\"classname\":\"CharaMake\",\"content\":[[\"1\",\"0\",\"1\",\"50\",\"1\",\"5\",\"161\",\"0\",\"3\",\"30\",\"103\",\"0\",\"0\",\"0\",\"1\",\"30\",\"4\",\"5\",\"2\",\"128\",\"35\",\"50\",\"0\",\"0\",\"0\",\"0\"],\"1\",\"1\",\"1\",\"1\",\"1\",\"1\"]}";
let chara_make = CharaMake::from_json(json);
assert_eq!(chara_make.customize.gender, 0);
assert_eq!(chara_make.classjob, 1);
// TODO: add more asserts
}
}

View file

@ -60,6 +60,36 @@ impl ClientCustomizeData {
self.face_paint_color.to_string(),
])
}
pub fn from_json(json: &Value) -> Self {
Self {
race: json[0].as_str().unwrap().parse::<i32>().unwrap(),
gender: json[1].as_str().unwrap().parse::<i32>().unwrap(),
height: json[2].as_str().unwrap().parse::<i32>().unwrap(),
subrace: json[3].as_str().unwrap().parse::<i32>().unwrap(),
face: json[4].as_str().unwrap().parse::<i32>().unwrap(),
hair: json[5].as_str().unwrap().parse::<i32>().unwrap(),
enable_highlights: json[6].as_str().unwrap().parse::<i32>().unwrap(),
skin_tone: json[7].as_str().unwrap().parse::<i32>().unwrap(),
right_eye_color: json[8].as_str().unwrap().parse::<i32>().unwrap(),
hair_tone: json[9].as_str().unwrap().parse::<i32>().unwrap(),
highlights: json[10].as_str().unwrap().parse::<i32>().unwrap(),
facial_features: json[11].as_str().unwrap().parse::<i32>().unwrap(),
facial_feature_color: json[12].as_str().unwrap().parse::<i32>().unwrap(),
eyebrows: json[13].as_str().unwrap().parse::<i32>().unwrap(),
left_eye_color: json[14].as_str().unwrap().parse::<i32>().unwrap(),
eyes: json[15].as_str().unwrap().parse::<i32>().unwrap(),
nose: json[16].as_str().unwrap().parse::<i32>().unwrap(),
jaw: json[17].as_str().unwrap().parse::<i32>().unwrap(),
mouth: json[18].as_str().unwrap().parse::<i32>().unwrap(),
lips_tone_fur_pattern: json[19].as_str().unwrap().parse::<i32>().unwrap(),
race_feature_size: json[20].as_str().unwrap().parse::<i32>().unwrap(),
race_feature_type: json[21].as_str().unwrap().parse::<i32>().unwrap(),
bust: json[22].as_str().unwrap().parse::<i32>().unwrap(),
face_paint: json[23].as_str().unwrap().parse::<i32>().unwrap(),
face_paint_color: json[24].as_str().unwrap().parse::<i32>().unwrap(),
}
}
}
/// See https://github.com/aers/FFXIVClientStructs/blob/main/FFXIVClientStructs/FFXIV/Application/Network/WorkDefinitions/ClientSelectData.cs
pub struct ClientSelectData {

View file

@ -96,6 +96,8 @@ pub enum IPCOpCode {
Unk10 = 0x110,
// Unknown, server sends this in response to Unk7
Unk11 = 0x156,
// Assumed what this is, but probably incorrect
CharacterCreated = 0xE,
}
#[binrw]
@ -493,6 +495,16 @@ pub enum IPCStructData {
#[brw(pad_after = 516)] // mostly empty
unk3: u32,
},
#[br(pre_assert(false))]
CharacterCreated {
#[brw(pad_after = 4)] // empty
unk1: u32,
#[brw(pad_after = 4)] // empty
unk2: u32,
#[brw(pad_before = 32)] // empty
#[brw(pad_after = 1136)] // empty
details: CharacterDetails,
},
}
#[binrw]
@ -558,6 +570,7 @@ impl IPCSegment {
IPCStructData::Unk10 { .. } => 8,
IPCStructData::Unk11 { .. } => 32,
IPCStructData::NameRejection { .. } => 536,
IPCStructData::CharacterCreated { .. } => 2568,
}
}
}

View file

@ -3,6 +3,7 @@ use rand::Rng;
use rand::distributions::Alphanumeric;
pub mod blowfish;
pub mod chara_make;
pub mod client_select_data;
mod common;
mod compression;