Compare commits

...
24 Commits
Author SHA1 Message Date
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
73 changed files with 5373 additions and 658 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ user/
out/
GLCache/
.runtime/
mod-game-*/
vkd3d-proton.cache
fsr4_shaders/
.local/
+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
```
+18 -4
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.
@@ -71,18 +75,28 @@ Launcher for God Of War 3 PC on Linux and Windows
### Sections of the launcher
**Home Screen**
<img width="2287" height="1241" alt="image" src="https://github.com/user-attachments/assets/937f6239-606f-439e-9924-f8df6549623e" />
<img width="1901" height="1025" alt="image" src="https://github.com/user-attachments/assets/31385740-38a4-4ddd-bf5b-3d795930c51f" />
**Graphics Settings**
<img width="2543" height="1388" alt="image" src="https://github.com/user-attachments/assets/ed939f69-93af-498a-a860-209bbb75bbd0" />
<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" />
**Keyboard Configuration**
<img width="3055" height="1675" alt="image" src="https://github.com/user-attachments/assets/fb06c6b1-2cf0-43a7-a212-aefe5082e087" />
**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" />
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+19
View File
@@ -415,3 +415,22 @@ void _ZN5ImGui11ProgressBarEfRK6ImVec2PKc(float fraction, const 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
Binary file not shown.
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

+3 -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,7 +1671,7 @@ dependencies = [
[[package]]
name = "launcher"
version = "2.0.0"
version = "2.2.0"
dependencies = [
"eframe",
"egui_extras",
@@ -2445,7 +2332,7 @@ checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6"
dependencies = [
"bytemuck",
"color",
"kurbo 0.13.1",
"kurbo",
"linebender_resource_handle",
"smallvec",
]
@@ -2543,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"
@@ -2598,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"
@@ -2838,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"
@@ -2879,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"
@@ -2955,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"
@@ -3134,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"
@@ -3278,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"
@@ -3418,7 +3180,6 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
"png 0.17.16",
"tiny-skia-path",
]
@@ -3579,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"
@@ -3626,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"
@@ -3714,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"
@@ -3956,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"
@@ -4509,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"
@@ -4685,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"
+3 -3
View File
@@ -1,12 +1,12 @@
[package]
name = "launcher"
version = "2.0.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"
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

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: 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.
+2 -2
View File
@@ -6,8 +6,8 @@ fn main() {
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", "1.9.0.0");
res.set("FileVersion", "1.9.0.0");
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);
}
+801 -252
View File
File diff suppressed because it is too large. Load diff
+43 -4
View File
@@ -241,8 +241,12 @@ pub struct LauncherConfig {
#[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,
@@ -292,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
@@ -320,7 +334,7 @@ fn default_last_commit() -> String {
"c5638d9956002b8a9f09a240028f6793ddf7b2f8".to_string()
}
fn default_launcher_version() -> String {
"v2.0".to_string()
"v2.2".to_string()
}
fn default_render_resolution() -> String {
"native".to_string()
@@ -338,7 +352,9 @@ impl Default for LauncherConfig {
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(),
@@ -358,6 +374,8 @@ impl Default for LauncherConfig {
deadzone_left: default_deadzone(),
deadzone_right: default_deadzone(),
keyboard_bindings: KeyboardBindings::default(),
discord_rpc_enabled: true,
discord_client_id: default_discord_client_id(),
}
}
}
@@ -537,6 +555,10 @@ impl LauncherConfig {
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);
}
@@ -782,11 +804,14 @@ mod tests {
}
#[test]
fn test_launcher_version_is_v20() {
assert_eq!(default_launcher_version(), "v2.0");
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]
@@ -843,4 +868,18 @@ mod tests {
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);
}
}
File diff suppressed because it is too large. Load diff
+64 -8
View File
@@ -114,6 +114,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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)",
@@ -148,11 +150,18 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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, "release_notes_title") => "God of War III Launcher - Sürüm Notları (v2.0)",
(Language::Turkish, "release_notes_content") => "🔥 GOD OF WAR III LINUX LAUNCHER v2.0 YENİLİKLERİ:\n\n• 🏛️ Yeni Sinematik Konsol Arayüzü:\n Orijinal God of War Ascension ana menüsü baz alınarak tasarlanan tam ekran arka planlı, PS3/PS4 tarzı konsol menüsü ve God of War yazı fontu.\n\n• ⚡ Resmi Steam Tarzı Shader Pre-Caching (28.131 Shader):\n Tüm haritalar, efektler ve patron dövüşleri taranarak derlenmiş temiz 28.000+ shader önbelleği. 17 MB paket olarak tek tıkla indirilir ve takılmaları sıfırlar.\n\n• 🛡️ Spartan Rage Shader Yükleme Ekranı:\n 'Shader derlenirken oyun açılsın' seçeneğiyle, boru hatları %100 olana kadar Spartan Rage barı dolar veya oyun hemen başlatılabilir.\n\n• 🚀 Grafik ve Stabilizasyon Optimizasyonları:\n - Anlık geri okuma ile sararma ve doku bozulmaları giderildi.\n - Asenkron geri okuma ile FPS stabilitesi sağlandı.\n - Otomatik VRAM koruması devrede.\n\n• 🌐 4 Dil Desteği: Türkçe, English, Deutsch, Español.",
(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",
@@ -197,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ı",
@@ -369,6 +383,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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)",
@@ -403,11 +419,18 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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, "release_notes_title") => "God of War III Launcher - Release Notes (v2.0)",
(Language::English, "release_notes_content") => "🔥 GOD OF WAR III LINUX LAUNCHER v2.0 CHANGELOG:\n\n• 🏛️ New Cinematic Console UI:\n Designed after the authentic God of War Ascension main menu with full-bleed wallpaper, authentic Spartan typography, and console navigation.\n\n• ⚡ Official Steam-Style Shader Pre-Caching (28,131 Shaders):\n Pre-compiled shader cache covering all maps, cutscenes, effects, and boss encounters. Downloaded with 1-click as a 17 MB compressed package, completely eliminating in-game shader stutter.\n\n• 🛡️ Spartan Rage Shader Loading Screen:\n With 'Launch while compiling shaders' toggle, shaders compile before launch showing the Spartan Rage bar, or compile in the background for instant boot.\n\n• 🚀 Graphics & Stability Fixes:\n - Instant readbacks fix completely resolves yellow tint and corrupted textures in snow and bright scenes.\n - Asynchronous readbacks decouple CPU from GPU render fences, maximizing frame pacing.\n - Automatic VRAM overflow protection.\n\n• 🌐 4 Languages: Turkish, English, German, Spanish.",
(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",
@@ -452,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",
@@ -624,6 +652,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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)",
@@ -658,11 +688,18 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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, "release_notes_title") => "God of War III Launcher - Versionshinweise (v2.0)",
(Language::German, "release_notes_content") => "🔥 GOD OF WAR III LINUX LAUNCHER v2.0 NEUIGKEITEN:\n\n• 🏛️ Neues kinoreifes Konsolen-Design:\n Gestaltet nach dem Vorbild des God of War Ascension Hauptmenüs mit Vollbild-Hintergrund, originaler Spartan-Schriftart und Konsolen-Navigation.\n\n• ⚡ Offizieller Shader-Cache (28.131 Shader):\n Vorkompilierter Shader-Cache für alle Bereiche und Bosse. Als 17-MB-Paket mit einem Klick ladbar, beseitigt Mikroruckler komplett.\n\n• 🛡️ Spartan Rage Ladebildschirm:\n Option für sofortigen Start oder Warten bis zur 100%igen Kompilierung mit aufsteigender Spartan Rage Anzeige.\n\n• 🚀 Grafik- & Leistungsoptimierungen:\n - Sofortige Readbacks beheben Gelbstich und Texturfehler in hellen Szenen.\n - Asynchrone Readbacks für flüssige Bildraten und optimale CPU-GPU-Synchronisation.\n\n• 🌐 4 Sprachen: Türkisch, Englisch, Deutsch, Spanisch.",
(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",
@@ -705,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",
@@ -877,6 +919,8 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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)",
@@ -911,11 +955,18 @@ pub fn text(lang: Language, key: &str) -> &'static str {
(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, "release_notes_title") => "God of War III Launcher - Notas de la Versión (v2.0)",
(Language::Spanish, "release_notes_content") => "🔥 NOVEDADES DE GOD OF WAR III LINUX LAUNCHER v2.0:\n\n• 🏛️ Nueva Interfaz Cinemática de Consola:\n Inspirada en el menú principal de God of War Ascension, con fondo a pantalla completa, tipografía espartana oficial y navegación de consola.\n\n• ⚡ Caché Oficial de Shaders estilo Steam (28.131 Shaders):\n Caché oficial precompilada para todos los mapas y jefes. Se descarga en un clic (17 MB) eliminando congelaciones y tirones.\n\n• 🛡️ Pantalla de Carga Spartan Rage:\n Permite compilar los shaders al 100% antes de iniciar con la barra Spartan Rage o arrancar de inmediato compilando en segundo plano.\n\n• 🚀 Mejoras de Gráficos y Estabilidad:\n - Lecturas instantáneas corrigen el tinte amarillo y texturas corruptas.\n - Lecturas asíncronas para máxima fluidez y tasa de cuadros estable.\n\n• 🌐 4 Idiomas: Turco, Inglés, Alemán, Español.",
(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",
@@ -958,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",
+3
View File
@@ -10,6 +10,9 @@ mod tuner;
mod updater;
mod shader_downloader;
pub mod mods_manager;
pub mod discord_rpc;
pub mod win_input;
pub mod trophy;
use app::LauncherApp;
use eframe::egui;
+5 -1
View File
@@ -53,7 +53,11 @@ pub fn scan_installed_mods(base_dir: &Path, disabled_mods: &[String]) -> Vec<Ins
continue;
}
let folder_name = entry.file_name().to_string_lossy().to_string();
if folder_name.starts_with('.') {
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;
}
+402 -6
View File
@@ -70,9 +70,27 @@ pub fn ensure_runtime_assets(base_dir: &Path) -> Result<(PathBuf, PathBuf), Stri
let _ = fs::write(&patch_xml, PATCH_GOW3_XML);
}
ensure_game_icon_bmp(base_dir);
Ok((scripts_dir, patches_dir))
}
/// Ensures gow3.bmp is available for SDL3 window icon loading.
pub fn ensure_game_icon_bmp(base_dir: &Path) {
let bmp_path = base_dir.join("gow3.bmp");
if !bmp_path.is_file() {
let png_path = base_dir.join("gow3.png");
let img = if png_path.is_file() {
image::open(&png_path).ok()
} else {
image::load_from_memory(include_bytes!("../../gow3.png")).ok()
};
if let Some(img) = img {
let _ = img.save_with_format(&bmp_path, image::ImageFormat::Bmp);
}
}
}
/// Finds an available Python 3 interpreter (checking local runtime bundle first, then system PATH)
pub fn find_python(base_dir: &Path) -> Result<String, String> {
// 1. Check local runtime python folder (.runtime/python/python.exe or bin/python3)
@@ -313,6 +331,224 @@ fn find_file_recursive(dir: &Path, filename: &str) -> Option<PathBuf> {
None
}
fn collect_xml_files(dir: &Path, xml_paths: &mut Vec<PathBuf>) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext.eq_ignore_ascii_case("xml") {
xml_paths.push(path);
}
}
} else if path.is_dir() {
collect_xml_files(&path, xml_paths);
}
}
}
}
pub fn build_rust_mod_overlay(
game_dir: &Path,
out_dir: &Path,
mods_dir: &Path,
config: &LauncherConfig,
) -> Option<PathBuf> {
if !game_dir.is_dir() || !mods_dir.is_dir() {
return None;
}
let mut enabled_mods = Vec::new();
if let Ok(entries) = fs::read_dir(mods_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with('.')
|| name.eq_ignore_ascii_case("gow3-windows")
|| name.eq_ignore_ascii_case("gow3-linux")
|| path.join("run.py").is_file()
{
continue;
}
if !config.disabled_mods.contains(&name) {
enabled_mods.push((name, path));
}
}
}
if enabled_mods.is_empty() {
return None;
}
enabled_mods.sort_by(|a, b| a.0.cmp(&b.0));
let overlay_dir = out_dir.join("mod-game-rust-overlay");
let _ = fs::remove_dir_all(&overlay_dir);
if fs::create_dir_all(&overlay_dir).is_err() {
return None;
}
fn link_or_copy_dir(target: &Path, link: &Path) -> bool {
#[cfg(unix)]
{
if std::os::unix::fs::symlink(target, link).is_ok() {
return true;
}
}
#[cfg(windows)]
{
if std::os::windows::fs::symlink_dir(target, link).is_ok() {
return true;
}
use std::os::windows::process::CommandExt;
let status = Command::new("cmd")
.args(["/C", "mklink", "/J"])
.arg(link)
.arg(target)
.creation_flags(0x08000000) // CREATE_NO_WINDOW
.status();
if let Ok(st) = status {
if st.success() && link.is_dir() {
return true;
}
}
}
// Fallback: create directory and link/copy contents
if fs::create_dir_all(link).is_ok() {
if let Ok(entries) = fs::read_dir(target) {
for entry in entries.flatten() {
let entry_target = entry.path();
let entry_link = link.join(entry.file_name());
if entry_target.is_dir() {
let _ = link_or_copy_dir(&entry_target, &entry_link);
} else {
let _ = link_or_copy_file(&entry_target, &entry_link);
}
}
return true;
}
}
false
}
fn link_or_copy_file(target: &Path, link: &Path) -> bool {
#[cfg(unix)]
{
if std::os::unix::fs::symlink(target, link).is_ok() {
return true;
}
}
if fs::hard_link(target, link).is_ok() {
return true;
}
fs::copy(target, link).is_ok()
}
// Link or copy base game root entries into overlay
if let Ok(entries) = fs::read_dir(game_dir) {
for entry in entries.flatten() {
let target = entry.path();
let link = overlay_dir.join(entry.file_name());
if target.is_dir() {
let _ = link_or_copy_dir(&target, &link);
} else {
let _ = link_or_copy_file(&target, &link);
}
}
}
fn find_content_root(mod_path: &Path) -> Option<PathBuf> {
let candidates = [
mod_path.join("dvdroot_ps4"),
mod_path.join("app0/dvdroot_ps4"),
mod_path.join("Game/EXEC"),
mod_path.join("EXEC"),
];
for c in candidates {
if c.is_dir() {
return Some(c);
}
}
None
}
fn apply_mod_dir(src: &Path, dst: &Path, base_game_src: &Path, count: &mut usize) {
if let Ok(entries) = fs::read_dir(src) {
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name();
let dest_path = dst.join(&name);
let base_child = base_game_src.join(&name);
if path.is_dir() {
let is_link = dest_path.is_symlink();
if is_link {
let _ = fs::remove_file(&dest_path);
#[cfg(windows)]
let _ = fs::remove_dir(&dest_path);
let _ = fs::create_dir_all(&dest_path);
if base_child.is_dir() {
if let Ok(base_entries) = fs::read_dir(&base_child) {
for be in base_entries.flatten() {
let bet = be.path();
let bel = dest_path.join(be.file_name());
if bet.is_dir() {
let _ = link_or_copy_dir(&bet, &bel);
} else {
let _ = link_or_copy_file(&bet, &bel);
}
}
}
}
} else if !dest_path.is_dir() {
let _ = fs::create_dir_all(&dest_path);
}
apply_mod_dir(&path, &dest_path, &base_child, count);
} else if path.is_file() {
if path.extension().map(|e| e.eq_ignore_ascii_case("xml")).unwrap_or(false) {
continue;
}
if dest_path.is_symlink() || dest_path.is_file() {
let _ = fs::remove_file(&dest_path);
}
if fs::copy(&path, &dest_path).is_ok() {
*count += 1;
}
}
}
}
}
let mut applied = 0;
for (_name, path) in enabled_mods {
if let Some(root) = find_content_root(&path) {
apply_mod_dir(&root, &overlay_dir, game_dir, &mut applied);
}
}
// Safety validation: Ensure eboot.bin and EXEC directory are present in overlay.
// If missing, NEVER return a broken directory to avoid black screen on game launch!
if !overlay_dir.join("eboot.bin").is_file() || !overlay_dir.join("EXEC").is_dir() {
crate::log_error!(
"Rust mod overlay validation failed (missing eboot.bin or EXEC). Falling back to original game_dir."
);
let _ = fs::remove_dir_all(&overlay_dir);
return None;
}
if applied > 0 {
crate::log_info!("Rust mod overlay successfully built with {} modified files", applied);
Some(overlay_dir)
} else {
None
}
}
#[allow(dead_code)]
pub fn download_windows_engine(base_dir: &Path, is_alternative: bool) -> Result<String, String> {
let dest_bin = base_dir.join("bin");
@@ -747,6 +983,7 @@ pub fn adjust_windows_windowed_mode(target_pid: u32) {
unsafe extern "system" {
fn GetWindowThreadProcessId(hwnd: *mut c_void, lpdwProcessId: *mut u32) -> u32;
fn GetWindowTextA(hwnd: *mut c_void, lp_string: *mut u8, n_max_count: i32) -> i32;
fn EnumWindows(lpEnumFunc: unsafe extern "system" fn(*mut c_void, isize) -> i32, lParam: isize) -> i32;
fn IsWindowVisible(hwnd: *mut c_void) -> i32;
fn GetWindowLongPtrA(hWnd: *mut c_void, nIndex: i32) -> isize;
@@ -762,7 +999,15 @@ pub fn adjust_windows_windowed_mode(target_pid: u32) {
let ctx = &mut *(lparam as *mut EnumContext);
let mut proc_id = 0u32;
GetWindowThreadProcessId(hwnd, &mut proc_id);
if proc_id == ctx.pid && IsWindowVisible(hwnd) != 0 {
if proc_id != std::process::id() && proc_id == ctx.pid && IsWindowVisible(hwnd) != 0 {
let mut title_buf = [0u8; 128];
let len = 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 1;
}
}
ctx.hwnd = hwnd;
return 0;
}
@@ -848,6 +1093,9 @@ pub fn launch_gow3(
running_pid: Option<Arc<AtomicU32>>,
) -> Result<i32, String> {
crate::log_info!("Initiating God of War III launch sequence...");
// Synchronize configuration files (gow3.ini, user/config.toml, etc.) before launch
let _ = crate::tuner::apply_settings(base_dir, config);
let game_dir = Path::new(&config.game_dir);
if !game_dir.join("eboot.bin").is_file() {
let msg = format!("Oyun klasöründe ('{}') 'eboot.bin' bulunamadı!", game_dir.display());
@@ -918,6 +1166,7 @@ pub fn launch_gow3(
}
let mut effective_app0 = game_dir.to_path_buf();
let mut mods_applied = false;
if let Ok(output) = mods_cmd
.env("PYTHONPATH", &scripts_path)
@@ -936,11 +1185,20 @@ pub fn launch_gow3(
if candidate.is_dir() {
crate::log_info!("Mods active. Mounting overlay directory: {:?}", candidate);
effective_app0 = candidate;
mods_applied = true;
}
}
}
}
if !mods_applied && mods_dir.is_dir() {
if let Some(rust_overlay) = build_rust_mod_overlay(game_dir, &out_dir, &mods_dir, config) {
crate::log_info!("Rust mod overlay active (fallback). Mounting overlay: {:?}", rust_overlay);
effective_app0 = rust_overlay;
mods_applied = true;
}
}
// 2. Run prepare.py (ELF parsing and analysis)
let status = Command::new(&python)
.arg(scripts_path.join("prepare.py"))
@@ -1055,12 +1313,30 @@ pub fn launch_gow3(
patch_list.push("Bug Fix - Startup Black Screen".to_string());
patch_list.push("Bug Fix - Intro Crash Fix".to_string());
patch_list.push("Bug Fix - Trophy Unlock Fix".to_string());
if config.bonus_play_fix {
patch_list.push("Bug Fix - Bonus Play QTE and Enemy Spawning Fix".to_string());
}
if config.skip_cinematics {
patch_list.push("Skip Any Video With X Button".to_string());
}
if config.patch_delta_time {
patch_list.push(format!("Frame Rate Patch - {} FPS", config.target_fps));
let fps_patch = match config.target_fps {
30 => Some("Frame Rate Patch - 30 FPS"),
40 => Some("Frame Rate Patch - 40 FPS"),
60 => Some("Frame Rate Patch - 60 FPS"),
90 => Some("Frame Rate Patch - 90 FPS"),
120 => Some("Frame Rate Patch - 120 FPS"),
144 => Some("Frame Rate Patch - 144 FPS"),
165 => Some("Frame Rate Patch - 165 FPS"),
240 => Some("Frame Rate Patch - 240 FPS"),
_ => None,
};
if let Some(p) = fps_patch {
patch_list.push(p.to_string());
}
}
if config.disable_motion_blur {
patch_list.push("Graphics - Disable Motion Blur".to_string());
@@ -1070,6 +1346,54 @@ pub fn launch_gow3(
}
let patches_str = patch_list.join(";");
// Prepare active patches directory: copy built-in patch XML + XML patches from enabled mods
let active_patches_dir = out_dir.join("active_patches");
let _ = fs::remove_dir_all(&active_patches_dir);
let _ = fs::create_dir_all(&active_patches_dir);
// Copy built-in XML
let builtin_xml = patches_path.join("God_of_War_III_Remastered.xml");
if builtin_xml.is_file() {
let _ = fs::copy(&builtin_xml, active_patches_dir.join("God_of_War_III_Remastered.xml"));
}
// Collect and copy all XML patches from enabled mods
if mods_dir.is_dir() {
if let Ok(entries) = fs::read_dir(&mods_dir) {
for entry in entries.flatten() {
let mod_path = entry.path();
if !mod_path.is_dir() {
continue;
}
let mod_name = entry.file_name().to_string_lossy().to_string();
if mod_name.starts_with('.')
|| mod_name.eq_ignore_ascii_case("gow3-windows")
|| mod_name.eq_ignore_ascii_case("gow3-linux")
|| mod_path.join("run.py").is_file()
{
continue;
}
if config.disabled_mods.contains(&mod_name) {
continue;
}
let mut xml_files = Vec::new();
collect_xml_files(&mod_path, &mut xml_files);
for xml_file in xml_files {
let file_name = xml_file.file_name().unwrap_or_default().to_string_lossy();
let safe_mod: String = mod_name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
let dest = active_patches_dir.join(format!("{}_{}", safe_mod, file_name));
if fs::copy(&xml_file, &dest).is_ok() {
crate::log_info!("Mod XML patch applied: {:?}", xml_file);
}
}
}
}
}
let _ = Command::new(&python)
.arg(scripts_path.join("patches.py"))
.arg("--out")
@@ -1079,7 +1403,7 @@ pub fn launch_gow3(
.arg("--game-dir")
.arg(game_dir)
.arg("--patches-dir")
.arg(&patches_path)
.arg(&active_patches_dir)
.env("PYTHONPATH", &scripts_path)
.env("GOW3_FORCE_PATCHES", "1")
.env("GOW3_PATCHES_ONLY", "1")
@@ -1287,18 +1611,41 @@ pub fn launch_gow3(
cmd.env("GOW3_PRESENT_THREAD", "1");
cmd.env("GOW3_ASYNC_FENCES", "1");
cmd.env("GOW3_ASYNC_SHADERS", "1");
cmd.env("GOW3_FPS_LIMIT", config.target_fps.to_string());
let target_fps_str = if config.target_fps > 0 {
config.target_fps.to_string()
} else {
"0".to_string()
};
cmd.env("GOW3_FPS_LIMIT", &target_fps_str);
cmd.env("GOW3_ENGINE_FPS", if config.target_fps > 0 { &target_fps_str } else { "60" });
cmd.env("GOW3_DEFAULT_ENGINE_FPS", if config.target_fps > 0 { &target_fps_str } else { "60" });
cmd.env("GOW3_VBLANK_HZ", if config.target_fps > 0 { &target_fps_str } else { "60" });
cmd.env("GOW3_VSYNC", if config.vsync { "1" } else { "0" });
let effective_budget = config.get_effective_vram_budget();
cmd.env("GOW3_GC_BUDGET_MB", effective_budget.to_string());
cmd.env("GOW3_DMEM_POOL_MB", config.dmem_mb.to_string());
cmd.env("GOW3_DMEM_MB", config.dmem_mb.to_string());
cmd.env("GOW3_DEFAULT_ENGINE_FPS", config.target_fps.to_string());
cmd.env("GOW3_VBLANK_HZ", config.target_fps.to_string());
cmd.env("GOW3_PATCHES", &patches_str);
cmd.env("GOW3_PATCHES_ONLY", "1");
cmd.env("GOW3_CONFIG", &config_ini);
cmd.env("GOW3_GAME_DIR", game_dir);
if mods_applied {
cmd.env("GOW3_MODS_DIR", &effective_app0);
} else {
cmd.env_remove("GOW3_MODS_DIR");
}
cmd.env("GOW3_MOD_TRACE", "1");
// Native SDL3 window & taskbar icon for gow3-probe
let icon_bmp = base_dir.join("gow3.bmp");
let icon_png = base_dir.join("gow3.png");
if icon_bmp.is_file() {
cmd.env("GOW3_WINDOW_ICON", &icon_bmp);
} else if icon_png.is_file() {
cmd.env("GOW3_WINDOW_ICON", &icon_png);
}
cmd.env(
"GOW3_FULLSCREEN",
if config.display_mode != crate::config::DisplayMode::Windowed {
@@ -1372,6 +1719,10 @@ pub fn launch_gow3(
pid_holder.store(child_pid, Ordering::SeqCst);
}
// Windows Input Bridge: Install low-level mouse hook & 1ms timer resolution while the game is running
#[cfg(target_os = "windows")]
let _win_input_guard = crate::win_input::start_mouse_hook(child_pid, config.hide_mouse_cursor);
#[cfg(target_os = "windows")]
if config.display_mode == crate::config::DisplayMode::Windowed {
adjust_windows_windowed_mode(child_pid);
@@ -1420,4 +1771,49 @@ mod tests {
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_collect_xml_files() {
let temp_dir = std::env::temp_dir().join("gow3_test_collect_xml");
let _ = fs::remove_dir_all(&temp_dir);
let sub = temp_dir.join("subdir/nested");
fs::create_dir_all(&sub).unwrap();
fs::write(sub.join("patch1.xml"), "<Patch/>").unwrap();
fs::write(temp_dir.join("patch2.XML"), "<Patch/>").unwrap();
fs::write(temp_dir.join("readme.txt"), "info").unwrap();
let mut collected = Vec::new();
collect_xml_files(&temp_dir, &mut collected);
assert_eq!(collected.len(), 2);
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_build_rust_mod_overlay() {
let temp_dir = std::env::temp_dir().join("gow3_test_rust_mod_overlay");
let _ = fs::remove_dir_all(&temp_dir);
let game_dir = temp_dir.join("CUSA01715");
let out_dir = temp_dir.join("out");
let mods_dir = temp_dir.join("mods");
fs::create_dir_all(game_dir.join("EXEC/WAD")).unwrap();
fs::write(game_dir.join("EXEC/WAD/ORIG.WAD"), b"ORIG").unwrap();
fs::write(game_dir.join("eboot.bin"), b"EBOOT").unwrap();
let mod_a = mods_dir.join("ModA/dvdroot_ps4/EXEC/WAD");
fs::create_dir_all(&mod_a).unwrap();
fs::write(mod_a.join("ORIG.WAD"), b"MODDED").unwrap();
let config = LauncherConfig::default();
let overlay = build_rust_mod_overlay(&game_dir, &out_dir, &mods_dir, &config);
assert!(overlay.is_some());
let ov = overlay.unwrap();
let modded_file = ov.join("EXEC/WAD/ORIG.WAD");
assert!(modded_file.is_file());
assert_eq!(fs::read(&modded_file).unwrap(), b"MODDED");
let _ = fs::remove_dir_all(&temp_dir);
}
}
File diff suppressed because it is too large. Load diff
+11 -2
View File
@@ -34,15 +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..];
@@ -218,6 +219,14 @@ fn update_run_sh(base_dir: &Path, config: &LauncherConfig) -> Result<(), String>
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(())
}
+6 -4
View File
@@ -5,7 +5,7 @@ use std::process::Command;
use crate::config::LauncherConfig;
use crate::tuner::apply_settings;
pub const BUILTIN_VERSION: &str = "v2.0";
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";
@@ -462,10 +462,9 @@ mod tests {
assert!(info.is_new);
}
// Test with current version v2.0: should NOT report as an update
let res_current = check_github_update(Path::new(".."), "test_dummy_sha", "v2.0");
// 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 {
// Because remote release on GitHub is <= v2.0, and current is v2.0, and files are present, is_new must be false!
assert!(!info.is_new);
}
}
@@ -477,6 +476,9 @@ mod tests {
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]
+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::*;
+20 -7
View File
@@ -7,7 +7,9 @@
"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,
@@ -21,8 +23,17 @@
"cpu_threads": 20
},
"last_git_commit": "0b4abe07acb711848d1b5d72402a9de8c0ad9844",
"launcher_version": "v2.0",
"disabled_mods": [],
"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,
@@ -36,10 +47,10 @@
"deadzone_left": 2,
"deadzone_right": 2,
"keyboard_bindings": {
"triangle": "u",
"circle": "j",
"cross": "g",
"square": "t",
"triangle": "mouse2",
"circle": "f",
"cross": "space",
"square": "mouse1",
"l1": "q",
"r1": "e",
"l2": "z",
@@ -60,5 +71,7 @@
"axis_right_x_plus": "kp6",
"axis_right_y_minus": "kp8",
"axis_right_y_plus": "kp2"
}
},
"discord_rpc_enabled": true,
"discord_client_id": "1440140183675867156"
}
+10 -1
View File
@@ -1,4 +1,13 @@
{
"disabled": [],
"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": []
}
+17
View File
@@ -31,6 +31,12 @@
<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="0x005f0c5b" Value="56030000"/>
@@ -201,4 +207,15 @@
<Line Type="bytes" Address="0x00566090" Value="c3"/>
</PatchList>
</Metadata>
<Metadata Title="God of War III Remastered" Name="Bug Fix - Trophy Unlock Fix" Note="Fixes emulator trophy count bounds check and disables Bonus Play trophy restriction in eboot.bin, enabling all 35 trophies to unlock seamlessly." Author="gow3-launcher" PatchVer="2.0" AppVer="01.02" AppElf="eboot.bin" isEnabled="true">
<PatchList>
<Line Type="bytes" Address="0x005f4510" Value="b840000000c3"/>
<Line Type="bytes" Address="0x00422f88" Value="909090909090"/>
<Line Type="bytes" Address="0x00422fce" Value="909090909090"/>
<Line Type="bytes" Address="0x00423709" Value="9090"/>
<Line Type="bytes" Address="0x0042373a" Value="9090"/>
<Line Type="bytes" Address="0x004292e9" Value="9090"/>
<Line Type="bytes" Address="0x00429330" Value="9090"/>
</PatchList>
</Metadata>
</Patch>
+101 -30
View File
@@ -29,25 +29,30 @@ 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, <mod>/EXEC,
<mod>/app0/EXEC and one wrapper folder around either."""
<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
if not base.is_dir():
continue
dvdroot = child(base, 'dvdroot_ps4')
if dvdroot.is_dir():
return base, ''
for d in KNOWN_GOW3_DIRS:
if child(base, d).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', '.json')]:
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
@@ -81,6 +86,8 @@ def mod_files(folder):
"""(game path, source) of every file a mod replaces or adds under the game root."""
layout = content_root(folder)
if not layout:
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()
@@ -96,10 +103,14 @@ def mod_files(folder):
if len(relative.parts) == 1:
if source.suffix.casefold() in ('.txt', '.md', '.json', '.jpg', '.png', '.ini'):
continue
# Strip packaging wrapper prefixes (app0, dvdroot_ps4) so files align with the game root
while len(relative.parts) > 1 and relative.parts[0].casefold() in ('app0', 'dvdroot_ps4'):
# 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}')
@@ -140,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
@@ -148,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):
@@ -169,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):
@@ -191,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():
@@ -200,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 = []
@@ -220,7 +291,7 @@ def build_overlay(game, out, mods):
out.mkdir(parents=True, exist_ok=True)
for old_dir in out.glob('mod-game-*'):
if old_dir.is_dir():
shutil.rmtree(old_dir, ignore_errors=True)
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
@@ -247,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
+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
}
}
}