using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using WandEnhancer.Core; using WandEnhancer.Models; using WandEnhancer.Utils; using WandEnhancer.View.MainWindow; namespace WandEnhancer { public static class Program { /// Log lines from a failed startup auto-patch, replayed by the UI when it opens. public static readonly List> StartupLog = new List>(); [STAThread] public static void Main(string[] args) { if (TryLaunchMode(args)) return; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; var application = new App(); application.InitializeComponent(); application.MainWindow = new MainWindow(); application.Run(); } private static bool TryLaunchMode(string[] args) { string myExe = Assembly.GetExecutingAssembly().Location; string myName = Path.GetFileNameWithoutExtension(myExe); if (!Constants.WeModBrandNames.Any( n => n.Equals(myName, StringComparison.OrdinalIgnoreCase))) return false; string myDir = Path.GetDirectoryName(myExe); string forwardedArgs = args.Length > 0 ? QuoteArguments(args) : null; LauncherLog.Open(myDir, $"WandEnhancer {Constants.Version} | {myExe}" + (forwardedArgs == null ? "" : $" | args {forwardedArgs}")); if (args.Length > 0 && args[0].StartsWith("--squirrel", StringComparison.OrdinalIgnoreCase)) { string updateExe = Path.Combine(myDir, "Update.exe"); if (File.Exists(updateExe)) { LauncherLog.Write($"Squirrel hook {args[0]} forwarded to Update.exe.", ELogType.Info); Process.Start(updateExe, QuoteArguments(args)); } else { LauncherLog.Write($"Squirrel hook {args[0]} ignored: Update.exe is missing.", ELogType.Warn); } return true; } var config = WeModInstalls.FindLatestWeMod(myDir); if (config == null) { LauncherLog.Write($"No Wand install found under {myDir}; opening the UI instead.", ELogType.Error); return false; } bool isPatched = Enhancer.IsPatched(config.RootDirectory); LauncherLog.Write($"Install {config.ExecutablePath} is {(isPatched ? "patched" : "not patched")}.", ELogType.Info); // A fresh Wand version drops our patches; re-apply the saved selection automatically. // On failure fall through to the UI so the user sees which patch broke. if (!isPatched && !TryAutoPatch(config, myDir)) return false; FuseLauncher.Launch(config.ExecutablePath, forwardedArgs, LauncherLog.Write); return true; } /// /// Re-quotes argv for a command line. Squirrel hands us paths with spaces /// (`--squirrel-install "C:\Users\Some Name\..."`); re-joining on spaces splits them. /// private static string QuoteArguments(IEnumerable args) { return string.Join(" ", args.Select(QuoteArgument)); } private static string QuoteArgument(string value) { if (!string.IsNullOrEmpty(value) && value.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) { return value; } // Backslashes are literal unless they run into the closing quote, where they double. var quoted = new System.Text.StringBuilder("\""); int backslashes = 0; foreach (char current in value ?? string.Empty) { if (current == '\\') { backslashes++; continue; } if (current == '"') { quoted.Append('\\', backslashes * 2 + 1).Append('"'); } else { quoted.Append('\\', backslashes).Append(current); } backslashes = 0; } return quoted.Append('\\', backslashes * 2).Append('"').ToString(); } private static bool TryAutoPatch(WeModConfig config, string launcherDir) { var patchConfig = Enhancer.LoadAutoPatchConfig(launcherDir); if (patchConfig == null) return true; // nothing saved to replay; launch as-is try { new Enhancer(config, RecordStartupLog, patchConfig).Patch(); return true; } catch (Exception e) { // Localization resources are not loaded yet in launcher mode (no Application), // so these two replay into the UI log in English by design. RecordStartupLog($"Auto-patch failed: {e.Message}", ELogType.Error); RecordStartupLog("The new Wand version may need updated patches. Restore the backup and patch again.", ELogType.Warn); return false; } } /// Buffers for the UI and mirrors to disk: auto-patch runs headless, so the /// file is the only copy if the user never opens the window afterwards. private static void RecordStartupLog(string message, ELogType type) { StartupLog.Add(new KeyValuePair(message, type)); LauncherLog.Write(message, type); } // Fires on the finalizer thread for a task nobody awaited. Non-fatal since .NET 4.5: // record it and mark it observed rather than killing a patch mid-run. private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { e.SetObserved(); RecordStartupLog($"Background task failed: {e.Exception.GetBaseException().Message}", ELogType.Error); } private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) { var error = e.ExceptionObject as Exception; MessageBox.Show( error?.Message ?? e.ExceptionObject?.ToString() ?? "Unknown error", Constants.RepoName, MessageBoxButtons.OK, MessageBoxIcon.Error); Environment.Exit(1); } } }