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