mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-23 07:37:46 +00:00
Create dedicated Connection implementations to handle future server work
The current situation of throw-every-piece-of-logic-into-one-file for each server isn't working out. So now there is a new ZoneConnection/LobbyConnection struct that will be delegating tasks to their own Handlers (for example, there could be a ChatHandler.) I'm not sure how well this architecture will scale, but it's better than what we have right now.
This commit is contained in:
parent
059becf55f
commit
f372f3173d
15 changed files with 721 additions and 795 deletions
|
@ -1,21 +1,11 @@
|
|||
use std::cmp::min;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use kawari::blowfish::Blowfish;
|
||||
use kawari::CONTENT_ID;
|
||||
use kawari::chara_make::CharaMake;
|
||||
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
|
||||
use kawari::encryption::generate_encryption_key;
|
||||
use kawari::ipc::{
|
||||
CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, LobbyCharacterAction, Server,
|
||||
ServiceAccount,
|
||||
};
|
||||
use kawari::ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, LobbyCharacterAction};
|
||||
use kawari::lobby::connection::LobbyConnection;
|
||||
use kawari::oodle::FFXIVOodle;
|
||||
use kawari::packet::{
|
||||
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
||||
};
|
||||
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, WORLD_NAME, ZONE_ID};
|
||||
use tokio::io::{AsyncReadExt, WriteHalf};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
@ -27,29 +17,33 @@ async fn main() {
|
|||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
let (mut read, mut write) = tokio::io::split(socket);
|
||||
|
||||
let mut state = State {
|
||||
let state = State {
|
||||
client_key: None,
|
||||
session_id: None,
|
||||
clientbound_oodle: FFXIVOodle::new(),
|
||||
serverbound_oodle: FFXIVOodle::new(),
|
||||
player_id: None,
|
||||
};
|
||||
|
||||
let mut connection = LobbyConnection { socket, state };
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 2056];
|
||||
loop {
|
||||
let n = read.read(&mut buf).await.expect("Failed to read data!");
|
||||
let n = connection
|
||||
.socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("Failed to read data!");
|
||||
|
||||
if n != 0 {
|
||||
tracing::info!("read {} bytes", n);
|
||||
|
||||
let (segments, _) = parse_packet(&buf[..n], &mut state).await;
|
||||
let (segments, _) = connection.parse_packet(&buf[..n]).await;
|
||||
for segment in &segments {
|
||||
match &segment.segment_type {
|
||||
SegmentType::InitializeEncryption { phrase, key } => {
|
||||
initialize_encryption(&mut write, &mut state, phrase, key).await;
|
||||
connection.initialize_encryption(phrase, key).await;
|
||||
}
|
||||
SegmentType::Ipc { data } => match &data.data {
|
||||
IPCStructData::ClientVersionInfo {
|
||||
|
@ -60,23 +54,17 @@ async fn main() {
|
|||
"Client {session_id} ({version_info}) logging in!"
|
||||
);
|
||||
|
||||
state.session_id = Some(session_id.clone());
|
||||
connection.state.session_id = Some(session_id.clone());
|
||||
|
||||
send_account_list(&mut write, &mut state).await;
|
||||
connection.send_account_list().await;
|
||||
}
|
||||
IPCStructData::RequestCharacterList { sequence } => {
|
||||
tracing::info!("Client is requesting character list...");
|
||||
|
||||
send_lobby_info(&mut write, &mut state, *sequence).await;
|
||||
connection.send_lobby_info(*sequence).await;
|
||||
}
|
||||
IPCStructData::LobbyCharacterAction {
|
||||
character_id,
|
||||
character_index,
|
||||
action,
|
||||
world_id,
|
||||
name,
|
||||
json,
|
||||
..
|
||||
action, name, json, ..
|
||||
} => {
|
||||
match action {
|
||||
LobbyCharacterAction::ReserveName => {
|
||||
|
@ -136,18 +124,15 @@ 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;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0x0,
|
||||
target_actor: 0x0,
|
||||
segment_type: SegmentType::Ipc {
|
||||
data: ipc,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
LobbyCharacterAction::Create => {
|
||||
|
@ -180,18 +165,15 @@ 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;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0x0,
|
||||
target_actor: 0x0,
|
||||
segment_type: SegmentType::Ipc {
|
||||
data: ipc,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
LobbyCharacterAction::Rename => todo!(),
|
||||
|
@ -212,15 +194,20 @@ async fn main() {
|
|||
} => {
|
||||
tracing::info!("Client is joining the world...");
|
||||
|
||||
send_enter_world(&mut write, &mut state, *sequence, *lookup_id)
|
||||
.await;
|
||||
connection.send_enter_world(*sequence, *lookup_id).await;
|
||||
}
|
||||
_ => {
|
||||
panic!("The server is recieving a IPC response packet!")
|
||||
}
|
||||
},
|
||||
SegmentType::KeepAlive { id, timestamp } => {
|
||||
send_keep_alive(&mut write, &mut state, *id, *timestamp).await
|
||||
send_keep_alive(
|
||||
&mut connection.socket,
|
||||
&mut connection.state,
|
||||
*id,
|
||||
*timestamp,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => {
|
||||
panic!("The server is recieving a response packet!")
|
||||
|
@ -232,322 +219,3 @@ async fn main() {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn initialize_encryption(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
state: &mut State,
|
||||
phrase: &str,
|
||||
key: &[u8; 4],
|
||||
) {
|
||||
// Generate an encryption key for this client
|
||||
state.client_key = Some(generate_encryption_key(key, phrase));
|
||||
|
||||
let mut data = 0xE0003C2Au32.to_le_bytes().to_vec();
|
||||
data.resize(0x280, 0);
|
||||
|
||||
let blowfish = Blowfish::new(&state.client_key.unwrap());
|
||||
blowfish.encrypt(&mut data);
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::InitializationEncryptionResponse { data },
|
||||
};
|
||||
send_packet(
|
||||
socket,
|
||||
&[response_packet],
|
||||
state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn send_account_list(socket: &mut WriteHalf<TcpStream>, state: &mut State) {
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
// send the client the service account list
|
||||
let service_accounts = [ServiceAccount {
|
||||
id: 0x002E4A2B,
|
||||
unk1: 0,
|
||||
index: 0,
|
||||
name: "FINAL FANTASY XIV".to_string(),
|
||||
}]
|
||||
.to_vec();
|
||||
|
||||
let service_account_list = IPCStructData::LobbyServiceAccountList {
|
||||
sequence: 0,
|
||||
num_service_accounts: service_accounts.len() as u8,
|
||||
unk1: 3,
|
||||
unk2: 0x99,
|
||||
service_accounts: service_accounts.to_vec(),
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyServiceAccountList,
|
||||
server_id: 0,
|
||||
timestamp,
|
||||
data: service_account_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
socket,
|
||||
&[response_packet],
|
||||
state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn send_lobby_info(socket: &mut WriteHalf<TcpStream>, state: &mut State, sequence: u64) {
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let mut packets = Vec::new();
|
||||
// send them the server list
|
||||
{
|
||||
let mut servers = [Server {
|
||||
id: WORLD_ID,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
icon: 0,
|
||||
name: WORLD_NAME.to_string(),
|
||||
}]
|
||||
.to_vec();
|
||||
// add any empty boys
|
||||
servers.resize(6, Server::default());
|
||||
|
||||
let lobby_server_list = IPCStructData::LobbyServerList {
|
||||
sequence: 0,
|
||||
unk1: 1,
|
||||
offset: 0,
|
||||
num_servers: 1,
|
||||
servers,
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyServerList,
|
||||
server_id: 0,
|
||||
timestamp,
|
||||
data: lobby_server_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
packets.push(response_packet);
|
||||
}
|
||||
|
||||
// send them the retainer list
|
||||
{
|
||||
let lobby_retainer_list = IPCStructData::LobbyRetainerList { unk1: 1 };
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyRetainerList,
|
||||
server_id: 0,
|
||||
timestamp,
|
||||
data: lobby_retainer_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
packets.push(response_packet);
|
||||
}
|
||||
|
||||
send_packet(socket, &packets, state, CompressionType::Uncompressed).await;
|
||||
|
||||
// now send them the character list
|
||||
{
|
||||
let select_data = ClientSelectData {
|
||||
game_name_unk: "Final Fantasy".to_string(),
|
||||
current_class: 2,
|
||||
class_levels: [5; 30],
|
||||
race: 0,
|
||||
subrace: 0,
|
||||
gender: 0,
|
||||
birth_month: 5,
|
||||
birth_day: 5,
|
||||
guardian: 2,
|
||||
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 {
|
||||
id: 0,
|
||||
content_id: CONTENT_ID,
|
||||
index: 0,
|
||||
unk1: [0; 16],
|
||||
origin_server_id: WORLD_ID,
|
||||
current_server_id: WORLD_ID,
|
||||
character_name: CHAR_NAME.to_string(),
|
||||
origin_server_name: WORLD_NAME.to_string(),
|
||||
current_server_name: WORLD_NAME.to_string(),
|
||||
character_detail_json: select_data.to_json(),
|
||||
unk2: [0; 20],
|
||||
}];
|
||||
|
||||
for i in 0..4 {
|
||||
let mut characters_in_packet = Vec::new();
|
||||
for _ in 0..min(characters.len(), 2) {
|
||||
characters_in_packet.push(characters.swap_remove(0));
|
||||
}
|
||||
// add any empty boys
|
||||
characters_in_packet.resize(2, CharacterDetails::default());
|
||||
|
||||
let lobby_character_list = if i == 3 {
|
||||
// On the last packet, add the account-wide information
|
||||
IPCStructData::LobbyCharacterList {
|
||||
sequence,
|
||||
counter: (i * 4) + 1, // TODO: why the + 1 here?
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 128,
|
||||
unk5: [0; 7],
|
||||
unk6: 0,
|
||||
veteran_rank: 0,
|
||||
unk7: 0,
|
||||
days_subscribed: 5,
|
||||
remaining_days: 5,
|
||||
days_to_next_rank: 0,
|
||||
unk8: 8,
|
||||
max_characters_on_world: 2,
|
||||
entitled_expansion: 4,
|
||||
characters: characters_in_packet,
|
||||
}
|
||||
} else {
|
||||
IPCStructData::LobbyCharacterList {
|
||||
sequence,
|
||||
counter: i * 4,
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 0,
|
||||
unk5: [0; 7],
|
||||
unk6: 0,
|
||||
veteran_rank: 0,
|
||||
unk7: 0,
|
||||
days_subscribed: 0,
|
||||
remaining_days: 0,
|
||||
days_to_next_rank: 0,
|
||||
max_characters_on_world: 0,
|
||||
unk8: 0,
|
||||
entitled_expansion: 0,
|
||||
characters: characters_in_packet,
|
||||
}
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyCharacterList,
|
||||
server_id: 0,
|
||||
timestamp,
|
||||
data: lobby_character_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
socket,
|
||||
&[response_packet],
|
||||
state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_enter_world(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
state: &mut State,
|
||||
sequence: u64,
|
||||
lookup_id: u64,
|
||||
) {
|
||||
let Some(session_id) = &state.session_id else {
|
||||
panic!("Missing session id!");
|
||||
};
|
||||
|
||||
let timestamp: u32 = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let enter_world = IPCStructData::LobbyEnterWorld {
|
||||
sequence,
|
||||
character_id: 0,
|
||||
content_id: lookup_id, // TODO: shouldn't these be named the same then?
|
||||
session_id: session_id.clone(),
|
||||
port: 7100,
|
||||
host: "127.0.0.1".to_string(),
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyEnterWorld,
|
||||
server_id: 0,
|
||||
timestamp,
|
||||
data: enter_world,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
socket,
|
||||
&[response_packet],
|
||||
state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use binrw::BinRead;
|
||||
use kawari::client_select_data::ClientCustomizeData;
|
||||
use kawari::ipc::{ActorSetPos, GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
||||
use kawari::ipc::{GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
||||
use kawari::oodle::FFXIVOodle;
|
||||
use kawari::packet::{
|
||||
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
||||
};
|
||||
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
|
||||
use kawari::world::{
|
||||
ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
||||
UpdateClassInfo, Zone,
|
||||
ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerSetup, PlayerSpawn,
|
||||
PlayerStats, Position, UpdateClassInfo, Zone, ZoneConnection,
|
||||
};
|
||||
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID};
|
||||
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID, timestamp_secs};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
|
@ -28,41 +24,40 @@ async fn main() {
|
|||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
let (mut read, mut write) = tokio::io::split(socket);
|
||||
|
||||
let mut state = State {
|
||||
let state = State {
|
||||
client_key: None,
|
||||
session_id: None,
|
||||
clientbound_oodle: FFXIVOodle::new(),
|
||||
serverbound_oodle: FFXIVOodle::new(),
|
||||
player_id: None,
|
||||
};
|
||||
|
||||
let zone = Zone::load(010);
|
||||
|
||||
let mut exit_position = None;
|
||||
|
||||
let mut connection = ZoneConnection {
|
||||
socket,
|
||||
state,
|
||||
player_id: 0,
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 2056];
|
||||
loop {
|
||||
let n = read.read(&mut buf).await.expect("Failed to read data!");
|
||||
let n = connection
|
||||
.socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("Failed to read data!");
|
||||
|
||||
if n != 0 {
|
||||
println!("recieved {n} bytes...");
|
||||
let (segments, connection_type) = parse_packet(&buf[..n], &mut state).await;
|
||||
let (segments, connection_type) = connection.parse_packet(&buf[..n]).await;
|
||||
for segment in &segments {
|
||||
let timestamp_secs = || {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
match &segment.segment_type {
|
||||
SegmentType::InitializeSession { player_id } => {
|
||||
state.player_id = Some(*player_id);
|
||||
connection.player_id = *player_id;
|
||||
|
||||
// We have send THEM a keep alive
|
||||
{
|
||||
|
@ -73,21 +68,16 @@ async fn main() {
|
|||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::KeepAlive {
|
||||
id: 0xE0037603u32,
|
||||
timestamp,
|
||||
},
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::KeepAlive {
|
||||
id: 0xE0037603u32,
|
||||
timestamp,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
match connection_type {
|
||||
|
@ -96,20 +86,15 @@ async fn main() {
|
|||
"Client {player_id} is initializing zone session..."
|
||||
);
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
kawari::packet::ConnectionType::Chat => {
|
||||
tracing::info!(
|
||||
|
@ -117,20 +102,15 @@ async fn main() {
|
|||
);
|
||||
|
||||
{
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::ZoneInitialize {
|
||||
player_id: *player_id,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -143,18 +123,13 @@ async fn main() {
|
|||
data: IPCStructData::InitializeChat { unk: [0; 8] },
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: *player_id,
|
||||
target_actor: *player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: *player_id,
|
||||
target_actor: *player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
|
@ -179,23 +154,18 @@ async fn main() {
|
|||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::InitResponse {
|
||||
unk1: 0,
|
||||
character_id: state.player_id.unwrap(),
|
||||
character_id: connection.player_id,
|
||||
unk2: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Control Data
|
||||
|
@ -220,18 +190,13 @@ async fn main() {
|
|||
),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Stats
|
||||
|
@ -250,18 +215,13 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Player Setup
|
||||
|
@ -281,18 +241,13 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Player Class Info
|
||||
|
@ -314,18 +269,13 @@ async fn main() {
|
|||
),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// unk10
|
||||
|
@ -341,18 +291,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// unk9
|
||||
|
@ -366,18 +311,13 @@ async fn main() {
|
|||
data: IPCStructData::Unk9 { unk: [0; 24] },
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// link shell information
|
||||
|
@ -393,18 +333,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// unk8
|
||||
|
@ -418,18 +353,13 @@ async fn main() {
|
|||
data: IPCStructData::Unk8 { unk: [0; 808] },
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Init Zone
|
||||
|
@ -448,18 +378,13 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
IPCStructData::FinishLoading { .. } => {
|
||||
|
@ -481,18 +406,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// send player spawn
|
||||
|
@ -537,18 +457,13 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// fade in?
|
||||
|
@ -564,18 +479,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// wipe any exit position so it isn't accidentally reused
|
||||
|
@ -621,18 +531,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
IPCStructData::UpdatePositionHandler { .. } => {
|
||||
|
@ -652,72 +557,24 @@ async fn main() {
|
|||
data: IPCStructData::LogOutComplete { unk: [0; 8] },
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
IPCStructData::Disconnected { .. } => {
|
||||
tracing::info!("Client disconnected!");
|
||||
}
|
||||
IPCStructData::ChatMessage { message, .. } => {
|
||||
tracing::info!("Client sent chat message: {message}!");
|
||||
|
||||
let parts: Vec<&str> = message.split(' ').collect();
|
||||
match parts[0] {
|
||||
"!setpos" => {
|
||||
let pos_x = parts[1].parse::<f32>().unwrap();
|
||||
let pos_y = parts[2].parse::<f32>().unwrap();
|
||||
let pos_z = parts[3].parse::<f32>().unwrap();
|
||||
|
||||
// set pos
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 14,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::ActorSetPos,
|
||||
server_id: WORLD_ID,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::ActorSetPos(
|
||||
ActorSetPos {
|
||||
unk: 0x020fa3b8,
|
||||
position: Position {
|
||||
x: pos_x,
|
||||
y: pos_y,
|
||||
z: pos_z,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc {
|
||||
data: ipc,
|
||||
},
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => tracing::info!("Unrecognized debug command!"),
|
||||
}
|
||||
IPCStructData::ChatMessage(chat_message) => {
|
||||
ChatHandler::handle_chat_message(
|
||||
&mut connection,
|
||||
chat_message,
|
||||
)
|
||||
.await
|
||||
}
|
||||
IPCStructData::GameMasterCommand { command, arg, .. } => {
|
||||
tracing::info!("Got a game master command!");
|
||||
|
@ -739,20 +596,15 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc {
|
||||
data: ipc,
|
||||
},
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc {
|
||||
data: ipc,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -800,18 +652,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// fade out? x2
|
||||
|
@ -827,18 +674,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
|
@ -869,18 +711,13 @@ async fn main() {
|
|||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// idk
|
||||
|
@ -896,18 +733,13 @@ async fn main() {
|
|||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: connection.player_id,
|
||||
target_actor: connection.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
IPCStructData::Unk13 { .. } => {
|
||||
|
@ -922,7 +754,13 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
SegmentType::KeepAlive { id, timestamp } => {
|
||||
send_keep_alive(&mut write, &mut state, *id, *timestamp).await
|
||||
send_keep_alive(
|
||||
&mut connection.socket,
|
||||
&mut connection.state,
|
||||
*id,
|
||||
*timestamp,
|
||||
)
|
||||
.await
|
||||
}
|
||||
SegmentType::KeepAliveResponse { .. } => {
|
||||
tracing::info!("Got keep alive response from client... cool...");
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
mod constants;
|
||||
use constants::{BLOWFISH_P, BLOWFISH_S};
|
||||
|
||||
|
@ -93,29 +91,28 @@ impl Blowfish {
|
|||
/// Calculates the F-function for `x`.
|
||||
fn f(&self, x: u32) -> u32 {
|
||||
let [a, b, c, d] = x.to_le_bytes();
|
||||
return ((self.s[0][d as usize].wrapping_add(self.s[1][c as usize]))
|
||||
^ (self.s[2][b as usize]))
|
||||
.wrapping_add(self.s[3][a as usize]);
|
||||
((self.s[0][d as usize].wrapping_add(self.s[1][c as usize])) ^ (self.s[2][b as usize]))
|
||||
.wrapping_add(self.s[3][a as usize])
|
||||
}
|
||||
|
||||
fn encrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
||||
for i in 0..ROUNDS {
|
||||
*xl ^= self.p[i as usize];
|
||||
*xr = self.f(*xl) ^ *xr;
|
||||
*xl ^= self.p[i];
|
||||
*xr ^= self.f(*xl);
|
||||
|
||||
(*xl, *xr) = (*xr, *xl);
|
||||
}
|
||||
|
||||
(*xl, *xr) = (*xr, *xl);
|
||||
|
||||
*xr ^= self.p[ROUNDS as usize];
|
||||
*xl ^= self.p[ROUNDS as usize + 1];
|
||||
*xr ^= self.p[ROUNDS];
|
||||
*xl ^= self.p[ROUNDS + 1];
|
||||
}
|
||||
|
||||
fn decrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
||||
for i in (2..ROUNDS + 2).rev() {
|
||||
*xl ^= self.p[i as usize];
|
||||
*xr = self.f(*xl) ^ *xr;
|
||||
*xl ^= self.p[i];
|
||||
*xr ^= self.f(*xl);
|
||||
|
||||
(*xl, *xr) = (*xr, *xl);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use binrw::binrw;
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::client_select_data::ClientCustomizeData;
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::ffi::CString;
|
||||
use std::{
|
||||
ffi::CString,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
pub(crate) fn read_bool_from<T: std::convert::From<u8> + std::cmp::PartialEq>(x: T) -> bool {
|
||||
x == T::from(1u8)
|
||||
|
@ -17,3 +20,12 @@ pub(crate) fn write_string(str: &String) -> Vec<u8> {
|
|||
let c_string = CString::new(&**str).unwrap();
|
||||
c_string.as_bytes_with_nul().to_vec()
|
||||
}
|
||||
|
||||
pub fn timestamp_secs() -> u32 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Failed to get UNIX timestamp!")
|
||||
.as_secs()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
|
20
src/ipc.rs
20
src/ipc.rs
|
@ -4,7 +4,7 @@ use crate::{
|
|||
CHAR_NAME_MAX_LENGTH,
|
||||
common::{read_string, write_string},
|
||||
world::{
|
||||
ActorControlSelf, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
||||
ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
||||
UpdateClassInfo,
|
||||
},
|
||||
};
|
||||
|
@ -342,23 +342,7 @@ pub enum IPCStructData {
|
|||
unk: [u8; 8],
|
||||
},
|
||||
#[br(pre_assert(*magic == IPCOpCode::ChatMessage))]
|
||||
ChatMessage {
|
||||
// TODO: incomplete
|
||||
#[brw(pad_before = 4)] // empty
|
||||
player_id: u32,
|
||||
|
||||
#[brw(pad_before = 4)] // empty
|
||||
timestamp: u32,
|
||||
|
||||
#[brw(pad_before = 8)] // NOT empty
|
||||
channel: u16,
|
||||
|
||||
#[br(count = 32)]
|
||||
#[bw(pad_size_to = 32)]
|
||||
#[br(map = read_string)]
|
||||
#[bw(map = write_string)]
|
||||
message: String,
|
||||
},
|
||||
ChatMessage(ChatMessage),
|
||||
#[br(pre_assert(*magic == IPCOpCode::GameMasterCommand))]
|
||||
GameMasterCommand {
|
||||
// TODO: incomplete
|
||||
|
|
|
@ -7,10 +7,12 @@ pub mod blowfish;
|
|||
pub mod chara_make;
|
||||
pub mod client_select_data;
|
||||
mod common;
|
||||
pub use common::timestamp_secs;
|
||||
mod compression;
|
||||
pub mod config;
|
||||
pub mod encryption;
|
||||
pub mod ipc;
|
||||
pub mod lobby;
|
||||
pub mod oodle;
|
||||
pub mod packet;
|
||||
pub mod patchlist;
|
||||
|
|
308
src/lobby/connection.rs
Normal file
308
src/lobby/connection.rs
Normal file
|
@ -0,0 +1,308 @@
|
|||
use std::cmp::min;
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::{
|
||||
CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, WORLD_NAME, ZONE_ID,
|
||||
blowfish::Blowfish,
|
||||
client_select_data::ClientSelectData,
|
||||
common::timestamp_secs,
|
||||
encryption::generate_encryption_key,
|
||||
ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, Server, ServiceAccount},
|
||||
packet::{
|
||||
CompressionType, ConnectionType, PacketSegment, SegmentType, State, parse_packet,
|
||||
send_packet,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct LobbyConnection {
|
||||
pub socket: TcpStream,
|
||||
|
||||
pub state: State,
|
||||
}
|
||||
|
||||
impl LobbyConnection {
|
||||
pub async fn parse_packet(&mut self, data: &[u8]) -> (Vec<PacketSegment>, ConnectionType) {
|
||||
parse_packet(data, &mut self.state).await
|
||||
}
|
||||
|
||||
pub async fn send_segment(&mut self, segment: PacketSegment) {
|
||||
send_packet(
|
||||
&mut self.socket,
|
||||
&[segment],
|
||||
&mut self.state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn initialize_encryption(&mut self, phrase: &str, key: &[u8; 4]) {
|
||||
// Generate an encryption key for this client
|
||||
self.state.client_key = Some(generate_encryption_key(key, phrase));
|
||||
|
||||
let mut data = 0xE0003C2Au32.to_le_bytes().to_vec();
|
||||
data.resize(0x280, 0);
|
||||
|
||||
let blowfish = Blowfish::new(&self.state.client_key.unwrap());
|
||||
blowfish.encrypt(&mut data);
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::InitializationEncryptionResponse { data },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn send_account_list(&mut self) {
|
||||
// send the client the service account list
|
||||
let service_accounts = [ServiceAccount {
|
||||
id: 0x002E4A2B,
|
||||
unk1: 0,
|
||||
index: 0,
|
||||
name: "FINAL FANTASY XIV".to_string(),
|
||||
}]
|
||||
.to_vec();
|
||||
|
||||
let service_account_list = IPCStructData::LobbyServiceAccountList {
|
||||
sequence: 0,
|
||||
num_service_accounts: service_accounts.len() as u8,
|
||||
unk1: 3,
|
||||
unk2: 0x99,
|
||||
service_accounts: service_accounts.to_vec(),
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyServiceAccountList,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: service_account_list,
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn send_lobby_info(&mut self, sequence: u64) {
|
||||
let mut packets = Vec::new();
|
||||
// send them the server list
|
||||
{
|
||||
let mut servers = [Server {
|
||||
id: WORLD_ID,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
icon: 0,
|
||||
name: WORLD_NAME.to_string(),
|
||||
}]
|
||||
.to_vec();
|
||||
// add any empty boys
|
||||
servers.resize(6, Server::default());
|
||||
|
||||
let lobby_server_list = IPCStructData::LobbyServerList {
|
||||
sequence: 0,
|
||||
unk1: 1,
|
||||
offset: 0,
|
||||
num_servers: 1,
|
||||
servers,
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyServerList,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: lobby_server_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
packets.push(response_packet);
|
||||
}
|
||||
|
||||
// send them the retainer list
|
||||
{
|
||||
let lobby_retainer_list = IPCStructData::LobbyRetainerList { unk1: 1 };
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyRetainerList,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: lobby_retainer_list,
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
packets.push(response_packet);
|
||||
}
|
||||
|
||||
send_packet(
|
||||
&mut self.socket,
|
||||
&packets,
|
||||
&mut self.state,
|
||||
CompressionType::Uncompressed,
|
||||
)
|
||||
.await;
|
||||
|
||||
// now send them the character list
|
||||
{
|
||||
let select_data = ClientSelectData {
|
||||
game_name_unk: "Final Fantasy".to_string(),
|
||||
current_class: 2,
|
||||
class_levels: [5; 30],
|
||||
race: 0,
|
||||
subrace: 0,
|
||||
gender: 0,
|
||||
birth_month: 5,
|
||||
birth_day: 5,
|
||||
guardian: 2,
|
||||
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 {
|
||||
id: 0,
|
||||
content_id: CONTENT_ID,
|
||||
index: 0,
|
||||
unk1: [0; 16],
|
||||
origin_server_id: WORLD_ID,
|
||||
current_server_id: WORLD_ID,
|
||||
character_name: CHAR_NAME.to_string(),
|
||||
origin_server_name: WORLD_NAME.to_string(),
|
||||
current_server_name: WORLD_NAME.to_string(),
|
||||
character_detail_json: select_data.to_json(),
|
||||
unk2: [0; 20],
|
||||
}];
|
||||
|
||||
for i in 0..4 {
|
||||
let mut characters_in_packet = Vec::new();
|
||||
for _ in 0..min(characters.len(), 2) {
|
||||
characters_in_packet.push(characters.swap_remove(0));
|
||||
}
|
||||
// add any empty boys
|
||||
characters_in_packet.resize(2, CharacterDetails::default());
|
||||
|
||||
let lobby_character_list = if i == 3 {
|
||||
// On the last packet, add the account-wide information
|
||||
IPCStructData::LobbyCharacterList {
|
||||
sequence,
|
||||
counter: (i * 4) + 1, // TODO: why the + 1 here?
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 128,
|
||||
unk5: [0; 7],
|
||||
unk6: 0,
|
||||
veteran_rank: 0,
|
||||
unk7: 0,
|
||||
days_subscribed: 5,
|
||||
remaining_days: 5,
|
||||
days_to_next_rank: 0,
|
||||
unk8: 8,
|
||||
max_characters_on_world: 2,
|
||||
entitled_expansion: 4,
|
||||
characters: characters_in_packet,
|
||||
}
|
||||
} else {
|
||||
IPCStructData::LobbyCharacterList {
|
||||
sequence,
|
||||
counter: i * 4,
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 0,
|
||||
unk5: [0; 7],
|
||||
unk6: 0,
|
||||
veteran_rank: 0,
|
||||
unk7: 0,
|
||||
days_subscribed: 0,
|
||||
remaining_days: 0,
|
||||
days_to_next_rank: 0,
|
||||
max_characters_on_world: 0,
|
||||
unk8: 0,
|
||||
entitled_expansion: 0,
|
||||
characters: characters_in_packet,
|
||||
}
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyCharacterList,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: lobby_character_list,
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_enter_world(&mut self, sequence: u64, lookup_id: u64) {
|
||||
let Some(session_id) = &self.state.session_id else {
|
||||
panic!("Missing session id!");
|
||||
};
|
||||
|
||||
let enter_world = IPCStructData::LobbyEnterWorld {
|
||||
sequence,
|
||||
character_id: 0,
|
||||
content_id: lookup_id, // TODO: shouldn't these be named the same then?
|
||||
session_id: session_id.clone(),
|
||||
port: 7100,
|
||||
host: "127.0.0.1".to_string(),
|
||||
};
|
||||
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::LobbyEnterWorld,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: enter_world,
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
1
src/lobby/mod.rs
Normal file
1
src/lobby/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod connection;
|
|
@ -5,10 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use binrw::{BinRead, BinWrite, binrw};
|
||||
use tokio::{
|
||||
io::{AsyncWriteExt, WriteHalf},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||
|
||||
use crate::{
|
||||
common::read_string,
|
||||
|
@ -150,7 +147,7 @@ fn dump(msg: &str, data: &[u8]) {
|
|||
}
|
||||
|
||||
pub async fn send_packet(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
socket: &mut TcpStream,
|
||||
segments: &[PacketSegment],
|
||||
state: &mut State,
|
||||
compression_type: CompressionType,
|
||||
|
@ -219,7 +216,6 @@ pub struct State {
|
|||
pub session_id: Option<String>,
|
||||
pub serverbound_oodle: FFXIVOodle,
|
||||
pub clientbound_oodle: FFXIVOodle,
|
||||
pub player_id: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>, ConnectionType) {
|
||||
|
@ -254,12 +250,7 @@ pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_keep_alive(
|
||||
socket: &mut WriteHalf<TcpStream>,
|
||||
state: &mut State,
|
||||
id: u32,
|
||||
timestamp: u32,
|
||||
) {
|
||||
pub async fn send_keep_alive(socket: &mut TcpStream, state: &mut State, id: u32, timestamp: u32) {
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: 0,
|
||||
target_actor: 0,
|
||||
|
|
27
src/world/chat_handler.rs
Normal file
27
src/world/chat_handler.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use super::{ChatMessage, Position, ZoneConnection};
|
||||
|
||||
pub struct ChatHandler {}
|
||||
|
||||
impl ChatHandler {
|
||||
pub async fn handle_chat_message(connection: &mut ZoneConnection, chat_message: &ChatMessage) {
|
||||
tracing::info!("Client sent chat message: {}!", chat_message.message);
|
||||
|
||||
let parts: Vec<&str> = chat_message.message.split(' ').collect();
|
||||
match parts[0] {
|
||||
"!setpos" => {
|
||||
let pos_x = parts[1].parse::<f32>().unwrap();
|
||||
let pos_y = parts[2].parse::<f32>().unwrap();
|
||||
let pos_z = parts[3].parse::<f32>().unwrap();
|
||||
|
||||
connection
|
||||
.set_player_position(Position {
|
||||
x: pos_x,
|
||||
y: pos_y,
|
||||
z: pos_z,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => tracing::info!("Unrecognized debug command!"),
|
||||
}
|
||||
}
|
||||
}
|
23
src/world/chat_message.rs
Normal file
23
src/world/chat_message.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use binrw::binrw;
|
||||
|
||||
use crate::common::{read_string, write_string};
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ChatMessage {
|
||||
// TODO: incomplete
|
||||
#[brw(pad_before = 4)] // empty
|
||||
pub player_id: u32,
|
||||
|
||||
#[brw(pad_before = 4)] // empty
|
||||
pub timestamp: u32,
|
||||
|
||||
#[brw(pad_before = 8)] // NOT empty
|
||||
pub channel: u16,
|
||||
|
||||
#[br(count = 32)]
|
||||
#[bw(pad_size_to = 32)]
|
||||
#[br(map = read_string)]
|
||||
#[bw(map = write_string)]
|
||||
pub message: String,
|
||||
}
|
67
src/world/connection.rs
Normal file
67
src/world/connection.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::{
|
||||
WORLD_ID,
|
||||
common::timestamp_secs,
|
||||
ipc::{ActorSetPos, IPCOpCode, IPCSegment, IPCStructData},
|
||||
packet::{
|
||||
CompressionType, ConnectionType, PacketSegment, SegmentType, State, parse_packet,
|
||||
send_packet,
|
||||
},
|
||||
};
|
||||
|
||||
use super::Position;
|
||||
|
||||
pub struct ZoneConnection {
|
||||
pub socket: TcpStream,
|
||||
|
||||
pub state: State,
|
||||
pub player_id: u32,
|
||||
}
|
||||
|
||||
impl ZoneConnection {
|
||||
pub async fn parse_packet(&mut self, data: &[u8]) -> (Vec<PacketSegment>, ConnectionType) {
|
||||
parse_packet(data, &mut self.state).await
|
||||
}
|
||||
|
||||
pub async fn send_segment(&mut self, segment: PacketSegment) {
|
||||
send_packet(
|
||||
&mut self.socket,
|
||||
&[segment],
|
||||
&mut self.state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn set_player_position(&mut self, position: Position) {
|
||||
// set pos
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 14,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::ActorSetPos,
|
||||
server_id: WORLD_ID,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::ActorSetPos(ActorSetPos {
|
||||
unk: 0x020fa3b8,
|
||||
position,
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: self.player_id,
|
||||
target_actor: self.player_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut self.socket,
|
||||
&[response_packet],
|
||||
&mut self.state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,3 +25,12 @@ pub use init_zone::InitZone;
|
|||
|
||||
mod zone;
|
||||
pub use zone::Zone;
|
||||
|
||||
mod chat_handler;
|
||||
pub use chat_handler::ChatHandler;
|
||||
|
||||
mod connection;
|
||||
pub use connection::ZoneConnection;
|
||||
|
||||
mod chat_message;
|
||||
pub use chat_message::ChatMessage;
|
||||
|
|
|
@ -6,7 +6,7 @@ use physis::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::{config::get_config, world::Position};
|
||||
use crate::config::get_config;
|
||||
|
||||
/// Represents a loaded zone
|
||||
pub struct Zone {
|
||||
|
|
Loading…
Add table
Reference in a new issue