Skip to main content

pacenotes_installer/
game_process.rs

1/// Whether `game` is running from `folder`, which holds its files open so an
2/// install would fail partway (P12). Another copy of the game elsewhere does
3/// not lock this folder, so the process's own path decides; one whose path
4/// cannot be read — say, running elevated while we are not — counts as
5/// running, since refusing is the safe answer.
6pub fn is_running_from(game: pacenotes_installer::gamedir::Game, folder: &str) -> bool {
7    let marker = game.marker().to_lowercase();
8    // Windows reports a process's path with `\` and no doubled separator; the
9    // folder field may hold `/` or a trailing `\`. Done by hand: std's
10    // canonicalize cost 3.5 kB.
11    let folder = folder.replace('/', "\\");
12    let expected = format!("{}\\{marker}", folder.trim_end_matches('\\').to_lowercase());
13    unsafe {
14        let snapshot = windows_sys::Win32::System::Diagnostics::ToolHelp::CreateToolhelp32Snapshot(
15            windows_sys::Win32::System::Diagnostics::ToolHelp::TH32CS_SNAPPROCESS,
16            0,
17        );
18        if snapshot == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
19            return false;
20        }
21        let mut entry: windows_sys::Win32::System::Diagnostics::ToolHelp::PROCESSENTRY32W =
22            core::mem::zeroed();
23        entry.dwSize = core::mem::size_of_val(&entry) as u32;
24        let mut found = false;
25        let mut more = windows_sys::Win32::System::Diagnostics::ToolHelp::Process32FirstW(
26            snapshot, &mut entry,
27        );
28        while more != 0 && !found {
29            if wide_text(&entry.szExeFile).to_lowercase() == marker {
30                found = image_path(entry.th32ProcessID).is_none_or(|path| path == expected);
31            }
32            more = windows_sys::Win32::System::Diagnostics::ToolHelp::Process32NextW(
33                snapshot, &mut entry,
34            );
35        }
36        windows_sys::Win32::Foundation::CloseHandle(snapshot);
37        found
38    }
39}
40
41/// The full path of a process's executable, lowercased, or `None` when it
42/// cannot be opened.
43unsafe fn image_path(process_id: u32) -> Option<String> {
44    let process = windows_sys::Win32::System::Threading::OpenProcess(
45        windows_sys::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION,
46        0,
47        process_id,
48    );
49    if process.is_null() {
50        return None;
51    }
52    let mut buffer = [0u16; 1024];
53    let mut length = buffer.len() as u32;
54    let read = windows_sys::Win32::System::Threading::QueryFullProcessImageNameW(
55        process,
56        0,
57        buffer.as_mut_ptr(),
58        &mut length,
59    );
60    windows_sys::Win32::Foundation::CloseHandle(process);
61    (read != 0).then(|| wide_text(&buffer[..length as usize]).to_lowercase())
62}
63
64fn wide_text(wide: &[u16]) -> String {
65    let end = wide
66        .iter()
67        .position(|&unit| unit == 0)
68        .unwrap_or(wide.len());
69    String::from_utf16_lossy(&wide[..end])
70}