mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-27 08:57:45 +00:00
Show the actual character list on the lobby screen
This doesn't do any actual account checking yet, but it works pretty well.
This commit is contained in:
parent
7b77c19008
commit
4b67b22c9f
6 changed files with 239 additions and 125 deletions
|
@ -6,56 +6,13 @@ use kawari::lobby::ipc::{
|
||||||
CharacterDetails, ClientLobbyIpcData, LobbyCharacterActionKind, ServerLobbyIpcData,
|
CharacterDetails, ClientLobbyIpcData, LobbyCharacterActionKind, ServerLobbyIpcData,
|
||||||
ServerLobbyIpcSegment, ServerLobbyIpcType,
|
ServerLobbyIpcSegment, ServerLobbyIpcType,
|
||||||
};
|
};
|
||||||
|
use kawari::lobby::send_custom_world_packet;
|
||||||
use kawari::oodle::OodleNetwork;
|
use kawari::oodle::OodleNetwork;
|
||||||
use kawari::packet::CompressionType;
|
|
||||||
use kawari::packet::ConnectionType;
|
use kawari::packet::ConnectionType;
|
||||||
use kawari::packet::parse_packet;
|
|
||||||
use kawari::packet::send_packet;
|
|
||||||
use kawari::packet::{PacketSegment, PacketState, SegmentType, send_keep_alive};
|
use kawari::packet::{PacketSegment, PacketState, SegmentType, send_keep_alive};
|
||||||
use kawari::{CONTENT_ID, WORLD_NAME};
|
use kawari::{CONTENT_ID, WORLD_NAME};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::net::TcpStream;
|
|
||||||
|
|
||||||
/// Sends a custom IPC packet to the world server, meant for private server-to-server communication.
|
|
||||||
/// Returns the first custom IPC segment returned.
|
|
||||||
async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option<CustomIpcSegment> {
|
|
||||||
let mut stream = TcpStream::connect("127.0.0.1:7100").await.unwrap();
|
|
||||||
|
|
||||||
let mut packet_state = PacketState {
|
|
||||||
client_key: None,
|
|
||||||
serverbound_oodle: OodleNetwork::new(),
|
|
||||||
clientbound_oodle: OodleNetwork::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let segment: PacketSegment<CustomIpcSegment> = PacketSegment {
|
|
||||||
source_actor: 0,
|
|
||||||
target_actor: 0,
|
|
||||||
segment_type: SegmentType::CustomIpc { data: segment },
|
|
||||||
};
|
|
||||||
|
|
||||||
send_packet(
|
|
||||||
&mut stream,
|
|
||||||
&mut packet_state,
|
|
||||||
ConnectionType::None,
|
|
||||||
CompressionType::Uncompressed,
|
|
||||||
&[segment],
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// read response
|
|
||||||
let mut buf = [0; 2056];
|
|
||||||
let n = stream.read(&mut buf).await.expect("Failed to read data!");
|
|
||||||
|
|
||||||
println!("Got {n} bytes of response!");
|
|
||||||
|
|
||||||
let (segments, _) = parse_packet::<CustomIpcSegment>(&buf[..n], &mut packet_state).await;
|
|
||||||
|
|
||||||
match &segments[0].segment_type {
|
|
||||||
SegmentType::CustomIpc { data } => Some(data.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -333,15 +290,14 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_segment = send_custom_world_packet(ipc_segment).await.unwrap();
|
let response_segment =
|
||||||
|
send_custom_world_packet(ipc_segment).await.unwrap();
|
||||||
|
|
||||||
match &response_segment.data {
|
match &response_segment.data {
|
||||||
CustomIpcData::ActorIdFound { actor_id } => {
|
CustomIpcData::ActorIdFound { actor_id } => {
|
||||||
our_actor_id = *actor_id;
|
our_actor_id = *actor_id;
|
||||||
}
|
}
|
||||||
_ => panic!(
|
_ => panic!("Unexpected custom IPC packet type here!"),
|
||||||
"Unexpected custom IPC packet type here!"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use kawari::WORLD_NAME;
|
||||||
use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType};
|
use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType};
|
||||||
use kawari::config::get_config;
|
use kawari::config::get_config;
|
||||||
use kawari::lobby::CharaMake;
|
use kawari::lobby::ipc::CharacterDetails;
|
||||||
|
use kawari::lobby::{CharaMake, ClientSelectData};
|
||||||
use kawari::oodle::OodleNetwork;
|
use kawari::oodle::OodleNetwork;
|
||||||
use kawari::packet::{ConnectionType, PacketSegment, PacketState, SegmentType, send_keep_alive};
|
use kawari::packet::{
|
||||||
|
CompressionType, ConnectionType, PacketSegment, PacketState, SegmentType, send_keep_alive,
|
||||||
|
send_packet,
|
||||||
|
};
|
||||||
use kawari::world::PlayerData;
|
use kawari::world::PlayerData;
|
||||||
use kawari::world::ipc::{
|
use kawari::world::ipc::{
|
||||||
ClientZoneIpcData, CommonSpawn, GameMasterCommandType, ObjectKind, ServerZoneIpcData,
|
ClientZoneIpcData, CommonSpawn, GameMasterCommandType, ObjectKind, ServerZoneIpcData,
|
||||||
|
@ -71,6 +76,91 @@ fn find_actor_id(connection: &Arc<Mutex<Connection>>, content_id: u64) -> u32 {
|
||||||
stmt.query_row((content_id,), |row| row.get(0)).unwrap()
|
stmt.query_row((content_id,), |row| row.get(0)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_character_list(
|
||||||
|
connection: &Arc<Mutex<Connection>>,
|
||||||
|
service_account_id: u32,
|
||||||
|
) -> Vec<CharacterDetails> {
|
||||||
|
let connection = connection.lock().unwrap();
|
||||||
|
|
||||||
|
let content_actor_ids: Vec<(u32, u32)>;
|
||||||
|
|
||||||
|
// find the content ids associated with the service account
|
||||||
|
{
|
||||||
|
let mut stmt = connection
|
||||||
|
.prepare("SELECT content_id, actor_id FROM characters WHERE service_account_id = ?1")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
content_actor_ids = stmt
|
||||||
|
.query_map((service_account_id,), |row| Ok((row.get(0)?, row.get(1)?)))
|
||||||
|
.unwrap()
|
||||||
|
.map(|x| x.unwrap())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut characters = Vec::new();
|
||||||
|
|
||||||
|
for (index, (content_id, actor_id)) in content_actor_ids.iter().enumerate() {
|
||||||
|
dbg!(content_id);
|
||||||
|
|
||||||
|
let mut stmt = connection
|
||||||
|
.prepare("SELECT name, chara_make FROM character_data WHERE content_id = ?1")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (name, chara_make): (String, String) = stmt
|
||||||
|
.query_row((content_id,), |row| Ok((row.get(0)?, row.get(1)?)))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let chara_make = CharaMake::from_json(&chara_make);
|
||||||
|
|
||||||
|
let select_data = ClientSelectData {
|
||||||
|
game_name_unk: "Final Fantasy".to_string(),
|
||||||
|
current_class: 2,
|
||||||
|
class_levels: [5; 30],
|
||||||
|
race: chara_make.customize.race as i32,
|
||||||
|
subrace: chara_make.customize.subrace as i32,
|
||||||
|
gender: chara_make.customize.gender as i32,
|
||||||
|
birth_month: chara_make.birth_month,
|
||||||
|
birth_day: chara_make.birth_day,
|
||||||
|
guardian: chara_make.guardian,
|
||||||
|
unk8: 0,
|
||||||
|
unk9: 0,
|
||||||
|
zone_id: ZONE_ID as i32,
|
||||||
|
unk11: 0,
|
||||||
|
customize: chara_make.customize,
|
||||||
|
unk12: 0,
|
||||||
|
unk13: 0,
|
||||||
|
unk14: [0; 10],
|
||||||
|
unk15: 0,
|
||||||
|
unk16: 0,
|
||||||
|
legacy_character: 0,
|
||||||
|
unk18: 0,
|
||||||
|
unk19: 0,
|
||||||
|
unk20: 0,
|
||||||
|
unk21: String::new(),
|
||||||
|
unk22: 0,
|
||||||
|
unk23: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
characters.push(CharacterDetails {
|
||||||
|
actor_id: *actor_id,
|
||||||
|
content_id: *content_id as u64,
|
||||||
|
index: index as u32,
|
||||||
|
unk1: [0; 16],
|
||||||
|
origin_server_id: WORLD_ID,
|
||||||
|
current_server_id: WORLD_ID,
|
||||||
|
character_name: name.clone(),
|
||||||
|
origin_server_name: WORLD_NAME.to_string(),
|
||||||
|
current_server_name: WORLD_NAME.to_string(),
|
||||||
|
character_detail_json: select_data.to_json(),
|
||||||
|
unk2: [0; 20],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(&characters);
|
||||||
|
|
||||||
|
characters
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_content_id() -> u32 {
|
fn generate_content_id() -> u32 {
|
||||||
rand::random()
|
rand::random()
|
||||||
}
|
}
|
||||||
|
@ -922,6 +1012,37 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CustomIpcData::RequestCharacterList { service_account_id } => {
|
||||||
|
let characters =
|
||||||
|
get_character_list(&db_connection, *service_account_id);
|
||||||
|
|
||||||
|
// send response
|
||||||
|
{
|
||||||
|
send_packet::<CustomIpcSegment>(
|
||||||
|
&mut connection.socket,
|
||||||
|
&mut connection.state,
|
||||||
|
ConnectionType::None,
|
||||||
|
CompressionType::Uncompressed,
|
||||||
|
&[PacketSegment {
|
||||||
|
source_actor: 0,
|
||||||
|
target_actor: 0,
|
||||||
|
segment_type: SegmentType::CustomIpc {
|
||||||
|
data: CustomIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: CustomIpcType::RequestCharacterListRepsonse,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: CustomIpcData::RequestCharacterListRepsonse {
|
||||||
|
characters
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"The server is recieving a response or unknown custom IPC!"
|
"The server is recieving a response or unknown custom IPC!"
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ use binrw::binrw;
|
||||||
use crate::{
|
use crate::{
|
||||||
CHAR_NAME_MAX_LENGTH,
|
CHAR_NAME_MAX_LENGTH,
|
||||||
common::read_string,
|
common::read_string,
|
||||||
|
lobby::ipc::CharacterDetails,
|
||||||
packet::{IpcSegment, ReadWriteIpcSegment},
|
packet::{IpcSegment, ReadWriteIpcSegment},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,6 +21,8 @@ impl ReadWriteIpcSegment for CustomIpcSegment {
|
||||||
CustomIpcType::ActorIdFound => 4,
|
CustomIpcType::ActorIdFound => 4,
|
||||||
CustomIpcType::CheckNameIsAvailable => CHAR_NAME_MAX_LENGTH as u32,
|
CustomIpcType::CheckNameIsAvailable => CHAR_NAME_MAX_LENGTH as u32,
|
||||||
CustomIpcType::NameIsAvailableResponse => 1,
|
CustomIpcType::NameIsAvailableResponse => 1,
|
||||||
|
CustomIpcType::RequestCharacterList => 4,
|
||||||
|
CustomIpcType::RequestCharacterListRepsonse => 1184 * 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,10 @@ pub enum CustomIpcType {
|
||||||
CheckNameIsAvailable = 0x5,
|
CheckNameIsAvailable = 0x5,
|
||||||
/// Response to CheckNameIsAvailable
|
/// Response to CheckNameIsAvailable
|
||||||
NameIsAvailableResponse = 0x6,
|
NameIsAvailableResponse = 0x6,
|
||||||
|
/// Request the character list from the world server
|
||||||
|
RequestCharacterList = 0x7,
|
||||||
|
/// Response to RequestCharacterList
|
||||||
|
RequestCharacterListRepsonse = 0x8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -76,6 +83,15 @@ pub enum CustomIpcData {
|
||||||
},
|
},
|
||||||
#[br(pre_assert(*magic == CustomIpcType::NameIsAvailableResponse))]
|
#[br(pre_assert(*magic == CustomIpcType::NameIsAvailableResponse))]
|
||||||
NameIsAvailableResponse { free: u8 },
|
NameIsAvailableResponse { free: u8 },
|
||||||
|
#[br(pre_assert(*magic == CustomIpcType::RequestCharacterList))]
|
||||||
|
RequestCharacterList { service_account_id: u32 },
|
||||||
|
#[br(pre_assert(*magic == CustomIpcType::RequestCharacterListRepsonse))]
|
||||||
|
RequestCharacterListRepsonse {
|
||||||
|
#[bw(calc = characters.len() as u8)]
|
||||||
|
num_characters: u8,
|
||||||
|
#[br(count = num_characters)]
|
||||||
|
characters: Vec<CharacterDetails>, // TODO: maybe chunk this into 4 parts ala the lobby server?
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CustomIpcData {
|
impl Default for CustomIpcData {
|
||||||
|
|
|
@ -68,30 +68,30 @@ impl CustomizeData {
|
||||||
Self {
|
Self {
|
||||||
race: json[0].as_str().unwrap().parse::<u8>().unwrap(),
|
race: json[0].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
gender: json[1].as_str().unwrap().parse::<u8>().unwrap(),
|
gender: json[1].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
height: json[2].as_str().unwrap().parse::<u8>().unwrap(),
|
age: json[2].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
subrace: json[3].as_str().unwrap().parse::<u8>().unwrap(),
|
height: json[3].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
face: json[4].as_str().unwrap().parse::<u8>().unwrap(),
|
subrace: json[4].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
hair: json[5].as_str().unwrap().parse::<u8>().unwrap(),
|
face: json[5].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
enable_highlights: json[6].as_str().unwrap().parse::<u8>().unwrap(),
|
hair: json[6].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
skin_tone: json[7].as_str().unwrap().parse::<u8>().unwrap(),
|
enable_highlights: json[7].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
right_eye_color: json[8].as_str().unwrap().parse::<u8>().unwrap(),
|
skin_tone: json[8].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
hair_tone: json[9].as_str().unwrap().parse::<u8>().unwrap(),
|
right_eye_color: json[9].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
highlights: json[10].as_str().unwrap().parse::<u8>().unwrap(),
|
hair_tone: json[10].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
facial_features: json[11].as_str().unwrap().parse::<u8>().unwrap(),
|
highlights: json[11].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
facial_feature_color: json[12].as_str().unwrap().parse::<u8>().unwrap(),
|
facial_features: json[12].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
eyebrows: json[13].as_str().unwrap().parse::<u8>().unwrap(),
|
facial_feature_color: json[13].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
left_eye_color: json[14].as_str().unwrap().parse::<u8>().unwrap(),
|
eyebrows: json[14].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
eyes: json[15].as_str().unwrap().parse::<u8>().unwrap(),
|
left_eye_color: json[15].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
nose: json[16].as_str().unwrap().parse::<u8>().unwrap(),
|
eyes: json[16].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
jaw: json[17].as_str().unwrap().parse::<u8>().unwrap(),
|
nose: json[17].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
mouth: json[18].as_str().unwrap().parse::<u8>().unwrap(),
|
jaw: json[18].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
lips_tone_fur_pattern: json[19].as_str().unwrap().parse::<u8>().unwrap(),
|
mouth: json[19].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
race_feature_size: json[20].as_str().unwrap().parse::<u8>().unwrap(),
|
lips_tone_fur_pattern: json[20].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
race_feature_type: json[21].as_str().unwrap().parse::<u8>().unwrap(),
|
race_feature_size: json[21].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
bust: json[22].as_str().unwrap().parse::<u8>().unwrap(),
|
race_feature_type: json[22].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
face_paint: json[23].as_str().unwrap().parse::<u8>().unwrap(),
|
bust: json[23].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
face_paint_color: json[24].as_str().unwrap().parse::<u8>().unwrap(),
|
face_paint: json[24].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
age: 1,
|
face_paint_color: json[25].as_str().unwrap().parse::<u8>().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::{io::AsyncReadExt, net::TcpStream};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, DEITY, NAMEDAY_DAY, NAMEDAY_MONTH, WORLD_ID, WORLD_NAME,
|
WORLD_ID, WORLD_NAME,
|
||||||
ZONE_ID,
|
|
||||||
blowfish::Blowfish,
|
blowfish::Blowfish,
|
||||||
common::timestamp_secs,
|
common::{
|
||||||
|
custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType},
|
||||||
|
timestamp_secs,
|
||||||
|
},
|
||||||
|
oodle::OodleNetwork,
|
||||||
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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::ipc::{
|
||||||
client_select_data::ClientSelectData,
|
CharacterDetails, LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server,
|
||||||
ipc::{
|
ServerLobbyIpcData, ServerLobbyIpcSegment, ServerLobbyIpcType, ServiceAccount,
|
||||||
CharacterDetails, LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server,
|
|
||||||
ServerLobbyIpcData, ServerLobbyIpcSegment, ServerLobbyIpcType, ServiceAccount,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
||||||
|
|
||||||
|
@ -181,48 +181,28 @@ impl LobbyConnection {
|
||||||
|
|
||||||
// now send them the character list
|
// now send them the character list
|
||||||
{
|
{
|
||||||
let select_data = ClientSelectData {
|
let charlist_request = CustomIpcSegment {
|
||||||
game_name_unk: "Final Fantasy".to_string(),
|
unk1: 0,
|
||||||
current_class: 2,
|
unk2: 0,
|
||||||
class_levels: [5; 30],
|
op_code: CustomIpcType::RequestCharacterList,
|
||||||
race: CUSTOMIZE_DATA.race as i32,
|
server_id: 0,
|
||||||
subrace: CUSTOMIZE_DATA.subrace as i32,
|
timestamp: 0,
|
||||||
gender: CUSTOMIZE_DATA.gender as i32,
|
data: CustomIpcData::RequestCharacterList {
|
||||||
birth_month: NAMEDAY_MONTH as i32,
|
service_account_id: 0x1, // TODO: placeholder
|
||||||
birth_day: NAMEDAY_DAY as i32,
|
},
|
||||||
guardian: DEITY as i32,
|
|
||||||
unk8: 0,
|
|
||||||
unk9: 0,
|
|
||||||
zone_id: ZONE_ID as i32,
|
|
||||||
unk11: 0,
|
|
||||||
customize: CUSTOMIZE_DATA,
|
|
||||||
unk12: 0,
|
|
||||||
unk13: 0,
|
|
||||||
unk14: [0; 10],
|
|
||||||
unk15: 0,
|
|
||||||
unk16: 0,
|
|
||||||
legacy_character: 0,
|
|
||||||
unk18: 0,
|
|
||||||
unk19: 0,
|
|
||||||
unk20: 0,
|
|
||||||
unk21: String::new(),
|
|
||||||
unk22: 0,
|
|
||||||
unk23: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut characters = vec![CharacterDetails {
|
let name_response = send_custom_world_packet(charlist_request)
|
||||||
actor_id: 0,
|
.await
|
||||||
content_id: CONTENT_ID,
|
.expect("Failed to get name request packet!");
|
||||||
index: 0,
|
let CustomIpcData::RequestCharacterListRepsonse { characters } = &name_response.data
|
||||||
unk1: [0; 16],
|
else {
|
||||||
origin_server_id: WORLD_ID,
|
panic!("Unexpedted custom IPC type!")
|
||||||
current_server_id: WORLD_ID,
|
};
|
||||||
character_name: CHAR_NAME.to_string(),
|
|
||||||
origin_server_name: WORLD_NAME.to_string(),
|
let mut characters = characters.to_vec();
|
||||||
current_server_name: WORLD_NAME.to_string(),
|
|
||||||
character_detail_json: select_data.to_json(),
|
dbg!(&characters);
|
||||||
unk2: [0; 20],
|
|
||||||
}];
|
|
||||||
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let mut characters_in_packet = Vec::new();
|
let mut characters_in_packet = Vec::new();
|
||||||
|
@ -355,3 +335,43 @@ impl LobbyConnection {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a custom IPC packet to the world server, meant for private server-to-server communication.
|
||||||
|
/// Returns the first custom IPC segment returned.
|
||||||
|
pub async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option<CustomIpcSegment> {
|
||||||
|
let mut stream = TcpStream::connect("127.0.0.1:7100").await.unwrap();
|
||||||
|
|
||||||
|
let mut packet_state = PacketState {
|
||||||
|
client_key: None,
|
||||||
|
serverbound_oodle: OodleNetwork::new(),
|
||||||
|
clientbound_oodle: OodleNetwork::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let segment: PacketSegment<CustomIpcSegment> = PacketSegment {
|
||||||
|
source_actor: 0,
|
||||||
|
target_actor: 0,
|
||||||
|
segment_type: SegmentType::CustomIpc { data: segment },
|
||||||
|
};
|
||||||
|
|
||||||
|
send_packet(
|
||||||
|
&mut stream,
|
||||||
|
&mut packet_state,
|
||||||
|
ConnectionType::None,
|
||||||
|
CompressionType::Uncompressed,
|
||||||
|
&[segment],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// read response
|
||||||
|
let mut buf = [0; 10024]; // TODO: this large buffer is just working around these packets not being compressed, but they really should be!
|
||||||
|
let n = stream.read(&mut buf).await.expect("Failed to read data!");
|
||||||
|
|
||||||
|
println!("Got {n} bytes of response!");
|
||||||
|
|
||||||
|
let (segments, _) = parse_packet::<CustomIpcSegment>(&buf[..n], &mut packet_state).await;
|
||||||
|
|
||||||
|
match &segments[0].segment_type {
|
||||||
|
SegmentType::CustomIpc { data } => Some(data.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,10 @@ mod chara_make;
|
||||||
pub use chara_make::CharaMake;
|
pub use chara_make::CharaMake;
|
||||||
|
|
||||||
mod client_select_data;
|
mod client_select_data;
|
||||||
|
pub use client_select_data::ClientSelectData;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
pub use connection::LobbyConnection;
|
pub use connection::{LobbyConnection, send_custom_world_packet};
|
||||||
|
|
||||||
/// The IPC packets for the Lobby connection.
|
/// The IPC packets for the Lobby connection.
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
|
Loading…
Add table
Reference in a new issue