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}