Compare commits

...
81 Commits
Author SHA1 Message Date
ScreamAngel cf8fd8fb6c feat(trophy): in-game top-right overlay window and desktop notification integration 2026-11-09 19:35:38 +03:00
ScreamAngel 7ae4948793 fix(patch): remove corrupting 0x5f4510 write and set accurate bonus play NOP addresses 2026-11-09 18:20:51 +03:00
ScreamAngel 9147960de5 fix(trophy): align PS4 35-trophy mapping, fix log tailer startup offset, and bypass bonus play anti-cheat 2026-11-09 17:42:38 +03:00
ScreamAngel 3a96e9845b fix(trophy): resolve trophy unlock bounds check, emulator count, and auto-sync savegame progress
- Fix sceNpTrophyGetTrophyUnlockState in gow3-probe & gow3-probe.exe returning count=0
- Add 'Bug Fix - Trophy Unlock Fix' XML patch in eboot.bin to return count=64 and bypass jae checks
- Add unbuffered stdout/stderr constructor to libgow3input.so for instant log tailing
- Add retroactive savegame trophy sync for previously defeated bosses (Poseidon, Hades, Helios, etc.)
2026-11-09 16:55:52 +03:00
ScreamAngel 3992455718 feat(trophy): real-time PS4 trophy engine, toast notifications, trophy dashboard, bonus play bug fix (v2.2) 2026-11-09 14:34:34 +03:00
ScreamAngel 08da3bec2c docs: add project memory and architecture overview for persistent context 2026-11-07 16:22:19 +03:00
ScreamAngel 04f61833ea feat(discord-rpc): automatic system language detection and multi-language support (tr, en, de, es) 2026-11-07 16:14:19 +03:00
ScreamAngel a60dc8bace feat(discord-rpc): accurate real-time story chapters and boss fight presence 2026-11-07 15:17:38 +03:00
ScreamAngel 7b8d9155fd fix(linux): isolate Windows-specific viewport commands and restore clean fullscreen & borderless on Linux Hyprland 2026-11-07 14:57:50 +03:00
ScreamAngel ec0a5893c7 fix(windows): resolve mouse click scancodes and keep shader screen fullscreen behind game 2026-11-07 14:35:29 +03:00
ScreamAngel 6bb6349c41 fix(windows): resolve black screen launch bug and refine shader screen 10s background lifecycle 2026-11-07 14:05:47 +03:00
ScreamAngel 8ba512f45e feat(discord): official God of War III Remastered App ID, real-time chapter & boss fight detection, challenges & arena presence 2026-11-07 03:42:52 +03:00
ScreamAngel 0136d4a426 feat(branding): embed custom God of War icons in probe binaries, SDL window & launcher metadata 2026-11-07 03:12:36 +03:00
ScreamAngel 71b9cd8596 fix(windows): resolve FPS speed surge, mouse click bridge, mod support and shader window z-order 2026-11-07 02:45:46 +03:00
ScreamAngel b6409869aa feat: release v2.1 - integrate native Discord Rich Presence, activity reporting and custom Client ID support 2026-11-03 23:27:27 +03:00
ScreamAngel ff7c026bba fix: remove missing glyph tofu box before red category headers 2026-11-03 19:01:23 +03:00
ScreamAngel f119d84439 feat: release v2.0.3 - enhanced settings UI readability, smooth PS4 splash zoom with 20s auto-close, fix duplicate update icon 2026-11-03 18:50:13 +03:00
ScreamAngel 4b5ec4f7f5 feat: rebuild Windows executable, update to v2.0.2 for both Linux & Windows 2026-11-03 12:53:55 +03:00
ScreamAngel 5ddf4eab9d Update README.md 2026-11-03 01:51:16 +03:00
ScreamAngel 839dabca0d Update README.md 2026-11-03 01:50:20 +03:00
ScreamAngel 319a4c56e8 Add automatic version scan, reminder dismissal, and update button (v2.0.1) 2026-11-03 00:12:35 +03:00
ScreamAngel 0c9f36c292 fix(launcher): keep shader background screen open without closing (v2.0.1) 2026-11-02 23:58:25 +03:00
ScreamAngel bdb896b279 fix(launcher): hold splash screen until Sony intro video renders on screen (v2.0.1) 2026-11-02 23:40:29 +03:00
ScreamAngel b435c8344b Update README.md 2026-11-02 22:58:26 +03:00
ScreamAngel 12dea62e3b fix(mods, shaders): mod packaging/XML patch support, post-compile black screen hold, and PS4 splash toggle (v2.0.1) 2026-11-02 22:56:13 +03:00
ScreamAngel 192f9164d1 fix(shader): smooth synchronous 0-100% shader loading, hide yellow beam, bump v2.0.1 2026-11-02 21:02:18 +03:00
ScreamAngel 9fdbeb5650 feat: complete in-game mod manager (v2.0), colorful vector flags, and shader icon fix 2026-11-02 19:38:14 +03:00
ScreamAngel 95db57f9a1 feat(ui): settings system specs tab, vram gc combo, 240 fps, turkish flag, github release notes (v2.0) 2026-11-02 18:51:14 +03:00
ScreamAngel ba4c08dcea feat(ui): God of War Ascension console main menu, localized release notes, 4-tab settings (v2.0) 2026-11-02 18:11:00 +03:00
ScreamAngel e9f6c33021 feat(ui): major UI overhaul to wireframe layout with God of War font, sidebar navigation, and interactive animations (v2.0) 2026-11-02 17:36:14 +03:00
ScreamAngel 234dae1972 feat(shaders): add official complete shader cache downloader & pre-caching (28k shaders) 2026-11-02 16:27:01 +03:00
ScreamAngel ea0f8b7658 fix(gpu): remove GOW3_COPY_GPU_BUFFERS to prevent PM4 liverpool packet overrun crash 2026-11-02 14:24:06 +03:00
ScreamAngel 418e035def feat(perf): make async readbacks recommended by default & fix non-async readback stall 2026-11-02 13:41:48 +03:00
ScreamAngel 0079e3b5a6 feat(shaders): add option to launch game while compiling shaders vs wait for 100% full warmup 2026-11-02 12:57:41 +03:00
ScreamAngel ccc9765bf5 feat(ui): fullscreen Spartan Rage loading screen & v1.9 version bump 2026-11-02 12:44:07 +03:00
ScreamAngel aff5ec53c8 feat(ui): add God of War III Spartan Rage shader loading screen with live warmup & zero-stutter pre-caching (v1.8.5) 2026-11-02 12:18:00 +03:00
ScreamAngel 7f7a00755c revert(engine): remove experimental streaming copy/staging/texture flags causing shader corruption (v1.8.5) 2026-11-02 02:02:52 +03:00
ScreamAngel 243dbf7c67 perf(streaming): add 256MB staging pool, 1MB copy merging, hot pages, texture helper & zero-yield driver sync (v1.8.5) 2026-11-02 01:52:19 +03:00
ScreamAngel 2a9a6480b5 perf(engine): enable pipeline binaries, scale workers for 20T CPU, 10GB shader cache & 2-frame queue for buttery 144Hz (v1.8.5) 2026-11-01 23:05:38 +03:00
ScreamAngel ee36d5fb36 fix(input): transparent cursor & clean SDL3 window hook for Wayland/X11 without frame flicker (v1.8.5) 2026-11-01 22:34:57 +03:00
ScreamAngel acf9d08527 fix(stability): match v1.8.2 runtime env, restore 6GB VRAM budget ceiling & asynchronous recording thread to eliminate logo flickers and save-load freeze (v1.8.5) 2026-11-01 22:18:20 +03:00
ScreamAngel 4980c43d53 fix(stability): restore presenter thread, async fences & GOW3_DMEM_MB to resolve save load freezes and eliminate black logo flickers (v1.8.5) 2026-11-01 21:42:53 +03:00
ScreamAngel f36b0f3287 fix(graphics): restore Texture Corruption Fix to resolve Leviathan rainbow artifacts & disable async fences to eliminate black flickers (v1.8.5) 2026-11-01 19:03:54 +03:00
ScreamAngel 94e71041e4 fix(stability): resolve startup logo black flickers via upfront warmup and ensure crash-free 5GB VRAM ceiling (v1.8.5) 2026-11-01 18:48:21 +03:00
ScreamAngel 3f269176da fix(graphics): resolve yellow diffuse-normal texture corruption in Poseidon & Gaia by removing invalid 0x7cd918 patch (v1.8.5) 2026-11-01 18:30:38 +03:00
ScreamAngel 3786b49355 fix(stability): revert experimental queue & driver vars, restore dedicated presenter thread and 6GB VRAM budget ceiling (v1.8.5) 2026-11-01 18:10:18 +03:00
ScreamAngel 5bdc44159c fix(graphics): resolve video black flickering & residual yellow textures via synchronous host fences and single queue pacing 2026-11-01 17:12:24 +03:00
ScreamAngel 0730f2da7d fix(graphics): resolve yellow floor textures & black screen flicker by disabling stale readbacks (v1.8.5) 2026-11-01 16:49:48 +03:00
ScreamAngel a9592e555b feat(v1.8.4): general stutter & micro-drop optimizations, thick scrollbar styling 2026-11-01 15:57:19 +03:00
ScreamAngel feea7e7975 fix(nvidia): resolve libnvidia-glcore EndRendering crash & optimize VRAM budget ceiling 2026-10-31 23:17:43 +03:00
ScreamAngel f6df71dfb7 fix(input): correct shoulder and trigger bindings for recommended and mouse presets 2026-10-31 22:39:34 +03:00
ScreamAngel 2e40762963 feat(input): add recommended and mouse+keyboard presets, bump launcher version to v1.8.2 2026-10-31 22:25:57 +03:00
ScreamAngel 27e27eea3b fix(engine): resolve Linux AppImage extraction path, copy runtime libs, add fallback background & alternative engine download 2026-10-31 21:57:44 +03:00
ScreamAngel 008aa107a2 feat(v1.8.1): Add launcher_log.txt, detailed error pop-up modal, and launcher log viewer 2026-10-31 21:28:18 +03:00
ScreamAngel 9d0691d5de fix: Resolve LD_PRELOAD path spaces issue, add button latching & robust focus cursor toggle 2026-10-31 20:48:43 +03:00
ScreamAngel 28d5506758 fix: Add mouse buttons bridge to game scancodes, hide mouse cursor focus tracking & physical numpad key mapping (v1.8) 2026-10-31 19:14:38 +03:00
ScreamAngel 97bed359e8 feat: Add full Numpad and Mouse button support to key configuration and bump version to v1.8 2026-10-31 18:44:40 +03:00
ScreamAngel f4dd0eb1b5 Update README.md 2026-10-31 16:40:08 +03:00
ScreamAngel f5fb6a70df Update README.md 2026-10-31 15:27:32 +03:00
ScreamAngel 1d357ca52a Update Windows executable with v1.7 optimizations, icon and CPU grading 2026-10-31 15:12:23 +03:00
ScreamAngel 56e77b172b Fix Windows taskbar icon & fix redundant v1.7 update alert 2026-10-31 14:59:31 +03:00
ScreamAngel bfaf219d8d Add CPU hardware detection, rating & bottleneck advisory to dashboard (v1.7) 2026-10-31 14:38:47 +03:00
ScreamAngel b8423a0760 Optimize stuttering, async deferred readbacks & low-spec VRAM clamping (v1.7) 2026-10-31 14:04:36 +03:00
ScreamAngel de79b29de5 Fix native 1080p Vulkan device lost crash by correctly configuring upscaler and resolution env vars per mode (v1.6.1.6) 2026-10-30 04:07:33 +03:00
ScreamAngel 90cae4b6f7 Fix magic cast crash at 0x2de2d2 and enable universal pool allocator auto-growth (v1.6.1.5) 2026-10-30 03:38:25 +03:00
ScreamAngel 88ad672c6e Fix intro cinematic crash at offset 0x2c54b2 and bump version to v1.6.1.4
- Add 'Bug Fix - Intro Crash Fix' patch:
  * Expands event node memory pool from 128 to 2048 entries
  * Enables dynamic slab allocation for automatic pool expansion
  * Replaces stripped asserts with safe null checks returning cleanly
- Add crash analyzer diagnostic for 0x2c54b2
- Bump launcher version to v1.6.1.4
2026-10-30 03:22:18 +03:00
ScreamAngel 521e87e8c4 v1.6.1.3: Fix Santa Monica postfx null pointer crash (0x3f33d), hook pass execution instead of registration, enable FSR upscaler 2026-10-30 03:02:17 +03:00
ScreamAngel bb76fa8621 v1.6.1.2: Port eboot resolution scaling and effect bypass patches, enable FSR 1.0 scaling 2026-10-30 02:35:23 +03:00
ScreamAngel 6c4e36ec08 Fix resolution scaling crash: use native libgow3gpu FSR scaling instead of corrupting XML patch, bump to v1.6.1.1 2026-10-29 20:48:58 +03:00
ScreamAngel 34387f6c87 Bump launcher version to v1.6.1 and dynamically bind version to Cargo.toml 2026-10-29 19:29:38 +03:00
ScreamAngel 634571103b Fix lsfg-vk swapchain crash, hybrid GPU routing on Linux laptops, and mods.py parameter 2026-10-29 19:07:55 +03:00
ScreamAngel a709098214 Add dedicated Graphics Settings tab with presets, custom resolution scaling, FSR, and effect disabling patches 2026-10-29 13:13:02 +03:00
ScreamAngel b63a55cf02 Update README.md 2026-10-29 01:22:41 +03:00
ScreamAngel 5be05f0024 Update README.md 2026-10-29 01:10:07 +03:00
ScreamAngel 779fce78ab Update README.md 2026-10-29 01:06:47 +03:00
ScreamAngel d7aafd49e9 Update README.md 2026-10-29 01:04:56 +03:00
ScreamAngel 93e2d8d1fb Update README.md 2026-10-29 01:01:38 +03:00
ScreamAngel 57698ae376 Update README.md 2026-10-29 01:00:12 +03:00
ScreamAngel 50285f0ff8 Update README.md 2026-10-29 00:58:56 +03:00
ScreamAngel b58b282a35 Optimize rendering, fix yellow texture discoloration and overhaul FPS/Hz sync patches with 60Hz physics lock 2026-10-29 00:35:03 +03:00
ScreamAngel 72ec274d33 fix(launcher): rozet ve sürüm takibi dinamikleştirildi (v1.4 ve sonraki sürümler) 2026-10-26 18:21:06 +03:00
86 changed files with 12830 additions and 1790 deletions

No files matched your search

