mirror of
https://github.com/ScreamAngel/gow3_launcher.git
synced 2026-11-08 17:48:49 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b4abe07ac | ||
|
|
6d7a6fad1b | ||
|
|
6d8770767c | ||
|
|
f2d09ade09 | ||
|
|
7f6505c5bc | ||
|
|
1b7dd8aeb1 | ||
|
|
d03ce5b8ab | ||
|
|
fe1874c93c | ||
|
|
fcee9a136c | ||
|
|
b548073f5c | ||
|
|
f5e9a0f10e | ||
|
|
17daa20a36 | ||
|
|
f6a1ade73d |
No files matched your search
@@ -1,11 +1,11 @@
|
||||
# gow3_pclauncher
|
||||
Launcher for God Of War 3 PC on Linux
|
||||
Launcher for God Of War 3 PC on Linux and Windows
|
||||
|
||||
# ⚡ God of War® III Remastered - Linux Port & Native Launcher
|
||||
# ⚡ God of War® III Remastered - Linux-Windows Port & Native Launcher
|
||||
|
||||
An optimized Linux 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.
|
||||
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.
|
||||
|
||||
[](https://kernel.org)
|
||||
[](https://kernel.org)
|
||||
[](https://www.rust-lang.org)
|
||||
[](https://www.vulkan.org)
|
||||
[](LICENSE)
|
||||
@@ -32,7 +32,7 @@ Launcher for God Of War 3 PC on Linux
|
||||
|
||||
| Component | Minimum | Recommended |
|
||||
|---|---|---|
|
||||
| **OS** | Linux (Arch, Ubuntu, Fedora, SteamOS, Debian) | Any modern 64-bit Linux distro |
|
||||
| **OS** | Linux (Arch, Ubuntu, Fedora, SteamOS, Debian) and Windows 10-11 | Any modern 64-bit Linux distro |
|
||||
| **CPU** | 6-core x86_64 processor | 8-core / 16-thread modern CPU |
|
||||
| **GPU** | Vulkan 1.3 compatible (4 GB VRAM) | Vulkan 1.3+ (6 GB+ VRAM, Nvidia / AMD) |
|
||||
| **RAM** | 8 GB | 16 GB+ |
|
||||
@@ -55,7 +55,7 @@ Launcher for God Of War 3 PC on Linux
|
||||
**Option B: Download Pre-compiled Binaries (GitHub Releases)**
|
||||
Download the executables directly from the GitHub Releases tab:
|
||||
- **Linux**: `gow3-launcher`
|
||||
- **Windows / Dual-Boot**: `gow3-launcher-win.exe`
|
||||
- **Windows**: `gow3-launcher-win.exe`
|
||||
|
||||
The launcher is completely standalone: it automatically detects your hardware, verifies the game dump, configures settings files, and applies all required patches without requiring external bash scripts.
|
||||
|
||||
@@ -115,12 +115,12 @@ Launcher for God Of War 3 PC on Linux
|
||||
|
||||
## 📜 Credits & Acknowledgments
|
||||
|
||||
- **Original Project:** https://github.com/FelipePrado0/gow3_pc
|
||||
- **Original Project:** [FelipePrado0](https://github.com/FelipePrado0/gow3_pc)
|
||||
- **Upstream Linux Port & Probe:** Developed by [R-V-L](https://github.com/R-V-L/gow3_pc) and community contributors.
|
||||
- **Launcher & Hardware Tuner:** Built with Rust, [egui/eframe](https://github.com/emilk/egui).
|
||||
|
||||
> **Disclaimer:** God of War is a registered trademark of Sony Interactive Entertainment LLC and Santa Monica Studio. This repository **does not contain any copyrighted game assets, ROMs, or binaries**. Users must supply their own legally obtained game files.
|
||||
|
||||
|
||||
#Licence
|
||||
## License
|
||||
GNU GPL v2 or later ([LICENSE](https://github.com/R-V-L/gow3_pc/blob/main/LICENSE)). Third-party components keep their own licenses.
|
||||
Binary file not shown.
Executable → Regular
BIN
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 449 KiB |
Binary file not shown.
@@ -77,6 +77,16 @@ pub struct LauncherApp {
|
||||
is_game_running: bool,
|
||||
game_exit_rx: Option<Receiver<Option<i32>>>,
|
||||
|
||||
// Python dependency state
|
||||
has_python: bool,
|
||||
is_downloading_python: bool,
|
||||
python_dl_rx: Option<Receiver<Result<String, String>>>,
|
||||
|
||||
// Engine dependency state
|
||||
has_engine: bool,
|
||||
is_downloading_engine: bool,
|
||||
engine_dl_rx: Option<Receiver<Result<String, String>>>,
|
||||
|
||||
// Modals
|
||||
show_first_launch_modal: bool,
|
||||
active_crash_diagnosis: Option<CrashDiagnosis>,
|
||||
@@ -104,6 +114,8 @@ impl LauncherApp {
|
||||
let is_first = !config.first_launch_done;
|
||||
let game_valid = is_valid_game_dir(Path::new(&config.game_dir));
|
||||
let gamemode_avail = is_gamemode_available();
|
||||
let has_python = crate::runner::find_python(&base_dir).is_ok();
|
||||
let has_engine = crate::runner::find_game_engine(&base_dir).is_some();
|
||||
|
||||
let mut app = Self {
|
||||
base_dir,
|
||||
@@ -120,6 +132,12 @@ impl LauncherApp {
|
||||
update_modal: None,
|
||||
is_game_running: false,
|
||||
game_exit_rx: None,
|
||||
has_python,
|
||||
is_downloading_python: false,
|
||||
python_dl_rx: None,
|
||||
has_engine,
|
||||
is_downloading_engine: false,
|
||||
engine_dl_rx: None,
|
||||
show_first_launch_modal: is_first,
|
||||
active_crash_diagnosis: None,
|
||||
show_log_modal: false,
|
||||
@@ -245,6 +263,37 @@ impl LauncherApp {
|
||||
});
|
||||
}
|
||||
|
||||
fn start_python_download(&mut self) {
|
||||
if self.is_downloading_python {
|
||||
return;
|
||||
}
|
||||
self.is_downloading_python = true;
|
||||
let (tx, rx) = channel();
|
||||
self.python_dl_rx = Some(rx);
|
||||
|
||||
let base_dir_clone = self.base_dir.clone();
|
||||
thread::spawn(move || {
|
||||
let res = crate::runner::download_portable_python(&base_dir_clone);
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn start_engine_download(&mut self) {
|
||||
if self.is_downloading_engine {
|
||||
return;
|
||||
}
|
||||
self.is_downloading_engine = true;
|
||||
let (tx, rx) = channel();
|
||||
self.engine_dl_rx = Some(rx);
|
||||
|
||||
let base_dir_clone = self.base_dir.clone();
|
||||
thread::spawn(move || {
|
||||
let res = crate::runner::download_engine(&base_dir_clone);
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
}
|
||||
|
||||
fn launch_game(&mut self) {
|
||||
// Apply settings before launching
|
||||
self.apply_all_optimizations();
|
||||
@@ -328,6 +377,41 @@ impl eframe::App for LauncherApp {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check background python downloader thread
|
||||
if let Some(ref rx) = self.python_dl_rx {
|
||||
if let Ok(res) = rx.try_recv() {
|
||||
self.is_downloading_python = false;
|
||||
self.python_dl_rx = None;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
self.has_python = true;
|
||||
self.status_message = Some((self.t("python_download_success").to_string(), false));
|
||||
}
|
||||
Err(e) => {
|
||||
self.status_message = Some((format!("{}{}", self.t("python_download_err"), e), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Check background engine downloader thread
|
||||
if let Some(ref rx) = self.engine_dl_rx {
|
||||
if let Ok(res) = rx.try_recv() {
|
||||
self.is_downloading_engine = false;
|
||||
self.engine_dl_rx = None;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
self.has_engine = true;
|
||||
self.has_python = crate::runner::find_python(&self.base_dir).is_ok();
|
||||
self.status_message = Some((self.t("engine_download_success").to_string(), false));
|
||||
}
|
||||
Err(e) => {
|
||||
self.status_message = Some((format!("❌ {}", e), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- FULL SCREEN BACKGROUND WALLPAPER ---
|
||||
if let Some(ref tex) = self.bg_texture {
|
||||
let painter = ui.painter();
|
||||
@@ -489,6 +573,93 @@ impl eframe::App for LauncherApp {
|
||||
ui.label(egui::RichText::new(format!("{} Hz", hw.refresh_rate_hz)).color(egui::Color32::from_rgb(130, 205, 135)).strong());
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(self.t("python_dep")).color(egui::Color32::LIGHT_GRAY).size(12.0));
|
||||
if self.has_python {
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("python_ready"))
|
||||
.color(egui::Color32::from_rgb(102, 225, 106))
|
||||
.strong()
|
||||
.size(12.0),
|
||||
);
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("python_missing"))
|
||||
.color(egui::Color32::from_rgb(239, 83, 80))
|
||||
.strong()
|
||||
.size(12.0),
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
if self.is_downloading_python {
|
||||
ui.spinner();
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("python_downloading"))
|
||||
.color(egui::Color32::from_rgb(255, 213, 79))
|
||||
.size(12.0),
|
||||
);
|
||||
} else {
|
||||
let dl_btn = egui::Button::new(
|
||||
egui::RichText::new(self.t("python_download"))
|
||||
.size(11.0)
|
||||
.color(egui::Color32::WHITE)
|
||||
.strong(),
|
||||
)
|
||||
.fill(egui::Color32::from_rgb(45, 120, 185))
|
||||
.corner_radius(egui::CornerRadius::same(5));
|
||||
|
||||
if ui.add(dl_btn).clicked() {
|
||||
self.start_python_download();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(self.t("engine_label")).color(egui::Color32::LIGHT_GRAY).size(12.0));
|
||||
if self.has_engine {
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("engine_ready"))
|
||||
.color(egui::Color32::from_rgb(102, 225, 106))
|
||||
.strong()
|
||||
.size(12.0),
|
||||
);
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("engine_missing"))
|
||||
.color(egui::Color32::from_rgb(239, 83, 80))
|
||||
.strong()
|
||||
.size(12.0),
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
if self.is_downloading_engine {
|
||||
ui.spinner();
|
||||
ui.label(
|
||||
egui::RichText::new(self.t("engine_downloading"))
|
||||
.color(egui::Color32::from_rgb(255, 213, 79))
|
||||
.size(12.0),
|
||||
);
|
||||
} else {
|
||||
let dl_btn = egui::Button::new(
|
||||
egui::RichText::new(self.t("engine_download"))
|
||||
.size(11.0)
|
||||
.color(egui::Color32::WHITE)
|
||||
.strong(),
|
||||
)
|
||||
.fill(egui::Color32::from_rgb(180, 80, 45))
|
||||
.corner_radius(egui::CornerRadius::same(5));
|
||||
|
||||
if ui.add(dl_btn).clicked() {
|
||||
self.start_engine_download();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- 2. GAME DIRECTORY CARD ---
|
||||
|
||||
@@ -105,6 +105,19 @@ pub fn text(lang: Language, key: &str) -> &'static str {
|
||||
(Language::Turkish, "latest_commit") => "Son Değişiklik:",
|
||||
(Language::Turkish, "commit_date") => "Tarih:",
|
||||
(Language::Turkish, "view_on_github") => "🌐 GitHub'da Görüntüle",
|
||||
(Language::Turkish, "python_dep") => "Python 3 Bağımlılığı:",
|
||||
(Language::Turkish, "python_ready") => "Kurulu / Hazır",
|
||||
(Language::Turkish, "python_missing") => "Bulunamadı",
|
||||
(Language::Turkish, "python_download") => "📥 Taşınabilir Python 3 İndir (~10 MB, Kurulumsuz)",
|
||||
(Language::Turkish, "python_downloading") => "⏳ Python 3 indiriliyor ve hazırlanıyor...",
|
||||
(Language::Turkish, "python_download_success") => "✅ Taşınabilir Python 3 başarıyla kuruldu ve hazırlandı!",
|
||||
(Language::Turkish, "python_download_err") => "❌ Python 3 indirilemedi: ",
|
||||
(Language::Turkish, "engine_label") => "⚙️ Oyun Motoru:",
|
||||
(Language::Turkish, "engine_ready") => "Kurulu / Hazır (Vulkan Native)",
|
||||
(Language::Turkish, "engine_missing") => "Bulunamadı / Eksik!",
|
||||
(Language::Turkish, "engine_download") => "📥 Motoru Otomatik İndir (~74 MB)",
|
||||
(Language::Turkish, "engine_downloading") => "⏳ Oyun motoru paketi indiriliyor ve kuruluyor...",
|
||||
(Language::Turkish, "engine_download_success") => "✅ Oyun motoru başarıyla kuruldu!",
|
||||
|
||||
// English
|
||||
(Language::English, "app_title") => "GOD OF WAR® III REMASTERED",
|
||||
@@ -167,6 +180,19 @@ 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, "python_dep") => "Python 3 Dependency:",
|
||||
(Language::English, "python_ready") => "Installed / Ready",
|
||||
(Language::English, "python_missing") => "Not Found",
|
||||
(Language::English, "python_download") => "📥 Download Portable Python 3 (~10 MB, No Install)",
|
||||
(Language::English, "python_downloading") => "⏳ Downloading & configuring Python 3...",
|
||||
(Language::English, "python_download_success") => "✅ Portable Python 3 installed and configured successfully!",
|
||||
(Language::English, "python_download_err") => "❌ Failed to download Python 3: ",
|
||||
(Language::English, "engine_label") => "⚙️ Game Engine:",
|
||||
(Language::English, "engine_ready") => "Installed / Ready (Vulkan Native)",
|
||||
(Language::English, "engine_missing") => "Not Found / Missing!",
|
||||
(Language::English, "engine_download") => "📥 Download Engine (~74 MB)",
|
||||
(Language::English, "engine_downloading") => "⏳ Downloading and configuring game engine...",
|
||||
(Language::English, "engine_download_success") => "✅ Game engine installed successfully!",
|
||||
|
||||
// German
|
||||
(Language::German, "app_title") => "GOD OF WAR® III REMASTERED",
|
||||
@@ -229,6 +255,19 @@ pub fn text(lang: Language, key: &str) -> &'static str {
|
||||
(Language::German, "latest_commit") => "Letzter Commit:",
|
||||
(Language::German, "commit_date") => "Datum:",
|
||||
(Language::German, "view_on_github") => "🌐 Auf GitHub ansehen",
|
||||
(Language::German, "python_dep") => "Python 3 Abhängigkeit:",
|
||||
(Language::German, "python_ready") => "Installiert / Bereit",
|
||||
(Language::German, "python_missing") => "Nicht gefunden",
|
||||
(Language::German, "python_download") => "📥 Portables Python 3 herunterladen (~10 MB, Ohne Installation)",
|
||||
(Language::German, "python_downloading") => "⏳ Python 3 wird heruntergeladen und eingerichtet...",
|
||||
(Language::German, "python_download_success") => "✅ Portables Python 3 erfolgreich eingerichtet!",
|
||||
(Language::German, "python_download_err") => "❌ Fehler beim Herunterladen von Python 3: ",
|
||||
(Language::German, "engine_label") => "⚙️ Spiel-Engine:",
|
||||
(Language::German, "engine_ready") => "Installiert / Bereit (Vulkan Native)",
|
||||
(Language::German, "engine_missing") => "Nicht gefunden / Fehlt!",
|
||||
(Language::German, "engine_download") => "📥 Engine herunterladen (~74 MB)",
|
||||
(Language::German, "engine_downloading") => "⏳ Spiel-Engine wird heruntergeladen und eingerichtet...",
|
||||
(Language::German, "engine_download_success") => "✅ Spiel-Engine erfolgreich installiert!",
|
||||
|
||||
// Spanish
|
||||
(Language::Spanish, "app_title") => "GOD OF WAR® III REMASTERED",
|
||||
@@ -291,6 +330,19 @@ 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, "python_dep") => "Dependencia Python 3:",
|
||||
(Language::Spanish, "python_ready") => "Instalado / Listo",
|
||||
(Language::Spanish, "python_missing") => "No encontrado",
|
||||
(Language::Spanish, "python_download") => "📥 Descargar Python 3 portable (~10 MB, Sin instalación)",
|
||||
(Language::Spanish, "python_downloading") => "⏳ Descargando y configurando Python 3...",
|
||||
(Language::Spanish, "python_download_success") => "✅ ¡Python 3 portable configurado con éxito!",
|
||||
(Language::Spanish, "python_download_err") => "❌ Error al descargar Python 3: ",
|
||||
(Language::Spanish, "engine_label") => "⚙️ Motor del Juego:",
|
||||
(Language::Spanish, "engine_ready") => "Instalado / Listo (Vulkan Native)",
|
||||
(Language::Spanish, "engine_missing") => "¡No encontrado / Falta!",
|
||||
(Language::Spanish, "engine_download") => "📥 Descargar Motor (~74 MB)",
|
||||
(Language::Spanish, "engine_downloading") => "⏳ Descargando y configurando motor de juego...",
|
||||
(Language::Spanish, "engine_download_success") => "✅ ¡Motor de juego instalado con éxito!",
|
||||
|
||||
_ => "",
|
||||
}
|
||||
|
||||
+323
-19
@@ -71,15 +71,315 @@ pub fn ensure_runtime_assets(base_dir: &Path) -> Result<(PathBuf, PathBuf), Stri
|
||||
Ok((scripts_dir, patches_dir))
|
||||
}
|
||||
|
||||
pub fn find_python() -> Result<String, String> {
|
||||
for cmd in &["python3", "python", "py"] {
|
||||
if let Ok(out) = Command::new(cmd).arg("--version").output() {
|
||||
if out.status.success() {
|
||||
return Ok(cmd.to_string());
|
||||
/// 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)
|
||||
let candidates = [
|
||||
base_dir.join(".runtime/python/python.exe"),
|
||||
base_dir.join(".runtime/python/python"),
|
||||
base_dir.join(".runtime/python/bin/python3"),
|
||||
base_dir.join(".runtime/python/bin/python"),
|
||||
base_dir.join("python/python.exe"),
|
||||
];
|
||||
for p in &candidates {
|
||||
if p.is_file() {
|
||||
if let Ok(out) = Command::new(p).arg("--version").output() {
|
||||
if out.status.success() {
|
||||
return Ok(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err("Python 3 bulunamadı. Lütfen sisteminize 'python' kurun.".to_string())
|
||||
|
||||
// 2. Check system-wide Python
|
||||
for cmd in &["python3", "python", "py"] {
|
||||
if let Ok(out) = Command::new(cmd).arg("--version").output() {
|
||||
if out.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
let combined = format!("{} {}", stdout, stderr);
|
||||
if combined.contains("Python 3.") {
|
||||
return Ok(cmd.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("Python 3 bulunamadı. Lütfen taşınabilir Python paketini indirin veya sisteminize kurun.".to_string())
|
||||
}
|
||||
|
||||
/// Automatically downloads and configures official portable Python 3 runtime into `.runtime/python`
|
||||
pub fn download_portable_python(base_dir: &Path) -> Result<String, String> {
|
||||
let runtime_dir = base_dir.join(".runtime");
|
||||
let python_dir = runtime_dir.join("python");
|
||||
let _ = fs::create_dir_all(&python_dir);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let ps_code = format!(
|
||||
r#"
|
||||
$ProgressPreference = 'SilentlyContinue';
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||
$url = 'https://www.python.org/ftp/python/3.11.9/python-3.11.9-embed-amd64.zip';
|
||||
$zip = Join-Path $env:TEMP 'gow3_python_embed.zip';
|
||||
$dest = '{}';
|
||||
if (Test-Path $zip) {{ Remove-Item $zip -Force }};
|
||||
Write-Host 'Downloading Python 3.11 embed package...';
|
||||
(New-Object System.Net.WebClient).DownloadFile($url, $zip);
|
||||
if (!(Test-Path $dest)) {{ New-Item -ItemType Directory -Path $dest -Force | Out-Null }};
|
||||
Write-Host 'Extracting archive...';
|
||||
Expand-Archive -Path $zip -DestinationPath $dest -Force;
|
||||
Remove-Item $zip -Force;
|
||||
"#,
|
||||
python_dir.to_string_lossy().replace('\'', "''")
|
||||
);
|
||||
|
||||
let status = Command::new("powershell")
|
||||
.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_code])
|
||||
.status()
|
||||
.map_err(|e| format!("PowerShell ile Python indirme komutu çalıştırılamadı: {}", e))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err("PowerShell üzerinden Python 3 indirilemedi.".to_string());
|
||||
}
|
||||
|
||||
// Configure python311._pth to enable import site and recognize scripts directory
|
||||
let pth_file = python_dir.join("python311._pth");
|
||||
let pth_content = "python311.zip\n.\n..\n..\\scripts\n..\\..\\scripts\nimport site\n";
|
||||
let _ = fs::write(pth_file, pth_content);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// On Linux / macOS fallback: if curl and unzip exist, download embed (usable with wine or testing)
|
||||
let zip_path = runtime_dir.join("python_embed.zip");
|
||||
let curl_res = Command::new("curl")
|
||||
.args([
|
||||
"-sL",
|
||||
"https://www.python.org/ftp/python/3.11.9/python-3.11.9-embed-amd64.zip",
|
||||
"-o",
|
||||
&zip_path.to_string_lossy(),
|
||||
])
|
||||
.status();
|
||||
|
||||
if let Ok(st) = curl_res {
|
||||
if st.success() {
|
||||
let _ = Command::new("unzip")
|
||||
.args(["-o", "-q", &zip_path.to_string_lossy(), "-d", &python_dir.to_string_lossy()])
|
||||
.status();
|
||||
let _ = fs::remove_file(&zip_path);
|
||||
|
||||
let pth_file = python_dir.join("python311._pth");
|
||||
let pth_content = "python311.zip\n.\n..\nimport site\n";
|
||||
let _ = fs::write(pth_file, pth_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find_python(base_dir).map_err(|_| "Python 3 indirildi ancak doğrulanamadı.".to_string())
|
||||
}
|
||||
|
||||
/// Ensures Python 3 is available, automatically downloading portable package if missing
|
||||
pub fn ensure_python(base_dir: &Path) -> Result<String, String> {
|
||||
if let Ok(py) = find_python(base_dir) {
|
||||
return Ok(py);
|
||||
}
|
||||
download_portable_python(base_dir)
|
||||
}
|
||||
|
||||
pub fn find_game_engine(base_dir: &Path) -> Option<PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let probe_candidates = [
|
||||
base_dir.join("bin/gow3-probe.exe"),
|
||||
base_dir.join("out/gow3-probe.exe"),
|
||||
base_dir.join("gow3-probe.exe"),
|
||||
base_dir.join("bin/gow3.exe"),
|
||||
base_dir.join("gow3.exe"),
|
||||
base_dir.join("God of War III.exe"),
|
||||
];
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let probe_candidates = [
|
||||
base_dir.join("bin/gow3-probe"),
|
||||
base_dir.join("out/gow3-probe"),
|
||||
base_dir.join("gow3-probe"),
|
||||
];
|
||||
|
||||
probe_candidates.into_iter().find(|p| p.is_file())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn download_windows_engine(base_dir: &Path) -> Result<String, String> {
|
||||
let dest_bin = base_dir.join("bin");
|
||||
let _ = fs::create_dir_all(&dest_bin);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let ps_code = format!(
|
||||
r#"
|
||||
$ProgressPreference = 'SilentlyContinue';
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||
$url = 'https://github.com/FelipePrado0/gow3_pc/releases/download/windows-v2.4.1/gow3-windows.zip';
|
||||
$zip = Join-Path $env:TEMP 'gow3_win_download.zip';
|
||||
$tempDir = Join-Path $env:TEMP 'gow3_win_extracted';
|
||||
if (Test-Path $tempDir) {{ Remove-Item $tempDir -Recurse -Force }};
|
||||
if (Test-Path $zip) {{ Remove-Item $zip -Force }};
|
||||
Write-Host 'Downloading Windows engine package (~74MB)...';
|
||||
(New-Object System.Net.WebClient).DownloadFile($url, $zip);
|
||||
Write-Host 'Extracting Windows package...';
|
||||
Expand-Archive -Path $zip -DestinationPath $tempDir -Force;
|
||||
|
||||
$probe = Get-ChildItem -Path $tempDir -Recurse -Filter "gow3-probe.exe" | Select-Object -First 1;
|
||||
if ($probe) {{
|
||||
$sourceBin = $probe.DirectoryName;
|
||||
$destBin = '{dest_bin}';
|
||||
if (!(Test-Path $destBin)) {{ New-Item -ItemType Directory -Path $destBin -Force | Out-Null }};
|
||||
Copy-Item "$sourceBin\*" -Destination $destBin -Recurse -Force;
|
||||
}} else {{
|
||||
throw "gow3-probe.exe extracted archive icinde bulunamadi!";
|
||||
}}
|
||||
|
||||
$base = '{base_dir}';
|
||||
$scripts = Get-ChildItem -Path $tempDir -Recurse -Directory -Filter "scripts" | Select-Object -First 1;
|
||||
if ($scripts -and !(Test-Path (Join-Path $base 'scripts'))) {{
|
||||
Copy-Item $scripts.FullName -Destination $base -Recurse -Force;
|
||||
}}
|
||||
$patches = Get-ChildItem -Path $tempDir -Recurse -Directory -Filter "patches" | Select-Object -First 1;
|
||||
if ($patches -and !(Test-Path (Join-Path $base 'patches'))) {{
|
||||
Copy-Item $patches.FullName -Destination $base -Recurse -Force;
|
||||
}}
|
||||
|
||||
Remove-Item $tempDir -Recurse -Force;
|
||||
Remove-Item $zip -Force;
|
||||
"#,
|
||||
dest_bin = dest_bin.to_string_lossy().replace('\'', "''"),
|
||||
base_dir = base_dir.to_string_lossy().replace('\'', "''")
|
||||
);
|
||||
|
||||
let status = Command::new("powershell")
|
||||
.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_code])
|
||||
.status()
|
||||
.map_err(|e| format!("PowerShell Windows motoru indirme komutu çalıştırılamadı: {}", e))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err("PowerShell üzerinden Windows motor paketi indirilemedi veya açılamadı.".to_string());
|
||||
}
|
||||
|
||||
if find_game_engine(base_dir).is_some() {
|
||||
return Ok("Windows oyun motoru başarıyla kuruldu!".to_string());
|
||||
}
|
||||
|
||||
Err("Windows oyun motoru ('bin/gow3-probe.exe') arşivden çıkarılamadı.".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Err("Bu fonksiyon yalnızca Windows üzerinde çalışır.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn download_linux_engine(base_dir: &Path) -> Result<String, String> {
|
||||
let dest_bin = base_dir.join("bin");
|
||||
let _ = fs::create_dir_all(&dest_bin);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let url = "https://github.com/FelipePrado0/gow3_pc/releases/download/windows-v2.4.1/gow3-x86_64.AppImage";
|
||||
let temp_dir = std::env::temp_dir().join("gow3_linux_extracted");
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
let appimage_path = temp_dir.join("gow3-x86_64.AppImage");
|
||||
|
||||
let dl_status = Command::new("curl")
|
||||
.args(["-sL", url, "-o", &appimage_path.to_string_lossy()])
|
||||
.status();
|
||||
|
||||
let success = match dl_status {
|
||||
Ok(s) if s.success() => true,
|
||||
_ => {
|
||||
Command::new("wget")
|
||||
.args(["-q", url, "-O", &appimage_path.to_string_lossy()])
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
};
|
||||
|
||||
if !success {
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
return Err("Linux AppImage paketi indirilemedi (curl/wget başarısız oldu).".to_string());
|
||||
}
|
||||
|
||||
let _ = Command::new("chmod")
|
||||
.args(["+x", &appimage_path.to_string_lossy()])
|
||||
.status();
|
||||
|
||||
let ext_status = Command::new(&appimage_path)
|
||||
.arg("--appimage-extract")
|
||||
.current_dir(&temp_dir)
|
||||
.status();
|
||||
|
||||
if let Ok(st) = ext_status {
|
||||
if st.success() {
|
||||
let squash_bin = temp_dir.join("squashfs-root/bin");
|
||||
if squash_bin.is_dir() {
|
||||
let _ = Command::new("cp")
|
||||
.args(["-r", &format!("{}/.", squash_bin.to_string_lossy()), &dest_bin.to_string_lossy()])
|
||||
.status();
|
||||
let _ = Command::new("chmod")
|
||||
.args(["+x", &dest_bin.join("gow3-probe").to_string_lossy()])
|
||||
.status();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
|
||||
if find_game_engine(base_dir).is_some() {
|
||||
return Ok("Linux oyun motoru başarıyla kuruldu!".to_string());
|
||||
}
|
||||
|
||||
Err("Linux oyun motoru paketten çıkarılamadı.".to_string())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Err("Bu fonksiyon yalnızca Linux üzerinde çalışır.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_engine(base_dir: &Path) -> Result<String, String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
download_windows_engine(base_dir)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
download_linux_engine(base_dir)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_game_engine(base_dir: &Path) -> Result<PathBuf, String> {
|
||||
if let Some(p) = find_game_engine(base_dir) {
|
||||
return Ok(p);
|
||||
}
|
||||
|
||||
// Try automatic download if missing
|
||||
let _ = download_engine(base_dir);
|
||||
if let Some(p) = find_game_engine(base_dir) {
|
||||
return Ok(p);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Err("Windows için oyun motoru ('bin/gow3-probe.exe') bulunamadı! Lütfen arayüzdeki '📥 Motoru İndir' butonuna basarak indirin.".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Err("Linux için oyun motoru ('bin/gow3-probe') bulunamadı! Lütfen arayüzdeki '📥 Motoru İndir' butonuna basarak indirin.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the full God of War III preparation and probe execution natively from Rust.
|
||||
@@ -93,17 +393,7 @@ pub fn launch_gow3(
|
||||
return Err("Oyun klasöründe 'eboot.bin' bulunamadı!".to_string());
|
||||
}
|
||||
|
||||
let probe_candidates = [
|
||||
base_dir.join("bin/gow3-probe"),
|
||||
base_dir.join("bin/gow3-probe.exe"),
|
||||
base_dir.join("out/gow3-probe"),
|
||||
base_dir.join("out/gow3-probe.exe"),
|
||||
base_dir.join("gow3-probe.exe"),
|
||||
];
|
||||
let probe_bin = probe_candidates
|
||||
.into_iter()
|
||||
.find(|p| p.is_file())
|
||||
.ok_or_else(|| "Oyun motoru ('gow3-probe' / 'gow3-probe.exe') bulunamadı!".to_string())?;
|
||||
let probe_bin = ensure_game_engine(base_dir)?;
|
||||
|
||||
let out_dir = base_dir.join("out");
|
||||
fs::create_dir_all(&out_dir).map_err(|e| format!("'out' klasörü oluşturulamadı: {}", e))?;
|
||||
@@ -127,7 +417,7 @@ pub fn launch_gow3(
|
||||
.try_clone()
|
||||
.map_err(|e| format!("Log dosyası çoğaltılamadı: {}", e))?;
|
||||
|
||||
let python = find_python()?;
|
||||
let python = ensure_python(base_dir)?;
|
||||
|
||||
// 1. Run mods.py
|
||||
let _ = Command::new(&python)
|
||||
@@ -317,6 +607,20 @@ pub fn launch_gow3(
|
||||
cmd.env("GOW3_CONFIG", &config_ini);
|
||||
cmd.env("GOW3_GAME_DIR", game_dir);
|
||||
|
||||
// Prepend bin directory to PATH so Windows/Linux loaders find bundled libraries
|
||||
let bin_dir = base_dir.join("bin");
|
||||
if bin_dir.is_dir() {
|
||||
if let Some(existing_path) = std::env::var_os("PATH") {
|
||||
let mut paths = vec![bin_dir.clone()];
|
||||
paths.extend(std::env::split_paths(&existing_path));
|
||||
if let Ok(new_path) = std::env::join_paths(paths) {
|
||||
cmd.env("PATH", new_path);
|
||||
}
|
||||
} else {
|
||||
cmd.env("PATH", &bin_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Pipe stdout & stderr to gow3_log.txt
|
||||
cmd.stdout(Stdio::from(log_file));
|
||||
cmd.stderr(Stdio::from(log_file_err));
|
||||
@@ -333,7 +637,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_find_python() {
|
||||
let py = find_python();
|
||||
let py = find_python(Path::new("."));
|
||||
assert!(py.is_ok(), "Python 3 should be detected on the system");
|
||||
}
|
||||
|
||||
|
||||
+54
-2
@@ -179,17 +179,68 @@ fn detect_refresh_rate() -> u32 {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn detect_gpu() -> (String, u64) {
|
||||
// 1. Try nvidia-smi first (exact VRAM for NVIDIA GPUs, e.g. RTX 4070 Laptop 8GB)
|
||||
if let Ok(output) = Command::new("nvidia-smi")
|
||||
.args(["--query-gpu=name,memory.total", "--format=csv,noheader,nounits"])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
if let Some(line) = text.lines().next() {
|
||||
let parts: Vec<&str> = line.split(',').collect();
|
||||
if parts.len() >= 2 {
|
||||
let name = parts[0].trim().to_string();
|
||||
let vram = parts[1].trim().parse::<u64>().unwrap_or(0);
|
||||
if vram > 0 {
|
||||
return (name, vram);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try Windows Registry for AMD / Intel / NVIDIA (64-bit QWORD qwMemorySize)
|
||||
let ps_script = r#"
|
||||
$g = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\00*' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.'HardwareInformation.qwMemorySize' -gt 0 } |
|
||||
Sort-Object 'HardwareInformation.qwMemorySize' -Descending |
|
||||
Select-Object -First 1;
|
||||
if ($g) {
|
||||
$name = if ($g.DriverDesc) { $g.DriverDesc } else { $g.'Device Description' };
|
||||
$vram = [int64]$g.'HardwareInformation.qwMemorySize';
|
||||
[PSCustomObject]@{ Name = $name; VRAM = $vram } | ConvertTo-Json -Compress
|
||||
}
|
||||
"#;
|
||||
if let Ok(output) = Command::new("powershell")
|
||||
.args(["-NoProfile", "-Command", ps_script])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(val) = serde_json::from_str::<serde_json::Value>(text.trim()) {
|
||||
let name = val["Name"].as_str().unwrap_or("Windows DirectX/Vulkan GPU").to_string();
|
||||
if let Some(vram_bytes) = val["VRAM"].as_u64() {
|
||||
let vram_mb = vram_bytes / (1024 * 1024);
|
||||
if vram_mb >= 1024 {
|
||||
return (name, vram_mb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to Win32_VideoController
|
||||
if let Ok(output) = Command::new("powershell")
|
||||
.args([
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"Get-CimInstance Win32_VideoController | Select-Object -First 1 -Property Name,AdapterRAM | ConvertTo-Json",
|
||||
"Get-CimInstance Win32_VideoController | Sort-Object AdapterRAM -Descending | Select-Object -First 1 -Property Name,AdapterRAM | ConvertTo-Json -Compress",
|
||||
])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||
if let Ok(val) = serde_json::from_str::<serde_json::Value>(text.trim()) {
|
||||
let name = val["Name"].as_str().unwrap_or("Windows DirectX/Vulkan GPU").to_string();
|
||||
let vram_bytes = val["AdapterRAM"].as_u64().unwrap_or(4 * 1024 * 1024 * 1024);
|
||||
let vram_mb = vram_bytes / (1024 * 1024);
|
||||
@@ -197,6 +248,7 @@ fn detect_gpu() -> (String, u64) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
("Windows DirectX/Vulkan GPU".to_string(), 4096)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user