Skip to content

Commit 9dba76c

Browse files
committed
Added a game installation detector
1 parent e73a15a commit 9dba76c

File tree

6 files changed

+339
-52
lines changed

6 files changed

+339
-52
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
3333
### ✨ Major Features
3434
- Reworked the map loading mechanism to allow for maps to be loaded individually by @cohaereo
3535
- Added a map and activity browser by @cohaereo
36+
- Added a game installation detector by @cohaereo
3637

3738
### Added
3839

Cargo.lock

Lines changed: 107 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ build-time = "0.1.3"
8989
rustc-hash = "1.1.0"
9090
once_cell = "1.19.0"
9191
directories = "5.0.1"
92+
game-detector = "0.1.1"
9293

9394
[features]
9495
default = ["discord_rpc"]

src/game_selector.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use std::{mem::transmute, sync::Arc, time::Duration};
2+
3+
use game_detector::InstalledGame;
4+
use windows::Win32::{
5+
Foundation::DXGI_STATUS_OCCLUDED,
6+
Graphics::{
7+
Direct3D11::ID3D11Texture2D,
8+
Dxgi::{
9+
Common::DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_PRESENT_TEST, DXGI_SWAP_EFFECT_SEQUENTIAL,
10+
},
11+
},
12+
};
13+
use winit::{
14+
dpi::PhysicalSize,
15+
event::{Event, WindowEvent},
16+
event_loop::{ControlFlow, EventLoop},
17+
platform::run_return::EventLoopExtRunReturn,
18+
};
19+
20+
use crate::{
21+
icons::{ICON_CONTROLLER, ICON_MICROSOFT, ICON_STEAM},
22+
overlays::{
23+
big_button::BigButton,
24+
gui::{GuiManager, PreDrawResult},
25+
},
26+
render::DeviceContextSwapchain,
27+
resources::Resources,
28+
};
29+
30+
/// Creates a temporary window with egui to select a game installation
31+
/// This function should not be called in another render loop, as it will hang until this function completes
32+
pub fn select_game_installation(event_loop: &mut EventLoop<()>) -> anyhow::Result<String> {
33+
let window = winit::window::WindowBuilder::new()
34+
.with_title("Alkahest")
35+
.with_inner_size(PhysicalSize::new(320, 320))
36+
.with_min_inner_size(PhysicalSize::new(320, 480))
37+
.build(event_loop)?;
38+
39+
let window = Arc::new(window);
40+
41+
let dcs = Arc::new(DeviceContextSwapchain::create(&window)?);
42+
let mut gui = GuiManager::create(&window, dcs.clone());
43+
let mut empty_resources = Resources::default();
44+
45+
let mut present_parameters = 0;
46+
let mut selected_path = Err(anyhow::anyhow!("No game installation selected"));
47+
48+
let mut installations = game_detector::find_all_games();
49+
installations.retain(|i| match i {
50+
InstalledGame::Steam(a) => a.appid == 1085660,
51+
InstalledGame::EpicGames(m) => m.display_name == "Destiny 2",
52+
InstalledGame::MicrosoftStore(p) => p.app_name == "Destiny2PCbasegame",
53+
_ => false,
54+
});
55+
56+
event_loop.run_return(|event, _, control_flow| match &event {
57+
Event::WindowEvent { event, .. } => {
58+
let _ = gui.handle_event(event);
59+
60+
match event {
61+
WindowEvent::Resized(new_dims) => unsafe {
62+
let _ = gui
63+
.renderer
64+
.resize_buffers(transmute(&dcs.swap_chain), || {
65+
*dcs.swapchain_target.write() = None;
66+
dcs.swap_chain
67+
.ResizeBuffers(
68+
1,
69+
new_dims.width,
70+
new_dims.height,
71+
DXGI_FORMAT_B8G8R8A8_UNORM,
72+
0,
73+
)
74+
.expect("Failed to resize swapchain");
75+
76+
let bb: ID3D11Texture2D = dcs.swap_chain.GetBuffer(0).unwrap();
77+
78+
let new_rtv = dcs.device.CreateRenderTargetView(&bb, None).unwrap();
79+
80+
dcs.context()
81+
.OMSetRenderTargets(Some(&[Some(new_rtv.clone())]), None);
82+
83+
*dcs.swapchain_target.write() = Some(new_rtv);
84+
85+
transmute(0i32)
86+
})
87+
.unwrap();
88+
},
89+
WindowEvent::CloseRequested => {
90+
*control_flow = ControlFlow::Exit;
91+
}
92+
_ => (),
93+
}
94+
}
95+
Event::RedrawRequested(..) => {
96+
gui.draw_frame(
97+
window.clone(),
98+
&mut empty_resources,
99+
|ctx, _resources| {
100+
egui::CentralPanel::default().show(ctx, |ui| {
101+
ui.heading("Select Destiny 2 installation");
102+
for i in &installations {
103+
let (icon, store_name, path) = match i {
104+
InstalledGame::Steam(a) => {
105+
(ICON_STEAM, "Steam", a.game_path.clone())
106+
}
107+
InstalledGame::EpicGames(e) => {
108+
(ICON_CONTROLLER, "Epic Games", e.install_location.clone())
109+
}
110+
InstalledGame::MicrosoftStore(p) => {
111+
(ICON_MICROSOFT, "Microsoft Store", p.path.clone())
112+
}
113+
_ => continue,
114+
};
115+
116+
if BigButton::new(icon, store_name)
117+
.with_subtext(&path)
118+
.full_width()
119+
.ui(ui)
120+
.clicked()
121+
{
122+
selected_path = Ok(path.clone());
123+
*control_flow = ControlFlow::Exit;
124+
}
125+
}
126+
127+
// if BigButton::new(ICON_FOLDER_OPEN, "Browse")
128+
// .full_width()
129+
// .ui(ui)
130+
// .clicked()
131+
// {
132+
// let dialog = native_dialog::FileDialog::new()
133+
// .set_title("Select Destiny 2 packages directory")
134+
// .show_open_single_dir()?;
135+
// }
136+
});
137+
138+
PreDrawResult::Continue
139+
},
140+
|_, _| {},
141+
);
142+
143+
unsafe {
144+
if dcs
145+
.swap_chain
146+
.Present(DXGI_SWAP_EFFECT_SEQUENTIAL.0 as _, present_parameters)
147+
== DXGI_STATUS_OCCLUDED
148+
{
149+
present_parameters = DXGI_PRESENT_TEST;
150+
std::thread::sleep(Duration::from_millis(50));
151+
} else {
152+
present_parameters = 0;
153+
}
154+
}
155+
}
156+
Event::MainEventsCleared => {
157+
window.request_redraw();
158+
}
159+
_ => (),
160+
});
161+
162+
selected_path
163+
}

