1use std::path::Path;
2
3use super::stack::ArmaContextStackTrace;
4
5#[repr(C)]
6struct RawArmaCallContext {
7 pub steam_id: *const u64,
8 pub source: *const libc::c_char,
9 pub mission: *const libc::c_char,
10 pub server: *const libc::c_char,
11 pub remote_exec_owner: i16,
12 pub call_stack: Option<*const super::stack::RawContextStackTrace>,
13}
14
15impl RawArmaCallContext {
16 fn from_arma(args: *mut *mut i8, count: libc::c_int) -> Self {
17 let steam_id = unsafe { *args.offset(0) as *const u64 };
18 let source = unsafe { *args.offset(1) as *const libc::c_char };
19 let mission = unsafe { *args.offset(2) as *const libc::c_char };
20 let server = unsafe { *args.offset(3) as *const libc::c_char };
21 let remote_exec_owner = unsafe { *args.offset(4) as i16 };
22
23 let call_stack = if count > 5 {
24 let stack = unsafe { *args.offset(5) as *const super::stack::RawContextStackTrace };
25 Some(stack)
26 } else {
27 None
28 };
29
30 Self {
31 steam_id,
32 source,
33 mission,
34 server,
35 remote_exec_owner,
36 call_stack,
37 }
38 }
39}
40
41pub trait StackRequest {}
42
43pub struct WithStackTrace;
44impl StackRequest for WithStackTrace {}
45
46pub struct WithoutStackTrace;
47impl StackRequest for WithoutStackTrace {}
48
49pub type CallContext = ArmaCallContext<WithoutStackTrace>;
51pub type CallContextStackTrace = ArmaCallContext<WithStackTrace>;
53
54#[derive(Clone, Default)]
55pub struct ArmaCallContext<T: StackRequest> {
57 pub(super) caller: Caller,
58 pub(super) source: Source,
59 pub(super) mission: Mission,
60 pub(super) server: Server,
61 pub(super) remote_exec_owner: i16,
62
63 _stack_marker: std::marker::PhantomData<T>,
64 stack: Option<ArmaContextStackTrace>,
65}
66
67impl<T: StackRequest> ArmaCallContext<T> {
68 pub(crate) const fn new(
69 caller: Caller,
70 source: Source,
71 mission: Mission,
72 server: Server,
73 remote_exec_owner: i16,
74 ) -> Self {
75 Self {
76 caller,
77 source,
78 mission,
79 server,
80 remote_exec_owner,
81
82 _stack_marker: std::marker::PhantomData,
83 stack: None,
84 }
85 }
86
87 pub fn from_arma(args: *mut *mut i8, count: libc::c_int) -> Self {
89 let raw = RawArmaCallContext::from_arma(args, count);
90 Self {
91 caller: Caller::Steam(unsafe { *raw.steam_id }),
92 source: Source::from(unsafe { std::ffi::CStr::from_ptr(raw.source).to_str().unwrap() }),
93 mission: Mission::from(unsafe {
94 std::ffi::CStr::from_ptr(raw.mission).to_str().unwrap()
95 }),
96 server: Server::from(unsafe { std::ffi::CStr::from_ptr(raw.server).to_str().unwrap() }),
97 remote_exec_owner: raw.remote_exec_owner,
98
99 _stack_marker: std::marker::PhantomData,
100 stack: raw.call_stack.map(ArmaContextStackTrace::from),
101 }
102 }
103
104 #[must_use]
105 pub fn caller(&self) -> &Caller {
109 &self.caller
110 }
111
112 #[must_use]
113 pub fn source(&self) -> &Source {
115 &self.source
116 }
117
118 #[must_use]
119 pub fn mission(&self) -> &Mission {
123 &self.mission
124 }
125
126 #[must_use]
127 pub fn server(&self) -> &Server {
129 &self.server
130 }
131
132 #[must_use]
133 pub fn remote_exec_owner(&self) -> i16 {
135 self.remote_exec_owner
136 }
137}
138
139impl ArmaCallContext<WithStackTrace> {
140 #[must_use]
141 pub fn stack_trace(&self) -> &ArmaContextStackTrace {
143 self.stack.as_ref().expect("Stack is missing")
145 }
146
147 pub(crate) fn into_without_stack(self) -> ArmaCallContext<WithoutStackTrace> {
149 ArmaCallContext::new(
150 self.caller,
151 self.source,
152 self.mission,
153 self.server,
154 self.remote_exec_owner,
155 )
156 }
157}
158
159#[derive(Debug, Clone, Default, PartialEq, Eq)]
161pub enum Caller {
162 Steam(u64),
164 #[default]
165 Unknown,
167}
168
169impl Caller {
170 pub fn as_str(&self) -> String {
172 match self {
173 Self::Steam(id) => id.to_string(),
174 Self::Unknown => "0".to_string(),
175 }
176 }
177
178 pub fn as_u64(&self) -> u64 {
180 match self {
181 Self::Steam(id) => *id,
182 Self::Unknown => 0,
183 }
184 }
185}
186
187impl From<&str> for Caller {
188 fn from(s: &str) -> Self {
189 if s.is_empty() || s == "0" {
190 Self::Unknown
191 } else {
192 s.parse::<u64>().map_or(Self::Unknown, Self::Steam)
193 }
194 }
195}
196
197impl From<u64> for Caller {
198 fn from(id: u64) -> Self {
199 if id == 0 {
200 Self::Unknown
201 } else {
202 Self::Steam(id)
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default, PartialEq, Eq)]
209pub enum Source {
210 File(String),
213 Pbo(String),
216 #[default]
217 Console,
219}
220
221impl Source {
222 pub fn as_str(&self) -> &str {
224 match self {
225 Self::File(s) | Self::Pbo(s) => s,
226 Self::Console => "",
227 }
228 }
229}
230
231impl From<&str> for Source {
232 fn from(s: &str) -> Self {
233 if s.is_empty() {
234 Self::Console
235 } else if Path::new(s).is_absolute() {
236 Self::File(s.to_string())
237 } else {
238 Self::Pbo(s.to_string())
239 }
240 }
241}
242
243impl From<*const libc::c_char> for Source {
244 #[allow(clippy::not_unsafe_ptr_arg_deref)]
245 fn from(s: *const libc::c_char) -> Self {
246 Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
247 }
248}
249
250#[derive(Debug, Clone, Default, PartialEq, Eq)]
252pub enum Mission {
253 Mission(String),
255 #[default]
256 None,
258}
259
260impl Mission {
261 pub fn as_str(&self) -> &str {
263 match self {
264 Self::Mission(s) => s,
265 Self::None => "",
266 }
267 }
268}
269
270impl From<&str> for Mission {
271 fn from(s: &str) -> Self {
272 if s.is_empty() {
273 Self::None
274 } else {
275 Self::Mission(s.to_string())
276 }
277 }
278}
279
280impl From<*const libc::c_char> for Mission {
281 #[allow(clippy::not_unsafe_ptr_arg_deref)]
282 fn from(s: *const libc::c_char) -> Self {
283 Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
284 }
285}
286
287#[derive(Debug, Clone, Default, PartialEq, Eq)]
289pub enum Server {
290 Multiplayer(String),
292 #[default]
293 Singleplayer,
295}
296
297impl Server {
298 pub fn as_str(&self) -> &str {
300 match self {
301 Self::Multiplayer(s) => s,
302 Self::Singleplayer => "",
303 }
304 }
305}
306
307impl From<&str> for Server {
308 fn from(s: &str) -> Self {
309 if s.is_empty() {
310 Self::Singleplayer
311 } else {
312 Self::Multiplayer(s.to_string())
313 }
314 }
315}
316
317impl From<*const libc::c_char> for Server {
318 #[allow(clippy::not_unsafe_ptr_arg_deref)]
319 fn from(s: *const libc::c_char) -> Self {
320 Self::from(unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() })
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn caller_empty() {
330 assert_eq!(Caller::from(""), Caller::Unknown);
331 }
332
333 #[test]
334 fn caller_zero() {
335 assert_eq!(Caller::from("0"), Caller::Unknown);
336 }
337
338 #[test]
339 fn source_empty() {
340 assert_eq!(Source::from(""), Source::Console);
341 }
342
343 #[test]
344 fn source_pbo() {
345 let path = "x\\ctx\\addons\\main\\fn_armaContext.sqf";
346 assert_eq!(Source::from(path), Source::Pbo(path.to_string()));
347 }
348
349 #[test]
350 fn source_file() {
351 let path = env!("CARGO_MANIFEST_DIR");
352 assert_eq!(Source::from(path), Source::File(path.to_string()));
353 }
354
355 #[test]
356 fn mission_empty() {
357 assert_eq!(Mission::from(""), Mission::None);
358 }
359
360 #[test]
361 fn server_empty() {
362 assert_eq!(Server::from(""), Server::Singleplayer);
363 }
364}