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::config::get_config;
|
||||
use kawari::lobby::LobbyConnection;
|
||||
use kawari::lobby::ipc::{
|
||||
CharacterDetails, ClientLobbyIpcData, LobbyCharacterActionKind, ServerLobbyIpcData,
|
||||
ServerLobbyIpcSegment, ServerLobbyIpcType,
|
||||
};
|
||||
use kawari::lobby::ipc::{ClientLobbyIpcData, ServerLobbyIpcSegment};
|
||||
use kawari::lobby::send_custom_world_packet;
|
||||
use kawari::oodle::OodleNetwork;
|
||||
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::net::TcpListener;
|
||||
|
||||
|
@ -43,10 +40,9 @@ async fn main() {
|
|||
state,
|
||||
session_id: None,
|
||||
stored_character_creation_name: String::new(),
|
||||
world_name: world_name.clone(),
|
||||
};
|
||||
|
||||
let world_name = world_name.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 2056];
|
||||
loop {
|
||||
|
@ -63,7 +59,7 @@ async fn main() {
|
|||
for segment in &segments {
|
||||
match &segment.segment_type {
|
||||
SegmentType::InitializeEncryption { phrase, key } => {
|
||||
connection.initialize_encryption(phrase, key).await;
|
||||
connection.initialize_encryption(phrase, key).await
|
||||
}
|
||||
SegmentType::Ipc { data } => match &data.data {
|
||||
ClientLobbyIpcData::ClientVersionInfo {
|
||||
|
@ -83,194 +79,10 @@ async fn main() {
|
|||
//connection.send_error(*sequence, 1012, 13101).await;
|
||||
}
|
||||
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) => {
|
||||
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 {
|
||||
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!(),
|
||||
}
|
||||
connection.handle_character_action(character_action).await
|
||||
}
|
||||
ClientLobbyIpcData::RequestEnterWorld {
|
||||
sequence,
|
||||
|
|
|
@ -841,6 +841,37 @@ async fn main() {
|
|||
.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!(
|
||||
"The server is recieving a response or unknown custom IPC!"
|
||||
),
|
||||
|
|
|
@ -23,6 +23,8 @@ impl ReadWriteIpcSegment for CustomIpcSegment {
|
|||
CustomIpcType::NameIsAvailableResponse => 1,
|
||||
CustomIpcType::RequestCharacterList => 4,
|
||||
CustomIpcType::RequestCharacterListRepsonse => 1184 * 8,
|
||||
CustomIpcType::DeleteCharacter => 4,
|
||||
CustomIpcType::CharacterDeleted => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +50,10 @@ pub enum CustomIpcType {
|
|||
RequestCharacterList = 0x7,
|
||||
/// Response to RequestCharacterList
|
||||
RequestCharacterListRepsonse = 0x8,
|
||||
/// Request that a character be deleted from the world server
|
||||
DeleteCharacter = 0x9,
|
||||
/// Response to DeleteCharacter
|
||||
CharacterDeleted = 0x10,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -92,6 +98,10 @@ pub enum CustomIpcData {
|
|||
#[br(count = num_characters)]
|
||||
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 {
|
||||
|
|
|
@ -17,8 +17,9 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::ipc::{
|
||||
CharacterDetails, LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server,
|
||||
ServerLobbyIpcData, ServerLobbyIpcSegment, ServerLobbyIpcType, ServiceAccount,
|
||||
CharacterDetails, LobbyCharacterAction, LobbyCharacterActionKind, LobbyCharacterList,
|
||||
LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData, ServerLobbyIpcSegment,
|
||||
ServerLobbyIpcType, ServiceAccount,
|
||||
};
|
||||
use crate::lobby::ipc::ClientLobbyIpcSegment;
|
||||
|
||||
|
@ -31,6 +32,8 @@ pub struct LobbyConnection {
|
|||
pub state: PacketState,
|
||||
|
||||
pub stored_character_creation_name: String,
|
||||
|
||||
pub world_name: String,
|
||||
}
|
||||
|
||||
impl LobbyConnection {
|
||||
|
@ -334,6 +337,217 @@ impl LobbyConnection {
|
|||
})
|
||||
.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.
|
||||
|
|
|
@ -37,7 +37,7 @@ pub enum LobbyCharacterActionKind {
|
|||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct LobbyCharacterAction {
|
||||
pub sequence: u64,
|
||||
pub character_id: u64,
|
||||
pub content_id: u64,
|
||||
#[br(pad_before = 8)]
|
||||
pub character_index: u8,
|
||||
pub action: LobbyCharacterActionKind,
|
||||
|
|
|
@ -102,10 +102,10 @@ impl WorldDatabase {
|
|||
.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 result: Result<(String, String), rusqlite::Error> =
|
||||
stmt.query_row((content_id,), |row| Ok((row.get(0)?, row.get(1)?)));
|
||||
|
||||
if let Ok((name, chara_make)) = result {
|
||||
let chara_make = CharaMake::from_json(&chara_make);
|
||||
|
||||
let select_data = ClientSelectData {
|
||||
|
@ -151,6 +151,7 @@ impl WorldDatabase {
|
|||
unk2: [0; 20],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
characters
|
||||
}
|
||||
|
@ -215,4 +216,14 @@ impl WorldDatabase {
|
|||
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