src/main.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ mod config;
113113
mod discord;
114114
mod dxbc;
115115
mod ecs;
116+
mod game_selector;
116117
mod hotkeys;
117118
mod icons;
118119
mod input;
@@ -210,6 +211,7 @@ pub async fn main() -> anyhow::Result<()> {
210211
)
211212
.expect("Failed to set up the tracing subscriber");
212213

214+
let mut event_loop = EventLoop::new();
213215
let package_dir = if let Some(p) = &args.package_dir {
214216
if p.ends_with(".pkg") {
215217
warn!("Please specify the directory containing the packages, not the package itself! Support for this will be removed in the future!");
@@ -224,9 +226,25 @@ pub async fn main() -> anyhow::Result<()> {
224226
} else if let Some(p) = config::with(|c| c.packages_directory.clone()) {
225227
PathBuf::from_str(&p).context("Invalid package directory")?
226228
} else {
227-
panic!("No package directory specified")
229+
let path = PathBuf::from_str(
230+
&game_selector::select_game_installation(&mut event_loop)
231+
.context("No game installation selected")?,
232+
)
233+
.unwrap();
234+
235+
path.join("packages")
228236
};
229237

238+
if !package_dir.exists() {
239+
config::with_mut(|c| c.packages_directory = None);
240+
config::persist();
241+
242+
panic!(
243+
"The specified package directory does not exist! ({})\nRelaunch alkahest with a valid package directory.",
244+
package_dir.display()
245+
);
246+
}
247+
230248
let pm = info_span!("Initializing package manager")
231249
.in_scope(|| PackageManager::new(package_dir, PackageVersion::Destiny2Lightfall).unwrap());
232250

@@ -235,7 +253,6 @@ pub async fn main() -> anyhow::Result<()> {
235253

236254
*PACKAGE_MANAGER.write() = Some(Arc::new(pm));
237255

238-
let event_loop = EventLoop::new();
239256
let window = winit::window::WindowBuilder::new()
240257
.with_title("Alkahest")
241258
.with_inner_size(config::with(|c| {

0 commit comments

Comments
 (0)