#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::net::Ipv4Addr;
use bitflags::bitflags;
use color_eyre::eyre::bail;
use num_derive::FromPrimitive;
use num_derive::ToPrimitive;
use serde::{Deserialize, Serialize};
use tracing::*;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct MumbleLink {
pub ui_tick: u32,
pub f_avatar_position: [f32; 3],
pub f_avatar_front: [f32; 3],
pub f_camera_position: [f32; 3],
pub f_camera_front: [f32; 3],
pub identity: CIdentity,
pub context: CMumbleContext,
}
impl MumbleLink {
pub fn update(&mut self, link_ptr: *const CMumbleLink) -> Result<(), MumbleUpdateError> {
let cmlink = match unsafe { link_ptr.as_ref() } {
Some(cmlink) => cmlink,
None => return Err(MumbleUpdateError::CMLinkPtrAsRefError),
};
if self.ui_tick != cmlink.ui_tick {
self.ui_tick = cmlink.ui_tick;
self.f_avatar_position = cmlink.f_avatar_position;
self.f_avatar_front = cmlink.f_avatar_front;
self.f_camera_position = cmlink.f_camera_position;
self.f_camera_front = cmlink.f_camera_front;
self.identity.update(link_ptr)?;
self.context.update(link_ptr);
}
Ok(())
}
pub fn update_from_slice(&mut self, buffer: &[u8]) -> Result<(), MumbleUpdateError> {
assert!(buffer.len() >= 1093);
self.update(buffer.as_ptr() as *const CMumbleLink)
}
}
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub struct CMumbleContext {
pub server_address: [u8; 28], pub map_id: u32,
pub map_type: u32,
pub shard_id: u32,
pub instance: u32,
pub build_id: u32,
pub ui_state: u32, pub compass_width: u16, pub compass_height: u16, pub compass_rotation: f32, pub player_x: f32, pub player_y: f32, pub map_center_x: f32, pub map_center_y: f32, pub map_scale: f32,
pub process_id: u32,
pub mount_index: u8,
}
#[derive(Debug, FromPrimitive, ToPrimitive, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mount {
None = 0,
Jackal = 1,
Griffon = 2,
Springer = 3,
Skimmer = 4,
Raptor = 5,
RollerBeetle = 6,
Warclaw = 7,
Skyscale = 8,
}
impl Default for Mount {
fn default() -> Self {
Self::None
}
}
bitflags! {
#[derive(Default, Serialize, Deserialize)]
pub struct UIState: u32 {
const IS_MAP_OPEN = 0b00000001;
const IS_COMPASS_TOP_RIGHT = 0b00000010;
const DOES_COMPASS_HAVE_ROTATION_ENABLED = 0b00000100;
const GAME_HAS_FOCUS = 0b00001000;
const IN_COMPETITIVE_GAMEMODE = 0b00010000;
const TEXTBOX_FOCUS = 0b00100000;
const IS_IN_COMBAT = 0b01000000;
}
}
impl CMumbleContext {
pub fn get_ui_state(&self) -> Option<UIState> {
UIState::from_bits(self.ui_state)
}
pub fn update(&mut self, link_ptr: *const CMumbleLink) {
let mc = unsafe {
std::ptr::read_volatile(&(*link_ptr).context as *const u8 as *const CMumbleContext)
};
*self = mc;
}
pub fn get_map_ip(&self) -> color_eyre::Result<Ipv4Addr> {
if self.server_address[0] != 2 {
bail!("ipaddr parsing failed for CMumble Context");
}
let ip = Ipv4Addr::from([
self.server_address[4],
self.server_address[5],
self.server_address[6],
self.server_address[7],
]);
Ok(ip)
}
pub fn get_mount(&self) -> Option<Mount> {
use num_traits::FromPrimitive;
Mount::from_u8(self.mount_index)
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
pub struct CIdentity {
pub name: String,
pub profession: u32,
pub spec: u32,
pub race: u32,
pub map_id: u32,
pub world_id: u32,
pub team_color_id: u32,
pub commander: bool,
pub fov: f32,
pub uisz: u32,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
FromPrimitive,
ToPrimitive,
)]
pub enum UISize {
Small = 0,
Normal = 1,
Large = 2,
Larger = 3,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
FromPrimitive,
ToPrimitive,
)]
pub enum Race {
Asura = 0,
Charr = 1,
Human = 2,
Norn = 3,
Sylvari = 4,
}
impl CIdentity {
pub fn get_uisz(&self) -> Option<UISize> {
use num_traits::FromPrimitive;
UISize::from_u32(self.uisz)
}
pub fn get_race(&self) -> Option<Race> {
use num_traits::FromPrimitive;
Race::from_u32(self.uisz)
}
pub fn update(&mut self, link_ptr: *const CMumbleLink) -> Result<(), MumbleIdentityError> {
use widestring::U16CStr;
let id = U16CStr::from_slice_truncate(unsafe { &(*link_ptr).identity })?;
let id = id.to_string()?;
*self = serde_json::from_str::<CIdentity>(&id)?;
Ok(())
}
}
pub const C_MUMBLE_LINK_SIZE: usize = std::mem::size_of::<CMumbleLink>();
pub const USEFUL_C_MUMBLE_LINK_SIZE: usize = 1193;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(C)]
pub struct CMumbleLink {
pub ui_version: u32,
pub ui_tick: u32,
pub f_avatar_position: [f32; 3],
pub f_avatar_front: [f32; 3],
pub f_avatar_top: [f32; 3],
pub name: [u8; 512],
pub f_camera_position: [f32; 3],
pub f_camera_front: [f32; 3],
pub f_camera_top: [f32; 3],
pub identity: [u16; 256],
pub context_len: u32,
pub context: [u8; 256],
pub description: [u8; 4096],
}
impl CMumbleLink {
pub fn get_cmumble_link(link_ptr: *const CMumbleLink) -> CMumbleLink {
unsafe { std::ptr::read_volatile(link_ptr) }
}
pub fn is_valid(link_ptr: *const CMumbleLink) -> bool {
unsafe { (*link_ptr).ui_tick > 0 }
}
pub fn get_ui_tick(link_ptr: *const CMumbleLink) -> u32 {
unsafe { (*link_ptr).ui_tick }
}
pub fn get_pid(link_ptr: *const CMumbleLink) -> u32 {
unsafe { (*(link_ptr as *const u8 as *const CMumbleContext)).process_id }
}
pub fn copy_raw_bytes_into(link_ptr: *const CMumbleLink, buffer: &mut [u8]) {
let max_len = usize::min(buffer.len(), C_MUMBLE_LINK_SIZE);
unsafe {
std::ptr::copy_nonoverlapping(link_ptr as *const u8, buffer.as_mut_ptr(), max_len);
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum MumbleUpdateError {
#[error("Mumble Identity error")]
MumbleIdentityError(#[from] MumbleIdentityError),
#[error("link_ptr.as_ref returned None when trying to update MumbleLink. ptr is null. something is very wrong")]
CMLinkPtrAsRefError,
}
#[derive(Debug, thiserror::Error)]
pub enum MumbleIdentityError {
#[error("Mumble Identity String missing null terminator error")]
U16CStrMissingNullTerminator(#[from] widestring::error::MissingNulTerminator),
#[error("Mumble Identity String is not valid utf-8")]
Utf16To8Error(#[from] widestring::error::Utf16Error),
#[error("Mumble Identity is not valid json")]
JsonError(#[from] serde_json::Error),
}