diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 68a1f8f..e1aa623 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -1,14 +1,14 @@ use std::time::{SystemTime, UNIX_EPOCH}; 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::packet::{ CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet, }; use kawari::world::{ ActorControlSelf, ActorControlType, CustomizeData, InitZone, PlayerSetup, PlayerSpawn, - PlayerStats, UpdateClassInfo, + PlayerStats, Position, UpdateClassInfo, }; use kawari::{CONTENT_ID, WORLD_ID, ZONE_ID}; use tokio::io::AsyncReadExt; @@ -483,6 +483,53 @@ async fn main() { } 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::().unwrap(); + let pos_y = parts[2].parse::().unwrap(); + let pos_z = parts[3].parse::().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::GameMasterCommand { command, arg, .. } => { tracing::info!("Got a game master command!"); diff --git a/src/ipc.rs b/src/ipc.rs index 99e6b6f..ff9d1ef 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -81,6 +81,8 @@ pub enum IPCOpCode { ChatMessage = 0xCA, // Sent by the client when they send a GM command. This can only be sent by the client if they are sent a GM rank. GameMasterCommand = 0x3B3, + // Sent by the server to modify the client's position + ActorSetPos = 0x223, } #[binrw] @@ -112,6 +114,15 @@ pub struct Server { pub name: String, } +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct ActorSetPos { + pub unk: u32, + pub layer_id: u32, + pub position: Position, + pub unk3: u32, +} + #[binrw] #[derive(Debug, Clone, Default)] pub struct CharacterDetails { @@ -417,6 +428,8 @@ pub enum IPCStructData { // TODO: guessed unk: [u8; 8], }, + #[br(pre_assert(false))] + ActorSetPos(ActorSetPos), } #[binrw] @@ -474,6 +487,7 @@ impl IPCSegment { IPCStructData::Disconnected { .. } => todo!(), IPCStructData::ChatMessage { .. } => 1056, IPCStructData::GameMasterCommand { .. } => todo!(), + IPCStructData::ActorSetPos { .. } => 24, } } } @@ -523,6 +537,7 @@ mod tests { IPCStructData::PlayerSetup(PlayerSetup::default()), IPCStructData::UpdateClassInfo(UpdateClassInfo::default()), IPCStructData::PlayerSpawn(PlayerSpawn::default()), + IPCStructData::ActorSetPos(ActorSetPos::default()), ]; for ipc in &ipc_types {