mirror of
https://github.com/ScreamAngel/gow3_launcher.git
synced 2026-11-07 08:15:57 -10:00
fix(windows): resolve black screen launch bug and refine shader screen 10s background lifecycle
This commit is contained in:
1 parent
8ba512f45e
commit
6bb6349c41
7 files changed
+149
-36
No files matched your search
@@ -10,6 +10,7 @@ user/
|
||||
out/
|
||||
GLCache/
|
||||
.runtime/
|
||||
mod-game-*/
|
||||
vkd3d-proton.cache
|
||||
fsr4_shaders/
|
||||
.local/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+17
-6
@@ -4443,15 +4443,26 @@ impl eframe::App for LauncherApp {
|
||||
self.discord_rpc.set_in_game(self.base_dir.clone(), now, self.config.target_fps, is_modded);
|
||||
}
|
||||
|
||||
// Oyun penceresi açıldığında veya 20 saniye dolduğunda shader ekranını kapat, launcher'ı küçült ve oyunu en öne getir
|
||||
if game_ready_now || elapsed >= 20.0 {
|
||||
// Oyun penceresi açıldığında oyun ekranını en öne getir ve launcher'ı Normal seviyeye çek
|
||||
// Shader ekranı hemen kapanmaz veya görev çubuğuna küçülmez! Oyunun arkasında 10 saniye açık kalır.
|
||||
if game_ready_now && sl.game_ready_at.is_none() {
|
||||
sl.game_ready_at = Some(std::time::Instant::now());
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::WindowLevel(egui::WindowLevel::Normal));
|
||||
crate::win_input::bring_game_window_to_front(pid);
|
||||
}
|
||||
|
||||
// Oyun açıldıktan 10 saniye sonra (veya algılanamazsa güvenlik zaman aşımı 25 saniye sonra)
|
||||
// shader ekranı arka planda kapanıp standart launcher arayüzüne döner (görev çubuğuna küçülmez!)
|
||||
let should_close = if let Some(ready_at) = sl.game_ready_at {
|
||||
ready_at.elapsed().as_secs_f32() >= 10.0
|
||||
} else {
|
||||
elapsed >= 25.0
|
||||
};
|
||||
|
||||
if should_close {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::WindowLevel(egui::WindowLevel::Normal));
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Minimized(true));
|
||||
self.shader_loading = None;
|
||||
|
||||
// Oyun penceresini Windows üzerinde doğrudan en öne odakla
|
||||
crate::win_input::bring_game_window_to_front(pid);
|
||||
} else if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
// Güvenlik: Kullanıcı ESC tuşuna basarak ekranı erken kapatabilir
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::WindowLevel(egui::WindowLevel::Normal));
|
||||
|
||||
+104
-18
@@ -391,22 +391,72 @@ pub fn build_rust_mod_overlay(
|
||||
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());
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let _ = std::os::unix::fs::symlink(&target, &link);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if target.is_dir() {
|
||||
let _ = std::os::windows::fs::symlink_dir(&target, &link);
|
||||
} else if fs::hard_link(&target, &link).is_err() {
|
||||
let _ = fs::copy(&target, &link);
|
||||
}
|
||||
if target.is_dir() {
|
||||
let _ = link_or_copy_dir(&target, &link);
|
||||
} else {
|
||||
let _ = link_or_copy_file(&target, &link);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,24 +476,44 @@ pub fn build_rust_mod_overlay(
|
||||
None
|
||||
}
|
||||
|
||||
fn copy_loose_files(src: &Path, dst: &Path, count: &mut usize) {
|
||||
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() {
|
||||
if dest_path.is_symlink() {
|
||||
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);
|
||||
}
|
||||
let _ = fs::create_dir_all(&dest_path);
|
||||
copy_loose_files(&path, &dest_path, count);
|
||||
|
||||
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.exists() {
|
||||
if dest_path.is_symlink() || dest_path.is_file() {
|
||||
let _ = fs::remove_file(&dest_path);
|
||||
}
|
||||
if fs::copy(&path, &dest_path).is_ok() {
|
||||
@@ -457,10 +527,20 @@ pub fn build_rust_mod_overlay(
|
||||
let mut applied = 0;
|
||||
for (_name, path) in enabled_mods {
|
||||
if let Some(root) = find_content_root(&path) {
|
||||
copy_loose_files(&root, &overlay_dir, &mut applied);
|
||||
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)
|
||||
@@ -1106,6 +1186,7 @@ pub fn launch_gow3(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1536,7 +1617,11 @@ pub fn launch_gow3(
|
||||
cmd.env("GOW3_PATCHES_ONLY", "1");
|
||||
cmd.env("GOW3_CONFIG", &config_ini);
|
||||
cmd.env("GOW3_GAME_DIR", game_dir);
|
||||
cmd.env("GOW3_MODS_DIR", &effective_app0);
|
||||
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
|
||||
@@ -1701,6 +1786,7 @@ mod tests {
|
||||
|
||||
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();
|
||||
|
||||
@@ -43,11 +43,6 @@ mod imp {
|
||||
|
||||
const KEYEVENTF_KEYUP: u32 = 0x0002;
|
||||
const SW_RESTORE: i32 = 9;
|
||||
const HWND_TOPMOST: HWND = -1isize as HWND;
|
||||
const HWND_NOTOPMOST: HWND = -2isize as HWND;
|
||||
const SWP_NOSIZE: u32 = 0x0001;
|
||||
const SWP_NOMOVE: u32 = 0x0002;
|
||||
const SWP_SHOWWINDOW: u32 = 0x0040;
|
||||
|
||||
#[repr(C)]
|
||||
struct POINT {
|
||||
@@ -90,8 +85,8 @@ mod imp {
|
||||
fn ShowWindow(hwnd: HWND, n_cmd_show: i32) -> i32;
|
||||
fn SetForegroundWindow(hwnd: HWND) -> i32;
|
||||
fn BringWindowToTop(hwnd: HWND) -> i32;
|
||||
fn SetWindowPos(hwnd: HWND, hwnd_insert_after: HWND, x: i32, y: i32, cx: i32, cy: i32, u_flags: u32) -> 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;
|
||||
}
|
||||
@@ -190,7 +185,7 @@ mod imp {
|
||||
let mut pid: u32 = 0;
|
||||
unsafe {
|
||||
GetWindowThreadProcessId(hwnd, &mut pid);
|
||||
if (p.target_pid > 0 && pid == p.target_pid) || is_game_window(hwnd) {
|
||||
if ((p.target_pid > 0 && pid == p.target_pid) || is_game_window(hwnd)) && IsWindowVisible(hwnd) != 0 {
|
||||
p.found_hwnd = hwnd;
|
||||
return 0; // stop enumerating
|
||||
}
|
||||
@@ -217,10 +212,8 @@ mod imp {
|
||||
if let Some(hwnd) = find_game_hwnd(pid) {
|
||||
unsafe {
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||
BringWindowToTop(hwnd);
|
||||
SetForegroundWindow(hwnd);
|
||||
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +276,13 @@ mod stub {
|
||||
pub fn start_mouse_hook(_pid: u32, _hide_cursor: bool) -> WinInputGuard {
|
||||
WinInputGuard
|
||||
}
|
||||
pub fn bring_game_window_to_front(_pid: u32) {}
|
||||
pub fn bring_game_window_to_front(pid: u32) {
|
||||
if pid > 0 {
|
||||
let _ = std::process::Command::new("xdotool")
|
||||
.args(["search", "--pid", &pid.to_string(), "windowactivate"])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
pub fn find_game_hwnd(_pid: u32) -> Option<isize> {
|
||||
None
|
||||
}
|
||||
|
||||
+18
-2
@@ -151,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
|
||||
@@ -212,13 +222,19 @@ def link_target(path):
|
||||
def remove_link(path):
|
||||
p_str = str(path)
|
||||
try:
|
||||
if path.is_dir() or (hasattr(path, 'is_junction') and path.is_junction()):
|
||||
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 os.path.isdir(path):
|
||||
if path.is_symlink():
|
||||
path.unlink()
|
||||
elif os.path.isdir(path):
|
||||
os.rmdir(path)
|
||||
else:
|
||||
path.unlink()
|
||||
|
||||
Reference in new issue
Block a user