Skip to main content

warcraft3_stats_observer/
observer.rs

1use std::ops::{Deref, DerefMut};
2use winapi::shared::minwindef::LPVOID;
3use winapi::shared::ntdef::HANDLE;
4use winapi::um::errhandlingapi::GetLastError;
5use winapi::um::handleapi::CloseHandle;
6use winapi::um::memoryapi::{FILE_MAP_WRITE, MapViewOfFile, OpenFileMappingW, UnmapViewOfFile};
7
8use std::time::Duration;
9
10use crate::game::ObserverGame;
11use crate::player::PlayerInfo;
12use crate::shop::ShopInfo;
13
14/// Maximum number of player slots tracked by the observer API.
15pub const MAX_PLAYERS: usize = 28;
16
17/// Maximum number of shops tracked by the observer API.
18pub const MAX_SHOPS: usize = 999;
19
20// Named tag where the warcraft 3 memory map lives
21const OBSERVER_PATH: &str = r"War3StatsObserverSharedMemory";
22
23/// Top-level layout of the Warcraft III Stats Observer shared memory map.
24///
25/// This is the type [`ObserverHandle`] dereferences to. Because the underlying
26/// memory is updated by Warcraft III, all numeric fields can change between
27/// reads. The struct is `#[repr(C, packed)]`, so borrow individual fields by
28/// copying them with `{ ... }`:
29///
30/// ```ignore
31/// println!("version: {}", { observer.version });
32/// ```
33#[repr(C, packed)]
34pub struct ObserverData {
35    /// Not quite sure what this version number is supposed to represent.
36    pub version: u32,
37    /// Current refresh rate of the API in milliseconds. A value of `0` disables updates.
38    pub refresh_rate: u32,
39    /// Game-wide state (clock, map name, in-game flag, …).
40    pub game: ObserverGame,
41    /// Per-player state. Only the first
42    /// [`ObserverGame::active_player_count`]
43    /// entries are meaningful.
44    pub players: [PlayerInfo; MAX_PLAYERS],
45    /// Number of valid entries in [`Self::shops`].
46    pub shop_count: u32,
47    /// Per-shop state. Only the first [`Self::shop_count`]
48    /// entries are meaningful.
49    pub shops: [ShopInfo; MAX_SHOPS],
50}
51
52impl ObserverData {
53    /// Disables observer updates by writing a refresh rate of `0`.
54    pub fn disable(&mut self) {
55        self.set_refresh_rate(Duration::ZERO);
56    }
57
58    /// Sets the observer's refresh rate. Sub-millisecond precision is
59    /// truncated. A duration of zero disables updates.
60    pub fn set_refresh_rate(&mut self, duration: Duration) {
61        self.refresh_rate = duration.as_millis() as u32;
62    }
63}
64
65// Number generated from SIZE fields of https://github.com/TinkerWorX/Blizzard.Net.Warcraft3
66// noinspection RsAssertEqual
67const _: () = assert!(size_of::<ObserverData>() == 181219642);
68
69/// Owns the Windows handles from `OpenFileMappingW` and `MapViewOfFile`.
70///
71/// Releases them via `Drop` and dereferences to [`ObserverData`] for read and
72/// write access to the memory map.
73pub struct ObserverHandle {
74    mapping: HANDLE,
75    view: LPVOID,
76}
77
78// SAFETY:
79// `Send` / `Sync` are required because the raw Win32 handle and pointer fields
80// make `ObserverHandle` opt out of these traits by default.
81//
82// - `Send` is fine: the file mapping and view belong to the process, not to a
83//   specific thread. Any thread may use them and any thread may free them
84//   (which `Drop` does).
85// - `Sync` is fine: the only shared-reference operation is reading
86//   `ObserverData` via `Deref`. Mutation goes through `&mut self`, so Rust's
87//   borrow rules already prevent reader/writer races between threads of *this*
88//   program.
89//
90// What these impls do NOT promise: Warcraft III is mutating the same memory
91// from another process. We can't synchronize with it, so callers should treat
92// each field read as a possibly-stale snapshot rather than a coherent view.
93unsafe impl Send for ObserverHandle {}
94unsafe impl Sync for ObserverHandle {}
95
96impl ObserverHandle {
97    /// Opens the observer memory map and sets the refresh rate to the default
98    /// of 500 ms.
99    ///
100    /// Returns an error if Warcraft III is not running, or the stats observer
101    /// API is otherwise unavailable.
102    pub fn new() -> std::io::Result<Self> {
103        Self::new_with_refresh_rate(Duration::from_millis(500))
104    }
105
106    /// Opens the observer memory map and writes `duration` as the refresh
107    /// rate. A duration of zero disables updates.
108    ///
109    /// Returns an error if Warcraft III is not running, or the stats observer
110    /// API is otherwise unavailable.
111    pub fn new_with_refresh_rate(duration: Duration) -> std::io::Result<Self> {
112        let mut path: Vec<u16> = OBSERVER_PATH.encode_utf16().collect();
113        path.push(0);
114
115        let mapping;
116        let errno: i32;
117
118        unsafe {
119            mapping = OpenFileMappingW(FILE_MAP_WRITE, 0, path.as_ptr());
120        }
121
122        if mapping.is_null() {
123            unsafe {
124                errno = GetLastError() as i32;
125            }
126            return Err(std::io::Error::from_raw_os_error(errno));
127        }
128
129        let view: LPVOID;
130
131        unsafe {
132            view = MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0);
133        }
134
135        if view.is_null() {
136            unsafe {
137                errno = GetLastError() as i32;
138                CloseHandle(mapping);
139            }
140            return Err(std::io::Error::from_raw_os_error(errno));
141        }
142
143        let mut handle = ObserverHandle { mapping, view };
144        handle.set_refresh_rate(duration);
145        Ok(handle)
146    }
147}
148
149impl Deref for ObserverHandle {
150    type Target = ObserverData;
151
152    fn deref(&self) -> &Self::Target {
153        // SAFETY: view is non-null (checked at construction) and valid for the handle's lifetime.
154        unsafe { &*(self.view as *const ObserverData) }
155    }
156}
157
158impl DerefMut for ObserverHandle {
159    fn deref_mut(&mut self) -> &mut Self::Target {
160        // SAFETY: view is non-null (checked at construction), valid for the handle's lifetime,
161        // and &mut self ensures no other mutable reference to this handle exists.
162        unsafe { &mut *(self.view as *mut ObserverData) }
163    }
164}
165
166impl Drop for ObserverHandle {
167    fn drop(&mut self) {
168        unsafe {
169            UnmapViewOfFile(self.view);
170            CloseHandle(self.mapping);
171        }
172    }
173}