mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-23 15:47:45 +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 kawari::CONTENT_ID;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
use kawari::blowfish::Blowfish;
|
|
||||||
use kawari::chara_make::CharaMake;
|
use kawari::chara_make::CharaMake;
|
||||||
use kawari::client_select_data::{ClientCustomizeData, ClientSelectData};
|
use kawari::ipc::{CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, LobbyCharacterAction};
|
||||||
use kawari::encryption::generate_encryption_key;
|
use kawari::lobby::connection::LobbyConnection;
|
||||||
use kawari::ipc::{
|
|
||||||
CharacterDetails, IPCOpCode, IPCSegment, IPCStructData, LobbyCharacterAction, Server,
|
|
||||||
ServiceAccount,
|
|
||||||
};
|
|
||||||
use kawari::oodle::FFXIVOodle;
|
use kawari::oodle::FFXIVOodle;
|
||||||
use kawari::packet::{
|
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
|
||||||
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
use tokio::io::AsyncReadExt;
|
||||||
};
|
use tokio::net::TcpListener;
|
||||||
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, WORLD_NAME, ZONE_ID};
|
|
||||||
use tokio::io::{AsyncReadExt, WriteHalf};
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -27,29 +17,33 @@ async fn main() {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
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,
|
client_key: None,
|
||||||
session_id: None,
|
session_id: None,
|
||||||
clientbound_oodle: FFXIVOodle::new(),
|
clientbound_oodle: FFXIVOodle::new(),
|
||||||
serverbound_oodle: FFXIVOodle::new(),
|
serverbound_oodle: FFXIVOodle::new(),
|
||||||
player_id: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut connection = LobbyConnection { socket, state };
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = [0; 2056];
|
let mut buf = [0; 2056];
|
||||||
loop {
|
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 {
|
if n != 0 {
|
||||||
tracing::info!("read {} bytes", n);
|
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 {
|
for segment in &segments {
|
||||||
match &segment.segment_type {
|
match &segment.segment_type {
|
||||||
SegmentType::InitializeEncryption { phrase, key } => {
|
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 {
|
SegmentType::Ipc { data } => match &data.data {
|
||||||
IPCStructData::ClientVersionInfo {
|
IPCStructData::ClientVersionInfo {
|
||||||
|
@ -60,23 +54,17 @@ async fn main() {
|
||||||
"Client {session_id} ({version_info}) logging in!"
|
"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 } => {
|
IPCStructData::RequestCharacterList { sequence } => {
|
||||||
tracing::info!("Client is requesting character list...");
|
tracing::info!("Client is requesting character list...");
|
||||||
|
|
||||||
send_lobby_info(&mut write, &mut state, *sequence).await;
|
connection.send_lobby_info(*sequence).await;
|
||||||
}
|
}
|
||||||
IPCStructData::LobbyCharacterAction {
|
IPCStructData::LobbyCharacterAction {
|
||||||
character_id,
|
action, name, json, ..
|
||||||
character_index,
|
|
||||||
action,
|
|
||||||
world_id,
|
|
||||||
name,
|
|
||||||
json,
|
|
||||||
..
|
|
||||||
} => {
|
} => {
|
||||||
match action {
|
match action {
|
||||||
LobbyCharacterAction::ReserveName => {
|
LobbyCharacterAction::ReserveName => {
|
||||||
|
@ -136,17 +124,14 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: 0x0,
|
source_actor: 0x0,
|
||||||
target_actor: 0x0,
|
target_actor: 0x0,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc {
|
||||||
};
|
data: ipc,
|
||||||
send_packet(
|
},
|
||||||
&mut write,
|
})
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Uncompressed,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,17 +165,14 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: 0x0,
|
source_actor: 0x0,
|
||||||
target_actor: 0x0,
|
target_actor: 0x0,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc {
|
||||||
};
|
data: ipc,
|
||||||
send_packet(
|
},
|
||||||
&mut write,
|
})
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Uncompressed,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,15 +194,20 @@ async fn main() {
|
||||||
} => {
|
} => {
|
||||||
tracing::info!("Client is joining the world...");
|
tracing::info!("Client is joining the world...");
|
||||||
|
|
||||||
send_enter_world(&mut write, &mut state, *sequence, *lookup_id)
|
connection.send_enter_world(*sequence, *lookup_id).await;
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("The server is recieving a IPC response packet!")
|
panic!("The server is recieving a IPC response packet!")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SegmentType::KeepAlive { id, timestamp } => {
|
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!")
|
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::io::Cursor;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use binrw::BinRead;
|
use binrw::BinRead;
|
||||||
use kawari::client_select_data::ClientCustomizeData;
|
use kawari::ipc::{GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
||||||
use kawari::ipc::{ActorSetPos, GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
|
||||||
use kawari::oodle::FFXIVOodle;
|
use kawari::oodle::FFXIVOodle;
|
||||||
use kawari::packet::{
|
use kawari::packet::{PacketSegment, SegmentType, State, send_keep_alive};
|
||||||
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
|
||||||
};
|
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
ActorControlSelf, ActorControlType, ChatHandler, InitZone, PlayerSetup, PlayerSpawn,
|
||||||
UpdateClassInfo, Zone,
|
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::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
@ -28,41 +24,40 @@ async fn main() {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
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,
|
client_key: None,
|
||||||
session_id: None,
|
session_id: None,
|
||||||
clientbound_oodle: FFXIVOodle::new(),
|
clientbound_oodle: FFXIVOodle::new(),
|
||||||
serverbound_oodle: FFXIVOodle::new(),
|
serverbound_oodle: FFXIVOodle::new(),
|
||||||
player_id: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let zone = Zone::load(010);
|
let zone = Zone::load(010);
|
||||||
|
|
||||||
let mut exit_position = None;
|
let mut exit_position = None;
|
||||||
|
|
||||||
|
let mut connection = ZoneConnection {
|
||||||
|
socket,
|
||||||
|
state,
|
||||||
|
player_id: 0,
|
||||||
|
};
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = [0; 2056];
|
let mut buf = [0; 2056];
|
||||||
loop {
|
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 {
|
if n != 0 {
|
||||||
println!("recieved {n} bytes...");
|
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 {
|
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 {
|
match &segment.segment_type {
|
||||||
SegmentType::InitializeSession { player_id } => {
|
SegmentType::InitializeSession { player_id } => {
|
||||||
state.player_id = Some(*player_id);
|
connection.player_id = *player_id;
|
||||||
|
|
||||||
// We have send THEM a keep alive
|
// We have send THEM a keep alive
|
||||||
{
|
{
|
||||||
|
@ -73,20 +68,15 @@ async fn main() {
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: 0,
|
source_actor: 0,
|
||||||
target_actor: 0,
|
target_actor: 0,
|
||||||
segment_type: SegmentType::KeepAlive {
|
segment_type: SegmentType::KeepAlive {
|
||||||
id: 0xE0037603u32,
|
id: 0xE0037603u32,
|
||||||
timestamp,
|
timestamp,
|
||||||
},
|
},
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,19 +86,14 @@ async fn main() {
|
||||||
"Client {player_id} is initializing zone session..."
|
"Client {player_id} is initializing zone session..."
|
||||||
);
|
);
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: 0,
|
source_actor: 0,
|
||||||
target_actor: 0,
|
target_actor: 0,
|
||||||
segment_type: SegmentType::ZoneInitialize {
|
segment_type: SegmentType::ZoneInitialize {
|
||||||
player_id: *player_id,
|
player_id: *player_id,
|
||||||
},
|
},
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
kawari::packet::ConnectionType::Chat => {
|
kawari::packet::ConnectionType::Chat => {
|
||||||
|
@ -117,19 +102,14 @@ async fn main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: 0,
|
source_actor: 0,
|
||||||
target_actor: 0,
|
target_actor: 0,
|
||||||
segment_type: SegmentType::ZoneInitialize {
|
segment_type: SegmentType::ZoneInitialize {
|
||||||
player_id: *player_id,
|
player_id: *player_id,
|
||||||
},
|
},
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,17 +123,12 @@ async fn main() {
|
||||||
data: IPCStructData::InitializeChat { unk: [0; 8] },
|
data: IPCStructData::InitializeChat { unk: [0; 8] },
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
source_actor: *player_id,
|
source_actor: *player_id,
|
||||||
target_actor: *player_id,
|
target_actor: *player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,22 +154,17 @@ async fn main() {
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: IPCStructData::InitResponse {
|
data: IPCStructData::InitResponse {
|
||||||
unk1: 0,
|
unk1: 0,
|
||||||
character_id: state.player_id.unwrap(),
|
character_id: connection.player_id,
|
||||||
unk2: 0,
|
unk2: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,17 +190,12 @@ async fn main() {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,17 +215,12 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,17 +241,12 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,17 +269,12 @@ async fn main() {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,17 +291,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,17 +311,12 @@ async fn main() {
|
||||||
data: IPCStructData::Unk9 { unk: [0; 24] },
|
data: IPCStructData::Unk9 { unk: [0; 24] },
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,17 +333,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,17 +353,12 @@ async fn main() {
|
||||||
data: IPCStructData::Unk8 { unk: [0; 808] },
|
data: IPCStructData::Unk8 { unk: [0; 808] },
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,17 +378,12 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,17 +406,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,17 +457,12 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,17 +479,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,17 +531,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -652,72 +557,24 @@ async fn main() {
|
||||||
data: IPCStructData::LogOutComplete { unk: [0; 8] },
|
data: IPCStructData::LogOutComplete { unk: [0; 8] },
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPCStructData::Disconnected { .. } => {
|
IPCStructData::Disconnected { .. } => {
|
||||||
tracing::info!("Client disconnected!");
|
tracing::info!("Client disconnected!");
|
||||||
}
|
}
|
||||||
IPCStructData::ChatMessage { message, .. } => {
|
IPCStructData::ChatMessage(chat_message) => {
|
||||||
tracing::info!("Client sent chat message: {message}!");
|
ChatHandler::handle_chat_message(
|
||||||
|
&mut connection,
|
||||||
let parts: Vec<&str> = message.split(' ').collect();
|
chat_message,
|
||||||
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;
|
.await
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => tracing::info!("Unrecognized debug command!"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
IPCStructData::GameMasterCommand { command, arg, .. } => {
|
IPCStructData::GameMasterCommand { command, arg, .. } => {
|
||||||
tracing::info!("Got a game master command!");
|
tracing::info!("Got a game master command!");
|
||||||
|
@ -739,19 +596,14 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc {
|
segment_type: SegmentType::Ipc {
|
||||||
data: ipc,
|
data: ipc,
|
||||||
},
|
},
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -800,17 +652,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,17 +674,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -869,17 +711,12 @@ async fn main() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -896,17 +733,12 @@ async fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_packet = PacketSegment {
|
connection
|
||||||
source_actor: state.player_id.unwrap(),
|
.send_segment(PacketSegment {
|
||||||
target_actor: state.player_id.unwrap(),
|
source_actor: connection.player_id,
|
||||||
|
target_actor: connection.player_id,
|
||||||
segment_type: SegmentType::Ipc { data: ipc },
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
};
|
})
|
||||||
send_packet(
|
|
||||||
&mut write,
|
|
||||||
&[response_packet],
|
|
||||||
&mut state,
|
|
||||||
CompressionType::Oodle,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -922,7 +754,13 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SegmentType::KeepAlive { id, timestamp } => {
|
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 { .. } => {
|
SegmentType::KeepAliveResponse { .. } => {
|
||||||
tracing::info!("Got keep alive response from client... cool...");
|
tracing::info!("Got keep alive response from client... cool...");
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::io::{Cursor, Write};
|
|
||||||
|
|
||||||
mod constants;
|
mod constants;
|
||||||
use constants::{BLOWFISH_P, BLOWFISH_S};
|
use constants::{BLOWFISH_P, BLOWFISH_S};
|
||||||
|
|
||||||
|
@ -93,29 +91,28 @@ impl Blowfish {
|
||||||
/// Calculates the F-function for `x`.
|
/// Calculates the F-function for `x`.
|
||||||
fn f(&self, x: u32) -> u32 {
|
fn f(&self, x: u32) -> u32 {
|
||||||
let [a, b, c, d] = x.to_le_bytes();
|
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[0][d as usize].wrapping_add(self.s[1][c as usize])) ^ (self.s[2][b as usize]))
|
||||||
^ (self.s[2][b as usize]))
|
.wrapping_add(self.s[3][a as usize])
|
||||||
.wrapping_add(self.s[3][a as usize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
fn encrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
||||||
for i in 0..ROUNDS {
|
for i in 0..ROUNDS {
|
||||||
*xl ^= self.p[i as usize];
|
*xl ^= self.p[i];
|
||||||
*xr = self.f(*xl) ^ *xr;
|
*xr ^= self.f(*xl);
|
||||||
|
|
||||||
(*xl, *xr) = (*xr, *xl);
|
(*xl, *xr) = (*xr, *xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
(*xl, *xr) = (*xr, *xl);
|
(*xl, *xr) = (*xr, *xl);
|
||||||
|
|
||||||
*xr ^= self.p[ROUNDS as usize];
|
*xr ^= self.p[ROUNDS];
|
||||||
*xl ^= self.p[ROUNDS as usize + 1];
|
*xl ^= self.p[ROUNDS + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
fn decrypt_pair(&self, xl: &mut u32, xr: &mut u32) {
|
||||||
for i in (2..ROUNDS + 2).rev() {
|
for i in (2..ROUNDS + 2).rev() {
|
||||||
*xl ^= self.p[i as usize];
|
*xl ^= self.p[i];
|
||||||
*xr = self.f(*xl) ^ *xr;
|
*xr ^= self.f(*xl);
|
||||||
|
|
||||||
(*xl, *xr) = (*xr, *xl);
|
(*xl, *xr) = (*xr, *xl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use binrw::binrw;
|
use serde_json::Value;
|
||||||
use serde_json::{Value, json};
|
|
||||||
|
|
||||||
use crate::client_select_data::ClientCustomizeData;
|
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 {
|
pub(crate) fn read_bool_from<T: std::convert::From<u8> + std::cmp::PartialEq>(x: T) -> bool {
|
||||||
x == T::from(1u8)
|
x == T::from(1u8)
|
||||||
|
@ -17,3 +20,12 @@ pub(crate) fn write_string(str: &String) -> Vec<u8> {
|
||||||
let c_string = CString::new(&**str).unwrap();
|
let c_string = CString::new(&**str).unwrap();
|
||||||
c_string.as_bytes_with_nul().to_vec()
|
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,
|
CHAR_NAME_MAX_LENGTH,
|
||||||
common::{read_string, write_string},
|
common::{read_string, write_string},
|
||||||
world::{
|
world::{
|
||||||
ActorControlSelf, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
ActorControlSelf, ChatMessage, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
||||||
UpdateClassInfo,
|
UpdateClassInfo,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -342,23 +342,7 @@ pub enum IPCStructData {
|
||||||
unk: [u8; 8],
|
unk: [u8; 8],
|
||||||
},
|
},
|
||||||
#[br(pre_assert(*magic == IPCOpCode::ChatMessage))]
|
#[br(pre_assert(*magic == IPCOpCode::ChatMessage))]
|
||||||
ChatMessage {
|
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,
|
|
||||||
},
|
|
||||||
#[br(pre_assert(*magic == IPCOpCode::GameMasterCommand))]
|
#[br(pre_assert(*magic == IPCOpCode::GameMasterCommand))]
|
||||||
GameMasterCommand {
|
GameMasterCommand {
|
||||||
// TODO: incomplete
|
// TODO: incomplete
|
||||||
|
|
|
@ -7,10 +7,12 @@ pub mod blowfish;
|
||||||
pub mod chara_make;
|
pub mod chara_make;
|
||||||
pub mod client_select_data;
|
pub mod client_select_data;
|
||||||
mod common;
|
mod common;
|
||||||
|
pub use common::timestamp_secs;
|
||||||
mod compression;
|
mod compression;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod encryption;
|
pub mod encryption;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
pub mod lobby;
|
||||||
pub mod oodle;
|
pub mod oodle;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod patchlist;
|
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 binrw::{BinRead, BinWrite, binrw};
|
||||||
use tokio::{
|
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||||
io::{AsyncWriteExt, WriteHalf},
|
|
||||||
net::TcpStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::read_string,
|
common::read_string,
|
||||||
|
@ -150,7 +147,7 @@ fn dump(msg: &str, data: &[u8]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_packet(
|
pub async fn send_packet(
|
||||||
socket: &mut WriteHalf<TcpStream>,
|
socket: &mut TcpStream,
|
||||||
segments: &[PacketSegment],
|
segments: &[PacketSegment],
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
compression_type: CompressionType,
|
compression_type: CompressionType,
|
||||||
|
@ -219,7 +216,6 @@ pub struct State {
|
||||||
pub session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
pub serverbound_oodle: FFXIVOodle,
|
pub serverbound_oodle: FFXIVOodle,
|
||||||
pub clientbound_oodle: FFXIVOodle,
|
pub clientbound_oodle: FFXIVOodle,
|
||||||
pub player_id: Option<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_packet(data: &[u8], state: &mut State) -> (Vec<PacketSegment>, ConnectionType) {
|
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(
|
pub async fn send_keep_alive(socket: &mut TcpStream, state: &mut State, id: u32, timestamp: u32) {
|
||||||
socket: &mut WriteHalf<TcpStream>,
|
|
||||||
state: &mut State,
|
|
||||||
id: u32,
|
|
||||||
timestamp: u32,
|
|
||||||
) {
|
|
||||||
let response_packet = PacketSegment {
|
let response_packet = PacketSegment {
|
||||||
source_actor: 0,
|
source_actor: 0,
|
||||||
target_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;
|
mod zone;
|
||||||
pub use zone::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
|
/// Represents a loaded zone
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
|
|
Loading…
Add table
Reference in a new issue