1#![allow(clippy::not_unsafe_ptr_arg_deref)]
2
3use std::net::Ipv4Addr;
4
5use bitflags::bitflags;
6use color_eyre::eyre::bail;
7use num_derive::FromPrimitive;
8use num_derive::ToPrimitive;
9use serde::{Deserialize, Serialize};
10use tracing::*;
11
12#[derive(Clone, Debug, Default, Serialize, Deserialize)]
14pub struct MumbleLink {
15 pub ui_tick: u32,
16 pub f_avatar_position: [f32; 3],
17 pub f_avatar_front: [f32; 3],
18 pub f_camera_position: [f32; 3],
19 pub f_camera_front: [f32; 3],
20 pub identity: CIdentity,
21 pub context: CMumbleContext,
22}
23
24impl MumbleLink {
25 pub fn update(&mut self, link_ptr: *const CMumbleLink) -> Result<(), MumbleUpdateError> {
28 let cmlink = match unsafe { link_ptr.as_ref() } {
29 Some(cmlink) => cmlink,
30 None => return Err(MumbleUpdateError::CMLinkPtrAsRefError),
31 };
32
33 if self.ui_tick != cmlink.ui_tick {
34 self.ui_tick = cmlink.ui_tick;
35
36 self.f_avatar_position = cmlink.f_avatar_position;
37
38 self.f_avatar_front = cmlink.f_avatar_front;
39
40 self.f_camera_position = cmlink.f_camera_position;
41
42 self.f_camera_front = cmlink.f_camera_front;
43 self.identity.update(link_ptr)?;
44 self.context.update(link_ptr);
45 }
46
47 Ok(())
48 }
49 pub fn update_from_slice(&mut self, buffer: &[u8]) -> Result<(), MumbleUpdateError> {
50 assert!(buffer.len() >= 1093);
51 self.update(buffer.as_ptr() as *const CMumbleLink)
52 }
53}
54
55#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
56#[repr(C)]
57pub struct CMumbleContext {
61 pub server_address: [u8; 28], pub map_id: u32,
64 pub map_type: u32,
65 pub shard_id: u32,
66 pub instance: u32,
67 pub build_id: u32,
68 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,
78 pub process_id: u32,
81 pub mount_index: u8,
83}
84#[derive(Debug, FromPrimitive, ToPrimitive, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
86pub enum Mount {
87 None = 0,
88 Jackal = 1,
89 Griffon = 2,
90 Springer = 3,
91 Skimmer = 4,
92 Raptor = 5,
93 RollerBeetle = 6,
94 Warclaw = 7,
95 Skyscale = 8,
96}
97impl Default for Mount {
98 fn default() -> Self {
99 Self::None
100 }
101}
102
103bitflags! {
104 #[derive(Default, Serialize, Deserialize)]
106 pub struct UIState: u32 {
107 const IS_MAP_OPEN = 0b00000001;
108 const IS_COMPASS_TOP_RIGHT = 0b00000010;
109 const DOES_COMPASS_HAVE_ROTATION_ENABLED = 0b00000100;
110 const GAME_HAS_FOCUS = 0b00001000;
111 const IN_COMPETITIVE_GAMEMODE = 0b00010000;
112 const TEXTBOX_FOCUS = 0b00100000;
113 const IS_IN_COMBAT = 0b01000000;
114 }
115}
116impl CMumbleContext {
117 pub fn get_ui_state(&self) -> Option<UIState> {
118 UIState::from_bits(self.ui_state)
119 }
120
121 pub fn update(&mut self, link_ptr: *const CMumbleLink) {
122 let mc = unsafe {
123 std::ptr::read_volatile(&(*link_ptr).context as *const u8 as *const CMumbleContext)
124 };
125 *self = mc;
126 }
127 pub fn get_map_ip(&self) -> color_eyre::Result<Ipv4Addr> {
130 if self.server_address[0] != 2 {
131 bail!("ipaddr parsing failed for CMumble Context");
132 }
133
134 let ip = Ipv4Addr::from([
135 self.server_address[4],
136 self.server_address[5],
137 self.server_address[6],
138 self.server_address[7],
139 ]);
140 Ok(ip)
141 }
142
143 pub fn get_mount(&self) -> Option<Mount> {
144 use num_traits::FromPrimitive;
145 Mount::from_u8(self.mount_index)
146 }
147}
148
149#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
150pub struct CIdentity {
155 pub name: String,
157 pub profession: u32,
159 pub spec: u32,
161 pub race: u32,
163 pub map_id: u32,
165 pub world_id: u32,
167 pub team_color_id: u32,
169 pub commander: bool,
171 pub fov: f32,
173 pub uisz: u32,
175}
176
177#[derive(
179 Debug,
180 Clone,
181 Copy,
182 PartialEq,
183 Eq,
184 PartialOrd,
185 Ord,
186 Hash,
187 Serialize,
188 Deserialize,
189 FromPrimitive,
190 ToPrimitive,
191)]
192pub enum UISize {
193 Small = 0,
194 Normal = 1,
195 Large = 2,
196 Larger = 3,
197}
198
199#[derive(
201 Debug,
202 Clone,
203 Copy,
204 PartialEq,
205 Eq,
206 PartialOrd,
207 Ord,
208 Hash,
209 Serialize,
210 Deserialize,
211 FromPrimitive,
212 ToPrimitive,
213)]
214pub enum Race {
215 Asura = 0,
216 Charr = 1,
217 Human = 2,
218 Norn = 3,
219 Sylvari = 4,
220}
221impl CIdentity {
222 pub fn get_uisz(&self) -> Option<UISize> {
223 use num_traits::FromPrimitive;
224 UISize::from_u32(self.uisz)
225 }
226 pub fn get_race(&self) -> Option<Race> {
227 use num_traits::FromPrimitive;
228 Race::from_u32(self.uisz)
229 }
230 pub fn update(&mut self, link_ptr: *const CMumbleLink) -> Result<(), MumbleIdentityError> {
231 use widestring::U16CStr;
232 let id = U16CStr::from_slice_truncate(unsafe { &(*link_ptr).identity })?;
233 let id = id.to_string()?;
234 *self = serde_json::from_str::<CIdentity>(&id)?;
235 Ok(())
236 }
237}
238
239pub const C_MUMBLE_LINK_SIZE: usize = std::mem::size_of::<CMumbleLink>();
241pub const USEFUL_C_MUMBLE_LINK_SIZE: usize = 1193;
243
244#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
247#[repr(C)]
248pub struct CMumbleLink {
249 pub ui_version: u32,
251 pub ui_tick: u32,
253 pub f_avatar_position: [f32; 3],
254 pub f_avatar_front: [f32; 3],
255 pub f_avatar_top: [f32; 3],
256 pub name: [u8; 512],
257 pub f_camera_position: [f32; 3],
258 pub f_camera_front: [f32; 3],
259 pub f_camera_top: [f32; 3],
260 pub identity: [u16; 256],
261 pub context_len: u32,
262 pub context: [u8; 256],
263 pub description: [u8; 4096],
264}
265impl CMumbleLink {
266 pub fn get_cmumble_link(link_ptr: *const CMumbleLink) -> CMumbleLink {
268 unsafe { std::ptr::read_volatile(link_ptr) }
269 }
270
271 pub fn is_valid(link_ptr: *const CMumbleLink) -> bool {
273 unsafe { (*link_ptr).ui_tick > 0 }
274 }
275
276 pub fn get_ui_tick(link_ptr: *const CMumbleLink) -> u32 {
278 unsafe { (*link_ptr).ui_tick }
279 }
280 pub fn get_pid(link_ptr: *const CMumbleLink) -> u32 {
281 unsafe { (*(link_ptr as *const u8 as *const CMumbleContext)).process_id }
282 }
283 pub fn copy_raw_bytes_into(link_ptr: *const CMumbleLink, buffer: &mut [u8]) {
290 let max_len = usize::min(buffer.len(), C_MUMBLE_LINK_SIZE);
291 unsafe {
292 std::ptr::copy_nonoverlapping(link_ptr as *const u8, buffer.as_mut_ptr(), max_len);
293 }
294 }
295}
296
297#[derive(Debug, thiserror::Error)]
298pub enum MumbleUpdateError {
299 #[error("Mumble Identity error")]
300 MumbleIdentityError(#[from] MumbleIdentityError),
301 #[error("link_ptr.as_ref returned None when trying to update MumbleLink. ptr is null. something is very wrong")]
302 CMLinkPtrAsRefError,
303}
304
305#[derive(Debug, thiserror::Error)]
306pub enum MumbleIdentityError {
307 #[error("Mumble Identity String missing null terminator error")]
308 U16CStrMissingNullTerminator(#[from] widestring::error::MissingNulTerminator),
309 #[error("Mumble Identity String is not valid utf-8")]
310 Utf16To8Error(#[from] widestring::error::Utf16Error),
311 #[error("Mumble Identity is not valid json")]
312 JsonError(#[from] serde_json::Error),
313}