iracing_telem/
lib.rs

1//! iracing-telem is a rust port of the iRacing provided c++ SDK.
2//!
3//! It allows for access to telemetetry data from a running instance of the iRacing simulator
4//! As well as the ability to send certain control messages to the simulator (e.g to change
5//! Pitstop settings)
6//!
7//! The iRacing data is exposed through a memory mapped file. Because of this, and the potential
8//! issue for the data to not be in the expected locations almost all methods are marked as unsafe.
9//!
10//! Details of the c++ SDK are available on the iRacing forums. Note you will need an active
11//! iRacing subsription to access these.
12//!
13//! <https://forums.iracing.com/discussion/62/iracing-sdk>
14use core::fmt;
15use encoding::all::WINDOWS_1252;
16use encoding::{DecoderTrap, Encoding};
17use std::cmp::Ordering;
18use std::ffi::{c_void, CStr};
19use std::os::raw::c_char;
20use std::rc::Rc;
21use std::time::{Duration, Instant};
22use std::{slice, thread};
23use windows::Win32::Foundation::{
24    CloseHandle, GetLastError, HANDLE, HWND, LPARAM, WIN32_ERROR, WPARAM,
25};
26use windows::Win32::System::{Memory, Threading};
27use windows::Win32::UI::WindowsAndMessaging::{RegisterWindowMessageA, SendNotifyMessageA};
28
29pub mod flags;
30
31const IRSDK_MAX_BUFS: usize = 4;
32const IRSDK_MAX_STRING: usize = 32;
33// descriptions can be longer than max_string!
34const IRSDK_MAX_DESC: usize = 64;
35
36// HWND_BROADCAST doesn't appear to be in windows crate?
37const HWND_BROADCAST: HWND = HWND(0xFFFF);
38
39/// define markers for unlimited session laps
40pub const IRSDK_UNLIMITED_LAPS: i32 = 32767;
41
42/// define markers for unlimited session time (in seconds)
43pub const IRSDK_UNLIMITED_TIME: f64 = 604800.0;
44
45/// Client is main entry point into the library. Create a client and then you can
46/// access Sessions that have the telemetry data in then.
47#[derive(Debug)]
48pub struct Client {
49    conn: Option<Rc<Connection>>,
50    // Incremented each time we issue a new session. Allows for session to determine its expired even if
51    // iRacing started a new session.
52    session_id: i32,
53}
54impl Client {
55    /// Creates a new Client
56    pub fn new() -> Client {
57        Client {
58            conn: None,
59            session_id: 0,
60        }
61    }
62    // Attempts to connect to iracing if we're not already.
63    // Returns true if we're now connected (or was already connected), false otherwise
64    unsafe fn connect(&mut self) -> bool {
65        match &self.conn {
66            Some(c) => c.connected(),
67            None => match Connection::new() {
68                Ok(c) => {
69                    let result = c.connected();
70                    self.conn = Some(Rc::new(c));
71                    result
72                }
73                Err(_) => false,
74            },
75        }
76    }
77    /// When iRacing is running, then a Session will be available that contains the
78    /// telemetry data. When iRacing is not running, then this returns None.
79    /// See also wait_for_session.
80    ///
81    /// # Safety
82    /// Creating a session requires dealing with memory mapped files and c strucutres
83    /// that are in them. A mismatch between our definition of the struct and iRacings
84    /// definition could cause chaos.
85    pub unsafe fn session(&mut self) -> Option<Session> {
86        if !self.connect() {
87            None
88        } else {
89            let sid = self.session_id;
90            self.session_id += 1;
91            let mut s = Session {
92                session_id: sid,
93                conn: self.conn.as_ref().unwrap().clone(),
94                last_tick_count: -2,
95                data: bytes::BytesMut::new(),
96                expired: false,
97            };
98            let d = s.wait_for_data(Duration::from_millis(16));
99            if DataUpdateResult::Updated == d {
100                Some(s)
101            } else {
102                None
103            }
104        }
105    }
106    /// Will wait upto the the supplied wait duration for a iRacing session to be available.
107    /// If the wait timeout's returns None, otherwise returns the new Session.
108    /// Wait must fit into a 32bit number of milliseconds, about 49 days. otherwise it'll panic.
109    ///
110    /// # Safety
111    /// Creating a session requires dealing with memory mapped files and c strucutres
112    /// that are in them. A mismatch between our definition of the struct and iRacings
113    /// definition could cause chaos.
114    pub unsafe fn wait_for_session(&mut self, wait: Duration) -> Option<Session> {
115        // If we had a prior connection then we can use the new data event to wait for the
116        // session. Otherwise we need to poll session fn above.
117        match &self.conn {
118            Some(c) => {
119                c.wait_for_new_data(wait);
120                self.session()
121            }
122            None => {
123                let start = Instant::now();
124                let loop_wait = Duration::from_millis(1000);
125                loop {
126                    let r = self.session();
127                    if r.is_some() || start.elapsed() > wait {
128                        return r;
129                    }
130                    // It takes iRacing a few seconds to get from when you can connect
131                    // to the telemetry to when you can actually do anything in the simulator
132                    // so a once a second check seems like plenty.
133                    thread::sleep(loop_wait);
134                }
135            }
136        }
137    }
138}
139impl Default for Client {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145/// The outcome of trying to read a row of telemetery data.
146#[derive(Debug, PartialEq)]
147pub enum DataUpdateResult {
148    /// The data was updated, and the new values are available via value & var_value
149    Updated,
150    /// There's no new data available, the previous data is still available.
151    NoUpdate,
152    /// We were unable to copy the row of data out of the iRacing buffer and into
153    /// our own. This is usually a timing issue, or an extremely slow CPU.
154    FailedToCopyRow,
155    /// When iRacing is closed, attempts to get new data will return SessionExpired.
156    SessionExpired,
157}
158
159/// A Session is used to access data from iRacing.
160///
161/// The data is split between metadata about the available data, and the data itself
162/// The metadata (returned in a Var) is valid for the life of the Session. The
163/// Var can be used to get the current value for that variable out of the last read
164/// row of data.
165///
166/// # Safety
167/// All the method in Session are marked as unsafe. They all ultimately interact with
168/// memory mapped data from iRacing in addition some use Win32 APIs as well.
169#[derive(Debug)]
170pub struct Session {
171    session_id: i32,
172    conn: Rc<Connection>,
173    last_tick_count: i32,
174    data: bytes::BytesMut,
175    expired: bool,
176}
177impl Session {
178    /// Is this session still connected to the iRacing data.
179    ///
180    /// # Safety
181    /// see details on Session
182    pub unsafe fn connected(&self) -> bool {
183        !self.expired()
184    }
185    /// Has this session expired? i.e. no longer connected to iRacing.
186    ///
187    /// # Safety
188    /// see details on Session
189    pub unsafe fn expired(&self) -> bool {
190        self.expired || (!self.conn.connected())
191    }
192    /// Waits for upto 'wait' amount of time for a new row of data to be available.
193    /// The wait value should not exceed an u32's worth of milliseconds, approx ~49 days
194    ///
195    /// # Safety
196    /// see details on Session
197    pub unsafe fn wait_for_data(&mut self, wait: Duration) -> DataUpdateResult {
198        let r = self.get_new_data();
199        if r == DataUpdateResult::NoUpdate {
200            self.conn.wait_for_new_data(wait);
201            self.get_new_data()
202        } else {
203            r
204        }
205    }
206    /// Attempt to get newer data from iRacing.
207    ///
208    /// # Safety
209    /// see details on Session
210    pub unsafe fn get_new_data(&mut self) -> DataUpdateResult {
211        if self.expired() {
212            self.expired = true;
213            return DataUpdateResult::SessionExpired;
214        }
215        let (buf_hdr, row) = self.conn.lastest();
216        match buf_hdr.tick_count.cmp(&self.last_tick_count) {
217            Ordering::Greater => {
218                for _tries in 0..2 {
219                    let curr_tick_count = buf_hdr.tick_count;
220                    self.data.clear();
221                    self.data.extend_from_slice(row);
222                    if curr_tick_count == buf_hdr.tick_count {
223                        self.last_tick_count = curr_tick_count;
224                        return DataUpdateResult::Updated;
225                    }
226                }
227                DataUpdateResult::FailedToCopyRow
228            }
229            Ordering::Less => {
230                // If ours is newer than the latest then iRacing has started a new
231                // session and this one is done.
232                self.expired = true;
233                DataUpdateResult::SessionExpired
234            }
235            Ordering::Equal => DataUpdateResult::NoUpdate,
236        }
237    }
238    /// dump_vars is for diagnostics and investigation, it writes out all the
239    /// variables definitions and their current value.
240    ///
241    /// # Safety
242    /// see details on Session
243    pub unsafe fn dump_vars(&self) {
244        for var_header in self.conn.variables() {
245            let var = Var {
246                hdr: *var_header,
247                session_id: self.session_id,
248            };
249            let value = self.var_value(&var);
250            println!(
251                "{:40} {:32}: {:?}: {}: {}: {:?}",
252                var.desc(),
253                var.name(),
254                var.var_type(),
255                var.count(),
256                var.hdr.count_as_time,
257                value,
258            );
259        }
260    }
261    /// find_var will look for an iracing data point/variable with
262    /// the supplied name (case sensitive). None is returned if its
263    /// unable to find a matching item. The return Var is only
264    /// valid for use with the Session that created it.
265    ///
266    /// # Safety
267    /// see details on Session
268    pub unsafe fn find_var(&self, name: &str) -> Option<Var> {
269        for var_header in self.conn.variables() {
270            if var_header.has_name(name) {
271                return Some(Var {
272                    hdr: *var_header,
273                    session_id: self.session_id,
274                });
275            }
276        }
277        None
278    }
279    /// return the value of the supplied variable as of the most recently fetched row of data.
280    /// this will panic if you pass a Var instance generated by a different Session instance.
281    ///
282    /// # Safety
283    /// see details on Session
284    pub unsafe fn var_value(&self, var: &Var) -> Value {
285        assert_eq!(
286            var.session_id, self.session_id,
287            "programmer error, Var was issued by a different Session"
288        );
289        // verify that value is inside the buffer
290        let var_offset = var.hdr.offset as usize;
291        assert!(
292            var.size() + var_offset <= self.data.len(),
293            "The value appears to be outside the buffer"
294        );
295
296        let x = self.data.as_ptr().add(var_offset);
297        if var.hdr.count == 1 {
298            match var.hdr.var_type {
299                VarType::Char => Value::Char(std::ptr::read_unaligned(x as *const u8)),
300                VarType::Bool => Value::Bool(std::ptr::read_unaligned(x as *const bool)),
301                VarType::Int => Value::Int(std::ptr::read_unaligned(x as *const i32)),
302                VarType::Bitfield => Value::Bitfield(std::ptr::read_unaligned(x as *const i32)),
303                VarType::Float => Value::Float(std::ptr::read_unaligned(x as *const f32)),
304                VarType::Double => Value::Double(std::ptr::read_unaligned(x as *const f64)),
305                _ => todo!(), // ETCount
306            }
307        } else {
308            let l = var.count();
309            match var.hdr.var_type {
310                VarType::Char => Value::Chars(slice::from_raw_parts(x, l)),
311                VarType::Bool => Value::Bools(slice::from_raw_parts(x as *const bool, l)),
312                VarType::Int => Value::Ints(slice::from_raw_parts(x as *const i32, l)),
313                VarType::Bitfield => Value::Bitfields(slice::from_raw_parts(x as *const i32, l)),
314                VarType::Float => Value::Floats(slice::from_raw_parts(x as *const f32, l)),
315                VarType::Double => Value::Doubles(slice::from_raw_parts(x as *const f64, l)),
316                _ => todo!(), // ETCount
317            }
318        }
319    }
320    /// Read the value of the supplied variable, and convert it to the relevent rust type. The
321    /// rust type can be a primitive such as i32,f32,f64 or one of the bitfield/enums defined
322    /// in the flags package.
323    ///
324    /// # Safety
325    /// see details on Session
326    pub unsafe fn value<'a, T>(&'a self, var: &Var) -> Result<T, T::Error>
327    where
328        T: TryFrom<Value<'a>, Error = Error>,
329    {
330        let v = self.var_value(var);
331        v.try_into()
332    }
333    /// iRacing has a second set of data called session_info that changes at a much slower rate.
334    /// session_info_update is incremented each time the session_info data is updated.
335    ///
336    /// # Safety
337    /// see details on Session
338    pub unsafe fn session_info_update(&self) -> i32 {
339        (*self.conn.header).session_info_update
340    }
341    /// Returns the current Session info string. This is a Yaml formatted string that you'll
342    /// need to parse.
343    ///
344    /// # Safety
345    /// see details on Session
346    pub unsafe fn session_info(&self) -> String {
347        let bytes = self.conn.session_info();
348        // as we're using replace, this should not ever return an error
349        WINDOWS_1252.decode(bytes, DecoderTrap::Replace).unwrap()
350    }
351
352    /// A number of things can be controlled in iRacing remotely via broadcast messages.
353    /// This will send the supplied message. There is no way to determine that iRacing has
354    /// actually acted on the message. Many of the messages only work when the simulator
355    /// is in a specific state.
356    ///
357    /// # Safety
358    /// Unlike the rest of Session, this doesn't interact with the memory mapped data,but
359    /// it does use Win32 calls to broadcast the message to iRacing.
360    pub unsafe fn broadcast_msg(&self, msg: flags::BroadcastMsg) -> Result<(), WIN32_ERROR> {
361        let (cmd_msg_id, (var1, var2)) = msg.params();
362        let x = makelong(cmd_msg_id, var1);
363        let r = SendNotifyMessageA(
364            HWND_BROADCAST,
365            self.conn.broadcast_msg_id,
366            WPARAM(x as usize),
367            LPARAM(var2),
368        );
369        if r.as_bool() {
370            Ok(())
371        } else {
372            Err(GetLastError())
373        }
374    }
375}
376fn makelong(var1: i16, var2: i16) -> isize {
377    // aka MAKELONG
378    let x = ((var1 as u32) & 0xFFFF) | (((var2 as u32) & 0xFFFF) << 16);
379    x as isize
380}
381
382/// Var is a handle to a variable or telemetry data point.
383///
384/// Var's are obtained via the find_var() method on Session, and are then
385/// valid for the lifetime of that Session. They can only be used with the
386/// Session that generated them.
387/// In addition the metadata available from Var, Var is also used as a key
388/// to read the current value for the item.
389pub struct Var {
390    hdr: IrsdkVarHeader,
391    session_id: i32,
392}
393impl Var {
394    /// returns the data type of this item, e.g. Float, Int
395    pub fn var_type(&self) -> VarType {
396        self.hdr.var_type
397    }
398    pub fn name(&self) -> &str {
399        self.hdr.name().unwrap()
400    }
401    pub fn desc(&self) -> &str {
402        self.hdr.desc().unwrap()
403    }
404    pub fn unit(&self) -> &str {
405        self.hdr.unit().unwrap()
406    }
407    /// returns the count. This indicates how many instances of the datapoint value
408    /// are associated with this variable. Typically its one, but there is a small
409    /// subset of points that have more, typically 64 (i.e. one per driver).
410    /// Values for Var's with a count > 1 are returned as slices of the relevent type
411    pub fn count(&self) -> usize {
412        self.hdr.count as usize
413    }
414    /// returns the size of a value for this Var in bytes.
415    fn size(&self) -> usize {
416        self.var_type().size() * self.count()
417    }
418}
419impl fmt::Debug for Var {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        write!(f, "{} {:?} ({})", self.name(), self.var_type(), self.desc())
422    }
423}
424
425/// Connection is a wrapper around the mechanics of opening the iRacing generated memory mapped file
426/// that all the data comes from. Its mostly dealing with win32 shenanigans.
427///
428/// The data returned out of Connection is typically directly mapped into the underlying memory mapped data.
429#[derive(Debug)]
430struct Connection {
431    file_mapping: HANDLE,
432    shared_mem: *mut c_void,
433    header: *mut IrsdkHeader,
434    new_data: HANDLE,
435    broadcast_msg_id: u32,
436}
437impl Connection {
438    // This will return an error if iRacing is not running.
439    // However once you have a Connection it will remain usable even if iracing is exited and started again.
440    unsafe fn new() -> Result<Self, WIN32_ERROR> {
441        let file_mapping =
442            Memory::OpenFileMappingA(Memory::FILE_MAP_READ.0, false, "Local\\IRSDKMemMapFileName");
443        if file_mapping.is_invalid() {
444            return Err(GetLastError());
445        }
446        let shared_mem = Memory::MapViewOfFile(file_mapping, Memory::FILE_MAP_READ, 0, 0, 0);
447        if shared_mem.is_null() {
448            let e = Err(GetLastError());
449            CloseHandle(file_mapping);
450            return e;
451        }
452        let new_data = Threading::OpenEventA(
453            windows::Win32::Storage::FileSystem::SYNCHRONIZE.0,
454            false,
455            "Local\\IRSDKDataValidEvent",
456        );
457        if new_data.is_invalid() {
458            let e = Err(GetLastError());
459            Memory::UnmapViewOfFile(shared_mem);
460            CloseHandle(file_mapping);
461            return e;
462        }
463        let bc_id = RegisterWindowMessageA("IRSDK_BROADCASTMSG");
464        if bc_id == 0 {
465            let e = Err(GetLastError());
466            CloseHandle(new_data);
467            Memory::UnmapViewOfFile(shared_mem);
468            CloseHandle(file_mapping);
469            return e;
470        }
471        Ok(Connection {
472            file_mapping,
473            shared_mem,
474            header: shared_mem as *mut IrsdkHeader,
475            new_data,
476            broadcast_msg_id: bc_id,
477        })
478    }
479    unsafe fn connected(&self) -> bool {
480        (*self.header)
481            .status
482            .intersects(flags::StatusField::CONNECTED)
483    }
484    unsafe fn variables(&self) -> &[IrsdkVarHeader] {
485        let vhbase = self
486            .shared_mem
487            .add((*self.header).var_header_offset as usize)
488            as *const IrsdkVarHeader;
489        slice::from_raw_parts(vhbase, (*self.header).num_vars as usize)
490    }
491    unsafe fn buffers(&self) -> &[IrsdkBuf] {
492        let l = (*self.header).num_buf as usize;
493        assert!(l <= IRSDK_MAX_BUFS);
494        &(*self.header).var_buf[..l]
495    }
496    // returns the telemetry buffer with the highest tick count, along with the actual data
497    // this is the buffer in the shared mem, so you should copy it.
498    unsafe fn lastest(&self) -> (&IrsdkBuf, &[u8]) {
499        let b = self.buffers();
500        let mut latest = &b[0];
501        for buff in b {
502            if buff.tick_count > latest.tick_count {
503                latest = buff;
504            }
505        }
506        let buf_len = (*self.header).buf_len as usize;
507        let src = self.shared_mem.add(latest.buf_offset as usize);
508        return (latest, slice::from_raw_parts(src as *const u8, buf_len));
509    }
510    unsafe fn wait_for_new_data(&self, wait: Duration) {
511        Threading::WaitForSingleObject(self.new_data, wait.as_millis().try_into().unwrap());
512    }
513    unsafe fn session_info(&self) -> &[u8] {
514        let p = self
515            .shared_mem
516            .add((*self.header).session_info_offset as usize) as *const u8;
517        let mut bytes = std::slice::from_raw_parts(p, (*self.header).session_info_len as usize);
518        // session_info_len is the size of the buffer, not necessarily the size of the string
519        // so we have to look for the null terminatior.
520        for i in 0..bytes.len() {
521            if bytes[i] == 0 {
522                bytes = &bytes[0..i];
523                break;
524            }
525        }
526        bytes
527    }
528}
529impl Drop for Connection {
530    fn drop(&mut self) {
531        unsafe {
532            CloseHandle(self.new_data);
533            Memory::UnmapViewOfFile(self.shared_mem);
534            windows::Win32::Foundation::CloseHandle(self.file_mapping);
535        }
536    }
537}
538
539#[repr(C)]
540#[derive(Debug)]
541struct IrsdkBuf {
542    tick_count: i32, // used to detect changes in data
543    buf_offset: i32, // offset from header
544    pad: [i32; 2],   // (16 byte align)
545}
546
547#[repr(C)]
548#[derive(Debug)]
549struct IrsdkHeader {
550    ver: i32,                   // this api header version, see IRSDK_VER
551    status: flags::StatusField, // bitfield using irsdk_StatusField
552    tick_rate: i32,             // ticks per second (60 or 360 etc)
553
554    // session information, updated periodicaly
555    session_info_update: i32, // Incremented when session info changes
556    session_info_len: i32,    // Length in bytes of session info string
557    session_info_offset: i32, // Session info, encoded in YAML format
558
559    // State data, output at tickRate
560    num_vars: i32,          // length of array pointed to by varHeaderOffset
561    var_header_offset: i32, // offset to irsdk_varHeader[numVars] array, Describes the variables received in varBuf
562
563    num_buf: i32,                        // <= IRSDK_MAX_BUFS (3 for now)
564    buf_len: i32,                        // length in bytes for one line
565    pad1: [i32; 2],                      // (16 byte align)
566    var_buf: [IrsdkBuf; IRSDK_MAX_BUFS], // buffers of data being written to
567}
568
569#[repr(C)]
570#[derive(Clone, Copy, Debug)]
571struct IrsdkVarHeader {
572    var_type: VarType, // irsdk_VarType
573    offset: i32,       // offset fron start of buffer row
574    count: i32, // number of entrys (array) so length in bytes would be irsdk_VarTypeBytes[type] * count
575
576    count_as_time: u8,
577    pad: [i8; 3], // (16 byte align)
578
579    name: [u8; IRSDK_MAX_STRING],
580    desc: [u8; IRSDK_MAX_DESC],
581    unit: [u8; IRSDK_MAX_STRING], // something like "kg/m^2"
582}
583impl IrsdkVarHeader {
584    fn name(&self) -> Result<&str, std::str::Utf8Error> {
585        unsafe { CStr::from_ptr(self.name.as_ptr() as *const c_char).to_str() }
586    }
587    fn desc(&self) -> Result<&str, std::str::Utf8Error> {
588        unsafe { CStr::from_ptr(self.desc.as_ptr() as *const c_char).to_str() }
589    }
590    fn unit(&self) -> Result<&str, std::str::Utf8Error> {
591        unsafe { CStr::from_ptr(self.unit.as_ptr() as *const c_char).to_str() }
592    }
593    fn has_name(&self, n: &str) -> bool {
594        if n.len() > IRSDK_MAX_STRING {
595            return false;
596        }
597        let b = n.as_bytes();
598        for (i, item) in b.iter().enumerate() {
599            if *item != self.name[i] {
600                return false;
601            }
602        }
603        for i in b.len()..IRSDK_MAX_STRING {
604            if self.name[i] != 0 {
605                return false;
606            }
607        }
608        true
609    }
610}
611
612/// These errors can be returned when accessing variable values and there is a mismatch
613/// between the type of the variable, and the type of value asked for.
614#[derive(Debug)]
615pub enum Error {
616    InvalidType,
617    InvalidEnumValue(i32),
618}
619
620/// The different types of variables or datapoints available.
621#[derive(Clone, Copy, Debug, PartialEq)]
622pub enum VarType {
623    // 1 byte
624    Char = 0,
625    Bool = 1,
626
627    // 4 bytes
628    Int = 2,
629    Bitfield = 3,
630    Float = 4,
631
632    // 8 bytes
633    Double = 5,
634
635    //index, don't use
636    #[deprecated]
637    Etcount = 6,
638}
639impl VarType {
640    fn size(&self) -> usize {
641        match *self {
642            VarType::Char => 1,
643            VarType::Bool => 1,
644            VarType::Int => 4,
645            VarType::Bitfield => 4,
646            VarType::Float => 4,
647            VarType::Double => 8,
648            _ => todo!(), //Etcount
649        }
650    }
651}
652/// An instance of a value for a variable.
653#[derive(Debug, Clone, Copy, PartialEq)]
654pub enum Value<'a> {
655    Char(u8),
656    Chars(&'a [u8]),
657    Bool(bool),
658    Bools(&'a [bool]),
659    Int(i32),
660    Ints(&'a [i32]),
661    Bitfield(i32),
662    Bitfields(&'a [i32]),
663    Float(f32),
664    Floats(&'a [f32]),
665    Double(f64),
666    Doubles(&'a [f64]),
667}
668
669impl<'a> Value<'a> {
670    pub fn as_f64(&self) -> Result<f64, Error> {
671        match *self {
672            Value::Double(f) => Ok(f),
673            _ => Err(Error::InvalidType),
674        }
675    }
676    pub fn as_f32(&self) -> Result<f32, Error> {
677        match *self {
678            Value::Float(f) => Ok(f),
679            _ => Err(Error::InvalidType),
680        }
681    }
682    pub fn as_i32(&self) -> Result<i32, Error> {
683        match *self {
684            Value::Int(f) => Ok(f),
685            Value::Bitfield(f) => Ok(f),
686            _ => Err(Error::InvalidType),
687        }
688    }
689    pub fn as_bool(&self) -> Result<bool, Error> {
690        match *self {
691            Value::Bool(f) => Ok(f),
692            _ => Err(Error::InvalidType),
693        }
694    }
695    pub fn as_u8(&self) -> Result<u8, Error> {
696        match *self {
697            Value::Char(f) => Ok(f),
698            _ => Err(Error::InvalidType),
699        }
700    }
701    pub fn as_f64s(&self) -> Result<&'a [f64], Error> {
702        match *self {
703            Value::Doubles(f) => Ok(f),
704            _ => Err(Error::InvalidType),
705        }
706    }
707    pub fn as_f32s(self) -> Result<&'a [f32], Error> {
708        match self {
709            Value::Floats(f) => Ok(f),
710            _ => Err(Error::InvalidType),
711        }
712    }
713    pub fn as_i32s(&self) -> Result<&'a [i32], Error> {
714        match *self {
715            Value::Ints(f) => Ok(f),
716            _ => Err(Error::InvalidType),
717        }
718    }
719    pub fn as_bools(&self) -> Result<&'a [bool], Error> {
720        match *self {
721            Value::Bools(f) => Ok(f),
722            _ => Err(Error::InvalidType),
723        }
724    }
725    pub fn as_u8s(&self) -> Result<&'a [u8], Error> {
726        match *self {
727            Value::Chars(f) => Ok(f),
728            _ => Err(Error::InvalidType),
729        }
730    }
731}
732
733impl TryFrom<Value<'_>> for bool {
734    type Error = Error;
735    fn try_from(v: Value) -> Result<Self, Self::Error> {
736        v.as_bool()
737    }
738}
739impl TryFrom<Value<'_>> for u8 {
740    type Error = Error;
741    fn try_from(v: Value) -> Result<Self, Self::Error> {
742        v.as_u8()
743    }
744}
745impl TryFrom<Value<'_>> for i32 {
746    type Error = Error;
747    fn try_from(v: Value) -> Result<Self, Self::Error> {
748        v.as_i32()
749    }
750}
751impl TryFrom<Value<'_>> for f32 {
752    type Error = Error;
753    fn try_from(v: Value) -> Result<Self, Self::Error> {
754        v.as_f32()
755    }
756}
757impl TryFrom<Value<'_>> for f64 {
758    type Error = Error;
759    fn try_from(value: Value) -> Result<Self, Self::Error> {
760        value.as_f64()
761    }
762}
763impl<'a> TryFrom<Value<'a>> for &'a [bool] {
764    type Error = Error;
765    fn try_from(v: Value<'a>) -> Result<Self, Self::Error> {
766        v.as_bools()
767    }
768}
769impl<'a> TryFrom<Value<'a>> for &'a [u8] {
770    type Error = Error;
771    fn try_from(v: Value<'a>) -> Result<Self, Self::Error> {
772        v.as_u8s()
773    }
774}
775impl<'a> TryFrom<Value<'a>> for &'a [i32] {
776    type Error = Error;
777    fn try_from(v: Value<'a>) -> Result<Self, Self::Error> {
778        v.as_i32s()
779    }
780}
781impl<'a> TryFrom<Value<'a>> for &'a [f32] {
782    type Error = Error;
783    fn try_from(v: Value<'a>) -> Result<Self, Self::Error> {
784        v.as_f32s()
785    }
786}
787impl<'a> TryFrom<Value<'a>> for &'a [f64] {
788    type Error = Error;
789    fn try_from(v: Value<'a>) -> Result<Self, Self::Error> {
790        v.as_f64s()
791    }
792}
793impl TryFrom<Value<'_>> for flags::EngineWarnings {
794    type Error = Error;
795    fn try_from(v: Value) -> Result<Self, Self::Error> {
796        Ok(Self::from_bits_truncate(v.as_i32()?))
797    }
798}
799impl TryFrom<Value<'_>> for flags::Flags {
800    type Error = Error;
801    fn try_from(v: Value) -> Result<Self, Self::Error> {
802        Ok(Self::from_bits_truncate(v.as_i32()? as u32))
803    }
804}
805impl TryFrom<Value<'_>> for flags::SessionState {
806    type Error = Error;
807    fn try_from(value: Value) -> Result<Self, Self::Error> {
808        let v = value.as_i32()?;
809        match num::FromPrimitive::from_i32(v) {
810            Some(t) => Ok(t),
811            None => Err(Error::InvalidEnumValue(v)),
812        }
813    }
814}
815impl TryFrom<Value<'_>> for flags::TrackLocation {
816    type Error = Error;
817    fn try_from(value: Value) -> Result<Self, Self::Error> {
818        let v = value.as_i32()?;
819        match num::FromPrimitive::from_i32(v) {
820            Some(t) => Ok(t),
821            None => Err(Error::InvalidEnumValue(v)),
822        }
823    }
824}
825impl TryFrom<Value<'_>> for flags::TrackSurface {
826    type Error = Error;
827    fn try_from(value: Value) -> Result<Self, Self::Error> {
828        let v = value.as_i32()?;
829        match num::FromPrimitive::from_i32(v) {
830            Some(t) => Ok(t),
831            None => Err(Error::InvalidEnumValue(v)),
832        }
833    }
834}
835impl TryFrom<Value<'_>> for flags::CarLeftRight {
836    type Error = Error;
837    fn try_from(value: Value) -> Result<Self, Self::Error> {
838        let v = value.as_i32()?;
839        match num::FromPrimitive::from_i32(v) {
840            Some(t) => Ok(t),
841            None => Err(Error::InvalidEnumValue(v)),
842        }
843    }
844}
845impl TryFrom<Value<'_>> for flags::CameraState {
846    type Error = Error;
847    fn try_from(v: Value) -> Result<Self, Self::Error> {
848        Ok(Self::from_bits_truncate(v.as_i32()?))
849    }
850}
851impl TryFrom<Value<'_>> for flags::PitSvcFlags {
852    type Error = Error;
853    fn try_from(v: Value) -> Result<Self, Self::Error> {
854        Ok(Self::from_bits_truncate(v.as_i32()?))
855    }
856}
857impl TryFrom<Value<'_>> for flags::PitSvcStatus {
858    type Error = Error;
859    fn try_from(value: Value) -> Result<Self, Self::Error> {
860        let v = value.as_i32()?;
861        match num::FromPrimitive::from_i32(v) {
862            Some(t) => Ok(t),
863            None => Err(Error::InvalidEnumValue(v)),
864        }
865    }
866}
867impl TryFrom<Value<'_>> for flags::PaceMode {
868    type Error = Error;
869    fn try_from(value: Value) -> Result<Self, Self::Error> {
870        let v = value.as_i32()?;
871        match num::FromPrimitive::from_i32(v) {
872            Some(t) => Ok(t),
873            None => Err(Error::InvalidEnumValue(v)),
874        }
875    }
876}
877impl TryFrom<Value<'_>> for flags::PaceFlags {
878    type Error = Error;
879    fn try_from(v: Value) -> Result<Self, Self::Error> {
880        Ok(Self::from_bits_truncate(v.as_i32()?))
881    }
882}
883
884#[cfg(test)]
885mod tests {
886
887    use std::ptr;
888
889    use crate::flags::StatusField;
890
891    use super::*;
892
893    #[test]
894    fn test_makelong() {
895        assert_eq!(makelong(0, 0), 0);
896        assert_eq!(makelong(0, 0x12), 0x00120000);
897        assert_eq!(makelong(0x12, 0), 0x12);
898        assert_eq!(makelong(0x12, 0x24), 0x00240012);
899        assert_eq!(makelong(-1, -1), 0xFFFFFFFF);
900        assert_eq!(makelong(0, -1), 0xFFFF0000);
901        assert_eq!(makelong(-1, 0), 0x0000FFFF);
902        assert_eq!(makelong(0x1234, 0x5678), 0x56781234);
903        // sanity check the as usize cast for WPARAM;
904        let m = |a, b| makelong(a, b) as usize;
905        assert_eq!(m(0, 0), 0);
906        assert_eq!(m(0, 0x12), 0x00120000);
907        assert_eq!(m(0x12, 0), 0x12);
908        assert_eq!(m(0x12, 0x24), 0x00240012);
909        assert_eq!(m(-1, -1), 0xFFFFFFFF);
910        assert_eq!(m(0, -1), 0xFFFF0000);
911        assert_eq!(m(-1, 0), 0x0000FFFF);
912        assert_eq!(m(0x1234, 0x5678), 0x56781234);
913    }
914
915    #[test]
916    fn test_var_size() {
917        let f = |t, c| {
918            Var {
919                session_id: 0,
920                hdr: IrsdkVarHeader {
921                    var_type: t,
922                    offset: 0,
923                    count: c,
924                    count_as_time: 0,
925                    pad: [0, 0, 0], // (16 byte align)
926                    name: [0; IRSDK_MAX_STRING],
927                    desc: [0; IRSDK_MAX_DESC],
928                    unit: [0; IRSDK_MAX_STRING],
929                },
930            }
931        };
932        assert_eq!(512, f(VarType::Double, 64).size());
933        assert_eq!(24, f(VarType::Double, 3).size());
934        assert_eq!(8, f(VarType::Double, 1).size());
935        assert_eq!(12, f(VarType::Float, 3).size());
936        assert_eq!(4, f(VarType::Float, 1).size());
937        assert_eq!(12, f(VarType::Int, 3).size());
938        assert_eq!(4, f(VarType::Int, 1).size());
939        assert_eq!(12, f(VarType::Bitfield, 3).size());
940        assert_eq!(4, f(VarType::Bitfield, 1).size());
941        assert_eq!(3, f(VarType::Char, 3).size());
942        assert_eq!(1, f(VarType::Char, 1).size());
943        assert_eq!(3, f(VarType::Bool, 3).size());
944        assert_eq!(1, f(VarType::Bool, 1).size());
945    }
946
947    #[test]
948    fn test_irsdk_var_header() {
949        let mut h = IrsdkVarHeader {
950            var_type: VarType::Float,
951            offset: 32,
952            count: 1,
953            count_as_time: 0,
954            pad: [0; 3],
955            name: [0; IRSDK_MAX_STRING],
956            desc: [0; IRSDK_MAX_DESC],
957            unit: [0; IRSDK_MAX_STRING],
958        };
959        // there must be an easier way than this
960        h.name[0] = 'b' as u8;
961        h.name[1] = 'o' as u8;
962        h.name[2] = 'b' as u8;
963        assert_eq!(Ok("bob"), h.name());
964        assert!(h.has_name("bob"));
965        assert!(!h.has_name("alice"));
966        assert!(!h.has_name("bobby"));
967    }
968
969    #[test]
970    fn test_var_value() {
971        let b = || IrsdkBuf {
972            tick_count: 1,
973            buf_offset: 0,
974            pad: [0, 2],
975        };
976        let mut h = IrsdkHeader {
977            ver: 2,
978            status: StatusField::CONNECTED,
979            tick_rate: 60,
980            session_info_update: 1,
981            session_info_len: 0,
982            session_info_offset: 100,
983            num_vars: 0,
984            var_header_offset: 0,
985            num_buf: 3,
986            buf_len: 12,
987            pad1: [0; 2],
988            var_buf: [b(), b(), b(), b()],
989        };
990        let mut s = Session {
991            session_id: 1,
992            conn: Rc::new(Connection {
993                file_mapping: HANDLE::default(),
994                shared_mem: ptr::null_mut(),
995                header: ptr::addr_of_mut!(h),
996                new_data: HANDLE::default(),
997                broadcast_msg_id: 1,
998            }),
999            last_tick_count: 1,
1000            data: bytes::BytesMut::new(),
1001            expired: false,
1002        };
1003        // char/bool
1004        s.data.extend_from_slice(&[55, 56, 57, 58, 1, 0, 1, 0]);
1005        // int
1006        s.data.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
1007        // float
1008        s.data
1009            .extend_from_slice(&[0x00, 0x00, 0x80, 0x3f, 0, 0, 0, 0xc0]);
1010        // double
1011        s.data
1012            .extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0xc0, 2, 0, 0, 0, 0, 0, 0xF0, 0x3F]);
1013        let v = |t, o, c| Var {
1014            hdr: IrsdkVarHeader {
1015                var_type: t,
1016                offset: o,
1017                count: c,
1018                count_as_time: 0,
1019                pad: [0; 3],
1020                name: [0; IRSDK_MAX_STRING],
1021                desc: [0; IRSDK_MAX_DESC],
1022                unit: [0; IRSDK_MAX_STRING],
1023            },
1024            session_id: 1,
1025        };
1026        unsafe {
1027            assert_eq!(s.var_value(&v(VarType::Char, 0, 1)), Value::Char(55));
1028            assert_eq!(s.var_value(&v(VarType::Char, 1, 1)), Value::Char(56));
1029            assert_eq!(s.var_value(&v(VarType::Bool, 4, 1)), Value::Bool(true));
1030            assert_eq!(s.var_value(&v(VarType::Bool, 5, 1)), Value::Bool(false));
1031            assert_eq!(s.var_value(&v(VarType::Int, 8, 1)), Value::Int(0x04030201));
1032            assert_eq!(
1033                s.var_value(&v(VarType::Bitfield, 8, 1)),
1034                Value::Bitfield(0x04030201)
1035            );
1036            assert_eq!(s.var_value(&v(VarType::Float, 16, 1)), Value::Float(1.0));
1037            assert_eq!(s.var_value(&v(VarType::Float, 20, 1)), Value::Float(-2.0));
1038            assert_eq!(s.var_value(&v(VarType::Double, 24, 1)), Value::Double(-2.0));
1039            assert_eq!(
1040                s.var_value(&v(VarType::Double, 32, 1)),
1041                Value::Double(1.0000000000000004)
1042            );
1043            assert_eq!(
1044                s.var_value(&v(VarType::Char, 0, 3)),
1045                Value::Chars(&[55, 56, 57])
1046            );
1047            assert_eq!(
1048                s.var_value(&v(VarType::Bool, 4, 4)),
1049                Value::Bools(&[true, false, true, false])
1050            );
1051            assert_eq!(
1052                s.var_value(&v(VarType::Int, 8, 2)),
1053                Value::Ints(&[0x04030201, 0x08070605])
1054            );
1055            assert_eq!(
1056                s.var_value(&v(VarType::Bitfield, 8, 2)),
1057                Value::Bitfields(&[0x04030201, 0x08070605])
1058            );
1059            assert_eq!(
1060                s.var_value(&v(VarType::Float, 16, 2)),
1061                Value::Floats(&[1.0, -2.0])
1062            );
1063            assert_eq!(
1064                s.var_value(&v(VarType::Double, 24, 2)),
1065                Value::Doubles(&[-2.0, 1.0000000000000004])
1066            );
1067        }
1068    }
1069    #[test]
1070    #[should_panic]
1071    fn test_cant_read_past_buffer() {
1072        // ugh, need something better for setting these types of tests up.
1073        let b = || IrsdkBuf {
1074            tick_count: 1,
1075            buf_offset: 0,
1076            pad: [0, 2],
1077        };
1078        let mut h = IrsdkHeader {
1079            ver: 2,
1080            status: StatusField::CONNECTED,
1081            tick_rate: 60,
1082            session_info_update: 1,
1083            session_info_len: 0,
1084            session_info_offset: 100,
1085            num_vars: 0,
1086            var_header_offset: 0,
1087            num_buf: 3,
1088            buf_len: 12,
1089            pad1: [0; 2],
1090            var_buf: [b(), b(), b(), b()],
1091        };
1092        let mut s = Session {
1093            session_id: 1,
1094            conn: Rc::new(Connection {
1095                file_mapping: HANDLE::default(),
1096                shared_mem: ptr::null_mut(),
1097                header: ptr::addr_of_mut!(h),
1098                new_data: HANDLE::default(),
1099                broadcast_msg_id: 1,
1100            }),
1101            last_tick_count: 1,
1102            data: bytes::BytesMut::new(),
1103            expired: false,
1104        };
1105        s.data.extend_from_slice(&[1, 2, 3, 4]);
1106        let v = Var {
1107            hdr: IrsdkVarHeader {
1108                var_type: VarType::Int,
1109                offset: 2,
1110                count: 1,
1111                count_as_time: 0,
1112                pad: [0; 3],
1113                name: [0; IRSDK_MAX_STRING],
1114                desc: [0; IRSDK_MAX_DESC],
1115                unit: [0; IRSDK_MAX_STRING],
1116            },
1117            session_id: 1,
1118        };
1119        unsafe {
1120            s.var_value(&v);
1121        }
1122    }
1123}