Compare commits
2 Commits
808420e601
...
3e51a44e30
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e51a44e30 | |||
| e04660b93b |
@ -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,36 @@ 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(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 ---
|
||||||
@ -55,7 +66,6 @@ async fn get_last_epic_id(base_path: &Path) -> String {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
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,11 +78,10 @@ fn get_hwid() -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"UNKNOWN-HWID".to_string()
|
// strictly avoid returning "unknown" to prevent key sharing bypasses
|
||||||
|
"00000000-0000-0000-0000-000000000000".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn validate_key(
|
async fn validate_key(
|
||||||
key: String,
|
key: String,
|
||||||
@ -87,19 +96,9 @@ 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]
|
||||||
@ -107,26 +106,29 @@ 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();
|
{
|
||||||
s.refresh_processes();
|
let mut sys = state.sys.lock().unwrap();
|
||||||
if s.processes_by_exact_name("RocketLeague.exe").next().is_none() {
|
// fix: use refresh_processes_specifics and add the remove_dead_processes bool
|
||||||
return Err("rocket league is not running".into());
|
sys.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||||
|
if sys.processes_by_exact_name("RocketLeague.exe").next().is_none() {
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,59 +136,51 @@ async fn inject_dll(state: State<'_, AppState>) -> Result<String, String> {
|
|||||||
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
|
// dynamically fetch latest hashes to avoid brittle hardcoding
|
||||||
|
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/raw/branch/main/injector.exe",
|
|
||||||
"a5fa657f6336d257d1c29808f4f7df6366a6110a1191a7c704c32b16a491bd14"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"RLIdentity.dll",
|
|
||||||
"https://git.rlidentity.me/bits/RLidentity/raw/branch/main/RLIdentity.dll",
|
|
||||||
"24ca50f56904d7a042f08ce87f2e027eb8b7006d1f12dd2512b3a7efbfc7bba6"
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
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]
|
||||||
fn get_app_version(app: tauri::AppHandle) -> String {
|
async fn check_status(state: State<'_, AppState>) -> Result<Status, String> {
|
||||||
app.package_info().version.to_string()
|
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());
|
||||||
|
let is_running = sys.processes_by_exact_name("RocketLeague.exe").next().is_some();
|
||||||
|
Ok(Status { is_running, is_injected: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn check_status() -> Status {
|
|
||||||
let mut s = System::new_all();
|
|
||||||
s.refresh_processes();
|
|
||||||
let is_running = s.processes_by_exact_name("RocketLeague.exe").next().is_some();
|
|
||||||
Status { is_running, is_injected: false }
|
|
||||||
}
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn save_config(config_data: String, state: State<'_, AppState>) -> Result<(), String> {
|
async fn save_config(config_data: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||||
let config_path = state.app_data.join("config.json");
|
let config_path = state.app_data.join("config.json");
|
||||||
fs::write(config_path, config_data)
|
let temp_path = state.app_data.join("config.json.tmp");
|
||||||
.await
|
|
||||||
.map_err(|e| format!("failed to save config: {}", e))
|
// atomic write: write to tmp then rename to prevent corruption on crash
|
||||||
|
fs::write(&temp_path, config_data).await.map_err(|e| e.to_string())?;
|
||||||
|
fs::rename(temp_path, config_path).await.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -201,13 +195,14 @@ 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,
|
||||||
@ -215,7 +210,6 @@ pub fn run() {
|
|||||||
check_status,
|
check_status,
|
||||||
get_hwid,
|
get_hwid,
|
||||||
download_assets,
|
download_assets,
|
||||||
get_app_version,
|
|
||||||
save_config
|
save_config
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
@ -246,4 +240,4 @@ pub fn run() {
|
|||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
21
src/App.tsx
21
src/App.tsx
@ -1,6 +1,5 @@
|
|||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState, useEffect } from "react";
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
const [version, setVersion] = useState('');
|
|
||||||
type Status =
|
type Status =
|
||||||
| "ready"
|
| "ready"
|
||||||
| "saved successfully"
|
| "saved successfully"
|
||||||
@ -13,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ export default function App() {
|
|||||||
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 [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);
|
||||||
const [isRevoked, setIsRevoked] = useState(false);
|
const [isRevoked, setIsRevoked] = useState(false);
|
||||||
@ -245,7 +244,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", { discordId: userData?.discord_id });
|
||||||
setLastLog(res || "Successfully Injected!");
|
setLastLog(res || "Successfully Injected!");
|
||||||
setStatus("Successfully Injected!");
|
setStatus("Successfully Injected!");
|
||||||
window.setTimeout(() => setStatus("ready"), 1400);
|
window.setTimeout(() => setStatus("ready"), 1400);
|
||||||
@ -394,9 +393,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">
|
||||||
@ -457,7 +456,7 @@ export default function App() {
|
|||||||
<h2 className="modal-title neon-text-soft">System Credits</h2>
|
<h2 className="modal-title neon-text-soft">System Credits</h2>
|
||||||
<div className="credits-grid glass-input">
|
<div className="credits-grid glass-input">
|
||||||
{[
|
{[
|
||||||
{ role: "Lead Dev & Owner", name: "Bits", accent: true },
|
{ role: "Lead Dev & Owner", name: "Bits", accent: true },
|
||||||
{ role: "Dev & Admin", name: "Danni" },
|
{ role: "Dev & Admin", name: "Danni" },
|
||||||
{ role: "Co-Owner", name: "Deniz" },
|
{ role: "Co-Owner", name: "Deniz" },
|
||||||
{ role: "Administrator", name: "Kairo" },
|
{ role: "Administrator", name: "Kairo" },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user