fix(windows): resolve black screen launch bug and refine shader screen 10s background lifecycle

This commit is contained in:
ScreamAngel committed 2026-11-07 14:05:47 +03:00
1 parent 8ba512f45e
commit 6bb6349c41
7 files changed
+149 -36

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ user/
out/
GLCache/
.runtime/
mod-game-*/
vkd3d-proton.cache
fsr4_shaders/
.local/
BIN
View File
Binary file not shown.
Binary file not shown.
+17 -6
View File
@@ -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
View File
@@ -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();
+9 -10
View File
@@ -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
View File
@@ -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()