Skip to main content

pacenotes_installer/
main.rs

1//! Pacenotes Installer: common controls, the developer self-test, then the wizard.
2//! The Win32-free logic lives in the sibling library (`lib.rs`).
3#![windows_subsystem = "windows"]
4#![cfg(windows)]
5// A panic here is a vanished window: final code shows a message instead.
6#![cfg_attr(
7    not(test),
8    deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)
9)]
10
11mod api;
12mod crash;
13mod elevation;
14mod game_process;
15mod http;
16mod i18n;
17mod package_download;
18mod run_header;
19mod selftest;
20mod single_instance;
21mod ui;
22mod wizard;
23mod worker;
24
25/// From `installer/.env`, read by build.rs; `--server <url>` overrides it, which
26/// is how the tests reach a local server.
27const DEFAULT_SERVER: &str = env!("PACENOTES_DEFAULT_SERVER");
28
29fn main() {
30    crash::install_panic_hook();
31
32    if let Some(selftest) = selftest::HttpSelfTest::from_args() {
33        std::process::exit(selftest.run());
34    }
35    if let Some(selftest) = selftest::DownloadSelfTest::from_args() {
36        std::process::exit(selftest.run());
37    }
38    if let Some(selftest) = selftest::ConflictReportSelfTest::from_args() {
39        std::process::exit(selftest.run());
40    }
41    selftest::panic_if_requested();
42
43    init_common_controls();
44
45    // Scene 1 is shown before the user can choose, so it starts in the OS
46    // language; a restart keeps the one already chosen.
47    let restart = elevation::Restart::from_args();
48    if let Some(language) = restart.as_ref().map(|restart| restart.language) {
49        i18n::set_language(language);
50    } else if let Some(os_language) = i18n::os_language() {
51        i18n::set_language(os_language);
52    }
53
54    let already_running = match restart {
55        Some(_) => single_instance::another_instance_outlasts_restart(),
56        None => single_instance::another_instance_running(),
57    };
58    if already_running {
59        ui::message_box_warning(
60            core::ptr::null_mut(),
61            &i18n::load_string(i18n::IDS_ALREADY_RUNNING),
62        );
63        return;
64    }
65
66    wizard::run(&server_url(), cache_override(), restart);
67}
68
69fn init_common_controls() {
70    let classes = windows_sys::Win32::UI::Controls::INITCOMMONCONTROLSEX {
71        dwSize: core::mem::size_of::<windows_sys::Win32::UI::Controls::INITCOMMONCONTROLSEX>()
72            as u32,
73        dwICC: windows_sys::Win32::UI::Controls::ICC_STANDARD_CLASSES
74            | windows_sys::Win32::UI::Controls::ICC_PROGRESS_CLASS
75            | windows_sys::Win32::UI::Controls::ICC_LISTVIEW_CLASSES
76            | windows_sys::Win32::UI::Controls::ICC_TAB_CLASSES
77            | windows_sys::Win32::UI::Controls::ICC_TREEVIEW_CLASSES,
78    };
79    unsafe { windows_sys::Win32::UI::Controls::InitCommonControlsEx(&classes) };
80}
81
82fn server_url() -> String {
83    argument_after("--server")
84        .map(|url| url.trim_end_matches('/').to_string())
85        .unwrap_or_else(|| DEFAULT_SERVER.to_string())
86}
87
88/// `--cache <dir>`, an override for development: the cache belongs in the game
89/// folder, which is not known until scene 3 settles one.
90fn cache_override() -> Option<std::path::PathBuf> {
91    argument_after("--cache").map(std::path::PathBuf::from)
92}
93
94fn argument_after(flag: &str) -> Option<String> {
95    let mut arguments = std::env::args().skip(1);
96    while let Some(argument) = arguments.next() {
97        if argument == flag {
98            return arguments.next();
99        }
100    }
101    None
102}