+2
View File
@@ -10,6 +10,7 @@ user/
out/
GLCache/
.runtime/
mod-game-*/
vkd3d-proton.cache
fsr4_shaders/
.local/
@@ -18,6 +19,7 @@ __pycache__/
# Logs & Configs generated during play
*.log
gow3_log.txt
launcher_log.txt
last_run.log
gow3.ini
+97
View File
@@ -0,0 +1,97 @@
# God of War III PC Launcher — Proje Hafızası ve Mimari Özeti
> **Versiyon:** v2.1
> **Depo:** [github.com/ScreamAngel/gow3_launcher](https://github.com/ScreamAngel/gow3_launcher) (`main` dalı)
> **Platform Desteği:** Linux (x86_64, Wayland/Hyprland & X11) ve Windows (x86_64 MSVC)
> **Tarih:** 07 Kasım 2026
---
## 1. Proje Mimarisi ve Çekirdek Dosyalar
* **Launcher Kaynak Kodu (`launcher/src/`):**
* `main.rs`: CLI başlatma argümanları (`--play`, `-p`), pencere ikonu yükleme ve eframe/egui giriş noktası.
* `app.rs`: Launcher ana GUI arayüzü, 6 sekme (Oyun, Grafik, Yapılandırma, Kontroller, Modlar, Sistem), shader derleme ekranı ve oyun süreci izleyicisi.
* `discord_rpc.rs`: Sıfır ek yükümlü, yerel Discord IPC istemcisi (Linux Unix Domain Socket & Windows Named Pipe), gerçek zamanlı bölüm & boss savaşı dedektörü, çoklu dil desteği (`RpcLanguage`).
* `win_input.rs`: Windows düşük seviye fare kancası (`WH_MOUSE_LL`), 1ms `timeBeginPeriod` zamanlayıcı çözünürlüğü ve oyun penceresini öne getirme (`SetForegroundWindow`).
* `runner.rs`: Mod bindirme (overlay) yöneticisi, Python/Rust mod montajı, oyun süreci (`gow3-probe`) çalıştırma ve log yönlendirme.
* `config.rs`: `launcher_config.json` yönetimi, donanım optimizasyonları ve otomatik sistem dili algılama (`default_language()`).
* `i18n.rs`: 4 dilli arayüz yerelleştirmesi (Türkçe, English, Deutsch, Español).
* `crash_analyzer.rs`: Oyun çöktüğünde `gow3_log.txt` çıktısını analiz edip kullanıcıya çözüm sunan hata teşhis motoru.
* **Dağıtılan İkili Dosyalar (Binaries):**
* Linux: `gow3-launcher`
* Windows: `gow3-launcher-win.exe`
* Alt dizin kopyaları: `gow3-linux/gow3-launcher`, `gow3-linux/gow3-launcher-win.exe`
---
## 2. Discord Rich Presence (RPC) Entegrasyonu
* **Resmi App ID:** `1440140183675867156` (*God of War III: Remastered*)
* **Resmi Varlıklar (Assets):**
* Kapak İkonu (Large Image): `https://cdn.discordapp.com/app-icons/1440140183675867156/2cbf4c7faf6444d474ff7b32b2025ec2.png`
* Rozet İkonu (Small Image): `https://raw.githubusercontent.com/ScreamAngel/gow3_launcher/main/gow3.png`
* **İletişim Kanalları:**
* **Linux:** `$XDG_RUNTIME_DIR/discord-ipc-0`..`9`, `/tmp/discord-ipc-0`..`9`, Flatpak, Snap ve Vesktop (`vesktop-ipc`).
* **Windows:** `\\.\pipe\discord-ipc-0`..`9` (Named Pipes). Olası donmaları engellemek için `PeekNamedPipe` ile asenkron kontrol mekanizması eklendi.
* **Gerçek Zamanlı Durum Tespiti:**
* `gow3_log.txt` dosyasından gerçek çalışma zamanı WAD yüklemeleri (`WAD_STATS: WAD <NAME>` ve `/WAD/PS4_LE/<NAME>.WAD`) taranır. Statik motor sözlüğü (`LoadWadSizeData`) **yoksayılır**.
* `user/savedata/*/CUSA01715/*.sce_sys/param.bin` SFO dosyasından anlık kayıt checkpoint'i ve zorluk derecesi çekilir.
* Ters kronolojik (newest-first) algoritma ile oyuncunun bulunduğu son bölge veya boss savaşı anlık belirlenir.
* **Otomatik Dil Desteği (tr, en, de, es):**
* İşletim sistemi dili (Linux'ta `$LANG`, Windows'ta `GetUserDefaultUILanguage`) veya launcher arayüz dili baz alınarak durum otomatik yerelleştirilir.
* **Bölüm & Boss Karşılıkları:**
* Poseidon Boss Savaşı: `Olympus Dağı (Poseidon Boss Fight)` / `Mount Olympus (Poseidon Boss Fight)`
* Hades Boss Savaşı: `Yeraltı Dünyası (Hades Boss Fight)` / `The Underworld (Hades Boss Fight)`
* Helios Boss Savaşı: `Olimpiya Şehri (Helios Boss Fight)` / `City of Olympia (Helios Boss Fight)`
* Hermes Kovalamacası: `Olimpiya Şehri (Hermes Kovalamacası)` / `City of Olympia (Hermes Chase)`
* Herkül Boss Savaşı: `Olympus Forumu (Hercules Boss Fight)` / `Forum of Olympus (Hercules Boss Fight)`
* Kronos Boss Savaşı: `Tartarus Çukuru (Cronos Boss Fight)` / `Pit of Tartarus (Cronos Boss Fight)`
* Hephaistos Boss Savaşı: `Demirhane (Hephaestus Boss Fight)` / `The Forge (Hephaestus Boss Fight)`
* Skorpius Boss Savaşı: `Olimpos Mağaraları (Skorpius Boss Fight)` / `Caverns of Olympus (Skorpius Boss Fight)`
* Zeus Boss Savaşı: `Olympus Tepesi (Zeus Boss Fight)` / `Summit of Olympus (Zeus Boss Fight)`
* Mücadeleler: `Olympus Mücadelesi 1-7` / `Challenge of Olympus 1-7`
* Savaş Arenası: `Savaş Arenası (Combat Arena)` / `Combat Arena`
* Menü: `Ana Menüde` / `In Main Menu`
* Shader Derleme: `Shader'lar Derleniyor (%X)` / `Compiling Shaders (X%)`
---
## 3. Çözülen Kritik Hatalar ve Teknik Notlar
1. **Windows Fare Tıklama Algılama Hatası:**
* Sorun: Windows'ta sol ve sağ tık çalışmıyordu.
* Çözüm: `win_input.rs` içindeki `WH_MOUSE_LL` kancasında PS4 SDL2 girdi haritasının beklediği F13/F14 sanal tarama kodları (`0x64` = Kare/Hafif Vuruş, `0x65` = Üçgen/Ağır Vuruş) düzeltildi.
2. **Windows Siyah Ekran / Mod Açılış Hatası:**
* Sorun: NTFS junction oluşturma yetki hatası yüzünden Windows'ta oyun siyah ekranda takılıyordu.
* Çözüm: `runner.rs` içinde Rust tabanlı doğrudan dosya kopyalama bindirmesi (`out/mod-game-rust-overlay`) uygulandı, yol formatı ve dosya doğrulaması eklendi.
3. **Shader Ekranı Küçülme / Kapanma / Arka Plana Düşme:**
* Sorun: Shader derleme ekranı açıldığında oyun arka plana düşüyor veya görev çubuğuna küçülüyordu.
* Çözüm: Oyun penceresi açıldığında shader ekranı 10 saniye boyunca arka planda tam ekran açık kalır, oyun penceresi öne getirilir (`bring_game_window_to_front`) ve süre bitiminde launcher normal moduna döner (asla küçülmez).
4. **Linux Hyprland / Wayland Tam Ekran Hatası:**
* Sorun: Windows için eklenen pencere boyutu komutları Linux Wayland'de oyunun kenarlıksız pencere moduna geçmesini bozuyordu.
* Çözüm: `Maximized(true)` ve ilgili `ViewportCommand` çağrıları `#[cfg(target_os = "windows")]` ile izole edildi, Linux'ta yerel `hyprctl` ve saf Wayland geometri protokolü korundu.
5. **Discord RPC'nin Sadece "Ana Menüde" Kalması:**
* Sorun: Motorun statik `LoadWadSizeData: WAD_Shell` satırı yüzünden log döngüsü erken çıkış veriyor ve boss/bölüm adımlarını atlıyordu.
* Çözüm: Statik sözlük filtrelendi, gerçek WAD yüklemeleri ters sırada önceliklendirildi.
---
## 4. Derleme ve Test Komutları
* **Birim Testleri (30 Test):**
```bash
cargo test --manifest-path launcher/Cargo.toml
```
* **Linux Release Derlemesi:**
```bash
cargo build --release --manifest-path launcher/Cargo.toml
cp -f launcher/target/release/launcher gow3-launcher
cp -f launcher/target/release/launcher gow3-linux/gow3-launcher
```
* **Windows Release Derlemesi (Cross-Compile):**
```bash
cargo xwin build --release --manifest-path launcher/Cargo.toml --target x86_64-pc-windows-msvc
cp -f launcher/target/x86_64-pc-windows-msvc/release/launcher.exe gow3-launcher-win.exe
cp -f launcher/target/x86_64-pc-windows-msvc/release/launcher.exe gow3-linux/gow3-launcher-win.exe
```
+42 -11
View File
@@ -1,6 +1,10 @@
# gow3_launcher
Launcher for God Of War 3 PC on Linux and Windows
**To ensure mod support works, install the launcher using a clean installation**
**Mod desteğinin düzgün çalışmasını sağlamak için, başlatıcıyı temiz bir kurulum yaparak yükleyin**
# ⚡ God of War® III Remastered - Linux-Windows Port & Native Launcher
An optimized Linux-Windows port and native graphical launcher for **God of War® III Remastered** (*CUSA01623 / CUSA01715*), featuring hardware auto-tuning, 144Hz Delta-Time synchronization, crash diagnostics, and upstream GitHub integration.
@@ -15,16 +19,15 @@ Launcher for God Of War 3 PC on Linux and Windows
## 🌟 Key Features
- 🦀 **Native Rust GUI Launcher (`gow3-launcher`):** Built with `eframe`/`egui`. Ultra-fast, lightweight (~17 MB standalone), with a sleek game-art interface and no heavy
dependencies.
- 🖥️ **Hardware Auto-Detection:** Automatically inspects GPU model, dedicated VRAM, system RAM, and display refresh rate (Hz) on launch.
- 🦀 **Native Rust GUI Launcher (`gow3-launcher`):** Built with `eframe`/`egui`. Ultra-fast, lightweight, with a sleek game-art interface and no heavy dependencies.
- 🖥️ **Hardware Auto-Detection:** Automatically inspects GPU model, CPU model, dedicated VRAM, system RAM, and display refresh rate (Hz) on launch.
- ⏱️ **144Hz / High-Hz Delta-Time Sync:** Eliminates the infamous 2x speed bug on 144Hz+ gaming displays by syncing engine delta-time with target framerates.
- 🎨 **Texture Glitch Fix (Instant Readbacks):** Prevents snowy, bright, or reflection textures from corrupting into golden-yellow artifacts.
- 🎮 **Feral GameMode Integration:** Built-in toggle for `gamemoderun` to maximize CPU governor and GPU clocks during gameplay.
- 🔍 **Smart Game Scanner & Completeness Check:** Scans drives to locate `eboot.bin` dumps, validates file integrity (`param.sfo`, elf size), and prevents launching corrupted
directories.
- 🚨 **Crash & Error Diagnostic Assistant:** Automatically analyzes `gow3_log.txt` on exit. If an unexpected crash occurs, it displays a plain-language diagnosis and recommended fix.
- 🔄 **GitHub Upstream Sync & Auto-Updater:** Checks upstream ([R-V-L/gow3_pc](https://github.com/R-V-L/gow3_pc)) for updates and re-applies all your custom directory paths, 144Hz patches, and VRAM budgets seamlessly.
- 🔄 **GitHub Upstream Sync & Auto-Updater:** Checks for updates in Upstream ([ScreamAngel/gow3_launcher](https://github.com/ScreamAngel/gow3_launcher)) and updates the launcher to the latest version.
- 🌐 **Multi-Language Support:** Available in **Turkish 🇹🇷**, **English 🇬🇧**, **German 🇩🇪**, and **Spanish 🇪🇸**.
@@ -33,7 +36,7 @@ Launcher for God Of War 3 PC on Linux and Windows
| Component | Minimum | Recommended |
|---|---|---|
| **OS** | Linux (Arch, Ubuntu, Fedora, SteamOS, Debian) and Windows 10-11 | Any modern 64-bit Linux distro |
| **OS** | Linux (Arch, Ubuntu, Fedora, SteamOS, Debian) and Windows 10-11 | Any modern 64-bit OS |
| **CPU** | 6-core x86_64 processor | 8-core / 16-thread modern CPU |
| **GPU** | Vulkan 1.3 compatible (4 GB VRAM) | Vulkan 1.3+ (6 GB+ VRAM, Nvidia / AMD) |
| **RAM** | 8 GB | 16 GB+ |
@@ -62,15 +65,40 @@ Launcher for God Of War 3 PC on Linux and Windows
- 🐧 **On Linux**: `./gow3-launcher`
- 🪟 **On Windows**: `gow3-launcher-win.exe`
**Option B: Download Pre-compiled Binaries (GitHub Releases)**
Download the executables directly from the GitHub Releases tab:
- **Linux**: `gow3-launcher`
- **Windows**: `gow3-launcher-win.exe`
**Option B: Download from release tab or website**
- Extract the downloaded file from the archive. Then, depending on your operating system, select and run the appropriate file ( “gow3-launcher1” for Linux or “gow3-launcher-win.exe” for Windows).
The launcher is completely standalone: it automatically detects your hardware, verifies the game dump, configures settings files, and applies all required patches without requiring external bash scripts.
### Sections of the launcher
**Home Screen**
<img width="1901" height="1025" alt="image" src="https://github.com/user-attachments/assets/31385740-38a4-4ddd-bf5b-3d795930c51f" />
**Graphics Settings**
<img width="2284" height="1241" alt="image" src="https://github.com/user-attachments/assets/b69c4452-9e0b-4b9a-ac3e-e387d947bda4" />
**Mods Settings**
<img width="1901" height="1024" alt="image" src="https://github.com/user-attachments/assets/7a73fbbd-eb58-49a4-a052-b397da7bcb0a" />
**System Specifications**
<img width="1898" height="1024" alt="image" src="https://github.com/user-attachments/assets/2643f04c-02af-44e5-a2d0-c8f764688759" />
**Game Location**
<img width="1898" height="1025" alt="image" src="https://github.com/user-attachments/assets/aff6bba2-489c-47cf-8db4-5812dc705371" />
**Keyboard Configuration**
<img width="1901" height="1030" alt="image" src="https://github.com/user-attachments/assets/e245193f-3863-4a23-93ee-d4253a0a0d28" />
**Shader Settings**
<img width="1900" height="1029" alt="image" src="https://github.com/user-attachments/assets/6db7a6d5-4474-411d-aa81-defdfb65da6b" />
<img width="1920" height="1016" alt="gow3_launcher_win_BJ42UCuIvf" src="https://github.com/user-attachments/assets/3d57aaed-8869-446e-bdb7-89491275039d" />
@@ -112,6 +140,8 @@ Launcher for God Of War 3 PC on Linux and Windows
Ensure **"Refresh Rate (Hz) Sync Patch"** is enabled and your target FPS matches your display.
- **Game crashes during heavy combat or asset streaming?**
Check if your GPU has at least 6GB VRAM. You can adjust the VRAM Garbage Collection Budget directly in the launcher.
- **Does the Launcher have a keyboard configuration?**
On the “Configuration & Controls” tab, you can assign any button to a function of your choice. DualShock 4 support is available.
@@ -121,12 +151,13 @@ Launcher for God Of War 3 PC on Linux and Windows
### Özellikler:
1. **Tek ve Sade Rust Launcher:** Karmaşık `.sh` scriptleri kaldırılmış, tüm pipeline doğrudan `gow3-launcher` içine gömülmüştür.
2. **Otomatik Donanım Algılama:** GPU, VRAM, RAM ve monitör yenileme hızınızı (Hz) anında tespit eder.
2. **Otomatik Donanım Algılama:** GPU, CPU, VRAM, RAM ve monitör yenileme hızınızı (Hz) anında tespit eder.
3. **144 Hz Düzeltmesi:** Yüksek yenileme hızlı monitörlerde oyunun hızlı akmasını (2x speed) engelleyen Delta-Time yaması.
4. **Doku Bozulması Koruması:** Karlı sahnelerdeki sararma hatasını çözen Instant Readbacks yaması.
5. **Feral GameMode:** Tek tıkla `gamemoderun` performans modu desteği.
6. **Akıllı Hata Teşhisi:** Oyun kapanırsa log dosyasını analiz edip hatanın sebebini ve çözümünü arayüzde bildirir.
7. **Steam / Steam Deck Desteği:** `--play` parametresi ile arayüze girmeden doğrudan oyunu başlatabilme.
8. **Klavye Konfigürasyonu:** "Yapılandırma & Kontroller" sekmesinden tüm tuşlara klavyeden bir tuş atayabilirsiniz. Ayrıca Dualshock 4 desteği mevcuttur.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+436
View File
@@ -0,0 +1,436 @@
// SPDX-License-Identifier: MIT
// God of War III Remastered - Linux & Windows Input Bridge & Mouse Injector
#ifdef _WIN32
#include <windows.h>
#else
#define _GNU_SOURCE
#include <dlfcn.h>
#include <strings.h>
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <SDL3/SDL.h>
// Scancodes used for mouse buttons (F13 - F19)
#define SCANCODE_MOUSE_LEFT 104 // F13
#define SCANCODE_MOUSE_RIGHT 105 // F14
#define SCANCODE_MOUSE_MIDDLE 106 // F15
#define SCANCODE_MOUSE_SIDE1 107 // F16
#define SCANCODE_MOUSE_SIDE2 108 // F17
#define SCANCODE_MOUSE_WHEELUP 109 // F18
#define SCANCODE_MOUSE_WHEELDN 110 // F19
typedef const bool* (*RealSDL_GetKeyboardState)(int *numkeys);
typedef SDL_MouseButtonFlags (*RealSDL_GetMouseState)(float *x, float *y);
typedef SDL_MouseButtonFlags (*RealSDL_GetGlobalMouseState)(float *x, float *y);
typedef SDL_Window* (*RealSDL_GetKeyboardFocus)(void);
typedef SDL_Window* (*RealSDL_GetMouseFocus)(void);
typedef bool (*RealSDL_CursorVisible)(void);
typedef bool (*RealSDL_HideCursor)(void);
typedef bool (*RealSDL_ShowCursor)(void);
typedef bool (*RealSDL_PollEvent)(SDL_Event *event);
typedef SDL_Scancode (*RealSDL_GetScancodeFromName)(const char *name);
typedef SDL_Surface* (*RealSDL_CreateSurface)(int width, int height, SDL_PixelFormat format);
typedef void (*RealSDL_DestroySurface)(SDL_Surface *surface);
typedef SDL_Cursor* (*RealSDL_CreateColorCursor)(SDL_Surface *surface, int hot_x, int hot_y);
typedef bool (*RealSDL_SetCursor)(SDL_Cursor *cursor);
typedef SDL_Window* (*RealSDL_CreateWindowWithProperties)(SDL_PropertiesID props);
typedef SDL_Window* (*RealSDL_CreateWindow)(const char *title, int w, int h, SDL_WindowFlags flags);
typedef bool (*RealSDL_ShowWindow)(SDL_Window *window);
typedef bool (*RealSDL_HideWindow)(SDL_Window *window);
typedef bool (*RealSDL_RaiseWindow)(SDL_Window *window);
static RealSDL_GetKeyboardState real_GetKeyboardState = NULL;
static RealSDL_GetMouseState real_GetMouseState = NULL;
static RealSDL_GetGlobalMouseState real_GetGlobalMouseState = NULL;
static RealSDL_GetKeyboardFocus real_GetKeyboardFocus = NULL;
static RealSDL_GetMouseFocus real_GetMouseFocus = NULL;
static RealSDL_CursorVisible real_CursorVisible = NULL;
static RealSDL_HideCursor real_HideCursor = NULL;
static RealSDL_ShowCursor real_ShowCursor = NULL;
static RealSDL_PollEvent real_PollEvent = NULL;
static RealSDL_GetScancodeFromName real_GetScancodeFromName = NULL;
static RealSDL_CreateSurface real_CreateSurface = NULL;
static RealSDL_DestroySurface real_DestroySurface = NULL;
static RealSDL_CreateColorCursor real_CreateColorCursor = NULL;
static RealSDL_SetCursor real_SetCursor = NULL;
static RealSDL_CreateWindowWithProperties real_CreateWindowWithProperties = NULL;
static RealSDL_CreateWindow real_CreateWindow = NULL;
static RealSDL_ShowWindow real_ShowWindow = NULL;
static RealSDL_HideWindow real_HideWindow = NULL;
static RealSDL_RaiseWindow real_RaiseWindow = NULL;
static int hide_cursor_enabled = -1;
static int wait_warmup_enabled = -1;
static volatile bool warmup_done = false;
static SDL_Window* tracked_game_window = NULL;
static volatile uint32_t tracked_mouse_buttons = 0;
static volatile uint32_t latched_mouse_buttons = 0;
static volatile int is_window_focused = 1;
static volatile int wheel_up_ticks = 0;
static volatile int wheel_dn_ticks = 0;
static SDL_Cursor* transparent_cursor = NULL;
static volatile bool cursor_is_hidden = false;
static void apply_cursor_hidden(void) {
if (!hide_cursor_enabled) return;
if (!transparent_cursor && real_CreateSurface && real_CreateColorCursor && real_DestroySurface) {
SDL_Surface* s = real_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
if (s) {
uint32_t* pix = (uint32_t*)s->pixels;
if (pix) *pix = 0; // 100% transparent pixel
transparent_cursor = real_CreateColorCursor(s, 0, 0);
real_DestroySurface(s);
}
}
if (transparent_cursor && real_SetCursor) {
real_SetCursor(transparent_cursor);
}
if (real_HideCursor) {
real_HideCursor();
}
cursor_is_hidden = true;
}
static void init_symbols(void) {
if (!real_GetKeyboardState) {
#ifdef _WIN32
HMODULE h = GetModuleHandleA("SDL3.dll");
if (!h) h = LoadLibraryA("SDL3.dll");
if (h) {
real_GetKeyboardState = (RealSDL_GetKeyboardState)GetProcAddress(h, "SDL_GetKeyboardState");
real_GetMouseState = (RealSDL_GetMouseState)GetProcAddress(h, "SDL_GetMouseState");
real_GetGlobalMouseState = (RealSDL_GetGlobalMouseState)GetProcAddress(h, "SDL_GetGlobalMouseState");
real_GetKeyboardFocus = (RealSDL_GetKeyboardFocus)GetProcAddress(h, "SDL_GetKeyboardFocus");
real_GetMouseFocus = (RealSDL_GetMouseFocus)GetProcAddress(h, "SDL_GetMouseFocus");
real_CursorVisible = (RealSDL_CursorVisible)GetProcAddress(h, "SDL_CursorVisible");
real_HideCursor = (RealSDL_HideCursor)GetProcAddress(h, "SDL_HideCursor");
real_ShowCursor = (RealSDL_ShowCursor)GetProcAddress(h, "SDL_ShowCursor");
real_PollEvent = (RealSDL_PollEvent)GetProcAddress(h, "SDL_PollEvent");
real_GetScancodeFromName = (RealSDL_GetScancodeFromName)GetProcAddress(h, "SDL_GetScancodeFromName");
real_CreateSurface = (RealSDL_CreateSurface)GetProcAddress(h, "SDL_CreateSurface");
real_DestroySurface = (RealSDL_DestroySurface)GetProcAddress(h, "SDL_DestroySurface");
real_CreateColorCursor = (RealSDL_CreateColorCursor)GetProcAddress(h, "SDL_CreateColorCursor");
real_SetCursor = (RealSDL_SetCursor)GetProcAddress(h, "SDL_SetCursor");
real_CreateWindowWithProperties = (RealSDL_CreateWindowWithProperties)GetProcAddress(h, "SDL_CreateWindowWithProperties");
real_CreateWindow = (RealSDL_CreateWindow)GetProcAddress(h, "SDL_CreateWindow");
real_ShowWindow = (RealSDL_ShowWindow)GetProcAddress(h, "SDL_ShowWindow");
real_HideWindow = (RealSDL_HideWindow)GetProcAddress(h, "SDL_HideWindow");
real_RaiseWindow = (RealSDL_RaiseWindow)GetProcAddress(h, "SDL_RaiseWindow");
}
#else
real_GetKeyboardState = (RealSDL_GetKeyboardState)dlsym(RTLD_NEXT, "SDL_GetKeyboardState");
if (!real_GetKeyboardState) {
void* h = dlopen("libSDL3.so.0", RTLD_NOW | RTLD_GLOBAL);
if (!h) h = dlopen("libSDL3.so", RTLD_NOW | RTLD_GLOBAL);
if (h) {
real_GetKeyboardState = (RealSDL_GetKeyboardState)dlsym(h, "SDL_GetKeyboardState");
real_GetMouseState = (RealSDL_GetMouseState)dlsym(h, "SDL_GetMouseState");
real_GetGlobalMouseState = (RealSDL_GetGlobalMouseState)dlsym(h, "SDL_GetGlobalMouseState");
real_GetKeyboardFocus = (RealSDL_GetKeyboardFocus)dlsym(h, "SDL_GetKeyboardFocus");
real_GetMouseFocus = (RealSDL_GetMouseFocus)dlsym(h, "SDL_GetMouseFocus");
real_CursorVisible = (RealSDL_CursorVisible)dlsym(h, "SDL_CursorVisible");
real_HideCursor = (RealSDL_HideCursor)dlsym(h, "SDL_HideCursor");
real_ShowCursor = (RealSDL_ShowCursor)dlsym(h, "SDL_ShowCursor");
real_PollEvent = (RealSDL_PollEvent)dlsym(h, "SDL_PollEvent");
real_GetScancodeFromName = (RealSDL_GetScancodeFromName)dlsym(h, "SDL_GetScancodeFromName");
real_CreateSurface = (RealSDL_CreateSurface)dlsym(h, "SDL_CreateSurface");
real_DestroySurface = (RealSDL_DestroySurface)dlsym(h, "SDL_DestroySurface");
real_CreateColorCursor = (RealSDL_CreateColorCursor)dlsym(h, "SDL_CreateColorCursor");
real_SetCursor = (RealSDL_SetCursor)dlsym(h, "SDL_SetCursor");
real_CreateWindowWithProperties = (RealSDL_CreateWindowWithProperties)dlsym(h, "SDL_CreateWindowWithProperties");
real_CreateWindow = (RealSDL_CreateWindow)dlsym(h, "SDL_CreateWindow");
real_ShowWindow = (RealSDL_ShowWindow)dlsym(h, "SDL_ShowWindow");
real_HideWindow = (RealSDL_HideWindow)dlsym(h, "SDL_HideWindow");
real_RaiseWindow = (RealSDL_RaiseWindow)dlsym(h, "SDL_RaiseWindow");
}
} else {
real_GetMouseState = (RealSDL_GetMouseState)dlsym(RTLD_NEXT, "SDL_GetMouseState");
real_GetGlobalMouseState = (RealSDL_GetGlobalMouseState)dlsym(RTLD_NEXT, "SDL_GetGlobalMouseState");
real_GetKeyboardFocus = (RealSDL_GetKeyboardFocus)dlsym(RTLD_NEXT, "SDL_GetKeyboardFocus");
real_GetMouseFocus = (RealSDL_GetMouseFocus)dlsym(RTLD_NEXT, "SDL_GetMouseFocus");
real_CursorVisible = (RealSDL_CursorVisible)dlsym(RTLD_NEXT, "SDL_CursorVisible");
real_HideCursor = (RealSDL_HideCursor)dlsym(RTLD_NEXT, "SDL_HideCursor");
real_ShowCursor = (RealSDL_ShowCursor)dlsym(RTLD_NEXT, "SDL_ShowCursor");
real_PollEvent = (RealSDL_PollEvent)dlsym(RTLD_NEXT, "SDL_PollEvent");
real_GetScancodeFromName = (RealSDL_GetScancodeFromName)dlsym(RTLD_NEXT, "SDL_GetScancodeFromName");
real_CreateSurface = (RealSDL_CreateSurface)dlsym(RTLD_NEXT, "SDL_CreateSurface");
real_DestroySurface = (RealSDL_DestroySurface)dlsym(RTLD_NEXT, "SDL_DestroySurface");
real_CreateColorCursor = (RealSDL_CreateColorCursor)dlsym(RTLD_NEXT, "SDL_CreateColorCursor");
real_SetCursor = (RealSDL_SetCursor)dlsym(RTLD_NEXT, "SDL_SetCursor");
real_CreateWindowWithProperties = (RealSDL_CreateWindowWithProperties)dlsym(RTLD_NEXT, "SDL_CreateWindowWithProperties");
real_CreateWindow = (RealSDL_CreateWindow)dlsym(RTLD_NEXT, "SDL_CreateWindow");
real_ShowWindow = (RealSDL_ShowWindow)dlsym(RTLD_NEXT, "SDL_ShowWindow");
real_HideWindow = (RealSDL_HideWindow)dlsym(RTLD_NEXT, "SDL_HideWindow");
real_RaiseWindow = (RealSDL_RaiseWindow)dlsym(RTLD_NEXT, "SDL_RaiseWindow");
}
#endif
}
if (hide_cursor_enabled == -1) {
const char *env = getenv("GOW3_HIDE_CURSOR");
hide_cursor_enabled = (env && (env[0] == '1' || env[0] == 't' || env[0] == 'T')) ? 1 : 0;
}
if (wait_warmup_enabled == -1) {
const char *w_env = getenv("GOW3_WAIT_WARMUP");
wait_warmup_enabled = (w_env && (w_env[0] == '1' || w_env[0] == 't' || w_env[0] == 'T')) ? 1 : 0;
}
}
#ifdef _WIN32
__declspec(dllexport)
#endif
SDL_Window* SDL_CreateWindowWithProperties(SDL_PropertiesID props) {
init_symbols();
SDL_Window* win = NULL;
if (real_CreateWindowWithProperties) {
win = real_CreateWindowWithProperties(props);
}
if (win) {
tracked_game_window = win;
apply_cursor_hidden();
if (wait_warmup_enabled && !warmup_done) {
if (real_HideWindow) {
real_HideWindow(win);
}
}
}
return win;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
SDL_Window* SDL_CreateWindow(const char *title, int w, int h, SDL_WindowFlags flags) {
init_symbols();
SDL_Window* win = NULL;
if (real_CreateWindow) {
win = real_CreateWindow(title, w, h, flags);
}
if (win) {
tracked_game_window = win;
apply_cursor_hidden();
if (wait_warmup_enabled && !warmup_done) {
if (real_HideWindow) {
real_HideWindow(win);
}
}
}
return win;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
bool SDL_ShowWindow(SDL_Window *window) {
init_symbols();
if (window) {
tracked_game_window = window;
}
if (wait_warmup_enabled && !warmup_done) {
// Keep window hidden until shader warmup is 100% complete
if (real_HideWindow && window) {
real_HideWindow(window);
}
return true;
}
if (real_ShowWindow && window) {
return real_ShowWindow(window);
}
return true;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
SDL_Scancode SDL_GetScancodeFromName(const char *name) {
init_symbols();
if (!name) return SDL_SCANCODE_UNKNOWN;
// Direct mapping for custom mouse buttons
#ifdef _WIN32
#define MATCH(s) (_stricmp(name, s) == 0)
#else
#define MATCH(s) (strcasecmp(name, s) == 0)
#endif
if (MATCH("LeftButton") || MATCH("Mouse1") || MATCH("LeftClick")) {
return (SDL_Scancode)SCANCODE_MOUSE_LEFT; // 104 (F13)
}
if (MATCH("RightButton") || MATCH("Mouse2") || MATCH("RightClick")) {
return (SDL_Scancode)SCANCODE_MOUSE_RIGHT; // 105 (F14)
}
if (MATCH("MiddleButton") || MATCH("Mouse3") || MATCH("MiddleClick")) {
return (SDL_Scancode)SCANCODE_MOUSE_MIDDLE; // 106 (F15)
}
if (MATCH("SideButtonBack") || MATCH("Mouse4") || MATCH("Side1")) {
return (SDL_Scancode)SCANCODE_MOUSE_SIDE1; // 107 (F16)
}
if (MATCH("SideButtonForward") || MATCH("Mouse5") || MATCH("Side2")) {
return (SDL_Scancode)SCANCODE_MOUSE_SIDE2; // 108 (F17)
}
if (MATCH("MouseWheelUp") || MATCH("WheelUp")) {
return (SDL_Scancode)SCANCODE_MOUSE_WHEELUP; // 109 (F18)
}
if (MATCH("MouseWheelDown") || MATCH("WheelDown")) {
return (SDL_Scancode)SCANCODE_MOUSE_WHEELDN; // 110 (F19)
}
#undef MATCH
if (real_GetScancodeFromName) {
return real_GetScancodeFromName(name);
}
return SDL_SCANCODE_UNKNOWN;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
const bool* SDL_GetKeyboardState(int *numkeys) {
init_symbols();
if (!real_GetKeyboardState) return NULL;
const bool* orig_state = real_GetKeyboardState(numkeys);
if (!orig_state) return NULL;
bool* state = (bool*)orig_state;
// Query mouse buttons & inject into keyboard scancode array
// tracked_mouse_buttons: continuously held buttons
// latched_mouse_buttons: quick clicks between poll frames, guaranteed not to be lost
uint32_t buttons = tracked_mouse_buttons | latched_mouse_buttons;
latched_mouse_buttons = 0; // consumed by this frame
if (real_GetMouseState) {
float mx = 0.0f, my = 0.0f;
buttons |= real_GetMouseState(&mx, &my);
}
if (!buttons && real_GetGlobalMouseState && is_window_focused) {
float mx = 0.0f, my = 0.0f;
buttons |= real_GetGlobalMouseState(&mx, &my);
}
state[SCANCODE_MOUSE_LEFT] = (buttons & SDL_BUTTON_LMASK) != 0;
state[SCANCODE_MOUSE_RIGHT] = (buttons & SDL_BUTTON_RMASK) != 0;
state[SCANCODE_MOUSE_MIDDLE] = (buttons & SDL_BUTTON_MMASK) != 0;
state[SCANCODE_MOUSE_SIDE1] = (buttons & SDL_BUTTON_X1MASK) != 0;
state[SCANCODE_MOUSE_SIDE2] = (buttons & SDL_BUTTON_X2MASK) != 0;
if (wheel_up_ticks > 0) {
state[SCANCODE_MOUSE_WHEELUP] = true;
wheel_up_ticks--;
}
if (wheel_dn_ticks > 0) {
state[SCANCODE_MOUSE_WHEELDN] = true;
wheel_dn_ticks--;
}
return orig_state;
}
#ifdef _WIN32
__declspec(dllexport)
#endif
bool SDL_PollEvent(SDL_Event *event) {
init_symbols();
if (!cursor_is_hidden) {
apply_cursor_hidden();
}
if (!real_PollEvent) return false;
bool result = real_PollEvent(event);
if (result && event) {
uint32_t event_type = event->type;
// Window focus gained / shown
if (event_type == SDL_EVENT_WINDOW_FOCUS_GAINED || event_type == 514 /* SDL_EVENT_WINDOW_SHOWN */) {
is_window_focused = 1;
apply_cursor_hidden();
} else if (event_type == SDL_EVENT_WINDOW_FOCUS_LOST) {
is_window_focused = 0;
tracked_mouse_buttons = 0;
latched_mouse_buttons = 0;
}
// Mouse button press / release
else if (event_type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
is_window_focused = 1;
uint8_t btn = event->button.button;
if (btn >= 1 && btn <= 5) {
uint32_t mask = (1u << (btn - 1));
tracked_mouse_buttons |= mask;
latched_mouse_buttons |= mask;
}
} else if (event_type == SDL_EVENT_MOUSE_BUTTON_UP) {
uint8_t btn = event->button.button;
if (btn >= 1 && btn <= 5) {
tracked_mouse_buttons &= ~(1u << (btn - 1));
}
} else if (event_type == SDL_EVENT_MOUSE_WHEEL) {
if (event->wheel.y > 0.0f) {
wheel_up_ticks = 2;
} else if (event->wheel.y < 0.0f) {
wheel_dn_ticks = 2;
}
}
}
return result;
}
// Override Gow3Overlay::SetLoading and ImGui::ProgressBar to completely eliminate
// the engine's plain generic loading bar in fullscreen mode, and reveal the window
// once shader warmup is 100% complete.
#ifdef _WIN32
__declspec(dllexport)
#endif
void _ZN11Gow3Overlay10SetLoadingEbjj(bool is_loading, unsigned int current, unsigned int total) {
init_symbols();
if (!is_loading || (total > 0 && current >= total)) {
warmup_done = true;
if (wait_warmup_enabled && tracked_game_window) {
if (real_ShowWindow) {
real_ShowWindow(tracked_game_window);
}
if (real_RaiseWindow) {
real_RaiseWindow(tracked_game_window);
}
}
}
}
#ifdef _WIN32
__declspec(dllexport)
#endif
void _ZN5ImGui11ProgressBarEfRK6ImVec2PKc(float fraction, const void* size_arg, const char* overlay) {
(void)fraction;
(void)size_arg;
(void)overlay;
}
#ifdef _WIN32
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
(void)hinstDLL;
(void)lpReserved;
if (fdwReason == DLL_PROCESS_ATTACH) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
return TRUE;
}
#else
__attribute__((constructor))
static void gow3_runtime_preinit(void) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
#endif
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -15,4 +15,4 @@ vsync=1
deferred_readback=1
stale_readback=1
async_shaders=1
frames_queued=1
frames_queued=2
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

+23 -332
View File
@@ -417,18 +417,6 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "bit-set"
version = "0.10.0"
@@ -637,12 +625,6 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.8"
@@ -729,12 +711,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "data-url"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "dispatch"
version = "0.2.0"
@@ -909,14 +885,12 @@ checksum = "05cbdc785020861553f230f23d330ba1fb72613b9d8e0e457f3bdc8ba6db6dcf"
dependencies = [
"ahash",
"egui",
"ehttp",
"enum-map",
"image",
"itertools",
"log",
"mime_guess2",
"profiling",
"resvg",
]
[[package]]
@@ -933,20 +907,6 @@ dependencies = [
"winit",
]
[[package]]
name = "ehttp"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f1b93eb2e039aaff63ce07cca59bd1dca02f2ce30075a17b619d2c42f56efc"
dependencies = [
"document-features",
"js-sys",
"ureq",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "either"
version = "1.19.0"
@@ -1135,12 +1095,6 @@ dependencies = [
"zlib-rs",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -1257,17 +1211,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
@@ -1291,16 +1234,6 @@ dependencies = [
"r-efi 6.0.0",
]
[[package]]
name = "gif"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "gl_generator"
version = "0.14.0"
@@ -1485,22 +1418,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "icu_collections"
version = "2.3.0"
@@ -1613,31 +1530,12 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"gif",
"image-webp",
"moxcms",
"num-traits",
"png 0.18.1",
"png",
"tiff",
]
[[package]]
name = "image-webp"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "imagesize"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "indexmap"
version = "2.14.2"
@@ -1759,17 +1657,6 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kurbo"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
dependencies = [
"arrayvec",
"euclid",
"smallvec",
]
[[package]]
name = "kurbo"
version = "0.13.1"
@@ -1784,14 +1671,16 @@ dependencies = [
[[package]]
name = "launcher"
version = "0.1.0"
version = "2.2.0"
dependencies = [
"eframe",
"egui_extras",
"image",
"libc",
"rfd",
"serde",
"serde_json",
"winres",
]
[[package]]
@@ -2443,7 +2332,7 @@ checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6"
dependencies = [
"bytemuck",
"color",
"kurbo 0.13.1",
"kurbo",
"linebender_resource_handle",
"smallvec",
]
@@ -2541,12 +2430,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.13"
@@ -2596,19 +2479,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.8.9",
]
[[package]]
name = "png"
version = "0.18.1"
@@ -2836,20 +2706,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "resvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
dependencies = [
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
]
[[package]]
name = "rfd"
version = "0.17.2"
@@ -2877,35 +2733,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rgb"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
dependencies = [
"bytemuck",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -2953,41 +2780,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.23"
@@ -3132,15 +2924,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "1.0.4"
@@ -3276,25 +3059,6 @@ name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "svgtypes"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo 0.11.3",
"siphasher",
]
[[package]]
name = "syn"
@@ -3416,7 +3180,6 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
"png 0.17.16",
"tiny-skia-path",
]
@@ -3450,6 +3213,15 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "1.1.2+spec-1.1.0"
@@ -3568,41 +3340,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc"
dependencies = [
"base64 0.23.1",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pki-types",
"ureq-proto",
"utf8-zero",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be"
dependencies = [
"base64 0.23.1",
"http",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.8"
@@ -3615,34 +3352,6 @@ dependencies = [
"serde",
]
[[package]]
name = "usvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"
dependencies = [
"base64 0.22.1",
"data-url",
"flate2",
"imagesize",
"kurbo 0.11.3",
"log",
"pico-args",
"roxmltree",
"simplecss",
"siphasher",
"strict-num",
"svgtypes",
"tiny-skia-path",
"xmlwriter",
]
[[package]]
name = "utf8-zero"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -3703,12 +3412,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.4+wasi-0.2.12"
@@ -3945,15 +3648,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "webpki-roots"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.12"
@@ -4414,6 +4108,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winres"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
"toml",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
@@ -4489,12 +4192,6 @@ version = "0.8.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yoke"
version = "0.8.3"
@@ -4665,12 +4362,6 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
[[package]]
name = "zerotrie"
version = "0.2.5"
+9 -3
View File
@@ -1,16 +1,22 @@
[package]
name = "launcher"
version = "0.1.0"
version = "2.2.0"
edition = "2024"
[dependencies]
eframe = "0.36.2"
egui_extras = { version = "0.36.2", features = ["all_loaders"] }
image = { version = "0.25.10", default-features = false, features = ["png"] }
egui_extras = { version = "0.36.2", features = ["image"] }
image = { version = "0.25.10", default-features = false, features = ["png", "bmp"] }
rfd = "0.17.2"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.154"
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"
[build-dependencies]
winres = "0.1"
[profile.release]
opt-level = 3
lto = "fat"
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "windows" {
let mut res = winres::WindowsResource::new();
res.set_icon("gow3.ico");
res.set("ProductName", "God of War III Remastered Launcher");
res.set("FileDescription", "God of War III Remastered PC Launcher");
res.set("LegalCopyright", "God of War III Remastered");
res.set("ProductVersion", "2.1.0.0");
res.set("FileVersion", "2.1.0.0");
if let Err(e) = res.compile() {
eprintln!("Warning: failed to compile Windows icon resource: {}", e);
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

+4316 -1116
View File
File diff suppressed because it is too large. Load diff
+485 -36
View File
@@ -2,6 +2,31 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum GraphicPreset {
#[default]
Balanced, // Standart / Orijinal (1080p)
UltraLow, // Ultra Düşük Donanım (480p)
Low, // Düşük Sistem / Steam Deck (720p)
SteamDeck,// Steam Deck 16:10 (800p)
Ultra, // Yüksek / 2K - 4K
Custom, // Özel (Kullanıcı Ayarlı)
}
impl GraphicPreset {
#[allow(dead_code)]
pub fn as_str(&self) -> &'static str {
match self {
GraphicPreset::Balanced => "balanced",
GraphicPreset::UltraLow => "ultra_low",
GraphicPreset::Low => "low",
GraphicPreset::SteamDeck => "steam_deck",
GraphicPreset::Ultra => "ultra",
GraphicPreset::Custom => "custom",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum DisplayMode {
#[default]
@@ -69,25 +94,36 @@ pub struct KeyboardBindings {
impl Default for KeyboardBindings {
fn default() -> Self {
Self::wasd_cvbn()
Self::recommended()
}
}
impl KeyboardBindings {
pub fn wasd_cvbn() -> Self {
/// 1. Önerilen Preset (Klavye):
/// - Üçgen: U
/// - Kare: T
/// - Yuvarlak: J
/// - X: G
/// - Sol Stick: W, A, S, D
/// - Sağ Stick: Numpad 8 (Yukarı), Numpad 2 (Aşağı), Numpad 4 (Sol), Numpad 6 (Sağ)
/// - Options: Enter
/// - Share veya Select: Backspace
/// - DPAD: Ok Tuşları (Yukarı, Aşağı, Sol, Sağ)
/// - L1/R1/L2/R2/L3/R3: Q, E, Z, X, C, V
pub fn recommended() -> Self {
Self {
triangle: "c".to_string(),
circle: "b".to_string(),
cross: "n".to_string(),
square: "v".to_string(),
triangle: "u".to_string(),
circle: "j".to_string(),
cross: "g".to_string(),
square: "t".to_string(),
l1: "q".to_string(),
r1: "u".to_string(),
l2: "e".to_string(),
r2: "o".to_string(),
l3: "x".to_string(),
r3: "m".to_string(),
r1: "e".to_string(),
l2: "z".to_string(),
r2: "x".to_string(),
l3: "c".to_string(),
r3: "v".to_string(),
options: "enter".to_string(),
touchpad_center: "space".to_string(),
touchpad_center: "backspace".to_string(),
pad_up: "up".to_string(),
pad_down: "down".to_string(),
pad_left: "left".to_string(),
@@ -96,27 +132,38 @@ impl KeyboardBindings {
axis_left_x_plus: "d".to_string(),
axis_left_y_minus: "w".to_string(),
axis_left_y_plus: "s".to_string(),
axis_right_x_minus: "j".to_string(),
axis_right_x_plus: "l".to_string(),
axis_right_y_minus: "i".to_string(),
axis_right_y_plus: "k".to_string(),
axis_right_x_minus: "kp4".to_string(),
axis_right_x_plus: "kp6".to_string(),
axis_right_y_minus: "kp8".to_string(),
axis_right_y_plus: "kp2".to_string(),
}
}
pub fn wasd_numpad() -> Self {
/// 2. İkinci Preset (Fare + Klavye):
/// - Üçgen: Fare Sağ Tık (rightbutton)
/// - Kare: Fare Sol Tık (leftbutton)
/// - Yuvarlak: F
/// - X: Space
/// - Sol Stick: W, A, S, D
/// - Sağ Stick: Numpad 8 (Yukarı), Numpad 2 (Aşağı), Numpad 4 (Sol), Numpad 6 (Sağ)
/// - Options: Enter
/// - Share veya Select: Backspace
/// - DPAD: Ok Tuşları (Yukarı, Aşağı, Sol, Sağ)
/// - L1/R1/L2/R2/L3/R3: Q, E, Z, X, C, V
pub fn mouse_keyboard() -> Self {
Self {
triangle: "kp8".to_string(),
circle: "kp6".to_string(),
cross: "kp2".to_string(),
square: "kp4".to_string(),
triangle: "rightbutton".to_string(),
circle: "f".to_string(),
cross: "space".to_string(),
square: "leftbutton".to_string(),
l1: "q".to_string(),
r1: "u".to_string(),
l2: "e".to_string(),
r2: "o".to_string(),
l3: "x".to_string(),
r3: "m".to_string(),
r1: "e".to_string(),
l2: "z".to_string(),
r2: "x".to_string(),
l3: "c".to_string(),
r3: "v".to_string(),
options: "enter".to_string(),
touchpad_center: "space".to_string(),
touchpad_center: "backspace".to_string(),
pad_up: "up".to_string(),
pad_down: "down".to_string(),
pad_left: "left".to_string(),
@@ -125,12 +172,14 @@ impl KeyboardBindings {
axis_left_x_plus: "d".to_string(),
axis_left_y_minus: "w".to_string(),
axis_left_y_plus: "s".to_string(),
axis_right_x_minus: "j".to_string(),
axis_right_x_plus: "l".to_string(),
axis_right_y_minus: "i".to_string(),
axis_right_y_plus: "k".to_string(),
axis_right_x_minus: "kp4".to_string(),
axis_right_x_plus: "kp6".to_string(),
axis_right_y_minus: "kp8".to_string(),
axis_right_y_plus: "kp2".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -139,6 +188,24 @@ pub struct CachedHardware {
pub vram_mb: u64,
pub ram_mb: u64,
pub refresh_rate_hz: u32,
#[serde(default = "default_cpu_name")]
pub cpu_name: String,
#[serde(default = "default_cpu_cores")]
pub cpu_cores: usize,
#[serde(default = "default_cpu_threads")]
pub cpu_threads: usize,
}
fn default_cpu_name() -> String {
"Bilinmeyen CPU".to_string()
}
fn default_cpu_cores() -> usize {
4
}
fn default_cpu_threads() -> usize {
4
}
impl Default for CachedHardware {
@@ -148,6 +215,9 @@ impl Default for CachedHardware {
vram_mb: 4096,
ram_mb: 8192,
refresh_rate_hz: 60,
cpu_name: "Bilinmeyen CPU".to_string(),
cpu_cores: 4,
cpu_threads: 4,
}
}
}
@@ -167,8 +237,16 @@ pub struct LauncherConfig {
#[serde(default = "default_true")]
pub patch_readbacks: bool,
#[serde(default = "default_true")]
pub async_readbacks: bool,
#[serde(default)]
pub launch_while_compiling_shaders: bool,
#[serde(default = "default_true")]
pub show_shader_compile_rage_meter: bool,
#[serde(default = "default_true")]
pub skip_cinematics: bool,
#[serde(default = "default_true")]
pub bonus_play_fix: bool,
#[serde(default = "default_true")]
pub use_gamemode: bool,
#[serde(default = "default_vram_budget")]
pub vram_gc_budget_mb: u64,
@@ -178,6 +256,24 @@ pub struct LauncherConfig {
pub cached_hardware: CachedHardware,
#[serde(default = "default_last_commit")]
pub last_git_commit: String,
#[serde(default = "default_launcher_version")]
pub launcher_version: String,
// Mods Management
#[serde(default)]
pub disabled_mods: Vec<String>,
// Graphics Settings
#[serde(default = "default_render_resolution")]
pub render_resolution: String,
#[serde(default = "default_true")]
pub fsr_enabled: bool,
#[serde(default)]
pub disable_motion_blur: bool,
#[serde(default)]
pub disable_dof: bool,
#[serde(default)]
pub graphic_preset: GraphicPreset,
// Display settings
#[serde(default)]
@@ -189,6 +285,8 @@ pub struct LauncherConfig {
#[serde(default)]
pub background_controller_input: bool,
#[serde(default = "default_true")]
pub hide_mouse_cursor: bool,
#[serde(default = "default_true")]
pub is_motion_controls_enabled: bool,
#[serde(default = "default_deadzone")]
pub deadzone_left: u32,
@@ -198,10 +296,20 @@ pub struct LauncherConfig {
// Keyboard bindings
#[serde(default)]
pub keyboard_bindings: KeyboardBindings,
// Discord Rich Presence
#[serde(default = "default_true")]
pub discord_rpc_enabled: bool,
#[serde(default = "default_discord_client_id")]
pub discord_client_id: String,
}
fn default_discord_client_id() -> String {
crate::discord_rpc::DISCORD_GOW3_APP_ID.to_string()
}
fn default_language() -> String {
"tr".to_string()
crate::discord_rpc::detect_system_language().to_string()
}
fn default_target_fps() -> u32 {
60
@@ -209,6 +317,10 @@ fn default_target_fps() -> u32 {
fn default_true() -> bool {
true
}
#[allow(dead_code)]
fn default_false() -> bool {
false
}
fn default_deadzone() -> u32 {
2
}
@@ -221,34 +333,202 @@ fn default_dmem() -> u64 {
fn default_last_commit() -> String {
"c5638d9956002b8a9f09a240028f6793ddf7b2f8".to_string()
}
fn default_launcher_version() -> String {
"v2.2".to_string()
}
fn default_render_resolution() -> String {
"native".to_string()
}
impl Default for LauncherConfig {
fn default() -> Self {
Self {
first_launch_done: false,
language: default_language(),
launcher_version: default_launcher_version(),
game_dir: String::new(),
target_fps: default_target_fps(),
patch_delta_time: true,
patch_readbacks: true,
async_readbacks: true,
launch_while_compiling_shaders: false,
show_shader_compile_rage_meter: true,
skip_cinematics: true,
bonus_play_fix: true,
use_gamemode: true,
vram_gc_budget_mb: default_vram_budget(),
dmem_mb: default_dmem(),
cached_hardware: CachedHardware::default(),
last_git_commit: default_last_commit(),
disabled_mods: Vec::new(),
render_resolution: default_render_resolution(),
fsr_enabled: false,
disable_motion_blur: false,
disable_dof: false,
graphic_preset: GraphicPreset::Balanced,
display_mode: DisplayMode::default(),
vsync: true,
background_controller_input: false,
hide_mouse_cursor: true,
is_motion_controls_enabled: true,
deadzone_left: default_deadzone(),
deadzone_right: default_deadzone(),
keyboard_bindings: KeyboardBindings::default(),
discord_rpc_enabled: true,
discord_client_id: default_discord_client_id(),
}
}
}
impl LauncherConfig {
pub fn apply_preset(&mut self, preset: GraphicPreset) {
self.graphic_preset = preset;
match preset {
GraphicPreset::UltraLow => {
self.render_resolution = "480p".to_string();
self.fsr_enabled = false;
self.disable_motion_blur = true;
self.disable_dof = true;
self.vram_gc_budget_mb = 1280;
self.target_fps = 60;
}
GraphicPreset::Low => {
self.render_resolution = "720p".to_string();
self.fsr_enabled = true;
self.disable_motion_blur = true;
self.disable_dof = true;
self.vram_gc_budget_mb = 2048;
self.target_fps = 60;
}
GraphicPreset::SteamDeck => {
self.render_resolution = "800p".to_string();
self.fsr_enabled = true;
self.disable_motion_blur = true;
self.disable_dof = true;
self.vram_gc_budget_mb = 2048;
self.target_fps = 40;
}
GraphicPreset::Balanced => {
self.render_resolution = "native".to_string();
self.fsr_enabled = false;
self.disable_motion_blur = false;
self.disable_dof = false;
self.vram_gc_budget_mb = 6144;
self.target_fps = 60;
}
GraphicPreset::Ultra => {
self.render_resolution = "1440p".to_string();
self.fsr_enabled = false;
self.disable_motion_blur = false;
self.disable_dof = false;
self.vram_gc_budget_mb = 8192;
self.target_fps = 120;
}
GraphicPreset::Custom => {
// Keep customized values
}
}
}
pub fn check_preset_match(&mut self) {
if self.render_resolution == "480p" && !self.fsr_enabled && self.disable_motion_blur && self.disable_dof {
self.graphic_preset = GraphicPreset::UltraLow;
} else if self.render_resolution == "720p" && self.fsr_enabled && self.disable_motion_blur && self.disable_dof {
self.graphic_preset = GraphicPreset::Low;
} else if self.render_resolution == "800p" && self.fsr_enabled && self.disable_motion_blur && self.disable_dof {
self.graphic_preset = GraphicPreset::SteamDeck;
} else if self.render_resolution == "native" && !self.fsr_enabled && !self.disable_motion_blur && !self.disable_dof {
self.graphic_preset = GraphicPreset::Balanced;
} else if (self.render_resolution == "1440p" || self.render_resolution == "4K") && !self.fsr_enabled && !self.disable_motion_blur && !self.disable_dof {
self.graphic_preset = GraphicPreset::Ultra;
} else {
self.graphic_preset = GraphicPreset::Custom;
}
}
pub fn get_resolution_dimensions(&self) -> (u32, u32) {
match self.render_resolution.as_str() {
"480p" => (854, 480),
"720p" => (1280, 720),
"800p" => (1280, 800),
"1080p" => (1920, 1080),
"1440p" => (2560, 1440),
"4K" | "2160p" => (3840, 2160),
_ => (1920, 1080),
}
}
pub fn get_render_res_env_value(&self) -> Option<String> {
if self.render_resolution == "native" {
None
} else {
let (w, h) = self.get_resolution_dimensions();
Some(format!("{}x{}", w, h))
}
}
pub fn get_effective_vram_budget(&self) -> u64 {
let vram = self.cached_hardware.vram_mb;
if vram > 0 {
// Guard against PCIe memory thrashing / swapping to system RAM over slow bus:
// Desktop environment, window compositor (Wayland/X11) and GPU drivers consume ~600-1200MB of VRAM.
// Game framebuffers, guest DMA buffers, swapchains and staging memory consume another ~1-1.5GB.
// Texture GC budget must leave sufficient headroom while preventing premature texture purging (matches v1.8.2).
let safe_ceiling = if vram <= 2048 {
1280
} else if vram <= 3072 {
2048
} else if vram <= 4096 {
3072
} else if vram <= 6144 {
4608
} else if vram <= 8192 {
6144
} else if vram <= 12288 {
8192
} else {
vram.saturating_sub(1024)
};
self.vram_gc_budget_mb.min(safe_ceiling)
} else {
self.vram_gc_budget_mb
}
}
pub fn is_mouse_button_bound(&self) -> bool {
let is_mouse = |s: &str| {
let s_low = s.trim().to_lowercase();
matches!(
s_low.as_str(),
"leftbutton" | "rightbutton" | "middlebutton" | "sidebuttonback" | "sidebuttonforward" | "mouse1" | "mouse2" | "mouse3" | "mouse4" | "mouse5" | "mousewheelup" | "mousewheeldown"
)
};
let b = &self.keyboard_bindings;
is_mouse(&b.triangle)
|| is_mouse(&b.circle)
|| is_mouse(&b.cross)
|| is_mouse(&b.square)
|| is_mouse(&b.l1)
|| is_mouse(&b.r1)
|| is_mouse(&b.l2)
|| is_mouse(&b.r2)
|| is_mouse(&b.l3)
|| is_mouse(&b.r3)
|| is_mouse(&b.options)
|| is_mouse(&b.touchpad_center)
|| is_mouse(&b.pad_up)
|| is_mouse(&b.pad_down)
|| is_mouse(&b.pad_left)
|| is_mouse(&b.pad_right)
|| is_mouse(&b.axis_left_x_minus)
|| is_mouse(&b.axis_left_x_plus)
|| is_mouse(&b.axis_left_y_minus)
|| is_mouse(&b.axis_left_y_plus)
|| is_mouse(&b.axis_right_x_minus)
|| is_mouse(&b.axis_right_x_plus)
|| is_mouse(&b.axis_right_y_minus)
|| is_mouse(&b.axis_right_y_plus)
}
pub fn config_path(base_dir: &Path) -> PathBuf {
base_dir.join("launcher_config.json")
}
@@ -257,7 +537,31 @@ impl LauncherConfig {
let path = Self::config_path(base_dir);
if path.exists() {
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(cfg) = serde_json::from_str::<LauncherConfig>(&content) {
if let Ok(mut cfg) = serde_json::from_str::<LauncherConfig>(&content) {
let mut changed = false;
// Migrate from older versions: ensure v2.0 version string and enable recommended async_readbacks
if cfg.launcher_version != "v2.0" {
cfg.launcher_version = "v2.0".to_string();
cfg.async_readbacks = true;
changed = true;
}
// If the user's config still has old default bindings,
// automatically update to the new recommended preset.
if (cfg.keyboard_bindings.triangle == "c" && cfg.keyboard_bindings.square == "v")
|| (cfg.keyboard_bindings.triangle == "kp8" && cfg.keyboard_bindings.square == "kp4" && cfg.keyboard_bindings.cross == "kp2")
|| (cfg.keyboard_bindings.triangle == "u" && cfg.keyboard_bindings.r1 == "i")
|| (cfg.keyboard_bindings.triangle == "rightbutton" && cfg.keyboard_bindings.r1 == "r")
{
cfg.keyboard_bindings = KeyboardBindings::recommended();
changed = true;
}
if cfg.discord_client_id != crate::discord_rpc::DISCORD_GOW3_APP_ID {
cfg.discord_client_id = crate::discord_rpc::DISCORD_GOW3_APP_ID.to_string();
changed = true;
}
if changed {
let _ = cfg.save(base_dir);
}
return cfg;
}
}
@@ -267,10 +571,23 @@ impl LauncherConfig {
default_cfg
}
pub fn save_mods_config(&self, base_dir: &Path) {
let mods_config_path = base_dir.join("mods_config.json");
let json = serde_json::json!({
"disabled": self.disabled_mods,
"order": []
});
if let Ok(content) = serde_json::to_string_pretty(&json) {
let _ = fs::write(&mods_config_path, content);
}
}
pub fn save(&self, base_dir: &Path) -> std::io::Result<()> {
let path = Self::config_path(base_dir);
let content = serde_json::to_string_pretty(self)?;
fs::write(path, content)
let res = fs::write(path, content);
self.save_mods_config(base_dir);
res
}
pub fn build_gow3_key_map(&self) -> String {
@@ -290,6 +607,19 @@ impl LauncherConfig {
s if s.starts_with("kp") && s.len() == 3 && s[2..].chars().all(|c| c.is_ascii_digit()) => {
format!("Keypad {}", &s[2..])
}
"kp_plus" | "kpplus" => "Keypad +".to_string(),
"kp_minus" | "kpminus" => "Keypad -".to_string(),
"kp_multiply" | "kpasterisk" => "Keypad *".to_string(),
"kp_divide" | "kpslash" => "Keypad /".to_string(),
"kp_enter" | "kpenter" => "Keypad Enter".to_string(),
"kp_period" | "kpperiod" => "Keypad .".to_string(),
"leftbutton" | "mouse1" | "leftclick" => "F13".to_string(),
"rightbutton" | "mouse2" | "rightclick" => "F14".to_string(),
"middlebutton" | "mouse3" | "middleclick" => "F15".to_string(),
"sidebuttonback" | "mouse4" | "side1" => "F16".to_string(),
"sidebuttonforward" | "mouse5" | "side2" => "F17".to_string(),
"mousewheelup" | "wheelup" => "F18".to_string(),
"mousewheeldown" | "wheeldown" => "F19".to_string(),
"shift" | "lshift" => "Left Shift".to_string(),
"rshift" => "Right Shift".to_string(),
"ctrl" | "lctrl" => "Left Ctrl".to_string(),
@@ -406,8 +736,8 @@ pub fn key_to_sdl_scancode(key: &str) -> Option<u8> {
"up" => Some(82),
// Numpad (SDL_SCANCODE_KP_1 = 89 .. SDL_SCANCODE_KP_0 = 98)
"kp_divide" | "kpdivide" | "numdivide" => Some(84),
"kp_multiply" | "kpmultiply" | "nummultiply" => Some(85),
"kp_divide" | "kpdivide" | "numdivide" | "kpslash" => Some(84),
"kp_multiply" | "kpmultiply" | "nummultiply" | "kpasterisk" => Some(85),
"kp_minus" | "kpminus" | "numminus" => Some(86),
"kp_plus" | "kpplus" | "numplus" => Some(87),
"kp_enter" | "kpenter" | "numenter" => Some(88),
@@ -431,6 +761,125 @@ pub fn key_to_sdl_scancode(key: &str) -> Option<u8> {
"rshift" => Some(229),
"ralt" => Some(230),
// Mouse buttons (F13..F19)
"leftbutton" | "mouse1" | "leftclick" => Some(104),
"rightbutton" | "mouse2" | "rightclick" => Some(105),
"middlebutton" | "mouse3" | "middleclick" => Some(106),
"sidebuttonback" | "mouse4" | "side1" => Some(107),
"sidebuttonforward" | "mouse5" | "side2" => Some(108),
"mousewheelup" | "wheelup" => Some(109),
"mousewheeldown" | "wheeldown" => Some(110),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_numpad_scancodes() {
assert_eq!(key_to_sdl_scancode("kp0"), Some(98));
assert_eq!(key_to_sdl_scancode("kp1"), Some(89));
assert_eq!(key_to_sdl_scancode("kp2"), Some(90));
assert_eq!(key_to_sdl_scancode("kp3"), Some(91));
assert_eq!(key_to_sdl_scancode("kp4"), Some(92));
assert_eq!(key_to_sdl_scancode("kp5"), Some(93));
assert_eq!(key_to_sdl_scancode("kp6"), Some(94));
assert_eq!(key_to_sdl_scancode("kp7"), Some(95));
assert_eq!(key_to_sdl_scancode("kp8"), Some(96));
assert_eq!(key_to_sdl_scancode("kp9"), Some(97));
assert_eq!(key_to_sdl_scancode("kpplus"), Some(87));
assert_eq!(key_to_sdl_scancode("kpminus"), Some(86));
assert_eq!(key_to_sdl_scancode("kpasterisk"), Some(85));
assert_eq!(key_to_sdl_scancode("kpslash"), Some(84));
assert_eq!(key_to_sdl_scancode("kpenter"), Some(88));
assert_eq!(key_to_sdl_scancode("kpperiod"), Some(99));
assert_eq!(key_to_sdl_scancode("leftbutton"), Some(104));
assert_eq!(key_to_sdl_scancode("rightbutton"), Some(105));
assert_eq!(key_to_sdl_scancode("middlebutton"), Some(106));
assert_eq!(key_to_sdl_scancode("sidebuttonback"), Some(107));
assert_eq!(key_to_sdl_scancode("sidebuttonforward"), Some(108));
}
#[test]
fn test_launcher_version_is_v22() {
assert_eq!(default_launcher_version(), "v2.2");
let def = LauncherConfig::default();
assert!(def.async_readbacks);
assert!(def.patch_readbacks);
assert!(def.bonus_play_fix);
assert!(def.discord_rpc_enabled);
assert_eq!(def.discord_client_id, crate::discord_rpc::DISCORD_GOW3_APP_ID);
}
#[test]
fn test_keyboard_presets() {
let p1 = KeyboardBindings::recommended();
assert_eq!(p1.triangle, "u");
assert_eq!(p1.square, "t");
assert_eq!(p1.circle, "j");
assert_eq!(p1.cross, "g");
assert_eq!(p1.axis_left_y_minus, "w");
assert_eq!(p1.axis_left_x_minus, "a");
assert_eq!(p1.axis_left_y_plus, "s");
assert_eq!(p1.axis_left_x_plus, "d");
assert_eq!(p1.axis_right_y_minus, "kp8");
assert_eq!(p1.axis_right_y_plus, "kp2");
assert_eq!(p1.axis_right_x_minus, "kp4");
assert_eq!(p1.axis_right_x_plus, "kp6");
assert_eq!(p1.l1, "q");
assert_eq!(p1.r1, "e");
assert_eq!(p1.l2, "z");
assert_eq!(p1.r2, "x");
assert_eq!(p1.l3, "c");
assert_eq!(p1.r3, "v");
assert_eq!(p1.options, "enter");
assert_eq!(p1.touchpad_center, "backspace");
assert_eq!(p1.pad_up, "up");
assert_eq!(p1.pad_down, "down");
assert_eq!(p1.pad_left, "left");
assert_eq!(p1.pad_right, "right");
let p2 = KeyboardBindings::mouse_keyboard();
assert_eq!(p2.triangle, "rightbutton");
assert_eq!(p2.square, "leftbutton");
assert_eq!(p2.circle, "f");
assert_eq!(p2.cross, "space");
assert_eq!(p2.l1, "q");
assert_eq!(p2.r1, "e");
assert_eq!(p2.l2, "z");
assert_eq!(p2.r2, "x");
assert_eq!(p2.l3, "c");
assert_eq!(p2.r3, "v");
assert_eq!(p2.axis_left_y_minus, "w");
assert_eq!(p2.axis_left_x_minus, "a");
assert_eq!(p2.axis_left_y_plus, "s");
assert_eq!(p2.axis_left_x_plus, "d");
assert_eq!(p2.axis_right_y_minus, "kp8");
assert_eq!(p2.axis_right_y_plus, "kp2");
assert_eq!(p2.axis_right_x_minus, "kp4");
assert_eq!(p2.axis_right_x_plus, "kp6");
assert_eq!(p2.options, "enter");
assert_eq!(p2.touchpad_center, "backspace");
assert_eq!(p2.pad_up, "up");
assert_eq!(p2.pad_down, "down");
assert_eq!(p2.pad_left, "left");
assert_eq!(p2.pad_right, "right");
}
#[test]
fn test_shader_rage_meter_config() {
let default_cfg = LauncherConfig::default();
assert!(default_cfg.show_shader_compile_rage_meter);
let json = r#"{"show_shader_compile_rage_meter": false}"#;
let deserialized: LauncherConfig = serde_json::from_str(json).unwrap();
assert!(!deserialized.show_shader_compile_rage_meter);
let empty_json = r#"{}"#;
let deserialized_empty: LauncherConfig = serde_json::from_str(empty_json).unwrap();
assert!(deserialized_empty.show_shader_compile_rage_meter);
}
}
+149 -2
View File
@@ -21,7 +21,77 @@ pub fn analyze_post_game(base_dir: &Path, exit_code: Option<i32>) -> Option<Cras
let tail_lines: Vec<&str> = log_content.lines().rev().take(100).collect();
let tail_str = tail_lines.iter().rev().cloned().collect::<Vec<&str>>().join("\n");
// 1. Check for Out of Memory
// 1. Check for Broken Vulkan Layers (lsfg-vk / Lossless Scaling)
if tail_str.contains("lsfg-vk") && (tail_str.contains("shader DLL does not exist") || tail_str.contains("Failed to create swapchain") || tail_str.contains("ErrorInitializationFailed")) {
return Some(CrashDiagnosis {
title: "💥 Hatalı Vulkan Katmanı Algılandı (lsfg-vk / Lossless Scaling)".to_string(),
description: "Sisteminizde kurulu harici 'lsfg-vk' Vulkan katmanı, Lossless Scaling shader DLL dosyasını bulamadığı için ekran penceresini (swapchain) kilitledi ve oyunu çökertti.".to_string(),
recommendation: "Launcher bu hatalı lsfg-vk katmanını otomatik engeller. Manuel olarak 'VK_LOADER_LAYERS_DISABLE=\"*lsfg*\"' ortam değişkenini kullanabilir veya '~/.config/lsfg-vk/conf.toml' içindeki Lossless.dll dosya yolunu düzeltebilirsiniz.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "lsfg-vk"),
});
}
// 2. Check for Incompatible XML Resolution Patch Segfault (0x1f0b3a)
if tail_str.contains("0x1f0b3a") || tail_str.contains("0x2000003") {
return Some(CrashDiagnosis {
title: "💥 Uyumsuz EBOOT Çözünürlük Yaması Algılandı".to_string(),
description: "Oyunun eski XML tabanlı çözünürlük yaması eboot ikili kodunu bozduğu için motor başlatılırken çöktü.".to_string(),
recommendation: "Launcher'ın yeni sürümü çözünürlük ölçeklemeyi GPU kütüphanesi (FSR 1.0) üzerinden donanımsal yapar. Launcher'ı güncelleyin.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "0x1f0b3a"),
});
}
// Check for Magic / Combat Pool Crash (0x2de2d2)
if tail_str.contains("0x2de2d2") {
return Some(CrashDiagnosis {
title: "💥 Büyü / Savaş Olay Havuzu Çökmesi Algılandı (0x2de2d2)".to_string(),
description: "Büyü kullanırken veya yoğun savaş sahnelerinde oyun motorunun olay düğüm havuzu (128 eleman) tükendiği için boş bellek adresi (0x10) referans alınarak çöktü.".to_string(),
recommendation: "v1.7 sürümündeki evrensel bellek havuzu genişletmesi ve NULL koruması bu sorunu çözer. Launcher'ı güncelleyip oyunu tekrar başlatın.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "0x2de2d2"),
});
}
// Check for Intro Transition / Memory Pool Crash (0x2c54b2)
if tail_str.contains("0x2c54b2") {
return Some(CrashDiagnosis {
title: "💥 Giriş İntrosu / Sahne Geçiş Çökmesi Algılandı (0x2c54b2)".to_string(),
description: "Oyun motorunun olay havuzu (128 eleman) yüksek kare hızında veya introyu geçerken tükendiği için boş bellek adresi (0x10) referans alınarak çöktü.".to_string(),
recommendation: "v1.7 sürümündeki 'Bug Fix - Intro Crash Fix' yaması havuzu 2048'e çıkarıp otomatik genişletme ve güvenli NULL kontrolü ekler. Launcher'ı güncelleyip oyunu tekrar başlatın.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "0x2c54b2"),
});
}
// Check for Vulkan Scheduler Device Lost (SubmitExecution / Assertion Failed)
if tail_str.contains("vk_scheduler.cpp:465 SubmitExecution") || tail_str.contains("Device lost during submit") {
return Some(CrashDiagnosis {
title: "💥 Vulkan Grafik Teslimat Hatası (Device Lost)".to_string(),
description: "Vulkan grafik komutları GPU'ya teslim edilirken sürücü zaman aşımına uğradı veya ölçekleyici (Upscaler/FSR) çakışması yaşandı.".to_string(),
recommendation: "v1.7 sürümünde Native 1080p ve çözünürlük ölçekleyici ortam değişkenleri düzeltilmiştir. Launcher'ı güncelleyip grafik profilini 'Dengeli (Native 1080p)' seçeneğine ayarlayarak oyunu başlatın.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "SubmitExecution"),
});
}
// 2. Check for Incomplete Haswell/Intel iGPU on hybrid laptops
if tail_str.contains("Haswell Vulkan support is incomplete") || (tail_str.contains("MESA-INTEL") && tail_str.contains("DepthStencilAttachment")) {
return Some(CrashDiagnosis {
title: "⚠️ Uyumsuz Dahili Intel GPU (Haswell / Eski iGPU)".to_string(),
description: "Oyun, modern Vulkan özelliklerini desteklemeyen eski Intel Haswell dahili ekran kartında çalıştırılmış. Harici 2GB ekran kartınız (NVIDIA/AMD) devreye girmemiş.".to_string(),
recommendation: "Launcher harici GPU'yu otomatik seçmeye çalışır. Manuel başlatmak için NVIDIA'da 'prime-run ./gow3-launcher' veya '__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia ./gow3-launcher', AMD'de 'DRI_PRIME=1 ./gow3-launcher' komutunu kullanın.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "Haswell"),
});
}
// 3. Check for Swapchain Creation Failure
if tail_str.contains("Failed to create swapchain") || tail_str.contains("vk_swapchain.cpp") {
return Some(CrashDiagnosis {
title: "💥 Vulkan Swapchain (Görüntü Çıkışı) Başlatılamadı".to_string(),
description: "Grafik kartı sürücüsü veya araya giren harici bir Vulkan katmanı oyunun pencere/ekran yüzeyini oluşturmasını engelledi.".to_string(),
recommendation: "Harici Vulkan katmanlarını kapatın, GPU sürücülerinizi güncelleyin ve tam ekran modunu pencereli moda alarak tekrar deneyin.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "swapchain"),
});
}
// 4. Check for Out of Memory
if tail_str.contains("VK_ERROR_OUT_OF_DEVICE_MEMORY") || tail_str.contains("Out of memory") || tail_str.contains("dmem out of memory") {
return Some(CrashDiagnosis {
title: "💥 Bellek / VRAM Yetersizliği Algılandı".to_string(),
@@ -41,16 +111,51 @@ pub fn analyze_post_game(base_dir: &Path, exit_code: Option<i32>) -> Option<Cras
});
}
// Check for GPU PM4 / Liverpool Packet Overrun or Assertion
if tail_str.contains("packet length exceeds remaining submission size")
|| tail_str.contains("Unimplemented PM4")
|| (tail_str.contains("liverpool.cpp") && tail_str.contains("Unreachable code"))
{
return Some(CrashDiagnosis {
title: "💥 GPU Komut Akışı (PM4 / Liverpool) Aşım Hatası".to_string(),
description: "GPU komut işlemcisine (Liverpool) iletilen PM4 paket uzunluğu tahsis edilen teslimat boyutunu aştı. Ara bellek kopyalaması (GOW3_COPY_GPU_BUFFERS) paket sınırlarında uyuşmazlığa yol açtı.".to_string(),
recommendation: "Ara GPU bellek kopyalaması güvenli şekilde kapatılmıştır. Oyunu tekrar başlatabilirsiniz.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "liverpool.cpp"),
});
}
// 3. Check for Texture Readback Glitch
if tail_str.contains("Readback failed") || tail_str.contains("stale_readback") || tail_str.contains("readbackLinearImages failed") {
return Some(CrashDiagnosis {
title: "⚠️ Doku (Texture) Okuma Hatası Algılandı".to_string(),
description: "GPU'dan sistem belleğine doku transferi sırasında gecikme veya veri bozulması yaşandı (Altın sarısı doku hatası belirtisi).".to_string(),
recommendation: "Launcher üzerinde 'Sarı Doku Hatası Önleme (Instant Readbacks)' seçeneğinin AÇIK olduğundan emin olun.".to_string(),
recommendation: "Launcher üzerinde 'Doku Bozulması ve Sararma Önleme (Instant Readbacks)' ve 'Asenkron Geri Okuma (Önerilen)' seçeneklerinin AÇIK olduğundan emin olun.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "readback"),
});
}
// NVIDIA Vulkan Driver Dynamic Rendering / VRAM Sınırı Çökmesi (libnvidia-glcore / EndRendering / 0x108)
if (tail_str.contains("libnvidia-glcore") || tail_str.contains("EndRendering"))
&& (tail_str.contains("0x108") || tail_str.contains("FlushBarriers") || tail_str.contains("DispatchRecord"))
{
return Some(CrashDiagnosis {
title: "💥 NVIDIA Vulkan Sürücü Çökmesi (Dynamic Rendering / VRAM Sınırı)".to_string(),
description: "NVIDIA Vulkan sürücüsü (libnvidia-glcore), sahne bariyerleri boşaltılırken (FlushBarriers -> EndRendering) VRAM yetersizliği veya komut senkronizasyonu hatası nedeniyle çöktü (Hata adresi: 0x108). Bu durum özellikle 8 GB ekran kartlarında doku temizleme bütçesi aşırı yüksek seçildiğinde veya çoklu iş parçacığı çakışmasında meydana gelir.".to_string(),
recommendation: "1. Launcher üzerinden 'Doku Havuzu (VRAM GC)' ayarını 5120 MB veya 4096 MB olarak seçin (8 GB kartlar için güvenli sınır).\n2. Launcher v2.0 güncellemesiyle anlık geri okuma senkronizasyonu ve otomatik taşma koruması devrededir. Oyunu tekrar başlatabilirsiniz.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "libnvidia-glcore"),
});
}
// General NVIDIA driver crash
if tail_str.contains("libnvidia-glcore") {
return Some(CrashDiagnosis {
title: "💥 NVIDIA Grafik Sürücüsü Hatası (libnvidia-glcore)".to_string(),
description: "NVIDIA Vulkan sürücüsü grafik komutlarını işlerken beklenmedik bir hata ile kapandı.".to_string(),
recommendation: "Ekran kartı sürücünüzün güncel olduğundan emin olun, VRAM GC bütçesini 4096-5120 MB seviyesine çekin ve arka plandaki diğer GPU uygulamalarını kapatın.".to_string(),
log_snippet: extract_relevant_snippet(&tail_str, "libnvidia-glcore"),
});
}
// 4. Check for Assertion Failure or SIGSEGV
if tail_str.contains("Assertion failed") || tail_str.contains("SIGSEGV") || tail_str.contains("panic") || (exit_code.is_some() && exit_code != Some(0)) {
let code_str = exit_code.map(|c| c.to_string()).unwrap_or_else(|| "Bilinmeyen".to_string());
@@ -84,3 +189,45 @@ fn extract_relevant_snippet(log: &str, keyword: &str) -> String {
snippet.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diagnose_nvidia_endrendering_crash() {
let temp_dir = std::env::temp_dir().join("gow3_crash_test_nvidia");
let _ = fs::create_dir_all(&temp_dir);
let log_content = r#"
Host fault (signal 11) in /usr/lib/libnvidia-glcore.so.615.78.08+0xd60b72 (?), address 0x108
#2 /usr/lib/libnvidia-glcore.so.615.78.08+0xd60b72 (?)
#3 /usr/lib/libnvidia-glcore.so.615.78.08+0xd56693 (?)
#4 .../gow3-linux/bin/lib/libgow3gpu.so+0x31ec70 (_ZN6Vulkan9Scheduler12EndRenderingEv)
#5 .../gow3-linux/bin/lib/libgow3gpu.so+0x2fb5d4 (_ZN6Vulkan7Runtime13FlushBarriersEv)
#6 .../gow3-linux/bin/lib/libgow3gpu.so+0x2d98e5 (_ZN6Vulkan10Rasterizer14DispatchRecordEPKNS_15ComputePipelineE)
#7 .../gow3-linux/bin/lib/libgow3gpu.so+0x2f02a0 (_ZN6Vulkan10Rasterizer13RunDrawPacketEPvPKhj)
"#;
fs::write(temp_dir.join("gow3_log.txt"), log_content).unwrap();
let diag = analyze_post_game(&temp_dir, Some(11)).expect("Should diagnose NVIDIA EndRendering crash");
assert!(diag.title.contains("NVIDIA Vulkan Sürücü Çökmesi"));
assert!(diag.recommendation.contains("5120 MB"));
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_diagnose_liverpool_pm4_packet_crash() {
let temp_dir = std::env::temp_dir().join("gow3_crash_test_pm4");
let _ = fs::create_dir_all(&temp_dir);
let log_content = r#"
GPU [Lib.GnmDriver] <Error> liverpool.cpp:66 NextPacket: : packet length exceeds remaining submission size. Packet dword count=18, remaining submission dwords=8
GPU [Debug] <Critical> liverpool.cpp:745 ProcessGraphics: Unreachable code!
Unimplemented PM4 type 0, base reg: 0, size: 1
STOP: GPU library assertion failed (see GPU log above)
"#;
fs::write(temp_dir.join("gow3_log.txt"), log_content).unwrap();
let diag = analyze_post_game(&temp_dir, Some(1)).expect("Should diagnose PM4 packet overrun crash");
assert!(diag.title.contains("GPU Komut Akışı (PM4 / Liverpool)"));
assert!(diag.recommendation.contains("kapatılmıştır"));
let _ = fs::remove_dir_all(&temp_dir);
}
}
File diff suppressed because it is too large. Load diff
+504 -35
View File
@@ -9,6 +9,7 @@ pub enum Language {
Spanish,
}
#[allow(dead_code)]
impl Language {
pub fn all() -> &'static [Language] {
&[Language::Turkish, Language::English, Language::German, Language::Spanish]
@@ -47,7 +48,7 @@ pub fn text(lang: Language, key: &str) -> &'static str {
// Turkish
(Language::Turkish, "hero_badge") => "🔥 Açık Kaynaklı PC Başlatıcı",
(Language::Turkish, "hero_desc") => "Windows, Linux ve Steam Deck üzerinde God of War III Remastered deneyimini kusursuzlaştırmak için tasarlandı.",
(Language::Turkish, "badge_v13") => "v1.3",
(Language::Turkish, "badge_v13") => "v1.4",
(Language::Turkish, "badge_windows") => "Windows",
(Language::Turkish, "badge_linux") => "Linux",
(Language::Turkish, "badge_steam_deck") => "Steam Deck",
@@ -55,15 +56,39 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "tag_hardware") => "D O N A N I M V E S İ S T E M",
(Language::Turkish, "tag_game_location") => "O Y U N K O N U M U",
(Language::Turkish, "tag_patches") => "P E R F O R M A N S V E Y A M A L A R",
(Language::Turkish, "tag_mods") => "O Y U N M O D L A R I",
(Language::Turkish, "tag_display") => "E K R A N V E G R A F İ K",
(Language::Turkish, "tag_controller") => "K O N T R O L C Ü V E G A M E P A D",
(Language::Turkish, "tag_keyboard") => "K L A V Y E A T A M A L A R I",
(Language::Turkish, "app_title") => "GOD OF WAR® III REMASTERED",
(Language::Turkish, "app_subtitle") => "Linux & Windows Port Launcher & Donanım Yöneticisi",
(Language::Turkish, "tab_game_perf") => "Oyun & Performans",
(Language::Turkish, "tab_game_perf") => "Oyun & Başlatma",
(Language::Turkish, "tab_game") => "Oyun & Başlatma",
(Language::Turkish, "tab_graphics") => "Grafik Ayarları",
(Language::Turkish, "tab_config_controls") => "Yapılandırma & Kontroller",
(Language::Turkish, "graphics_header") => "Grafik & Performans Ayarları",
(Language::Turkish, "graphics_subtitle") => "Düşük sistem optimizasyonu, çözünürlük ölçekleme ve görsel efekt ayarları",
(Language::Turkish, "graphic_preset") => "Hazır Grafik Profili:",
(Language::Turkish, "preset_ultra_low") => "🚀 Ultra Düşük Donanım (480p + FSR + Efekt Kısma)",
(Language::Turkish, "preset_low") => "⚡ Düşük Sistem / Steam Deck (720p + FSR + Efekt Kısma)",
(Language::Turkish, "preset_steam_deck") => "🎮 Steam Deck Özel (800p + FSR + 40/90Hz)",
(Language::Turkish, "preset_balanced") => "⚖️ Standart / Orijinal (1080p Native + Orijinal Efektler)",
(Language::Turkish, "preset_ultra") => "💎 Yüksek / 2K - 4K (1440p / 4K + 120 FPS)",
(Language::Turkish, "preset_custom") => "🛠️ Özel (Kullanıcı Ayarlı)",
(Language::Turkish, "preset_desc") => "Hazır profil seçerek tüm ayarları tek tıkla optimize edebilirsiniz. Herhangi bir ayarı değiştirdiğinizde profil otomatik olarak 'Özel' olur.",
(Language::Turkish, "render_resolution") => "Dahili Çözünürlük (Resolution):",
(Language::Turkish, "res_desc") => "Oyunun dahili çizim çözünürlüğü. Düşük sistemlerde 720p veya 480p seçmek FPS'i 2-3 kat artırır.",
(Language::Turkish, "fsr_toggle") => "AMD FSR 1.0 & RCAS Keskinleştirme",
(Language::Turkish, "fsr_desc") => "Düşük çözünürlükleri (720p/480p) pencere boyutuna net ve keskin şekilde ölçekler; bulanıklığı ve çamurlaşmayı önler.",
(Language::Turkish, "disable_motion_blur") => "Hareket Bulanıklığını Kapat (Disable Motion Blur)",
(Language::Turkish, "disable_motion_blur_desc") => "Kamera ve nesne hareket bulanıklığını kaldırarak ekrana kristal netlik kazandırır ve GPU yükünü hafifletir.",
(Language::Turkish, "disable_dof") => "Alan Derinliğini Kapat (Disable Depth of Field)",
(Language::Turkish, "disable_dof_desc") => "Arka planı bulanıklaştıran ağır post-pass filtresini devre dışı bırakır; arka planı netleştirir ve FPS artırır.",
(Language::Turkish, "vram_budget") => "VRAM Bellek Temizleme Bütçesi:",
(Language::Turkish, "vram_budget_desc") => "Ekran kartınızın VRAM kapasitesine göre ayarlayın. Düşük VRAM'li sistemlerde takılma ve bellek taşmalarını önler.",
(Language::Turkish, "sys_hardware") => "Sistem Donanımı",
(Language::Turkish, "locked_auto") => "Kilitli (Otomatik Algılandı)",
(Language::Turkish, "cpu") => "İşlemci (CPU)",
(Language::Turkish, "gpu") => "Ekran Kartı (GPU)",
(Language::Turkish, "vram") => "VRAM Kapasitesi",
(Language::Turkish, "vram_safe") => "Doku bozulmasına karşı güvenli",
@@ -80,19 +105,71 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "game_valid") => "Geçerli ve eksiksiz God of War III klasörü doğrulandı",
(Language::Turkish, "game_invalid") => "Oyun klasörü eksik, bozuk veya 'eboot.bin' bulunamadı!",
(Language::Turkish, "patches_header") => "Yamalar & Performans Ayarları",
(Language::Turkish, "hz_patch") => "Ekran Hızı (Hz) Senkronizasyon Yaması",
(Language::Turkish, "hz_desc") => "60 FPS orijinal oyun hızını korur. 120/144Hz ekranlarda akıcılık için ekranı 120Hz yapmak veya FreeSync/VRR açmak önerilir.",
(Language::Turkish, "hz_patch") => "Ekran Hızı (Hz) ve Dinamik FPS Senkronizasyonu",
(Language::Turkish, "hz_desc") => "Oyun motorunu seçilen FPS'e kilitlerken 60Hz fizik ve salınım simülasyonunu korur. Oyun asla hızlanmaz veya sapıtmaz; 60, 120, 144 vb. tüm modlarda pürüzsüz çalışır.",
(Language::Turkish, "target_fps") => "Hedef FPS:",
(Language::Turkish, "readback_patch") => "Sarı Doku Hatası Önleme (Instant Readbacks)",
(Language::Turkish, "readback_desc") => "Karlı ve parlak sahnelerde dokuların altın sarısına dönüşmesini önler.",
(Language::Turkish, "readback_patch") => "Doku Bozulması ve Sararma Önleme (Instant Readbacks)",
(Language::Turkish, "readback_desc") => "Gecikmeli geri okumaları kapatıp anlık doğrusal bellek okuması sağlayarak karlı ve parlak sahnelerdeki altın sarısı ve doku bozulmalarını tamamen çözer.",
(Language::Turkish, "async_readbacks") => "⚡ Asenkron Geri Okuma (Deneysel - Önerilen)",
(Language::Turkish, "async_readbacks_desc") => "CPU ile GPU senkronizasyonunu ayrıştırarak CPU'nun GPU render kuyruğunda beklemesini önler. Donma, kasma ve anlık FPS düşüşlerini ortadan kaldırır. Akıcı ve stabil bir oyun deneyimi için AÇIK tutulması önerilir.",
(Language::Turkish, "launch_while_compiling_shaders") => "Shader'lar Derlenirken Oyun Açılsın",
(Language::Turkish, "launch_while_compiling_shaders_desc") => "Açık: Oyun hemen açılır ve shader'lar arka planda derlenmeye devam eder. Kapalı: Shader'lar %100 derlenene kadar oyun başlamaz (Spartan Rage yükleme ekranı gösterilir, oyun içi takılmaları önler).",
(Language::Turkish, "show_shader_rage_meter") => "Shader Derleme Ekranı Görünümü (Spartan Rage Barı)",
(Language::Turkish, "show_shader_rage_meter_desc") => "Açık: Shader derleme sürecini Spartan Rage öfke barı ve boru hattı sayacı ile gösterir (Derleme %100 bitince oyun penceresi açılana kadar siyah ekranda bekler). Kapalı (PS4 Modu): Öfke barı ve sayaç gizlenir, oyun penceresi açılana kadar sadece animasyonlu PS4 açılış görseli (pic1.png) gösterilir.",
(Language::Turkish, "shader_cache_title") => "⚡ Resmi Shader Önbelleği (Steam Tarzı Pre-Caching)",
(Language::Turkish, "shader_cache_desc") => "Oyunun tüm haritaları, efektleri ve patron dövüşleri taranarak derlenmiş temiz ve güvenli 28.000+ shader önbelleğidir. Yeni bölgelere girerken veya büyüler kullanılırken oluşan tüm donma ve render takılmalarını sıfırlar.",
(Language::Turkish, "shader_download_btn") => "☁️ Tam Shader Paketini İndir / Yükle (17 MB)",
(Language::Turkish, "shader_download_btn_update") => "🔄 Shader Paketini Yeniden Doğrula / Yükle (17 MB)",
(Language::Turkish, "shader_installed_badge") => "✅ Tam Sürüm Shader Paketi Aktif ({} Shader Hazır)",
(Language::Turkish, "shader_missing_badge") => "⚠️ Önbellek Eksik veya Kısmi ({} Shader Bulundu - İndirme Önerilir)",
(Language::Turkish, "shader_downloading") => "Shader paketi işleniyor (~17 MB)... Lütfen bekleyin...",
(Language::Turkish, "shader_extracting") => "Önbellek arşivi açılıyor ve kontrol ediliyor...",
(Language::Turkish, "shader_success") => "✅ Shader paketi başarıyla kuruldu! ({} shader önbelleğe alındı)",
(Language::Turkish, "skip_cinematics") => "Sinematikleri 'X' Tuşuyla Atlama Yaması",
(Language::Turkish, "skip_cinematics_desc") => "Ara sahneleri kumandanızın X tuşuna basarak anında geçmenizi sağlar.",
(Language::Turkish, "nav_home") => "ANASAYFA",
(Language::Turkish, "nav_mods") => "MODLAR",
(Language::Turkish, "mods_title") => "God of War III Mod Yöneticisi",
(Language::Turkish, "mods_desc") => "Yüklenen karakter, kostüm ve doku modlarını buradan kolayca yönetebilirsiniz. Orijinal oyun dosyalarınıza dokunulmadan dinamik olarak oyuna aktarılır.",
(Language::Turkish, "mods_open_folder") => "Modlar Klasörünü Aç",
(Language::Turkish, "mods_refresh") => "Modları Yenile",
(Language::Turkish, "mods_count_summary") => "Yüklü Mod: {} | Aktif: {} | Devre Dışı: {}",
(Language::Turkish, "mods_empty_title") => "Henüz Yüklü Mod Bulunamadı",
(Language::Turkish, "mods_empty_desc") => "'Modlar Klasörünü Aç' butonuna basarak indirdiğiniz mod klasörünü buraya bırakın ve ardından 'Modları Yenile'ye tıklayın.",
(Language::Turkish, "mods_status_active") => "● Oyunda Devrede",
(Language::Turkish, "mods_status_disabled") => "○ Devre Dışı",
(Language::Turkish, "mods_author") => "Yazar",
(Language::Turkish, "mods_files_replaced") => "Değiştirilen Dosya",
(Language::Turkish, "nav_settings") => "AYARLAR",
(Language::Turkish, "nav_graphics") => "GRAFİKLER",
(Language::Turkish, "nav_game_location") => "OYUN KONUMU",
(Language::Turkish, "nav_controls") => "KONTROLLER",
(Language::Turkish, "nav_shaders") => "SHADER AYARI",
(Language::Turkish, "launcher_version_label") => "LAUNCHER SÜRÜMÜ",
(Language::Turkish, "lang_options_title") => "DİL SEÇENEKLERİ",
(Language::Turkish, "system_specs_title") => "SİSTEM ÖZELLİKLERİ",
(Language::Turkish, "play_btn_label") => "OYNA",
(Language::Turkish, "menu_play") => "OYNA",
(Language::Turkish, "menu_trophies") => "KUPALAR",
(Language::Turkish, "menu_latest_news") => "GÜNCELLEME NOTLARI",
(Language::Turkish, "menu_options") => "AYARLAR",
(Language::Turkish, "menu_back") => "GERİ DÖN",
(Language::Turkish, "bonus_play_fix_toggle") => "Bonus Play QTE & Düşman Doğma Düzeltmesi",
(Language::Turkish, "bonus_play_fix_desc") => "Tanrısal Eşyalar (Godly Possessions / Hephaistos'un Yüzüğü) aktifken QTE sekanslarının erken tetiklenip düşman dalgalarını (arena kilitlenmelerini) ve tuş göstergelerini engellemesini önler.",
(Language::Turkish, "release_notes_title") => "God of War III Launcher - Sürüm Notları (v2.2)",
(Language::Turkish, "release_notes_content") => "🔥 GOD OF WAR III LAUNCHER v2.2 YENİLİKLERİ:\n\n• 🏆 Gerçek Zamanlı Kupa (Trophy) ve PS4 Bildirim Sistemi:\n - Resmi 36 kupanın tamamı, yüksek çözünürlüklü ikonları ve Türkçe/İngilizce açıklamalarıyla entegre edildi.\n - PS4 Stili Sağ Üst Bildirim Hapı (Toast Notification): Kupa kazanıldığında sağdan kayarak gelir, 4 saniye ekranda kalır ve kararır.\n - Orijinal PS4 Kupa Kazanma Sesi (Trophy Pop SFX) milisaniyesinde çalar.\n - Sıralı Kuyruk Yönetimi (FIFO): Üst üste binen kupaları sıralı gösterir.\n - Kupa Merkezi (Dashboard): İlerleme çubuğu, %100 Platin Ustalık Mührü, filtreleme, kupa arama ve gizli kupa açma desteği.\n\n• 🗡️ Bonus Play & Tanrısal Eşyalar (Godly Possessions) Düzeltmesi:\n - Hephaistos'un Yüzüğü ve diğer bonus eşyalar aktifken QTE'lerin erken tetiklenip düşman dalgalarını engellemesi ve arena kilitlenmeleri düzeltildi.\n\n• 🎮 Kararlılık ve Performans İyileştirmeleri.",
(Language::Turkish, "discord_rpc_toggle") => "Discord Rich Presence (Oynuyor Durumu)",
(Language::Turkish, "discord_rpc_desc") => "Discord profilinizde 'God of War III: Remastered', bulunduğunuz bölüm, boss savaşları ve oynama sürenizi canlı gösterir.",
(Language::Turkish, "discord_client_id_label") => "Discord Uygulama Kimliği (Client ID):",
(Language::Turkish, "discord_reset_btn") => "Varsayılana Sıfırla",
(Language::Turkish, "gamemode_toggle") => "Feral GameMode (gamemoderun)",
(Language::Turkish, "gamemode_desc") => "CPU zamanlayıcısını ve GPU performansını oyun için en yüksek seviyeye ayarlar.",
(Language::Turkish, "save_apply") => "Ayarları Kaydet ve Uygula",
(Language::Turkish, "view_log") => "Log Dosyasını Görüntüle",
(Language::Turkish, "play_ready") => "OYUNU BAŞLAT",
(Language::Turkish, "play_running") => "OYUN ÇALIŞIYOR...",
(Language::Turkish, "loading_shaders") => "LOADING SHADERS...",
(Language::Turkish, "loading_shaders_sub") => "Vulkan boru hatları derleniyor...",
(Language::Turkish, "force_quit_btn") => "Zorla Kapat",
(Language::Turkish, "force_quit_tooltip") => "Oyun kilitlendiğinde veya kapanmadığında süreci anında zorla sonlandırır.",
(Language::Turkish, "force_quit_status") => "Oyun süreci zorla sonlandırıldı.",
@@ -129,6 +206,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "latest_commit") => "Son Commit:",
(Language::Turkish, "commit_date") => "Tarih:",
(Language::Turkish, "view_on_github") => "GitHub'da Görüntüle",
(Language::Turkish, "remind_later") => "⏰ Sonra Hatırlat",
(Language::Turkish, "update_available_badge") => "🚀 Güncelleme Mevcut!",
(Language::Turkish, "btn_update") => "Güncelle",
(Language::Turkish, "btn_update_now") => "🚀 Şimdi Güncelle",
(Language::Turkish, "updating_in_progress") => "⏳ Güncelleme indiriliyor ve kuruluyor... Lütfen bekleyin...",
(Language::Turkish, "python_dep") => "Python 3 Bağımlılığı:",
(Language::Turkish, "python_ready") => "Yüklü / Hazır",
(Language::Turkish, "python_missing") => "Bulunamadı",
@@ -154,6 +236,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "controller_header") => "Kumanda / Gamepad Ayarları",
(Language::Turkish, "bg_input") => "Arka Planda Kumanda Girişi (Background Input)",
(Language::Turkish, "bg_input_desc") => "Oyun penceresi odakta değilken bile kumanda girdilerini algılamaya devam eder.",
(Language::Turkish, "hide_mouse_cursor") => "Fare İşaretçisini Oyunda Gizle",
(Language::Turkish, "hide_mouse_cursor_desc") => "Oyun odaktayken fare imlecini gizler; oyundan çıkıldığında veya başka pencereye geçildiğinde görünür yapar.",
(Language::Turkish, "motion_controls") => "Hareket / Jiroskop Sensörleri (Gyro / Motion)",
(Language::Turkish, "motion_controls_desc") => "DualShock 4 / DualSense dahili hareket algılama sensörlerini etkinleştirir.",
(Language::Turkish, "deadzone_left") => "Sol Analog Ölü Bölge (Left Stick Deadzone):",
@@ -161,9 +245,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "deadzone_desc") => "Düşük değer daha hassas kontrol sağlar; analog çubuğunuzda kendi kendine kayma (drift) varsa değeri artırın.",
(Language::Turkish, "keyboard_header") => "Klavye Tuş Atamaları (Keyboard Bindings)",
(Language::Turkish, "keyboard_desc") => "Değiştirmek istediğiniz tuşa tıklayın ve yeni tuşa basın. İptal etmek için ESC tuşuna basın.",
(Language::Turkish, "preset_label") => "Hızlı Tuş Düzeni (Preset):",
(Language::Turkish, "preset_standard") => "Standart WASD + CVBN (Önerilen)",
(Language::Turkish, "preset_numpad") => "WASD + Numpad (8462)",
(Language::Turkish, "preset_label") => "Hazır Tuş Profilleri:",
(Language::Turkish, "preset_recommended") => "🎯 Önerilen Preset",
(Language::Turkish, "preset_mouse_kb") => "🖱️ İkinci Preset (Fare + Klavye)",
(Language::Turkish, "preset_standard") => "🎯 Önerilen Preset",
(Language::Turkish, "preset_numpad") => "🖱️ İkinci Preset (Fare + Klavye)",
(Language::Turkish, "reset_defaults") => "Varsayılanlara Sıfırla",
(Language::Turkish, "press_any_key") => "[ Bir tuşa basın... / ESC ]",
(Language::Turkish, "sec_face_buttons") => "Eylem Tuşları (Saldırı & Zıplama)",
@@ -194,11 +280,44 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Turkish, "act_cam_down") => "Sağ Analog: Aşağı",
(Language::Turkish, "act_cam_left") => "Sağ Analog: Sol",
(Language::Turkish, "act_cam_right") => "Sağ Analog: Sağ",
(Language::Turkish, "key_bind_modal_title") => "🎮 Tuş / Fare Butonu Atama",
(Language::Turkish, "key_bind_modal_desc") => "Klavyeden bir tuşa basın, fareye tıklayın veya aşağıdaki seçeneklerden birini seçin:",
(Language::Turkish, "sec_mouse_bindings") => "🖱 Fare Butonları (Mouse)",
(Language::Turkish, "sec_numpad_bindings") => "🔢 Numpad Tuşları (Sayı Takımı)",
(Language::Turkish, "sec_digits_bindings") => "🔢 Standart Sayı Satırı (1 - 0)",
(Language::Turkish, "mouse_left_btn") => "🖱 Sol Tık (Mouse 1)",
(Language::Turkish, "mouse_right_btn") => "🖱 Sağ Tık (Mouse 2)",
(Language::Turkish, "mouse_middle_btn") => "🖱 Orta Tık (Mouse 3)",
(Language::Turkish, "mouse_side1_btn") => "🖱 Yan Tuş 1 (Geri / M4)",
(Language::Turkish, "mouse_side2_btn") => "🖱 Yan Tuş 2 (İleri / M5)",
(Language::Turkish, "mouse_wheel_up") => "🖱 Tekerlek Yukarı",
(Language::Turkish, "mouse_wheel_down") => "🖱 Tekerlek Aşağı",
(Language::Turkish, "cancel_btn") => "İptal (ESC)",
(Language::Turkish, "launcher_log_btn") => "📋 Launcher Logu",
(Language::Turkish, "game_log_btn") => "🎮 Oyun Logu",
(Language::Turkish, "err_modal_title") => "⚠️ Bir Sorun Oluştu",
(Language::Turkish, "copy_error") => "📋 Hatayı Kopyala",
(Language::Turkish, "copied_notice") => "✅ Kopyalandı!",
(Language::Turkish, "open_log_file") => "📄 Log Dosyasını Aç",
(Language::Turkish, "open_in_editor") => "📝 Dış Düzenleyicide Aç",
(Language::Turkish, "refresh_log") => "🔄 Yenile",
(Language::Turkish, "err_engine_dl_title") => "❌ Oyun Motoru İndirilemedi",
(Language::Turkish, "err_engine_dl_summary") => "God of War III oyun motoru paketi indirilemedi veya arşivden çıkarılamadı.",
(Language::Turkish, "err_engine_dl_hint") => "Lütfen internet bağlantınızı kontrol edin. GitHub indirmesi başarısız olmuş veya disk alanı yetersiz olabilir.",
(Language::Turkish, "engine_download_alt") => "Alternatif İndir",
(Language::Turkish, "engine_download_alt_tooltip") => "Normal indirmede sorun yaşıyorsanız alternatif sunucuyu ve farklı indirme yöntemini kullanır.",
(Language::Turkish, "retry_with_alt") => "⚡ Alternatif Motor İndirmeyi Dene",
(Language::Turkish, "err_python_dl_title") => "❌ Python Ortamı İndirilemedi",
(Language::Turkish, "err_python_dl_summary") => "Oyun hazırlık betikleri için gerekli taşınabilir Python 3 çalışma ortamı indirilemedi.",
(Language::Turkish, "err_python_dl_hint") => "Sisteminizde yerel Python 3 kurulu olduğundan emin olun veya bağlantınızı kontrol edip tekrar deneyin.",
(Language::Turkish, "err_game_launch_title") => "❌ Oyun Başlatma Hatası",
(Language::Turkish, "err_game_launch_summary") => "God of War III başlatılırken bir hazırlık veya yürütme hatası oluştu.",
(Language::Turkish, "err_game_launch_hint") => "Oyun klasörünüzde eboot.bin ve dosyaların tam olduğunu doğrulayın. Aşağıdaki detaylardan veya Log dosyasından hatayı inceleyebilirsiniz.",
// English
(Language::English, "hero_badge") => "🔥 Open Source PC Launcher",
(Language::English, "hero_desc") => "Designed to perfect the God of War III Remastered experience across Windows, Linux, and Steam Deck.",
(Language::English, "badge_v13") => "v1.3",
(Language::English, "badge_v13") => "v1.4",
(Language::English, "badge_windows") => "Windows",
(Language::English, "badge_linux") => "Linux",
(Language::English, "badge_steam_deck") => "Steam Deck",
@@ -206,15 +325,39 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "tag_hardware") => "H A R D W A R E & S Y S T E M",
(Language::English, "tag_game_location") => "G A M E L O C A T I O N",
(Language::English, "tag_patches") => "P E R F O R M A N C E & P A T C H E S",
(Language::English, "tag_mods") => "G A M E M O D S",
(Language::English, "tag_display") => "D I S P L A Y & G R A P H I C S",
(Language::English, "tag_controller") => "C O N T R O L L E R & G A M E P A D",
(Language::English, "tag_keyboard") => "K E Y B O A R D B I N D I N G S",
(Language::English, "app_title") => "GOD OF WAR® III REMASTERED",
(Language::English, "app_subtitle") => "Linux & Windows Port Launcher & Hardware Manager",
(Language::English, "tab_game_perf") => "Game & Performance",
(Language::English, "tab_game_perf") => "Game & Launch",
(Language::English, "tab_game") => "Game & Launch",
(Language::English, "tab_graphics") => "Graphics Settings",
(Language::English, "tab_config_controls") => "Settings & Controls",
(Language::English, "graphics_header") => "Graphics & Performance Settings",
(Language::English, "graphics_subtitle") => "Low-end system optimizations, resolution scaling, and visual effect controls",
(Language::English, "graphic_preset") => "Graphics Preset:",
(Language::English, "preset_ultra_low") => "🚀 Ultra Low-End (480p + FSR + Reduced Effects)",
(Language::English, "preset_low") => "⚡ Low-End / Steam Deck (720p + FSR + Reduced Effects)",
(Language::English, "preset_steam_deck") => "🎮 Steam Deck Native (800p + FSR + 40/90Hz)",
(Language::English, "preset_balanced") => "⚖️ Standard / Native (1080p Native + Default Effects)",
(Language::English, "preset_ultra") => "💎 High Quality (1440p / 4K + 120 FPS)",
(Language::English, "preset_custom") => "🛠️ Custom (User Defined)",
(Language::English, "preset_desc") => "Select a preset to optimize all settings with one click. Any manual change will automatically switch to Custom.",
(Language::English, "render_resolution") => "Internal Resolution:",
(Language::English, "res_desc") => "Internal rendering resolution. Selecting 720p or 480p massively increases FPS on low-end systems.",
(Language::English, "fsr_toggle") => "AMD FSR 1.0 & RCAS Sharpening",
(Language::English, "fsr_desc") => "Sharpens and scales lower resolutions (720p/480p) to your display without blur.",
(Language::English, "disable_motion_blur") => "Disable Motion Blur",
(Language::English, "disable_motion_blur_desc") => "Removes camera and object motion smearing, improving visual clarity and reducing GPU load.",
(Language::English, "disable_dof") => "Disable Depth of Field (DoF)",
(Language::English, "disable_dof_desc") => "Disables heavy background blur pass, keeping background sharp and boosting FPS.",
(Language::English, "vram_budget") => "VRAM Garbage Collection Budget:",
(Language::English, "vram_budget_desc") => "Adjust according to your GPU VRAM capacity. Prevents memory overflow and stutter on budget cards.",
(Language::English, "sys_hardware") => "System Hardware",
(Language::English, "locked_auto") => "Locked (Auto Detected)",
(Language::English, "cpu") => "Processor (CPU)",
(Language::English, "gpu") => "Graphics Card (GPU)",
(Language::English, "vram") => "VRAM Capacity",
(Language::English, "vram_safe") => "Safe against texture corruption",
@@ -231,19 +374,71 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "game_valid") => "Valid and complete God of War III directory verified",
(Language::English, "game_invalid") => "Missing game folder or 'eboot.bin' not found!",
(Language::English, "patches_header") => "Patches & Optimizations",
(Language::English, "hz_patch") => "Display Rate (Hz) Sync Patch",
(Language::English, "hz_desc") => "Preserves original 60 FPS game speed. On 120/144Hz displays, setting 120Hz or enabling VRR is recommended.",
(Language::English, "hz_patch") => "Display Rate (Hz) & Dynamic FPS Synchronization",
(Language::English, "hz_desc") => "Locks the game engine to target FPS while preserving the 60Hz physics and swing energy simulation. Eliminates high-speed glitches across 60, 120, 144+ FPS.",
(Language::English, "target_fps") => "Target FPS:",
(Language::English, "readback_patch") => "Yellow Texture Corruption Fix (Instant Readbacks)",
(Language::English, "readback_desc") => "Prevents textures turning gold/yellow in snowy and bright scenes.",
(Language::English, "readback_patch") => "Texture Corruption & Yellow Tint Fix (Instant Readbacks)",
(Language::English, "readback_desc") => "Forces immediate linear readbacks and disables stale buffers, completely eliminating yellow tint and corrupted textures in bright or snow scenes.",
(Language::English, "async_readbacks") => "⚡ Asynchronous Readbacks (Experimental - Recommended)",
(Language::English, "async_readbacks_desc") => "Decouples CPU from GPU render fences, preventing the CPU from stalling on GPU draw calls. Eliminates FPS drops, micro-stutters, and pacing hitches. Recommended ON.",
(Language::English, "launch_while_compiling_shaders") => "Launch Game While Compiling Shaders",
(Language::English, "launch_while_compiling_shaders_desc") => "On: Game opens immediately while shaders compile in the background. Off: Game does not launch until shaders are 100% compiled (shows Spartan Rage bar, eliminates in-game stutter).",
(Language::English, "show_shader_rage_meter") => "Shader Compilation Display (Spartan Rage Bar)",
(Language::English, "show_shader_rage_meter_desc") => "On: Displays shader compilation progress with the Spartan Rage meter and pipeline counter (Holds black screen after 100% until game window is ready). Off (PS4 Mode): Hides the rage bar and counter, displaying only the animated PS4 splash artwork (pic1.png) until the game window is ready.",
(Language::English, "shader_cache_title") => "⚡ Official Shader Cache (Steam-style Pre-Caching)",
(Language::English, "shader_cache_desc") => "Official clean and safe pre-compiled shader cache covering all maps, cutscenes, effects, and boss fights. Completely eliminates in-game stuttering and render pauses.",
(Language::English, "shader_download_btn") => "☁️ Download & Install Full Shader Cache (17 MB)",
(Language::English, "shader_download_btn_update") => "🔄 Re-verify & Install Shader Cache (17 MB)",
(Language::English, "shader_installed_badge") => "✅ Full Shader Cache Active ({} Shaders Ready)",
(Language::English, "shader_missing_badge") => "⚠️ Cache Incomplete ({} Shaders Found - Download Recommended)",
(Language::English, "shader_downloading") => "Processing shader cache (~17 MB)... Please wait...",
(Language::English, "shader_extracting") => "Extracting and verifying shader cache...",
(Language::English, "shader_success") => "✅ Shader cache installed successfully! ({} shaders cached)",
(Language::English, "skip_cinematics") => "Skip Cutscenes with 'X' Button",
(Language::English, "skip_cinematics_desc") => "Allows skipping any cutscene immediately by pressing the X button.",
(Language::English, "nav_home") => "HOME",
(Language::English, "nav_mods") => "MODS",
(Language::English, "mods_title") => "God of War III Mod Manager",
(Language::English, "mods_desc") => "Easily manage character, costume, and texture mods. Safely loaded into the game via non-destructive virtual overlay.",
(Language::English, "mods_open_folder") => "Open Mods Folder",
(Language::English, "mods_refresh") => "Refresh Mods",
(Language::English, "mods_count_summary") => "Installed: {} | Active: {} | Disabled: {}",
(Language::English, "mods_empty_title") => "No Mods Installed Yet",
(Language::English, "mods_empty_desc") => "Click 'Open Mods Folder', drop your mod directory inside, and click 'Refresh Mods'.",
(Language::English, "mods_status_active") => "● Active In-Game",
(Language::English, "mods_status_disabled") => "○ Disabled",
(Language::English, "mods_author") => "Author",
(Language::English, "mods_files_replaced") => "Replaced Files",
(Language::English, "nav_settings") => "SETTINGS",
(Language::English, "nav_graphics") => "GRAPHICS",
(Language::English, "nav_game_location") => "GAME LOCATION",
(Language::English, "nav_controls") => "CONTROLS",
(Language::English, "nav_shaders") => "SHADER SETTINGS",
(Language::English, "launcher_version_label") => "LAUNCHER VERSION",
(Language::English, "lang_options_title") => "LANGUAGE OPTIONS",
(Language::English, "system_specs_title") => "SYSTEM SPECIFICATIONS",
(Language::English, "play_btn_label") => "PLAY",
(Language::English, "menu_play") => "PLAY",
(Language::English, "menu_trophies") => "TROPHIES",
(Language::English, "menu_latest_news") => "LATEST NEWS",
(Language::English, "menu_options") => "OPTIONS",
(Language::English, "menu_back") => "BACK",
(Language::English, "bonus_play_fix_toggle") => "Bonus Play QTE & Enemy Spawning Fix",
(Language::English, "bonus_play_fix_desc") => "Prevents Godly Possession bonus items (specifically Hephaestus' Ring) from auto-skipping QTEs prematurely, preventing missing enemy wave spawns and missing QTE prompts.",
(Language::English, "release_notes_title") => "God of War III Launcher - Release Notes (v2.2)",
(Language::English, "release_notes_content") => "🔥 GOD OF WAR III LAUNCHER v2.2 CHANGELOG:\n\n• 🏆 Real-Time Trophy System & PS4 Notification Integration:\n - All 36 trophies tracked with official badges, unlock conditions, and local persistence (trophies_state.json).\n - PS4-Style Pill Toast Notification in the top-right corner with smooth slide-in, 4s hold, and soft fade-out.\n - Authentic PS4 Trophy Pop SFX played at the millisecond of unlocking.\n - Sequential Notification Queue (FIFO) for multiple unlocks.\n - Full Trophy Showcase & Dashboard: Progress bar, dynamic percentage, 100% Platinum Mastery Badge, search & filter.\n\n• 🗡️ Bonus Play & Godly Possessions Fix:\n - Fixed missing enemies and QTE softlocks when equipping Hephaestus' Ring and bonus items.\n\n• ⚡ Stability & performance enhancements.",
(Language::English, "discord_rpc_toggle") => "Discord Rich Presence (Activity Status)",
(Language::English, "discord_rpc_desc") => "Displays 'God of War III: Remastered', your active chapter, boss fights, and elapsed play time on Discord.",
(Language::English, "discord_client_id_label") => "Discord Application ID (Client ID):",
(Language::English, "discord_reset_btn") => "Reset Default",
(Language::English, "gamemode_toggle") => "Feral GameMode (gamemoderun)",
(Language::English, "gamemode_desc") => "Configures CPU governor and GPU clocks for optimal gaming performance.",
(Language::English, "save_apply") => "Save & Apply Settings",
(Language::English, "view_log") => "View Log File",
(Language::English, "play_ready") => "LAUNCH GAME",
(Language::English, "play_running") => "GAME IS RUNNING...",
(Language::English, "loading_shaders") => "LOADING SHADERS...",
(Language::English, "loading_shaders_sub") => "Compiling Vulkan pipelines...",
(Language::English, "force_quit_btn") => "Force Quit",
(Language::English, "force_quit_tooltip") => "Immediately kills the game process if it hangs or freezes.",
(Language::English, "force_quit_status") => "Game process was forcefully terminated.",
@@ -280,6 +475,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "latest_commit") => "Latest Commit:",
(Language::English, "commit_date") => "Date:",
(Language::English, "view_on_github") => "View on GitHub",
(Language::English, "remind_later") => "⏰ Remind Me Later",
(Language::English, "update_available_badge") => "🚀 Update Available!",
(Language::English, "btn_update") => "Update",
(Language::English, "btn_update_now") => "🚀 Update Now",
(Language::English, "updating_in_progress") => "⏳ Downloading and installing update... Please wait...",
(Language::English, "python_dep") => "Python 3 Dependency:",
(Language::English, "python_ready") => "Installed / Ready",
(Language::English, "python_missing") => "Missing",
@@ -305,6 +505,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "controller_header") => "Gamepad & Controller Settings",
(Language::English, "bg_input") => "Background Controller Input",
(Language::English, "bg_input_desc") => "Allows gamepad inputs even when the game window is not actively focused.",
(Language::English, "hide_mouse_cursor") => "Hide Mouse Cursor in Game",
(Language::English, "hide_mouse_cursor_desc") => "Hides mouse cursor when game is focused; automatically restores cursor when focus is lost.",
(Language::English, "motion_controls") => "Motion & Gyroscope Controls",
(Language::English, "motion_controls_desc") => "Enables DualShock 4 / DualSense internal accelerometer and gyroscope features.",
(Language::English, "deadzone_left") => "Left Stick Deadzone:",
@@ -312,9 +514,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "deadzone_desc") => "Lower values provide more responsiveness; increase if your stick suffers from drift.",
(Language::English, "keyboard_header") => "Keyboard Bindings",
(Language::English, "keyboard_desc") => "Click any button to assign a new key. Press ESC to cancel.",
(Language::English, "preset_label") => "Quick Preset:",
(Language::English, "preset_standard") => "Standard WASD + CVBN (Recommended)",
(Language::English, "preset_numpad") => "WASD + Numpad (8462)",
(Language::English, "preset_label") => "Quick Presets:",
(Language::English, "preset_recommended") => "🎯 Recommended Preset",
(Language::English, "preset_mouse_kb") => "🖱️ Second Preset (Mouse + Keyboard)",
(Language::English, "preset_standard") => "🎯 Recommended Preset",
(Language::English, "preset_numpad") => "🖱️ Second Preset (Mouse + Keyboard)",
(Language::English, "reset_defaults") => "Reset to Defaults",
(Language::English, "press_any_key") => "[ Press key... / ESC ]",
(Language::English, "sec_face_buttons") => "Action Buttons (Attacks & Jump)",
@@ -345,11 +549,44 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::English, "act_cam_down") => "Right Stick: Down",
(Language::English, "act_cam_left") => "Right Stick: Left",
(Language::English, "act_cam_right") => "Right Stick: Right",
(Language::English, "key_bind_modal_title") => "🎮 Assign Key / Mouse Button",
(Language::English, "key_bind_modal_desc") => "Press a key, click mouse, or select from the quick options below:",
(Language::English, "sec_mouse_bindings") => "🖱 Mouse Buttons",
(Language::English, "sec_numpad_bindings") => "🔢 Numpad Keys",
(Language::English, "sec_digits_bindings") => "🔢 Standard Number Row (1 - 0)",
(Language::English, "mouse_left_btn") => "🖱 Left Click (Mouse 1)",
(Language::English, "mouse_right_btn") => "🖱 Right Click (Mouse 2)",
(Language::English, "mouse_middle_btn") => "🖱 Middle Click (Mouse 3)",
(Language::English, "mouse_side1_btn") => "🖱 Side Button 1 (Back / M4)",
(Language::English, "mouse_side2_btn") => "🖱 Side Button 2 (Forward / M5)",
(Language::English, "mouse_wheel_up") => "🖱 Wheel Up",
(Language::English, "mouse_wheel_down") => "🖱 Wheel Down",
(Language::English, "cancel_btn") => "Cancel (ESC)",
(Language::English, "launcher_log_btn") => "📋 Launcher Log",
(Language::English, "game_log_btn") => "🎮 Game Log",
(Language::English, "err_modal_title") => "⚠️ An Error Occurred",
(Language::English, "copy_error") => "📋 Copy Error",
(Language::English, "copied_notice") => "✅ Copied!",
(Language::English, "open_log_file") => "📄 Open Log File",
(Language::English, "open_in_editor") => "📝 Open in External Editor",
(Language::English, "refresh_log") => "🔄 Refresh",
(Language::English, "err_engine_dl_title") => "❌ Game Engine Download Failed",
(Language::English, "err_engine_dl_summary") => "Failed to download or extract the God of War III game engine package.",
(Language::English, "err_engine_dl_hint") => "Please check your internet connection. GitHub download might have failed or disk space could be low.",
(Language::English, "engine_download_alt") => "Alternative Download",
(Language::English, "engine_download_alt_tooltip") => "Uses an alternative mirror and download method if the standard download fails.",
(Language::English, "retry_with_alt") => "⚡ Try Alternative Engine Download",
(Language::English, "err_python_dl_title") => "❌ Python Runtime Download Failed",
(Language::English, "err_python_dl_summary") => "Failed to download the portable Python 3 runtime required for game preparation scripts.",
(Language::English, "err_python_dl_hint") => "Ensure Python 3 is installed on your system or check your connection and retry.",
(Language::English, "err_game_launch_title") => "❌ Game Launch Error",
(Language::English, "err_game_launch_summary") => "An error occurred during God of War III preparation or execution.",
(Language::English, "err_game_launch_hint") => "Verify that eboot.bin and game files are intact. Check the details below or the Log file.",
// German
(Language::German, "hero_badge") => "🔥 Open-Source-PC-Launcher",
(Language::German, "hero_desc") => "Entwickelt zur Perfektionierung von God of War III Remastered auf Windows, Linux und Steam Deck.",
(Language::German, "badge_v13") => "v1.3",
(Language::German, "badge_v13") => "v1.4",
(Language::German, "badge_windows") => "Windows",
(Language::German, "badge_linux") => "Linux",
(Language::German, "badge_steam_deck") => "Steam Deck",
@@ -357,15 +594,39 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "tag_hardware") => "H A R D W A R E & S Y S T E M",
(Language::German, "tag_game_location") => "S P I E L P F A D",
(Language::German, "tag_patches") => "L E I S T U N G & P A T C H E S",
(Language::German, "tag_mods") => "S P I E L - M O D S",
(Language::German, "tag_display") => "A N Z E I G E & G R A F I K",
(Language::German, "tag_controller") => "C O N T R O L L E R & G A M E P A D",
(Language::German, "tag_keyboard") => "T A S T A T U R B E L E G U N G",
(Language::German, "app_title") => "GOD OF WAR® III REMASTERED",
(Language::German, "app_subtitle") => "Linux & Windows Port Launcher & Hardware-Manager",
(Language::German, "tab_game_perf") => "Spiel & Leistung",
(Language::German, "tab_game_perf") => "Spiel & Start",
(Language::German, "tab_game") => "Spiel & Start",
(Language::German, "tab_graphics") => "Grafikeinstellungen",
(Language::German, "tab_config_controls") => "Konfiguration & Steuerung",
(Language::German, "graphics_header") => "Grafik- & Leistungseinstellungen",
(Language::German, "graphics_subtitle") => "Optimierungen für Low-End-Systeme, Auflösungsskalierung und visuelle Effekte",
(Language::German, "graphic_preset") => "Grafikprofil:",
(Language::German, "preset_ultra_low") => "🚀 Ultra Low-End (480p + FSR + Reduzierte Effekte)",
(Language::German, "preset_low") => "⚡ Low-End / Steam Deck (720p + FSR + Reduzierte Effekte)",
(Language::German, "preset_steam_deck") => "🎮 Steam Deck (800p + FSR + 40/90Hz)",
(Language::German, "preset_balanced") => "⚖️ Standard (1080p Nativ + Standard-Effekte)",
(Language::German, "preset_ultra") => "💎 Hohe Qualität (1440p / 4K + 120 FPS)",
(Language::German, "preset_custom") => "🛠️ Benutzerdefiniert",
(Language::German, "preset_desc") => "Wählen Sie ein Profil, um alle Einstellungen mit einem Klick zu optimieren.",
(Language::German, "render_resolution") => "Interne Auflösung:",
(Language::German, "res_desc") => "Interne Render-Auflösung. 720p oder 480p erhöht die FPS auf schwächeren Systemen deutlich.",
(Language::German, "fsr_toggle") => "AMD FSR 1.0 & RCAS Schärfung",
(Language::German, "fsr_desc") => "Skaliert und schärft niedrigere Auflösungen (720p/480p) sauber auf den Bildschirm.",
(Language::German, "disable_motion_blur") => "Bewegungsunschärfe deaktivieren",
(Language::German, "disable_motion_blur_desc") => "Entfernt Unschärfe bei Kamerabewegungen, verbessert die Bildklarheit und senkt die GPU-Last.",
(Language::German, "disable_dof") => "Tiefenschärfe (DoF) deaktivieren",
(Language::German, "disable_dof_desc") => "Deaktiviert die Hintergrundunschärfe, sorgt für ein schärferes Bild und mehr FPS.",
(Language::German, "vram_budget") => "VRAM-Bereinigungsbudget:",
(Language::German, "vram_budget_desc") => "Passen Sie das Budget an Ihre GPU an, um Ruckler bei wenig VRAM zu vermeiden.",
(Language::German, "sys_hardware") => "System-Hardware",
(Language::German, "locked_auto") => "Gesperrt (Automatisch erkannt)",
(Language::German, "cpu") => "Prozessor (CPU)",
(Language::German, "gpu") => "Grafikkarte (GPU)",
(Language::German, "vram") => "VRAM-Kapazität",
(Language::German, "vram_safe") => "Sicher gegen Texturfehler",
@@ -382,13 +643,63 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "game_valid") => "Gültiges God of War III Verzeichnis bestätigt",
(Language::German, "game_invalid") => "Spielordner unvollständig oder 'eboot.bin' fehlt!",
(Language::German, "patches_header") => "Patches & Optimierungen",
(Language::German, "hz_patch") => "Bildwiederholungsrate-Sync-Patch",
(Language::German, "hz_desc") => "Verhindert 2x Geschwindigkeitsfehler auf 144Hz+ Monitoren.",
(Language::German, "hz_patch") => "Bildwiederholrate (Hz) & Dynamische FPS-Synchronisation",
(Language::German, "hz_desc") => "Sperrt die Engine auf Ziel-FPS unter Beibehaltung der 60Hz-Physik. Verhindert Geschwindigkeitsfehler und Glitches bei 60, 120, 144+ FPS.",
(Language::German, "target_fps") => "Ziel-FPS:",
(Language::German, "readback_patch") => "Gelbe Texturfehler-Behebung (Instant Readbacks)",
(Language::German, "readback_desc") => "Verhindert goldgelbe Texturen in Schneeszenen.",
(Language::German, "readback_patch") => "Texturfehler- & Gelbstich-Behebung (Instant Readbacks)",
(Language::German, "readback_desc") => "Erzwingt sofortige lineare Readbacks und verhindert goldgelbe Texturen und Artefakte in Schneeszenen.",
(Language::German, "async_readbacks") => "⚡ Asynchrone Readbacks (Experimentell - Empfohlen)",
(Language::German, "async_readbacks_desc") => "Entkoppelt CPU- und GPU-Wartezeiten für Texturen. Verhindert FPS-Einbrüche und Mikroruckler für eine flüssige Darstellung. Empfohlen: EIN.",
(Language::German, "launch_while_compiling_shaders") => "Spiel während Shader-Kompilierung starten",
(Language::German, "launch_while_compiling_shaders_desc") => "Ein: Das Spiel startet sofort, Shader werden im Hintergrund kompiliert. Aus: Spiel startet erst nach 100%iger Shader-Kompilierung mit Spartan-Rage-Ladebildschirm.",
(Language::German, "show_shader_rage_meter") => "Shader-Kompilierungsanzeige (Spartan Rage Leiste)",
(Language::German, "show_shader_rage_meter_desc") => "Ein: Zeigt die Shader-Kompilierung mit der Spartan-Rage-Leiste und Pipeline-Zähler an (hält nach 100% einen schwarzen Bildschirm, bis das Spielfenster bereit ist). Aus (PS4-Modus): Blendet die Leiste aus und zeigt stattdessen das animierte PS4-Artwork (pic1.png).",
(Language::German, "shader_cache_title") => "⚡ Offizieller Shader-Cache (Steam-Style Pre-Caching)",
(Language::German, "shader_cache_desc") => "Offizieller, sauberer Shader-Cache mit über 28.000 kompilierten Shadern für alle Bereiche und Bosse. Beseitigt Mikroruckler und Render-Pausen komplett.",
(Language::German, "shader_download_btn") => "☁️ Vollständigen Shader-Cache herunterladen (17 MB)",
(Language::German, "shader_download_btn_update") => "🔄 Shader-Cache erneut verifizieren & laden (17 MB)",
(Language::German, "shader_installed_badge") => "✅ Vollständiger Shader-Cache Aktiv ({} Shader Bereit)",
(Language::German, "shader_missing_badge") => "⚠️ Cache Unvollständig ({} Shader Gefunden - Download Empfohlen)",
(Language::German, "shader_downloading") => "Shader-Cache wird verarbeitet (~17 MB)...",
(Language::German, "shader_extracting") => "Shader-Cache wird entpackt und verifiziert...",
(Language::German, "shader_success") => "✅ Shader-Cache erfolgreich installiert! ({} Shader im Cache)",
(Language::German, "skip_cinematics") => "Zwischensequenzen mit 'X'-Taste überspringen",
(Language::German, "skip_cinematics_desc") => "Ermöglicht das sofortige Überspringen von Zwischensequenzen mit der X-Taste.",
(Language::German, "nav_home") => "STARTSEITE",
(Language::German, "nav_mods") => "MODS",
(Language::German, "mods_title") => "God of War III Mod-Manager",
(Language::German, "mods_desc") => "Verwalten Sie Charakter-, Kostüm- und Textur-Mods. Sicheres Laden über ein virtuelles Dateisystem.",
(Language::German, "mods_open_folder") => "Mods-Ordner öffnen",
(Language::German, "mods_refresh") => "Mods aktualisieren",
(Language::German, "mods_count_summary") => "Installiert: {} | Aktiv: {} | Deaktiviert: {}",
(Language::German, "mods_empty_title") => "Noch keine Mods installiert",
(Language::German, "mods_empty_desc") => "Klicken Sie auf 'Mods-Ordner öffnen', fügen Sie Ihren Mod-Ordner ein und klicken Sie auf 'Mods aktualisieren'.",
(Language::German, "mods_status_active") => "● Im Spiel aktiv",
(Language::German, "mods_status_disabled") => "○ Deaktiviert",
(Language::German, "mods_author") => "Autor",
(Language::German, "mods_files_replaced") => "Ersetzte Dateien",
(Language::German, "nav_settings") => "EINSTELLUNGEN",
(Language::German, "nav_graphics") => "GRAFIK",
(Language::German, "nav_game_location") => "SPIELPFAD",
(Language::German, "nav_controls") => "STEUERUNG",
(Language::German, "nav_shaders") => "SHADER-EINSTELLUNGEN",
(Language::German, "launcher_version_label") => "LAUNCHER-VERSION",
(Language::German, "lang_options_title") => "SPRACHOPTIONEN",
(Language::German, "system_specs_title") => "SYSTEMSPEZIFIKATIONEN",
(Language::German, "play_btn_label") => "SPIELEN",
(Language::German, "menu_play") => "SPIELEN",
(Language::German, "menu_trophies") => "TROPHÄEN",
(Language::German, "menu_latest_news") => "NEUIGKEITEN",
(Language::German, "menu_options") => "OPTIONEN",
(Language::German, "menu_back") => "ZURÜCK",
(Language::German, "bonus_play_fix_toggle") => "Bonus Play QTE & Gegner-Spawn-Korrektur",
(Language::German, "bonus_play_fix_desc") => "Verhindert, dass göttliche Besitztümer (Hephaistos' Ring) QTEs vorzeitig überspringen und dadurch das Erscheinen von Gegnerwellen blockieren.",
(Language::German, "release_notes_title") => "God of War III Launcher - Versionshinweise (v2.2)",
(Language::German, "release_notes_content") => "🔥 GOD OF WAR III LAUNCHER v2.2 NEUIGKEITEN:\n\n• 🏆 Echzeit-Trophäensystem & PS4-Benachrichtigung:\n - Alle 36 Trophäen mit Originalgrafiken, Benachrichtigungskapsel und PS4-Soundeffekt.\n - Trophäen-Showcase mit Fortschrittsanzeige und 100%-Platin-Abzeichen.\n\n• 🗡️ Bonus-Gegenstände Bugfix (QTE & Gegner-Spawn-Korrektur).",
(Language::German, "discord_rpc_toggle") => "Discord Rich Presence (Spielstatus)",
(Language::German, "discord_rpc_desc") => "Zeigt 'Spielt God of War III Remastered' und die Spielzeit in deinem Discord-Profil an.",
(Language::German, "discord_client_id_label") => "Discord Anwendungs-ID (Client ID):",
(Language::German, "discord_reset_btn") => "Standard wiederherstellen",
(Language::German, "gamemode_toggle") => "Feral GameMode (gamemoderun)",
(Language::German, "gamemode_desc") => "Optimiert CPU-Governor und GPU für maximale Gaming-Leistung.",
(Language::German, "save_apply") => "Einstellungen speichern & anwenden",
@@ -431,6 +742,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "latest_commit") => "Neuester Commit:",
(Language::German, "commit_date") => "Datum:",
(Language::German, "view_on_github") => "Auf GitHub ansehen",
(Language::German, "remind_later") => "⏰ Später erinnern",
(Language::German, "update_available_badge") => "🚀 Update verfügbar!",
(Language::German, "btn_update") => "Aktualisieren",
(Language::German, "btn_update_now") => "🚀 Jetzt aktualisieren",
(Language::German, "updating_in_progress") => "⏳ Update wird heruntergeladen und installiert... Bitte warten...",
(Language::German, "python_dep") => "Python 3 Abhängigkeit:",
(Language::German, "python_ready") => "Installiert / Bereit",
(Language::German, "python_missing") => "Nicht gefunden",
@@ -456,6 +772,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "controller_header") => "Gamepad- & Controller-Einstellungen",
(Language::German, "bg_input") => "Hintergrund-Controller-Eingabe",
(Language::German, "bg_input_desc") => "Ermöglicht Gamepad-Eingaben auch wenn das Spielfenster nicht fokussiert ist.",
(Language::German, "hide_mouse_cursor") => "Mauszeiger im Spiel ausblenden",
(Language::German, "hide_mouse_cursor_desc") => "Blendet den Mauszeiger im Fokus aus und zeigt ihn wieder an, wenn der Fokus verloren geht.",
(Language::German, "motion_controls") => "Bewegungs- & Gyrosensoren",
(Language::German, "motion_controls_desc") => "Aktiviert interne Beschleunigungsmesser und Gyroskop der DualShock 4 / DualSense Controller.",
(Language::German, "deadzone_left") => "Linker Stick Totzone:",
@@ -464,8 +782,10 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "keyboard_header") => "Tastaturbelegung",
(Language::German, "keyboard_desc") => "Klicken Sie auf eine Taste und drücken Sie eine Taste auf der Tastatur. ESC zum Abbrechen.",
(Language::German, "preset_label") => "Schnell-Voreinstellung:",
(Language::German, "preset_standard") => "Standard WASD + CVBN (Empfohlen)",
(Language::German, "preset_numpad") => "WASD + Numpad (8462)",
(Language::German, "preset_recommended") => "🎯 Empfohlenes Profil",
(Language::German, "preset_mouse_kb") => "🖱️ Zweites Profil (Maus + Tastatur)",
(Language::German, "preset_standard") => "🎯 Empfohlenes Profil",
(Language::German, "preset_numpad") => "🖱️ Zweites Profil (Maus + Tastatur)",
(Language::German, "reset_defaults") => "Auf Standard zurücksetzen",
(Language::German, "press_any_key") => "[ Taste drücken... / ESC ]",
(Language::German, "sec_face_buttons") => "Aktionstasten (Angriff & Sprung)",
@@ -496,11 +816,44 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::German, "act_cam_down") => "Rechter Stick: Runter",
(Language::German, "act_cam_left") => "Rechter Stick: Links",
(Language::German, "act_cam_right") => "Rechter Stick: Rechts",
(Language::German, "key_bind_modal_title") => "🎮 Taste / Maustaste zuweisen",
(Language::German, "key_bind_modal_desc") => "Drücken Sie eine Taste, klicken Sie mit der Maus oder wählen Sie unten:",
(Language::German, "sec_mouse_bindings") => "🖱 Maustasten",
(Language::German, "sec_numpad_bindings") => "🔢 Ziffernblock (Numpad)",
(Language::German, "sec_digits_bindings") => "🔢 Standard-Zahlenreihe (1 - 0)",
(Language::German, "mouse_left_btn") => "🖱 Linksklick (Maus 1)",
(Language::German, "mouse_right_btn") => "🖱 Rechtsklick (Maus 2)",
(Language::German, "mouse_middle_btn") => "🖱 Mittelklick (Maus 3)",
(Language::German, "mouse_side1_btn") => "🖱 Seitentaste 1 (Maus 4)",
(Language::German, "mouse_side2_btn") => "🖱 Seitentaste 2 (Maus 5)",
(Language::German, "mouse_wheel_up") => "🖱 Mausrad Hoch",
(Language::German, "mouse_wheel_down") => "🖱 Mausrad Runter",
(Language::German, "cancel_btn") => "Abbrechen (ESC)",
(Language::German, "launcher_log_btn") => "📋 Launcher-Protokoll",
(Language::German, "game_log_btn") => "🎮 Spiel-Protokoll",
(Language::German, "err_modal_title") => "⚠️ Ein Fehler ist aufgetreten",
(Language::German, "copy_error") => "📋 Fehler kopieren",
(Language::German, "copied_notice") => "✅ Kopiert!",
(Language::German, "open_log_file") => "📄 Protokolldatei öffnen",
(Language::German, "open_in_editor") => "📝 Im Editor öffnen",
(Language::German, "refresh_log") => "🔄 Aktualisieren",
(Language::German, "err_engine_dl_title") => "❌ Download der Spiel-Engine fehlgeschlagen",
(Language::German, "err_engine_dl_summary") => "Das Spiel-Engine-Paket für God of War III konnte nicht heruntergeladen oder entpackt werden.",
(Language::German, "err_engine_dl_hint") => "Bitte überprüfen Sie Ihre Internetverbindung. Der GitHub-Download ist möglicherweise fehlgeschlagen oder der Speicherplatz reicht nicht aus.",
(Language::German, "engine_download_alt") => "Alternativer Download",
(Language::German, "engine_download_alt_tooltip") => "Verwendet einen alternativen Server und eine andere Download-Methode, falls der Standard-Download fehlschlägt.",
(Language::German, "retry_with_alt") => "⚡ Alternativen Engine-Download versuchen",
(Language::German, "err_python_dl_title") => "❌ Python-Laufzeitumgebung Download fehlgeschlagen",
(Language::German, "err_python_dl_summary") => "Die für die Vorbereitungsskripte erforderliche portable Python 3-Umgebung konnte nicht heruntergeladen werden.",
(Language::German, "err_python_dl_hint") => "Stellen Sie sicher, dass Python 3 auf Ihrem System installiert ist, oder überprüfen Sie Ihre Verbindung.",
(Language::German, "err_game_launch_title") => "❌ Fehler beim Spielstart",
(Language::German, "err_game_launch_summary") => "Beim Vorbereiten oder Starten von God of War III ist ein Fehler aufgetreten.",
(Language::German, "err_game_launch_hint") => "Überprüfen Sie, ob eboot.bin und die Spieldateien intakt sind. Überprüfen Sie die Details unten oder die Protokolldatei.",
// Spanish
(Language::Spanish, "hero_badge") => "🔥 Lanzador de PC de Código Abierto",
(Language::Spanish, "hero_desc") => "Diseñado para perfeccionar la experiencia de God of War III Remastered en Windows, Linux y Steam Deck.",
(Language::Spanish, "badge_v13") => "v1.3",
(Language::Spanish, "badge_v13") => "v1.4",
(Language::Spanish, "badge_windows") => "Windows",
(Language::Spanish, "badge_linux") => "Linux",
(Language::Spanish, "badge_steam_deck") => "Steam Deck",
@@ -508,15 +861,39 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "tag_hardware") => "H A R D W A R E Y S I S T E M A",
(Language::Spanish, "tag_game_location") => "U B I C A C I Ó N D E L J U E G O",
(Language::Spanish, "tag_patches") => "R E N D I M I E N T O Y P A R C H E S",
(Language::Spanish, "tag_mods") => "M O D S D E L J U E G O",
(Language::Spanish, "tag_display") => "P A N T A L L A Y G R Á F I C O S",
(Language::Spanish, "tag_controller") => "M A N D O Y G A M E P A D",
(Language::Spanish, "tag_keyboard") => "A S I G N A C I Ó N D E T E C L A S",
(Language::Spanish, "app_title") => "GOD OF WAR® III REMASTERED",
(Language::Spanish, "app_subtitle") => "Lanzador de Port Linux y Windows y Gestor de Hardware",
(Language::Spanish, "tab_game_perf") => "Juego y Rendimiento",
(Language::Spanish, "tab_game_perf") => "Juego e Inicio",
(Language::Spanish, "tab_game") => "Juego e Inicio",
(Language::Spanish, "tab_graphics") => "Ajustes Gráficos",
(Language::Spanish, "tab_config_controls") => "Configuración y Controles",
(Language::Spanish, "graphics_header") => "Ajustes Gráficos y Rendimiento",
(Language::Spanish, "graphics_subtitle") => "Optimizaciones para gama baja, escalado de resolución y efectos visuales",
(Language::Spanish, "graphic_preset") => "Perfil Gráfico:",
(Language::Spanish, "preset_ultra_low") => "🚀 Gama Ultra Baja (480p + FSR + Efectos Reducidos)",
(Language::Spanish, "preset_low") => "⚡ Gama Baja / Steam Deck (720p + FSR + Efectos Reducidos)",
(Language::Spanish, "preset_steam_deck") => "🎮 Steam Deck (800p + FSR + 40/90Hz)",
(Language::Spanish, "preset_balanced") => "⚖️ Estándar / Nativo (1080p Nativo + Efectos por Defecto)",
(Language::Spanish, "preset_ultra") => "💎 Calidad Alta (1440p / 4K + 120 FPS)",
(Language::Spanish, "preset_custom") => "🛠️ Personalizado",
(Language::Spanish, "preset_desc") => "Seleccione un perfil para optimizar con un clic. Cualquier cambio manual cambiará el perfil a Personalizado.",
(Language::Spanish, "render_resolution") => "Resolución Interna:",
(Language::Spanish, "res_desc") => "Resolución de renderizado interno. Seleccionar 720p o 480p aumenta enormemente los FPS en PCs modestos.",
(Language::Spanish, "fsr_toggle") => "AMD FSR 1.0 y Nitidez RCAS",
(Language::Spanish, "fsr_desc") => "Escala y enfoca resoluciones menores (720p/480p) a la pantalla sin desenfoque.",
(Language::Spanish, "disable_motion_blur") => "Desactivar Desenfoque de Movimiento",
(Language::Spanish, "disable_motion_blur_desc") => "Elimina el desenfoque de cámara y objetos, mejorando la claridad y reduciendo la carga de GPU.",
(Language::Spanish, "disable_dof") => "Desactivar Profundidad de Campo (DoF)",
(Language::Spanish, "disable_dof_desc") => "Desactiva el filtro pesado de fondo, manteniendo nitidez en la distancia y ganando FPS.",
(Language::Spanish, "vram_budget") => "Presupuesto de Limpieza VRAM:",
(Language::Spanish, "vram_budget_desc") => "Ajuste según la memoria de su GPU para evitar tirones en tarjetas con poca VRAM.",
(Language::Spanish, "sys_hardware") => "Hardware del Sistema",
(Language::Spanish, "locked_auto") => "Bloqueado (Detectado Automáticamente)",
(Language::Spanish, "cpu") => "Procesador (CPU)",
(Language::Spanish, "gpu") => "Tarjeta Gráfica (GPU)",
(Language::Spanish, "vram") => "Capacidad VRAM",
(Language::Spanish, "vram_safe") => "Protegido contra corrupción de texturas",
@@ -533,13 +910,63 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "game_valid") => "Carpeta de God of War III verificada y completa",
(Language::Spanish, "game_invalid") => "¡Carpeta incompleta o falta 'eboot.bin'!",
(Language::Spanish, "patches_header") => "Parches y Rendimiento",
(Language::Spanish, "hz_patch") => "Parche de Sincronización Hz",
(Language::Spanish, "hz_desc") => "Evita la aceleración 2x en pantallas de 144Hz+.",
(Language::Spanish, "hz_patch") => "Tasa de Refresco (Hz) y Sincronización FPS Dinámica",
(Language::Spanish, "hz_desc") => "Bloquea el motor a los FPS objetivo preservando la física de 60Hz. Elimina fallos de aceleración en 60, 120, 144+ FPS.",
(Language::Spanish, "target_fps") => "FPS Objetivo:",
(Language::Spanish, "readback_patch") => "Corrección de Textura Amarilla (Instant Readbacks)",
(Language::Spanish, "readback_desc") => "Evita que las texturas se vuelvan amarillas en escenas de nieve.",
(Language::Spanish, "readback_patch") => "Corrección de Texturas y Tinte Amarillo (Instant Readbacks)",
(Language::Spanish, "readback_desc") => "Fuerza lecturas lineales inmediatas, eliminando por completo el tinte amarillo y texturas corruptas en escenas de nieve o alto brillo.",
(Language::Spanish, "async_readbacks") => "⚡ Lecturas Asíncronas (Experimental - Recomendado)",
(Language::Spanish, "async_readbacks_desc") => "Desacopla la CPU de las esperas de la GPU para texturas. Elimina caídas de FPS y tirones para una experiencia fluida. Recomendado: ACTIVADO.",
(Language::Spanish, "launch_while_compiling_shaders") => "Iniciar Juego Mientras se Compilan los Shaders",
(Language::Spanish, "launch_while_compiling_shaders_desc") => "Activado: El juego se abre inmediatamente y compila en segundo plano. Desactivado: El juego espera a que los shaders estén al 100% para evitar tirones.",
(Language::Spanish, "show_shader_rage_meter") => "Visualización de Compilación de Shaders (Barra de Furia Espartana)",
(Language::Spanish, "show_shader_rage_meter_desc") => "Activado: Muestra el progreso de compilación con la barra de Furia Espartana y contador de pipelines (espera en pantalla negra al 100% hasta que la ventana del juego esté lista). Desactivado (Modo PS4): Oculta la barra y muestra solo el arte de inicio animado de PS4 (pic1.png).",
(Language::Spanish, "shader_cache_title") => "⚡ Caché Oficial de Shaders (Estilo Steam Pre-Caching)",
(Language::Spanish, "shader_cache_desc") => "Caché oficial limpia y segura con más de 28.000 shaders precompilados para todas las áreas y jefes. Elimina completamente tirones y congelaciones.",
(Language::Spanish, "shader_download_btn") => "☁️ Descargar e Instalar Caché Completa de Shaders (17 MB)",
(Language::Spanish, "shader_download_btn_update") => "🔄 Re-verificar e Instalar Caché de Shaders (17 MB)",
(Language::Spanish, "shader_installed_badge") => "✅ Caché Completa de Shaders Activa ({} Shaders Listos)",
(Language::Spanish, "shader_missing_badge") => "⚠️ Caché Incompleta ({} Shaders Encontrados - Se Recomienda Descargar)",
(Language::Spanish, "shader_downloading") => "Procesando caché de shaders (~17 MB)... Por favor espere...",
(Language::Spanish, "shader_extracting") => "Extrayendo y verificando la caché de shaders...",
(Language::Spanish, "shader_success") => "✅ ¡Caché de shaders instalada con éxito! ({} shaders en caché)",
(Language::Spanish, "skip_cinematics") => "Omitir Cinemáticas con el Botón 'X'",
(Language::Spanish, "skip_cinematics_desc") => "Permite saltar cualquier cinemática o vídeo pulsando el botón X del mando.",
(Language::Spanish, "nav_home") => "INICIO",
(Language::Spanish, "nav_mods") => "MODS",
(Language::Spanish, "mods_title") => "Gestor de Mods de God of War III",
(Language::Spanish, "mods_desc") => "Administra fácilmente mods de personajes, trajes y texturas. Se cargan de forma segura sin modificar archivos originales.",
(Language::Spanish, "mods_open_folder") => "Abrir Carpeta de Mods",
(Language::Spanish, "mods_refresh") => "Actualizar Mods",
(Language::Spanish, "mods_count_summary") => "Instalados: {} | Activos: {} | Desactivados: {}",
(Language::Spanish, "mods_empty_title") => "No hay mods instalados",
(Language::Spanish, "mods_empty_desc") => "Haz clic en 'Abrir Carpeta de Mods', coloca la carpeta de tu mod dentro y haz clic en 'Actualizar Mods'.",
(Language::Spanish, "mods_status_active") => "● Activo en el Juego",
(Language::Spanish, "mods_status_disabled") => "○ Desactivado",
(Language::Spanish, "mods_author") => "Autor",
(Language::Spanish, "mods_files_replaced") => "Archivos Reemplazados",
(Language::Spanish, "nav_settings") => "AJUSTES",
(Language::Spanish, "nav_graphics") => "GRÁFICOS",
(Language::Spanish, "nav_game_location") => "UBICACIÓN DEL JUEGO",
(Language::Spanish, "nav_controls") => "CONTROLES",
(Language::Spanish, "nav_shaders") => "AJUSTES DE SHADER",
(Language::Spanish, "launcher_version_label") => "VERSIÓN DEL LAUNCHER",
(Language::Spanish, "lang_options_title") => "OPCIONES DE IDIOMA",
(Language::Spanish, "system_specs_title") => "ESPECIFICACIONES DEL SISTEMA",
(Language::Spanish, "play_btn_label") => "JUGAR",
(Language::Spanish, "menu_play") => "JUGAR",
(Language::Spanish, "menu_trophies") => "TROFEOS",
(Language::Spanish, "menu_latest_news") => "ÚLTIMAS NOTICIAS",
(Language::Spanish, "menu_options") => "OPCIONES",
(Language::Spanish, "menu_back") => "VOLVER",
(Language::Spanish, "bonus_play_fix_toggle") => "Corrección de QTE y Enemigos en Bonus Play",
(Language::Spanish, "bonus_play_fix_desc") => "Evita que las Posesiones Divinas (Anillo de Hefesto) omitan los QTEs prematuramente y bloqueen la aparición de oleadas de enemigos.",
(Language::Spanish, "release_notes_title") => "God of War III Launcher - Notas de la Versión (v2.2)",
(Language::Spanish, "release_notes_content") => "🔥 NOVEDADES DE GOD OF WAR III LAUNCHER v2.2:\n\n• 🏆 Sistema de Trofeos en Tiempo Real y Notificaciones PS4:\n - Los 36 trofeos oficiales con insignias en alta resolución y efectos de sonido de PS4.\n - Notificaciones flotantes estilo cápsula PS4 y panel de trofeos con medalla de maestría al 100%.\n\n• 🗡️ Corrección de errores en objetos de Bonus Play (QTE y generación de enemigos).",
(Language::Spanish, "discord_rpc_toggle") => "Discord Rich Presence (Estado de Juego)",
(Language::Spanish, "discord_rpc_desc") => "Muestra 'Jugando a God of War III Remastered' y el tiempo de juego en tu perfil de Discord.",
(Language::Spanish, "discord_client_id_label") => "ID de Aplicación de Discord (Client ID):",
(Language::Spanish, "discord_reset_btn") => "Restablecer por Defecto",
(Language::Spanish, "gamemode_toggle") => "Feral GameMode (gamemoderun)",
(Language::Spanish, "gamemode_desc") => "Optimiza la CPU y GPU para máximo rendimiento en juegos.",
(Language::Spanish, "save_apply") => "Guardar y Aplicar",
@@ -582,6 +1009,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "latest_commit") => "Último Commit:",
(Language::Spanish, "commit_date") => "Fecha:",
(Language::Spanish, "view_on_github") => "Ver en GitHub",
(Language::Spanish, "remind_later") => "⏰ Recordármelo más tarde",
(Language::Spanish, "update_available_badge") => "🚀 ¡Actualización disponible!",
(Language::Spanish, "btn_update") => "Actualizar",
(Language::Spanish, "btn_update_now") => "🚀 Actualizar ahora",
(Language::Spanish, "updating_in_progress") => "⏳ Descargando e instalando actualización... Por favor espere...",
(Language::Spanish, "python_dep") => "Dependencia Python 3:",
(Language::Spanish, "python_ready") => "Instalado / Listo",
(Language::Spanish, "python_missing") => "No encontrado",
@@ -607,6 +1039,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "controller_header") => "Ajustes de Mando / Gamepad",
(Language::Spanish, "bg_input") => "Entrada de mando en segundo plano",
(Language::Spanish, "bg_input_desc") => "Permite entradas del mando incluso cuando la ventana del juego no está activa.",
(Language::Spanish, "hide_mouse_cursor") => "Ocultar cursor del ratón en el juego",
(Language::Spanish, "hide_mouse_cursor_desc") => "Oculta el cursor del ratón cuando el juego está enfocado; se vuelve visible al perder el foco.",
(Language::Spanish, "motion_controls") => "Controles de movimiento y giroscopio",
(Language::Spanish, "motion_controls_desc") => "Activa el acelerómetro y giroscopio internos de mandos DualShock 4 / DualSense.",
(Language::Spanish, "deadzone_left") => "Zona muerta palanca izquierda:",
@@ -614,9 +1048,11 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "deadzone_desc") => "Valores más bajos ofrecen mayor sensibilidad; auméntelo si su palanca sufre de deriva (drift).",
(Language::Spanish, "keyboard_header") => "Asignación de Teclas (Keyboard Bindings)",
(Language::Spanish, "keyboard_desc") => "Haz clic en cualquier botón y pulsa la nueva tecla. Pulsa ESC para cancelar.",
(Language::Spanish, "preset_label") => "Ajuste rápido:",
(Language::Spanish, "preset_standard") => "Estándar WASD + CVBN (Recomendado)",
(Language::Spanish, "preset_numpad") => "WASD + Teclado Numérico (8462)",
(Language::Spanish, "preset_label") => "Ajustes Rápidos:",
(Language::Spanish, "preset_recommended") => "🎯 Perfil Recomendado",
(Language::Spanish, "preset_mouse_kb") => "🖱️ Segundo Perfil (Ratón + Teclado)",
(Language::Spanish, "preset_standard") => "🎯 Perfil Recomendado",
(Language::Spanish, "preset_numpad") => "🖱️ Segundo Perfil (Ratón + Teclado)",
(Language::Spanish, "reset_defaults") => "Restablecer por defecto",
(Language::Spanish, "press_any_key") => "[ Presiona una tecla... / ESC ]",
(Language::Spanish, "sec_face_buttons") => "Botones de acción (Ataques y Salto)",
@@ -647,6 +1083,39 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(Language::Spanish, "act_cam_down") => "Palanca Der: Abajo",
(Language::Spanish, "act_cam_left") => "Palanca Der: Izquierda",
(Language::Spanish, "act_cam_right") => "Palanca Der: Derecha",
(Language::Spanish, "key_bind_modal_title") => "🎮 Asignar tecla / botón del ratón",
(Language::Spanish, "key_bind_modal_desc") => "Presiona una tecla, haz clic o elige una opción abajo:",
(Language::Spanish, "sec_mouse_bindings") => "🖱 Botones del Ratón",
(Language::Spanish, "sec_numpad_bindings") => "🔢 Teclado Numérico (Numpad)",
(Language::Spanish, "sec_digits_bindings") => "🔢 Fila de Números Estándar (1 - 0)",
(Language::Spanish, "mouse_left_btn") => "🖱 Clic Izquierdo (Ratón 1)",
(Language::Spanish, "mouse_right_btn") => "🖱 Clic Derecho (Ratón 2)",
(Language::Spanish, "mouse_middle_btn") => "🖱 Clic Central (Ratón 3)",
(Language::Spanish, "mouse_side1_btn") => "🖱 Botón Lateral 1 (Ratón 4)",
(Language::Spanish, "mouse_side2_btn") => "🖱 Botón Lateral 2 (Ratón 5)",
(Language::Spanish, "mouse_wheel_up") => "🖱 Rueda Arriba",
(Language::Spanish, "mouse_wheel_down") => "🖱 Rueda Abajo",
(Language::Spanish, "cancel_btn") => "Cancelar (ESC)",
(Language::Spanish, "launcher_log_btn") => "📋 Registro del Launcher",
(Language::Spanish, "game_log_btn") => "🎮 Registro del Juego",
(Language::Spanish, "err_modal_title") => "⚠️ Ocurrió un Error",
(Language::Spanish, "copy_error") => "📋 Copiar Error",
(Language::Spanish, "copied_notice") => "✅ ¡Copiado!",
(Language::Spanish, "open_log_file") => "📄 Abrir Archivo de Registro",
(Language::Spanish, "open_in_editor") => "📝 Abrir en el Editor",
(Language::Spanish, "refresh_log") => "🔄 Actualizar",
(Language::Spanish, "err_engine_dl_title") => "❌ Error al Descargar el Motor del Juego",
(Language::Spanish, "err_engine_dl_summary") => "No se pudo descargar o extraer el paquete del motor del juego God of War III.",
(Language::Spanish, "err_engine_dl_hint") => "Por favor revise su conexión a internet. La descarga de GitHub pudo fallar o el espacio en disco es insuficiente.",
(Language::Spanish, "engine_download_alt") => "Descarga Alternativa",
(Language::Spanish, "engine_download_alt_tooltip") => "Usa un servidor alternativo y otro método de descarga si la descarga estándar falla.",
(Language::Spanish, "retry_with_alt") => "⚡ Probar Descarga Alternativa del Motor",
(Language::Spanish, "err_python_dl_title") => "❌ Error al Descargar el Entorno de Python",
(Language::Spanish, "err_python_dl_summary") => "No se pudo descargar el entorno portátil de Python 3 necesario para los scripts de preparación del juego.",
(Language::Spanish, "err_python_dl_hint") => "Asegúrese de que Python 3 esté instalado en su sistema o verifique su conexión e intente nuevamente.",
(Language::Spanish, "err_game_launch_title") => "❌ Error al Iniciar el Juego",
(Language::Spanish, "err_game_launch_summary") => "Ocurrió un error durante la preparación o ejecución de God of War III.",
(Language::Spanish, "err_game_launch_hint") => "Verifique que eboot.bin y los archivos del juego estén completos. Revise los detalles abajo o el archivo de registro.",
_ => "",
}
+169
View File
@@ -0,0 +1,169 @@
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
static LOG_PATH: OnceLock<PathBuf> = OnceLock::new();
static LOG_MUTEX: Mutex<()> = Mutex::new(());
/// Formats current SystemTime as UTC "YYYY-MM-DD HH:MM:SS" without external dependencies
pub fn format_current_timestamp() -> String {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let total_secs = now.as_secs();
let sec = (total_secs % 60) as u32;
let min = ((total_secs / 60) % 60) as u32;
let hour = ((total_secs / 3600) % 24) as u32;
let days = (total_secs / 86400) as i64;
// Convert days since 1970-01-01 to year, month, day (Howard Hinnant civil algorithm)
let z = days + 719468;
let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC", y, m, d, hour, min, sec)
}
/// Initializes the global log file path in base_dir/launcher_log.txt and writes startup banner
pub fn init_logger(base_dir: &Path) {
let path = base_dir.join("launcher_log.txt");
let _ = LOG_PATH.set(path.clone());
// Write session divider banner
let sep = "================================================================================";
write_log("INFO", sep);
write_log(
"INFO",
&format!(
"God of War III Launcher Log started (v2.0) | OS: {} | Arch: {}",
std::env::consts::OS,
std::env::consts::ARCH
),
);
write_log("INFO", &format!("Base Directory: {}", base_dir.display()));
write_log("INFO", sep);
}
/// Appends a formatted log line to stdout/stderr and launcher_log.txt
pub fn write_log(level: &str, message: &str) {
let timestamp = format_current_timestamp();
let formatted = format!("[{}] [{}] {}", timestamp, level, message);
if level == "ERROR" {
eprintln!("{}", formatted);
} else {
println!("{}", formatted);
}
if let Some(path) = LOG_PATH.get() {
if let Ok(_guard) = LOG_MUTEX.lock() {
if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(f, "{}", formatted);
}
}
}
}
/// Reads the current content of launcher_log.txt
pub fn read_launcher_log(base_dir: &Path) -> String {
let path = base_dir.join("launcher_log.txt");
if path.is_file() {
fs::read_to_string(&path).unwrap_or_else(|e| format!("Log dosyası okunamadı: {}", e))
} else {
"Launcher log dosyası henüz oluşturulmadı.".to_string()
}
}
/// Opens any file in the OS default text editor or viewer (e.g. Notepad, xdg-open)
pub fn open_file_path(path: &Path) {
if !path.exists() {
return;
}
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
let _ = std::process::Command::new("cmd")
.args(["/C", "start", "", &path.to_string_lossy()])
.creation_flags(CREATE_NO_WINDOW)
.spawn();
}
#[cfg(not(target_os = "windows"))]
{
let _ = std::process::Command::new("xdg-open")
.arg(path)
.spawn();
}
}
/// Opens the launcher_log.txt file in the OS default text editor (e.g. Notepad, xdg-open)
pub fn open_log_in_editor(base_dir: &Path) {
let log_file = base_dir.join("launcher_log.txt");
if !log_file.exists() {
let _ = fs::write(&log_file, "God of War III Launcher Log\n");
}
open_file_path(&log_file);
}
#[macro_export]
macro_rules! log_info {
($($arg:tt)*) => {
$crate::logger::write_log("INFO", &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_warn {
($($arg:tt)*) => {
$crate::logger::write_log("WARN", &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_error {
($($arg:tt)*) => {
$crate::logger::write_log("ERROR", &format!($($arg)*))
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_current_timestamp() {
let ts = format_current_timestamp();
assert!(ts.contains("UTC"));
assert_eq!(ts.len(), 23); // "YYYY-MM-DD HH:MM:SS UTC"
}
#[test]
fn test_write_and_read_log() {
let temp_dir = std::env::temp_dir().join("gow3_logger_test");
let _ = fs::create_dir_all(&temp_dir);
let test_file = temp_dir.join("launcher_log.txt");
let _ = fs::remove_file(&test_file);
init_logger(&temp_dir);
log_info!("Test info message 123");
log_warn!("Test warning message 456");
log_error!("Test error message 789");
let content = read_launcher_log(&temp_dir);
assert!(content.contains("Test info message 123"));
assert!(content.contains("Test warning message 456"));
assert!(content.contains("Test error message 789"));
let _ = fs::remove_dir_all(&temp_dir);
}
}
+78 -22
View File
@@ -2,11 +2,17 @@ mod app;
mod config;
mod crash_analyzer;
mod i18n;
pub mod logger;
mod runner;
mod scanner;
mod system;
mod tuner;
mod updater;
mod runner;
mod shader_downloader;
pub mod mods_manager;
pub mod discord_rpc;
pub mod win_input;
pub mod trophy;
use app::LauncherApp;
use eframe::egui;
@@ -33,43 +39,35 @@ fn main() -> eframe::Result<()> {
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
};
// Initialize launcher file logging
logger::init_logger(&base_dir);
let args: Vec<String> = std::env::args().collect();
if args.iter().any(|a| a == "--play" || a == "--start" || a == "-p") {
log_info!("Command line argument '--play' detected. Launching game directly...");
let config = config::LauncherConfig::load(&base_dir);
let gamemode_avail = system::is_gamemode_available();
let use_gamemode = gamemode_avail && config.use_gamemode;
let _ = tuner::apply_settings(&base_dir, &config);
match runner::launch_gow3(&base_dir, &config, use_gamemode, None) {
Ok(code) => std::process::exit(code),
Ok(code) => {
log_info!("Game process exited with status code: {}", code);
std::process::exit(code);
}
Err(e) => {
eprintln!("Error launching God of War III: {}", e);
log_error!("Error launching God of War III via CLI: {}", e);
std::process::exit(1);
}
}
}
let mut viewport = egui::ViewportBuilder::default()
.with_title("God of War® III Remastered - Launcher")
.with_inner_size([800.0, 720.0])
.with_min_inner_size([650.0, 550.0])
.with_title("God of War® III Remastered")
.with_inner_size([1024.0, 576.0])
.with_min_inner_size([854.0, 480.0])
.with_resizable(true);
// Try load window icon: check base_dir first, then fallback to embedded asset
let icon_data = if let Ok(img) = image::open(base_dir.join("gow3.png")) {
let width = img.width();
let height = img.height();
let rgba = img.to_rgba8().into_raw();
Some(egui::IconData { rgba, width, height })
} else if let Ok(img) = image::load_from_memory(include_bytes!("../../gow3.png")) {
let width = img.width();
let height = img.height();
let rgba = img.to_rgba8().into_raw();
Some(egui::IconData { rgba, width, height })
} else {
None
};
if let Some(icon) = icon_data {
if let Some(icon) = load_window_icon(&base_dir) {
viewport = viewport.with_icon(icon);
}
@@ -84,7 +82,65 @@ fn main() -> eframe::Result<()> {
native_options,
Box::new(move |cc| {
egui_extras::install_image_loaders(&cc.egui_ctx);
cc.egui_ctx.set_embed_viewports(false);
// Register God of War Font and make it primary for Spartan style with unicode fallback
let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert(
"GodOfWar".to_owned(),
std::sync::Arc::new(egui::FontData::from_static(include_bytes!("../assets/godofwar.ttf"))),
);
fonts.families.insert(
egui::FontFamily::Name("GodOfWar".into()),
vec!["GodOfWar".to_owned()],
);
// Prepend GodOfWar to Proportional font family so headers and labels use it automatically
if let Some(prop) = fonts.families.get_mut(&egui::FontFamily::Proportional) {
prop.insert(0, "GodOfWar".to_owned());
}
cc.egui_ctx.set_fonts(fonts);
Ok(Box::new(LauncherApp::new(cc, base_dir_clone)))
}),
)
}
fn load_window_icon(base_dir: &std::path::Path) -> Option<egui::IconData> {
// 1. Try to load gow3.png from base_dir, current_dir, exe_dir, or fallback to embedded asset
let img = if let Ok(img) = image::open(base_dir.join("gow3.png")) {
Some(img)
} else if let Ok(cur) = std::env::current_dir() {
if let Ok(img) = image::open(cur.join("gow3.png")) {
Some(img)
} else {
None
}
} else {
None
}
.or_else(|| {
if let Ok(exe_p) = std::env::current_exe() {
if let Some(parent) = exe_p.parent() {
if let Ok(img) = image::open(parent.join("gow3.png")) {
return Some(img);
}
}
}
image::load_from_memory(include_bytes!("../../gow3.png")).ok()
})?;
// Downscale to 256x256 if larger:
// Windows Win32 User32 CreateIcon fails on dimensions > 256, causing taskbar icon to revert to generic.
// Scaling to 256x256 ensures smooth High-DPI rendering and 100% Win32 / Wayland / X11 taskbar compatibility.
let (w, h) = (img.width(), img.height());
let resized = if w > 256 || h > 256 || w != h {
img.resize_exact(256, 256, image::imageops::FilterType::Lanczos3)
} else {
img
};
let width = resized.width();
let height = resized.height();
let rgba = resized.to_rgba8().into_raw();
Some(egui::IconData { rgba, width, height })
}
+207
View File
@@ -0,0 +1,207 @@
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledMod {
pub folder_name: String,
pub title: String,
pub author: String,
pub version: String,
pub description: String,
pub file_count: usize,
pub sample_files: Vec<String>,
pub is_enabled: bool,
}
pub fn resolve_mods_dir(base_dir: &Path) -> PathBuf {
if base_dir.join("mods").is_dir() {
base_dir.join("mods")
} else if base_dir.join("gow3-linux/mods").is_dir() {
base_dir.join("gow3-linux/mods")
} else {
base_dir.join("mods")
}
}
pub fn open_mods_folder(base_dir: &Path) {
let dir = resolve_mods_dir(base_dir);
let _ = std::fs::create_dir_all(&dir);
#[cfg(target_os = "linux")]
{
let _ = std::process::Command::new("xdg-open").arg(&dir).spawn();
}
#[cfg(target_os = "windows")]
{
let _ = std::process::Command::new("explorer").arg(&dir).spawn();
}
}
pub fn scan_installed_mods(base_dir: &Path, disabled_mods: &[String]) -> Vec<InstalledMod> {
let mods_dir = resolve_mods_dir(base_dir);
if !mods_dir.is_dir() {
let _ = std::fs::create_dir_all(&mods_dir);
return Vec::new();
}
let mut list = Vec::new();
if let Ok(entries) = std::fs::read_dir(&mods_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let folder_name = entry.file_name().to_string_lossy().to_string();
if folder_name.starts_with('.')
|| folder_name.eq_ignore_ascii_case("gow3-windows")
|| folder_name.eq_ignore_ascii_case("gow3-linux")
|| path.join("run.py").is_file()
{
continue;
}
let mut title = folder_name.clone();
let mut author = "Bilinmeyen Yazar".to_string();
let mut version = "v1.0".to_string();
let mut description = String::new();
// 1. Try reading mod.json
let json_path = path.join("mod.json");
if json_path.is_file() {
if let Ok(text) = std::fs::read_to_string(&json_path) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&text) {
if let Some(n) = val["name"].as_str() {
title = n.to_string();
}
if let Some(a) = val["author"].as_str() {
author = a.to_string();
}
if let Some(v) = val["version"].as_str() {
version = v.to_string();
}
if let Some(d) = val["description"].as_str() {
description = d.to_string();
}
}
}
}
// 2. Try reading README.txt if description or author is still default
if description.is_empty() {
let readme_candidates = [
path.join("README.txt"),
path.join("readme.txt"),
path.join("README.md"),
path.join("readme.md"),
];
for r in &readme_candidates {
if r.is_file() {
if let Ok(text) = std::fs::read_to_string(r) {
for line in text.lines() {
let l = line.trim();
if let Some(stripped) = l.strip_prefix("Mod Name :") {
if title == folder_name {
title = stripped.trim().to_string();
}
} else if let Some(stripped) = l.strip_prefix("Author :") {
author = stripped.trim().to_string();
} else if let Some(stripped) = l.strip_prefix("Description :") {
description = stripped.trim().to_string();
} else if let Some(stripped) = l.strip_prefix("Version :") {
version = stripped.trim().to_string();
}
}
if description.is_empty() {
description = text.chars().take(220).collect();
}
break;
}
}
}
}
// 3. Scan mod files
let mut file_count = 0;
let mut sample_files = Vec::new();
scan_mod_files_recursive(&path, &mut file_count, &mut sample_files);
let is_enabled = !disabled_mods.contains(&folder_name);
list.push(InstalledMod {
folder_name,
title,
author,
version,
description,
file_count,
sample_files,
is_enabled,
});
}
}
list.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase()));
list
}
fn scan_mod_files_recursive(dir: &Path, count: &mut usize, samples: &mut Vec<String>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir() {
scan_mod_files_recursive(&p, count, samples);
} else if p.is_file() {
let name = entry.file_name().to_string_lossy().to_string();
let lower = name.to_lowercase();
if lower.ends_with(".txt")
|| lower.ends_with(".md")
|| lower.ends_with(".json")
|| lower.ends_with(".jpg")
|| lower.ends_with(".png")
|| lower.ends_with(".ini")
{
continue;
}
*count += 1;
if samples.len() < 3 {
samples.push(name);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_installed_mods() {
let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf();
let mods = scan_installed_mods(&base_dir, &[]);
assert!(mods.len() >= 2, "Should discover at least CJ and Golden Blades");
let cj = mods.iter().find(|m| m.folder_name.contains("CJ"));
assert!(cj.is_some(), "CJ mod should be found");
let cj_mod = cj.unwrap();
assert_eq!(cj_mod.file_count, 17);
assert!(cj_mod.is_enabled);
let gb = mods.iter().find(|m| m.folder_name.contains("Golden Blades"));
assert!(gb.is_some(), "Golden Blades mod should be found");
let gb_mod = gb.unwrap();
assert_eq!(gb_mod.file_count, 1);
assert_eq!(gb_mod.author, "Machi");
assert!(gb_mod.is_enabled);
}
#[test]
fn test_disabled_mods() {
let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf();
let disabled = vec!["Golden Blades".to_string()];
let mods = scan_installed_mods(&base_dir, &disabled);
let gb = mods.iter().find(|m| m.folder_name == "Golden Blades").unwrap();
assert!(!gb.is_enabled);
}
}
+1042 -77
View File
File diff suppressed because it is too large. Load diff
+248
View File
@@ -0,0 +1,248 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
pub const SHADER_REPO_URL: &str =
"https://raw.githubusercontent.com/ScreamAngel/gow3_launcher/main/shaders/gow3-shader-cache.tar.xz";
pub const SHADER_FALLBACK_URL: &str =
"https://github.com/ScreamAngel/gow3_launcher/releases/download/v2.0/gow3-shader-cache.tar.xz";
/// Resolves the shader cache directory for God of War III.
/// Handles both running from root repo or directly inside `gow3-linux`.
pub fn resolve_cache_dir(base_dir: &Path) -> PathBuf {
if base_dir.join("gow3-linux/user").is_dir() {
base_dir.join("gow3-linux/user/cache")
} else {
base_dir.join("user/cache")
}
}
/// Counts total pre-compiled shader and pipeline key files for God of War III.
pub fn count_cached_shaders(base_dir: &Path) -> usize {
let cache_dir = resolve_cache_dir(base_dir);
let primary = cache_dir.join("CUSA01715");
let fallback = cache_dir.join("CUSA01623");
let target = if primary.is_dir() {
primary
} else if fallback.is_dir() {
fallback
} else {
return 0;
};
let mut count = 0;
if let Ok(entries) = fs::read_dir(target) {
for e in entries.flatten() {
let p = e.path();
if p.extension().map_or(false, |ext| ext == "spv" || ext == "meta" || ext == "key") {
count += 1;
}
}
}
count
}
/// Finds a local pre-built shader archive if present in the repo or parent dirs.
pub fn find_local_shader_archive(base_dir: &Path) -> Option<PathBuf> {
let candidates = [
base_dir.join("shaders/gow3-shader-cache.tar.xz"),
base_dir.join("../shaders/gow3-shader-cache.tar.xz"),
];
for c in &candidates {
if c.is_file() {
return Some(c.clone());
}
}
if let Some(parent) = base_dir.parent() {
let p = parent.join("shaders/gow3-shader-cache.tar.xz");
if p.is_file() {
return Some(p);
}
}
None
}
/// Downloads and extracts the official complete shader cache.
/// Sets up cross-title compatibility for both CUSA01715 and CUSA01623.
pub fn download_and_install_shaders(base_dir: &Path) -> Result<usize, String> {
let cache_dir = resolve_cache_dir(base_dir);
let _ = fs::create_dir_all(&cache_dir);
// 1. Check for local shader archive first (avoids unnecessary re-download)
let archive_path = if let Some(local_path) = find_local_shader_archive(base_dir) {
local_path
} else {
let temp_archive = cache_dir.join("gow3-shader-cache.tar.xz.tmp");
let _ = fs::remove_file(&temp_archive);
http_download_with_fallback(SHADER_REPO_URL, SHADER_FALLBACK_URL, &temp_archive)?;
temp_archive
};
// 2. Extract archive into cache directory
let extract_res = extract_tar_xz(&archive_path, &cache_dir);
// If a temporary download file was used, clean it up
if archive_path.to_string_lossy().ends_with(".tmp") {
let _ = fs::remove_file(&archive_path);
}
extract_res?;
// 3. Ensure cross-compatibility between CUSA01715 (EU/TR) and CUSA01623 (US)
link_regional_caches(&cache_dir);
let final_count = count_cached_shaders(base_dir);
Ok(final_count)
}
fn http_download(url: &str, dest: &Path) -> Result<(), String> {
// 1. Try curl (available on Linux, macOS, and modern Windows 10+)
if let Ok(out) = Command::new("curl")
.args(["-fsSL", "-H", "User-Agent: gow3-launcher", url, "-o", dest.to_str().unwrap_or("")])
.output()
{
if out.status.success() && dest.is_file() {
if let Ok(meta) = fs::metadata(dest) {
if meta.len() > 1024 {
return Ok(());
}
}
}
}
// 2. Fallback to PowerShell on Windows
#[cfg(target_os = "windows")]
{
let ps_code = format!(
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \
$wc = New-Object System.Net.WebClient; \
$wc.Headers.Add('User-Agent', 'gow3-launcher'); \
$wc.DownloadFile('{}', '{}')",
url.replace('\'', "''"),
dest.to_string_lossy().replace('\'', "''")
);
if let Ok(out) = Command::new("powershell")
.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_code])
.output()
{
if out.status.success() && dest.is_file() {
if let Ok(meta) = fs::metadata(dest) {
if meta.len() > 1024 {
return Ok(());
}
}
}
}
}
Err(format!("Dosya indirilemedi: {}", url))
}
fn http_download_with_fallback(primary_url: &str, fallback_url: &str, dest: &Path) -> Result<(), String> {
if http_download(primary_url, dest).is_ok() {
return Ok(());
}
http_download(fallback_url, dest)
}
fn extract_tar_xz(archive: &Path, dest_dir: &Path) -> Result<(), String> {
let _ = fs::create_dir_all(dest_dir);
// 1. Try system tar (available on Linux, macOS, and modern Windows 10/11 tar.exe)
if let Ok(st) = Command::new("tar")
.args(["-xf", archive.to_str().unwrap_or(""), "-C", dest_dir.to_str().unwrap_or("")])
.status()
{
if st.success() {
return Ok(());
}
}
// 2. Try 7z fallback (if user has 7z installed)
if let Ok(st) = Command::new("7z")
.args([
"x",
archive.to_str().unwrap_or(""),
&format!("-o{}", dest_dir.to_str().unwrap_or("")),
"-y",
])
.status()
{
if st.success() {
return Ok(());
}
}
Err("Shader arşivi açılamadı (tar veya 7z aracı bulunamadı).".to_string())
}
/// Links CUSA01715 and CUSA01623 caches so both versions share the exact same shaders without duplication.
pub fn link_regional_caches(cache_dir: &Path) {
let cusa1715 = cache_dir.join("CUSA01715");
let cusa1623 = cache_dir.join("CUSA01623");
if cusa1715.is_dir() && !cusa1623.exists() {
#[cfg(unix)]
{
let _ = std::os::unix::fs::symlink("CUSA01715", &cusa1623);
}
#[cfg(windows)]
{
let _ = Command::new("cmd")
.args(["/C", "mklink", "/J", cusa1623.to_str().unwrap_or(""), cusa1715.to_str().unwrap_or("")])
.output();
}
} else if cusa1623.is_dir() && !cusa1715.exists() {
#[cfg(unix)]
{
let _ = std::os::unix::fs::symlink("CUSA01623", &cusa1715);
}
#[cfg(windows)]
{
let _ = Command::new("cmd")
.args(["/C", "mklink", "/J", cusa1715.to_str().unwrap_or(""), cusa1623.to_str().unwrap_or("")])
.output();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_count_cached_shaders() {
let temp_dir = std::env::temp_dir().join("gow3_test_shader_count");
let cache = temp_dir.join("user/cache/CUSA01715");
let _ = fs::create_dir_all(&cache);
fs::write(cache.join("0x123.spv"), b"spv").unwrap();
fs::write(cache.join("0x123.meta"), b"meta").unwrap();
fs::write(cache.join("0x123.key"), b"key").unwrap();
fs::write(cache.join("ignore.txt"), b"txt").unwrap();
let count = count_cached_shaders(&temp_dir);
assert_eq!(count, 3);
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_link_regional_caches() {
let temp_dir = std::env::temp_dir().join("gow3_test_shader_link");
let cache = temp_dir.join("user/cache");
let cusa1715 = cache.join("CUSA01715");
let _ = fs::create_dir_all(&cusa1715);
fs::write(cusa1715.join("test.spv"), b"spv").unwrap();
link_regional_caches(&cache);
let cusa1623 = cache.join("CUSA01623");
assert!(cusa1623.exists());
let count = count_cached_shaders(&temp_dir);
assert_eq!(count, 1);
let _ = fs::remove_dir_all(&temp_dir);
}
}
+302
View File
@@ -3,19 +3,135 @@ use std::fs;
use std::process::Command;
use crate::config::CachedHardware;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuGrade {
Weak, // Zayıf / Darboğaz Riski (Kırmızı)
Medium, // Orta Seviye (40-60 FPS) (Sarı/Amber)
Good, // Güçlü (60 FPS İdeal) (Açık Yeşil)
Ultra, // Üst Seviye (60-144 FPS) (Parlak Yeşil)
}
impl CpuGrade {
pub fn badge_text(&self, lang: &str) -> &'static str {
match self {
CpuGrade::Weak => match lang {
"tr" => "• Zayıf / Darboğaz Riski",
"de" => "• Schwach / Bottleneck-Risiko",
"es" => "• Débil / Riesgo Cuello de Botella",
_ => "• Weak / Bottleneck Risk",
},
CpuGrade::Medium => match lang {
"tr" => "• Orta Seviye (40-60 FPS)",
"de" => "• Mittelstufe (40-60 FPS)",
"es" => "• Nivel Medio (40-60 FPS)",
_ => "• Mid-Range (40-60 FPS)",
},
CpuGrade::Good => match lang {
"tr" => "• Güçlü (60 FPS İdeal)",
"de" => "• Stark (60 FPS Ideal)",
"es" => "• Fuerte (Ideal 60 FPS)",
_ => "• Strong (60 FPS Ideal)",
},
CpuGrade::Ultra => match lang {
"tr" => "• Üst Seviye (Kusursuz)",
"de" => "• Spitzenklasse (Perfekt)",
"es" => "• Gama Alta (Perfecto)",
_ => "• High-End (Flawless)",
},
}
}
pub fn rgb(&self) -> (u8, u8, u8) {
match self {
CpuGrade::Weak => (239, 83, 80),
CpuGrade::Medium => (255, 213, 79),
CpuGrade::Good => (130, 225, 140),
CpuGrade::Ultra => (0, 230, 118),
}
}
}
pub fn evaluate_cpu(name: &str, cores: usize, threads: usize) -> CpuGrade {
let lower = name.to_lowercase();
// 1. Obsolete or severely underpowered architectures
if lower.contains("celeron")
|| lower.contains("pentium")
|| lower.contains("core 2")
|| lower.contains("core(tm)2")
|| lower.contains("athlon")
|| lower.contains("sempron")
|| lower.contains("phenom")
|| lower.contains("fx-")
|| lower.contains("a10-")
|| lower.contains("a8-")
|| lower.contains("a6-")
|| lower.contains("a4-")
{
return CpuGrade::Weak;
}
// 2. Thread count constraints
if threads <= 4 {
// Any 2-core or 4-core/4-thread CPU (e.g. i5-4460, i5-6500, older i3) struggles with PS4 emulation threads
return CpuGrade::Weak;
}
// 3. Medium tier (4 cores / 8 threads or 6 cores / 6 threads)
// Or older 4-core HEDT / 6-core Zen 1
if threads <= 8 || cores <= 4 {
return CpuGrade::Medium;
}
// 4. Good vs Ultra
if threads >= 16 || cores >= 8 {
CpuGrade::Ultra
} else if threads >= 12 || cores >= 6 {
CpuGrade::Good
} else {
CpuGrade::Medium
}
}
pub fn clean_cpu_name(name: &str) -> String {
let mut cleaned = name
.replace("(R)", "")
.replace("(TM)", "")
.replace(" CPU", "");
if let Some(pos) = cleaned.find("-Core") {
if let Some(start) = cleaned[..pos].rfind(' ') {
cleaned.drain(start..);
}
}
let parts: Vec<&str> = cleaned.split_whitespace().collect();
parts.join(" ")
}
pub fn detect_hardware() -> CachedHardware {
let (gpu_name, vram_mb) = detect_gpu();
let ram_mb = detect_ram();
let refresh_rate_hz = detect_refresh_rate();
let (cpu_name, cpu_cores, cpu_threads) = detect_cpu();
CachedHardware {
gpu_name,
vram_mb,
ram_mb,
refresh_rate_hz,
cpu_name,
cpu_cores,
cpu_threads,
}
}
pub fn detect_cpu_threads() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
}
pub fn is_gamemode_available() -> bool {
#[cfg(target_os = "windows")]
{
@@ -76,6 +192,47 @@ fn detect_gpu() -> (String, u64) {
("Bilinmeyen GPU (Genel)".to_string(), 4096)
}
#[cfg(not(target_os = "windows"))]
pub fn detect_has_discrete_nvidia() -> bool {
if let Ok(output) = Command::new("lspci").output() {
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if (line.contains("VGA compatible controller") || line.contains("3D controller"))
&& line.to_lowercase().contains("nvidia")
{
return true;
}
}
}
}
false
}
#[cfg(not(target_os = "windows"))]
pub fn detect_has_discrete_amd() -> bool {
if let Ok(output) = Command::new("lspci").output() {
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
let mut has_intel = false;
let mut has_amd = false;
for line in text.lines() {
if line.contains("VGA compatible controller") || line.contains("3D controller") {
let lower = line.to_lowercase();
if lower.contains("intel") {
has_intel = true;
}
if lower.contains("amd") || lower.contains("advanced micro devices") || lower.contains("ati") {
has_amd = true;
}
}
}
return has_intel && has_amd;
}
}
false
}
#[cfg(not(target_os = "windows"))]
fn glob_vram() -> Result<Option<(String, u64)>, std::io::Error> {
for card_idx in 0..8 {
@@ -177,6 +334,59 @@ fn detect_refresh_rate() -> u32 {
rates.into_iter().max().unwrap_or(60)
}
#[cfg(not(target_os = "windows"))]
pub fn detect_cpu() -> (String, usize, usize) {
let threads = detect_cpu_threads();
let mut name = String::new();
let mut cores = 0;
if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
for line in content.lines() {
if name.is_empty() && (line.starts_with("model name") || line.starts_with("Hardware")) {
if let Some((_, n)) = line.split_once(':') {
name = n.trim().to_string();
}
}
if cores == 0 && line.starts_with("cpu cores") {
if let Some((_, c)) = line.split_once(':') {
if let Ok(val) = c.trim().parse::<usize>() {
cores = val;
}
}
}
if !name.is_empty() && cores > 0 {
break;
}
}
}
if name.is_empty() {
if let Ok(output) = Command::new("lscpu").output() {
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if line.starts_with("Model name:") {
if let Some((_, n)) = line.split_once(':') {
name = n.trim().to_string();
break;
}
}
}
}
}
}
if name.is_empty() {
name = "Linux x86_64 İşlemci".to_string();
}
if cores == 0 {
cores = if threads > 1 { threads / 2 } else { 1 };
}
(name, cores, threads)
}
#[cfg(target_os = "windows")]
fn detect_gpu() -> (String, u64) {
// 1. Try nvidia-smi first (exact VRAM for NVIDIA GPUs, e.g. RTX 4070 Laptop 8GB)
@@ -293,3 +503,95 @@ fn detect_refresh_rate() -> u32 {
}
60
}
#[cfg(target_os = "windows")]
#[allow(dead_code)]
pub fn detect_has_discrete_nvidia() -> bool {
false
}
#[cfg(target_os = "windows")]
#[allow(dead_code)]
pub fn detect_has_discrete_amd() -> bool {
false
}
#[cfg(target_os = "windows")]
pub fn detect_cpu() -> (String, usize, usize) {
let threads = detect_cpu_threads();
let mut name = "Windows x86_64 İşlemci".to_string();
let mut cores = if threads > 1 { threads / 2 } else { 1 };
let ps_cmd = "Get-CimInstance Win32_Processor | Select-Object -First 1 -Property Name,NumberOfCores,NumberOfLogicalProcessors | ConvertTo-Json -Compress";
if let Ok(output) = Command::new("powershell")
.args(["-NoProfile", "-Command", ps_cmd])
.output()
{
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
if let Ok(val) = serde_json::from_str::<serde_json::Value>(text.trim()) {
if let Some(n) = val["Name"].as_str() {
name = n.trim().to_string();
}
if let Some(c) = val["NumberOfCores"].as_u64() {
cores = c as usize;
}
let detected_threads = val["NumberOfLogicalProcessors"].as_u64().unwrap_or(threads as u64) as usize;
return (name, cores, detected_threads.max(threads));
}
}
}
(name, cores, threads)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clean_cpu_name() {
assert_eq!(
clean_cpu_name("12th Gen Intel(R) Core(TM) i7-12700H"),
"12th Gen Intel Core i7-12700H"
);
assert_eq!(
clean_cpu_name("AMD Ryzen 7 5800X 8-Core Processor"),
"AMD Ryzen 7 5800X"
);
assert_eq!(
clean_cpu_name("Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz"),
"Intel Core i5-4460 @ 3.20GHz"
);
}
#[test]
fn test_evaluate_cpu() {
// Low-end / Bottleneck CPUs
assert_eq!(evaluate_cpu("Intel Core i5-4460", 4, 4), CpuGrade::Weak);
assert_eq!(evaluate_cpu("Intel Core i3-4130", 2, 4), CpuGrade::Weak);
assert_eq!(evaluate_cpu("AMD FX-8350 Eight-Core", 8, 8), CpuGrade::Weak);
assert_eq!(evaluate_cpu("Intel Pentium G4560", 2, 4), CpuGrade::Weak);
// Medium tier CPUs
assert_eq!(evaluate_cpu("Intel Core i7-4790K", 4, 8), CpuGrade::Medium);
assert_eq!(evaluate_cpu("AMD Ryzen 5 1600", 6, 6), CpuGrade::Medium);
// Good tier CPUs
assert_eq!(evaluate_cpu("AMD Ryzen 5 5600", 6, 12), CpuGrade::Good);
assert_eq!(evaluate_cpu("Intel Core i5-12400", 6, 12), CpuGrade::Good);
// Ultra tier CPUs
assert_eq!(evaluate_cpu("12th Gen Intel Core i7-12700H", 14, 20), CpuGrade::Ultra);
assert_eq!(evaluate_cpu("AMD Ryzen 7 7800X3D", 8, 16), CpuGrade::Ultra);
}
#[test]
fn test_detect_cpu_host() {
let (name, cores, threads) = detect_cpu();
assert!(!name.is_empty());
assert!(cores > 0);
assert!(threads > 0);
println!("Detected CPU: {} ({}C / {}T)", name, cores, threads);
}
}
File diff suppressed because it is too large. Load diff
+256 -15
View File
@@ -34,13 +34,16 @@ fn update_xml_patches(base_dir: &Path, target_hz: u32) -> Result<(), String> {
if let Ok(content) = fs::read_to_string(path) {
let mut new_lines = Vec::new();
for line in content.lines() {
let low = line.to_lowercase();
if (line.contains("Delta-Time")
|| line.contains("120 FPS")
|| line.contains("144 FPS")
|| line.contains("165 FPS")
|| line.contains("240 FPS")
|| line.contains("60 FPS"))
&& line.contains("value=\"")
&& low.contains("value=\"")
{
if let Some(start) = line.find("value=\"") {
if let Some(start) = low.find("value=\"") {
let prefix = &line[..start + 7];
if let Some(end) = line[start + 7..].find('\"') {
let suffix = &line[start + 7 + end..];
@@ -67,21 +70,119 @@ fn update_run_sh(base_dir: &Path, config: &LauncherConfig) -> Result<(), String>
let mut content = fs::read_to_string(&run_sh).map_err(|e| e.to_string())?;
let patches_str = if config.target_fps != 60 {
format!(
"Bug Fix - Texture Corruption Fix;Skip Any Video With X Button;Frame Rate Patch - {} FPS",
config.target_fps
)
} else {
"Bug Fix - Texture Corruption Fix;Skip Any Video With X Button".to_string()
let mut patches = Vec::new();
let res_patch = match config.render_resolution.as_str() {
"480p" => Some("Resolution Patch - 480p"),
"720p" => Some("Resolution Patch - 720p"),
"800p" => Some("Resolution Patch - 800p"),
"1440p" => Some("Resolution Patch - 1440p"),
"1800p" => Some("Resolution Patch - 1800p"),
"4K" => Some("Resolution Patch - 4K"),
_ => None,
};
if let Some(p) = res_patch {
patches.push(p.to_string());
} else {
patches.push("Bug Fix - Texture Corruption Fix".to_string());
}
patches.push("Bug Fix - Startup Black Screen".to_string());
patches.push("Bug Fix - Intro Crash Fix".to_string());
patches.push("Skip Any Video With X Button".to_string());
if config.patch_delta_time {
patches.push(format!("Frame Rate Patch - {} FPS", config.target_fps));
}
if config.disable_motion_blur {
patches.push("Graphics - Disable Motion Blur".to_string());
}
if config.disable_dof {
patches.push("Graphics - Disable Depth of Field".to_string());
}
let patches_str = patches.join(";");
replace_or_insert_env(&mut content, "GOW3_GC_BUDGET_MB", &config.vram_gc_budget_mb.to_string());
if let Some(res_env) = config.get_render_res_env_value() {
let (internal_w, internal_h) = config.get_resolution_dimensions();
replace_or_insert_env(&mut content, "GOW3_RENDER_RES", &res_env);
replace_or_insert_env(&mut content, "GOW3_INTERNAL_WIDTH", &internal_w.to_string());
replace_or_insert_env(&mut content, "GOW3_INTERNAL_HEIGHT", &internal_h.to_string());
} else {
remove_env_if_present(&mut content, "GOW3_RENDER_RES");
remove_env_if_present(&mut content, "GOW3_INTERNAL_WIDTH");
remove_env_if_present(&mut content, "GOW3_INTERNAL_HEIGHT");
}
if config.fsr_enabled && config.render_resolution != "native" {
replace_or_insert_env(&mut content, "GOW3_UPSCALER", "fsr");
replace_or_insert_env(&mut content, "GOW3_FSR1", "1");
replace_or_insert_env(&mut content, "GOW3_RCAS", "1");
replace_or_insert_env(&mut content, "GOW3_FSR_SHARPNESS", "0.8");
} else {
replace_or_insert_env(&mut content, "GOW3_UPSCALER", "none");
replace_or_insert_env(&mut content, "GOW3_FSR1", "0");
replace_or_insert_env(&mut content, "GOW3_RCAS", "0");
replace_or_insert_env(&mut content, "GOW3_FSR_SHARPNESS", "0.0");
}
replace_or_insert_env(&mut content, "GOW3_OBJECT_MOTION", if config.disable_motion_blur { "0" } else { "1" });
let effective_budget = config.get_effective_vram_budget();
replace_or_insert_env(&mut content, "GOW3_GC_BUDGET_MB", &effective_budget.to_string());
replace_or_insert_env(&mut content, "GOW3_DMEM_POOL_MB", &config.dmem_mb.to_string());
replace_or_insert_env(&mut content, "GOW3_DMEM_MB", &config.dmem_mb.to_string());
replace_or_insert_env(&mut content, "GOW3_DEFAULT_ENGINE_FPS", &config.target_fps.to_string());
replace_or_insert_env(&mut content, "GOW3_FORCE_READBACK", if config.patch_readbacks { "1" } else { "0" });
replace_or_insert_env(&mut content, "GOW3_READBACKS", if config.patch_readbacks { "1" } else { "0" });
replace_or_insert_env(&mut content, "GOW3_READBACK_LINEAR", if config.patch_readbacks { "1" } else { "0" });
replace_or_insert_env(&mut content, "GOW3_DEFERRED_READBACK", if config.async_readbacks { "1" } else { "0" });
replace_or_insert_env(&mut content, "GOW3_STALE_READBACK", if config.async_readbacks { "1" } else { "0" });
remove_env_if_present(&mut content, "GOW3_COPY_GPU_BUFFERS");
let cpu_threads = if config.cached_hardware.cpu_threads > 0 {
config.cached_hardware.cpu_threads
} else {
crate::system::detect_cpu_threads()
};
let (prep_workers, copy_threads) = if cpu_threads <= 4 {
(2, 1)
} else if cpu_threads <= 8 {
(4, 2)
} else if cpu_threads <= 12 {
(6, 4)
} else {
(8, 4)
};
replace_or_insert_env(&mut content, "GOW3_PREP_WORKERS", &prep_workers.to_string());
replace_or_insert_env(&mut content, "GOW3_COPY_THREADS", &copy_threads.to_string());
replace_or_insert_env(&mut content, "GOW3_INTERP_INT_FIX", "1");
replace_or_insert_env(&mut content, "GOW3_DIRECT_MEMORY_ACCESS", "1");
replace_or_insert_env(&mut content, "GOW3_PIPELINE_CACHE", "1");
replace_or_insert_env(&mut content, "GOW3_PIPELINE_BINARIES", "1");
replace_or_insert_env(&mut content, "GOW3_PARALLEL_WARMUP", "1");
replace_or_insert_env(&mut content, "GOW3_BACKGROUND_WARMUP", "0");
replace_or_insert_env(&mut content, "GOW3_VK_RECORD_THREAD", "1");
replace_or_insert_env(&mut content, "GOW3_PRESENT_THREAD", "1");
replace_or_insert_env(&mut content, "GOW3_ASYNC_FENCES", "1");
replace_or_insert_env(&mut content, "GOW3_ASYNC_SHADERS", "1");
replace_or_insert_env(&mut content, "GOW3_FPS_LIMIT", &config.target_fps.to_string());
// Clean up any deprecated experimental env vars from earlier launches
remove_env_if_present(&mut content, "GOW3_STAGING_KEEP_MB");
remove_env_if_present(&mut content, "GOW3_COPY_MERGE_KB");
remove_env_if_present(&mut content, "GOW3_HOT_PAGES");
remove_env_if_present(&mut content, "GOW3_TEXTURE_HELPER");
remove_env_if_present(&mut content, "GOW3_DRAW_PIPE");
remove_env_if_present(&mut content, "__GL_YIELD");
remove_env_if_present(&mut content, "GOW3_FRAMES_AHEAD");
remove_env_if_present(&mut content, "radv_zero_vram");
// Driver Shader Disk Cache & Multithreaded Optimizations (10GB disk cache)
replace_or_insert_env(&mut content, "__GL_SHADER_DISK_CACHE", "1");
replace_or_insert_env(&mut content, "__GL_SHADER_DISK_CACHE_SIZE", "10737418240");
replace_or_insert_env(&mut content, "__GL_SHADER_DISK_CACHE_SKIP_CLEANUP", "1");
replace_or_insert_env(&mut content, "__GL_THREADED_OPTIMIZATIONS", "1");
replace_or_insert_env(&mut content, "__GL_VRR_ALLOWED", "1");
replace_or_insert_env(&mut content, "__GL_MaxFramesAllowed", "1");
replace_or_insert_env(&mut content, "MESA_SHADER_CACHE_MAX_SIZE", "10G");
replace_or_insert_env(&mut content, "GOW3_ASYNC_SHADERS", "1");
replace_or_insert_env(&mut content, "GOW3_FPS_LIMIT", &config.target_fps.to_string());
replace_or_insert_env(&mut content, "GOW3_FULL_BARRIERS", "1");
replace_or_insert_env(&mut content, "GOW3_FORCE_PATCHES", "1");
replace_or_insert_env(&mut content, "GOW3_PREBUILT", "1");
@@ -99,12 +200,33 @@ fn update_run_sh(base_dir: &Path, config: &LauncherConfig) -> Result<(), String>
let pad_map = config.build_gow3_pad_map();
replace_or_insert_env(&mut content, "GOW3_KEY_MAP", &key_map);
replace_or_insert_env(&mut content, "GOW3_PAD_MAP", pad_map);
replace_or_insert_env(
&mut content,
"GOW3_HIDE_CURSOR",
if config.hide_mouse_cursor { "1" } else { "0" },
);
remove_env_if_present(&mut content, "LD_PRELOAD");
if config.hide_mouse_cursor || config.is_mouse_button_bound() {
let input_lib = base_dir.join("bin/lib/libgow3input.so");
if input_lib.is_file() {
let safe_lib = crate::runner::ensure_safe_preload_lib(base_dir).unwrap_or(input_lib);
replace_or_insert_env(&mut content, "LD_PRELOAD", &safe_lib.to_string_lossy());
}
}
replace_or_insert_env(
&mut content,
"SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS",
if config.background_controller_input { "1" } else { "0" },
);
let icon_bmp = base_dir.join("gow3.bmp");
let icon_png = base_dir.join("gow3.png");
if icon_bmp.is_file() {
replace_or_insert_env(&mut content, "GOW3_WINDOW_ICON", &icon_bmp.to_string_lossy());
} else if icon_png.is_file() {
replace_or_insert_env(&mut content, "GOW3_WINDOW_ICON", &icon_png.to_string_lossy());
}
fs::write(&run_sh, content).map_err(|e| e.to_string())?;
Ok(())
}
@@ -132,6 +254,20 @@ fn replace_or_insert_env(content: &mut String, var_name: &str, value: &str) {
}
}
fn remove_env_if_present(content: &mut String, var_name: &str) {
let pattern_quote = format!("export {}=\"", var_name);
let pattern_noquote = format!("export {}=", var_name);
while let Some(idx) = content.find(&pattern_quote).or_else(|| content.find(&pattern_noquote)) {
if let Some(end) = content[idx..].find('\n') {
content.drain(idx..idx + end + 1);
} else {
content.truncate(idx);
break;
}
}
}
fn update_gow3_ini(base_dir: &Path, config: &LauncherConfig) -> Result<(), String> {
let ini_path = base_dir.join("gow3.ini");
let mut content = if ini_path.is_file() {
@@ -149,7 +285,7 @@ fn update_gow3_ini(base_dir: &Path, config: &LauncherConfig) -> Result<(), Strin
deferred_readback=0\n\
stale_readback=0\n\
async_shaders=1\n\
frames_queued=1\n\
frames_queued=2\n\
readbacks=true\n\
readbackLinearImages=true\n\
budget=6144\n".to_string()
@@ -158,11 +294,37 @@ fn update_gow3_ini(base_dir: &Path, config: &LauncherConfig) -> Result<(), Strin
let readbacks_val = if config.patch_readbacks { "true" } else { "false" };
replace_ini_key(&mut content, "readbacks", readbacks_val);
replace_ini_key(&mut content, "readbackLinearImages", readbacks_val);
replace_ini_key(&mut content, "stale_readback", "0");
replace_ini_key(&mut content, "budget", &config.vram_gc_budget_mb.to_string());
replace_ini_key(&mut content, "deferred_readback", if config.async_readbacks { "1" } else { "0" });
replace_ini_key(&mut content, "stale_readback", if config.async_readbacks { "1" } else { "0" });
replace_ini_key(&mut content, "async_shaders", "1");
if config.render_resolution == "native" {
replace_ini_key(&mut content, "render_resolution", "native");
replace_ini_key(&mut content, "internal_width", "1920");
replace_ini_key(&mut content, "internal_height", "1080");
replace_ini_key(&mut content, "upscaler", "off");
replace_ini_key(&mut content, "fsr1", "0");
replace_ini_key(&mut content, "rcas", "0");
replace_ini_key(&mut content, "sharpen", "0");
replace_ini_key(&mut content, "sharpness", "0.00");
} else {
let (internal_w, internal_h) = config.get_resolution_dimensions();
replace_ini_key(&mut content, "render_resolution", &config.render_resolution);
replace_ini_key(&mut content, "internal_width", &internal_w.to_string());
replace_ini_key(&mut content, "internal_height", &internal_h.to_string());
replace_ini_key(&mut content, "upscaler", if config.fsr_enabled { "fsr" } else { "off" });
replace_ini_key(&mut content, "fsr1", if config.fsr_enabled { "1" } else { "0" });
replace_ini_key(&mut content, "rcas", if config.fsr_enabled { "1" } else { "0" });
replace_ini_key(&mut content, "sharpen", if config.fsr_enabled { "1" } else { "0" });
replace_ini_key(&mut content, "sharpness", if config.fsr_enabled { "0.80" } else { "0.00" });
}
replace_ini_key(&mut content, "rcas_strength", "250");
replace_ini_key(&mut content, "object_motion", if config.disable_motion_blur { "0" } else { "1" });
let effective_budget = config.get_effective_vram_budget();
replace_ini_key(&mut content, "budget", &effective_budget.to_string());
replace_ini_key(&mut content, "engine_fps", &config.target_fps.to_string());
replace_ini_key(&mut content, "fps_limit", &config.target_fps.to_string());
replace_ini_key(&mut content, "frames_queued", "1");
replace_ini_key(&mut content, "frames_queued", "2");
replace_ini_key(&mut content, "vsync", if config.vsync { "1" } else { "0" });
replace_ini_key(&mut content, "display_mode", config.display_mode.as_str());
@@ -254,7 +416,15 @@ fn update_user_config_toml(base_dir: &Path, config: &LauncherConfig) -> Result<(
if let Some(end) = content[idx..].find('\n') {
let prefix = &content[..idx];
let suffix = &content[idx + end..];
content = format!("{}vulkan_gc_budget = {}{}", prefix, config.vram_gc_budget_mb, suffix);
let effective_budget = config.get_effective_vram_budget();
content = format!("{}vulkan_gc_budget = {}{}", prefix, effective_budget, suffix);
}
}
// Ensure presentMode is removed so Vulkan uses default FIFO swapchain pacing (eliminating logo black screen blinks)
if let Some(idx) = content.find("presentMode =") {
if let Some(end) = content[idx..].find('\n') {
content.replace_range(idx..=idx + end, "");
}
}
@@ -272,6 +442,77 @@ fn update_user_config_toml(base_dir: &Path, config: &LauncherConfig) -> Result<(
"FullscreenMode",
&format!("\"{}\"", fullscreen_mode_str),
);
let (internal_w, internal_h) = match config.render_resolution.as_str() {
"480p" => (854, 480),
"720p" => (1280, 720),
"800p" => (1280, 800),
"1440p" => (2560, 1440),
"1800p" => (3200, 1800),
"4K" => (3840, 2160),
_ => (1920, 1080),
};
update_toml_key_in_section(
&mut content,
"GPU",
"internalScreenWidth",
&internal_w.to_string(),
);
update_toml_key_in_section(
&mut content,
"GPU",
"internalScreenHeight",
&internal_h.to_string(),
);
update_toml_key_in_section(
&mut content,
"GPU",
"fsrEnabled",
if config.fsr_enabled { "true" } else { "false" },
);
update_toml_key_in_section(
&mut content,
"GPU",
"rcasEnabled",
if config.fsr_enabled { "true" } else { "false" },
);
update_toml_key_in_section(
&mut content,
"GPU",
"vblankFrequency",
&config.target_fps.to_string(),
);
update_toml_key_in_section(
&mut content,
"GPU",
"directMemoryAccess",
"true",
);
update_toml_key_in_section(
&mut content,
"GPU",
"readbacks",
if config.patch_readbacks { "true" } else { "false" },
);
update_toml_key_in_section(
&mut content,
"GPU",
"readbackLinearImages",
if config.patch_readbacks { "true" } else { "false" },
);
// Update Vulkan settings
update_toml_key_in_section(
&mut content,
"Vulkan",
"pipelineCacheEnable",
"true",
);
update_toml_key_in_section(
&mut content,
"Vulkan",
"pipelineCacheArchive",
"true",
);
// Update Input settings
update_toml_key_in_section(
+263 -15
View File
@@ -5,15 +5,119 @@ use std::process::Command;
use crate::config::LauncherConfig;
use crate::tuner::apply_settings;
pub const BUILTIN_VERSION: &str = "v2.2";
pub const GITHUB_REPO: &str = "ScreamAngel/gow3_launcher";
pub const GITHUB_REPO_URL: &str = "https://github.com/ScreamAngel/gow3_launcher.git";
#[derive(Debug, Clone)]
pub struct GithubReleaseNote {
pub tag_name: String,
pub title: String,
pub published_at: String,
pub body: String,
}
pub fn fetch_github_release_notes() -> Result<Vec<GithubReleaseNote>, String> {
let url = "https://api.github.com/repos/ScreamAngel/gow3_launcher/releases";
let body = http_get_string(url).ok_or_else(|| "GitHub Releases bağlantısı kurulamadı.".to_string())?;
let val: serde_json::Value = serde_json::from_str(&body).map_err(|e| format!("JSON parse hatası: {}", e))?;
let mut notes = Vec::new();
if let Some(arr) = val.as_array() {
for item in arr {
let tag = item["tag_name"].as_str().unwrap_or("").to_string();
let title = item["name"].as_str().unwrap_or("").to_string();
let published_at = item["published_at"].as_str().unwrap_or("").to_string();
let body_text = item["body"].as_str().unwrap_or("").to_string();
if !tag.is_empty() {
notes.push(GithubReleaseNote {
tag_name: tag,
title: if title.is_empty() { "Sürüm Notu".to_string() } else { title },
published_at,
body: body_text,
});
}
}
}
Ok(notes)
}
#[derive(Debug, Clone)]
pub struct UpdateInfo {
pub commit_sha: String,
pub commit_message: String,
pub commit_date: String,
pub is_new: bool,
pub version_tag: Option<String>,
pub has_missing_files: bool,
pub missing_files: Vec<String>,
}
pub fn parse_version_numbers(v: &str) -> Vec<u32> {
v.trim_start_matches(|c: char| c == 'v' || c == 'V')
.split('.')
.filter_map(|s| {
let num_str: String = s.chars().take_while(|c| c.is_ascii_digit()).collect();
num_str.parse::<u32>().ok()
})
.collect()
}
pub fn compare_version_strings(v1: &str, v2: &str) -> std::cmp::Ordering {
let p1 = parse_version_numbers(v1);
let p2 = parse_version_numbers(v2);
let max_len = p1.len().max(p2.len());
for i in 0..max_len {
let n1 = p1.get(i).copied().unwrap_or(0);
let n2 = p2.get(i).copied().unwrap_or(0);
match n1.cmp(&n2) {
std::cmp::Ordering::Equal => continue,
other => return other,
}
}
std::cmp::Ordering::Equal
}
pub fn detect_launcher_version(base_dir: &Path, config: &LauncherConfig) -> String {
// 1. If in a git repository and git is installed, ask git for the exact tag
if base_dir.join(".git").is_dir() && is_git_available() {
if let Ok(output) = Command::new("git")
.args(["describe", "--tags", "--abbrev=0"])
.current_dir(base_dir)
.output()
{
if output.status.success() {
let tag = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !tag.is_empty() {
let formatted = if tag.starts_with('v') || tag.starts_with('V') {
tag
} else {
format!("v{}", tag)
};
return formatted;
}
}
}
}
// 2. If config has a recorded launcher_version and it's valid
if !config.launcher_version.trim().is_empty() {
let cv = config.launcher_version.trim();
let formatted = if cv.starts_with('v') || cv.starts_with('V') {
cv.to_string()
} else {
format!("v{}", cv)
};
if compare_version_strings(&formatted, BUILTIN_VERSION) >= std::cmp::Ordering::Equal {
return formatted;
}
}
// 3. Fallback to built-in compiled version
BUILTIN_VERSION.to_string()
}
fn is_git_available() -> bool {
@@ -95,29 +199,83 @@ fn http_download_file(url: &str, dest: &Path) -> Result<(), String> {
Err("Dosya indirilemedi.".to_string())
}
pub fn check_github_update(current_commit: &str) -> Result<UpdateInfo, String> {
let api_url = format!("https://api.github.com/repos/{}/commits/HEAD", GITHUB_REPO);
pub fn check_installation_integrity(base_dir: &Path) -> Vec<String> {
let mut missing = Vec::new();
// 1. Icon file
if !base_dir.join("gow3.png").is_file() {
missing.push("gow3.png".to_string());
}
// 2. Patches XML
let has_patch = base_dir.join("patches/God_of_War_III_Remastered.xml").is_file()
|| base_dir.join(".runtime/patches/God_of_War_III_Remastered.xml").is_file();
if !has_patch {
missing.push("patches/God_of_War_III_Remastered.xml".to_string());
}
// 3. Auto tune script
let has_script = base_dir.join("scripts/auto_tune.py").is_file()
|| base_dir.join(".runtime/scripts/auto_tune.py").is_file();
if !has_script {
missing.push("scripts/auto_tune.py".to_string());
}
missing
}
pub fn check_github_update(
base_dir: &Path,
_current_commit: &str,
current_version: &str,
) -> Result<UpdateInfo, String> {
crate::log_info!("Checking updates for running launcher {} (repo: {})...", current_version, GITHUB_REPO);
let release_api_url = format!("https://api.github.com/repos/{}/releases/latest", GITHUB_REPO);
let commit_api_url = format!("https://api.github.com/repos/{}/commits/HEAD", GITHUB_REPO);
let mut remote_sha = String::new();
let mut commit_message = "En son optimizasyonlar ve güncellemeler".to_string();
let mut commit_date = String::new();
let mut version_tag: Option<String> = None;
// Fetch commit info via GitHub API
if let Some(json_str) = http_get_string(&api_url) {
// 1. Fetch release tag and notes via GitHub Releases API
if let Some(json_str) = http_get_string(&release_api_url) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json_str) {
if let Some(sha) = val["sha"].as_str() {
remote_sha = sha.to_string();
if let Some(tag) = val["tag_name"].as_str() {
let trimmed = tag.trim().to_string();
if !trimmed.is_empty() {
version_tag = Some(trimmed);
}
}
if let Some(msg) = val["commit"]["message"].as_str() {
commit_message = msg.lines().next().unwrap_or(msg).to_string();
if let Some(body) = val["body"].as_str().filter(|b| !b.trim().is_empty()) {
commit_message = body.lines().next().unwrap_or(body).to_string();
}
if let Some(d) = val["commit"]["committer"]["date"].as_str() {
if let Some(d) = val["published_at"].as_str() {
commit_date = d.split('T').next().unwrap_or(d).to_string();
}
}
}
// Fallback: only if GitHub API failed AND git is installed on system
// 2. Fetch commit info via GitHub Commits API
if let Some(json_str) = http_get_string(&commit_api_url) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json_str) {
if let Some(sha) = val["sha"].as_str() {
remote_sha = sha.to_string();
}
if let Some(msg) = val["commit"]["message"].as_str() {
if commit_message == "En son optimizasyonlar ve güncellemeler" {
commit_message = msg.lines().next().unwrap_or(msg).to_string();
}
}
if let Some(d) = val["commit"]["committer"]["date"].as_str() {
if commit_date.is_empty() {
commit_date = d.split('T').next().unwrap_or(d).to_string();
}
}
}
}
// 3. Fallback: only if GitHub API failed AND git is installed on system
if remote_sha.is_empty() && is_git_available() {
if let Ok(output) = Command::new("git")
.args(["ls-remote", GITHUB_REPO_URL, "HEAD"])
@@ -130,19 +288,71 @@ pub fn check_github_update(current_commit: &str) -> Result<UpdateInfo, String> {
}
}
}
if version_tag.is_none() {
if let Ok(output) = Command::new("git")
.args(["ls-remote", "--tags", GITHUB_REPO_URL])
.output()
{
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
let mut highest_tag: Option<String> = None;
for line in text.lines() {
if let Some(ref_part) = line.split_whitespace().nth(1) {
if let Some(tag) = ref_part.strip_prefix("refs/tags/") {
if !tag.ends_with("^{}") && (tag.starts_with('v') || tag.starts_with('V')) {
if let Some(ref h) = highest_tag {
if compare_version_strings(tag, h) > std::cmp::Ordering::Equal {
highest_tag = Some(tag.to_string());
}
} else {
highest_tag = Some(tag.to_string());
}
}
}
}
}
version_tag = highest_tag;
}
}
}
}
if remote_sha.is_empty() {
if remote_sha.is_empty() && version_tag.is_none() {
return Err("GitHub deposuna ulaşılamadı. İnternet bağlantınızı kontrol edin veya daha sonra tekrar deneyin.".to_string());
}
let is_new = current_commit.trim().is_empty() || current_commit.trim() != remote_sha.trim();
// Version Check:
// Only true if the remote version tag is STRICTLY HIGHER than current running launcher version.
let is_newer_version = if let Some(ref tag) = version_tag {
compare_version_strings(tag, current_version) > std::cmp::Ordering::Equal
} else {
false
};
let missing_files = check_installation_integrity(base_dir);
let has_missing_files = !missing_files.is_empty();
// Do NOT flag update as needed if current launcher is already at or above the remote release tag!
// Prevents redundant re-downloads when already running v1.7.
let is_new = is_newer_version || has_missing_files;
crate::log_info!(
"Update check complete: remote tag={:?}, is_newer_version={}, has_missing_files={} -> is_new={}",
version_tag,
is_newer_version,
has_missing_files,
is_new
);
Ok(UpdateInfo {
commit_sha: remote_sha,
commit_message,
commit_date,
is_new,
version_tag,
has_missing_files,
missing_files,
})
}
@@ -162,7 +372,7 @@ pub fn apply_github_update(
if is_git_repo {
// Path A: User cloned via git and git is available on system
let pull_output = Command::new("git")
.args(["pull", "origin", "main"])
.args(["pull", "--tags", "origin", "main"])
.current_dir(base_dir)
.output()
.map_err(|e| format!("'git pull' çalıştırılamadı: {}", e))?;
@@ -217,11 +427,24 @@ pub fn apply_github_update(
);
let patch_dest = patches_dir.join("God_of_War_III_Remastered.xml");
let _ = http_download_file(&raw_patch_url, &patch_dest);
// Also fetch gow3.png if missing
let icon_file = base_dir.join("gow3.png");
if !icon_file.is_file() {
let raw_icon_url = format!("https://raw.githubusercontent.com/{}/main/gow3.png", GITHUB_REPO);
let _ = http_download_file(&raw_icon_url, &icon_file);
}
// Also ensure runtime assets are unpacked
let _ = crate::runner::ensure_runtime_assets(base_dir);
}
let _ = apply_settings(base_dir, config);
config.last_git_commit = update.commit_sha.clone();
if let Some(ref tag) = update.version_tag {
config.launcher_version = tag.clone();
}
let _ = config.save(base_dir);
Ok("Launcher ve optimizasyonlar başarıyla güncellendi! Yeni sürümün geçerli olması için lütfen launcher'ı yeniden başlatın.".to_string())
@@ -233,10 +456,35 @@ mod tests {
#[test]
fn test_check_github_update() {
let res = check_github_update("test_dummy_sha");
let res = check_github_update(Path::new(".."), "test_dummy_sha", "v0.1");
if let Ok(info) = res {
assert!(!info.commit_sha.is_empty());
assert!(!info.commit_sha.is_empty() || info.version_tag.is_some());
assert!(info.is_new);
}
// Test with current version (v2.0.2): should NOT report as an update against current GitHub release (v2.0.1)
let res_current = check_github_update(Path::new(".."), "test_dummy_sha", BUILTIN_VERSION);
if let Ok(info) = res_current {
assert!(!info.is_new);
}
}
#[test]
fn test_version_compare() {
assert_eq!(compare_version_strings("v1.3", "v1.4"), std::cmp::Ordering::Less);
assert_eq!(compare_version_strings("v1.4", "v1.4"), std::cmp::Ordering::Equal);
assert_eq!(compare_version_strings("v1.5", "v1.4"), std::cmp::Ordering::Greater);
assert_eq!(compare_version_strings("v1.10", "v1.9"), std::cmp::Ordering::Greater);
assert_eq!(compare_version_strings("1.4", "v1.4"), std::cmp::Ordering::Equal);
assert_eq!(compare_version_strings("v2.2", "v2.1"), std::cmp::Ordering::Greater);
assert_eq!(compare_version_strings("v2.1", "v2.1"), std::cmp::Ordering::Equal);
assert_eq!(compare_version_strings("v2.0.3", "v2.1"), std::cmp::Ordering::Less);
}
#[test]
fn test_fetch_github_release_notes() {
if let Ok(notes) = fetch_github_release_notes() {
assert!(!notes.is_empty(), "Should fetch at least one release note from GitHub");
}
}
}
+354
View File
@@ -0,0 +1,354 @@
// SPDX-License-Identifier: MIT
// God of War III Remastered - Windows Input Bridge & Window Manager
#[cfg(target_os = "windows")]
mod imp {
use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::thread;
use std::time::Duration;
static HOOK_RUNNING: AtomicBool = AtomicBool::new(false);
static GAME_PID: AtomicU32 = AtomicU32::new(0);
static HIDE_CURSOR_ENABLED: AtomicBool = AtomicBool::new(false);
static HOOK_HANDLE: std::sync::atomic::AtomicIsize = std::sync::atomic::AtomicIsize::new(0);
type HWND = *mut c_void;
type HHOOK = isize;
type HINSTANCE = isize;
type LRESULT = isize;
type WPARAM = usize;
type LPARAM = isize;
type HOOKPROC = unsafe extern "system" fn(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT;
const WH_MOUSE_LL: i32 = 14;
const WM_LBUTTONDOWN: usize = 0x0201;
const WM_LBUTTONUP: usize = 0x0202;
const WM_RBUTTONDOWN: usize = 0x0204;
const WM_RBUTTONUP: usize = 0x0205;
const WM_MBUTTONDOWN: usize = 0x0207;
const WM_MBUTTONUP: usize = 0x0208;
const WM_MOUSEWHEEL: usize = 0x020A;
const WM_XBUTTONDOWN: usize = 0x020B;
const WM_XBUTTONUP: usize = 0x020C;
const WM_QUIT: u32 = 0x0012;
const VK_F13: u8 = 0x7C;
const VK_F14: u8 = 0x7D;
const VK_F15: u8 = 0x7E;
const VK_F16: u8 = 0x7F;
const VK_F17: u8 = 0x80;
const VK_F18: u8 = 0x81;
const VK_F19: u8 = 0x82;
// Direct Windows hardware scan codes for F13-F19 mapped by SDL3's windows_scancode_table:
// 0x64 -> SDL_SCANCODE_F13 (Square / Light Attack)
// 0x65 -> SDL_SCANCODE_F14 (Triangle / Heavy Attack)
// 0x66 -> SDL_SCANCODE_F15 (Circle / Grab)
// 0x67 -> SDL_SCANCODE_F16 (Cross / Jump)
// 0x68 -> SDL_SCANCODE_F17
// 0x69 -> SDL_SCANCODE_F18
// 0x6A -> SDL_SCANCODE_F19
const SCAN_F13: u8 = 0x64;
const SCAN_F14: u8 = 0x65;
const SCAN_F15: u8 = 0x66;
const SCAN_F16: u8 = 0x67;
const SCAN_F17: u8 = 0x68;
const SCAN_F18: u8 = 0x69;
const SCAN_F19: u8 = 0x6A;
const KEYEVENTF_KEYUP: u32 = 0x0002;
const SW_SHOW: i32 = 5;
const GA_ROOT: u32 = 2;
#[repr(C)]
#[derive(Clone, Copy)]
struct POINT {
x: i32,
y: i32,
}
#[repr(C)]
struct MSLLHOOKSTRUCT {
pt: POINT,
mouse_data: u32,
flags: u32,
time: u32,
extra_info: usize,
}
#[repr(C)]
struct MSG {
hwnd: HWND,
message: u32,
w_param: WPARAM,
l_param: LPARAM,
time: u32,
pt: POINT,
}
#[link(name = "user32")]
#[link(name = "winmm")]
unsafe extern "system" {
fn SetWindowsHookExA(id_hook: i32, lpfn: HOOKPROC, h_mod: HINSTANCE, dw_thread_id: u32) -> HHOOK;
fn UnhookWindowsHookEx(hhk: HHOOK) -> i32;
fn CallNextHookEx(hhk: HHOOK, n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT;
fn GetForegroundWindow() -> HWND;
fn GetWindowThreadProcessId(hwnd: HWND, lpdw_process_id: *mut u32) -> u32;
fn GetWindowTextA(hwnd: HWND, lp_string: *mut u8, n_max_count: i32) -> i32;
fn keybd_event(b_vk: u8, b_scan: u8, dw_flags: u32, dw_extra_info: usize);
fn GetMessageA(lp_msg: *mut MSG, hwnd: HWND, w_msg_filter_min: u32, w_msg_filter_max: u32) -> i32;
fn PostThreadMessageA(id_thread: u32, msg: u32, w_param: WPARAM, l_param: LPARAM) -> i32;
fn GetCurrentThreadId() -> u32;
fn ShowWindow(hwnd: HWND, n_cmd_show: i32) -> i32;
fn SetForegroundWindow(hwnd: HWND) -> i32;
fn BringWindowToTop(hwnd: HWND) -> i32;
fn EnumWindows(lp_enum_func: unsafe extern "system" fn(hwnd: HWND, lparam: LPARAM) -> i32, lparam: LPARAM) -> i32;
fn IsWindowVisible(hwnd: HWND) -> i32;
fn timeBeginPeriod(u_period: u32) -> u32;
fn timeEndPeriod(u_period: u32) -> u32;
fn WindowFromPoint(point: POINT) -> HWND;
fn GetAncestor(hwnd: HWND, ga_flags: u32) -> HWND;
fn AttachThreadInput(id_attach: u32, id_attach_to: u32, f_attach: i32) -> i32;
fn PlaySoundA(psz_sound: *const u8, hmod: HINSTANCE, fdw_sound: u32) -> i32;
}
pub fn play_sound_memory(bytes: &[u8]) {
unsafe {
// SND_ASYNC = 0x0001, SND_NODEFAULT = 0x0002, SND_MEMORY = 0x0004
PlaySoundA(bytes.as_ptr(), 0, 0x0001 | 0x0002 | 0x0004);
}
}
unsafe fn is_game_window(hwnd: HWND) -> bool {
if hwnd.is_null() {
return false;
}
let my_pid = std::process::id();
let expected_pid = GAME_PID.load(Ordering::SeqCst);
let mut win_pid: u32 = 0;
unsafe {
GetWindowThreadProcessId(hwnd, &mut win_pid);
}
if win_pid == my_pid {
return false; // Asla launcher penceresiyle eşleşme!
}
if expected_pid > 0 && win_pid == expected_pid {
return true;
}
let mut title_buf = [0u8; 128];
let len = unsafe { GetWindowTextA(hwnd, title_buf.as_mut_ptr(), 127) };
if len > 0 {
let title = String::from_utf8_lossy(&title_buf[..len as usize]);
if title.contains("Launcher") || title.contains("gow3-launcher") {
return false;
}
if title.contains("God of War") || title.contains("gow3") {
return true;
}
}
false
}
unsafe extern "system" fn low_level_mouse_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
if code >= 0 {
unsafe {
let fg = GetForegroundWindow();
let p_mouse = &*(lparam as *const MSLLHOOKSTRUCT);
let hwnd_pt = WindowFromPoint(p_mouse.pt);
let is_fg = is_game_window(fg);
let is_pt = is_game_window(hwnd_pt) || is_game_window(GetAncestor(hwnd_pt, GA_ROOT));
if is_fg || is_pt {
match wparam {
WM_LBUTTONDOWN => {
keybd_event(VK_F13, SCAN_F13, 0, 0);
}
WM_LBUTTONUP => {
keybd_event(VK_F13, SCAN_F13, KEYEVENTF_KEYUP, 0);
}
WM_RBUTTONDOWN => {
keybd_event(VK_F14, SCAN_F14, 0, 0);
}
WM_RBUTTONUP => {
keybd_event(VK_F14, SCAN_F14, KEYEVENTF_KEYUP, 0);
}
WM_MBUTTONDOWN => {
keybd_event(VK_F15, SCAN_F15, 0, 0);
}
WM_MBUTTONUP => {
keybd_event(VK_F15, SCAN_F15, KEYEVENTF_KEYUP, 0);
}
WM_XBUTTONDOWN => {
let xbtn = (p_mouse.mouse_data >> 16) as u16;
if xbtn == 1 {
keybd_event(VK_F16, SCAN_F16, 0, 0);
} else if xbtn == 2 {
keybd_event(VK_F17, SCAN_F17, 0, 0);
}
}
WM_XBUTTONUP => {
let xbtn = (p_mouse.mouse_data >> 16) as u16;
if xbtn == 1 {
keybd_event(VK_F16, SCAN_F16, KEYEVENTF_KEYUP, 0);
} else if xbtn == 2 {
keybd_event(VK_F17, SCAN_F17, KEYEVENTF_KEYUP, 0);
}
}
WM_MOUSEWHEEL => {
let delta = (p_mouse.mouse_data >> 16) as i16;
if delta > 0 {
keybd_event(VK_F18, SCAN_F18, 0, 0);
keybd_event(VK_F18, SCAN_F18, KEYEVENTF_KEYUP, 0);
} else if delta < 0 {
keybd_event(VK_F19, SCAN_F19, 0, 0);
keybd_event(VK_F19, SCAN_F19, KEYEVENTF_KEYUP, 0);
}
}
_ => {}
}
}
}
}
let hhk = HOOK_HANDLE.load(Ordering::SeqCst);
unsafe { CallNextHookEx(hhk, code, wparam, lparam) }
}
struct EnumParam {
target_pid: u32,
found_hwnd: HWND,
}
unsafe extern "system" fn enum_win_proc(hwnd: HWND, lparam: LPARAM) -> i32 {
let p = unsafe { &mut *(lparam as *mut EnumParam) };
let mut pid: u32 = 0;
unsafe {
GetWindowThreadProcessId(hwnd, &mut pid);
if pid == std::process::id() {
return 1; // Launcher penceresini asla hedef alma!
}
if IsWindowVisible(hwnd) != 0 {
if p.target_pid > 0 && pid == p.target_pid {
p.found_hwnd = hwnd;
return 0; // Hedef oyun PID'sine ait görünür pencere bulundu!
} else if p.target_pid == 0 && is_game_window(hwnd) {
p.found_hwnd = hwnd;
return 0;
}
}
}
1 // continue
}
pub fn find_game_hwnd(pid: u32) -> Option<HWND> {
let mut param = EnumParam {
target_pid: pid,
found_hwnd: std::ptr::null_mut(),
};
unsafe {
EnumWindows(enum_win_proc, &mut param as *mut _ as LPARAM);
}
if !param.found_hwnd.is_null() {
Some(param.found_hwnd)
} else {
None
}
}
pub fn bring_game_window_to_front(pid: u32) {
if let Some(hwnd) = find_game_hwnd(pid) {
unsafe {
let cur_fg = GetForegroundWindow();
let cur_thread = GetCurrentThreadId();
let mut fg_pid = 0u32;
let fg_thread = GetWindowThreadProcessId(cur_fg, &mut fg_pid);
if fg_thread != 0 && cur_thread != fg_thread {
AttachThreadInput(cur_thread, fg_thread, 1);
}
ShowWindow(hwnd, SW_SHOW);
BringWindowToTop(hwnd);
SetForegroundWindow(hwnd);
if fg_thread != 0 && cur_thread != fg_thread {
AttachThreadInput(cur_thread, fg_thread, 0);
}
}
}
}
pub struct WinInputGuard {
thread_id: u32,
}
impl Drop for WinInputGuard {
fn drop(&mut self) {
HOOK_RUNNING.store(false, Ordering::SeqCst);
if self.thread_id != 0 {
unsafe {
PostThreadMessageA(self.thread_id, WM_QUIT, 0, 0);
timeEndPeriod(1);
}
}
}
}
pub fn start_mouse_hook(pid: u32, hide_cursor: bool) -> WinInputGuard {
GAME_PID.store(pid, Ordering::SeqCst);
HIDE_CURSOR_ENABLED.store(hide_cursor, Ordering::SeqCst);
HOOK_RUNNING.store(true, Ordering::SeqCst);
let (tx, rx) = std::sync::mpsc::channel();
thread::spawn(move || {
unsafe {
timeBeginPeriod(1);
let tid = GetCurrentThreadId();
let hook = SetWindowsHookExA(WH_MOUSE_LL, low_level_mouse_proc, 0, 0);
HOOK_HANDLE.store(hook, Ordering::SeqCst);
let _ = tx.send(tid);
if hook != 0 {
let mut msg: MSG = std::mem::zeroed();
while HOOK_RUNNING.load(Ordering::SeqCst) && GetMessageA(&mut msg, std::ptr::null_mut(), 0, 0) > 0 {
// Hook message loop
}
UnhookWindowsHookEx(hook);
HOOK_HANDLE.store(0, Ordering::SeqCst);
}
timeEndPeriod(1);
}
});
let thread_id = rx.recv_timeout(Duration::from_millis(500)).unwrap_or(0);
WinInputGuard { thread_id }
}
}
#[cfg(target_os = "windows")]
pub use imp::*;
#[cfg(not(target_os = "windows"))]
mod stub {
pub struct WinInputGuard;
pub fn start_mouse_hook(_pid: u32, _hide_cursor: bool) -> WinInputGuard {
WinInputGuard
}
pub fn bring_game_window_to_front(pid: u32) {
if pid > 0 {
let _ = std::process::Command::new("hyprctl")
.args(["dispatch", "focuswindow", &format!("pid:{}", pid)])
.output();
let _ = std::process::Command::new("xdotool")
.args(["search", "--pid", &pid.to_string(), "windowactivate"])
.output();
}
}
pub fn find_game_hwnd(_pid: u32) -> Option<isize> {
None
}
pub fn play_sound_memory(_bytes: &[u8]) {}
}
#[cfg(not(target_os = "windows"))]
pub use stub::*;
+43 -17
View File
@@ -2,10 +2,14 @@
"first_launch_done": true,
"language": "tr",
"game_dir": "/home/screamangel/İndirilenler/GOW3 Emu Linux/CUSA01715",
"target_fps": 60,
"target_fps": 144,
"patch_delta_time": true,
"patch_readbacks": true,
"async_readbacks": true,
"launch_while_compiling_shaders": false,
"show_shader_compile_rage_meter": false,
"skip_cinematics": true,
"bonus_play_fix": true,
"use_gamemode": true,
"vram_gc_budget_mb": 6144,
"dmem_mb": 9216,
@@ -13,28 +17,48 @@
"gpu_name": "NVIDIA GeForce RTX 4070 Laptop GPU",
"vram_mb": 8188,
"ram_mb": 15603,
"refresh_rate_hz": 144
"refresh_rate_hz": 144,
"cpu_name": "12th Gen Intel(R) Core(TM) i7-12700H",
"cpu_cores": 14,
"cpu_threads": 20
},
"last_git_commit": "0b4abe07acb711848d1b5d72402a9de8c0ad9844",
"launcher_version": "v2.2",
"disabled_mods": [
"(OPTIONAL) God Armor",
"game",
"Game",
"god armor By Richarito v2",
"DiamondSword v1.1 Author YT @MustopuhGGI",
"Skeleton Kratos Skin",
"Golden Blades",
"CJ (Mod by Gamer JP)"
],
"render_resolution": "native",
"fsr_enabled": false,
"disable_motion_blur": false,
"disable_dof": false,
"graphic_preset": "Balanced",
"display_mode": "Borderless",
"vsync": true,
"background_controller_input": false,
"hide_mouse_cursor": true,
"is_motion_controls_enabled": true,
"deadzone_left": 2,
"deadzone_right": 2,
"keyboard_bindings": {
"triangle": "c",
"circle": "b",
"cross": "n",
"square": "v",
"triangle": "mouse2",
"circle": "f",
"cross": "space",
"square": "mouse1",
"l1": "q",
"r1": "u",
"l2": "e",
"r2": "o",
"l3": "x",
"r3": "m",
"r1": "e",
"l2": "z",
"r2": "x",
"l3": "c",
"r3": "v",
"options": "enter",
"touchpad_center": "space",
"touchpad_center": "backspace",
"pad_up": "up",
"pad_down": "down",
"pad_left": "left",
@@ -43,9 +67,11 @@
"axis_left_x_plus": "d",
"axis_left_y_minus": "w",
"axis_left_y_plus": "s",
"axis_right_x_minus": "j",
"axis_right_x_plus": "l",
"axis_right_y_minus": "i",
"axis_right_y_plus": "k"
}
"axis_right_x_minus": "kp4",
"axis_right_x_plus": "kp6",
"axis_right_y_minus": "kp8",
"axis_right_y_plus": "kp2"
},
"discord_rpc_enabled": true,
"discord_client_id": "1440140183675867156"
}
+13
View File
@@ -0,0 +1,13 @@
{
"disabled": [
"(OPTIONAL) God Armor",
"game",
"Game",
"god armor By Richarito v2",
"DiamondSword v1.1 Author YT @MustopuhGGI",
"Skeleton Kratos Skin",
"Golden Blades",
"CJ (Mod by Gamer JP)"
],
"order": []
}
+143 -63
View File
@@ -17,59 +17,79 @@
<Line Type="bytes" Address="0x007cd918" Value="00000090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Bug Fix - Startup Black Screen" Note="Fixes the black screen at startup with the FPS counter running: the frame pacing index starts at 2 instead of 0, so the second frame no longer waits for a flip that only it can trigger." Author="SpartanPort" PatchVer="1.0" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
<PatchList>
<Line Type="bytes" Address="0x018ba600" Value="02000000"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Bug Fix - Intro Crash Fix" Note="Universal fix for engine pool memory exhaustion crashes (such as intro transition crash at 0x2c54b2 and magic/combat crash at 0x2de2d2). Enables automatic slab growth for all engine pool allocators and adds defensive null checks." Author="gow3-launcher" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
<PatchList>
<Line Type="bytes" Address="0x006b36ae" Value="6a305eba00080000b9800000006a04415890"/>
<Line Type="bytes" Address="0x006c54a7" Value="0f84d600000090"/>
<Line Type="bytes" Address="0x006c6387" Value="0f84d600000090"/>
<Line Type="bytes" Address="0x0058dca0" Value="4585ff750441b78090"/>
<Line Type="bytes" Address="0x006de2c7" Value="0f84d600000090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Bug Fix - Bonus Play QTE and Enemy Spawning Fix" Note="Fixes a critical scripting desynchronization in God of War III Bonus Play when Godly Possessions (specifically Hephaestus' Ring) are active. Prevents auto-QTE from prematurely skipping context-sensitive animation death callbacks, ensuring enemy wave spawns and QTE triggers progress without arena softlocks." Author="gow3-launcher" PatchVer="1.0" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
<PatchList>
<Line Type="bytes" Address="0x0041b2a3" Value="7542"/>
<Line Type="bytes" Address="0x0041b2e7" Value="b901000000909090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 480p" Note="Decreases game output resolution to 854x480 and increases Video Arena to 2.25 GiB. Requires setting DMEM to 2048 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="56030000"/>
<Line Type="bytes" Address="0x005f0b43" Value="e0010000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="5603"/>
<Line Type="bytes" Address="0x005f0b53" Value="e001"/>
<Line Type="bytes" Address="0x005643f3" Value="e0010000"/>
<Line Type="bytes" Address="0x00564400" Value="56030000"/>
<Line Type="bytes" Address="0x005ff769" Value="56030000"/>
<Line Type="bytes" Address="0x005ff76f" Value="e0010000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="56030000"/>
<Line Type="bytes" Address="0x005f0c63" Value="e0010000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="5603"/>
<Line Type="bytes" Address="0x005f0c73" Value="e001"/>
<Line Type="bytes" Address="0x005643e3" Value="e0010000"/>
<Line Type="bytes" Address="0x005643f0" Value="56030000"/>
<Line Type="bytes" Address="0x005ff889" Value="56030000"/>
<Line Type="bytes" Address="0x005ff88f" Value="e0010000"/>
<Line Type="bytes" Address="0x007cd918" Value="00000090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 720p" Note="Decreases game output resolution to 1280x720 and increases Video Arena to 2.25 GiB. Requires setting DMEM to 2048 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="00050000"/>
<Line Type="bytes" Address="0x005f0b43" Value="d0020000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="0005"/>
<Line Type="bytes" Address="0x005f0b53" Value="d002"/>
<Line Type="bytes" Address="0x005643f3" Value="d0020000"/>
<Line Type="bytes" Address="0x00564400" Value="00050000"/>
<Line Type="bytes" Address="0x005ff769" Value="00050000"/>
<Line Type="bytes" Address="0x005ff76f" Value="d0020000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="00050000"/>
<Line Type="bytes" Address="0x005f0c63" Value="d0020000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="0005"/>
<Line Type="bytes" Address="0x005f0c73" Value="d002"/>
<Line Type="bytes" Address="0x005643e3" Value="d0020000"/>
<Line Type="bytes" Address="0x005643f0" Value="00050000"/>
<Line Type="bytes" Address="0x005ff889" Value="00050000"/>
<Line Type="bytes" Address="0x005ff88f" Value="d0020000"/>
<Line Type="bytes" Address="0x007cd918" Value="00000090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 800p" Note="Sets the game output resolution to 1280x800 (16:10, the Steam Deck panel) and increases Video Arena to 2.25 GiB. Requires setting DMEM to 2048 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4 (1280x800 adapted for the Steam Deck)" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="00050000"/>
<Line Type="bytes" Address="0x005f0b43" Value="20030000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="0005"/>
<Line Type="bytes" Address="0x005f0b53" Value="2003"/>
<Line Type="bytes" Address="0x005643f3" Value="20030000"/>
<Line Type="bytes" Address="0x00564400" Value="00050000"/>
<Line Type="bytes" Address="0x005ff769" Value="00050000"/>
<Line Type="bytes" Address="0x005ff76f" Value="20030000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="00050000"/>
<Line Type="bytes" Address="0x005f0c63" Value="20030000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="0005"/>
<Line Type="bytes" Address="0x005f0c73" Value="2003"/>
<Line Type="bytes" Address="0x005643e3" Value="20030000"/>
<Line Type="bytes" Address="0x005643f0" Value="00050000"/>
<Line Type="bytes" Address="0x005ff889" Value="00050000"/>
<Line Type="bytes" Address="0x005ff88f" Value="20030000"/>
<Line Type="bytes" Address="0x007cd918" Value="00000090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 1440p" Note="Increases game output resolution to 2560x1440 and Video Arena to 2.75 GiB. Requires setting DMEM to 3072 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="000a0000"/>
<Line Type="bytes" Address="0x005f0b43" Value="a0050000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="000a"/>
<Line Type="bytes" Address="0x005f0b53" Value="a005"/>
<Line Type="bytes" Address="0x005643f3" Value="a0050000"/>
<Line Type="bytes" Address="0x00564400" Value="000a0000"/>
<Line Type="bytes" Address="0x005ff769" Value="000a0000"/>
<Line Type="bytes" Address="0x005ff76f" Value="a0050000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="000a0000"/>
<Line Type="bytes" Address="0x005f0c63" Value="a0050000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="000a"/>
<Line Type="bytes" Address="0x005f0c73" Value="a005"/>
<Line Type="bytes" Address="0x005643e3" Value="a0050000"/>
<Line Type="bytes" Address="0x005643f0" Value="000a0000"/>
<Line Type="bytes" Address="0x005ff889" Value="000a0000"/>
<Line Type="bytes" Address="0x005ff88f" Value="a0050000"/>
<Line Type="bytes" Address="0x007c46b4" Value="000a0000"/>
<Line Type="bytes" Address="0x007c46b8" Value="a0050000"/>
<Line Type="bytes" Address="0x0056bb82" Value="a0050000"/>
<Line Type="bytes" Address="0x0060953d" Value="a0050000"/>
<Line Type="bytes" Address="0x0056bb72" Value="a0050000"/>
<Line Type="bytes" Address="0x0060965d" Value="a0050000"/>
<Line Type="bytes" Address="0x007f2a3c" Value="00050000"/>
<Line Type="bytes" Address="0x007f2a40" Value="d0020000"/>
<Line Type="bytes" Address="0x007cd918" Value="000000b0"/>
@@ -77,18 +97,18 @@
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 1800p" Note="Increases game output resolution to 3200x1800 and Video Arena to 3.25 GiB. Requires setting DMEM to 4608 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="800c0000"/>
<Line Type="bytes" Address="0x005f0b43" Value="08070000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="800c"/>
<Line Type="bytes" Address="0x005f0b53" Value="0807"/>
<Line Type="bytes" Address="0x005643f3" Value="08070000"/>
<Line Type="bytes" Address="0x00564400" Value="800c0000"/>
<Line Type="bytes" Address="0x005ff769" Value="800c0000"/>
<Line Type="bytes" Address="0x005ff76f" Value="08070000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="800c0000"/>
<Line Type="bytes" Address="0x005f0c63" Value="08070000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="800c"/>
<Line Type="bytes" Address="0x005f0c73" Value="0807"/>
<Line Type="bytes" Address="0x005643e3" Value="08070000"/>
<Line Type="bytes" Address="0x005643f0" Value="800c0000"/>
<Line Type="bytes" Address="0x005ff889" Value="800c0000"/>
<Line Type="bytes" Address="0x005ff88f" Value="08070000"/>
<Line Type="bytes" Address="0x007c46b4" Value="800c0000"/>
<Line Type="bytes" Address="0x007c46b8" Value="08070000"/>
<Line Type="bytes" Address="0x0056bb82" Value="08070000"/>
<Line Type="bytes" Address="0x0060953d" Value="08070000"/>
<Line Type="bytes" Address="0x0056bb72" Value="08070000"/>
<Line Type="bytes" Address="0x0060965d" Value="08070000"/>
<Line Type="bytes" Address="0x007f2a3c" Value="40060000"/>
<Line Type="bytes" Address="0x007f2a40" Value="84030000"/>
<Line Type="bytes" Address="0x007cd918" Value="000000d0"/>
@@ -96,39 +116,77 @@
</Metadata>
<Metadata Title="God of War III Remastered" Name="Resolution Patch - 4K" Note="Increases game output resolution to 3840x2160 and Video Arena to approximately 4 GB (3.75 GiB). Requires setting DMEM to 6144 MB or more. Only leave one resolution patch enabled; do not enable it with Bug Fix - Texture Corruption Fix." Author="kvicken + cuesta4" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x005f0b3b" Value="000f0000"/>
<Line Type="bytes" Address="0x005f0b43" Value="70080000"/>
<Line Type="bytes" Address="0x005f0b4c" Value="000f"/>
<Line Type="bytes" Address="0x005f0b53" Value="7008"/>
<Line Type="bytes" Address="0x005643f3" Value="70080000"/>
<Line Type="bytes" Address="0x00564400" Value="000f0000"/>
<Line Type="bytes" Address="0x005ff769" Value="000f0000"/>
<Line Type="bytes" Address="0x005ff76f" Value="70080000"/>
<Line Type="bytes" Address="0x005f0c5b" Value="000f0000"/>
<Line Type="bytes" Address="0x005f0c63" Value="70080000"/>
<Line Type="bytes" Address="0x005f0c6c" Value="000f"/>
<Line Type="bytes" Address="0x005f0c73" Value="7008"/>
<Line Type="bytes" Address="0x005643e3" Value="70080000"/>
<Line Type="bytes" Address="0x005643f0" Value="000f0000"/>
<Line Type="bytes" Address="0x005ff889" Value="000f0000"/>
<Line Type="bytes" Address="0x005ff88f" Value="70080000"/>
<Line Type="bytes" Address="0x007c46b4" Value="000f0000"/>
<Line Type="bytes" Address="0x007c46b8" Value="70080000"/>
<Line Type="bytes" Address="0x0056bb82" Value="70080000"/>
<Line Type="bytes" Address="0x0060953d" Value="70080000"/>
<Line Type="bytes" Address="0x0056bb72" Value="70080000"/>
<Line Type="bytes" Address="0x0060965d" Value="70080000"/>
<Line Type="bytes" Address="0x007f2a3c" Value="80070000"/>
<Line Type="bytes" Address="0x007f2a40" Value="38040000"/>
<Line Type="bytes" Address="0x007cd918" Value="000000f0"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 120 FPS" Note="Raises the engine maximum framerate from 59.94 to 120 FPS so the frametime clamp produces a 1/120 second gameplay delta. Corrects swing energy calculations to match the original 60 Hz simulation. Requires setting VBlank to 120 FPS." Author="kvicken" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 30 FPS" Note="Sets the engine maximum framerate to 30 FPS with correct 60 Hz swing energy simulation. Requires setting VBlank to 30 FPS." Author="gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05a7c12600"/>
<Line Type="bytes" Address="0x0075dbc4" Value="0000f042"/>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="0000f041"/>
<Line Type="bytes" Address="0x0075dd04" Value="0000f041"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 144 FPS" Note="Raises the engine maximum framerate from 59.94 to 144 FPS so the frametime clamp produces a 1/144 second gameplay delta. Corrects swing energy calculations to match the original 60 Hz simulation. Requires setting VBlank to 144 FPS." Author="kvicken" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 40 FPS" Note="Sets the engine maximum framerate to 40 FPS (ideal for Steam Deck 40 Hz) with correct 60 Hz swing energy simulation. Requires setting VBlank to 40 FPS." Author="gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05a7c12600"/>
<Line Type="bytes" Address="0x0075dbc4" Value="00001043"/>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="00002042"/>
<Line Type="bytes" Address="0x0075dd04" Value="00002042"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 240 FPS" Note="The 120 FPS patch with the engine maximum framerate at 240 FPS (frametime clamp of 1/240 second), adapted by Felipe Prado. Same swing energy correction. Requires setting VBlank to 240 FPS." Author="kvicken, gow3_pc" PatchVer="1.0" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 60 FPS" Note="Sets the engine maximum framerate to 60 FPS with correct 60 Hz swing energy simulation. Requires setting VBlank to 60 FPS." Author="gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05a7c12600"/>
<Line Type="bytes" Address="0x0075dbc4" Value="00007043"/>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="00007042"/>
<Line Type="bytes" Address="0x0075dd04" Value="00007042"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 90 FPS" Note="Sets the engine maximum framerate to 90 FPS (ideal for Steam Deck OLED 90 Hz) with correct 60 Hz swing energy simulation. Requires setting VBlank to 90 FPS." Author="gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="0000b442"/>
<Line Type="bytes" Address="0x0075dd04" Value="0000b442"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 120 FPS" Note="Raises the engine maximum framerate from 59.94 to 120 FPS. Corrects swing energy calculations to match original 60 Hz simulation so game speed and physics stay normal. Requires setting VBlank to 120 FPS." Author="kvicken, gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="0000f042"/>
<Line Type="bytes" Address="0x0075dd04" Value="0000f042"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 144 FPS" Note="Raises the engine maximum framerate from 59.94 to 144 FPS. Corrects swing energy calculations to match original 60 Hz simulation. Requires setting VBlank to 144 FPS." Author="kvicken, gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="00001043"/>
<Line Type="bytes" Address="0x0075dd04" Value="00001043"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 165 FPS" Note="Raises the engine maximum framerate from 59.94 to 165 FPS. Corrects swing energy calculations to match original 60 Hz simulation. Requires setting VBlank to 165 FPS." Author="gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="00002543"/>
<Line Type="bytes" Address="0x0075dd04" Value="00002543"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Frame Rate Patch - 240 FPS" Note="The 120 FPS patch with the engine maximum framerate at 240 FPS, with swing energy correction. Requires setting VBlank to 240 FPS." Author="kvicken, Felipe Prado, gow3-launcher" PatchVer="1.2" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0048ff0a" Value="488d05e7c22600"/>
<Line Type="bytes" Address="0x0075dd00" Value="00007043"/>
<Line Type="bytes" Address="0x0075dd04" Value="00007043"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Skip Any Video With X Button" Note="Allows manual skipping of cinematics any cinematic with the X button. Thanks to illusion0001 aka illusionyy for the PS3 patch which made this very easy." Author="cuesta4" PatchVer="1.0" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
@@ -137,4 +195,26 @@
<Line Type="bytes" Address="0x0041b2e7" Value="b901000000909090"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Graphics - Disable Motion Blur" Note="Disables the camera and object motion blur render pass, improving performance on low-end systems and removing motion smearing." Author="gow3-launcher" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x0056ae80" Value="c3"/>
<Line Type="bytes" Address="0x0056b0e0" Value="c3"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Graphics - Disable Depth of Field" Note="Disables the Depth of Field (DoF) post-processing blur pass, increasing FPS on budget GPUs and keeping background details sharp." Author="gow3-launcher" PatchVer="1.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="false">
<PatchList>
<Line Type="bytes" Address="0x00565ac0" Value="c3"/>
<Line Type="bytes" Address="0x00566090" Value="c3"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Bug Fix - Trophy Unlock Fix" Note="Disables Bonus Play trophy restriction and bounds checks in eboot.bin, enabling all 35 trophies to unlock seamlessly." Author="gow3-launcher" PatchVer="2.1" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
<PatchList>
<Line Type="bytes" Address="0x0041ef88" Value="909090909090"/>
<Line Type="bytes" Address="0x0041efce" Value="909090909090"/>
<Line Type="bytes" Address="0x0041f709" Value="9090"/>
<Line Type="bytes" Address="0x0041f73a" Value="9090"/>
<Line Type="bytes" Address="0x004252e9" Value="9090"/>
<Line Type="bytes" Address="0x00425330" Value="9090"/>
</PatchList>
</Metadata>
</Patch>
+7 -5
View File
@@ -118,8 +118,10 @@ def calculate_settings(specs, override_hz=None):
gc_budget_mb = 4608
elif vram_mb >= 3500:
gc_budget_mb = 3072
else:
elif vram_mb >= 2500:
gc_budget_mb = 2048
else:
gc_budget_mb = 1280
# Guest DMEM Pool (based on RAM)
if ram_mb >= 24000:
@@ -198,10 +200,7 @@ def update_run_sh(settings):
dmem = settings["dmem_mb"]
# Target patches string
if target_hz != 60:
patches_str = f"Bug Fix - Texture Corruption Fix;Skip Any Video With X Button;Frame Rate Patch - {target_hz} FPS"
else:
patches_str = "Bug Fix - Texture Corruption Fix;Skip Any Video With X Button"
patches_str = f"Bug Fix - Texture Corruption Fix;Bug Fix - Startup Black Screen;Bug Fix - Intro Crash Fix;Skip Any Video With X Button;Frame Rate Patch - {target_hz} FPS"
# Update or add GOW3_GC_BUDGET_MB near the top
if re.search(r"^export GOW3_GC_BUDGET_MB=.*$", content, re.M):
@@ -291,6 +290,9 @@ def update_user_config_toml(settings):
content = re.sub(r"^readbacks\s*=\s*\w+", "readbacks = true", content, flags=re.M)
content = re.sub(r"^readbackLinearImages\s*=\s*\w+", "readbackLinearImages = true", content, flags=re.M)
content = re.sub(r"^copyGPUBuffers\s*=\s*\w+", "copyGPUBuffers = true", content, flags=re.M)
content = re.sub(r"^directMemoryAccess\s*=\s*\w+", "directMemoryAccess = true", content, flags=re.M)
content = re.sub(r"^pipelineCacheEnable\s*=\s*\w+", "pipelineCacheEnable = true", content, flags=re.M)
content = re.sub(r"^pipelineCacheArchive\s*=\s*\w+", "pipelineCacheArchive = true", content, flags=re.M)
toml_path.write_text(content, encoding="utf-8")
+12 -8
View File
@@ -11,9 +11,9 @@ import json
from pathlib import Path
RESOLUTIONS = ('native', '480p', '720p', '800p', '1440p', '1800p', '4K')
ENGINE_FPS = (60, 120, 144, 240)
DEFAULTS = {'render_resolution': 'native', 'engine_fps': '60', 'deferred_readback': '1',
'stale_readback': '1', 'async_shaders': '1'}
ENGINE_FPS = (30, 40, 60, 90, 120, 144, 165, 240)
DEFAULTS = {'render_resolution': 'native', 'engine_fps': '60', 'deferred_readback': '0',
'stale_readback': '0', 'async_shaders': '1'}
RESOLUTION_PREFIX = 'Resolution Patch'
FPS_PREFIX = 'Frame Rate Patch'
TEXTURE_FIX = 'Bug Fix - Texture Corruption Fix'
@@ -96,12 +96,16 @@ def patch_names(names, options, texture_fix_available=True):
names = [n for n in names if n != TEXTURE_FIX] + [f'{RESOLUTION_PREFIX} - {resolution}']
elif texture_fix_available and TEXTURE_FIX not in names:
names.insert(0, TEXTURE_FIX)
if options['engine_fps'] != '60':
names.append(f'{FPS_PREFIX} - {options["engine_fps"]} FPS')
fps = options.get('engine_fps', '60')
names.append(f'{FPS_PREFIX} - {fps} FPS')
return names
def environment(options):
return {'GOW3_DEFERRED_READBACK': options['deferred_readback'],
'GOW3_STALE_READBACK': options['stale_readback'],
'GOW3_ASYNC_SHADERS': options['async_shaders']}
return {'GOW3_DEFERRED_READBACK': options.get('deferred_readback', '0'),
'GOW3_STALE_READBACK': options.get('stale_readback', '0'),
'GOW3_ASYNC_SHADERS': options.get('async_shaders', '1'),
'GOW3_READBACKS': '1',
'GOW3_READBACK_LINEAR': '1',
'GOW3_FORCE_READBACK': '1',
'GOW3_FPS_LIMIT': options.get('engine_fps', '60')}
+121 -30
View File
@@ -19,24 +19,40 @@ def child(folder, name):
return folder / name
KNOWN_GOW3_DIRS = {
'exec', 'dutch', 'english', 'french', 'german',
'italian', 'polish', 'portuguese', 'russian', 'spanish'
}
def content_root(folder):
"""(root, prefix): where a mod's files are and the game path they go to, or None.
Accepted layouts: <mod>/dvdroot_ps4, <mod>/app0/dvdroot_ps4 and one wrapper folder around
either (an archive extracted into a folder of its name)."""
Accepted layouts: <mod>/dvdroot_ps4, <mod>/app0/dvdroot_ps4, <mod>/EXEC,
<mod>/Game/EXEC, XML patches, and wrapper folders around them."""
folder = Path(folder)
if not folder.is_dir():
return None
for wrapper in ('', 'app0'):
base = child(folder, wrapper) if wrapper else folder
dvdroot = child(base, 'dvdroot_ps4')
if dvdroot.is_dir():
return base, ''
entries = [e for e in folder.iterdir() if not e.name.startswith('.')]
folders = [e for e in entries if e.is_dir()]
if len(folders) == 1 and not [e for e in entries if e.is_file() and
e.suffix.casefold() not in ('.txt', '.md', '.jpg', '.png', '.ini')]:
return content_root(folders[0])
if folder.name.casefold() in ('gow3-windows', 'gow3-linux') or (folder / 'run.py').is_file():
return None
candidates = [folder]
for _ in range(3):
next_candidates = []
for cand in candidates:
if not cand.is_dir():
continue
if child(cand, 'dvdroot_ps4').is_dir():
return cand, ''
for d in KNOWN_GOW3_DIRS:
if child(cand, d).is_dir():
return cand, ''
if list(cand.glob('*.xml')) or child(cand, 'patches').is_dir():
return cand, ''
for sub in cand.iterdir():
if sub.is_dir() and not sub.name.startswith('.'):
next_candidates.append(sub)
candidates = next_candidates
return None
@@ -67,10 +83,12 @@ def selected(root, config):
def mod_files(folder):
"""(game path, source) of every file a mod replaces or adds under dvdroot_ps4."""
"""(game path, source) of every file a mod replaces or adds under the game root."""
layout = content_root(folder)
if not layout:
raise ValueError(f'{folder}: expected dvdroot_ps4 (or chr/, parts/, ...) inside the mod folder')
if list(Path(folder).rglob('*.xml')):
return
raise ValueError(f'{folder}: expected EXEC/ or dvdroot_ps4/ inside the mod folder')
root, prefix = layout
root = root.resolve()
for directory, folders, files in os.walk(root, followlinks=False):
@@ -81,14 +99,24 @@ def mod_files(folder):
for name in sorted(files):
source = directory / name
relative = Path(prefix) / source.relative_to(root) if prefix else source.relative_to(root)
# Readme/metadata stay outside the mounted game. This loader handles assets;
# executable patches use the existing patch compiler, with address validation.
if relative.parts[0].casefold() != 'dvdroot_ps4':
if relative.parts[0].casefold() in ('eboot.bin', 'sce_module', 'sce_sys'):
# Skip documentation / metadata at the mod root
if len(relative.parts) == 1:
if source.suffix.casefold() in ('.txt', '.md', '.json', '.jpg', '.png', '.ini'):
continue
# Strip packaging wrapper prefixes (app0, dvdroot_ps4, game) so files align with the game root
while len(relative.parts) > 1 and relative.parts[0].casefold() in ('app0', 'dvdroot_ps4', 'game'):
relative = Path(*relative.parts[1:])
# Skip patch XML files in mod folder (handled by patches.py)
if source.suffix.casefold() == '.xml':
continue
if relative.parts[0].casefold() in ('eboot.bin', 'sce_module', 'sce_sys'):
if relative.parts[0].casefold() in ('eboot.bin', 'sce_module'):
raise ValueError(f'Executable/system replacement is unsupported: {source}')
continue
if not source.is_file():
raise ValueError(f'Not a regular mod file: {source}')
continue
yield relative, source
@@ -123,6 +151,16 @@ LINKED = {}
def make_link(link, target):
if link.is_symlink() or link.is_file():
try:
link.unlink()
except OSError:
pass
elif link.is_dir():
try:
remove_link(link)
except OSError:
pass
try:
link.symlink_to(target, target_is_directory=target.is_dir())
return
@@ -131,13 +169,19 @@ def make_link(link, target):
raise
if target.is_dir():
import _winapi
_winapi.CreateJunction(str(target), str(link))
try:
_winapi.CreateJunction(str(target), str(link))
except OSError:
# Fallback if junction creation fails across volumes or permissions
shutil.copytree(target, link, dirs_exist_ok=True)
else:
try:
os.link(target, link)
except OSError:
shutil.copy2(target, link)
LINKED[str(link)] = target
p_str = str(link)
LINKED[p_str] = target
LINKED[p_str.casefold()] = target
def symlinks_work(directory):
@@ -152,19 +196,52 @@ def symlinks_work(directory):
def is_link(path):
return path.is_symlink() or str(path) in LINKED or (hasattr(path, 'is_junction') and path.is_junction())
p_str = str(path)
if path.is_symlink():
return True
if p_str in LINKED or p_str.casefold() in LINKED:
return True
if hasattr(path, 'is_junction') and path.is_junction():
return True
if os.name == 'nt':
try:
import stat
mode = os.lstat(path).st_mode
if stat.S_ISLNK(mode):
return True
except OSError:
pass
return False
def link_target(path):
return LINKED.get(str(path)) or path.resolve(strict=True)
p_str = str(path)
return LINKED.get(p_str) or LINKED.get(p_str.casefold()) or path.resolve(strict=True)
def remove_link(path):
if hasattr(path, 'is_junction') and path.is_junction():
os.rmdir(path)
else:
path.unlink()
LINKED.pop(str(path), None)
p_str = str(path)
try:
if path.is_symlink():
path.unlink()
elif hasattr(path, 'is_junction') and path.is_junction():
os.rmdir(path)
elif path.is_dir():
os.rmdir(path)
else:
path.unlink()
except OSError:
try:
if path.is_symlink():
path.unlink()
elif os.path.isdir(path):
os.rmdir(path)
else:
path.unlink()
except OSError:
pass
LINKED.pop(p_str, None)
LINKED.pop(p_str.casefold(), None)
def expand(directory):
@@ -174,7 +251,7 @@ def expand(directory):
if not target.is_dir():
raise ValueError(f'File/directory conflict at {directory.name}')
remove_link(directory)
directory.mkdir()
directory.mkdir(exist_ok=True)
for entry in target.iterdir():
make_link(directory / entry.name, entry)
elif directory.exists() and not directory.is_dir():
@@ -183,6 +260,17 @@ def expand(directory):
directory.mkdir(exist_ok=True)
def on_rm_error(func, path, exc_info):
try:
os.chmod(path, 0o777)
if os.path.isdir(path):
os.rmdir(path)
else:
os.unlink(path)
except OSError:
pass
def build_overlay(game, out, mods):
game = Path(game).resolve(strict=True)
replacements = []
@@ -201,6 +289,9 @@ def build_overlay(game, out, mods):
return game
out = Path(out).resolve()
out.mkdir(parents=True, exist_ok=True)
for old_dir in out.glob('mod-game-*'):
if old_dir.is_dir():
shutil.rmtree(old_dir, onerror=on_rm_error)
if os.name == 'nt' and not symlinks_work(out) and out.drive.casefold() != game.drive.casefold():
# Hard links only reach files on the same volume: the overlay goes beside the game.
out = game.parent
@@ -227,7 +318,7 @@ def build_overlay(game, out, mods):
print(f'Mods: {replaced} game files replaced, {added} added', file=sys.stderr)
return overlay
except BaseException:
shutil.rmtree(overlay)
shutil.rmtree(overlay, onerror=on_rm_error)
raise
+2 -2
View File
@@ -134,7 +134,7 @@ def external_patches(directory, app_version, exclude, ids):
key is "<file name>/<patch name>" (the launcher's selection, patches.json)."""
found=[]
directory=Path(directory)
for path in sorted(directory.glob('*.xml')) if directory.is_dir() else []:
for path in sorted(directory.rglob('*.xml')) if directory.is_dir() else []:
if path.resolve()==Path(exclude).resolve(): continue # the built-in file (patches/ in a checkout)
try:
root=ET.parse(path).getroot()
@@ -144,7 +144,7 @@ def external_patches(directory, app_version, exclude, ids):
file_ids={e.text.strip() for e in root.iter('ID') if e.text}
if file_ids and not file_ids&ids: continue
for meta in root.iter('Metadata'):
if meta.get('AppVer')==app_version and meta.get('AppElf','eboot.bin')=='eboot.bin':
if (meta.get('AppVer')==app_version or meta.get('AppVer') in ('01.00', '01.02')) and meta.get('AppElf','eboot.bin')=='eboot.bin':
found.append((f'{path.name}/{meta.get("Name")}',path,meta))
return found
Binary file not shown.
+181
View File
@@ -0,0 +1,181 @@
{
"title_id": "CUSA01715",
"last_updated": 1794235113,
"trophies": {
"0": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"1": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"2": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"3": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"4": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"5": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"6": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"7": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"8": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"9": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"10": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"11": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"12": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"13": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"14": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"15": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"16": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"17": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"18": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"19": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"20": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"21": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"22": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"23": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"24": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"25": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"26": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"27": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"28": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"29": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"30": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"31": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"32": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"33": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
},
"34": {
"unlocked": false,
"unlocked_at": null,
"unlocked_at_formatted": null
}
}
}