Skip to main content

pfx_steam/
fake.rs

1use std::collections::{BTreeMap, VecDeque};
2
3use crate::{
4    Board, Call, Delivery, Display, Error, Event, Hub, Lobby, LobbyFilter, LobbyKind, MAX_SAVE,
5    Message, Overlay, PRESENCE_COUNT_MAX, Quota, Range, Sort, SteamApi, Upload, User, api_name,
6    cloud_error, details_len, file_name, fits, lobby_key, lobby_size, lobby_value, message_len,
7    presence_key, presence_value, save_len,
8};
9
10#[cfg(any(feature = "input", test))]
11#[derive(Clone, Debug, Default)]
12struct FakePad {
13    kind: i32,
14    set: Option<String>,
15    digital: BTreeMap<String, bool>,
16    analog: BTreeMap<String, [f32; 2]>,
17    origins: BTreeMap<String, Vec<i32>>,
18}
19
20#[cfg(any(feature = "input", test))]
21const ORIGIN_COUNT: i32 = 495;
22
23pub const FAKE_USER: User = User(76_561_197_960_265_729);
24
25#[derive(Clone, Copy, Debug, PartialEq)]
26enum Stat {
27    Int(i32),
28    Float(f32),
29}
30
31#[derive(Debug)]
32pub struct Fake {
33    up: bool,
34    achieved: BTreeMap<String, bool>,
35    stats: BTreeMap<String, Stat>,
36    files: BTreeMap<String, Vec<u8>>,
37    presence: BTreeMap<String, String>,
38    overlays: VecDeque<Overlay>,
39    account_cloud: bool,
40    app_cloud: bool,
41    total: u64,
42    stores: u32,
43    hub: Hub,
44    user: User,
45    calls: u64,
46    invites: Vec<Lobby>,
47    #[cfg(any(feature = "input", test))]
48    pads: BTreeMap<u64, FakePad>,
49    #[cfg(any(feature = "input", test))]
50    vibrations: Vec<(u64, u16, u16)>,
51}
52
53impl Fake {
54    pub fn new() -> Self {
55        Self::with_quota(u64::try_from(MAX_SAVE).unwrap_or(u64::MAX))
56    }
57
58    pub fn with_quota(total: u64) -> Self {
59        Self::on_with_quota(&Hub::new(), FAKE_USER, total)
60    }
61
62    pub fn on(hub: &Hub, user: User) -> Self {
63        Self::on_with_quota(hub, user, u64::try_from(MAX_SAVE).unwrap_or(u64::MAX))
64    }
65
66    fn on_with_quota(hub: &Hub, user: User, total: u64) -> Self {
67        hub.world().arrive(user);
68        Self {
69            up: true,
70            achieved: BTreeMap::new(),
71            stats: BTreeMap::new(),
72            files: BTreeMap::new(),
73            presence: BTreeMap::new(),
74            overlays: VecDeque::new(),
75            account_cloud: true,
76            app_cloud: true,
77            total,
78            stores: 0,
79            hub: hub.clone(),
80            user,
81            calls: 0,
82            invites: Vec::new(),
83            #[cfg(any(feature = "input", test))]
84            pads: BTreeMap::new(),
85            #[cfg(any(feature = "input", test))]
86            vibrations: Vec::new(),
87        }
88    }
89
90    pub fn hub(&self) -> &Hub {
91        &self.hub
92    }
93
94    #[cfg(any(feature = "input", test))]
95    pub fn plug_pad(&mut self, handle: u64, kind: i32) {
96        self.pads.insert(
97            handle,
98            FakePad {
99                kind,
100                ..FakePad::default()
101            },
102        );
103    }
104
105    #[cfg(any(feature = "input", test))]
106    pub fn unplug_pad(&mut self, handle: u64) {
107        self.pads.remove(&handle);
108    }
109
110    #[cfg(any(feature = "input", test))]
111    pub fn press(&mut self, handle: u64, action: &str, down: bool) {
112        if let Some(pad) = self.pads.get_mut(&handle) {
113            pad.digital.insert(action.to_string(), down);
114        }
115    }
116
117    #[cfg(any(feature = "input", test))]
118    pub fn tilt(&mut self, handle: u64, action: &str, x: f32, y: f32) {
119        if let Some(pad) = self.pads.get_mut(&handle) {
120            pad.analog.insert(action.to_string(), [x, y]);
121        }
122    }
123
124    #[cfg(any(feature = "input", test))]
125    pub fn bind(&mut self, handle: u64, action: &str, origins: &[i32]) {
126        if let Some(pad) = self.pads.get_mut(&handle) {
127            pad.origins.insert(action.to_string(), origins.to_vec());
128        }
129    }
130
131    #[cfg(any(feature = "input", test))]
132    pub fn action_set(&self, handle: u64) -> Option<&str> {
133        self.pads.get(&handle).and_then(|pad| pad.set.as_deref())
134    }
135
136    #[cfg(any(feature = "input", test))]
137    pub fn vibrations(&self) -> &[(u64, u16, u16)] {
138        &self.vibrations
139    }
140
141    #[cfg(any(feature = "input", test))]
142    fn fake_pad(&self, handle: u64) -> Result<&FakePad, Error> {
143        self.open()?;
144        self.pads.get(&handle).ok_or(Error::Unknown)
145    }
146
147    pub fn invite_dialogs(&self) -> &[Lobby] {
148        &self.invites
149    }
150
151    fn call(&mut self) -> Call {
152        self.calls += 1;
153        Call(self.calls)
154    }
155
156    fn post(&self, event: Event) {
157        self.hub.world().push(self.user, event);
158    }
159
160    pub fn set_account_cloud(&mut self, enabled: bool) {
161        self.account_cloud = enabled;
162    }
163
164    pub fn set_app_cloud(&mut self, enabled: bool) {
165        self.app_cloud = enabled;
166    }
167
168    pub fn push_overlay(&mut self, open: bool) {
169        if self.up {
170            self.overlays.push_back(if open {
171                Overlay::Opened
172            } else {
173                Overlay::Closed
174            });
175        }
176    }
177
178    pub fn presence(&self, key: &str) -> Option<&str> {
179        self.presence.get(key).map(String::as_str)
180    }
181
182    pub fn stores(&self) -> u32 {
183        self.stores
184    }
185
186    fn open(&self) -> Result<(), Error> {
187        if self.up { Ok(()) } else { Err(Error::Closed) }
188    }
189
190    fn cloud(&self) -> Result<(), Error> {
191        self.open()?;
192        cloud_error(self.account_cloud, self.app_cloud)
193    }
194
195    fn used(&self) -> u64 {
196        self.files.values().map(|bytes| bytes.len() as u64).sum()
197    }
198
199    fn available(&self) -> u64 {
200        self.total.saturating_sub(self.used())
201    }
202}
203
204impl Default for Fake {
205    fn default() -> Self {
206        Self::new()
207    }
208}
209
210impl SteamApi for Fake {
211    fn run_callbacks(&mut self) -> Vec<Event> {
212        if !self.up {
213            return Vec::new();
214        }
215        let mut events: Vec<Event> = self.overlays.drain(..).map(Event::Overlay).collect();
216        events.extend(self.hub.world().take(self.user));
217        events
218    }
219
220    fn shutdown(&mut self) {
221        if self.up {
222            self.hub.world().depart(self.user);
223        }
224        self.up = false;
225        self.overlays.clear();
226    }
227
228    fn unlock(&mut self, name: &str) -> Result<(), Error> {
229        self.open()?;
230        api_name(name)?;
231        self.achieved.insert(name.to_string(), true);
232        Ok(())
233    }
234
235    fn achieved(&self, name: &str) -> Result<bool, Error> {
236        self.open()?;
237        api_name(name)?;
238        Ok(self.achieved.get(name).copied().unwrap_or(false))
239    }
240
241    fn store(&mut self) -> Result<(), Error> {
242        self.open()?;
243        self.stores = self.stores.saturating_add(1);
244        Ok(())
245    }
246
247    #[cfg(any(test, debug_assertions))]
248    fn clear(&mut self, name: &str) -> Result<(), Error> {
249        self.open()?;
250        api_name(name)?;
251        self.achieved.remove(name);
252        Ok(())
253    }
254
255    fn stat_i32(&self, name: &str) -> Result<i32, Error> {
256        self.open()?;
257        api_name(name)?;
258        match self.stats.get(name) {
259            Some(Stat::Int(value)) => Ok(*value),
260            Some(Stat::Float(_)) => Err(Error::Rejected),
261            None => Ok(0),
262        }
263    }
264
265    fn set_stat_i32(&mut self, name: &str, value: i32) -> Result<(), Error> {
266        self.open()?;
267        api_name(name)?;
268        if matches!(self.stats.get(name), Some(Stat::Float(_))) {
269            return Err(Error::Rejected);
270        }
271        self.stats.insert(name.to_string(), Stat::Int(value));
272        Ok(())
273    }
274
275    fn stat_f32(&self, name: &str) -> Result<f32, Error> {
276        self.open()?;
277        api_name(name)?;
278        match self.stats.get(name) {
279            Some(Stat::Float(value)) => Ok(*value),
280            Some(Stat::Int(_)) => Err(Error::Rejected),
281            None => Ok(0.0),
282        }
283    }
284
285    fn set_stat_f32(&mut self, name: &str, value: f32) -> Result<(), Error> {
286        self.open()?;
287        api_name(name)?;
288        if !value.is_finite() || matches!(self.stats.get(name), Some(Stat::Int(_))) {
289            return Err(Error::Rejected);
290        }
291        self.stats.insert(name.to_string(), Stat::Float(value));
292        Ok(())
293    }
294
295    fn write(&mut self, name: &str, bytes: &[u8]) -> Result<(), Error> {
296        self.cloud()?;
297        file_name(name)?;
298        save_len(bytes.len())?;
299        let old = self
300            .files
301            .get(name)
302            .map(|stored| stored.len() as u64)
303            .unwrap_or(0);
304        fits(self.available(), old, bytes.len())?;
305        self.files.insert(name.to_string(), bytes.to_vec());
306        Ok(())
307    }
308
309    fn read(&self, name: &str) -> Result<Vec<u8>, Error> {
310        self.cloud()?;
311        file_name(name)?;
312        self.files.get(name).cloned().ok_or(Error::Missing)
313    }
314
315    fn exists(&self, name: &str) -> Result<bool, Error> {
316        self.cloud()?;
317        file_name(name)?;
318        Ok(self.files.contains_key(name))
319    }
320
321    fn delete(&mut self, name: &str) -> Result<(), Error> {
322        self.cloud()?;
323        file_name(name)?;
324        if self.files.remove(name).is_none() {
325            Err(Error::Missing)
326        } else {
327            Ok(())
328        }
329    }
330
331    fn quota(&self) -> Result<Quota, Error> {
332        self.cloud()?;
333        Ok(Quota {
334            total: self.total,
335            available: self.available(),
336        })
337    }
338
339    fn set_presence(&mut self, key: &str, value: &str) -> Result<(), Error> {
340        self.open()?;
341        presence_key(key)?;
342        presence_value(value)?;
343        if !self.presence.contains_key(key) && self.presence.len() >= PRESENCE_COUNT_MAX {
344            return Err(Error::Rejected);
345        }
346        self.presence.insert(key.to_string(), value.to_string());
347        Ok(())
348    }
349
350    fn clear_presence(&mut self, key: &str) -> Result<(), Error> {
351        self.open()?;
352        presence_key(key)?;
353        self.presence.remove(key);
354        Ok(())
355    }
356
357    fn me(&self) -> Result<User, Error> {
358        self.open()?;
359        Ok(self.user)
360    }
361
362    fn server_time(&self) -> Result<Option<u64>, Error> {
363        self.open()?;
364        Ok(self.hub.world().server_time())
365    }
366
367    fn find_board(&mut self, name: &str) -> Result<Call, Error> {
368        self.open()?;
369        api_name(name)?;
370        let call = self.call();
371        let result = self.hub.world().find_board(name);
372        self.post(Event::Board { call, result });
373        Ok(call)
374    }
375
376    fn find_or_create_board(
377        &mut self,
378        name: &str,
379        sort: Sort,
380        display: Display,
381    ) -> Result<Call, Error> {
382        self.open()?;
383        api_name(name)?;
384        let call = self.call();
385        let info = self.hub.world().create_board(name, sort, display);
386        self.post(Event::Board {
387            call,
388            result: Ok(info),
389        });
390        Ok(call)
391    }
392
393    fn upload_score(
394        &mut self,
395        board: Board,
396        score: i32,
397        details: &[i32],
398        upload: Upload,
399    ) -> Result<Call, Error> {
400        self.open()?;
401        details_len(details)?;
402        let call = self.call();
403        let result = self
404            .hub
405            .world()
406            .upload(self.user, board, score, details, upload);
407        self.post(Event::Uploaded {
408            call,
409            board,
410            result,
411        });
412        Ok(call)
413    }
414
415    fn download_scores(
416        &mut self,
417        board: Board,
418        range: Range,
419        details: usize,
420    ) -> Result<Call, Error> {
421        self.open()?;
422        if !range.valid() {
423            return Err(Error::Rejected);
424        }
425        let call = self.call();
426        let result =
427            self.hub
428                .world()
429                .download(self.user, board, range, details.min(crate::DETAILS_MAX));
430        self.post(Event::Scores {
431            call,
432            board,
433            result,
434        });
435        Ok(call)
436    }
437
438    fn share_file(&mut self, name: &str) -> Result<Call, Error> {
439        self.cloud()?;
440        file_name(name)?;
441        let call = self.call();
442        let result = match self.files.get(name) {
443            Some(bytes) => Ok(self.hub.world().share(bytes.clone())),
444            None => Err(Error::Missing),
445        };
446        self.post(Event::Shared { call, result });
447        Ok(call)
448    }
449
450    fn attach_ugc(&mut self, board: Board, ugc: crate::Ugc) -> Result<(), Error> {
451        self.open()?;
452        self.hub.world().attach(self.user, board, ugc)
453    }
454
455    fn download_ugc(&mut self, ugc: crate::Ugc) -> Result<Call, Error> {
456        self.open()?;
457        let call = self.call();
458        let result = self.hub.world().ugc(ugc);
459        self.post(Event::UgcDownloaded { call, ugc, result });
460        Ok(call)
461    }
462
463    fn create_lobby(&mut self, kind: LobbyKind, max_members: u32) -> Result<Call, Error> {
464        self.open()?;
465        lobby_size(max_members)?;
466        let call = self.call();
467        let lobby = self.hub.world().create_lobby(self.user, kind, max_members);
468        self.post(Event::LobbyCreated {
469            call,
470            result: Ok(lobby),
471        });
472        Ok(call)
473    }
474
475    fn find_lobbies(&mut self, filter: &LobbyFilter) -> Result<Call, Error> {
476        self.open()?;
477        filter.check()?;
478        let call = self.call();
479        let found = self.hub.world().find_lobbies(filter);
480        self.post(Event::Lobbies {
481            call,
482            result: Ok(found),
483        });
484        Ok(call)
485    }
486
487    fn join_lobby(&mut self, lobby: Lobby) -> Result<Call, Error> {
488        self.open()?;
489        let call = self.call();
490        let result = self.hub.world().join(self.user, lobby);
491        self.post(Event::Joined { call, result });
492        Ok(call)
493    }
494
495    fn leave_lobby(&mut self, lobby: Lobby) -> Result<(), Error> {
496        self.open()?;
497        self.hub
498            .world()
499            .leave(self.user, lobby, crate::Change::Left);
500        Ok(())
501    }
502
503    fn lobby_members(&self, lobby: Lobby) -> Result<Vec<User>, Error> {
504        self.open()?;
505        Ok(self.hub.world().members(self.user, lobby))
506    }
507
508    fn lobby_owner(&self, lobby: Lobby) -> Result<User, Error> {
509        self.open()?;
510        self.hub.world().owner(self.user, lobby)
511    }
512
513    fn set_lobby_data(&mut self, lobby: Lobby, key: &str, value: &str) -> Result<(), Error> {
514        self.open()?;
515        lobby_key(key)?;
516        lobby_value(value)?;
517        self.hub
518            .world()
519            .set_lobby_data(self.user, lobby, key, value)
520    }
521
522    fn lobby_data(&self, lobby: Lobby, key: &str) -> Result<Option<String>, Error> {
523        self.open()?;
524        lobby_key(key)?;
525        Ok(self.hub.world().lobby_data(lobby, key))
526    }
527
528    fn invite_dialog(&mut self, lobby: Lobby) -> Result<(), Error> {
529        self.open()?;
530        if self.hub.world().members(self.user, lobby).is_empty() {
531            return Err(Error::Rejected);
532        }
533        self.invites.push(lobby);
534        Ok(())
535    }
536
537    fn launched(&mut self, args: &[String]) -> Option<Lobby> {
538        let lobby = crate::connect_lobby(args)?;
539        self.open().ok()?;
540        self.post(Event::JoinRequested {
541            lobby,
542            friend: None,
543        });
544        Some(lobby)
545    }
546
547    fn send_message(
548        &mut self,
549        peer: User,
550        channel: u32,
551        bytes: &[u8],
552        delivery: Delivery,
553    ) -> Result<(), Error> {
554        self.open()?;
555        message_len(bytes.len())?;
556        if peer == self.user {
557            return Err(Error::Rejected);
558        }
559        self.hub
560            .world()
561            .send(self.user, peer, channel, bytes, delivery);
562        Ok(())
563    }
564
565    fn receive_messages(&mut self, channel: u32, max: usize) -> Result<Vec<Message>, Error> {
566        self.open()?;
567        Ok(self.hub.world().receive(self.user, channel, max))
568    }
569
570    fn accept_session(&mut self, peer: User) -> Result<(), Error> {
571        self.open()?;
572        self.hub.world().accept(self.user, peer)
573    }
574
575    #[cfg(any(feature = "input", test))]
576    fn input_frame(&mut self) -> Result<Vec<u64>, Error> {
577        self.open()?;
578        Ok(self.pads.keys().copied().collect())
579    }
580
581    #[cfg(any(feature = "input", test))]
582    fn input_type(&mut self, pad: u64) -> Result<i32, Error> {
583        Ok(self.fake_pad(pad)?.kind)
584    }
585
586    #[cfg(any(feature = "input", test))]
587    fn activate_action_set(&mut self, pad: u64, set: &str) -> Result<(), Error> {
588        self.fake_pad(pad)?;
589        api_name(set)?;
590        if let Some(state) = self.pads.get_mut(&pad) {
591            state.set = Some(set.to_string());
592        }
593        Ok(())
594    }
595
596    #[cfg(any(feature = "input", test))]
597    fn digital_action(&mut self, pad: u64, action: &str) -> Result<crate::Digital, Error> {
598        let state = self.fake_pad(pad)?;
599        let active = state.set.is_some();
600        Ok(crate::Digital {
601            state: active && state.digital.get(action).copied().unwrap_or(false),
602            active,
603        })
604    }
605
606    #[cfg(any(feature = "input", test))]
607    fn analog_action(&mut self, pad: u64, action: &str) -> Result<crate::Analog, Error> {
608        let state = self.fake_pad(pad)?;
609        let active = state.set.is_some();
610        let [x, y] = state.analog.get(action).copied().unwrap_or([0.0, 0.0]);
611        Ok(crate::Analog { x, y, active })
612    }
613
614    #[cfg(any(feature = "input", test))]
615    fn digital_origins(&mut self, pad: u64, _set: &str, action: &str) -> Result<Vec<i32>, Error> {
616        Ok(self
617            .fake_pad(pad)?
618            .origins
619            .get(action)
620            .cloned()
621            .unwrap_or_default())
622    }
623
624    #[cfg(any(feature = "input", test))]
625    fn analog_origins(&mut self, pad: u64, set: &str, action: &str) -> Result<Vec<i32>, Error> {
626        self.digital_origins(pad, set, action)
627    }
628
629    #[cfg(any(feature = "input", test))]
630    fn trigger_vibration(&mut self, pad: u64, low: u16, high: u16) -> Result<(), Error> {
631        self.fake_pad(pad)?;
632        self.vibrations.push((pad, low, high));
633        Ok(())
634    }
635
636    #[cfg(any(feature = "input", test))]
637    fn steam_glyph(
638        &mut self,
639        origin: i32,
640        glyph: crate::SteamGlyph,
641    ) -> Result<Option<String>, Error> {
642        self.open()?;
643        if !(1..ORIGIN_COUNT).contains(&origin) {
644            return Ok(None);
645        }
646        let style = glyph.flags();
647        Ok(Some(match glyph.format {
648            crate::GlyphFormat::Png(size) => {
649                format!("steam/glyphs/{origin}-{style}-{size:?}.png").to_lowercase()
650            }
651            crate::GlyphFormat::Svg => format!("steam/glyphs/{origin}-{style}.svg"),
652        }))
653    }
654}