Skip to content

Commit fdf985e

Browse files
Merge pull request #22 from thunderstore-io/eco-game-import
Simplify game resolver code and add support for platform specifiers
2 parents 8f9e7df + cbc8d9a commit fdf985e

File tree

9 files changed

+344
-373
lines changed

9 files changed

+344
-373
lines changed

src/game/import/ea.rs

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,36 @@
11
use std::path::PathBuf;
22

3-
use super::GameImporter;
3+
use super::ImportOverrides;
44
use crate::error::Error;
55
use crate::game::error::GameError;
6-
use crate::game::import::ImportBase;
7-
use crate::game::registry::{ActiveDistribution, GameData};
8-
use crate::ts::v1::models::ecosystem::GameDefPlatform;
6+
use crate::game::registry::ActiveDistribution;
7+
use crate::ts::v1::models::ecosystem::{GameDef, GamePlatform};
98
use crate::util::reg::{self, HKey};
109

11-
pub struct EaImporter {
12-
ident: String,
13-
}
14-
15-
impl EaImporter {
16-
pub fn new(ident: &str) -> EaImporter {
17-
EaImporter {
18-
ident: ident.into(),
19-
}
20-
}
21-
}
22-
23-
impl GameImporter for EaImporter {
24-
fn construct(self: Box<Self>, base: ImportBase) -> Result<GameData, Error> {
25-
let subkey = format!("Software\\{}\\", self.ident.replace('.', "\\"));
26-
let value = reg::get_value_at(HKey::LocalMachine, &subkey, "Install Dir")?;
10+
pub fn get_gamedist(ident: &str, game_def: &GameDef, overrides: &ImportOverrides) -> Result<Option<ActiveDistribution>, Error> {
11+
let subkey = format!("Software\\{}\\", ident.replace('.', "\\"));
12+
let value = reg::get_value_at(HKey::LocalMachine, &subkey, "Install Dir")?;
2713

28-
let game_dir = PathBuf::from(value);
29-
let r2mm = base.game_def.r2modman.as_ref().expect(
30-
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
31-
);
14+
let game_dir = PathBuf::from(value);
15+
let r2mm = game_def.r2modman.as_ref().expect(
16+
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
17+
).first().unwrap();
3218

33-
let exe_path = base
34-
.overrides
35-
.custom_exe
36-
.clone()
37-
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
38-
.ok_or_else(|| GameError::ExeNotFound {
39-
possible_names: r2mm.exe_names.clone(),
40-
base_path: game_dir.clone(),
41-
})?;
42-
let dist = ActiveDistribution {
43-
dist: GameDefPlatform::Origin {
44-
identifier: self.ident.to_string(),
45-
},
46-
game_dir: game_dir.to_path_buf(),
47-
data_dir: game_dir.join(&r2mm.data_folder_name),
48-
exe_path,
49-
};
19+
let exe_path = overrides
20+
.custom_exe
21+
.clone()
22+
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
23+
.ok_or_else(|| GameError::ExeNotFound {
24+
possible_names: r2mm.exe_names.clone(),
25+
base_path: game_dir.clone(),
26+
})?;
5027

51-
Ok(super::construct_data(base, dist))
52-
}
28+
Ok(Some(ActiveDistribution {
29+
dist: GamePlatform::Origin {
30+
identifier: ident.to_string(),
31+
},
32+
game_dir: game_dir.to_path_buf(),
33+
data_dir: game_dir.join(&r2mm.data_folder_name),
34+
exe_path,
35+
}))
5336
}

src/game/import/egs.rs

Lines changed: 55 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use std::path::PathBuf;
33

44
use serde::{Deserialize, Serialize};
55

6-
use super::{GameImporter, ImportBase};
6+
use super::ImportOverrides;
77
use crate::error::{Error, IoError};
88
use crate::game::error::GameError;
9-
use crate::game::registry::{ActiveDistribution, GameData};
10-
use crate::ts::v1::models::ecosystem::GameDefPlatform;
9+
use crate::game::registry::ActiveDistribution;
10+
use crate::ts::v1::models::ecosystem::{GameDef, GamePlatform};
1111
use crate::util::reg::{self, HKey};
1212

1313
#[derive(Serialize, Deserialize, Debug)]
@@ -17,81 +17,65 @@ struct PartialInstallManifest {
1717
app_name: String,
1818
}
1919

20-
pub struct EgsImporter {
21-
ident: String,
22-
}
23-
24-
impl EgsImporter {
25-
pub fn new(ident: &str) -> EgsImporter {
26-
EgsImporter {
27-
ident: ident.into(),
28-
}
29-
}
30-
}
20+
pub fn get_gamedist(ident: &str, game_def: &GameDef, overrides: &ImportOverrides) -> Result<Option<ActiveDistribution>, Error> {
21+
let game_label = game_def.label.clone();
3122

32-
impl GameImporter for EgsImporter {
33-
fn construct(self: Box<Self>, base: ImportBase) -> Result<GameData, Error> {
34-
let game_label = base.game_def.label.clone();
23+
// There's a couple ways that we can retrieve the path of a game installed via EGS.
24+
// 1. Parse LauncherInstalled.dat in C:/ProgramData/Epic/UnrealEngineLauncher/
25+
// 2. Parse game manifest files in C:/ProgramData/Epic/EpicGamesLauncher/Data/Manifests
26+
// I'm going to go for the second option.
3527

36-
// There's a couple ways that we can retrieve the path of a game installed via EGS.
37-
// 1. Parse LauncherInstalled.dat in C:/ProgramData/Epic/UnrealEngineLauncher/
38-
// 2. Parse game manifest files in C:/ProgramData/Epic/EpicGamesLauncher/Data/Manifests
39-
// I'm going to go for the second option.
28+
// Attempt to get the path of the EGS /Data directory from the registry.
29+
let subkey = r#"Software\WOW64Node\Epic Games\EpicGamesLauncher"#;
30+
let value = reg::get_value_at(HKey::LocalMachine, subkey, "AppDataPath")?;
31+
let manifests_dir = PathBuf::from(value).join("Manifests");
4032

41-
// Attempt to get the path of the EGS /Data directory from the registry.
42-
let subkey = r#"Software\WOW64Node\Epic Games\EpicGamesLauncher"#;
43-
let value = reg::get_value_at(HKey::LocalMachine, subkey, "AppDataPath")?;
44-
let manifests_dir = PathBuf::from(value).join("Manifests");
45-
46-
if !manifests_dir.exists() {
47-
Err(IoError::DirNotFound(manifests_dir.clone()))?;
48-
}
33+
if !manifests_dir.exists() {
34+
Err(IoError::DirNotFound(manifests_dir.clone()))?;
35+
}
4936

50-
// Manifest files are JSON files with .item extensions.
51-
let manifest_files = fs::read_dir(manifests_dir)
52-
.unwrap()
53-
.filter_map(|x| x.ok())
54-
.map(|x| x.path())
55-
.filter(|x| x.is_file() && x.extension().is_some())
56-
.filter(|x| x.extension().unwrap() == "item")
57-
.collect::<Vec<_>>();
37+
// Manifest files are JSON files with .item extensions.
38+
let manifest_files = fs::read_dir(manifests_dir)
39+
.unwrap()
40+
.filter_map(|x| x.ok())
41+
.map(|x| x.path())
42+
.filter(|x| x.is_file() && x.extension().is_some())
43+
.filter(|x| x.extension().unwrap() == "item")
44+
.collect::<Vec<_>>();
5845

59-
// Search for the manifest which contains the correct game AppName.
60-
let game_dir = manifest_files
61-
.into_iter()
62-
.find_map(|x| {
63-
let file_contents = fs::read_to_string(x).unwrap();
64-
let manifest: PartialInstallManifest =
65-
serde_json::from_str(&file_contents).unwrap();
46+
// Search for the manifest which contains the correct game AppName.
47+
let game_dir = manifest_files
48+
.into_iter()
49+
.find_map(|x| {
50+
let file_contents = fs::read_to_string(x).unwrap();
51+
let manifest: PartialInstallManifest =
52+
serde_json::from_str(&file_contents).unwrap();
6653

67-
if manifest.app_name == self.ident {
68-
Some(manifest.install_location)
69-
} else {
70-
None
71-
}
72-
})
73-
.ok_or_else(|| GameError::NotFound(game_label.clone(), "EGS".to_string()))?;
54+
if manifest.app_name == ident {
55+
Some(manifest.install_location)
56+
} else {
57+
None
58+
}
59+
})
60+
.ok_or_else(|| GameError::NotFound(game_label.clone(), "EGS".to_string()))?;
7461

75-
let r2mm = base.game_def.r2modman.as_ref().expect(
76-
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
77-
);
62+
let r2mm = game_def.r2modman.as_ref().expect(
63+
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
64+
).first().unwrap();
7865

79-
let exe_path = base
80-
.overrides
81-
.custom_exe
82-
.clone()
83-
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
84-
.ok_or_else(|| GameError::ExeNotFound {
85-
possible_names: r2mm.exe_names.clone(),
86-
base_path: game_dir.clone(),
87-
})?;
88-
let dist = ActiveDistribution {
89-
dist: GameDefPlatform::Other,
90-
game_dir: game_dir.to_path_buf(),
91-
data_dir: game_dir.join(&r2mm.data_folder_name),
92-
exe_path,
93-
};
66+
let exe_path = overrides
67+
.custom_exe
68+
.clone()
69+
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
70+
.ok_or_else(|| GameError::ExeNotFound {
71+
possible_names: r2mm.exe_names.clone(),
72+
base_path: game_dir.clone(),
73+
})?;
9474

95-
Ok(super::construct_data(base, dist))
96-
}
75+
Ok(Some(ActiveDistribution {
76+
dist: GamePlatform::Other,
77+
game_dir: game_dir.to_path_buf(),
78+
data_dir: game_dir.join(&r2mm.data_folder_name),
79+
exe_path,
80+
}))
9781
}

src/game/import/gamepass.rs

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,50 @@
11
use std::path::PathBuf;
22

3-
use super::{GameImporter, ImportBase};
3+
use super::ImportOverrides;
44
use crate::error::Error;
55
use crate::game::error::GameError;
6-
use crate::game::registry::{ActiveDistribution, GameData};
7-
use crate::ts::v1::models::ecosystem::GameDefPlatform;
6+
use crate::game::registry::ActiveDistribution;
7+
use crate::ts::v1::models::ecosystem::{GameDef, GamePlatform};
88
use crate::util::reg::{self, HKey};
99

10-
pub struct GamepassImporter {
11-
ident: String,
12-
}
13-
14-
impl GamepassImporter {
15-
pub fn new(ident: &str) -> GamepassImporter {
16-
GamepassImporter {
17-
ident: ident.into(),
18-
}
19-
}
20-
}
21-
22-
impl GameImporter for GamepassImporter {
23-
fn construct(self: Box<Self>, base: ImportBase) -> Result<GameData, Error> {
24-
let root = r#"Software\Microsoft\GamingServices\PackageRepository"#;
25-
26-
let uuid = reg::get_values_at(HKey::LocalMachine, &format!("{root}\\Package\\"))?
27-
.into_iter()
28-
.find(|x| x.key.starts_with(&self.ident))
29-
.ok_or_else(|| {
30-
GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
31-
})?
32-
.val
33-
.replace('\"', "");
34-
35-
let game_root = reg::get_keys_at(HKey::LocalMachine, &format!("Root\\{}\\", uuid))?
36-
.into_iter()
37-
.next()
38-
.ok_or_else(|| {
39-
GameError::NotFound(base.game_def.label.clone(), "Gamepass".to_string())
40-
})?;
41-
let game_dir = PathBuf::from(reg::get_value_at(HKey::LocalMachine, &game_root, "Root")?);
42-
43-
let r2mm = base.game_def.r2modman.as_ref().expect(
44-
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug.",
45-
);
46-
47-
let exe_path = base
48-
.overrides
49-
.custom_exe
50-
.clone()
51-
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
52-
.ok_or_else(|| GameError::ExeNotFound {
53-
possible_names: r2mm.exe_names.clone(),
54-
base_path: game_dir.clone(),
55-
})?;
56-
let dist = ActiveDistribution {
57-
dist: GameDefPlatform::GamePass {
58-
identifier: self.ident.to_string(),
59-
},
60-
game_dir: game_dir.to_path_buf(),
61-
data_dir: game_dir.join(&r2mm.data_folder_name),
62-
exe_path,
63-
};
64-
65-
Ok(super::construct_data(base, dist))
66-
}
10+
pub fn get_gamedist(ident: &str, game_def: &GameDef, overrides: &ImportOverrides) -> Result<Option<ActiveDistribution>, Error> {
11+
let root = r#"Software\Microsoft\GamingServices\PackageRepository"#;
12+
let r2mm = game_def.r2modman.as_ref().expect(
13+
"Expected a valid r2mm field in the ecosystem schema, got nothing. This is a bug."
14+
).first().unwrap();
15+
16+
let uuid = reg::get_values_at(HKey::LocalMachine, &format!("{root}\\Package\\"))?
17+
.into_iter()
18+
.find(|x| x.key.starts_with(ident))
19+
.ok_or_else(|| {
20+
GameError::NotFound(ident.into(), "Gamepass".to_string())
21+
})?
22+
.val
23+
.replace('\"', "");
24+
25+
let game_root = reg::get_keys_at(HKey::LocalMachine, &format!("Root\\{}\\", uuid))?
26+
.into_iter()
27+
.next()
28+
.ok_or_else(|| {
29+
GameError::NotFound(ident.into(), "Gamepass".to_string())
30+
})?;
31+
let game_dir = PathBuf::from(reg::get_value_at(HKey::LocalMachine, &game_root, "Root")?);
32+
33+
let exe_path = overrides
34+
.custom_exe
35+
.clone()
36+
.or_else(|| super::find_game_exe(&r2mm.exe_names, &game_dir))
37+
.ok_or_else(|| GameError::ExeNotFound {
38+
possible_names: r2mm.exe_names.clone(),
39+
base_path: game_dir.clone(),
40+
})?;
41+
42+
Ok(Some(ActiveDistribution {
43+
dist: GamePlatform::XboxGamePass {
44+
identifier: ident.to_string(),
45+
},
46+
game_dir: game_dir.to_path_buf(),
47+
data_dir: game_dir.join(&r2mm.data_folder_name),
48+
exe_path,
49+
}))
6750
}

0 commit comments

Comments
 (0)