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:
parent
4d4720b192
commit
5c22532474
5 changed files with 176 additions and 2 deletions
|
@ -2,6 +2,7 @@ use std::cmp::min;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use kawari::blowfish::Blowfish;
|
use kawari::blowfish::Blowfish;
|
||||||
|
use kawari::chara_make::CharaMake;
|
||||||
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
|
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
|
||||||
use kawari::encryption::generate_encryption_key;
|
use kawari::encryption::generate_encryption_key;
|
||||||
use kawari::ipc::{
|
use kawari::ipc::{
|
||||||
|
@ -74,6 +75,7 @@ async fn main() {
|
||||||
action,
|
action,
|
||||||
world_id,
|
world_id,
|
||||||
name,
|
name,
|
||||||
|
json,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
match action {
|
match action {
|
||||||
|
@ -83,7 +85,7 @@ async fn main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// reject
|
// reject
|
||||||
{
|
/*{
|
||||||
let ipc = IPCSegment {
|
let ipc = IPCSegment {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
unk2: 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 {
|
let response_packet = PacketSegment {
|
||||||
source_actor: 0x0,
|
source_actor: 0x0,
|
||||||
target_actor: 0x0,
|
target_actor: 0x0,
|
||||||
|
@ -111,7 +194,6 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LobbyCharacterAction::Create => todo!(),
|
|
||||||
LobbyCharacterAction::Rename => todo!(),
|
LobbyCharacterAction::Rename => todo!(),
|
||||||
LobbyCharacterAction::Delete => todo!(),
|
LobbyCharacterAction::Delete => todo!(),
|
||||||
LobbyCharacterAction::Move => todo!(),
|
LobbyCharacterAction::Move => todo!(),
|
||||||
|
|
48
src/chara_make.rs
Normal file
48
src/chara_make.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,6 +60,36 @@ impl ClientCustomizeData {
|
||||||
self.face_paint_color.to_string(),
|
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
|
/// See https://github.com/aers/FFXIVClientStructs/blob/main/FFXIVClientStructs/FFXIV/Application/Network/WorkDefinitions/ClientSelectData.cs
|
||||||
pub struct ClientSelectData {
|
pub struct ClientSelectData {
|
||||||
|
|
13
src/ipc.rs
13
src/ipc.rs
|
@ -96,6 +96,8 @@ pub enum IPCOpCode {
|
||||||
Unk10 = 0x110,
|
Unk10 = 0x110,
|
||||||
// Unknown, server sends this in response to Unk7
|
// Unknown, server sends this in response to Unk7
|
||||||
Unk11 = 0x156,
|
Unk11 = 0x156,
|
||||||
|
// Assumed what this is, but probably incorrect
|
||||||
|
CharacterCreated = 0xE,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -493,6 +495,16 @@ pub enum IPCStructData {
|
||||||
#[brw(pad_after = 516)] // mostly empty
|
#[brw(pad_after = 516)] // mostly empty
|
||||||
unk3: u32,
|
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]
|
#[binrw]
|
||||||
|
@ -558,6 +570,7 @@ impl IPCSegment {
|
||||||
IPCStructData::Unk10 { .. } => 8,
|
IPCStructData::Unk10 { .. } => 8,
|
||||||
IPCStructData::Unk11 { .. } => 32,
|
IPCStructData::Unk11 { .. } => 32,
|
||||||
IPCStructData::NameRejection { .. } => 536,
|
IPCStructData::NameRejection { .. } => 536,
|
||||||
|
IPCStructData::CharacterCreated { .. } => 2568,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use rand::Rng;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
|
|
||||||
pub mod blowfish;
|
pub mod blowfish;
|
||||||
|
pub mod chara_make;
|
||||||
pub mod client_select_data;
|
pub mod client_select_data;
|
||||||
mod common;
|
mod common;
|
||||||
mod compression;
|
mod compression;
|
||||||
|
|
Loading…
Add table
Reference in a new issue