Compare commits

..

8 Commits
v2.0.1 ... main

Author SHA1 Message Date
7117b65cf5 meow
Some checks failed
Build & Release / build-windows (push) Has been cancelled
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 17:34:02 +03:00
e6404f781c meow
Some checks failed
Build & Release / build-windows (push) Has been cancelled
2026-04-28 06:32:09 +03:00
7e46f9a710 fix
Some checks failed
Build & Release / build-windows (push) Has been cancelled
2026-04-28 06:16:56 +03:00
5b9646ff75 mrow
Some checks failed
Build & Release / build-windows (push) Has been cancelled
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 06:13:23 +03:00
3e51a44e30 fix
Some checks failed
Build & Release / build-windows (push) Has been cancelled
2026-04-28 06:09:14 +03:00
e04660b93b Refactor user info properties to use snake_case naming convention 2026-04-28 06:05:13 +03:00
808420e601 s
Some checks failed
Build & Release / build-windows (push) Has been cancelled
2026-04-28 05:57:06 +03:00
58145f97a0 no way i forgot those
Some checks failed
Build & Release / build-windows (push) Has been cancelled
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 05:44:29 +03:00
9 changed files with 186 additions and 125 deletions

View File

@ -1,4 +1,4 @@
# RLidentity v2.0.0 # RLidentity v2.0.1
> **Be anyone, Win everything.** > **Be anyone, Win everything.**

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",

40
src-tauri/Cargo.lock generated
View File

@ -2,6 +2,26 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "RLIdentity"
version = "2.0.1"
dependencies = [
"dirs 5.0.1",
"hex",
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2",
"sysinfo",
"tauri",
"tauri-build",
"tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-updater",
"tokio",
"window-vibrancy 0.5.3",
]
[[package]] [[package]]
name = "adler2" name = "adler2"
version = "2.0.1" version = "2.0.1"
@ -3452,26 +3472,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rlidentitygui"
version = "2.0.0"
dependencies = [
"dirs 5.0.1",
"hex",
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2",
"sysinfo",
"tauri",
"tauri-build",
"tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-updater",
"tokio",
"window-vibrancy 0.5.3",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"

View File

@ -1,8 +1,8 @@
[package] [package]
name = "rlidentitygui" name = "RLIdentity"
version = "2.0.0" version = "2.0.1"
description = "A Tauri App" description = "check out https://rlidentity.me for more info!"
authors = ["you"] authors = ["bits", "danni :3"]
edition = "2021" edition = "2021"
[lib] [lib]

View File

@ -14,6 +14,6 @@
"opener:default", "opener:default",
"updater:default", "updater:default",
"process:allow-restart", "process:allow-restart",
"default" "allow-main-commands"
] ]
} }

View File

@ -0,0 +1,16 @@
{
"identifier": "allow-main-commands",
"description": "Allows RLIdentity core commands",
"commands": {
"allow": [
"minimize_to_tray",
"save_config",
"inject_dll",
"validate_key",
"check_status",
"get_hwid",
"get_app_version",
"download_assets"
]
}
}

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

