chore(history): squash 10 commit(s) from 2026-07-31

- feat: Фаза 1 ребрендинг combatant → lovisual + восстановление config
- refactor: Фаза 2 — выкинуть Runtime/JarReplacement, заменить на Lifecycle
- refactor: Фаза 3.5 — удалить читерские модули, ESP под легит
- refactor: Фаза 3 — разбить Renderer2D (3379→2150 фасад + 14 хелперов ≤200)
- refactor: Фаза 3 — разбить BetterChatRenderer (2870→210 фасад + 21 хелпер ≤200)
- refactor: Фаза 3 — разбить UnifiedSettingRenderer (2798→208 фасад + 24 хелпера ≤200)
- refactor: Фаза 3 — разбить ClickGuiRenderer (1887→542 фасад + 15 хелперов ≤200)
- refactor: Фаза 3 — разбить NameTags (1791→438 фасад + 11 хелперов ≤200)
- refactor: Фаза 3 — разбить TargetHud (1655→340 фасад + 11 хелперов ≤200)
- refactor: Фаза 3 — разбить ESP (1538→269 фасад + 11 хелперов ≤200)
This commit is contained in:
loki5512344 2026-07-31 23:14:14 +02:00
parent 8084ac50b3
commit 52d87368c9
1595 changed files with 35857 additions and 51145 deletions

6855
launcher/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

36
launcher/Cargo.toml Normal file
View file

@ -0,0 +1,36 @@
[package]
name = "lovisual-launcher"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
description = "LoVisual launcher: login, dashboard, Minecraft launch"
license = "GPL-3.0"
[dependencies]
slint = "1.17"
tokio = { version = "1.53", features = ["full"] }
reqwest = { version = "0.13", features = ["json", "rustls"], default-features = false }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
dirs = "6"
[build-dependencies]
slint-build = "1.17"
[lints.rust]
dead_code = "deny"
[lints.clippy]
correctness = "deny"
suspicious = "deny"
style = "deny"
complexity = "deny"
perf = "deny"
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true

37
launcher/TODO.md Normal file
View file

@ -0,0 +1,37 @@
# LoVisual Launcher (Rust + Slint)
## Правила (ОБЯЗАТЕЛЬНЫЕ, нарушение = откат)
- **KISS / DRY / SOLID** — простая структура, без оверинжиниринга
- **Макс. 200 строк на файл** — превышение = разбить на модули
- **Макс. 3 файла на папку** — больше = новая подпапка по смыслу
- **Обязательное прохождение:** `cargo fmt` + `cargo clippy --all-targets` (ноль ошибок) + `cargo test`
- **Запрещено `#[allow(dead_code)]`** — мёртвый код удаляется, не прячется
- **unsafe запрещён в нашем коде** (сгенерированный Slint-код — исключение, его не трогаем)
- **Без пустышек** — модуль создаётся только с рабочей реализацией
- **Каждый этап компилируется** — перед переходом к следующему
---
## Сделано
- [x] Каркас: main.rs + api/ (health check в фоне)
- [x] Окно логина (Slint): логин/пароль/статус
- [x] Rust 2024 edition, актуальные версии пакетов
- [x] Фоновая проверка `/health` бэкенда, статус в UI
---
## Фаза 1: API клиент (`src/api/`)
- [ ] `auth.rs` — login/register (reqwest, таймауты, rename_all)
- [ ] `profile.rs` — get/update/playtime/version check
- [ ] Версия лаунчера читается из Cargo.toml (не хардкод)
## Фаза 2: Экраны (Slint)
- [ ] login — реальный логин/регистрация через backend
- [ ] dashboard — профиль, playtime, version check
- [ ] settings — кастомная команда запуска (ЧИТАЕТСЯ на Play, не stale)
## Фаза 3: Launch
- [ ] `src/launch/mod.rs` — запуск Minecraft (команда из настроек, защита от пустой строки)
- [ ] `src/session/mod.rs` — save_session только при логине, удалять при logout
### Проверка после каждой фазы: `cargo fmt && cargo clippy --all-targets && cargo test`

3
launcher/build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
slint_build::compile("ui/main.slint").expect("failed to compile slint ui");
}

35
launcher/src/api/mod.rs Normal file
View file

@ -0,0 +1,35 @@
//! Minimal client for the LoVisual backend API.
use anyhow::Context as _;
use serde::Deserialize;
/// Default backend base URL for local development.
pub const DEFAULT_API_URL: &str = "http://127.0.0.1:8765";
/// Health response from `GET /health`.
#[derive(Debug, Deserialize)]
pub struct Health {
pub status: String,
pub version: String,
}
/// Fetch backend health status.
pub async fn health(base_url: &str) -> anyhow::Result<Health> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.context("failed to build http client")?;
let url = format!("{}/health", base_url.trim_end_matches('/'));
let response = client
.get(&url)
.send()
.await
.context("health request failed")?;
response
.error_for_status()
.context("health returned error status")?
.json()
.await
.context("invalid health response")
}

52
launcher/src/main.rs Normal file
View file

@ -0,0 +1,52 @@
//! LoVisual launcher entry point.
mod api;
slint::include_modules!();
use slint::ComponentHandle;
fn main() -> Result<(), slint::PlatformError> {
let ui = MainWindow::new()?;
spawn_backend_status_check(ui.as_weak());
let weak = ui.as_weak();
ui.on_login(move || {
let ui = weak.unwrap();
let login = ui.get_login_input().trim().to_string();
let password = ui.get_password_input().trim().to_string();
if login.is_empty() || password.is_empty() {
ui.set_status("Заполните логин и пароль".into());
return;
}
// TODO: auth against backend, save session, transition to dashboard.
ui.set_status(format!("Вход выполнен как {login}").into());
});
ui.run()
}
/// Ping backend health in background and surface the result in the UI.
fn spawn_backend_status_check(weak: slint::Weak<MainWindow>) {
std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to build tokio runtime");
let status = runtime.block_on(api::health(api::DEFAULT_API_URL));
let message = match status {
Ok(health) => format!("Backend v{}: {}", health.version, health.status),
Err(error) => format!("Backend недоступен: {error}"),
};
let _ = slint::invoke_from_event_loop(move || {
if let Some(ui) = weak.upgrade() {
ui.set_status(message.clone().into());
}
});
});
}

63
launcher/ui/main.slint Normal file
View file

@ -0,0 +1,63 @@
import { VerticalBox, LineEdit, Button } from "std-widgets.slint";
export component MainWindow inherits Window {
title: "LoVisual Launcher";
width: 480px;
height: 320px;
in-out property <string> status: "Disconnected";
VerticalBox {
spacing: 12px;
padding: 24px;
Text {
text: "LoVisual Launcher";
font-size: 28px;
font-weight: 700;
horizontal-alignment: center;
color: #00bfff;
}
Text {
text: "Войдите в аккаунт, чтобы запустить Minecraft";
font-size: 13px;
horizontal-alignment: center;
color: #9aa0a6;
}
VerticalBox {
spacing: 8px;
Text { text: "Логин"; }
LineEdit {
text <=> login-input;
placeholder-text: "username or email";
}
Text { text: "Пароль"; }
LineEdit {
text <=> password-input;
input-type: InputType.password;
placeholder-text: "••••••••";
}
}
Button {
text: "Войти";
primary: true;
clicked => root.login();
}
Text {
text: status;
horizontal-alignment: center;
color: #ff6b6b;
font-size: 12px;
}
}
callback login();
in-out property <string> login-input: "";
in-out property <string> password-input: "";
}