iracing/
lib.rs

1#![allow(dead_code)] // We may not need to use functions we export
2#[macro_use]
3extern crate bitflags;
4extern crate winapi;
5extern crate serde;
6extern crate serde_yaml;
7extern crate encoding;
8extern crate libc;
9
10pub mod session;
11pub mod telemetry;
12pub mod track_surface;
13pub mod states;
14
15use crate::session::*;
16use serde_yaml::from_str as yaml_from;
17use std::ptr::null;
18use std::io::Error;
19use std::slice::from_raw_parts;
20use std::io::Result as IOResult;
21use std::mem::transmute;
22use std::sync::Mutex;
23use libc::c_void;
24use encoding::{Encoding, DecoderTrap};
25use encoding::all::ISO_8859_1;
26
27#[cfg(target_os="windows")]
28use crate::telemetry::Header;
29
30#[cfg(target_os="windows")]
31use std::os::windows::raw::HANDLE;
32#[cfg(target_os="windows")]
33use winapi::shared::minwindef::LPVOID;
34#[cfg(target_os="windows")]
35use winapi::um::errhandlingapi::GetLastError;
36#[cfg(target_os="windows")]
37use winapi::um::handleapi::CloseHandle;
38#[cfg(target_os="windows")]
39use winapi::um::memoryapi::{OpenFileMappingW, FILE_MAP_READ, MapViewOfFile};
40
41
42pub const TELEMETRY_PATH: &'static str = "Local\\IRSDKMemMapFileName";
43pub const UNLIMITED_LAPS: i32 = 32767;
44pub const UNLIMITED_TIME: f32 = 604800.0;
45
46
47#[cfg(target_os="windows")]
48///
49/// iRacing live telemetry and session data connection.
50/// 
51/// Allows retrival of live data fro iRacing.
52/// The data is provided using a shared memory map, allowing the simulator
53/// to deposit data roughly every 16ms to be read.
54/// 
55/// # Examples
56/// 
57/// ```
58/// use iracing::Connection;
59/// 
60/// let _ = Connection::new().expect("Unable to find telemetry data");
61/// ```
62pub struct Connection {
63    mux: Mutex<()>,
64    location: *mut c_void,
65    header: Header
66}
67
68#[cfg(target_os = "windows")]
69impl Connection {
70    pub fn new() -> IOResult<Connection> {
71        let mut path: Vec<u16> = TELEMETRY_PATH.encode_utf16().collect();
72        path.push(0);
73
74        let mapping: HANDLE;
75        let errno: i32;
76
77        unsafe { mapping = OpenFileMappingW(FILE_MAP_READ, 0, path.as_ptr()); };
78
79        if null() == mapping {
80            
81            unsafe { errno = GetLastError() as i32; }
82
83            return Err(Error::from_raw_os_error(errno));
84        }
85
86        let view: LPVOID;
87 
88        unsafe {
89            view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
90        }
91
92        if null() == view {
93            unsafe { errno = GetLastError() as i32; }
94
95            return Err(Error::from_raw_os_error(errno))
96        }
97
98        let header = unsafe { Self::read_header(view) };
99
100        return Ok(Connection {mux: Mutex::default(), location: view, header: header});
101    }
102
103    ///
104    /// Get the data header
105    /// 
106    /// Reads the data header from the shared memory map and returns a copy of the header
107    /// which can be used safely elsewhere.
108    /// 
109    /// # Examples
110    /// 
111    /// ```
112    /// use iracing::Connection;
113    /// 
114    /// let location_of_an_iracing_header: *const c_void;
115    /// let header = Connection::read_header(location_of_an_iracing_header);
116    /// 
117    /// println!("Data Version: {}", header.version);
118    /// ```
119    pub unsafe fn read_header(from: *const c_void) -> Header {
120        let raw_header: *const Header = transmute(from);
121        let h: Header = *raw_header;
122
123        h.clone()
124    }
125
126    ///
127    /// Get session information
128    /// 
129    /// Get general session information - This data is mostly static and contains
130    /// overall information related to the current or replayed session
131    /// 
132    /// # Examples
133    /// 
134    /// ```
135    /// use iracing::Connection;
136    /// 
137    /// match Connection::new().expect("Unable to open session").session_info() {
138    ///     Ok(session) => println!("Track Name: {}", session.weekend.track_display_name),
139    ///     Err(e) => println!("Invalid Session")
140    /// };
141    /// ```
142    pub fn session_info(&mut self) -> Result<SessionDetails, Box<dyn std::error::Error>> {
143        let header = unsafe { Self::read_header(self.location) };
144
145        let start = (self.location as usize + header.session_info_offset as usize) as *const u8;
146        let size = header.session_info_length as usize;
147        
148        let data: &[u8] = unsafe { from_raw_parts(start, size) };
149
150        // Decode the data as ISO-8859-1 (Rust wants UTF-8)
151        let content: String = match ISO_8859_1.decode(data, DecoderTrap::Strict) {
152            Ok(value) => value,
153            Err(e) => return Err(Box::from(e))
154        };
155
156        match yaml_from(content.as_str()) {
157            Ok(session) => Ok(session),
158            Err(e) => Err(Box::from(e))
159        }
160    }
161
162    ///
163    /// Get latest telemetry.
164    /// 
165    /// Get the latest live telemetry data, the telemetry is updated roughtly every 16ms
166    /// 
167    /// # Examples
168    /// 
169    /// ```
170    /// use iracing::Connection;
171    /// 
172    /// let sample = Connection::new()?.telemetry()?;
173    /// ``` 
174    pub fn telemetry(&self) -> Result<telemetry::Sample, Box<dyn std::error::Error>> {
175        let header = unsafe { Self::read_header(self.location) };
176        header.telemetry(self.location as *const std::ffi::c_void)
177    }
178
179    ///
180    /// Get Blocking Telemetry Interface.
181    /// 
182    /// Creates a new `iracing::telemetry::Blocking` connector which allows telemetry samples to
183    /// be collected, and will wait and block until a new sample is available, or a timeout is reached.
184    /// 
185    /// # Examples
186    /// 
187    /// ```
188    /// use iracing::Connection;
189    /// use std::time::Duration;
190    /// 
191    /// let sampler = Connection::new()?.blocking()?;
192    /// let sample = sample.sample(Duration::from_millis(50))?;
193    /// ```
194    pub fn blocking(&self) -> IOResult<telemetry::Blocking> {
195        telemetry::Blocking::new(self.location, unsafe { Self::read_header(self.location) })
196    }
197
198    pub fn close(&self) -> IOResult<()> {
199        let succ = unsafe { CloseHandle(self.location) };
200
201        if succ != 0 {
202            Ok(())
203        } else {
204            let errno: i32 = unsafe { GetLastError() as i32 };
205
206            Err(Error::from_raw_os_error(errno))
207        }
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_session_info() {
217        match Connection::new().expect("Unable to open telemetry").session_info() {
218            Ok(session) => println!("Track: {}", session.weekend.track_name),
219            Err(e) => println!("Error: {:?}", e)
220        };
221    }
222
223    #[test]
224    fn test_latest_telemetry() {
225        Connection::new().expect("Unable to open telemetry").telemetry().expect("Couldn't get latest telem");
226    }
227}
228