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}