mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-25 08:27:44 +00:00
Move lobby character actions to LobbyConnection, support deleting characters
This commit is contained in:
parent
5c8998ddb2
commit
107a00aa92
6 changed files with 320 additions and 242 deletions
|
@ -4,14 +4,11 @@ use kawari::common::custom_ipc::CustomIpcType;
|
||||||
use kawari::common::get_world_name;
|
use kawari::common::get_world_name;
|
||||||
use kawari::config::get_config;
|
use kawari::config::get_config;
|
||||||
use kawari::lobby::LobbyConnection;
|
use kawari::lobby::LobbyConnection;
|
||||||
use kawari::lobby::ipc::{
|
use kawari::lobby::ipc::{ClientLobbyIpcData, ServerLobbyIpcSegment};
|
||||||
CharacterDetails, ClientLobbyIpcData, LobbyCharacterActionKind, ServerLobbyIpcData,
|
|
||||||
ServerLobbyIpcSegment, ServerLobbyIpcType,
|
|
||||||
};
|
|
||||||
use kawari::lobby::send_custom_world_packet;
|
use kawari::lobby::send_custom_world_packet;
|
||||||
use kawari::oodle::OodleNetwork;
|
use kawari::oodle::OodleNetwork;
|
||||||
use kawari::packet::ConnectionType;
|
use kawari::packet::ConnectionType;
|
||||||
use kawari::packet::{PacketSegment, PacketState, SegmentType, send_keep_alive};
|
use kawari::packet::{PacketState, SegmentType, send_keep_alive};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
@ -43,10 +40,9 @@ async fn main() {
|
||||||
state,
|
state,
|
||||||
session_id: None,
|
session_id: None,
|
||||||
stored_character_creation_name: String::new(),
|
stored_character_creation_name: String::new(),
|
||||||
|
world_name: world_name.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let world_name = world_name.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = [0; 2056];
|
let mut buf = [0; 2056];
|
||||||
loop {
|
loop {
|
||||||
|
@ -63,7 +59,7 @@ async fn main() {
|
||||||
for segment in &segments {
|
for segment in &segments {
|
||||||
match &segment.segment_type {
|
match &segment.segment_type {
|
||||||
SegmentType::InitializeEncryption { phrase, key } => {
|
SegmentType::InitializeEncryption { phrase, key } => {
|
||||||
connection.initialize_encryption(phrase, key).await;
|
connection.initialize_encryption(phrase, key).await
|
||||||
}
|
}
|
||||||
SegmentType::Ipc { data } => match &data.data {
|
SegmentType::Ipc { data } => match &data.data {
|
||||||
ClientLobbyIpcData::ClientVersionInfo {
|
ClientLobbyIpcData::ClientVersionInfo {
|
||||||
|
@ -83,194 +79,10 @@ async fn main() {
|
||||||
//connection.send_error(*sequence, 1012, 13101).await;
|
//connection.send_error(*sequence, 1012, 13101).await;
|
||||||
}
|
}
|
||||||
ClientLobbyIpcData::RequestCharacterList { sequence } => {
|
ClientLobbyIpcData::RequestCharacterList { sequence } => {
|
||||||
tracing::info!("Client is requesting character list...");
|
connection.send_lobby_info(*sequence).await
|
||||||
|
|
||||||
connection.send_lobby_info(*sequence).await;
|
|
||||||
}
|
}
|
||||||
ClientLobbyIpcData::LobbyCharacterAction(character_action) => {
|
ClientLobbyIpcData::LobbyCharacterAction(character_action) => {
|
||||||
match &character_action.action {
|
connection.handle_character_action(character_action).await
|
||||||
LobbyCharacterActionKind::ReserveName => {
|
|
||||||
tracing::info!(
|
|
||||||
"Player is requesting {} as a new character name!",
|
|
||||||
character_action.name
|
|
||||||
);
|
|
||||||
|
|
||||||
// check with the world server if the name is available
|
|
||||||
let name_request = CustomIpcSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: CustomIpcType::CheckNameIsAvailable,
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: CustomIpcData::CheckNameIsAvailable {
|
|
||||||
name: character_action.name.clone(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let name_response =
|
|
||||||
send_custom_world_packet(name_request)
|
|
||||||
.await
|
|
||||||
.expect("Failed to get name request packet!");
|
|
||||||
let CustomIpcData::NameIsAvailableResponse { free } =
|
|
||||||
&name_response.data
|
|
||||||
else {
|
|
||||||
panic!("Unexpedted custom IPC type!")
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::info!("Is name free? {free}");
|
|
||||||
|
|
||||||
// TODO: use read_bool_as
|
|
||||||
let free: bool = *free == 1u8;
|
|
||||||
|
|
||||||
if free {
|
|
||||||
connection.stored_character_creation_name =
|
|
||||||
character_action.name.clone();
|
|
||||||
|
|
||||||
let ipc = ServerLobbyIpcSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: ServerLobbyIpcType::CharacterCreated,
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: ServerLobbyIpcData::CharacterCreated {
|
|
||||||
sequence: character_action.sequence + 1,
|
|
||||||
unk: 0x00010101,
|
|
||||||
details: CharacterDetails {
|
|
||||||
character_name: character_action
|
|
||||||
.name
|
|
||||||
.clone(),
|
|
||||||
origin_server_name: world_name.clone(),
|
|
||||||
current_server_name: world_name.clone(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send_segment(PacketSegment {
|
|
||||||
source_actor: 0x0,
|
|
||||||
target_actor: 0x0,
|
|
||||||
segment_type: SegmentType::Ipc {
|
|
||||||
data: ipc,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
} else {
|
|
||||||
let ipc = ServerLobbyIpcSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: ServerLobbyIpcType::LobbyError,
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: ServerLobbyIpcData::LobbyError {
|
|
||||||
sequence: 0x03,
|
|
||||||
error: 0x0bdb, // TODO: I screwed this up when translating from the old struct to the new LobbyError
|
|
||||||
exd_error_id: 0,
|
|
||||||
value: 0,
|
|
||||||
unk1: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
|
||||||
source_actor: 0x0,
|
|
||||||
target_actor: 0x0,
|
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
|
||||||
};
|
|
||||||
connection.send_segment(response_packet).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LobbyCharacterActionKind::Create => {
|
|
||||||
tracing::info!("Player is creating a new character!");
|
|
||||||
|
|
||||||
let our_actor_id;
|
|
||||||
let our_content_id;
|
|
||||||
|
|
||||||
// tell the world server to create this character
|
|
||||||
{
|
|
||||||
let ipc_segment = CustomIpcSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: CustomIpcType::RequestCreateCharacter,
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: CustomIpcData::RequestCreateCharacter {
|
|
||||||
name: connection
|
|
||||||
.stored_character_creation_name
|
|
||||||
.clone(), // TODO: worth double-checking, but AFAIK we have to store it this way?
|
|
||||||
chara_make_json: character_action
|
|
||||||
.json
|
|
||||||
.clone(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let response_segment =
|
|
||||||
send_custom_world_packet(ipc_segment)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
match &response_segment.data {
|
|
||||||
CustomIpcData::CharacterCreated {
|
|
||||||
actor_id,
|
|
||||||
content_id,
|
|
||||||
} => {
|
|
||||||
our_actor_id = *actor_id;
|
|
||||||
our_content_id = *content_id;
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"Unexpected custom IPC packet type here!"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
"Got new player info from world server: {our_content_id} {our_actor_id}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// a slightly different character created packet now
|
|
||||||
{
|
|
||||||
let ipc = ServerLobbyIpcSegment {
|
|
||||||
unk1: 0,
|
|
||||||
unk2: 0,
|
|
||||||
op_code: ServerLobbyIpcType::CharacterCreated,
|
|
||||||
server_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
data: ServerLobbyIpcData::CharacterCreated {
|
|
||||||
sequence: character_action.sequence + 1,
|
|
||||||
unk: 0x00020101,
|
|
||||||
details: CharacterDetails {
|
|
||||||
actor_id: our_actor_id,
|
|
||||||
content_id: our_content_id,
|
|
||||||
character_name: character_action
|
|
||||||
.name
|
|
||||||
.clone(),
|
|
||||||
origin_server_name: world_name.clone(),
|
|
||||||
current_server_name: world_name.clone(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send_segment(PacketSegment {
|
|
||||||
source_actor: 0x0,
|
|
||||||
target_actor: 0x0,
|
|
||||||
segment_type: SegmentType::Ipc {
|
|
||||||
data: ipc,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LobbyCharacterActionKind::Rename => todo!(),
|
|
||||||
LobbyCharacterActionKind::Delete => todo!(),
|
|
||||||
LobbyCharacterActionKind::Move => todo!(),
|
|
||||||
LobbyCharacterActionKind::RemakeRetainer => todo!(),
|
|
||||||
LobbyCharacterActionKind::RemakeChara => todo!(),
|
|
||||||
LobbyCharacterActionKind::SettingsUploadBegin => todo!(),
|
|
||||||
LobbyCharacterActionKind::SettingsUpload => todo!(),
|
|
||||||
LobbyCharacterActionKind::WorldVisit => todo!(),
|
|
||||||
LobbyCharacterActionKind::DataCenterToken => todo!(),
|
|
||||||
LobbyCharacterActionKind::Request => todo!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClientLobbyIpcData::RequestEnterWorld {
|
ClientLobbyIpcData::RequestEnterWorld {
|
||||||
sequence,
|
sequence,
|
||||||
|
|
|
@ -841,6 +841,37 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CustomIpcData::DeleteCharacter { content_id } => {
|
||||||
|
database.delete_character(*content_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::CharacterDeleted,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: CustomIpcData::CharacterDeleted {
|
||||||
|
deleted: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"The server is recieving a response or unknown custom IPC!"
|
"The server is recieving a response or unknown custom IPC!"
|
||||||
),
|
),
|
||||||
|
|
|
@ -23,6 +23,8 @@ impl ReadWriteIpcSegment for CustomIpcSegment {
|
||||||
CustomIpcType::NameIsAvailableResponse => 1,
|
CustomIpcType::NameIsAvailableResponse => 1,
|
||||||
CustomIpcType::RequestCharacterList => 4,
|
CustomIpcType::RequestCharacterList => 4,
|
||||||
CustomIpcType::RequestCharacterListRepsonse => 1184 * 8,
|
CustomIpcType::RequestCharacterListRepsonse => 1184 * 8,
|
||||||
|
CustomIpcType::DeleteCharacter => 4,
|
||||||
|
CustomIpcType::CharacterDeleted => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +50,10 @@ pub enum CustomIpcType {
|
||||||
RequestCharacterList = 0x7,
|
RequestCharacterList = 0x7,
|
||||||
/// Response to RequestCharacterList
|
/// Response to RequestCharacterList
|
||||||
RequestCharacterListRepsonse = 0x8,
|
RequestCharacterListRepsonse = 0x8,
|
||||||
|
/// Request that a character be deleted from the world server
|
||||||
|
DeleteCharacter = 0x9,
|
||||||
|
/// Response to DeleteCharacter
|
||||||
|
CharacterDeleted = 0x10,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -92,6 +98,10 @@ pub enum CustomIpcData {
|
||||||
#[br(count = num_characters)]
|
#[br(count = num_characters)]
|
||||||
characters: Vec<CharacterDetails>, // TODO: maybe chunk this into 4 parts ala the lobby server?
|
characters: Vec<CharacterDetails>, // TODO: maybe chunk this into 4 parts ala the lobby server?
|
||||||
},
|
},
|
||||||
|
#[br(pre_assert(*magic == CustomIpcType::DeleteCharacter))]
|
||||||
|
DeleteCharacter { content_id: u64 },
|
||||||
|
#[br(pre_assert(*magic == CustomIpcType::CharacterDeleted))]
|
||||||
|
CharacterDeleted { deleted: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CustomIpcData {
|
impl Default for CustomIpcData {
|
||||||
|
|
|
@ -17,8 +17,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ipc::{
|
use super::ipc::{
|
||||||
CharacterDetails, LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server,
|
CharacterDetails, LobbyCharacterAction, LobbyCharacterActionKind, LobbyCharacterList,
|
||||||
ServerLobbyIpcData, ServerLobbyIpcSegment, ServerLobbyIpcType, ServiceAccount,
|
LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData, ServerLobbyIpcSegment,
|
||||||
|
ServerLobbyIpcType, ServiceAccount,
|
||||||
};
|
};
|
||||||
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
||||||
|
|
||||||
|
@ -31,6 +32,8 @@ pub struct LobbyConnection {
|
||||||
pub state: PacketState,
|
pub state: PacketState,
|
||||||
|
|
||||||
pub stored_character_creation_name: String,
|
pub stored_character_creation_name: String,
|
||||||
|
|
||||||
|
pub world_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LobbyConnection {
|
impl LobbyConnection {
|
||||||
|
@ -334,6 +337,217 @@ impl LobbyConnection {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_character_action(&mut self, character_action: &LobbyCharacterAction) {
|
||||||
|
match &character_action.action {
|
||||||
|
LobbyCharacterActionKind::ReserveName => {
|
||||||
|
tracing::info!(
|
||||||
|
"Player is requesting {} as a new character name!",
|
||||||
|
character_action.name
|
||||||
|
);
|
||||||
|
|
||||||
|
// check with the world server if the name is available
|
||||||
|
let name_request = CustomIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: CustomIpcType::CheckNameIsAvailable,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: CustomIpcData::CheckNameIsAvailable {
|
||||||
|
name: character_action.name.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let name_response = send_custom_world_packet(name_request)
|
||||||
|
.await
|
||||||
|
.expect("Failed to get name request packet!");
|
||||||
|
let CustomIpcData::NameIsAvailableResponse { free } = &name_response.data else {
|
||||||
|
panic!("Unexpedted custom IPC type!")
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("Is name free? {free}");
|
||||||
|
|
||||||
|
// TODO: use read_bool_as
|
||||||
|
let free: bool = *free == 1u8;
|
||||||
|
|
||||||
|
if free {
|
||||||
|
self.stored_character_creation_name = character_action.name.clone();
|
||||||
|
|
||||||
|
let ipc = ServerLobbyIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerLobbyIpcType::CharacterCreated,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerLobbyIpcData::CharacterCreated {
|
||||||
|
sequence: character_action.sequence + 1,
|
||||||
|
unk: 0x00010101,
|
||||||
|
details: CharacterDetails {
|
||||||
|
character_name: character_action.name.clone(),
|
||||||
|
origin_server_name: self.world_name.clone(),
|
||||||
|
current_server_name: self.world_name.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: 0x0,
|
||||||
|
target_actor: 0x0,
|
||||||
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
let ipc = ServerLobbyIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerLobbyIpcType::LobbyError,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerLobbyIpcData::LobbyError {
|
||||||
|
sequence: 0x03,
|
||||||
|
error: 0x0bdb, // TODO: I screwed this up when translating from the old struct to the new LobbyError
|
||||||
|
exd_error_id: 0,
|
||||||
|
value: 0,
|
||||||
|
unk1: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let response_packet = PacketSegment {
|
||||||
|
source_actor: 0x0,
|
||||||
|
target_actor: 0x0,
|
||||||
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
|
};
|
||||||
|
self.send_segment(response_packet).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LobbyCharacterActionKind::Create => {
|
||||||
|
tracing::info!("Player is creating a new character!");
|
||||||
|
|
||||||
|
let our_actor_id;
|
||||||
|
let our_content_id;
|
||||||
|
|
||||||
|
// tell the world server to create this character
|
||||||
|
{
|
||||||
|
let ipc_segment = CustomIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: CustomIpcType::RequestCreateCharacter,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: CustomIpcData::RequestCreateCharacter {
|
||||||
|
name: self.stored_character_creation_name.clone(), // TODO: worth double-checking, but AFAIK we have to store it this way?
|
||||||
|
chara_make_json: character_action.json.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let response_segment = send_custom_world_packet(ipc_segment).await.unwrap();
|
||||||
|
match &response_segment.data {
|
||||||
|
CustomIpcData::CharacterCreated {
|
||||||
|
actor_id,
|
||||||
|
content_id,
|
||||||
|
} => {
|
||||||
|
our_actor_id = *actor_id;
|
||||||
|
our_content_id = *content_id;
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected custom IPC packet type here!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Got new player info from world server: {our_content_id} {our_actor_id}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// a slightly different character created packet now
|
||||||
|
{
|
||||||
|
let ipc = ServerLobbyIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerLobbyIpcType::CharacterCreated,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerLobbyIpcData::CharacterCreated {
|
||||||
|
sequence: character_action.sequence + 1,
|
||||||
|
unk: 0x00020101,
|
||||||
|
details: CharacterDetails {
|
||||||
|
actor_id: our_actor_id,
|
||||||
|
content_id: our_content_id,
|
||||||
|
character_name: character_action.name.clone(),
|
||||||
|
origin_server_name: self.world_name.clone(),
|
||||||
|
current_server_name: self.world_name.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: 0x0,
|
||||||
|
target_actor: 0x0,
|
||||||
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LobbyCharacterActionKind::Rename => todo!(),
|
||||||
|
LobbyCharacterActionKind::Delete => {
|
||||||
|
// tell the world server to yeet this guy
|
||||||
|
{
|
||||||
|
let ipc_segment = CustomIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: CustomIpcType::DeleteCharacter,
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: CustomIpcData::DeleteCharacter {
|
||||||
|
content_id: character_action.content_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = send_custom_world_packet(ipc_segment).await.unwrap();
|
||||||
|
|
||||||
|
// we intentionally don't care about the response right now, it's not expected to fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a confirmation that the deletion was successful
|
||||||
|
{
|
||||||
|
let ipc = ServerLobbyIpcSegment {
|
||||||
|
unk1: 0,
|
||||||
|
unk2: 0,
|
||||||
|
op_code: ServerLobbyIpcType::CharacterCreated, // FIXME: a TERRIBLE name for this packet
|
||||||
|
server_id: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
data: ServerLobbyIpcData::CharacterCreated {
|
||||||
|
sequence: character_action.sequence + 1,
|
||||||
|
unk: 0x00040101, // TODO: probably LobbyCharacterAction actually, see create character packet too
|
||||||
|
details: CharacterDetails {
|
||||||
|
actor_id: 0, // TODO: fill maybe?
|
||||||
|
content_id: character_action.content_id,
|
||||||
|
character_name: character_action.name.clone(),
|
||||||
|
origin_server_name: self.world_name.clone(),
|
||||||
|
current_server_name: self.world_name.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: 0x0,
|
||||||
|
target_actor: 0x0,
|
||||||
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LobbyCharacterActionKind::Move => todo!(),
|
||||||
|
LobbyCharacterActionKind::RemakeRetainer => todo!(),
|
||||||
|
LobbyCharacterActionKind::RemakeChara => todo!(),
|
||||||
|
LobbyCharacterActionKind::SettingsUploadBegin => todo!(),
|
||||||
|
LobbyCharacterActionKind::SettingsUpload => todo!(),
|
||||||
|
LobbyCharacterActionKind::WorldVisit => todo!(),
|
||||||
|
LobbyCharacterActionKind::DataCenterToken => todo!(),
|
||||||
|
LobbyCharacterActionKind::Request => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a custom IPC packet to the world server, meant for private server-to-server communication.
|
/// Sends a custom IPC packet to the world server, meant for private server-to-server communication.
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub enum LobbyCharacterActionKind {
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct LobbyCharacterAction {
|
pub struct LobbyCharacterAction {
|
||||||
pub sequence: u64,
|
pub sequence: u64,
|
||||||
pub character_id: u64,
|
pub content_id: u64,
|
||||||
#[br(pad_before = 8)]
|
#[br(pad_before = 8)]
|
||||||
pub character_index: u8,
|
pub character_index: u8,
|
||||||
pub action: LobbyCharacterActionKind,
|
pub action: LobbyCharacterActionKind,
|
||||||
|
|
|
@ -102,54 +102,55 @@ impl WorldDatabase {
|
||||||
.prepare("SELECT name, chara_make FROM character_data WHERE content_id = ?1")
|
.prepare("SELECT name, chara_make FROM character_data WHERE content_id = ?1")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (name, chara_make): (String, String) = stmt
|
let result: Result<(String, String), rusqlite::Error> =
|
||||||
.query_row((content_id,), |row| Ok((row.get(0)?, row.get(1)?)))
|
stmt.query_row((content_id,), |row| Ok((row.get(0)?, row.get(1)?)));
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let chara_make = CharaMake::from_json(&chara_make);
|
if let Ok((name, chara_make)) = result {
|
||||||
|
let chara_make = CharaMake::from_json(&chara_make);
|
||||||
|
|
||||||
let select_data = ClientSelectData {
|
let select_data = ClientSelectData {
|
||||||
game_name_unk: "Final Fantasy".to_string(),
|
game_name_unk: "Final Fantasy".to_string(),
|
||||||
current_class: 2,
|
current_class: 2,
|
||||||
class_levels: [5; 30],
|
class_levels: [5; 30],
|
||||||
race: chara_make.customize.race as i32,
|
race: chara_make.customize.race as i32,
|
||||||
subrace: chara_make.customize.subrace as i32,
|
subrace: chara_make.customize.subrace as i32,
|
||||||
gender: chara_make.customize.gender as i32,
|
gender: chara_make.customize.gender as i32,
|
||||||
birth_month: chara_make.birth_month,
|
birth_month: chara_make.birth_month,
|
||||||
birth_day: chara_make.birth_day,
|
birth_day: chara_make.birth_day,
|
||||||
guardian: chara_make.guardian,
|
guardian: chara_make.guardian,
|
||||||
unk8: 0,
|
unk8: 0,
|
||||||
unk9: 0,
|
unk9: 0,
|
||||||
zone_id: ZONE_ID as i32,
|
zone_id: ZONE_ID as i32,
|
||||||
unk11: 0,
|
unk11: 0,
|
||||||
customize: chara_make.customize,
|
customize: chara_make.customize,
|
||||||
unk12: 0,
|
unk12: 0,
|
||||||
unk13: 0,
|
unk13: 0,
|
||||||
unk14: [0; 10],
|
unk14: [0; 10],
|
||||||
unk15: 0,
|
unk15: 0,
|
||||||
unk16: 0,
|
unk16: 0,
|
||||||
legacy_character: 0,
|
legacy_character: 0,
|
||||||
unk18: 0,
|
unk18: 0,
|
||||||
unk19: 0,
|
unk19: 0,
|
||||||
unk20: 0,
|
unk20: 0,
|
||||||
unk21: String::new(),
|
unk21: String::new(),
|
||||||
unk22: 0,
|
unk22: 0,
|
||||||
unk23: 0,
|
unk23: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
characters.push(CharacterDetails {
|
characters.push(CharacterDetails {
|
||||||
actor_id: *actor_id,
|
actor_id: *actor_id,
|
||||||
content_id: *content_id as u64,
|
content_id: *content_id as u64,
|
||||||
index: index as u32,
|
index: index as u32,
|
||||||
unk1: [0; 16],
|
unk1: [0; 16],
|
||||||
origin_server_id: world_id,
|
origin_server_id: world_id,
|
||||||
current_server_id: world_id,
|
current_server_id: world_id,
|
||||||
character_name: name.clone(),
|
character_name: name.clone(),
|
||||||
origin_server_name: world_name.to_string(),
|
origin_server_name: world_name.to_string(),
|
||||||
current_server_name: world_name.to_string(),
|
current_server_name: world_name.to_string(),
|
||||||
character_detail_json: select_data.to_json(),
|
character_detail_json: select_data.to_json(),
|
||||||
unk2: [0; 20],
|
unk2: [0; 20],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
characters
|
characters
|
||||||
|
@ -215,4 +216,14 @@ impl WorldDatabase {
|
||||||
chara_make: CharaMake::from_json(&chara_make_json),
|
chara_make: CharaMake::from_json(&chara_make_json),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes a character and all associated data
|
||||||
|
pub fn delete_character(&self, content_id: u64) {
|
||||||
|
let connection = self.connection.lock().unwrap();
|
||||||
|
|
||||||
|
let mut stmt = connection
|
||||||
|
.prepare("DELETE FROM character_data WHERE content_id = ?1; DELETE FROM characters WHERE content_id = ?1;")
|
||||||
|
.unwrap();
|
||||||
|
stmt.execute((content_id,)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue