Skip to main content

game_detector/
ms_store.rs

1#[derive(Debug, Clone)]
2pub struct GamePackage {
3    pub app_id: String,
4
5    pub app_publisher: String,
6    pub app_name: String,
7    pub app_version: String,
8    pub app_arch: String,
9    pub app_publisher_id: String,
10
11    pub path: String,
12}
13
14/// Returns [publisher, name, version, arch, publisher id]
15/// eg. ["Bungie", "Destiny2PCbasegame", "0.3.26071.0", "x64", "8xb1a0vv8ay84"]
16#[cfg(windows)]
17fn parse_app_id(s: &str) -> anyhow::Result<[String; 5]> {
18    use anyhow::Context;
19    let mut parts = s.split('_');
20    let publisher_and_name = parts.next().context("Invalid app id (publisher+name)")?;
21    let mut pan_parts = publisher_and_name.split('.');
22    let publisher = pan_parts.next().context("Invalid app id (publisher)")?;
23    let name = pan_parts.next().context("Invalid app id (name)")?;
24
25    let version = parts.next().context("Invalid app id (version)")?;
26    let arch = parts.next().context("Invalid app id (arch)")?;
27    parts.next().context("Invalid app id (field4)")?;
28    let publisher_id = parts.next().context("Invalid app id (publisher id)")?;
29    Ok([
30        publisher.to_string(),
31        name.to_string(),
32        version.to_string(),
33        arch.to_string(),
34        publisher_id.to_string(),
35    ])
36}
37
38#[cfg(not(windows))]
39pub fn get_game_packages() -> anyhow::Result<Vec<GamePackage>> {
40    Ok(vec![])
41}
42
43#[cfg(windows)]
44pub fn get_game_packages() -> anyhow::Result<Vec<GamePackage>> {
45    use log::warn;
46    use std::path::Path;
47
48    const GAMING_SERVICES_PACKAGE_REPOSITORY: &str =
49        "SOFTWARE\\Microsoft\\GamingServices\\PackageRepository\\Package";
50    const FULL_PACKAGE_REPOSITORY: &str =
51        "SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\PackageRepository\\Packages";
52
53    let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
54    let Ok(package_repo) = hklm.open_subkey(GAMING_SERVICES_PACKAGE_REPOSITORY) else {
55        // User has no gaming services/packages installed, therefore no games
56        return Ok(vec![]);
57    };
58
59    let mut packages = vec![];
60    for (app_id, _) in package_repo.enum_values().flatten() {
61        let pkg_path = Path::new(FULL_PACKAGE_REPOSITORY).join(&app_id);
62        let pkg = hklm.open_subkey(pkg_path)?;
63        match pkg.get_value::<String, _>("Path") {
64            Ok(path) => match parse_app_id(&app_id) {
65                Ok(o) => {
66                    let [app_publisher, app_name, app_version, app_arch, app_publisher_id] = o;
67                    packages.push(GamePackage {
68                        app_id,
69                        app_publisher,
70                        app_name,
71                        app_version,
72                        app_arch,
73                        app_publisher_id,
74                        path,
75                    })
76                }
77                Err(e) => {
78                    eprintln!("Couldn't parse package id '{app_id}': {e}");
79                }
80            },
81            Err(e) => {
82                warn!("Couldn't read path key for package '{app_id}': {e}");
83            }
84        }
85    }
86
87    Ok(packages)
88}