mirror of
https://github.com/ScreamAngel/gow3_launcher.git
synced 2026-11-07 08:15:57 -10:00
fix(windows): resolve VRAM detection cap and add portable Python dependency downloader
This commit is contained in:
1 parent
5d1de303af
commit
01771af1d7
8 files changed
+275
-10
No files matched your search
Binary file not shown.
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,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 ---
|
||||
|
||||
@@ -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
@@ -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
@@ -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