Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7117b65cf5 | |||
| e6404f781c | |||
| 7e46f9a710 | |||
| 5b9646ff75 | |||
| 3e51a44e30 | |||
| e04660b93b | |||
| 808420e601 | |||
| 58145f97a0 | |||
| 2e9d26e674 | |||
| 9992e42eff | |||
| 8a77c6e769 | |||
| 311dca1db9 | |||
| c57d70ddca | |||
| 37563fa53d | |||
| d35ea4444f | |||
| 5a928b0b1b | |||
| 5cf74a49ce | |||
| 0865606056 | |||
| f417559290 | |||
| 403292d71c | |||
| 9ecabfb778 | |||
| 2d60399989 | |||
| c6d8b61a00 | |||
| ee0e403195 | |||
| edd5cb5ca5 | |||
| 7eb1cbffae | |||
| a641bf4246 | |||
| 9c3d0bf4b1 | |||
| f50e8b3727 | |||
| 0b9fa9acd1 |
42
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Build & Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: x86_64-pc-windows-msvc
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
src-tauri/target
|
||||||
|
key: windows-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
|
||||||
|
- uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
CARGO_BUILD_JOBS: "2"
|
||||||
|
with:
|
||||||
|
tagName: ${{ github.ref_name }}
|
||||||
|
releaseName: RLidentity ${{ github.ref_name }}
|
||||||
|
releaseBody: New release
|
||||||
|
releaseDraft: false
|
||||||
66
.gitignore
vendored
@ -22,3 +22,69 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Node / package managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
# Vite / build caches
|
||||||
|
.vite/
|
||||||
|
.cache/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
/node_modules/
|
||||||
|
# TypeScript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Tauri / Rust
|
||||||
|
src-tauri/target/
|
||||||
|
target/
|
||||||
|
/dist/tauri
|
||||||
|
|
||||||
|
# CMake / build artifacts
|
||||||
|
build/
|
||||||
|
build-x64/
|
||||||
|
cmake-build-debug/
|
||||||
|
CMakeFiles/
|
||||||
|
bin/
|
||||||
|
lib/
|
||||||
|
Debug/
|
||||||
|
Release/
|
||||||
|
*.obj
|
||||||
|
*.o
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.key
|
||||||
|
*.pvk
|
||||||
|
*.pfx
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
*.cer
|
||||||
|
# IDEs
|
||||||
|
.idea/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs again (catch-all)
|
||||||
|
*.log
|
||||||
|
|
||||||
|
|||||||
11
README.md
@ -1,10 +1,12 @@
|
|||||||
# RLidentity v2.0.0
|
# RLidentity v2.0.1
|
||||||
|
|
||||||
> **Be anyone, Win everything.**
|
> **Be anyone, Win everything.**
|
||||||
|
|
||||||
RLidentity is a modern, high-performance identity management tool for Rocket League. It features a sleek, glass-morphism GUI built with Tauri and React, backed by a powerful C++ injection system.
|

