meow
Some checks failed
Build & Release / build-windows (push) Has been cancelled

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
bits 2026-04-28 17:34:02 +03:00
parent e6404f781c
commit 7117b65cf5
4 changed files with 74 additions and 46 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "rlidentitygui", "name": "RLIdentity",
"private": true, "private": true,
"version": "2.0.0", "version": "2.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -13,5 +13,6 @@ commands.allow = [
"inject_dll", "inject_dll",
"validate_key", "validate_key",
"check_status", "check_status",
"get_hwid" "get_hwid",
"download_assets"
] ]

View File

@ -37,6 +37,13 @@ pub struct KeyValidationResponse {
pub user: Option<UserInfo>, pub user: Option<UserInfo>,
} }
#[derive(Serialize, Deserialize)]
struct SaveConfigPayload {
#[serde(rename = "spoofedName")]
name: String,
platform: String,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct AssetManifest { struct AssetManifest {
injector_hash: String, injector_hash: String,
@ -64,7 +71,7 @@ async fn get_last_epic_id(base_path: &Path) -> String {
// --- commands --- // --- commands ---
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
fn get_hwid() -> String { fn get_hwid() -> String {
let output = Command::new("reg") let output = Command::new("reg")
.args(["query", r"HKLM\SOFTWARE\Microsoft\Cryptography", "/v", "MachineGuid"]) .args(["query", r"HKLM\SOFTWARE\Microsoft\Cryptography", "/v", "MachineGuid"])
@ -78,11 +85,10 @@ fn get_hwid() -> String {
} }
} }
} }
// strictly avoid returning "unknown" to prevent key sharing bypasses
"00000000-0000-0000-0000-000000000000".to_string() "00000000-0000-0000-0000-000000000000".to_string()
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn validate_key( async fn validate_key(
key: String, key: String,
hwid: String, hwid: String,
@ -101,19 +107,20 @@ async fn validate_key(
.map_err(|e| format!("api schema mismatch: {}", e)) .map_err(|e| format!("api schema mismatch: {}", e))
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn inject_dll(state: State<'_, AppState>) -> Result<String, String> { async fn inject_dll(state: State<'_, AppState>) -> Result<String, String> {
let injector_path = state.app_data.join("injector.exe"); let injector_path = state.app_data.join("injector.exe");
let dll_path = state.app_data.join("RLIdentity.dll"); let dll_path = state.app_data.join("RLIdentity.dll");
{ let is_running = {
let mut sys = state.sys.lock().unwrap(); let mut sys = state.sys.lock().unwrap();
// fix: use refresh_processes_specifics and add the remove_dead_processes bool sys.refresh_processes_specifics(ProcessRefreshKind::new());
sys.refresh_processes_specifics(ProcessRefreshKind::new()); let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
if sys.processes_by_exact_name("RocketLeague.exe").next().is_none() { };
if !is_running {
return Err("rocket league is not running".into()); return Err("rocket league is not running".into());
} }
}
if !injector_path.exists() || !dll_path.exists() { if !injector_path.exists() || !dll_path.exists() {
return Err("files missing, please update".into()); return Err("files missing, please update".into());
@ -132,11 +139,10 @@ sys.refresh_processes_specifics(ProcessRefreshKind::new());
} }
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn download_assets(state: State<'_, AppState>) -> Result<(), String> { async fn download_assets(state: State<'_, AppState>) -> Result<(), String> {
fs::create_dir_all(&state.app_data).await.map_err(|e| e.to_string())?; fs::create_dir_all(&state.app_data).await.map_err(|e| e.to_string())?;
// dynamically fetch latest hashes to avoid brittle hardcoding
let manifest: AssetManifest = state.client.get("https://api.rlidentity.me/manifest") let manifest: AssetManifest = state.client.get("https://api.rlidentity.me/manifest")
.send().await.map_err(|e| e.to_string())? .send().await.map_err(|e| e.to_string())?
.json().await.map_err(|e| e.to_string())?; .json().await.map_err(|e| e.to_string())?;
@ -164,29 +170,36 @@ async fn download_assets(state: State<'_, AppState>) -> Result<(), String> {
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn check_status(state: State<'_, AppState>) -> Result<Status, String> { async fn check_status(state: State<'_, AppState>) -> Result<Status, String> {
let is_running = {
let mut sys = state.sys.lock().unwrap(); let mut sys = state.sys.lock().unwrap();
// fix: use refresh_processes_specifics and add the remove_dead_processes bool sys.refresh_processes_specifics(ProcessRefreshKind::new());
sys.refresh_processes_specifics(ProcessRefreshKind::new()); let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
let is_running = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); };
Ok(Status { is_running, is_injected: false }) Ok(Status { is_running, is_injected: false })
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn save_config(config_data: String, state: State<'_, AppState>) -> Result<(), String> { async fn save_config(name: String, platform: String, state: State<'_, AppState>) -> Result<(), String> {
let config_path = state.app_data.join("config.json"); let config_path = state.app_data.join("config.json");
let temp_path = state.app_data.join("config.json.tmp"); let payload = SaveConfigPayload { name, platform };
// atomic write: write to tmp then rename to prevent corruption on crash if !state.app_data.exists() {
fs::write(&temp_path, config_data).await.map_err(|e| e.to_string())?; fs::create_dir_all(&state.app_data).await.map_err(|e| e.to_string())?;
fs::rename(temp_path, config_path).await.map_err(|e| e.to_string()) }
let json = serde_json::to_string(&payload).map_err(|e| e.to_string())?;
fs::write(config_path, json).await.map_err(|e| e.to_string())
} }
#[tauri::command]
#[tauri::command(rename_all = "snake_case")]
fn get_app_version(app: tauri::AppHandle) -> String { fn get_app_version(app: tauri::AppHandle) -> String {
app.package_info().version.to_string() app.package_info().version.to_string()
} }
#[tauri::command]
#[tauri::command(rename_all = "snake_case")]
fn minimize_to_tray(window: WebviewWindow) { fn minimize_to_tray(window: WebviewWindow) {
let _ = window.hide(); let _ = window.hide();
} }
@ -222,23 +235,26 @@ pub fn run() {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
apply_acrylic(&window, Some((18, 18, 18, 125))).ok(); apply_acrylic(&window, Some((18, 18, 18, 125))).ok();
let handle = app.handle().clone(); let monitor_handle = app.handle().clone();
let tray_menu = tauri::menu::Menu::with_items(app, &[ tauri::async_runtime::spawn(async move {
&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?, let mut last_seen_running = false;
])?; loop {
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let _ = tauri::tray::TrayIconBuilder::new() let state = monitor_handle.state::<AppState>();
.icon(app.default_window_icon().unwrap().clone()) let is_running = {
.menu(&tray_menu) let mut sys = state.sys.lock().unwrap();
.on_menu_event(move |_app, event| { sys.refresh_processes_specifics(ProcessRefreshKind::new());
if event.id().as_ref() == "quit" { handle.exit(0); } let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
}) };
.on_tray_icon_event(|tray, event| {
if let tauri::tray::TrayIconEvent::Click { button: tauri::tray::MouseButton::Left, .. } = event { if is_running && !last_seen_running {
let _ = tray.app_handle().get_webview_window("main").unwrap().show(); println!("auto-detect: rocket league started. injecting...");
let _ = inject_dll(state).await;
} }
}) last_seen_running = is_running;
.build(app)?; }
});
Ok(()) Ok(())
}) })

View File

@ -138,9 +138,20 @@ export default function App() {
useEffect(() => { useEffect(() => {
if (initialApiKey) authorize(initialApiKey); if (initialApiKey) authorize(initialApiKey);
syncAssetsAndCheckUpdates(); syncAssetsAndCheckUpdates();
tryInvoke("get_app_version").then(v => setVersion(v as string)); loadAppVersion();
}, []); }, []);
async function loadAppVersion() {
if (!isTauriRuntime()) return;
try {
const app = await import("@tauri-apps/api/app");
setVersion(await app.getVersion());
} catch (e) {
console.error("Failed to get app version:", e);
setVersion("");
}
}
async function syncAssetsAndCheckUpdates() { async function syncAssetsAndCheckUpdates() {
if (!isTauriRuntime()) return; if (!isTauriRuntime()) return;
try { try {
@ -244,7 +255,7 @@ export default function App() {
async function inject() { async function inject() {
setStatus("injecting..."); setStatus("injecting...");
try { try {
const res = await tryInvoke<string>("inject_dll", { discordId: userData?.discord_id }); const res = await tryInvoke<string>("inject_dll");
setLastLog(res || "Successfully Injected!"); setLastLog(res || "Successfully Injected!");
setStatus("Successfully Injected!"); setStatus("Successfully Injected!");
window.setTimeout(() => setStatus("ready"), 1400); window.setTimeout(() => setStatus("ready"), 1400);