From 7b8a89a5e4e53f5af12af8a255d10f0a5e07c060 Mon Sep 17 00:00:00 2001 From: goldorakiller Date: Thu, 13 Aug 2026 23:02:35 +0200 Subject: [PATCH 1/2] Refactor Main() into dedicated classes and fix startup bugs - Fix: propagate --external-launcher to the intro video, so it no longer forces focus back to EmulationStation when launched from an external frontend. - Fix: align FocusDelay/VideoDelay fallback defaults with the values written to the default retrobat.ini (previously inconsistent). - Fix: preserve original exception stack trace in IniFile's constructor (`throw ex;` -> `throw;`). - Cleanup: remove RawInput.cs (dead code, never referenced) and the orphaned SharpDX packages.config entry (no longer referenced by the project). - Cleanup: replace empty `catch { }` blocks with logged warnings across Program.cs, SplashVideo.cs and VideoPlayerForm.cs. - Refactor: split the ~900-line Main() into focused, testable classes: StartupFileChecker, RetroBatConfigLoader, AutostartManager, DpiAwarenessManager, EmulationStationSettings and EmulationStationLauncher. Behavior is preserved, including edge cases (early return when EmulationStation fails to start still skips the final success log, same as before). --- RetroBat/RetroBat/AutostartManager.cs | 127 +++ RetroBat/RetroBat/DpiAwarenessManager.cs | 95 +++ RetroBat/RetroBat/EmulationStationLauncher.cs | 165 ++++ RetroBat/RetroBat/EmulationStationSettings.cs | 215 +++++ RetroBat/RetroBat/IniReader.cs | 11 +- RetroBat/RetroBat/Program.cs | 746 +----------------- RetroBat/RetroBat/RawInput.cs | 120 --- RetroBat/RetroBat/RetroBat.csproj | 12 +- RetroBat/RetroBat/RetroBatConfigLoader.cs | 78 ++ RetroBat/RetroBat/SplashVideo.cs | 4 +- RetroBat/RetroBat/StartupFileChecker.cs | 89 +++ RetroBat/RetroBat/VideoPlayerForm.cs | 2 +- RetroBat/RetroBat/packages.config | 4 - 13 files changed, 801 insertions(+), 867 deletions(-) create mode 100644 RetroBat/RetroBat/AutostartManager.cs create mode 100644 RetroBat/RetroBat/DpiAwarenessManager.cs create mode 100644 RetroBat/RetroBat/EmulationStationLauncher.cs create mode 100644 RetroBat/RetroBat/EmulationStationSettings.cs delete mode 100644 RetroBat/RetroBat/RawInput.cs create mode 100644 RetroBat/RetroBat/RetroBatConfigLoader.cs create mode 100644 RetroBat/RetroBat/StartupFileChecker.cs delete mode 100644 RetroBat/RetroBat/packages.config diff --git a/RetroBat/RetroBat/AutostartManager.cs b/RetroBat/RetroBat/AutostartManager.cs new file mode 100644 index 0000000..9f05618 --- /dev/null +++ b/RetroBat/RetroBat/AutostartManager.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using Microsoft.Win32; + +namespace RetroBat +{ + internal static class AutostartManager + { + public static void CleanupLegacyShortcut() + { + try + { + string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + string linkStartup = Path.Combine(startupFolder, "RetroBat.lnk"); + + if (File.Exists(linkStartup)) + { + try { File.Delete(linkStartup); } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to delete legacy RetroBat.lnk: " + ex.Message); } + } + } + catch (Exception ex) { SimpleLogger.Instance.Warning("CleanupStartup failed: " + ex.Message); } + } + + public static void Apply(int autostartMode, string appFolder, string appExe) + { + if (autostartMode == 1) + { + AddToStartupFolder(appFolder, appExe); + RemoveFromStartupReg(); + } + else if (autostartMode == 2) + { + AddToStartupReg(appFolder, appExe); + RemoveFromStartupFolder("RetroBat"); + } + else + { + RemoveFromStartupReg(); + RemoveFromStartupFolder("RetroBat"); + } + } + + private static void AddToStartupReg(string appPath, string appExe) + { + SimpleLogger.Instance.Info("Setting RetroBat to launch at startup."); + + string batPath = Path.Combine(appPath, appExe); + + string regValue = string.Format( + "cmd.exe /c \"cd /d {0} && start \"\" \"{1}\"\"\"", + appPath, + batPath + ); + + try + { + RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); + key.SetValue("RetroBat", regValue); + SimpleLogger.Instance.Info("RetroBat set in registry to startup."); + } + catch (Exception ex) + { + SimpleLogger.Instance.Warning("Failed to set startup registry key: " + ex.Message); + } + } + + private static void AddToStartupFolder(string exePath, string shortcutName) + { + try + { + string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + string exeName = Path.GetFileNameWithoutExtension(shortcutName); + string batPath = Path.Combine(startupFolder, exeName + ".bat"); + string exe = Path.Combine(exePath, shortcutName); + + // Write a simple batch file to start RetroBat + string batContent = $"@echo off{Environment.NewLine}cd /d \"{exePath}\"{Environment.NewLine}\"{exe}\""; + File.WriteAllText(batPath, batContent); + + SimpleLogger.Instance.Info("RetroBat batch added to Startup folder: " + batPath); + } + catch (Exception ex) + { + SimpleLogger.Instance.Warning("Failed to add RetroBat to Startup folder: " + ex.Message); + } + } + + private static void RemoveFromStartupFolder(string shortcutName) + { + try + { + string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + string batPath = Path.Combine(startupFolder, shortcutName + ".bat"); + + if (File.Exists(batPath)) + { + File.Delete(batPath); + SimpleLogger.Instance.Info("RetroBat removed from Startup folder: " + batPath); + } + else + { + SimpleLogger.Instance.Info("RetroBat startup batch not found, nothing to remove."); + } + } + catch (Exception ex) + { + SimpleLogger.Instance.Warning("Failed to remove RetroBat from Startup folder: " + ex.Message); + } + } + + private static void RemoveFromStartupReg() + { + SimpleLogger.Instance.Info("Ensuring RetroBat does not launch at startup."); + + try + { + RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); + key.DeleteValue("RetroBat"); + } + catch (Exception ex) + { + SimpleLogger.Instance.Warning("Failed to remove startup registry key: " + ex.Message); + } + } + } +} diff --git a/RetroBat/RetroBat/DpiAwarenessManager.cs b/RetroBat/RetroBat/DpiAwarenessManager.cs new file mode 100644 index 0000000..cf62d02 --- /dev/null +++ b/RetroBat/RetroBat/DpiAwarenessManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Win32; + +namespace RetroBat +{ + internal static class DpiAwarenessManager + { + public static void ApplyOverridesIfNeeded(string appFolder) + { + if (!HasDpiScaling()) + return; + + string dpiFile = Path.Combine(appFolder, "system", "tools", "dpi_awareness.txt"); + + if (!File.Exists(dpiFile)) + return; + + try + { + var dpiLines = File.ReadAllLines(dpiFile); + + if (dpiLines.Length > 0) + { + foreach (var dpiLine in dpiLines) + { + string dpiExePath = Path.Combine(appFolder, dpiLine.Trim()); + + if (File.Exists(dpiExePath)) + SetDpiAwarenessOverride(dpiExePath, true); + } + } + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to apply DPI awareness overrides: " + ex.Message); } + } + + public static bool HasDpiScaling() + { + using (var key = Registry.LocalMachine.OpenSubKey( + @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI")) + { + object val = key != null ? key.GetValue("LogPixels") : null; + if (val is int dpi) + return dpi != 96; + } + + using (var key = Registry.CurrentUser.OpenSubKey( + @"Control Panel\Desktop")) + { + object val = key != null ? key.GetValue("LogPixels") : null; + if (val is int dpi) + return dpi != 96; + } + + return false; + } + + public static void SetDpiAwarenessOverride(string exePath, bool enable) + { + const string keyPath = @"Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"; + + RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath, true) + ?? Registry.CurrentUser.CreateSubKey(keyPath); + + if (key == null) + return; + + using (key) + { + string current = key.GetValue(exePath) as string ?? string.Empty; + + var flags = new HashSet(current.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + + if (enable) + { + if (flags.Contains("HIGHDPIAWARE")) + return; + flags.Add("HIGHDPIAWARE"); + } + else + { + if (!flags.Contains("HIGHDPIAWARE")) + return; + flags.Remove("HIGHDPIAWARE"); + } + + if (flags.Count == 0) + key.DeleteValue(exePath, false); + else + key.SetValue(exePath, string.Join(" ", flags), RegistryValueKind.String); + } + } + } +} diff --git a/RetroBat/RetroBat/EmulationStationLauncher.cs b/RetroBat/RetroBat/EmulationStationLauncher.cs new file mode 100644 index 0000000..5259b3f --- /dev/null +++ b/RetroBat/RetroBat/EmulationStationLauncher.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Windows.Forms; + +namespace RetroBat +{ + internal static class EmulationStationLauncher + { + public static string BuildArguments(RetroBatConfig config, string esPath, Screen[] screens) + { + List commandArray = new List(); + + bool borderless = config.FullscreenBorderless; + + if (config.Fullscreen && config.ForceFullscreenRes) + { + commandArray.Add("--resolution"); + commandArray.Add(config.WindowXSize.ToString()); + commandArray.Add(config.WindowYSize.ToString()); + } + else if (!config.Fullscreen && !borderless) + { + commandArray.Add("--windowed"); + commandArray.Add("--resolution"); + commandArray.Add(config.WindowXSize.ToString()); + commandArray.Add(config.WindowYSize.ToString()); + } + else if (borderless) + { + commandArray.Add("--fullscreen-borderless"); + } + else + { + commandArray.Add("--fullscreen"); + } + + if (config.GameListOnly) + commandArray.Add("--gamelist-only"); + + if (config.InterfaceMode == 2) + commandArray.Add("--force-kid"); + else if (config.InterfaceMode == 1) + commandArray.Add("--force-kiosk"); + + if (config.MonitorIndex > 0 && config.MonitorIndex < screens.Length) + { + commandArray.Add("--monitor"); + commandArray.Add(config.MonitorIndex.ToString()); + } + + if (config.NoExitMenu) + commandArray.Add("--no-exit"); + + if (config.VSync) + commandArray.Add("--vsync 1"); + else + commandArray.Add("--vsync 0"); + + if (config.DrawFramerate) + commandArray.Add("--draw-framerate"); + + commandArray.Add("--home"); + commandArray.Add(esPath); + + return string.Join(" ", commandArray.Select(a => a.Contains(" ") ? "\"" + a + "\"" : a)); + } + + public static void RunWiimoteGun(string esPath) + { + SimpleLogger.Instance.Info("Running WiimoteGun."); + + string wgunExe = Path.Combine(esPath, "WiimoteGun.exe"); + + if (!File.Exists(wgunExe)) + { + SimpleLogger.Instance.Warning("WiimoteGun executable not found at: " + wgunExe); + return; + } + + try + { + var wgStart = new ProcessStartInfo + { + FileName = wgunExe, + WorkingDirectory = esPath, + UseShellExecute = false, + CreateNoWindow = true + }; + + Process.Start(wgStart); + SimpleLogger.Instance.Info("WiimoteGun started successfully."); + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to start WiimoteGun: " + ex.Message); } + } + + /// Starts EmulationStation and waits for/restores focus on its window. Returns false if the process failed to start. + public static bool LaunchAndFocus(ProcessStartInfo start, RetroBatConfig config, bool isExternalLauncher) + { + try + { + SimpleLogger.Instance.Info("Launching " + start.FileName + " " + start.Arguments); + + var exe = Process.Start(start); + if (exe == null) + { + SimpleLogger.Instance.Error("Failed to start EmulationStation process."); + return false; + } + + int maxWaitMs = 10000; + int intervalMs = 50; + int waited = 0; + + IntPtr esHandle = IntPtr.Zero; + + SimpleLogger.Instance.Info("Waiting for EmulationStation main window…"); + while (!exe.HasExited && esHandle == IntPtr.Zero && waited < maxWaitMs) + { + Thread.Sleep(intervalMs); + waited += intervalMs; + exe.Refresh(); + esHandle = exe.MainWindowHandle; + + if (waited % 1000 == 0) + SimpleLogger.Instance.Info($"…still waiting ({waited / 1000}s)"); + } + + if (esHandle == IntPtr.Zero) + { + SimpleLogger.Instance.Warning("EmulationStation window handle not detected (likely exclusive fullscreen). Skipping focus."); + } + + if (esHandle != IntPtr.Zero && !isExternalLauncher) + { + SplashVideo.CloseBlackSplash(); + Thread.Sleep(300); + + if (config.FocusDelay > 0) + { + Thread.Sleep(config.FocusDelay); + } + + FocusHelper.BringProcessWindowToFront(exe); + } + else + { + if (exe.HasExited) + SimpleLogger.Instance.Error("EmulationStation process exited before creating a window."); + else + SimpleLogger.Instance.Warning("EmulationStation process is running but no main window detected."); + } + } + catch (Exception ex) + { + SimpleLogger.Instance.Warning("Failed to start EmulationStation: " + ex.Message); + } + + return true; + } + } +} diff --git a/RetroBat/RetroBat/EmulationStationSettings.cs b/RetroBat/RetroBat/EmulationStationSettings.cs new file mode 100644 index 0000000..e3ae456 --- /dev/null +++ b/RetroBat/RetroBat/EmulationStationSettings.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml; + +namespace RetroBat +{ + internal static class EmulationStationSettings + { + private static readonly Random _rand = new Random(); + + public static void WriteLanguage(string esPath, CultureInfo culture) + { + string cultureText = culture.Name.ToString().Replace('-', '_'); + string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); + if (!File.Exists(esSettingsPath)) + { + SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); + throw new FileNotFoundException("es_settings.cfg not found."); + } + else + SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); + + SimpleLogger.Instance.Info("Updating EmulationStation language."); + + try + { + XmlDocument xml = new XmlDocument(); + xml.Load(esSettingsPath); + XmlNode languageNode = xml.SelectSingleNode("//string[@name='Language']"); + + if (languageNode != null && languageNode.Attributes != null) + { + // Update existing node + languageNode.Attributes["value"].Value = cultureText; + } + else + { + // Create the node + XmlElement newNode = xml.CreateElement("string"); + newNode.SetAttribute("name", "Language"); + newNode.SetAttribute("value", cultureText); + + // Append to root element + XmlNode configNode = xml.SelectSingleNode("/config"); + if (configNode != null) + configNode.AppendChild(newNode); + else + SimpleLogger.Instance.Warning("Could not update EmulationStation language."); + } + xml.Save(esSettingsPath); + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation language: " + ex.Message); } + } + + public static void SetGLVersion(string esPath, bool oldOpenGL) + { + string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); + if (!File.Exists(esSettingsPath)) + { + SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); + throw new FileNotFoundException("es_settings.cfg not found."); + } + else + SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); + + try + { + XmlDocument xml = new XmlDocument(); + xml.Load(esSettingsPath); + XmlNode GLNode = xml.SelectSingleNode("//string[@name='Renderer']"); + + if (GLNode != null && GLNode.Attributes != null) + { + if (oldOpenGL) + { + SimpleLogger.Instance.Info("es_settings.cfg, setting old renderer"); + GLNode.Attributes["value"].Value = "OPENGL 2.1"; + } + else + GLNode.RemoveAll(); + } + else if (oldOpenGL) + { + // Create the node + XmlElement newNode = xml.CreateElement("string"); + newNode.SetAttribute("name", "Renderer"); + newNode.SetAttribute("value", "OPENGL 2.1"); + + // Append to root element + XmlNode configNode = xml.SelectSingleNode("/config"); + if (configNode != null) + configNode.AppendChild(newNode); + else + SimpleLogger.Instance.Warning("Could not update EmulationStation renderer."); + } + xml.Save(esSettingsPath); + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation renderer: " + ex.Message); } + } + + public static void SetRandomTheme(string esPath, bool randomTheme) + { + if (!randomTheme) + return; + + bool updated = false; + + string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); + if (!File.Exists(esSettingsPath)) + { + SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); + throw new FileNotFoundException("es_settings.cfg not found."); + } + else + SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); + + try + { + XmlDocument xml = new XmlDocument(); + xml.Load(esSettingsPath); + XmlNode Theme = xml.SelectSingleNode("//string[@name='ThemeSet']"); + + if (Theme != null && Theme.Attributes != null) + { + string currentTheme = Theme.Attributes["value"]?.Value; + string themesPath = Path.Combine(esPath, ".emulationstation", "themes"); + + if (Directory.Exists(themesPath)) + { + var themeDirs = Directory.GetDirectories(themesPath); + var candidates = themeDirs.Select(Path.GetFileName).Where(t => !string.Equals(t, currentTheme, StringComparison.OrdinalIgnoreCase)).ToArray(); + + if (candidates.Length > 0) + { + string randomThemeName = candidates[_rand.Next(candidates.Length)]; + SimpleLogger.Instance.Info("es_settings.cfg, setting random theme: " + randomThemeName); + Theme.Attributes["value"].Value = randomThemeName; + updated = true; + } + else + SimpleLogger.Instance.Warning("No themes found in themes directory."); + } + else + SimpleLogger.Instance.Warning("Themes directory not found at: " + themesPath); + } + if (updated) + xml.Save(esSettingsPath); + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation theme: " + ex.Message); } + } + + public static void ResetToDefaults(string path) + { + SimpleLogger.Instance.Info("Resetting configuration."); + + List filesToReset = new List + { + "es_input.cfg", + "es_padtokey.cfg", + "es_settings.cfg", + "es_systems.cfg" + }; + + string templatepathES = Path.Combine(path, "system", "templates", "emulationstation"); + string esPath = Path.Combine(path, "emulationstation"); + string targetPath = Path.Combine(esPath, ".emulationstation"); + + foreach (var file in filesToReset) + { + string sourceFile = Path.Combine(templatepathES, file); + string targetFile = Path.Combine(targetPath, file); + + if (File.Exists(sourceFile)) + { + try + { + string oldFile = targetFile + ".old"; + File.Delete(oldFile); + File.Move(targetFile, oldFile); + File.Copy(sourceFile, targetFile, true); + SimpleLogger.Instance.Info($"Reset {file} to default."); + } + catch (Exception ex) { SimpleLogger.Instance.Warning($"Could not reset {file}: " + ex.Message); } + } + else + SimpleLogger.Instance.Warning($"Template file {sourceFile} does not exist."); + } + + string rbIniFile = Path.Combine(path, "retrobat.ini"); + + try + { + if (File.Exists(rbIniFile)) + { + try { File.Delete(rbIniFile); } + catch (Exception ex) { SimpleLogger.Instance.Warning("Could not delete RetroBat ini file: " + ex.Message); } + + SimpleLogger.Instance.Info("Deleted RetroBat ini file: " + rbIniFile); + } + + try + { + string iniDefault = IniFile.GetDefaultIniContent(); + File.WriteAllText(rbIniFile, iniDefault); + SimpleLogger.Instance.Info("ini file regenrated with default values."); + } + catch { SimpleLogger.Instance.Warning("Impossible to create ini file."); } + } + catch { SimpleLogger.Instance.Warning("Could not reinitialize ini file."); } + } + } +} diff --git a/RetroBat/RetroBat/IniReader.cs b/RetroBat/RetroBat/IniReader.cs index 11ac149..4cd5970 100644 --- a/RetroBat/RetroBat/IniReader.cs +++ b/RetroBat/RetroBat/IniReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -51,7 +51,7 @@ public static string GetDefaultIniContent() LanguageDetection=1 ; At startup RetroBat will reset the default config files options of emulationstation and retrobat.ini. -; Use at your own risk. +; Use at your own risk. ResetConfigMode=0 ; Run automatically RetroBat at Windows startup (0=NO 1=STARTUP 2=REGISTRY). @@ -111,7 +111,7 @@ public static string GetDefaultIniContent() ; The frontend will parse only the gamelist.xml files in roms directories to display available games. ; If files are added when this option is enabled, they will not appear in the gamelists of the frontend. The option must be enabled again to display new entries properly. GameListOnly=0 - + ; 0 = run the frontend normally. ; 1 = run the frontend in kiosk mode. ; 2 = run the frontend in kid mode. @@ -239,7 +239,8 @@ public IniFile(string path, IniOptions options = (IniOptions)0) } catch (Exception ex) { - throw ex; + SimpleLogger.Instance.Error("[IniFile] Failed to parse ini file " + path, ex); + throw; } } @@ -696,7 +697,7 @@ public class RetroBatConfig "--draw-framerate display the framerate" "--no-exit don't show the exit option in the menu" "--no-splash don't show the splash screen" - "--debug more logging, show console on Windows" + "--debug more logging, show console on Windows" "--windowed not fullscreen, should be used with --resolution" "--vsync [1/on or 0/off] turn vsync on or off (default is on)" "--max-vram [size] Max VRAM to use in Mb before swapping. 0 for unlimited" diff --git a/RetroBat/RetroBat/Program.cs b/RetroBat/RetroBat/Program.cs index 171eb45..ae5a4b8 100644 --- a/RetroBat/RetroBat/Program.cs +++ b/RetroBat/RetroBat/Program.cs @@ -1,18 +1,11 @@ -using Microsoft.Win32; using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Forms; -using System.Xml; -using System.Xml.Linq; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Rebar; namespace RetroBat { @@ -56,7 +49,7 @@ static void Main(string[] args) { MessageBox.Show("Executable name has been changed!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); } - + SimpleLogger.Instance.Info("[Startup] RetroBat.exe"); CultureInfo windowsCulture = CultureInfo.CurrentUICulture; @@ -80,104 +73,13 @@ static void Main(string[] args) } // Check existence of required files - SimpleLogger.Instance.Info("Checking availability of necessary files."); - string templatepathES = Path.Combine(appFolder, "system", "templates", "emulationstation"); - string versionInfoFile = Path.Combine(appFolder, "system", "version.info"); - - // ES folder - var esFiles = new HashSet(Directory.EnumerateFiles(esPath).Select(Path.GetFileName),StringComparer.OrdinalIgnoreCase); - - // about.info - if (!esFiles.Contains("about.info")) - { - SimpleLogger.Instance.Warning("Creating file 'about.info'"); - try { File.WriteAllText(Path.Combine(esPath, "about.info"), "RETROBAT"); } - catch { SimpleLogger.Instance.Warning("Impossible to create about.info file."); } - } - - // emulationstation - if (!esFiles.Contains("emulationstation.exe")) - { - SimpleLogger.Instance.Error("EmulationStation cannot be found at: " + Path.Combine(esPath, "emulationstation.exe")); - throw new FileNotFoundException("EmulationStation executable not found."); - } - - // emulatorlauncher - if (!esFiles.Contains("emulatorlauncher.exe")) - { - SimpleLogger.Instance.Error("EmulatorLauncher cannot be found at: " + Path.Combine(esPath, "emulatorlauncher.exe")); - throw new FileNotFoundException("EmulatorLauncher executable not found."); - } - - // optional - if (!esFiles.Contains("batocera-store.exe")) - SimpleLogger.Instance.Warning("Batocera-store executable not found, continuing without it."); - - if (!esFiles.Contains("batocera-systems.exe")) - SimpleLogger.Instance.Warning("Batocera-systems executable not found, continuing without it."); - - if (!esFiles.Contains("es-update.exe")) - SimpleLogger.Instance.Warning("es-update executable not found, continuing without it."); - - if (!esFiles.Contains("es-checkversion.exe")) - SimpleLogger.Instance.Warning("es-checkversion executable not found, continuing without it."); - - if (!esFiles.Contains("emulatorlauncher.common.dll")) - { - SimpleLogger.Instance.Error("emulatorlauncher common DLL does not exist"); - throw new FileNotFoundException("emulatorlauncher common DLL not found."); - } - - // check that es_features exists - if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_features.cfg"))) - { - SimpleLogger.Instance.Error("es_features cannot be found at: " + Path.Combine(esPath, ".emulationstation", "es_features.cfg")); - throw new FileNotFoundException("es_features not found."); - } - - // check that es_settings exists - if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_systems.cfg"))) - { - SimpleLogger.Instance.Warning("es_systems cannot be found, trying to copy template."); - - try { File.Copy(Path.Combine(templatepathES, "es_systems.cfg"), Path.Combine(esPath, ".emulationstation", "es_systems.cfg"), true); } catch { } - - if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_systems.cfg"))) - { - SimpleLogger.Instance.Error("es_systems cannot be found at: " + Path.Combine(esPath, ".emulationstation", "es_systems.cfg")); - throw new FileNotFoundException("es_systems not found."); - } - } - - // check that emulatorlauncher.cfg exists - if (!File.Exists(Path.Combine(esPath, "emulatorLauncher.cfg"))) - { - SimpleLogger.Instance.Warning("emulatorLauncher.cfg cannot be found, trying to copy template."); - - try { File.Copy(Path.Combine(templatepathES, "emulatorLauncher.cfg"), Path.Combine(esPath, "emulatorLauncher.cfg"), true); } catch { } - - if (!File.Exists(Path.Combine(esPath, "emulatorLauncher.cfg"))) - { - SimpleLogger.Instance.Error("emulatorLauncher.cfg cannot be found at: " + Path.Combine(esPath, "emulatorLauncher.cfg")); - throw new FileNotFoundException("emulatorLauncher.cfg not found."); - } - } - SimpleLogger.Instance.Info("All necessary files exist."); + StartupFileChecker.EnsureRequiredFilesExist(appFolder, esPath); // Write path to registry RegistryTools.SetRegistryKey(appFolder); // Get values from ini file - RetroBatConfig config = new RetroBatConfig(); - - using (IniFile ini = new IniFile(iniPath)) - { - SimpleLogger.Instance.Info("Reading values from inifile: " + iniPath); - config = GetConfigValues(ini); - - foreach (PropertyInfo prop in config.GetType().GetProperties()) - try { SimpleLogger.Instance.Info($"{prop.Name} = {prop.GetValue(config, null)}"); } catch { } - } + RetroBatConfig config = RetroBatConfigLoader.Load(iniPath); // Get emulationstation.exe path string emulationStationExe = Path.Combine(esPath, "emulationstation.exe"); @@ -190,62 +92,25 @@ static void Main(string[] args) SimpleLogger.Instance.Info("EmulationStation.exe found."); // DPI Awareness - if (HasDpiScaling()) - { - string dpiFile = Path.Combine(appFolder, "system", "tools", "dpi_awareness.txt"); - - if (File.Exists(dpiFile)) - { - try - { - var dpiLines = File.ReadAllLines(dpiFile); - - if (dpiLines.Length > 0) - { - foreach (var dpiLine in dpiLines) - { - string dpiExePath = Path.Combine(appFolder, dpiLine.Trim()); - - if (File.Exists(dpiExePath)) - SetDpiAwarenessOverride(dpiExePath, true); - } - } - } - catch { } - } - } + DpiAwarenessManager.ApplyOverridesIfNeeded(appFolder); // Language if (config.LanguageDetection) - WriteLanguageToES(esPath, windowsCulture); + EmulationStationSettings.WriteLanguage(esPath, windowsCulture); // Set old OpenGL - SetGLVersion(esPath, config.OpenGL2_1); + EmulationStationSettings.SetGLVersion(esPath, config.OpenGL2_1); // Set theme to random if enabled - SetRandomTheme(esPath, config.RandomTheme); + EmulationStationSettings.SetRandomTheme(esPath, config.RandomTheme); // Set RetroBat to start at startup - CleanupStartup(); - if (config.Autostart == 1) - { - AddToStartupFolder(appFolder, "RetroBat.exe"); - RemoveFromStartupReg(); - } - else if (config.Autostart == 2) - { - AddToStartupReg(appFolder, "RetroBat.exe"); - RemoveFromStartupFolder("RetroBat"); - } - else - { - RemoveFromStartupReg(); - RemoveFromStartupFolder("RetroBat"); - } + AutostartManager.CleanupLegacyShortcut(); + AutostartManager.Apply(config.Autostart, appFolder, "RetroBat.exe"); // Reset es_settings if (config.ResetConfigMode) - ResetESConfig(appFolder); + EmulationStationSettings.ResetToDefaults(appFolder); // Run splash video if enabled var screens = Screen.AllScreens; @@ -270,7 +135,7 @@ static void Main(string[] args) SplashVideo.ShowBlackSplash(targetScreen); var splashStart = DateTime.UtcNow; - var videoDone = SplashVideo.RunIntroVideo(config, esPath, targetScreen); + var videoDone = SplashVideo.RunIntroVideo(config, esPath, targetScreen, isExternalLauncher); // Wait depending on mode if (config.WaitForVideoEnd) @@ -294,67 +159,11 @@ static void Main(string[] args) // Arguments SimpleLogger.Instance.Info("Setting up arguments to run EmulationStation."); - List commandArray = new List(); - - bool borderless = config.FullscreenBorderless; - - if (config.Fullscreen && config.ForceFullscreenRes) - { - commandArray.Add("--resolution"); - commandArray.Add(config.WindowXSize.ToString()); - commandArray.Add(config.WindowYSize.ToString()); - } - - else if (!config.Fullscreen && !borderless) - { - commandArray.Add("--windowed"); - commandArray.Add("--resolution"); - commandArray.Add(config.WindowXSize.ToString()); - commandArray.Add(config.WindowYSize.ToString()); - } - - else if (borderless) - { - commandArray.Add("--fullscreen-borderless"); - } - else - { - commandArray.Add("--fullscreen"); - } - - if (config.GameListOnly) - commandArray.Add("--gamelist-only"); - - if (config.InterfaceMode == 2) - commandArray.Add("--force-kid"); - else if (config.InterfaceMode == 1) - commandArray.Add("--force-kiosk"); - - if (config.MonitorIndex > 0 && config.MonitorIndex < screens.Length) - { - commandArray.Add("--monitor"); - commandArray.Add(config.MonitorIndex.ToString()); - } - - if (config.NoExitMenu) - commandArray.Add("--no-exit"); - - if (config.VSync) - commandArray.Add("--vsync 1"); - else - commandArray.Add("--vsync 0"); - - if (config.DrawFramerate) - commandArray.Add("--draw-framerate"); - - commandArray.Add("--home"); - commandArray.Add(esPath); - - string elargs = string.Join(" ", commandArray.Select(a => a.Contains(" ") ? "\"" + a + "\"" : a)); + string elargs = EmulationStationLauncher.BuildArguments(config, esPath, screens); // Run wiimoteGun if enabled if (config.WiimoteGun) - RunWiimoteGun(esPath); + EmulationStationLauncher.RunWiimoteGun(esPath); // Run EmulationStation SimpleLogger.Instance.Info("Preparing to run emulationstation."); @@ -367,76 +176,16 @@ static void Main(string[] args) UseShellExecute = false }; - if (start == null) - return; - TimeSpan uptime = TimeSpan.FromMilliseconds(Environment.TickCount); if (config.Autostart != 0 && uptime.TotalSeconds < 10 && config.AutoStartDelay > 0) { SimpleLogger.Instance.Info("RetroBat set to run at startup, adding a delay."); int delay = config.AutoStartDelay; - System.Threading.Thread.Sleep(delay); + Thread.Sleep(delay); } - try - { - SimpleLogger.Instance.Info("Launching " + emulationStationExe + " " + elargs); - - var exe = Process.Start(start); - if (exe == null) - { - SimpleLogger.Instance.Error("Failed to start EmulationStation process."); - return; - } - - int maxWaitMs = 10000; - int intervalMs = 50; - int waited = 0; - - IntPtr esHandle = IntPtr.Zero; - - SimpleLogger.Instance.Info("Waiting for EmulationStation main window…"); - while (!exe.HasExited && esHandle == IntPtr.Zero && waited < maxWaitMs) - { - Thread.Sleep(intervalMs); - waited += intervalMs; - exe.Refresh(); - esHandle = exe.MainWindowHandle; - - if (waited % 1000 == 0) - SimpleLogger.Instance.Info($"…still waiting ({waited / 1000}s)"); - } - - if (esHandle == IntPtr.Zero) - { - SimpleLogger.Instance.Warning("EmulationStation window handle not detected (likely exclusive fullscreen). Skipping focus."); - } - - if (esHandle != IntPtr.Zero && !isExternalLauncher) - { - SplashVideo.CloseBlackSplash(); - Thread.Sleep(300); - - if (config.FocusDelay > 0) - { - Thread.Sleep(config.FocusDelay); - } - - FocusHelper.BringProcessWindowToFront(exe); - } - - else - { - if (exe.HasExited) - SimpleLogger.Instance.Error("EmulationStation process exited before creating a window."); - else - SimpleLogger.Instance.Warning("EmulationStation process is running but no main window detected."); - } - } - catch (Exception ex) - { - SimpleLogger.Instance.Warning("Failed to start EmulationStation: " + ex.Message); - } + if (!EmulationStationLauncher.LaunchAndFocus(start, config, isExternalLauncher)) + return; } finally @@ -446,468 +195,5 @@ static void Main(string[] args) SimpleLogger.Instance.Info("All is good, enjoy, quitting RetroBat launcher."); } - - private static RetroBatConfig GetConfigValues(IniFile ini) - { - RetroBatConfig config = new RetroBatConfig - { - LanguageDetection = GetOptBoolean(IniFile.GetOptionValue(ini, "RetroBat", "LanguageDetection", "true")), - ResetConfigMode = GetOptBoolean(IniFile.GetOptionValue(ini, "RetroBat", "ResetConfigMode", "false")), - WiimoteGun = GetOptBoolean(IniFile.GetOptionValue(ini, "RetroBat", "WiimoteGun", "false")), - EnableIntro = GetOptBoolean(IniFile.GetOptionValue(ini, "SplashScreen", "EnableIntro", "true")), - RandomVideo = GetOptBoolean(IniFile.GetOptionValue(ini, "SplashScreen", "RandomVideo", "true")), - GamepadVideoKill = GetOptBoolean(IniFile.GetOptionValue(ini, "SplashScreen", "GamepadVideoKill", "true")), - KillVideoWhenESReady = GetOptBoolean(IniFile.GetOptionValue(ini, "SplashScreen", "KillVideoWhenESReady", "false")), - WaitForVideoEnd = GetOptBoolean(IniFile.GetOptionValue(ini, "SplashScreen", "WaitForVideoEnd", "true")), - FileName = IniFile.GetOptionValue(ini, "SplashScreen", "FileName", "retrobat-neon.mp4"), - FilePath = IniFile.GetOptionValue(ini, "SplashScreen", "FilePath", "default"), - Fullscreen = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "Fullscreen", "true")), - FullscreenBorderless = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "FullscreenBorderless", "true")), - ForceFullscreenRes = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "ForceFullscreenRes", "false")), - GameListOnly = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "GameListOnly", "false")), - NoExitMenu = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "NoExitMenu", "false")), - OpenGL2_1 = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "OpenGL2_1", "false")), - VSync = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "VSync", "true")), - DrawFramerate = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "DrawFramerate", "false")), - RandomTheme = GetOptBoolean(IniFile.GetOptionValue(ini, "EmulationStation", "RandomTheme", "false")) - }; - - if (int.TryParse(IniFile.GetOptionValue(ini, "RetroBat", "Autostart", "0"), out int Autostart)) - config.Autostart = Autostart; - else - config.Autostart = 0; - - if (int.TryParse(IniFile.GetOptionValue(ini, "RetroBat", "AutoStartDelay", "0"), out int startdelay)) - config.AutoStartDelay = startdelay; - else - config.AutoStartDelay = 0; - - if (int.TryParse(IniFile.GetOptionValue(ini, "EmulationStation", "FocusDelay", "2000"), out int FocusDelay)) - config.FocusDelay = FocusDelay; - else - config.FocusDelay = 1000; - - if (int.TryParse(IniFile.GetOptionValue(ini, "SplashScreen", "VideoDelay", "5000"), out int VideoDelay)) - config.VideoDelay = VideoDelay; - else - config.VideoDelay = 1000; - - if (int.TryParse(IniFile.GetOptionValue(ini, "EmulationStation", "InterfaceMode", "0"), out int interfaceMode)) - config.InterfaceMode = interfaceMode; - else - config.InterfaceMode = 0; - - if (int.TryParse(IniFile.GetOptionValue(ini, "EmulationStation", "MonitorIndex", "0"), out int monitorIndex)) - config.MonitorIndex = monitorIndex; - else - config.MonitorIndex = 0; - - if (int.TryParse(IniFile.GetOptionValue(ini, "EmulationStation", "WindowXSize", "1280"), out int windowX)) - config.WindowXSize = windowX; - else - config.WindowXSize = 1280; - - if (int.TryParse(IniFile.GetOptionValue(ini, "EmulationStation", "WindowYSize", "720"), out int windowY)) - config.WindowYSize = windowY; - else - config.WindowYSize = 720; - - return config; - } - - public static bool GetOptBoolean(string input) - { - if (input == "1" || input == "true" || input == "yes") - return true; - else - return false; - } - - private static void AddToStartupReg(string appPath, string appExe) - { - SimpleLogger.Instance.Info("Setting RetroBat to launch at startup."); - - string batPath = Path.Combine(appPath, appExe); - - string regValue = string.Format( - "cmd.exe /c \"cd /d {0} && start \"\" \"{1}\"\"\"", - appPath, - batPath - ); - - try - { - RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); - key.SetValue("RetroBat", regValue); - SimpleLogger.Instance.Info("RetroBat set in registry to startup."); - } - catch (Exception ex) - { - SimpleLogger.Instance.Warning("Failed to set startup registry key: " + ex.Message); - } - } - - private static void AddToStartupFolder(string exePath, string shortcutName) - { - try - { - string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); - string exeName = Path.GetFileNameWithoutExtension(shortcutName); - string batPath = Path.Combine(startupFolder, exeName + ".bat"); - string exe = Path.Combine(exePath, shortcutName); - - // Write a simple batch file to start RetroBat - string batContent = $"@echo off{Environment.NewLine}cd /d \"{exePath}\"{Environment.NewLine}\"{exe}\""; - File.WriteAllText(batPath, batContent); - - SimpleLogger.Instance.Info("RetroBat batch added to Startup folder: " + batPath); - } - catch (Exception ex) - { - SimpleLogger.Instance.Warning("Failed to add RetroBat to Startup folder: " + ex.Message); - } - } - - private static void RemoveFromStartupFolder(string shortcutName) - { - try - { - string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); - string batPath = Path.Combine(startupFolder, shortcutName + ".bat"); - - if (File.Exists(batPath)) - { - File.Delete(batPath); - SimpleLogger.Instance.Info("RetroBat removed from Startup folder: " + batPath); - } - else - { - SimpleLogger.Instance.Info("RetroBat startup batch not found, nothing to remove."); - } - } - catch (Exception ex) - { - SimpleLogger.Instance.Warning("Failed to remove RetroBat from Startup folder: " + ex.Message); - } - } - - private static void CleanupStartup() - { - try - { - string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup); - string linkStartup = Path.Combine(startupFolder, "RetroBat.lnk"); - - if (File.Exists(linkStartup)) - { - try { File.Delete(linkStartup); } catch { } - } - } - catch { } - } - - private static void RemoveFromStartupReg() - { - SimpleLogger.Instance.Info("Ensuring RetroBat does not launch at startup."); - - try - { - RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); - key.DeleteValue("RetroBat"); - } - catch (Exception ex) - { - SimpleLogger.Instance.Warning("Failed to remove startup registry key: " + ex.Message); - } - } - - private static void RunWiimoteGun(string esPath) - { - SimpleLogger.Instance.Info("Running WiimoteGun."); - - string wgunExe = Path.Combine(esPath, "WiimoteGun.exe"); - - if (!File.Exists(wgunExe)) - { - SimpleLogger.Instance.Warning("WiimoteGun executable not found at: " + wgunExe); - return; - } - - try - { - var wgStart = new ProcessStartInfo - { - FileName = wgunExe, - WorkingDirectory = esPath, - UseShellExecute = false, - CreateNoWindow = true - }; - - Process.Start(wgStart); - SimpleLogger.Instance.Info("WiimoteGun started successfully."); - } - catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to start WiimoteGun: " + ex.Message); } - } - - private static void ResetESConfig(string path) - { - SimpleLogger.Instance.Info("Resetting configuration."); - - List filesToReset = new List - { - "es_input.cfg", - "es_padtokey.cfg", - "es_settings.cfg", - "es_systems.cfg" - }; - - string templatepathES = Path.Combine(path, "system", "templates", "emulationstation"); - string esPath = Path.Combine(path, "emulationstation"); - string targetPath = Path.Combine(esPath, ".emulationstation"); - - foreach (var file in filesToReset) - { - string sourceFile = Path.Combine(templatepathES, file); - string targetFile = Path.Combine(targetPath, file); - - if (File.Exists(sourceFile)) - { - try - { - string oldFile = targetFile + ".old"; - File.Delete(oldFile); - File.Move(targetFile, oldFile); - File.Copy(sourceFile, targetFile, true); - SimpleLogger.Instance.Info($"Reset {file} to default."); - } - catch (Exception ex) { SimpleLogger.Instance.Warning($"Could not reset {file}: " + ex.Message); } - } - else - SimpleLogger.Instance.Warning($"Template file {sourceFile} does not exist."); - } - - string rbIniFile = Path.Combine(path, "retrobat.ini"); - - try - { - if (File.Exists(rbIniFile)) - { - try { File.Delete(rbIniFile); } - catch (Exception ex) { SimpleLogger.Instance.Warning("Could not delete RetroBat ini file: " + ex.Message); } - - SimpleLogger.Instance.Info("Deleted RetroBat ini file: " + rbIniFile); - } - - try - { - string iniDefault = IniFile.GetDefaultIniContent(); - File.WriteAllText(rbIniFile, iniDefault); - SimpleLogger.Instance.Info("ini file regenrated with default values."); - } - catch { SimpleLogger.Instance.Warning("Impossible to create ini file."); } - } - catch { SimpleLogger.Instance.Warning("Could not reinitialize ini file."); } - } - - private static void WriteLanguageToES(string esPath, CultureInfo culture) - { - string cultureText = culture.Name.ToString().Replace('-', '_'); - string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); - if (!File.Exists(esSettingsPath)) - { - SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); - throw new FileNotFoundException("es_settings.cfg not found."); - } - else - SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); - - SimpleLogger.Instance.Info("Updating EmulationStation language."); - - try - { - XmlDocument xml = new XmlDocument(); - xml.Load(esSettingsPath); - XmlNode languageNode = xml.SelectSingleNode("//string[@name='Language']"); - - if (languageNode != null && languageNode.Attributes != null) - { - // Update existing node - languageNode.Attributes["value"].Value = cultureText; - } - else - { - // Create the node - XmlElement newNode = xml.CreateElement("string"); - newNode.SetAttribute("name", "Language"); - newNode.SetAttribute("value", cultureText); - - // Append to root element - XmlNode configNode = xml.SelectSingleNode("/config"); - if (configNode != null) - configNode.AppendChild(newNode); - else - SimpleLogger.Instance.Warning("Could not update EmulationStation language."); - } - xml.Save(esSettingsPath); - } - catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation language: " + ex.Message); } - } - - private static void SetGLVersion(string esPath, bool oldOpenGL) - { - string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); - if (!File.Exists(esSettingsPath)) - { - SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); - throw new FileNotFoundException("es_settings.cfg not found."); - } - else - SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); - - try - { - XmlDocument xml = new XmlDocument(); - xml.Load(esSettingsPath); - XmlNode GLNode = xml.SelectSingleNode("//string[@name='Renderer']"); - - if (GLNode != null && GLNode.Attributes != null) - { - if (oldOpenGL) - { - SimpleLogger.Instance.Info("es_settings.cfg, setting old renderer"); - GLNode.Attributes["value"].Value = "OPENGL 2.1"; - } - else - GLNode.RemoveAll(); - } - else if (oldOpenGL) - { - // Create the node - XmlElement newNode = xml.CreateElement("string"); - newNode.SetAttribute("name", "Renderer"); - newNode.SetAttribute("value", "OPENGL 2.1"); - - // Append to root element - XmlNode configNode = xml.SelectSingleNode("/config"); - if (configNode != null) - configNode.AppendChild(newNode); - else - SimpleLogger.Instance.Warning("Could not update EmulationStation renderer."); - } - xml.Save(esSettingsPath); - } - catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation renderer: " + ex.Message); } - } - - private static readonly Random _rand = new Random(); - - private static void SetRandomTheme(string esPath, bool randomTheme) - { - if (!randomTheme) - return; - - bool updated = false; - - string esSettingsPath = Path.Combine(esPath, ".emulationstation", "es_settings.cfg"); - if (!File.Exists(esSettingsPath)) - { - SimpleLogger.Instance.Error("es_settings.cfg cannot be found at: " + esSettingsPath); - throw new FileNotFoundException("es_settings.cfg not found."); - } - else - SimpleLogger.Instance.Info("es_settings.cfg path: " + esSettingsPath); - - try - { - XmlDocument xml = new XmlDocument(); - xml.Load(esSettingsPath); - XmlNode Theme = xml.SelectSingleNode("//string[@name='ThemeSet']"); - - if (Theme != null && Theme.Attributes != null) - { - string currentTheme = Theme.Attributes["value"]?.Value; - string themesPath = Path.Combine(esPath, ".emulationstation", "themes"); - - if (Directory.Exists(themesPath)) - { - var themeDirs = Directory.GetDirectories(themesPath); - var candidates = themeDirs.Select(Path.GetFileName).Where(t => !string.Equals(t, currentTheme, StringComparison.OrdinalIgnoreCase)).ToArray(); - - if (candidates.Length > 0) - { - string randomThemeName = candidates[_rand.Next(candidates.Length)]; - SimpleLogger.Instance.Info("es_settings.cfg, setting random theme: " + randomThemeName); - Theme.Attributes["value"].Value = randomThemeName; - updated = true; - } - else - SimpleLogger.Instance.Warning("No themes found in themes directory."); - } - else - SimpleLogger.Instance.Warning("Themes directory not found at: " + themesPath); - } - if (updated) - xml.Save(esSettingsPath); - } - catch (Exception ex) { SimpleLogger.Instance.Warning("Could not update EmulationStation theme: " + ex.Message); } - } - - public static bool HasDpiScaling() - { - using (var key = Registry.LocalMachine.OpenSubKey( - @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI")) - { - object val = key != null ? key.GetValue("LogPixels") : null; - if (val is int dpi) - return dpi != 96; - } - - using (var key = Registry.CurrentUser.OpenSubKey( - @"Control Panel\Desktop")) - { - object val = key != null ? key.GetValue("LogPixels") : null; - if (val is int dpi) - return dpi != 96; - } - - return false; - } - - public static void SetDpiAwarenessOverride(string exePath, bool enable) - { - const string keyPath = @"Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"; - - RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath, true) - ?? Registry.CurrentUser.CreateSubKey(keyPath); - - if (key == null) - return; - - using (key) - { - string current = key.GetValue(exePath) as string ?? string.Empty; - - var flags = new HashSet(current.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); - - if (enable) - { - if (flags.Contains("HIGHDPIAWARE")) - return; - flags.Add("HIGHDPIAWARE"); - } - else - { - if (!flags.Contains("HIGHDPIAWARE")) - return; - flags.Remove("HIGHDPIAWARE"); - } - - if (flags.Count == 0) - key.DeleteValue(exePath, false); - else - key.SetValue(exePath, string.Join(" ", flags), RegistryValueKind.String); - } - } } } - diff --git a/RetroBat/RetroBat/RawInput.cs b/RetroBat/RetroBat/RawInput.cs deleted file mode 100644 index 60ad937..0000000 --- a/RetroBat/RetroBat/RawInput.cs +++ /dev/null @@ -1,120 +0,0 @@ -using RetroBat; -using System; -using System.Drawing; -using System.Linq; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -namespace RetroBat -{ - public abstract class RawInputForm : Form - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - SimpleLogger.Instance.Info("RawInputForm started, registering raw input devices..."); - - // Register to receive raw input from gamepads (usage page 1, usage 5 = gamepad) - RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1]; - rid[0].usUsagePage = 0x01; // Generic Desktop Controls - rid[0].usUsage = 0x05; // Gamepad (use 0x04 for Joystick) - rid[0].dwFlags = RIDEV_INPUTSINK; // Receive input even if not focused - rid[0].hwndTarget = this.Handle; - - if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE)))) - SimpleLogger.Instance.Warning("Failed to register raw input device(s)."); - else - SimpleLogger.Instance.Info("Registered raw input device(s) successfully."); - } - - protected bool RawInputDetected { get; private set; } - - protected override void WndProc(ref Message m) - { - if (m.Msg == WM_INPUT) - { - uint dwSize = 0; - // First get the size of the raw input data - GetRawInputData(m.LParam, RID_INPUT, IntPtr.Zero, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))); - - if (dwSize > 0) - { - IntPtr buffer = Marshal.AllocHGlobal((int)dwSize); - try - { - uint readSize = GetRawInputData(m.LParam, RID_INPUT, buffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))); - if (readSize == dwSize) - { - // Read the header - RAWINPUTHEADER header = (RAWINPUTHEADER)Marshal.PtrToStructure(buffer, typeof(RAWINPUTHEADER)); - - if (header.dwType == RIM_TYPEHID) - { - IntPtr pRawHidData = IntPtr.Add(buffer, Marshal.SizeOf(typeof(RAWINPUTHEADER))); - RAWHID rawHid = (RAWHID)Marshal.PtrToStructure(pRawHidData, typeof(RAWHID)); - IntPtr pRawData = IntPtr.Add(pRawHidData, Marshal.SizeOf(typeof(RAWHID))); - - int rawDataLength = (int)(rawHid.dwSizeHid * rawHid.dwCount); - byte[] rawData = new byte[rawDataLength]; - Marshal.Copy(pRawData, rawData, 0, rawDataLength); - - - if (rawData.Any(r => r >= 1 && r <= 64)) - RawInputDetected = true; - } - } - } - finally - { - Marshal.FreeHGlobal(buffer); - } - } - return; - } - base.WndProc(ref m); - } - - #region Api - // Constants - const int WM_INPUT = 0x00FF; - const uint RID_INPUT = 0x10000003; - const uint RIM_TYPEHID = 2; - const uint RIDEV_INPUTSINK = 0x00000100; - - // P/Invoke declarations - [DllImport("User32.dll")] - static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevices, uint uiNumDevices, uint cbSize); - - [DllImport("User32.dll")] - static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader); - - // Structures - [StructLayout(LayoutKind.Sequential)] - struct RAWINPUTDEVICE - { - public ushort usUsagePage; - public ushort usUsage; - public uint dwFlags; - public IntPtr hwndTarget; - } - - [StructLayout(LayoutKind.Sequential)] - struct RAWINPUTHEADER - { - public uint dwType; - public uint dwSize; - public IntPtr hDevice; - public IntPtr wParam; - } - - [StructLayout(LayoutKind.Sequential)] - struct RAWHID - { - public uint dwSizeHid; - public uint dwCount; - // Followed by variable length raw data, handled manually - } - #endregion - } -} \ No newline at end of file diff --git a/RetroBat/RetroBat/RetroBat.csproj b/RetroBat/RetroBat/RetroBat.csproj index abd3d61..a01e627 100644 --- a/RetroBat/RetroBat/RetroBat.csproj +++ b/RetroBat/RetroBat/RetroBat.csproj @@ -1,4 +1,4 @@ - + @@ -58,16 +58,19 @@ + + + + - - Form - + + Form @@ -75,7 +78,6 @@ - diff --git a/RetroBat/RetroBat/RetroBatConfigLoader.cs b/RetroBat/RetroBat/RetroBatConfigLoader.cs new file mode 100644 index 0000000..48e2762 --- /dev/null +++ b/RetroBat/RetroBat/RetroBatConfigLoader.cs @@ -0,0 +1,78 @@ +using System; +using System.Reflection; + +namespace RetroBat +{ + internal static class RetroBatConfigLoader + { + public static RetroBatConfig Load(string iniPath) + { + RetroBatConfig config; + + using (IniFile ini = new IniFile(iniPath)) + { + SimpleLogger.Instance.Info("Reading values from inifile: " + iniPath); + config = GetConfigValues(ini); + + foreach (PropertyInfo prop in config.GetType().GetProperties()) + try { SimpleLogger.Instance.Info($"{prop.Name} = {prop.GetValue(config, null)}"); } + catch (Exception ex) { SimpleLogger.Instance.Warning($"Failed to log config property '{prop.Name}': " + ex.Message); } + } + + return config; + } + + private static RetroBatConfig GetConfigValues(IniFile ini) + { + return new RetroBatConfig + { + LanguageDetection = GetOptBool(ini, "RetroBat", "LanguageDetection", true), + ResetConfigMode = GetOptBool(ini, "RetroBat", "ResetConfigMode", false), + Autostart = GetOptInt(ini, "RetroBat", "Autostart", 0), + AutoStartDelay = GetOptInt(ini, "RetroBat", "AutoStartDelay", 0), + WiimoteGun = GetOptBool(ini, "RetroBat", "WiimoteGun", false), + EnableIntro = GetOptBool(ini, "SplashScreen", "EnableIntro", true), + RandomVideo = GetOptBool(ini, "SplashScreen", "RandomVideo", true), + GamepadVideoKill = GetOptBool(ini, "SplashScreen", "GamepadVideoKill", true), + KillVideoWhenESReady = GetOptBool(ini, "SplashScreen", "KillVideoWhenESReady", false), + WaitForVideoEnd = GetOptBool(ini, "SplashScreen", "WaitForVideoEnd", true), + FileName = IniFile.GetOptionValue(ini, "SplashScreen", "FileName", "retrobat-neon.mp4"), + FilePath = IniFile.GetOptionValue(ini, "SplashScreen", "FilePath", "default"), + VideoDelay = GetOptInt(ini, "SplashScreen", "VideoDelay", 1000), + Fullscreen = GetOptBool(ini, "EmulationStation", "Fullscreen", true), + FullscreenBorderless = GetOptBool(ini, "EmulationStation", "FullscreenBorderless", true), + ForceFullscreenRes = GetOptBool(ini, "EmulationStation", "ForceFullscreenRes", false), + GameListOnly = GetOptBool(ini, "EmulationStation", "GameListOnly", false), + NoExitMenu = GetOptBool(ini, "EmulationStation", "NoExitMenu", false), + OpenGL2_1 = GetOptBool(ini, "EmulationStation", "OpenGL2_1", false), + VSync = GetOptBool(ini, "EmulationStation", "VSync", true), + DrawFramerate = GetOptBool(ini, "EmulationStation", "DrawFramerate", false), + RandomTheme = GetOptBool(ini, "EmulationStation", "RandomTheme", false), + FocusDelay = GetOptInt(ini, "EmulationStation", "FocusDelay", 2000), + InterfaceMode = GetOptInt(ini, "EmulationStation", "InterfaceMode", 0), + MonitorIndex = GetOptInt(ini, "EmulationStation", "MonitorIndex", 0), + WindowXSize = GetOptInt(ini, "EmulationStation", "WindowXSize", 1280), + WindowYSize = GetOptInt(ini, "EmulationStation", "WindowYSize", 720) + }; + } + + private static bool GetOptBool(IniFile ini, string section, string key, bool defaultValue) + { + return GetOptBoolean(IniFile.GetOptionValue(ini, section, key, defaultValue ? "true" : "false")); + } + + private static int GetOptInt(IniFile ini, string section, string key, int defaultValue) + { + string raw = IniFile.GetOptionValue(ini, section, key, defaultValue.ToString()); + return int.TryParse(raw, out int value) ? value : defaultValue; + } + + private static bool GetOptBoolean(string input) + { + if (input == "1" || input == "true" || input == "yes") + return true; + else + return false; + } + } +} diff --git a/RetroBat/RetroBat/SplashVideo.cs b/RetroBat/RetroBat/SplashVideo.cs index 8da220f..83ee32f 100644 --- a/RetroBat/RetroBat/SplashVideo.cs +++ b/RetroBat/RetroBat/SplashVideo.cs @@ -144,7 +144,7 @@ public static void ShowBlackSplash(Screen targetScreen = null) _blackSplashForm.Focus(); _blackSplashForm.Activate(); } - catch { } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to focus/activate black splash form: " + ex.Message); } }; _blackSplashForm.Shown += (s, e) => splashDone.Set(); @@ -158,7 +158,7 @@ public static void ShowBlackSplash(Screen targetScreen = null) watchdog.Stop(); _blackSplashForm?.Close(); } - catch { } + catch (Exception ex) { SimpleLogger.Instance.Warning("Black splash watchdog failed to close form: " + ex.Message); } }; watchdog.Start(); diff --git a/RetroBat/RetroBat/StartupFileChecker.cs b/RetroBat/RetroBat/StartupFileChecker.cs new file mode 100644 index 0000000..3753f2b --- /dev/null +++ b/RetroBat/RetroBat/StartupFileChecker.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace RetroBat +{ + internal static class StartupFileChecker + { + public static void EnsureRequiredFilesExist(string appFolder, string esPath) + { + SimpleLogger.Instance.Info("Checking availability of necessary files."); + string templatepathES = Path.Combine(appFolder, "system", "templates", "emulationstation"); + + var esFiles = new HashSet(Directory.EnumerateFiles(esPath).Select(Path.GetFileName), System.StringComparer.OrdinalIgnoreCase); + + if (!esFiles.Contains("about.info")) + { + SimpleLogger.Instance.Warning("Creating file 'about.info'"); + try { File.WriteAllText(Path.Combine(esPath, "about.info"), "RETROBAT"); } + catch { SimpleLogger.Instance.Warning("Impossible to create about.info file."); } + } + + if (!esFiles.Contains("emulationstation.exe")) + { + SimpleLogger.Instance.Error("EmulationStation cannot be found at: " + Path.Combine(esPath, "emulationstation.exe")); + throw new FileNotFoundException("EmulationStation executable not found."); + } + + if (!esFiles.Contains("emulatorlauncher.exe")) + { + SimpleLogger.Instance.Error("EmulatorLauncher cannot be found at: " + Path.Combine(esPath, "emulatorlauncher.exe")); + throw new FileNotFoundException("EmulatorLauncher executable not found."); + } + + if (!esFiles.Contains("batocera-store.exe")) + SimpleLogger.Instance.Warning("Batocera-store executable not found, continuing without it."); + + if (!esFiles.Contains("batocera-systems.exe")) + SimpleLogger.Instance.Warning("Batocera-systems executable not found, continuing without it."); + + if (!esFiles.Contains("es-update.exe")) + SimpleLogger.Instance.Warning("es-update executable not found, continuing without it."); + + if (!esFiles.Contains("es-checkversion.exe")) + SimpleLogger.Instance.Warning("es-checkversion executable not found, continuing without it."); + + if (!esFiles.Contains("emulatorlauncher.common.dll")) + { + SimpleLogger.Instance.Error("emulatorlauncher common DLL does not exist"); + throw new FileNotFoundException("emulatorlauncher common DLL not found."); + } + + if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_features.cfg"))) + { + SimpleLogger.Instance.Error("es_features cannot be found at: " + Path.Combine(esPath, ".emulationstation", "es_features.cfg")); + throw new FileNotFoundException("es_features not found."); + } + + if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_systems.cfg"))) + { + SimpleLogger.Instance.Warning("es_systems cannot be found, trying to copy template."); + + try { File.Copy(Path.Combine(templatepathES, "es_systems.cfg"), Path.Combine(esPath, ".emulationstation", "es_systems.cfg"), true); } + catch (System.Exception ex) { SimpleLogger.Instance.Warning("Failed to copy es_systems.cfg template: " + ex.Message); } + + if (!File.Exists(Path.Combine(esPath, ".emulationstation", "es_systems.cfg"))) + { + SimpleLogger.Instance.Error("es_systems cannot be found at: " + Path.Combine(esPath, ".emulationstation", "es_systems.cfg")); + throw new FileNotFoundException("es_systems not found."); + } + } + + if (!File.Exists(Path.Combine(esPath, "emulatorLauncher.cfg"))) + { + SimpleLogger.Instance.Warning("emulatorLauncher.cfg cannot be found, trying to copy template."); + + try { File.Copy(Path.Combine(templatepathES, "emulatorLauncher.cfg"), Path.Combine(esPath, "emulatorLauncher.cfg"), true); } + catch (System.Exception ex) { SimpleLogger.Instance.Warning("Failed to copy emulatorLauncher.cfg template: " + ex.Message); } + + if (!File.Exists(Path.Combine(esPath, "emulatorLauncher.cfg"))) + { + SimpleLogger.Instance.Error("emulatorLauncher.cfg cannot be found at: " + Path.Combine(esPath, "emulatorLauncher.cfg")); + throw new FileNotFoundException("emulatorLauncher.cfg not found."); + } + } + SimpleLogger.Instance.Info("All necessary files exist."); + } + } +} diff --git a/RetroBat/RetroBat/VideoPlayerForm.cs b/RetroBat/RetroBat/VideoPlayerForm.cs index 2c2a93b..c4e7e7a 100644 --- a/RetroBat/RetroBat/VideoPlayerForm.cs +++ b/RetroBat/RetroBat/VideoPlayerForm.cs @@ -31,7 +31,7 @@ public VideoPlayerForm(string videoPath, string path, bool gamepadKill = false, if (File.Exists(_path)) { try { File.Delete(_path); } - catch { } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to delete stale emulationstation.ready file: " + ex.Message); } } var screen = targetScreen ?? Screen.PrimaryScreen; diff --git a/RetroBat/RetroBat/packages.config b/RetroBat/RetroBat/packages.config deleted file mode 100644 index 0b6b0a3..0000000 --- a/RetroBat/RetroBat/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 80e1f43987d73e86ccff0845dd167df438ee9710 Mon Sep 17 00:00:00 2001 From: goldorakiller Date: Thu, 13 Aug 2026 23:03:12 +0200 Subject: [PATCH 2/2] Add AppLauncher: launch companion apps in parallel with RetroBat Adds a retrobat.ini option to launch one or more external programs (.exe, .bat, .cmd...) alongside EmulationStation, useful for companion tools such as DMD emulators, overlays, or other launcher utilities. - New [RetroBat] key AppLauncher (and AppLauncher2, AppLauncher3, ... for additional apps). Empty by default, fully opt-in. - Optional "-nowindow" suffix on the value to start the app without a visible window (uses CreateNoWindow, only effective when paired with UseShellExecute=false). - Apps are launched fire-and-forget as early as possible in the startup sequence (right after retrobat.ini is loaded), so they run in parallel with the rest of RetroBat's startup instead of waiting for the splash video and EmulationStation to be ready. - Missing files or launch failures are logged as warnings and never block RetroBat's own startup. --- RetroBat/RetroBat/EmulationStationLauncher.cs | 57 +++++++++++++++++++ RetroBat/RetroBat/IniReader.cs | 14 +++-- RetroBat/RetroBat/Program.cs | 3 + RetroBat/RetroBat/RetroBatConfigLoader.cs | 33 ++++++++++- 4 files changed, 102 insertions(+), 5 deletions(-) diff --git a/RetroBat/RetroBat/EmulationStationLauncher.cs b/RetroBat/RetroBat/EmulationStationLauncher.cs index 5259b3f..3271414 100644 --- a/RetroBat/RetroBat/EmulationStationLauncher.cs +++ b/RetroBat/RetroBat/EmulationStationLauncher.cs @@ -97,6 +97,63 @@ public static void RunWiimoteGun(string esPath) catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to start WiimoteGun: " + ex.Message); } } + /// Launches every configured companion app (AppLauncher, AppLauncher2... in retrobat.ini), if any, in parallel with EmulationStation. Fire-and-forget: does not wait for them to exit. + public static void RunExternalApps(IEnumerable appLaunchers) + { + if (appLaunchers == null) + return; + + foreach (var appLauncher in appLaunchers) + RunExternalApp(appLauncher); + } + + /// Launches a single companion app entry. Append " -nowindow" to the ini value to start it hidden; otherwise it starts normally. + private static void RunExternalApp(string appLauncher) + { + if (string.IsNullOrWhiteSpace(appLauncher)) + return; + + string appPath = ParseAppLauncherPath(appLauncher, out bool noWindow); + + if (string.IsNullOrWhiteSpace(appPath) || !File.Exists(appPath)) + { + SimpleLogger.Instance.Warning("AppLauncher file not found at: " + appPath); + return; + } + + SimpleLogger.Instance.Info("Starting external app: " + appPath + (noWindow ? " (no window)" : "")); + + try + { + var appStart = new ProcessStartInfo + { + FileName = appPath, + WorkingDirectory = Path.GetDirectoryName(appPath), + UseShellExecute = !noWindow, + CreateNoWindow = noWindow + }; + + Process.Start(appStart); + SimpleLogger.Instance.Info("External app started successfully."); + } + catch (Exception ex) { SimpleLogger.Instance.Warning("Failed to start external app: " + ex.Message); } + } + + private static string ParseAppLauncherPath(string raw, out bool noWindow) + { + noWindow = false; + string value = raw.Trim(); + + const string flag = "-nowindow"; + if (value.EndsWith(flag, StringComparison.OrdinalIgnoreCase)) + { + value = value.Substring(0, value.Length - flag.Length).Trim(); + noWindow = true; + } + + return value.Trim('"'); + } + /// Starts EmulationStation and waits for/restores focus on its window. Returns false if the process failed to start. public static bool LaunchAndFocus(ProcessStartInfo start, RetroBatConfig config, bool isExternalLauncher) { diff --git a/RetroBat/RetroBat/IniReader.cs b/RetroBat/RetroBat/IniReader.cs index 4cd5970..da05b65 100644 --- a/RetroBat/RetroBat/IniReader.cs +++ b/RetroBat/RetroBat/IniReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -51,7 +51,7 @@ public static string GetDefaultIniContent() LanguageDetection=1 ; At startup RetroBat will reset the default config files options of emulationstation and retrobat.ini. -; Use at your own risk. +; Use at your own risk. ResetConfigMode=0 ; Run automatically RetroBat at Windows startup (0=NO 1=STARTUP 2=REGISTRY). @@ -63,6 +63,11 @@ public static string GetDefaultIniContent() ; Run WiimoteGun at RetroBat's startup. You can use your wiimote as a gun and navigate through EmulationStation. WiimoteGun=0 +; Path to an additional application to launch in parallel with RetroBat (.exe, .bat, .cmd...). Leave empty to disable. Use quotes if the path contains spaces. +; Add -nowindow at the end to start it without a visible window (e.g. AppLauncher=""C:\tools\app.exe"" -nowindow). Without -nowindow, it starts normally. +; To launch more than one application, add AppLauncher2, AppLauncher3, etc. with the same syntax. +AppLauncher= + [SplashScreen] ; Set if video introduction is played before running the interface. @@ -111,7 +116,7 @@ public static string GetDefaultIniContent() ; The frontend will parse only the gamelist.xml files in roms directories to display available games. ; If files are added when this option is enabled, they will not appear in the gamelists of the frontend. The option must be enabled again to display new entries properly. GameListOnly=0 - + ; 0 = run the frontend normally. ; 1 = run the frontend in kiosk mode. ; 2 = run the frontend in kid mode. @@ -662,6 +667,7 @@ public class RetroBatConfig public int Autostart { get; set; } public int AutoStartDelay { get; set; } public bool WiimoteGun { get; set; } + public List AppLaunchers { get; set; } public bool EnableIntro { get; set; } public string FileName { get; set; } public string FilePath { get; set; } @@ -697,7 +703,7 @@ public class RetroBatConfig "--draw-framerate display the framerate" "--no-exit don't show the exit option in the menu" "--no-splash don't show the splash screen" - "--debug more logging, show console on Windows" + "--debug more logging, show console on Windows" "--windowed not fullscreen, should be used with --resolution" "--vsync [1/on or 0/off] turn vsync on or off (default is on)" "--max-vram [size] Max VRAM to use in Mb before swapping. 0 for unlimited" diff --git a/RetroBat/RetroBat/Program.cs b/RetroBat/RetroBat/Program.cs index ae5a4b8..3316afc 100644 --- a/RetroBat/RetroBat/Program.cs +++ b/RetroBat/RetroBat/Program.cs @@ -81,6 +81,9 @@ static void Main(string[] args) // Get values from ini file RetroBatConfig config = RetroBatConfigLoader.Load(iniPath); + // Launch companion apps as early as possible, in parallel with the rest of the startup sequence + EmulationStationLauncher.RunExternalApps(config.AppLaunchers); + // Get emulationstation.exe path string emulationStationExe = Path.Combine(esPath, "emulationstation.exe"); diff --git a/RetroBat/RetroBat/RetroBatConfigLoader.cs b/RetroBat/RetroBat/RetroBatConfigLoader.cs index 48e2762..2b7c8c2 100644 --- a/RetroBat/RetroBat/RetroBatConfigLoader.cs +++ b/RetroBat/RetroBat/RetroBatConfigLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; namespace RetroBat @@ -15,8 +16,17 @@ public static RetroBatConfig Load(string iniPath) config = GetConfigValues(ini); foreach (PropertyInfo prop in config.GetType().GetProperties()) - try { SimpleLogger.Instance.Info($"{prop.Name} = {prop.GetValue(config, null)}"); } + { + try + { + object value = prop.GetValue(config, null); + if (value is List list) + value = string.Join(", ", list); + + SimpleLogger.Instance.Info($"{prop.Name} = {value}"); + } catch (Exception ex) { SimpleLogger.Instance.Warning($"Failed to log config property '{prop.Name}': " + ex.Message); } + } } return config; @@ -31,6 +41,7 @@ private static RetroBatConfig GetConfigValues(IniFile ini) Autostart = GetOptInt(ini, "RetroBat", "Autostart", 0), AutoStartDelay = GetOptInt(ini, "RetroBat", "AutoStartDelay", 0), WiimoteGun = GetOptBool(ini, "RetroBat", "WiimoteGun", false), + AppLaunchers = GetAppLauncherEntries(ini), EnableIntro = GetOptBool(ini, "SplashScreen", "EnableIntro", true), RandomVideo = GetOptBool(ini, "SplashScreen", "RandomVideo", true), GamepadVideoKill = GetOptBool(ini, "SplashScreen", "GamepadVideoKill", true), @@ -56,6 +67,26 @@ private static RetroBatConfig GetConfigValues(IniFile ini) }; } + private static List GetAppLauncherEntries(IniFile ini) + { + var entries = new List(); + + // Backward compatible: original unnumbered key + string first = ini.GetValue("RetroBat", "AppLauncher"); + if (!string.IsNullOrWhiteSpace(first)) + entries.Add(first.Trim()); + + // Additional apps: AppLauncher2, AppLauncher3, ... + for (int i = 2; i <= 20; i++) + { + string value = ini.GetValue("RetroBat", "AppLauncher" + i); + if (!string.IsNullOrWhiteSpace(value)) + entries.Add(value.Trim()); + } + + return entries; + } + private static bool GetOptBool(IniFile ini, string section, string key, bool defaultValue) { return GetOptBoolean(IniFile.GetOptionValue(ini, section, key, defaultValue ? "true" : "false"));