|
||||||
|
|
||||||
|
|
||||||
|
RLidentity is a modern, high-performance Name Spoofing tool for Rocket League. It features a sleek, glass-morphism GUI built with Tauri and React, backed by a powerful C++ injection system.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -42,10 +44,7 @@ The DLL and Injector are built using Visual Studio 2022 (v143 toolset). Ensure `
|
|||||||
|
|
||||||
* **Lead Dev & Owner**: Bits
|
* **Lead Dev & Owner**: Bits
|
||||||
* **Dev & Admin**: Danni
|
* **Dev & Admin**: Danni
|
||||||
* **Co-Owner**: Deniz
|
|
||||||
* **Administrator**: Kairo
|
|
||||||
* **Helpers**: Quinn, sndr
|
* **Helpers**: Quinn, sndr
|
||||||
* **Tester**: Emir
|
|
||||||
|
|
||||||
## 🔗 Links
|
## 🔗 Links
|
||||||
|
|
||||||
|
|||||||
BIN
RLIdentity.dll
Normal file
BIN
injector.exe
Normal 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",
|
||||||
|
|||||||
16
src-tauri/.gitignore
vendored
@ -1,3 +1,19 @@
|
|||||||
|
# src-tauri (Rust / Tauri) ignores
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
/target/
|
||||||
|
**/target/
|
||||||
|
|
||||||
|
# Tauri build artifacts
|
||||||
|
/.tauri/build/
|
||||||
|
/.tauri/bundle/
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
|
|||||||
38
src-tauri/Cargo.lock
generated
@ -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,24 +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",
|
|
||||||
"reqwest 0.12.28",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"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"
|
||||||
|
|||||||
@ -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]
|
||||||
@ -13,9 +13,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = { version = '0.12', features = ['json'] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
tokio = { version = '1', features = ['full'] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
window-vibrancy = "0.5.2"
|
|
||||||
tauri = { version = "2", features = ["tray-icon", "image-png"] }
|
tauri = { version = "2", features = ["tray-icon", "image-png"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@ -24,4 +23,7 @@ sysinfo = "0.30"
|
|||||||
dirs = "5"
|
dirs = "5"
|
||||||
tauri-plugin-updater = "2.10.0"
|
tauri-plugin-updater = "2.10.0"
|
||||||
tauri-plugin-process = "2.3.1"
|
tauri-plugin-process = "2.3.1"
|
||||||
|
sha2 = "0.10"
|
||||||
|
hex = "0.4"
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
window-vibrancy = "0.5.2"
|
||||||
|
|||||||
@ -14,6 +14,6 @@
|
|||||||
"opener:default",
|
"opener:default",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
"process:allow-restart",
|
"process:allow-restart",
|
||||||
"default"
|
"allow-main-commands"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 186 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 649 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
16
src-tauri/permissions/allow-main-commands.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,5 +13,6 @@ commands.allow = [
|
|||||||
"inject_dll",
|
"inject_dll",
|
||||||
"validate_key",
|
"validate_key",
|
||||||
"check_status",
|
"check_status",
|
||||||
"get_hwid"
|
"get_hwid",
|
||||||
|
"download_assets"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
use reqwest;
|
use reqwest::Client;
|
||||||
use tauri::{Manager, WebviewWindow};
|
use serde::{Deserialize, Serialize};
|
||||||
use window_vibrancy::apply_acrylic;
|
use std::path::{Path, PathBuf};
|
||||||
use serde::Serialize;
|
|
||||||
use std::fs;
|
|
||||||
use sysinfo::System;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::path::Path;
|
use std::sync::Mutex;
|
||||||
|
use tokio::fs;
|
||||||
|
use tauri::{Manager, State, WebviewWindow};
|
||||||
|
use window_vibrancy::apply_acrylic;
|
||||||
|
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
|
||||||
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
|
// --- types ---
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
@ -13,245 +17,245 @@ 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[derive(Serialize, Deserialize)]
|
||||||
fn minimize_to_tray(window: WebviewWindow) {
|
struct SaveConfigPayload {
|
||||||
let _ = window.hide();
|
#[serde(rename = "spoofedName")]
|
||||||
|
name: String,
|
||||||
|
platform: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[derive(Deserialize)]
|
||||||
fn get_hwid() -> String {
|
struct AssetManifest {
|
||||||
// Simple HWID using Windows UUID
|
injector_hash: String,
|
||||||
let output = Command::new("wmic")
|
dll_hash: String,
|
||||||
.args(["csproduct", "get", "uuid"])
|
|
||||||
.output()
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Some(out) = output {
|
|
||||||
let s = String::from_utf8_lossy(&out.stdout);
|
|
||||||
let lines: Vec<&str> = s.lines().collect();
|
|
||||||
if lines.len() >= 2 {
|
|
||||||
return lines[1].trim().to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"UNKNOWN-HWID".to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_epic_id() -> String {
|
struct AppState {
|
||||||
if let Some(mut path) = dirs::data_dir() {
|
client: Client,
|
||||||
path.push("RLidentity");
|
app_data: PathBuf,
|
||||||
path.push("last_epic_id.txt");
|
sys: Mutex<System>,
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(content) = fs::read_to_string(path) {
|
// --- helpers ---
|
||||||
|
|
||||||
|
async fn get_last_epic_id(base_path: &Path) -> String {
|
||||||
|
let path = base_path.join("last_epic_id.txt");
|
||||||
|
if let Ok(content) = fs::read_to_string(path).await {
|
||||||
let trimmed = content.trim();
|
let trimmed = content.trim();
|
||||||
if trimmed.len() == 32 {
|
if trimmed.len() == 32 {
|
||||||
return trimmed.to_string();
|
return trimmed.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
// --- commands ---
|
||||||
async fn validate_key(key: String, hwid: String) -> Result<KeyValidationResponse, String> {
|
|
||||||
let epic_id = get_last_epic_id();
|
|
||||||
// Added epicId query parameter to the URL
|
|
||||||
let url = format!(
|
|
||||||
"https://api.rlidentity.me/keys/{}?hwid={}&epicId={}",
|
|
||||||
key, hwid, epic_id
|
|
||||||
);
|
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
#[tauri::command(rename_all = "snake_case")]
|
||||||
.danger_accept_invalid_certs(true)
|
fn get_hwid() -> String {
|
||||||
.build()
|
let output = Command::new("reg")
|
||||||
.map_err(|e| format!("Client Error: {}", e))?;
|
.args(["query", r"HKLM\SOFTWARE\Microsoft\Cryptography", "/v", "MachineGuid"])
|
||||||
|
.output();
|
||||||
|
|
||||||
println!("[LOG] Connecting to: {}", url);
|
if let Ok(out) = output {
|
||||||
println!("[LOG] Sending Epic ID: {}", epic_id);
|
let s = String::from_utf8_lossy(&out.stdout);
|
||||||
|
if let Some(guid) = s.split_whitespace().last() {
|
||||||
|
if guid.len() == 36 && guid.contains('-') {
|
||||||
|
return guid.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"00000000-0000-0000-0000-000000000000".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
let res = client.get(&url)
|
#[tauri::command(rename_all = "snake_case")]
|
||||||
|
async fn validate_key(
|
||||||
|
key: String,
|
||||||
|
hwid: String,
|
||||||
|
state: State<'_, AppState>
|
||||||
|
) -> Result<KeyValidationResponse, String> {
|
||||||
|
let epic_id = get_last_epic_id(&state.app_data).await;
|
||||||
|
let url = format!("https://api.rlidentity.me/keys/{}?hwid={}&epicId={}", key, hwid, epic_id);
|
||||||
|
|
||||||
|
let res = state.client.get(&url)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| format!("network error: {}", e))?;
|
||||||
let err_msg = format!("Network Error: {}. Is the server on 443?", e);
|
|
||||||
println!("[ERROR] {}", err_msg);
|
|
||||||
err_msg
|
|
||||||
})?;
|
|
||||||
|
|
||||||
println!("[LOG] HTTP Status: {}", res.status());
|
res.json::<KeyValidationResponse>()
|
||||||
|
.await
|
||||||
let json: serde_json::Value = res.json().await.map_err(|e| {
|
.map_err(|e| format!("api schema mismatch: {}", e))
|
||||||
let err_msg = format!("JSON Parse Error: {}", e);
|
|
||||||
println!("[ERROR] {}", err_msg);
|
|
||||||
err_msg
|
|
||||||
})?;
|
|
||||||
|
|
||||||
println!("[LOG] Server Payload: {:?}", json);
|
|
||||||
|
|
||||||
let status = json.get("status").and_then(|v| v.as_str()).unwrap_or("unknown").to_string();
|
|
||||||
|
|
||||||
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 save_config(name: String, platform: String) -> Result<(), String> {
|
async fn inject_dll(state: State<'_, AppState>) -> Result<String, String> {
|
||||||
let mut path = dirs::data_dir().ok_or("Could not find AppData")?;
|
let injector_path = state.app_data.join("injector.exe");
|
||||||
path.push("RLidentity");
|
let dll_path = state.app_data.join("RLIdentity.dll");
|
||||||
fs::create_dir_all(&path).map_err(|e| e.to_string())?;
|
|
||||||
path.push("config.json");
|
|
||||||
|
|
||||||
let json = serde_json::json!({
|
let is_running = {
|
||||||
"spoofedName": name,
|
let mut sys = state.sys.lock().unwrap();
|
||||||
"platform": platform
|
sys.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||||
});
|
let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
|
||||||
|
};
|
||||||
|
|
||||||
fs::write(path, serde_json::to_string_pretty(&json).unwrap()).map_err(|e| e.to_string())?;
|
if !is_running {
|
||||||
Ok(())
|
return Err("rocket league is not running".into());
|
||||||
}
|
|
||||||
|
|
||||||
#[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]
|
|
||||||
async fn download_assets() -> Result<(), String> {
|
|
||||||
let mut path = dirs::data_dir().ok_or("Could not find AppData")?;
|
|
||||||
path.push("RLidentity");
|
|
||||||
fs::create_dir_all(&path).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
let assets = [
|
|
||||||
("injector.exe", "https://git.rlidentity.me/bits/RLidentity/raw/branch/dll/injector.exe"),
|
|
||||||
("RLIdentity.dll", "https://git.rlidentity.me/bits/RLidentity/raw/branch/dll/RLIdentity.dll"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (name, url) in assets {
|
|
||||||
let mut file_path = path.clone();
|
|
||||||
file_path.push(name);
|
|
||||||
|
|
||||||
let response = client.get(url).send().await.map_err(|e| e.to_string())?;
|
|
||||||
let bytes = response.bytes().await.map_err(|e| e.to_string())?;
|
|
||||||
fs::write(file_path, bytes).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn inject_dll(_discordId: Option<String>) -> Result<String, String> {
|
|
||||||
let mut base_path = dirs::data_dir().ok_or("Could not find AppData")?;
|
|
||||||
base_path.push("RLidentity");
|
|
||||||
|
|
||||||
let injector_path = base_path.join("injector.exe");
|
|
||||||
let dll_path = base_path.join("RLIdentity.dll");
|
|
||||||
|
|
||||||
let mut s = System::new_all();
|
|
||||||
s.refresh_processes();
|
|
||||||
if s.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("Required files missing. Please wait for update to finish.".into());
|
return Err("files missing, please update".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the injector and capture FULL output
|
|
||||||
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!("Execution failed: {}", e))?;
|
.map_err(|e| format!("exec failed: {}", e))?;
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
||||||
|
|
||||||
let full_log = format!("STDOUT:\n{}\n\nSTDERR:\n{}", stdout, stderr);
|
|
||||||
println!("[LOG] Injector results:\n{}", full_log);
|
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Ok(format!("Successfully injected!\n\n{}", stdout))
|
Ok("injected".into())
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Injection failed!\n\n{}", full_log))
|
Err("injection failed: check admin privileges".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(rename_all = "snake_case")]
|
||||||
|
async fn download_assets(state: State<'_, AppState>) -> Result<(), String> {
|
||||||
|
fs::create_dir_all(&state.app_data).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
("injector.exe", "https://git.rlidentity.me/bits/RLidentity/raw/branch/main/injector.exe", manifest.injector_hash),
|
||||||
|
("RLIdentity.dll", "https://git.rlidentity.me/bits/RLidentity/raw/branch/main/RLIdentity.dll", manifest.dll_hash),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, url, expected_hash) in assets {
|
||||||
|
let file_path = state.app_data.join(name);
|
||||||
|
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 mut hasher = Sha256::new();
|
||||||
|
hasher.update(&bytes);
|
||||||
|
let actual_hash = hex::encode(hasher.finalize());
|
||||||
|
|
||||||
|
if actual_hash != expected_hash {
|
||||||
|
return Err(format!("integrity check failed for {}", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(file_path, bytes).await.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command(rename_all = "snake_case")]
|
||||||
|
async fn check_status(state: State<'_, AppState>) -> Result<Status, String> {
|
||||||
|
let is_running = {
|
||||||
|
let mut sys = state.sys.lock().unwrap();
|
||||||
|
sys.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||||
|
let x = sys.processes_by_exact_name("RocketLeague.exe").next().is_some(); x
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Status { is_running, is_injected: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) {
|
||||||
|
let _ = window.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- main ---
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.manage(AppState {
|
||||||
.plugin(tauri_plugin_process::init())
|
client: Client::builder()
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.danger_accept_invalid_certs(false)
|
||||||
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
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())
|
||||||
|
)),
|
||||||
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
minimize_to_tray,
|
minimize_to_tray,
|
||||||
save_config,
|
|
||||||
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 icon_bytes = include_bytes!("../icons/32x32.png");
|
|
||||||
let icon = tauri::image::Image::from_bytes(icon_bytes)?;
|
|
||||||
|
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
window.set_icon(icon.clone())?;
|
|
||||||
|
|
||||||
#[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, "tray_quit", "Quit", true, None::<&str>)?,
|
let mut last_seen_running = false;
|
||||||
])?;
|
loop {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||||
|
|
||||||
tauri::tray::TrayIconBuilder::new()
|
let state = monitor_handle.state::<AppState>();
|
||||||
.icon(icon)
|
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() == "tray_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 {
|
if is_running && !last_seen_running {
|
||||||
button: tauri::tray::MouseButton::Left,
|
println!("auto-detect: rocket league started. injecting...");
|
||||||
..
|
let _ = inject_dll(state).await;
|
||||||
} = event {
|
|
||||||
let app = tray.app_handle();
|
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
|
||||||
let _ = window.show();
|
|
||||||
let _ = window.set_focus();
|
|
||||||
}
|
}
|
||||||
|
last_seen_running = is_running;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.build(app)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "rlidentitygui",
|
"productName": "rlidentitygui",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"identifier": "me.rlidentity.gui",
|
"identifier": "me.rlidentity.gui",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
@ -19,24 +19,29 @@
|
|||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"transparent": false,
|
"transparent": false,
|
||||||
"decorations": false,
|
"decorations": false,
|
||||||
"devtools": true
|
"devtools": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": "default-src 'self'; connect-src 'self' https://api.rlidentity.me https://git.rlidentity.me"
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://api.rlidentity.me/version"
|
"https://api.rlidentity.me/version"
|
||||||
],
|
],
|
||||||
"pubkey": "DWY+YmX5uY2E3N0N3Q0N3Q0N3Q0N3Q0N3Q0N3Q0N3Q0N3Q=="
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEFGNUM0MTdBMUQxQTBBQzUKUldURkNob2Rla0Zjcng4TWxpSWh3QVhlQ2xFZnRZUE5Ock1KQmk0T3ZkV25EQ1R2dWZrNWZPNUEK"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
|
"resources": [
|
||||||
|
"E:\\projects\\Rocket League\\RLIdentityDLL\\injector.exe",
|
||||||
|
"E:\\projects\\Rocket League\\RLIdentityDLL\\RLIdentity.dll"
|
||||||
|
],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
1
src-tauri/updater-keys.key.pub
Normal file
@ -0,0 +1 @@
|
|||||||
|
dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEFGNUM0MTdBMUQxQTBBQzUKUldURkNob2Rla0Zjcng4TWxpSWh3QVhlQ2xFZnRZUE5Ock1KQmk0T3ZkV25EQ1R2dWZrNWZPNUEK
|
||||||
896
src/App.css
388
src/App.tsx
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,16 +25,28 @@ interface KeyValidationResponse {
|
|||||||
user: UserInfo | null;
|
user: UserInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const THEMES = [
|
||||||
|
{ id: "phantom", label: "Phantom", color: "#a855f7" },
|
||||||
|
{ id: "glacier", label: "Glacier", color: "#38bdf8" },
|
||||||
|
{ id: "inferno", label: "Inferno", color: "#f97316" },
|
||||||
|
{ id: "matrix", label: "Matrix", color: "#00ff41" },
|
||||||
|
{ id: "synthwave", label: "Synthwave", color: "#f72585" },
|
||||||
|
{ id: "eclipse", label: "Eclipse", color: "#fbbf24" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type ThemeId = typeof THEMES[number]["id"];
|
||||||
|
|
||||||
const LS_KEYS = {
|
const LS_KEYS = {
|
||||||
spoofed: "neonGlass.spoofedUsername",
|
spoofed: "rlidentity.spoofedUsername",
|
||||||
apiKey: "neonGlass.apiKey",
|
apiKey: "rlidentity.apiKey",
|
||||||
minimizeToTray: "neonGlass.minimizeToTray",
|
minimizeToTray: "rlidentity.minimizeToTray",
|
||||||
platform: "neonGlass.platform",
|
platform: "rlidentity.platform",
|
||||||
autoInject: "neonGlass.autoInject",
|
autoInject: "rlidentity.autoInject",
|
||||||
|
theme: "rlidentity.theme",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const GITHUB_URL = "https://github.com/RLidentity";
|
const GITHUB_URL = "https://git.rlidentity.me/bits/rlidentity";
|
||||||
const FAQ_URL = "https://rlidentity.me/faq";
|
const FAQ_URL = "https://rlidentity.me/#faq";
|
||||||
|
|
||||||
function isTauriRuntime() {
|
function isTauriRuntime() {
|
||||||
return typeof window !== "undefined" && typeof (window as any).__TAURI_INTERNALS__ !== "undefined";
|
return typeof window !== "undefined" && typeof (window as any).__TAURI_INTERNALS__ !== "undefined";
|
||||||
@ -56,7 +68,6 @@ async function tryInvoke<T>(cmd: string, args?: Record<string, unknown>) {
|
|||||||
async function openUrl(url: string) {
|
async function openUrl(url: string) {
|
||||||
const fallback = () => window.open(url, "_blank", "noopener,noreferrer");
|
const fallback = () => window.open(url, "_blank", "noopener,noreferrer");
|
||||||
if (!isTauriRuntime()) return fallback();
|
if (!isTauriRuntime()) return fallback();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mod: any = await import("@tauri-apps/plugin-opener");
|
const mod: any = await import("@tauri-apps/plugin-opener");
|
||||||
if (typeof mod.openUrl === "function") {
|
if (typeof mod.openUrl === "function") {
|
||||||
@ -75,7 +86,8 @@ export default function App() {
|
|||||||
const initialMinToTray = useMemo(() => localStorage.getItem(LS_KEYS.minimizeToTray) === "true", []);
|
const initialMinToTray = useMemo(() => localStorage.getItem(LS_KEYS.minimizeToTray) === "true", []);
|
||||||
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 [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);
|
||||||
@ -91,8 +103,12 @@ export default function App() {
|
|||||||
const [platform, setPlatform] = useState(initialPlatform);
|
const [platform, setPlatform] = useState(initialPlatform);
|
||||||
const [autoInject, setAutoInject] = useState(initialAutoInject);
|
const [autoInject, setAutoInject] = useState(initialAutoInject);
|
||||||
const [platformPickerOpen, setPlatformPickerOpen] = useState(false);
|
const [platformPickerOpen, setPlatformPickerOpen] = useState(false);
|
||||||
|
const [theme, setTheme] = useState<ThemeId>(initialTheme);
|
||||||
|
|
||||||
// Easter Egg State
|
// Update modal
|
||||||
|
const [pendingUpdate, setPendingUpdate] = useState<{ version: string; install: () => Promise<void> } | null>(null);
|
||||||
|
|
||||||
|
// Easter egg
|
||||||
const [debugOpen, setDebugOpen] = useState(false);
|
const [debugOpen, setDebugOpen] = useState(false);
|
||||||
const [logoClicks, setLogoClicks] = useState(0);
|
const [logoClicks, setLogoClicks] = useState(0);
|
||||||
|
|
||||||
@ -112,31 +128,42 @@ export default function App() {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [logoClicks]);
|
}, [logoClicks]);
|
||||||
|
|
||||||
// Tutorial State
|
// Apply theme
|
||||||
const [tutorialStep, setTutorialStep] = useState(-1);
|
|
||||||
|
|
||||||
// Startup Authorization & Update Check
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialApiKey) {
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
authorize(initialApiKey);
|
localStorage.setItem(LS_KEYS.theme, theme);
|
||||||
}
|
}, [theme]);
|
||||||
|
|
||||||
|
// Startup
|
||||||
|
useEffect(() => {
|
||||||
|
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;
|
||||||
|
|
||||||
// 1. Download DLL and Injector
|
|
||||||
try {
|
try {
|
||||||
|
setStatus("syncing assets...");
|
||||||
await tryInvoke("download_assets");
|
await tryInvoke("download_assets");
|
||||||
console.log("Assets synced successfully");
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check for App updates
|
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
if (!isTauriRuntime()) return;
|
if (!isTauriRuntime()) return;
|
||||||
@ -144,46 +171,37 @@ export default function App() {
|
|||||||
const { check } = await import("@tauri-apps/plugin-updater");
|
const { check } = await import("@tauri-apps/plugin-updater");
|
||||||
const update = await check();
|
const update = await check();
|
||||||
if (update) {
|
if (update) {
|
||||||
console.log(`Update available: ${update.version}`);
|
setPendingUpdate({
|
||||||
const confirmed = window.confirm(`A new version (${update.version}) is available. Would you like to update?`);
|
version: update.version,
|
||||||
if (confirmed) {
|
install: async () => {
|
||||||
setStatus("Updating...");
|
setStatus("Updating...");
|
||||||
await update.downloadAndInstall();
|
await update.downloadAndInstall();
|
||||||
// The app will restart automatically after install on some platforms,
|
|
||||||
// or we might need to relaunch. Tauri v2 updater usually handles this.
|
|
||||||
const { relaunch } = await import("@tauri-apps/plugin-process");
|
const { relaunch } = await import("@tauri-apps/plugin-process");
|
||||||
await relaunch();
|
await relaunch();
|
||||||
}
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to check for updates:", e);
|
console.error("Failed to check for updates:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync revoked background to body
|
// Revoked bg
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isRevoked) {
|
document.body.classList.toggle("revoked-bg", isRevoked);
|
||||||
document.body.classList.add('revoked-bg');
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('revoked-bg');
|
|
||||||
}
|
|
||||||
}, [isRevoked]);
|
}, [isRevoked]);
|
||||||
|
|
||||||
// Poll for Rocket League status & Auto Inject
|
// Poll RL status + auto-inject
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthorized || isRevoked) return;
|
if (!isAuthorized || isRevoked) return;
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await tryInvoke<{is_running: boolean}>("check_status");
|
const res = await tryInvoke<{ is_running: boolean }>("check_status");
|
||||||
if (res) {
|
if (res) {
|
||||||
const wasRunning = rlStatus === "RL Running";
|
const wasRunning = rlStatus === "RL Running";
|
||||||
const isRunning = res.is_running;
|
const isRunning = res.is_running;
|
||||||
setRlStatus(isRunning ? "RL Running" : "RL Closed");
|
setRlStatus(isRunning ? "RL Running" : "RL Closed");
|
||||||
|
if (!wasRunning && isRunning && autoInject) inject();
|
||||||
// Auto Inject Logic: if it just started running and autoInject is on
|
|
||||||
if (!wasRunning && isRunning && autoInject) {
|
|
||||||
inject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -193,39 +211,31 @@ export default function App() {
|
|||||||
}, [isAuthorized, isRevoked, rlStatus, autoInject]);
|
}, [isAuthorized, isRevoked, rlStatus, autoInject]);
|
||||||
|
|
||||||
async function authorize(keyToTry: string) {
|
async function authorize(keyToTry: string) {
|
||||||
if (!keyToTry.trim()) {
|
if (!keyToTry.trim()) { setStatus("Please enter a key"); return; }
|
||||||
setStatus("Please enter a key");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setStatus("validating key...");
|
setStatus("validating key...");
|
||||||
setIsRevoked(false);
|
setIsRevoked(false);
|
||||||
try {
|
try {
|
||||||
const hwid = await tryInvoke<string>("get_hwid") || "UNKNOWN";
|
const hwid = await tryInvoke<string>("get_hwid") || "UNKNOWN-HWID";
|
||||||
const res = await tryInvoke<KeyValidationResponse>("validate_key", { key: keyToTry.trim(), hwid });
|
const res = await tryInvoke<KeyValidationResponse>("validate_key", { key: keyToTry.trim(), hwid });
|
||||||
|
|
||||||
if (res && res.status === "valid") {
|
if (res?.status === "valid") {
|
||||||
localStorage.setItem(LS_KEYS.apiKey, keyToTry.trim());
|
localStorage.setItem(LS_KEYS.apiKey, keyToTry.trim());
|
||||||
setUserData(res.user);
|
setUserData(res.user);
|
||||||
setIsAuthorized(true);
|
setIsAuthorized(true);
|
||||||
setIsRevoked(false);
|
setIsRevoked(false);
|
||||||
setStatus("ready");
|
setStatus("ready");
|
||||||
|
} else if (res?.status === "revoked") {
|
||||||
// Check for tutorial
|
|
||||||
if (res.user?.logins === 0) {
|
|
||||||
setTutorialStep(0);
|
|
||||||
}
|
|
||||||
} else if (res && res.status === "revoked") {
|
|
||||||
setIsRevoked(true);
|
setIsRevoked(true);
|
||||||
setIsAuthorized(false);
|
setIsAuthorized(false);
|
||||||
setStatus("Error: Key Revoked");
|
setStatus("Error: Key Revoked");
|
||||||
} else if (res && res.status === "invalid_hwid") {
|
} else if (res?.status === "invalid_hwid") {
|
||||||
setStatus("Error: Key locked to another PC");
|
setStatus("Error: Key locked to another PC");
|
||||||
setIsAuthorized(false);
|
setIsAuthorized(false);
|
||||||
} else {
|
} else {
|
||||||
setStatus("Error: Invalid key");
|
setStatus("Error: Invalid key");
|
||||||
setIsAuthorized(false);
|
setIsAuthorized(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
setStatus("Network Error: Check connection");
|
setStatus("Network Error: Check connection");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,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);
|
||||||
@ -271,7 +281,7 @@ export default function App() {
|
|||||||
if (minimizeToTray) {
|
if (minimizeToTray) {
|
||||||
await tryInvoke("minimize_to_tray");
|
await tryInvoke("minimize_to_tray");
|
||||||
} else {
|
} else {
|
||||||
await tryWindowApi((w) => w.minimize());
|
await tryWindowApi(w => w.minimize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,41 +290,53 @@ export default function App() {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const tutorialSteps = [
|
const closeAllModals = () => {
|
||||||
{ title: "Welcome to RLidentity", text: "Let's show you around. First, enter your spoofed username here.", target: "input" },
|
setSettingsOpen(false);
|
||||||
{ title: "Injection", text: "Once Rocket League is running, click Inject to start. Or enable Auto-Inject in settings!", target: "btn-primary" },
|
setLogsOpen(false);
|
||||||
{ title: "Settings", text: "Customize your experience here. Change your platform or toggle Auto-Injection.", target: "tb-action" },
|
setDebugOpen(false);
|
||||||
{ title: "All Set!", text: "You're ready to win. Happy gaming!", target: "none" }
|
};
|
||||||
];
|
|
||||||
|
|
||||||
|
const isModalOpen = settingsOpen || logsOpen || debugOpen;
|
||||||
|
|
||||||
|
// ── Auth screen ───────────────────────────────────────────────────────────
|
||||||
if (!isAuthorized) {
|
if (!isAuthorized) {
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<div className={`bg-aurora ${isRevoked ? 'revoked-aurora' : ''}`} aria-hidden="true" />
|
<div className={`bg-aurora ${isRevoked ? "revoked-aurora" : ""}`} aria-hidden="true" />
|
||||||
|
|
||||||
<div className="window-titlebar" data-tauri-drag-region>
|
<div className="window-titlebar" data-tauri-drag-region>
|
||||||
<div className="window-titlebar-left">
|
<div className="window-titlebar-left">
|
||||||
<img src="/rlidentity.webp" className="app-logo" alt="logo" onClick={handleLogoClick} draggable="false" style={{ cursor: 'default' }} />
|
<img
|
||||||
|
src="/rlidentity.webp"
|
||||||
|
className="app-logo"
|
||||||
|
alt="logo"
|
||||||
|
onClick={handleLogoClick}
|
||||||
|
draggable="false"
|
||||||
|
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">RLidentity <span style={{ fontSize: '10px', opacity: 0.6, marginLeft: '4px' }}>v2.0.0</span></div>
|
<div className="app-name neon-text-soft">
|
||||||
<div className="app-slogan">{isRevoked ? 'License Revoked' : 'Authorize to continue'}</div>
|
RLidentity <span style={{ fontSize: "10px", opacity: 0.6, marginLeft: "4px" }}>
|
||||||
|
v{version || "2.0.1"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="app-slogan">{isRevoked ? "License Revoked" : "Authorize to continue"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="titlebar-controls">
|
<div className="titlebar-controls">
|
||||||
<button className="win-btn" onClick={handleMinimizeClick}>—</button>
|
<button className="win-btn" onClick={handleMinimizeClick}>—</button>
|
||||||
<button className="win-btn" onClick={async () => await tryWindowApi(w => w.close())}>✕</button>
|
<button className="win-btn" onClick={() => tryWindowApi(w => w.close())}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="panel-wrap">
|
<main className="panel-wrap">
|
||||||
<section className={`glass-card neon-ring ${isRevoked ? 'revoked' : ''}`} style={{ maxWidth: '400px', margin: 'auto' }}>
|
<section className={`glass-card neon-ring ${isRevoked ? "revoked" : ""}`} style={{ maxWidth: "400px", margin: "auto" }}>
|
||||||
<header className="card-header">
|
<header className="card-header">
|
||||||
<h1 className={`headline neon-text ${isRevoked ? 'red' : ''}`}>
|
<h1 className={`headline neon-text ${isRevoked ? "red" : ""}`}>
|
||||||
{isRevoked ? 'ACCESS REVOKED' : 'RLidentity'}
|
{isRevoked ? "ACCESS REVOKED" : "RLidentity"}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="app-slogan">
|
<p className="app-slogan">
|
||||||
{isRevoked ? 'This license is no longer active' : 'Enter your API key to continue'}
|
{isRevoked ? "This license is no longer active" : "Enter your API key to continue"}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div className="form-stack">
|
<div className="form-stack">
|
||||||
@ -326,19 +348,22 @@ export default function App() {
|
|||||||
type="password"
|
type="password"
|
||||||
className="input"
|
className="input"
|
||||||
value={apiKey}
|
value={apiKey}
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
onChange={e => setApiKey(e.target.value)}
|
||||||
|
onKeyDown={e => e.key === "Enter" && authorize(apiKey)}
|
||||||
placeholder="Enter your license key..."
|
placeholder="Enter your license key..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button className="btn btn-primary" onClick={() => isRevoked ? logout() : authorize(apiKey)}>
|
<button className="btn btn-primary" onClick={() => isRevoked ? logout() : authorize(apiKey)}>
|
||||||
{isRevoked ? 'Change Key' : 'Login'}
|
{isRevoked ? "Change Key" : "Login"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{status !== "ready" && (
|
{status !== "ready" && (
|
||||||
<p className="status-value" style={{textAlign:'center', marginTop:'10px', color: (isRevoked || status.startsWith('Error')) ? '#ff5555' : 'inherit'}}>
|
<p className="status-value" style={{
|
||||||
|
textAlign: "center",
|
||||||
|
marginTop: "10px",
|
||||||
|
color: (isRevoked || status.startsWith("Error")) ? "var(--red0)" : "inherit",
|
||||||
|
}}>
|
||||||
{status}
|
{status}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@ -349,30 +374,39 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Main app ──────────────────────────────────────────────────────────────
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<div className="bg-aurora" aria-hidden="true" />
|
<div className="bg-aurora" aria-hidden="true" />
|
||||||
|
|
||||||
<div className="window-titlebar" data-tauri-drag-region>
|
<div className="window-titlebar" data-tauri-drag-region>
|
||||||
<div className="window-titlebar-left">
|
<div className="window-titlebar-left">
|
||||||
<img src="/rlidentity.webp" className="app-logo" alt="logo" onClick={handleLogoClick} draggable="false" style={{ cursor: 'default' }} />
|
<img
|
||||||
|
src="/rlidentity.webp"
|
||||||
|
className="app-logo"
|
||||||
|
alt="logo"
|
||||||
|
onClick={handleLogoClick}
|
||||||
|
draggable="false"
|
||||||
|
style={{ cursor: "default" }}
|
||||||
|
/>
|
||||||
<div className="titlebar-app-name neon-text-soft" data-tauri-drag-region>RLidentity</div>
|
<div className="titlebar-app-name neon-text-soft" data-tauri-drag-region>RLidentity</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="titlebar-controls-wrap" data-tauri-drag-region>
|
<div className="titlebar-controls-wrap" data-tauri-drag-region>
|
||||||
<div className="titlebar-controls">
|
<div className="titlebar-controls">
|
||||||
<button id="step-settings" className="tb-action" onClick={() => setSettingsOpen(true)}>Settings</button>
|
<button className="tb-action" onClick={() => setSettingsOpen(true)}>Settings</button>
|
||||||
<button className="tb-action" onClick={() => openUrl(GITHUB_URL)}>GitHub</button>
|
<button className="tb-action" onClick={() => openUrl(GITHUB_URL)}>GitHub</button>
|
||||||
<button className="win-btn" onClick={handleMinimizeClick}>—</button>
|
<button className="win-btn" onClick={handleMinimizeClick}>—</button>
|
||||||
<button className="win-btn" onClick={async () => await tryWindowApi(w => w.close())}>✕</button>
|
<button className="win-btn" onClick={() => tryWindowApi(w => w.close())}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="panel-wrap">
|
<main className="panel-wrap">
|
||||||
<header className="welcome-section">
|
<header className="welcome-section">
|
||||||
<h2 className="welcome-text">Welcome, <span className="neon-text-soft">{userData?.globalName || userData?.username || "User"}</span></h2>
|
<h2 className="welcome-text">
|
||||||
<div className="user-id-badge">User #{userData?.userId || "0"}</div>
|
Welcome, <span className="neon-text-soft">{userData?.global_name || userData?.username || "User"}</span>
|
||||||
|
</h2>
|
||||||
|
<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">
|
||||||
@ -381,22 +415,22 @@ export default function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="form-stack">
|
<div className="form-stack">
|
||||||
<div className="field" id="step-username">
|
<div className="field">
|
||||||
<label className="label">spoofed username</label>
|
<label className="label">spoofed username</label>
|
||||||
<div className="glass-input">
|
<div className="glass-input">
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input"
|
||||||
value={spoofedUsername}
|
value={spoofedUsername}
|
||||||
onChange={(e) => setSpoofedUsername(e.target.value)}
|
onChange={e => setSpoofedUsername(e.target.value)}
|
||||||
|
placeholder="Enter a username..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="step-inject"
|
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={inject}
|
onClick={inject}
|
||||||
disabled={rlStatus !== "RL Running" && tutorialStep !== 1}
|
disabled={rlStatus !== "RL Running"}
|
||||||
>
|
>
|
||||||
{rlStatus === "RL Running" ? "Inject" : "Start Rocket League"}
|
{rlStatus === "RL Running" ? "Inject" : "Start Rocket League"}
|
||||||
</button>
|
</button>
|
||||||
@ -414,105 +448,109 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="status-item">
|
<div className="status-item">
|
||||||
<span className="status-label">game</span>
|
<span className="status-label">game</span>
|
||||||
<span className="status-value">{rlStatus}</span>
|
<span className={`status-value ${rlStatus === "RL Running" ? "status-active" : ""}`}>{rlStatus}</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{(settingsOpen || logsOpen || debugOpen) && (
|
{/* ── Modals ── */}
|
||||||
<div className="modal-overlay" onClick={() => { setSettingsOpen(false); setLogsOpen(false); setDebugOpen(false); }}>
|
{isModalOpen && (
|
||||||
<div className="modal glass-card neon-ring" onClick={e => e.stopPropagation()} style={{ maxWidth: (logsOpen || debugOpen) ? '600px' : '400px' }}>
|
<div className="modal-overlay" onClick={closeAllModals}>
|
||||||
|
<div
|
||||||
|
className="modal glass-card neon-ring"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
style={{ maxWidth: logsOpen || debugOpen ? "600px" : "420px" }}
|
||||||
|
>
|
||||||
{debugOpen ? (
|
{debugOpen ? (
|
||||||
<>
|
<>
|
||||||
<h2 className="modal-title neon-text-soft">System Credits & Debug</h2>
|
<h2 className="modal-title neon-text-soft">System Credits</h2>
|
||||||
<div className="glass-input" style={{ padding: '15px', marginBottom: '15px' }}>
|
<div className="credits-grid glass-input">
|
||||||
<div style={{ display: 'grid', gap: '10px', fontSize: '13px' }}>
|
{[
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
{ role: "Lead Dev & Owner", name: "Bits", accent: true },
|
||||||
<span style={{ color: '#aaa' }}>Lead Dev & Owner:</span>
|
{ role: "Dev & Admin", name: "Danni" },
|
||||||
<span className="neon-text-soft">Bits</span>
|
{ role: "Co-Owner", name: "Deniz" },
|
||||||
|
{ role: "Administrator", name: "Kairo" },
|
||||||
|
{ role: "Helpers", name: "Quinn, SNDR" },
|
||||||
|
{ role: "Tester", name: "Emir" },
|
||||||
|
].map(({ role, name, accent }) => (
|
||||||
|
<div key={role} className="credit-row">
|
||||||
|
<span className="credit-role">{role}</span>
|
||||||
|
<span className={accent ? "neon-text-soft" : "credit-name"}>{name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
))}
|
||||||
<span style={{ color: '#aaa' }}>Dev & Admin:</span>
|
<hr className="credit-divider" />
|
||||||
<span style={{ color: '#fff' }}>Danni</span>
|
<button className="btn btn-secondary" style={{ width: "100%" }} onClick={() => openUrl("https://rlidentity.me/discord")}>
|
||||||
</div>
|
Join Discord — rlidentity.me/discord
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span style={{ color: '#aaa' }}>Co-Owner:</span>
|
|
||||||
<span style={{ color: '#fff' }}>Deniz</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span style={{ color: '#aaa' }}>Administrator:</span>
|
|
||||||
<span style={{ color: '#fff' }}>Kairo</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span style={{ color: '#aaa' }}>Helpers:</span>
|
|
||||||
<span style={{ color: '#fff' }}>Quinn, SNDR</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span style={{ color: '#aaa' }}>Tester:</span>
|
|
||||||
<span style={{ color: '#fff' }}>Emir</span>
|
|
||||||
</div>
|
|
||||||
<hr style={{ border: 'none', borderTop: '1px solid rgba(255,255,255,0.1)', margin: '5px 0' }} />
|
|
||||||
<div style={{ textAlign: 'center' }}>
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary"
|
|
||||||
style={{ width: '100%', marginTop: '5px' }}
|
|
||||||
onClick={() => openUrl('https://rlidentity.me/discord')}
|
|
||||||
>
|
|
||||||
Join Discord (rlidentity.me/discord)
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button className="btn btn-primary" style={{ marginTop: "14px" }} onClick={() => setDebugOpen(false)}>Close</button>
|
||||||
</div>
|
|
||||||
<button className="btn btn-primary" onClick={() => setDebugOpen(false)}>Close Debug</button>
|
|
||||||
</>
|
</>
|
||||||
) : logsOpen ? (
|
) : logsOpen ? (
|
||||||
<>
|
<>
|
||||||
<h2 className="modal-title neon-text-soft">Injection Logs</h2>
|
<h2 className="modal-title neon-text-soft">Injection Logs</h2>
|
||||||
<div className="glass-input" style={{ height: '300px', padding: '10px', overflowY: 'auto' }}>
|
<div className="glass-input" style={{ height: "300px", padding: "10px", overflowY: "auto" }}>
|
||||||
<pre style={{ fontSize: '12px', color: '#fff', whiteSpace: 'pre-wrap' }}>
|
<pre style={{ fontSize: "12px", color: "#fff", whiteSpace: "pre-wrap", margin: 0 }}>
|
||||||
{lastLog || "No logs yet..."}
|
{lastLog || "No logs yet..."}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={() => setLogsOpen(false)}>Close Logs</button>
|
<button className="btn btn-primary" style={{ marginTop: "14px" }} onClick={() => setLogsOpen(false)}>Close Logs</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h2 className="modal-title neon-text-soft">Settings</h2>
|
<h2 className="modal-title neon-text-soft">Settings</h2>
|
||||||
|
|
||||||
|
{/* Platform */}
|
||||||
<div className="setting-row">
|
<div className="setting-row">
|
||||||
<div className="setting-text">
|
<div className="setting-text">
|
||||||
<div className="setting-title">Platform</div>
|
<div className="setting-title">Platform</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="custom-dropdown-wrap">
|
<div className="custom-dropdown-wrap">
|
||||||
<button
|
<button
|
||||||
className="glass-input dropdown-trigger"
|
className="dropdown-trigger"
|
||||||
onClick={() => setPlatformPickerOpen(!platformPickerOpen)}
|
onClick={() => setPlatformPickerOpen(p => !p)}
|
||||||
>
|
>
|
||||||
<span className="dropdown-value">{platform === "Epic" ? "Epic Games" : "Steam"}</span>
|
<span>{platform === "Epic" ? "Epic Games" : "Steam"}</span>
|
||||||
<span className="dropdown-arrow">▾</span>
|
<span className="dropdown-arrow">▾</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{platformPickerOpen && (
|
{platformPickerOpen && (
|
||||||
<div className="dropdown-menu glass-card neon-ring">
|
<div className="dropdown-menu">
|
||||||
|
{["Epic", "Steam"].map(p => (
|
||||||
<button
|
<button
|
||||||
className={`dropdown-item ${platform === "Epic" ? "active" : ""}`}
|
key={p}
|
||||||
onClick={() => { setPlatform("Epic"); setPlatformPickerOpen(false); }}
|
className={`dropdown-item ${platform === p ? "active" : ""}`}
|
||||||
|
onClick={() => { setPlatform(p); setPlatformPickerOpen(false); }}
|
||||||
>
|
>
|
||||||
Epic Games
|
{p === "Epic" ? "Epic Games" : "Steam"}
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`dropdown-item ${platform === "Steam" ? "active" : ""}`}
|
|
||||||
onClick={() => { setPlatform("Steam"); setPlatformPickerOpen(false); }}
|
|
||||||
>
|
|
||||||
Steam
|
|
||||||
</button>
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="setting-row" id="step-autoinject">
|
{/* Theme picker */}
|
||||||
|
<div className="setting-row">
|
||||||
|
<div className="setting-text">
|
||||||
|
<div className="setting-title">Theme</div>
|
||||||
|
<div className="setting-sub">Accent color</div>
|
||||||
|
</div>
|
||||||
|
<div className="theme-swatches">
|
||||||
|
{THEMES.map(t => (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
className={`theme-swatch ${theme === t.id ? "active" : ""}`}
|
||||||
|
style={{ "--swatch-color": t.color } as React.CSSProperties}
|
||||||
|
onClick={() => setTheme(t.id)}
|
||||||
|
title={t.label}
|
||||||
|
aria-label={`${t.label} theme`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Auto inject */}
|
||||||
|
<div className="setting-row">
|
||||||
<div className="setting-text">
|
<div className="setting-text">
|
||||||
<div className="setting-title">Auto Injection</div>
|
<div className="setting-title">Auto Injection</div>
|
||||||
<div className="setting-sub">Injects automatically when RL starts</div>
|
<div className="setting-sub">Injects automatically when RL starts</div>
|
||||||
@ -523,6 +561,7 @@ export default function App() {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Minimize to tray */}
|
||||||
<div className="setting-row">
|
<div className="setting-row">
|
||||||
<div className="setting-text">
|
<div className="setting-text">
|
||||||
<div className="setting-title">Minimize to tray</div>
|
<div className="setting-title">Minimize to tray</div>
|
||||||
@ -533,8 +572,10 @@ export default function App() {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="btn-row" style={{ flexDirection: 'column', gap: '10px', marginTop: '10px' }}>
|
<div className="btn-stack">
|
||||||
<button className="btn btn-tertiary" onClick={() => { setSettingsOpen(false); setLogsOpen(true); }}>View Last Injection Log</button>
|
<button className="btn btn-tertiary" onClick={() => { setSettingsOpen(false); setLogsOpen(true); }}>
|
||||||
|
View Last Injection Log
|
||||||
|
</button>
|
||||||
<button className="btn btn-secondary" onClick={logout}>Logout</button>
|
<button className="btn btn-secondary" onClick={logout}>Logout</button>
|
||||||
<button className="btn btn-primary" onClick={() => setSettingsOpen(false)}>Close</button>
|
<button className="btn btn-primary" onClick={() => setSettingsOpen(false)}>Close</button>
|
||||||
</div>
|
</div>
|
||||||
@ -544,34 +585,19 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tutorialStep >= 0 && (
|
{/* ── Update modal ── */}
|
||||||
<div className="tutorial-overlay">
|
{pendingUpdate && (
|
||||||
<div className={`tutorial-spotlight step-${tutorialStep}`} />
|
<div className="modal-overlay" onClick={() => setPendingUpdate(null)}>
|
||||||
<div className={`tutorial-card glass-card neon-ring step-${tutorialStep}`}>
|
<div className="modal glass-card neon-ring" onClick={e => e.stopPropagation()} style={{ maxWidth: "380px" }}>
|
||||||
<h2 className="modal-title neon-text">{tutorialSteps[tutorialStep].title}</h2>
|
<h2 className="modal-title neon-text-soft">Update Available</h2>
|
||||||
<p className="modal-p">{tutorialSteps[tutorialStep].text}</p>
|
<p style={{ margin: "0 0 20px", color: "var(--muted)", fontSize: "14px" }}>
|
||||||
|
Version <strong style={{ color: "var(--text)" }}>{pendingUpdate.version}</strong> is ready to install.
|
||||||
|
The app will restart automatically.
|
||||||
|
</p>
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
<button
|
<button className="btn btn-secondary" onClick={() => setPendingUpdate(null)}>Later</button>
|
||||||
className="btn btn-secondary"
|
<button className="btn btn-primary" style={{ width: "100%" }} onClick={pendingUpdate.install}>
|
||||||
onClick={() => setTutorialStep(-1)}
|
Update Now
|
||||||
>
|
|
||||||
Skip
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => {
|
|
||||||
if (tutorialStep === 2) {
|
|
||||||
setSettingsOpen(true);
|
|
||||||
}
|
|
||||||
if (tutorialStep < tutorialSteps.length - 1) {
|
|
||||||
setTutorialStep(tutorialStep + 1);
|
|
||||||
} else {
|
|
||||||
setSettingsOpen(false);
|
|
||||||
setTutorialStep(-1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tutorialStep < tutorialSteps.length - 1 ? "Next" : "Finish"}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||