@ -1,11 +1,12 @@
use reqwest::Client; use reqwest::Client;
use serde::Serialize; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::sync::Mutex;
use tokio::fs; use tokio::fs;
use tauri::{Manager, State, WebviewWindow}; use tauri::{Manager, State, WebviewWindow};
use window_vibrancy::apply_acrylic; use window_vibrancy::apply_acrylic;
use sysinfo::System; use sysinfo::{ProcessRefreshKind, RefreshKind, System};
use sha2::{Sha256, Digest}; use sha2::{Sha256, Digest};
// --- types --- // --- types ---
@ -16,26 +17,43 @@ pub struct Status {
pub is_injected: bool, pub is_injected: bool,
} }
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
pub struct UserInfo { pub struct UserInfo {
pub userId: Option<String>, #[serde(rename = "userId")]
pub discordId: Option<String>, pub user_id: Option<String>,
pub epicId: Option<String>, #[serde(rename = "discordId")]
pub discord_id: Option<String>,
#[serde(rename = "epicId")]
pub epic_id: Option<String>,
pub username: Option<String>, pub username: Option<String>,
pub globalName: Option<String>, #[serde(rename = "globalName")]
pub global_name: Option<String>,
pub logins: Option<i32>, pub logins: Option<i32>,
} }
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
pub struct KeyValidationResponse { pub struct KeyValidationResponse {
pub status: String, pub status: String,
pub user: Option<UserInfo>, pub user: Option<UserInfo>,
} }
// global state for optimization #[derive(Serialize, Deserialize)]
struct SaveConfigPayload {
#[serde(rename = "spoofedName")]
name: String,
platform: String,
}
#[derive(Deserialize)]
struct AssetManifest {
injector_hash: String,
dll_hash: String,
}
struct AppState { struct AppState {
client: Client, client: Client,
app_data: PathBuf, app_data: PathBuf,
sys: Mutex<System>,
} }
// --- helpers --- // --- helpers ---
@ -53,9 +71,8 @@ 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 {
// direct registry query for speed
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"])
.output(); .output();
@ -68,12 +85,10 @@ fn get_hwid() -> String {
} }
} }
} }
"UNKNOWN-HWID".to_string() "00000000-0000-0000-0000-000000000000".to_string()
} }
#[tauri::command(rename_all = "snake_case")]
#[tauri::command]
async fn validate_key( async fn validate_key(
key: String, key: String,
hwid: String, hwid: String,
@ -87,98 +102,104 @@ async fn validate_key(
.await .await
.map_err(|e| format!("network error: {}", e))?; .map_err(|e| format!("network error: {}", e))?;
let json: serde_json::Value = res.json().await.map_err(|e| format!("json error: {}", e))?; res.json::<KeyValidationResponse>()
.await
let status = json.get("status").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(); .map_err(|e| format!("api schema mismatch: {}", e))
let user = json.get("user").map(|u| UserInfo {
userId: u.get("userId").and_then(|v| v.as_str().map(|s| s.to_string()).or_else(|| v.as_i64().map(|n| n.to_string()))),
discordId: u.get("discordId").and_then(|v| v.as_str()).map(|s| s.to_string()),
epicId: u.get("epicId").and_then(|v| v.as_str()).map(|s| s.to_string()),
username: u.get("username").and_then(|v| v.as_str()).map(|s| s.to_string()),
globalName: u.get("globalName").and_then(|v| v.as_str()).map(|s| s.to_string()),
logins: u.get("logins").and_then(|v| v.as_i64()).map(|n| n as i32),
});
Ok(KeyValidationResponse { status, user })
} }
#[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 mut s = System::new_all(); let is_running = {
s.refresh_processes(); let mut sys = state.sys.lock().unwrap();
if s.processes_by_exact_name("RocketLeague.exe").next().is_none() { sys.refresh_processes_specifics(ProcessRefreshKind::new());
let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
};
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, wait for update".into()); return Err("files missing, please update".into());
} }
let output = Command::new(injector_path) let output = Command::new(injector_path)
.arg("RocketLeague.exe") .arg("RocketLeague.exe")
.arg(dll_path) .arg(&dll_path)
.output() .output()
.map_err(|e| format!("exec failed: {}", e))?; .map_err(|e| format!("exec failed: {}", e))?;
if output.status.success() { if output.status.success() {
Ok("injected".into()) Ok("injected".into())
} else { } else {
Err("injection failed".into()) Err("injection failed: check admin privileges".into())
} }
} }
#[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())?;
// in a real scenario, you'd fetch these hashes from your api first let manifest: AssetManifest = state.client.get("https://api.rlidentity.me/manifest")
.send().await.map_err(|e| e.to_string())?
.json().await.map_err(|e| e.to_string())?;
let assets = [ let assets = [
( ("injector.exe", "https://git.rlidentity.me/bits/RLidentity/raw/branch/main/injector.exe", manifest.injector_hash),
"injector.exe", ("RLIdentity.dll", "https://git.rlidentity.me/bits/RLidentity/raw/branch/main/RLIdentity.dll", manifest.dll_hash),
"https://git.rlidentity.me/bits/RLidentity/src/branch/dll/injector.exe",
"B447D618886EEDE9F6A331A5605BFC40FADEB3F508916D8B19916467EC8E0E69"
),
(
"RLIdentity.dll",
"https://git.rlidentity.me/bits/RLidentity/src/branch/main/RLIdentity.dll",
"69108E3E1084EA9AE6AC97D4F19D68356213FEF3A29193D63CE3A6069D333CD8"
),
]; ];
for (name, url, expected_hash) in assets { for (name, url, expected_hash) in assets {
let file_path = state.app_data.join(name); let file_path = state.app_data.join(name);
// download
let res = state.client.get(url).send().await.map_err(|e| e.to_string())?; let res = state.client.get(url).send().await.map_err(|e| e.to_string())?;
let bytes = res.bytes().await.map_err(|e| e.to_string())?; let bytes = res.bytes().await.map_err(|e| e.to_string())?;
// verify integrity (signature check)
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(&bytes); hasher.update(&bytes);
let actual_hash = hex::encode(hasher.finalize()); let actual_hash = hex::encode(hasher.finalize());
if actual_hash != expected_hash { if actual_hash != expected_hash {
return Err(format!("integrity check failed for {}: hash mismatch", name)); return Err(format!("integrity check failed for {}", name));
} }
// only write if the "signature" (hash) is correct
fs::write(file_path, bytes).await.map_err(|e| e.to_string())?; fs::write(file_path, bytes).await.map_err(|e| e.to_string())?;
} }
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn check_status() -> Status { async fn check_status(state: State<'_, AppState>) -> Result<Status, String> {
let mut s = System::new_all(); let is_running = {
s.refresh_processes(); let mut sys = state.sys.lock().unwrap();
let is_running = s.processes_by_exact_name("RocketLeague.exe").next().is_some(); sys.refresh_processes_specifics(ProcessRefreshKind::new());
Status { is_running, is_injected: false } let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
};
Ok(Status { is_running, is_injected: false })
} }
#[tauri::command] #[tauri::command(rename_all = "snake_case")]
async fn save_config(name: String, platform: String, state: State<'_, AppState>) -> Result<(), String> {
let config_path = state.app_data.join("config.json");
let payload = SaveConfigPayload { name, platform };
if !state.app_data.exists() {
fs::create_dir_all(&state.app_data).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(rename_all = "snake_case")]
fn get_app_version(app: tauri::AppHandle) -> String {
app.package_info().version.to_string()
}
#[tauri::command(rename_all = "snake_case")]
fn minimize_to_tray(window: WebviewWindow) { fn minimize_to_tray(window: WebviewWindow) {
let _ = window.hide(); let _ = window.hide();
} }
@ -190,20 +211,23 @@ pub fn run() {
.manage(AppState { .manage(AppState {
client: Client::builder() client: Client::builder()
.danger_accept_invalid_certs(false) .danger_accept_invalid_certs(false)
.timeout(std::time::Duration::from_secs(30))
.build() .build()
.unwrap(), .unwrap(),
app_data: dirs::data_dir().unwrap().join("RLidentity"), app_data: dirs::data_dir().expect("could not find data dir").join("RLidentity"),
sys: Mutex::new(System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::new())
)),
}) })
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
minimize_to_tray, minimize_to_tray,
inject_dll, inject_dll,
validate_key, validate_key,
check_status, check_status,
get_hwid, get_hwid,
download_assets download_assets,
save_config,
get_app_version
]) ])
.setup(|app| { .setup(|app| {
let window = app.get_webview_window("main").unwrap(); let window = app.get_webview_window("main").unwrap();
@ -211,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

@ -12,11 +12,11 @@ type Status =
| string; | string;
interface UserInfo { interface UserInfo {
userId: string | null; user_id: string | null;
discordId: string | null; discord_id: string | null;
epicId: string | null; epic_id: string | null;
username: string | null; username: string | null;
globalName: string | null; global_name: string | null;
logins?: number; logins?: number;
} }
@ -87,7 +87,7 @@ export default function App() {
const initialPlatform = useMemo(() => localStorage.getItem(LS_KEYS.platform) ?? "Epic", []); const initialPlatform = useMemo(() => localStorage.getItem(LS_KEYS.platform) ?? "Epic", []);
const initialAutoInject = useMemo(() => localStorage.getItem(LS_KEYS.autoInject) === "true", []); const initialAutoInject = useMemo(() => localStorage.getItem(LS_KEYS.autoInject) === "true", []);
const initialTheme = useMemo(() => (localStorage.getItem(LS_KEYS.theme) ?? "phantom") as ThemeId, []); const initialTheme = useMemo(() => (localStorage.getItem(LS_KEYS.theme) ?? "phantom") as ThemeId, []);
const [version, setVersion] = useState('');
const [apiKey, setApiKey] = useState(initialApiKey); const [apiKey, setApiKey] = useState(initialApiKey);
const [spoofedUsername, setSpoofedUsername] = useState(initialSpoofed); const [spoofedUsername, setSpoofedUsername] = useState(initialSpoofed);
const [isAuthorized, setIsAuthorized] = useState(false); const [isAuthorized, setIsAuthorized] = useState(false);
@ -138,17 +138,32 @@ export default function App() {
useEffect(() => { useEffect(() => {
if (initialApiKey) authorize(initialApiKey); if (initialApiKey) authorize(initialApiKey);
syncAssetsAndCheckUpdates(); syncAssetsAndCheckUpdates();
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 {
setStatus("syncing assets...");
await tryInvoke("download_assets"); await tryInvoke("download_assets");
setStatus("ready");
} catch (e) { } catch (e) {
console.error("Failed to sync assets:", e); console.error("Failed to sync assets:", e);
setStatus("Sync Error: " + String(e));
} }
checkForUpdates(); checkForUpdates();
} }
async function checkForUpdates() { async function checkForUpdates() {
if (!isTauriRuntime()) return; if (!isTauriRuntime()) return;
@ -240,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?.discordId }); 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);
@ -300,9 +315,11 @@ export default function App() {
style={{ cursor: "default" }} style={{ cursor: "default" }}
/> />
<div className="titlebar-text" data-tauri-drag-region> <div className="titlebar-text" data-tauri-drag-region>
<div className="app-name neon-text-soft"> <div className="app-name neon-text-soft">
RLidentity <span style={{ fontSize: "10px", opacity: 0.6, marginLeft: "4px" }}>v2.0.0</span> RLidentity <span style={{ fontSize: "10px", opacity: 0.6, marginLeft: "4px" }}>
</div> v{version || "2.0.1"}
</span>
</div>
<div className="app-slogan">{isRevoked ? "License Revoked" : "Authorize to continue"}</div> <div className="app-slogan">{isRevoked ? "License Revoked" : "Authorize to continue"}</div>
</div> </div>
</div> </div>
@ -387,9 +404,9 @@ export default function App() {
<main className="panel-wrap"> <main className="panel-wrap">
<header className="welcome-section"> <header className="welcome-section">
<h2 className="welcome-text"> <h2 className="welcome-text">
Welcome, <span className="neon-text-soft">{userData?.globalName || userData?.username || "User"}</span> Welcome, <span className="neon-text-soft">{userData?.global_name || userData?.username || "User"}</span>
</h2> </h2>
<div className="user-id-badge">User #{userData?.userId || "0"}</div> <div className="user-id-badge">User #{userData?.user_id || "0"}</div>
</header> </header>
<section className="glass-card neon-ring"> <section className="glass-card neon-ring">