fix(windows): resolve VRAM detection cap and add portable Python dependency downloader

This commit is contained in:
ScreamAngel committed 2026-10-17 13:34:54 +03:00
1 parent 5d1de303af
commit 01771af1d7
8 files changed
+275 -10

No files matched your search

BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.
+82
View File
@@ -77,6 +77,10 @@ pub struct LauncherApp {
is_game_running: bool,
game_exit_rx: Option<Receiver<Option<i32>>>,
// Python dependency state
is_downloading_python: bool,
python_dl_rx: Option<Receiver<Result<String, String>>>,
// Modals
show_first_launch_modal: bool,
active_crash_diagnosis: Option<CrashDiagnosis>,
@@ -120,6 +124,8 @@ impl LauncherApp {
update_modal: None,
is_game_running: false,
game_exit_rx: None,
is_downloading_python: false,
python_dl_rx: None,
show_first_launch_modal: is_first,
active_crash_diagnosis: None,
show_log_modal: false,
@@ -245,6 +251,21 @@ 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);
});
}
fn launch_game(&mut self) {
// Apply settings before launching
self.apply_all_optimizations();
@@ -328,6 +349,22 @@ 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.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));
}
}
}
}
// --- FULL SCREEN BACKGROUND WALLPAPER ---
if let Some(ref tex) = self.bg_texture {
let painter = ui.painter();
@@ -489,6 +526,51 @@ 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));
let has_py = crate::runner::find_python(&self.base_dir).is_ok();
if has_py {
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();
}
}
}
});
});
// --- 2. GAME DIRECTORY CARD ---
+28
View File
@@ -105,6 +105,13 @@ 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: ",
// English
(Language::English, "app_title") => "GOD OF WAR® III REMASTERED",
@@ -167,6 +174,13 @@ 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: ",
// German
(Language::German, "app_title") => "GOD OF WAR® III REMASTERED",
@@ -229,6 +243,13 @@ 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: ",
// Spanish
(Language::Spanish, "app_title") => "GOD OF WAR® III REMASTERED",
@@ -291,6 +312,13 @@ 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: ",
_ => "",
}
+111 -8
View File
@@ -71,15 +71,118 @@ 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#"
$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 = '{}';
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Write-Host 'Downloading Python 3.11 embed package...';
Invoke-WebRequest -Uri $url -OutFile $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", "-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)
}
/// Executes the full God of War III preparation and probe execution natively from Rust.
@@ -127,7 +230,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)
@@ -333,7 +436,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
View File
@@ -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)
}