Skip to content

Commit

Permalink
Add Lobby channel for Send
Browse files Browse the repository at this point in the history
Previously this was not implemented.
There are a few behavioral changes as a result of this change, though
they should not affect a normal use case.

1. Deucalion now accepts a sig for SendLobbyPacket via sending a Payload
   with OP `Send` and CHANNEL `0` (Lobby). Deucalion accepts a sig for
   SendPacket via a Payload with OP `Send` and any CHANNEL != 0.
2. The SERVER HELLO message has been shortened like so:
   `SERVER HELLO. HOOK STATUS: RECV {}. SEND {}. SEND_LOBBY {}.`
  • Loading branch information
ff14wed committed Mar 13, 2023
1 parent 8449785 commit 9df6a3b
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 90 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ region = "3.0"
once_cell = "1.17"
binary-layout = "3.1.3"
dirs = "4.0"
strum = "0.24"
strum_macros = "0.24"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = [
Expand Down
38 changes: 18 additions & 20 deletions src/hook/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::fmt::Display;
use std::sync::Arc;
use std::time::Instant;

Expand All @@ -10,45 +9,40 @@ use tokio::sync::{mpsc, Mutex};
use pelite::{pattern, pe::PeView, ImageMap};

use log::info;
use strum_macros::Display;

use crate::procloader::{find_pattern_matches, get_ffxiv_filepath};
use crate::rpc;

mod packet;
mod recv;
mod send;
mod send_lobby;
mod waitgroup;

pub struct State {
recv_hook: recv::Hook,
send_hook: send::Hook,
send_lobby_hook: send_lobby::Hook,
wg: waitgroup::WaitGroup,
pub broadcast_rx: Arc<Mutex<mpsc::UnboundedReceiver<rpc::Payload>>>,
}

pub enum Direction {
#[derive(Display, Clone, Copy)]
pub enum HookType {
Recv,
Send,
SendLobby,
}

#[repr(u32)]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Display)]
pub(self) enum Channel {
Lobby,
Zone,
Chat,
}

impl Display for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
match *self {
Channel::Lobby => f.write_str("lobby"),
Channel::Zone => f.write_str("zone"),
Channel::Chat => f.write_str("chat"),
}
}
}

