Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9147960de5 | ||
|
|
3a96e9845b | ||
|
|
3992455718 | ||
|
|
08da3bec2c | ||
|
|
04f61833ea | ||
|
|
a60dc8bace | ||
|
|
7b8d9155fd | ||
|
|
ec0a5893c7 | ||
|
|
6bb6349c41 | ||
|
|
8ba512f45e | ||
|
|
0136d4a426 | ||
|
|
71b9cd8596 | ||
|
|
b6409869aa | ||
|
|
ff7c026bba | ||
|
|
f119d84439 | ||
|
|
4b5ec4f7f5 | ||
|
|
5ddf4eab9d | ||
|
|
839dabca0d | ||
|
|
319a4c56e8 | ||
|
|
0c9f36c292 | ||
|
|
bdb896b279 | ||
|
|
b435c8344b | ||
|
|
12dea62e3b | ||
|
|
192f9164d1 |
No files matched your search
@@ -10,6 +10,7 @@ user/
|
||||
out/
|
||||
GLCache/
|
||||
.runtime/
|
||||
mod-game-*/
|
||||
vkd3d-proton.cache
|
||||
fsr4_shaders/
|
||||
.local/
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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" />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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::*;
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||