diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..bc2788c --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- Close riot client if dolos closes +- Read which games are installed and only show the ones installed \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cfad2fb..6b77228 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dolos" -version = "0.1.0" +version = "0.1.1" description = "Dolos Desktop Application" default-run = "dolos" license = "GPLv3" @@ -16,7 +16,7 @@ tauri-build = { version = "1.5.0", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.5.2", features = [ "updater", "system-tray"] } +tauri = { version = "1.5.2", features = [ "window-close", "updater", "system-tray"] } tokio = { version = "1.34.0", features = ["full"] } hyper = { version = "1", features = ["full"] } http-body-util = "0.1" @@ -27,6 +27,7 @@ tokio-native-tls = "0.3.1" jsonwebtoken = "9.1.0" quick-xml = { version = "0.31.0", features = ["async-tokio"] } sysinfo = "0.29.11" +winapi = "0.3.9" [features] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3c4502a..9330e7a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,6 +6,7 @@ mod utils; use std::{error::Error, env, fs}; +use tauri::Manager; use tokio::{sync::OnceCell, process::Command}; pub static RIOT_CLIENT_PATH: OnceCell = OnceCell::const_new(); @@ -13,9 +14,6 @@ pub static RIOT_CLIENT_PATH: OnceCell = OnceCell::const_new(); type DolosResult = Result>; fn main() { - // TODO: Check if riot client is running at boot, close if so - // TODO: Close riot client if dolos closes - // utils::is_running(); let file_path = format!( "{}\\Riot Games\\RiotClientInstalls.json", env::var("ProgramData").expect("[Dolos] [Main] Could not find program data folder") @@ -23,13 +21,31 @@ fn main() { let data = serde_json::from_str(&String::from_utf8_lossy(&fs::read(file_path).expect("[Dolos] [Main] Could not read riot installs config"))).expect("[Dolos] [Main] Could not parse riot installs config"); RIOT_CLIENT_PATH.set(utils::choose_channel(data).unwrap()).expect("[Dolos] [Main] Could not set RIOT_CLIENT_PATH"); + let open_pids = utils::get_pids(); + let open_pids_c = open_pids.clone(); + tauri::Builder::default() .invoke_handler(tauri::generate_handler![launch_game]) - .setup(|_app| { + .on_page_load(move |window, _| { + if !open_pids_c.is_empty() { + window.eval("showRiotClientPopup()").unwrap(); + window.eval(&format!("gamesToClose = {}", open_pids_c.iter().count())).unwrap(); + } + }) + .setup(|app| { + + let handle = app.handle(); + handle.once_global("closeClients", move |_| { + for (pid, name) in open_pids { + utils::kill_process(pid, name) + } + }); + tauri::async_runtime::spawn(async { tokio::spawn(tcp_proxy::proxy_tcp_chat()); tokio::spawn(http_proxy::listen_http()); }); + Ok(()) }) .run(tauri::generate_context!()) @@ -37,7 +53,7 @@ fn main() { } #[tauri::command] -fn launch_game(game: &str) -> () { +fn launch_game(game: &str) { Command::new(RIOT_CLIENT_PATH.get().unwrap()) .arg(format!("--client-config-url=http://127.0.0.1:{}", http_proxy::HTTP_PORT.get().unwrap())) .arg(format!("--launch-product={}", game)) diff --git a/src-tauri/src/tcp_proxy.rs b/src-tauri/src/tcp_proxy.rs index 70041ae..dd0ebd7 100644 --- a/src-tauri/src/tcp_proxy.rs +++ b/src-tauri/src/tcp_proxy.rs @@ -120,7 +120,6 @@ async fn rewrite_presence(data: &str) -> Vec { let mut inside_show = false; let mut inside_game = false; - let mut inside_game_st = false; loop { match reader.read_event() { @@ -131,21 +130,15 @@ async fn rewrite_presence(data: &str) -> Vec { }, Ok(Event::Start(e)) if e.name().as_ref() == b"league_of_legends" || e.name().as_ref() == b"valorant" => { inside_game = true; - writer.write_event_async(Event::Start(e.clone())).await.unwrap(); - }, - Ok(Event::Start(e)) if inside_game && e.name().as_ref() == b"st" => { - inside_game_st = true; - writer.write_event_async(Event::Start(e.clone())).await.unwrap(); }, + Ok(Event::Start(_)) if inside_game => {} Ok(Event::Start(e)) if e.name().as_ref() == b"status" => {}, // Tag insides Ok(Event::Text(_)) if inside_show => { writer.write_event_async(Event::Text(BytesText::new("offline"))).await.unwrap(); }, - Ok(Event::Text(_)) if inside_game && inside_game_st => { - writer.write_event_async(Event::Text(BytesText::new("offline"))).await.unwrap(); - }, + Ok(Event::Text(_)) if inside_game => {}, // Tag ends Ok(Event::End(e)) if inside_show => { @@ -154,13 +147,9 @@ async fn rewrite_presence(data: &str) -> Vec { }, Ok(Event::End(e)) if e.name().as_ref() == b"league_of_legends" || e.name().as_ref() == b"valorant" => { inside_game = false; - writer.write_event_async(Event::End(e)).await.unwrap(); - }, - Ok(Event::End(e)) if inside_game_st && e.name().as_ref() == b"st" => { - inside_game_st = false; - writer.write_event_async(Event::End(e)).await.unwrap(); }, Ok(Event::End(e)) if e.name().as_ref() == b"status" => {}, + Ok(Event::End(_)) if inside_game => {}, // Other Ok(Event::Eof) => break, diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 5245a7d..844d631 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,5 +1,8 @@ use serde_json::Value; -use sysinfo::{System, SystemExt, ProcessExt}; +use sysinfo::{System, SystemExt, ProcessExt, PidExt}; +use winapi::um::{processthreadsapi::{OpenProcess, TerminateProcess}, winnt::PROCESS_ALL_ACCESS, handleapi::CloseHandle, errhandlingapi::GetLastError}; + +const PROCESS_NAMES: [&str; 3] = ["RiotClientServices.exe", "LeagueClient.exe", "VALORANT-Win64-Shipping.exe"]; pub fn choose_channel(data: Value) -> Option { let keys = ["rc_default", "rc_live", "rc_beta"]; @@ -13,7 +16,29 @@ pub fn choose_channel(data: Value) -> Option { None } -pub fn is_running() -> bool { +pub fn get_pids() -> Vec<(u32, String)> { let sys = System::new_all(); - sys.processes().values().find(|p| p.name() == "RiotClientServices.exe").is_some() + sys.processes().iter().filter_map(|(pid, process)| { + if PROCESS_NAMES.contains(&process.name()) { + Some((pid.as_u32(), process.name().to_string())) + } else { + None + } + }).collect::>() +} + +pub fn kill_process(pid: u32, name: String) { + unsafe { + let process_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + if process_handle.is_null() { + eprintln!("[DOLOS] Failed to open handle to {}. Error code: {}", name, GetLastError()); + } + + if TerminateProcess(process_handle, 0) == 0 { + eprintln!("[DOLOS] Failed to terminate {}. Error code: {}", name, GetLastError()) + } else { + println!("[DOLOS] Successfully terminated {}", name); + }; + CloseHandle(process_handle); + } } \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6b0fe13..180bc92 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,11 +8,13 @@ }, "package": { "productName": "Dolos", - "version": "0.1.0" + "version": "0.1.1" }, "tauri": { "allowlist": { - "all": false + "window": { + "close": true + } }, "systemTray": { "iconPath": "icons/icon.png", diff --git a/ui/assets/styles.css b/ui/assets/styles.css new file mode 100644 index 0000000..51ced31 --- /dev/null +++ b/ui/assets/styles.css @@ -0,0 +1,143 @@ +body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #202020; + font-family: Arial, sans-serif; + overflow: hidden; + user-select: none; + position: relative; +} +.header { + position: absolute; + top: 5px; +} +.header h1 { + margin-right: 25px; + text-align: center; + font-size: 40px; + color: whitesmoke; + font-weight: bold; +} +.header p { + margin-top: 5px; + color: rgba(128, 128, 128, 0.925); +} +.header img { + width: 50px; + height: auto; + margin-right: 10px; + position: relative; + top: 15px; +} +.game-container { + position: absolute; + top: 170px; + display: grid; + grid-gap: 20px; + grid-template-columns: repeat(2, 1fr); + width: 80%; +} +.game-card { + background: #303030; + border-radius: 20px; + color: white; + text-align: center; + padding: 20px; + transition: 0.3s; + cursor:pointer; +} +.game-card:hover { + transform: scale(1.1); +} +.game-card img { + width: 100%; + height: 100%; + border-radius: 20px; +} + +.overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + justify-content: center; + align-items: center; + z-index: 1000; +} + +.loading-container { + text-align: center; + color: white; + font-size: x-large; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.loading-icon { + border: 4px solid #f3f3f3; + border-top: 4px solid #b52712; + border-radius: 50%; + width: 50px; + height: 50px; + animation: spin 1s linear infinite; + margin-bottom: 10px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.popup { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + justify-content: center; + align-items: center; + z-index: 1001; +} + +.popup-content { + background: #303030; + color: rgba(245, 245, 245, 0.767); + padding: 20px; + border-radius: 10px; + text-align: center; + width: 40%; + margin: auto; + position: relative; + top: 40%; +} + +.popup-content p { + margin-bottom: 20px; +} + +.btn-yes, +.btn-no { + background-color: #4CAF50; + color: rgba(245, 245, 245, 0.842);; + padding: 10px 20px; + margin: 0 10px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +.btn-no { + background-color: #f44336; +} \ No newline at end of file diff --git a/ui/index.html b/ui/index.html index 151d6bc..971be8e 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,107 +1,8 @@ - - + +
@@ -126,7 +27,17 @@

+ + \ No newline at end of file