#[derive(Debug, Error)]
pub(self) enum HookError {
#[error("failed to set up {0} hook")]
Expand All @@ -65,13 +59,14 @@ impl State {
let hs = State {
recv_hook: recv::Hook::new(broadcast_tx.clone(), wg.clone())?,
send_hook: send::Hook::new(broadcast_tx.clone(), wg.clone())?,
send_lobby_hook: send_lobby::Hook::new(broadcast_tx.clone(), wg.clone())?,
wg,
broadcast_rx: Arc::new(Mutex::new(broadcast_rx)),
};
Ok(hs)
}

pub fn initialize_hook(&self, sig_str: String, direction: Direction) -> Result<()> {
pub fn initialize_hook(&self, sig_str: String, hook_type: HookType) -> Result<()> {
let pat =
pattern::parse(&sig_str).context(format!("Invalid signature: \"{}\"", sig_str))?;
let sig: &[pattern::Atom] = &pat;
Expand All @@ -80,25 +75,28 @@ impl State {
let image_map = ImageMap::open(&ffxiv_file_path).unwrap();
let pe_image = PeView::from_bytes(image_map.as_ref())?;

let sig_name = match direction {
Direction::Recv => "RecvPacket",
Direction::Send => "SendPacket",
let sig_name = match hook_type {
HookType::Recv => "RecvPacket",
HookType::Send => "SendPacket",
HookType::SendLobby => "SendLobbyPacket",
};
info!("Scanning for {} sig: `{}`", sig_name, sig_str);
let scan_start = Instant::now();
let rvas = find_pattern_matches(sig_name, sig, pe_image)
.map_err(|e| format_err!("{}: {}", e, sig_str))?;
info!("Sig scan took {:?}", scan_start.elapsed());

match direction {
Direction::Recv => self.recv_hook.setup(rvas),
Direction::Send => self.send_hook.setup(rvas),
match hook_type {
HookType::Recv => self.recv_hook.setup(rvas),
HookType::Send => self.send_hook.setup(rvas),
HookType::SendLobby => self.send_lobby_hook.setup(rvas),
}
}

pub fn shutdown(&self) {
self.recv_hook.shutdown();
self.send_hook.shutdown();
self.send_lobby_hook.shutdown();
// Wait for any hooks to finish what they're doing
self.wg.wait();
}
Expand Down
105 changes: 105 additions & 0 deletions src/hook/send_lobby.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::mem;
use std::sync::Arc;

use anyhow::Result;

use tokio::sync::mpsc;

use once_cell::sync::OnceCell;

use retour;
use retour::static_detour;

use crate::rpc;

use crate::procloader::get_ffxiv_handle;

use super::packet;
use super::waitgroup;
use super::{Channel, HookError};

use log::error;

type HookedFunction = unsafe extern "system" fn(*const u8) -> usize;

static_detour! {
static SendLobbyPacket: unsafe extern "system" fn(*const u8) -> usize;
}

#[derive(Clone)]
pub struct Hook {
data_tx: mpsc::UnboundedSender<rpc::Payload>,

lobby_hook: Arc<OnceCell<&'static retour::StaticDetour<HookedFunction>>>,

wg: waitgroup::WaitGroup,
}

impl Hook {
pub fn new(
data_tx: mpsc::UnboundedSender<rpc::Payload>,
wg: waitgroup::WaitGroup,
) -> Result<Hook> {
Ok(Hook {
data_tx,
lobby_hook: Arc::new(OnceCell::new()),
wg,
})
}

pub fn setup(&self, rvas: Vec<usize>) -> Result<()> {
if rvas.len() != 1 {
return Err(HookError::SignatureMatchFailed(rvas.len(), 1).into());
}
let mut ptrs: Vec<*const u8> = Vec::new();
for rva in rvas {
ptrs.push(get_ffxiv_handle()?.wrapping_offset(rva as isize));
}

let self_clone = self.clone();
let lobby_hook = unsafe {
let ptr_fn: HookedFunction = mem::transmute(ptrs[0] as *const ());
SendLobbyPacket.initialize(ptr_fn, move |a| self_clone.send_lobby_packet(a))?
};
if let Err(_) = self.lobby_hook.set(lobby_hook) {
return Err(HookError::SetupFailed(Channel::Lobby).into());
}

unsafe {
self.lobby_hook.get_unchecked().enable()?;
}
Ok(())
}

unsafe fn send_lobby_packet(&self, a1: *const u8) -> usize {
let _guard = self.wg.add();

let ptr_frame: *const u8 = *(a1.add(32) as *const usize) as *const u8;

match packet::extract_packets_from_frame(ptr_frame) {
Ok(packets) => {
for packet in packets {
let _ = self.data_tx.send(rpc::Payload {
op: rpc::MessageOps::Send,
ctx: Channel::Lobby as u32,
data: packet,
});
}
}
Err(e) => {
error!("Could not process packet: {}", e)
}
}

const INVALID_MSG: &str = "Hook function was called without a valid hook";
return self.lobby_hook.get().expect(INVALID_MSG).call(a1);
}

pub fn shutdown(&self) {
unsafe {
if let Some(hook) = self.lobby_hook.get() {
let _ = hook.disable();
};
}
}
}
63 changes: 35 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,25 @@ use log::{error, info};
#[cfg(debug_assertions)]
use simplelog::{CombinedLogger, SimpleLogger};

const RECV_HOOK_SIG: &str = "E8 $ { ' } 4C 8B 43 10 41 8B 40 18";
const SEND_HOOK_SIG: &str = "E8 $ { ' } 8B 53 2C 48 8D 8B";
const RECV_SIG: &str = "E8 $ { ' } 4C 8B 43 10 41 8B 40 18";
const SEND_SIG: &str = "E8 $ { ' } 8B 53 2C 48 8D 8B";
const SEND_LOBBY_SIG: &str = "40 53 48 83 EC 20 44 8B 41 28";

fn handle_payload(payload: rpc::Payload, hs: Arc<hook::State>) -> Result<()> {
info!("Received payload from subscriber: {:?}", payload);
if payload.op == rpc::MessageOps::Recv || payload.op == rpc::MessageOps::Send {
let direction = match payload.op {
rpc::MessageOps::Recv => hook::Direction::Recv,
rpc::MessageOps::Send => hook::Direction::Send,
let hook_type = match payload.op {
rpc::MessageOps::Recv => hook::HookType::Recv,
rpc::MessageOps::Send => {
if payload.ctx == 0 {
hook::HookType::SendLobby
} else {
hook::HookType::Send
}
}
_ => panic!("This case shouldn't be possible"),
};
if let Err(e) = parse_sig_and_initialize_hook(hs, payload.data, direction) {
if let Err(e) = parse_sig_and_initialize_hook(hs, payload.data, hook_type) {
let err = format_err!("error initializing hook: {:?}", e);
error!("{:?}", err);
return Err(err);
Expand All @@ -61,10 +68,28 @@ fn handle_payload(payload: rpc::Payload, hs: Arc<hook::State>) -> Result<()> {
fn parse_sig_and_initialize_hook(
hs: Arc<hook::State>,
data: Vec<u8>,
direction: hook::Direction,
hook_type: hook::HookType,
) -> Result<()> {
let sig_str = String::from_utf8(data).context("Invalid string")?;
hs.initialize_hook(sig_str, direction)
hs.initialize_hook(sig_str, hook_type)
}

fn initialize_hook_with_sig(hs: &Arc<hook::State>, sig: &str, hook_type: hook::HookType) -> bool {
if let Err(e) = hs.initialize_hook(sig.into(), hook_type) {
error!("Could not auto-initialize the {} hook: {}", hook_type, e);
false
} else {
true
}
}
/// Automatically initialize all the hooks, but do not fatally exit if any
/// fail to initialize.
/// Returns initialization status for Recv, Send, SendLobby hook types
fn auto_initialize_hooks(hs: &Arc<hook::State>) -> (bool, bool, bool) {
let r = initialize_hook_with_sig(hs, RECV_SIG, hook::HookType::Recv);
let s = initialize_hook_with_sig(hs, SEND_SIG, hook::HookType::Send);
let sl = initialize_hook_with_sig(hs, SEND_LOBBY_SIG, hook::HookType::SendLobby);
(r, s, sl)
}

#[tokio::main]
Expand All @@ -74,27 +99,9 @@ async fn main_with_result() -> Result<()> {

info!("Attempting to auto-initialize the hooks");

let recv_initialized = {
if let Err(e) = hs.initialize_hook(RECV_HOOK_SIG.into(), hook::Direction::Recv) {
error!("Could not auto-initialize the recv hook: {}", e);
false
} else {
true
}
};

let send_initialized = {
if let Err(e) = hs.initialize_hook(SEND_HOOK_SIG.into(), hook::Direction::Send) {
error!("Could not auto-initialize the send hook: {}", e);
false
} else {
true
}
};
let (r, s, sl) = auto_initialize_hooks(&hs);

deucalion_server
.set_hook_state(recv_initialized, send_initialized)
.await;
deucalion_server.set_hook_status(r, s, sl).await;
info!("Hooks initialized.");

// Clone references to hook state and server state so that they can
Expand Down
Loading

0 comments on commit 9df6a3b

Please sign in to comment.