2025-03-15 19:34:29 -04:00
|
|
|
use std::io::Cursor;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::sync::{Arc, Mutex};
|
2025-03-10 21:31:21 -04:00
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
|
2025-03-15 19:34:29 -04:00
|
|
|
use binrw::BinRead;
|
2025-03-13 00:02:37 -04:00
|
|
|
use kawari::client_select_data::ClientCustomizeData;
|
2025-03-13 19:52:01 -04:00
|
|
|
use kawari::ipc::{ActorSetPos, GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
2025-03-10 21:31:21 -04:00
|
|
|
use kawari::oodle::FFXIVOodle;
|
|
|
|
use kawari::packet::{
|
2025-03-11 23:52:55 -04:00
|
|
|
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
2025-03-12 18:05:41 -04:00
|
|
|
use kawari::world::{
|
2025-03-14 00:30:37 -04:00
|
|
|
ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
2025-03-15 19:34:29 -04:00
|
|
|
UpdateClassInfo, Zone,
|
2025-03-12 18:05:41 -04:00
|
|
|
};
|
2025-03-14 00:30:37 -04:00
|
|
|
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID};
|
2025-03-09 11:07:01 -04:00
|
|
|
use tokio::io::AsyncReadExt;
|
|
|
|
use tokio::net::TcpListener;
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
|
|
|
tracing_subscriber::fmt::init();
|
|
|
|
|
|
|
|
let listener = TcpListener::bind("127.0.0.1:7100").await.unwrap();
|
|
|
|
|
|
|
|
tracing::info!("World server started on 7100");
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let (socket, _) = listener.accept().await.unwrap();
|
|
|
|
let (mut read, mut write) = tokio::io::split(socket);
|
|
|
|
|
|
|
|
let mut state = State {
|
|
|
|
client_key: None,
|
|
|
|
session_id: None,
|
2025-03-11 23:52:55 -04:00
|
|
|
clientbound_oodle: FFXIVOodle::new(),
|
|
|
|
serverbound_oodle: FFXIVOodle::new(),
|
2025-03-10 21:31:21 -04:00
|
|
|
player_id: None,
|
2025-03-09 11:07:01 -04:00
|
|
|
};
|
|
|
|
|
2025-03-15 19:34:29 -04:00
|
|
|
let zone = Zone::load(010);
|
|
|
|
|
|
|
|
let mut exit_position = None;
|
|
|
|
|
2025-03-09 11:07:01 -04:00
|
|
|
tokio::spawn(async move {
|
|
|
|
let mut buf = [0; 2056];
|
|
|
|
loop {
|
|
|
|
let n = read.read(&mut buf).await.expect("Failed to read data!");
|
|
|
|
|
|
|
|
if n != 0 {
|
2025-03-10 21:31:21 -04:00
|
|
|
println!("recieved {n} bytes...");
|
|
|
|
let (segments, connection_type) = parse_packet(&buf[..n], &mut state).await;
|
2025-03-09 11:07:01 -04:00
|
|
|
for segment in &segments {
|
2025-03-12 00:32:37 -04:00
|
|
|
let timestamp_secs = || {
|
|
|
|
SystemTime::now()
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
.expect("Failed to get UNIX timestamp!")
|
|
|
|
.as_secs()
|
|
|
|
.try_into()
|
|
|
|
.unwrap()
|
|
|
|
};
|
|
|
|
|
2025-03-09 11:07:01 -04:00
|
|
|
match &segment.segment_type {
|
2025-03-10 21:31:21 -04:00
|
|
|
SegmentType::InitializeSession { player_id } => {
|
|
|
|
state.player_id = Some(*player_id);
|
|
|
|
|
|
|
|
// We have send THEM a keep alive
|
|
|
|
{
|
|
|
|
let timestamp: u32 = SystemTime::now()
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
.expect("Failed to get UNIX timestamp!")
|
|
|
|
.as_secs()
|
|
|
|
.try_into()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::KeepAlive {
|
|
|
|
id: 0xE0037603u32,
|
|
|
|
timestamp,
|
|
|
|
},
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
match connection_type {
|
|
|
|
kawari::packet::ConnectionType::Zone => {
|
|
|
|
tracing::info!(
|
|
|
|
"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,
|
|
|
|
},
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
kawari::packet::ConnectionType::Chat => {
|
|
|
|
tracing::info!(
|
|
|
|
"Client {player_id} is initializing chat session..."
|
|
|
|
);
|
|
|
|
|
|
|
|
{
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: 0,
|
|
|
|
target_actor: 0,
|
|
|
|
segment_type: SegmentType::ZoneInitialize {
|
|
|
|
player_id: *player_id,
|
|
|
|
},
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitializeChat,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: 0,
|
2025-03-11 23:52:55 -04:00
|
|
|
data: IPCStructData::InitializeChat { unk: [0; 8] },
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: *player_id,
|
|
|
|
target_actor: *player_id,
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => panic!(
|
|
|
|
"The client is trying to initialize the wrong connection?!"
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
SegmentType::Ipc { data } => {
|
2025-03-10 21:31:21 -04:00
|
|
|
match &data.data {
|
|
|
|
IPCStructData::InitRequest { .. } => {
|
|
|
|
tracing::info!(
|
|
|
|
"Client is now requesting zone information. Sending!"
|
|
|
|
);
|
|
|
|
|
|
|
|
// IPC Init(?)
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitResponse,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::InitResponse {
|
|
|
|
unk1: 0,
|
|
|
|
character_id: state.player_id.unwrap(),
|
|
|
|
unk2: 0,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Control Data
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::ActorControlSelf,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:05:41 -04:00
|
|
|
data: IPCStructData::ActorControlSelf(
|
|
|
|
ActorControlSelf {
|
|
|
|
category:
|
|
|
|
ActorControlType::SetCharaGearParamUI,
|
|
|
|
param1: 1,
|
|
|
|
param2: 1,
|
|
|
|
param3: 0,
|
|
|
|
param4: 0,
|
|
|
|
param5: 0,
|
|
|
|
param6: 0,
|
|
|
|
},
|
|
|
|
),
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stats
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PlayerStats,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:05:41 -04:00
|
|
|
data: IPCStructData::PlayerStats(PlayerStats {
|
2025-03-10 21:31:21 -04:00
|
|
|
strength: 1,
|
|
|
|
hp: 100,
|
|
|
|
mp: 100,
|
2025-03-12 18:05:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Player Setup
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PlayerSetup,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:05:41 -04:00
|
|
|
data: IPCStructData::PlayerSetup(PlayerSetup {
|
2025-03-10 21:31:21 -04:00
|
|
|
content_id: CONTENT_ID,
|
|
|
|
exp: [10000; 32],
|
|
|
|
levels: [100; 32],
|
2025-03-14 00:30:37 -04:00
|
|
|
name: CHAR_NAME.to_string(),
|
2025-03-12 18:05:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Player Class Info
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::UpdateClassInfo,
|
|
|
|
server_id: 69, // lol
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:05:41 -04:00
|
|
|
data: IPCStructData::UpdateClassInfo(
|
|
|
|
UpdateClassInfo {
|
|
|
|
class_id: 35,
|
|
|
|
unknown: 1,
|
|
|
|
synced_level: 90,
|
|
|
|
class_level: 90,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
),
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
|
|
|
|
2025-03-13 21:49:03 -04:00
|
|
|
// unk10
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::Unk10,
|
|
|
|
server_id: 69, // lol
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::Unk10 {
|
|
|
|
unk: 0x41a0000000000002,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unk9
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::Unk9,
|
|
|
|
server_id: 69, // lol
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// link shell information
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::LinkShellInformation,
|
|
|
|
server_id: 69, // lol
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::LinkShellInformation {
|
|
|
|
unk: [0; 456],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unk8
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::Unk8,
|
|
|
|
server_id: 69, // lol
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-03-10 21:31:21 -04:00
|
|
|
// Init Zone
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitZone,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:05:41 -04:00
|
|
|
data: IPCStructData::InitZone(InitZone {
|
2025-03-10 21:31:21 -04:00
|
|
|
server_id: WORLD_ID,
|
|
|
|
zone_id: ZONE_ID,
|
2025-03-14 20:47:36 -04:00
|
|
|
weather_id: 1,
|
2025-03-12 18:05:41 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-10 21:31:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-11 23:52:55 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
|
|
|
IPCStructData::FinishLoading { .. } => {
|
|
|
|
tracing::info!(
|
|
|
|
"Client has finished loading... spawning in!"
|
|
|
|
);
|
2025-03-11 23:52:55 -04:00
|
|
|
|
2025-03-13 21:11:20 -04:00
|
|
|
// send welcome message
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::ServerChatMessage,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::ServerChatMessage {
|
|
|
|
message: "Welcome to Kawari!".to_string(),
|
|
|
|
unk: 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;
|
|
|
|
}
|
|
|
|
|
2025-03-12 00:32:37 -04:00
|
|
|
// send player spawn
|
|
|
|
{
|
2025-03-11 23:52:55 -04:00
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
2025-03-12 00:32:37 -04:00
|
|
|
op_code: IPCOpCode::PlayerSpawn,
|
2025-03-11 23:52:55 -04:00
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
2025-03-12 18:22:17 -04:00
|
|
|
data: IPCStructData::PlayerSpawn(PlayerSpawn {
|
2025-03-12 19:34:15 -04:00
|
|
|
current_world_id: WORLD_ID,
|
|
|
|
home_world_id: WORLD_ID,
|
|
|
|
title: 1,
|
|
|
|
class_job: 35,
|
2025-03-14 00:30:37 -04:00
|
|
|
name: CHAR_NAME.to_string(),
|
2025-03-12 18:22:17 -04:00
|
|
|
hp_curr: 100,
|
|
|
|
hp_max: 100,
|
|
|
|
mp_curr: 100,
|
|
|
|
mp_max: 100,
|
2025-03-12 19:34:15 -04:00
|
|
|
model_type: 1,
|
|
|
|
spawn_index: 1,
|
|
|
|
state: 1,
|
2025-03-13 00:41:16 -04:00
|
|
|
gm_rank: 3,
|
2025-03-14 00:30:37 -04:00
|
|
|
look: CUSTOMIZE_DATA,
|
2025-03-13 00:02:37 -04:00
|
|
|
fc_tag: "LOCAL".to_string(),
|
2025-03-14 00:30:37 -04:00
|
|
|
models: [
|
|
|
|
0, // head
|
|
|
|
89, // body
|
|
|
|
89, // hands
|
|
|
|
89, // legs
|
|
|
|
89, // feet
|
|
|
|
0, // ears
|
|
|
|
0, // neck
|
|
|
|
0, // wrists
|
|
|
|
0, // left finger
|
|
|
|
0, // right finger
|
|
|
|
],
|
2025-03-15 19:34:29 -04:00
|
|
|
pos: exit_position
|
|
|
|
.unwrap_or(Position::default()),
|
2025-03-12 18:22:17 -04:00
|
|
|
..Default::default()
|
|
|
|
}),
|
2025-03-11 23:52:55 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let response_packet = PacketSegment {
|
|
|
|
source_actor: state.player_id.unwrap(),
|
|
|
|
target_actor: state.player_id.unwrap(),
|
|
|
|
segment_type: SegmentType::Ipc { data: ipc },
|
|
|
|
};
|
2025-03-12 00:32:37 -04:00
|
|
|
send_packet(
|
|
|
|
&mut write,
|
|
|
|
&[response_packet],
|
|
|
|
&mut state,
|
|
|
|
CompressionType::Oodle,
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
}
|
2025-03-15 19:34:29 -04:00
|
|
|
|
|
|
|
// fade in?
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PrepareZoning,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::PrepareZoning {
|
|
|
|
unk: [0, 0, 0, 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// wipe any exit position so it isn't accidentally reused
|
|
|
|
exit_position = None;
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
|
|
|
IPCStructData::Unk1 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk1!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk2 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk2!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk3 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk3!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk4 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk4!");
|
|
|
|
}
|
|
|
|
IPCStructData::SetSearchInfoHandler { .. } => {
|
|
|
|
tracing::info!("Recieved SetSearchInfoHandler!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk5 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk5!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk6 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk6!");
|
|
|
|
}
|
2025-03-13 21:49:03 -04:00
|
|
|
IPCStructData::Unk7 {
|
|
|
|
timestamp, unk1, ..
|
|
|
|
} => {
|
|
|
|
tracing::info!("Recieved Unk7! {:#?}", unk1);
|
|
|
|
|
|
|
|
// send unk11 in response
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::Unk11,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::Unk11 {
|
|
|
|
timestamp: *timestamp,
|
|
|
|
unk: 333,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2025-03-12 00:32:37 -04:00
|
|
|
}
|
|
|
|
IPCStructData::UpdatePositionHandler { .. } => {
|
|
|
|
tracing::info!("Recieved UpdatePositionHandler!");
|
2025-03-10 21:31:21 -04:00
|
|
|
}
|
2025-03-12 18:44:05 -04:00
|
|
|
IPCStructData::LogOut { .. } => {
|
|
|
|
tracing::info!("Recieved log out from client!");
|
|
|
|
|
|
|
|
// tell the client to disconnect
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::LogOutComplete,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IPCStructData::Disconnected { .. } => {
|
|
|
|
tracing::info!("Client disconnected!");
|
|
|
|
}
|
2025-03-12 19:45:15 -04:00
|
|
|
IPCStructData::ChatMessage { message, .. } => {
|
2025-03-12 19:34:15 -04:00
|
|
|
tracing::info!("Client sent chat message: {message}!");
|
2025-03-13 19:52:01 -04:00
|
|
|
|
|
|
|
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!"),
|
|
|
|
}
|
2025-03-12 19:34:15 -04:00
|
|
|
}
|
2025-03-13 00:41:16 -04:00
|
|
|
IPCStructData::GameMasterCommand { command, arg, .. } => {
|
|
|
|
tracing::info!("Got a game master command!");
|
|
|
|
|
|
|
|
match &command {
|
|
|
|
GameMasterCommandType::ChangeTerritory => {
|
|
|
|
// Init Zone
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitZone,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::InitZone(InitZone {
|
|
|
|
server_id: WORLD_ID,
|
|
|
|
zone_id: *arg as u16,
|
|
|
|
..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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-14 00:30:37 -04:00
|
|
|
IPCStructData::Unk12 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk12!");
|
|
|
|
}
|
2025-03-15 19:34:29 -04:00
|
|
|
IPCStructData::EnterZoneLine {
|
|
|
|
exit_box_id,
|
|
|
|
position,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
tracing::info!(
|
|
|
|
"Character entered {exit_box_id} with a position of {position:#?}!"
|
|
|
|
);
|
|
|
|
|
|
|
|
// find the exit box id
|
|
|
|
let (_, exit_box) =
|
|
|
|
zone.find_exit_box(*exit_box_id).unwrap();
|
|
|
|
tracing::info!("exit box: {:#?}", exit_box);
|
|
|
|
|
|
|
|
// find the pop range on the other side
|
|
|
|
let new_zone = Zone::load(exit_box.territory_type);
|
|
|
|
let (destination_object, _) = new_zone
|
|
|
|
.find_pop_range(exit_box.destination_instance_id)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// set the exit position
|
|
|
|
exit_position = Some(Position {
|
|
|
|
x: destination_object.transform.translation[0],
|
|
|
|
y: destination_object.transform.translation[1],
|
|
|
|
z: destination_object.transform.translation[2],
|
|
|
|
});
|
|
|
|
|
|
|
|
// fade out?
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PrepareZoning,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::PrepareZoning {
|
|
|
|
unk: [0x01000000, 0, 0, 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fade out? x2
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PrepareZoning,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::PrepareZoning {
|
|
|
|
unk: [0, 0x00000085, 0x00030000, 0x000008ff], // last thing is probably a float?
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
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!(
|
|
|
|
"sending them to {:#?}",
|
|
|
|
exit_box.territory_type
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
d.push("resources/tests/init_zone.bin");
|
|
|
|
|
|
|
|
let buffer = std::fs::read(d).unwrap();
|
|
|
|
let mut buffer = Cursor::new(&buffer);
|
|
|
|
|
|
|
|
let init_zone = InitZone::read_le(&mut buffer).unwrap();
|
|
|
|
|
|
|
|
// Init Zone
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::InitZone,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::InitZone(InitZone {
|
|
|
|
server_id: WORLD_ID,
|
|
|
|
zone_id: exit_box.territory_type,
|
|
|
|
..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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// idk
|
|
|
|
{
|
|
|
|
let ipc = IPCSegment {
|
|
|
|
unk1: 0,
|
|
|
|
unk2: 0,
|
|
|
|
op_code: IPCOpCode::PrepareZoning,
|
|
|
|
server_id: 0,
|
|
|
|
timestamp: timestamp_secs(),
|
|
|
|
data: IPCStructData::PrepareZoning {
|
|
|
|
unk: [0x01100000, 0, 0, 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IPCStructData::Unk13 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk13!");
|
|
|
|
}
|
|
|
|
IPCStructData::Unk14 { .. } => {
|
|
|
|
tracing::info!("Recieved Unk14!");
|
|
|
|
}
|
2025-03-10 21:31:21 -04:00
|
|
|
_ => panic!(
|
|
|
|
"The server is recieving a IPC response or unknown packet!"
|
|
|
|
),
|
|
|
|
}
|
2025-03-09 11:07:01 -04:00
|
|
|
}
|
|
|
|
SegmentType::KeepAlive { id, timestamp } => {
|
2025-03-10 21:31:21 -04:00
|
|
|
send_keep_alive(&mut write, &mut state, *id, *timestamp).await
|
|
|
|
}
|
|
|
|
SegmentType::KeepAliveResponse { .. } => {
|
|
|
|
tracing::info!("Got keep alive response from client... cool...");
|
2025-03-09 11:07:01 -04:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!("The server is recieving a response or unknown packet!")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|