/// <summary>Hook into Stardew Valley and launch the game.</summary> private void StartGame() { try { this.Monitor.Log("Loading game..."); // add error handlers #if SMAPI_FOR_WINDOWS Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); #endif AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); // override Game1 instance this.GameInstance = new SGame(this.Monitor); this.GameInstance.Exiting += (sender, e) => this.IsGameRunning = false; this.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(this.Monitor, sender, e); this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} with SMAPI {Constants.ApiVersion}"; StardewValley.Program.gamePtr = this.GameInstance; // configure Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; // load mods this.LoadMods(); if (this.CancellationTokenSource.IsCancellationRequested) { this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); return; } // initialise console after game launches new Thread(() => { // wait for the game to load up while (!this.IsGameRunning) { Thread.Sleep(1000); } // register help command this.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help <cmd>' returns command description", this.HandleHelpCommand); // listen for command line input this.Monitor.Log("Starting console..."); this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info); Thread consoleInputThread = new Thread(this.ConsoleInputLoop); consoleInputThread.Start(); while (this.IsGameRunning) { Thread.Sleep(1000 / 10); // Check if the game is still running 10 times a second } // abort the console thread, we're closing if (consoleInputThread.ThreadState == ThreadState.Running) { consoleInputThread.Abort(); } }).Start(); // start game loop this.Monitor.Log("Starting game..."); if (this.CancellationTokenSource.IsCancellationRequested) { this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); return; } try { this.IsGameRunning = true; this.GameInstance.Run(); } finally { this.IsGameRunning = false; } } catch (Exception ex) { this.Monitor.Log($"The game encountered a fatal error:\n{ex.GetLogSummary()}", LogLevel.Error); } }
/// <summary>Launch SMAPI.</summary> public void RunInteractively() { // initialise SMAPI try { // init logging this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} on {this.GetFriendlyPlatformName()}", LogLevel.Info); this.Monitor.Log($"Mods go here: {Constants.ModPath}"); this.Monitor.Log("Preparing SMAPI..."); // validate paths this.VerifyPath(Constants.ModPath); this.VerifyPath(Constants.LogDir); // validate game version if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but the oldest supported version is {Constants.GetGameDisplayVersion(Constants.MinimumGameVersion)}. Please update your game before using SMAPI. If you have the beta version on Steam, you may need to opt out to get the latest non-beta updates.", LogLevel.Error); this.PressAnyKeyToExit(); return; } if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) { this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.GetGameDisplayVersion(Constants.MaximumGameVersion)}. Please check for a newer version of SMAPI.", LogLevel.Error); this.PressAnyKeyToExit(); return; } // add error handlers #if SMAPI_FOR_WINDOWS Application.ThreadException += (sender, e) => this.Monitor.Log($"Critical thread exception: {e.Exception.GetLogSummary()}", LogLevel.Error); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); #endif AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); // override game this.GameInstance = new SGame(this.Monitor, this.Reflection); StardewValley.Program.gamePtr = this.GameInstance; // add exit handler new Thread(() => { this.CancellationTokenSource.Token.WaitHandle.WaitOne(); if (this.IsGameRunning) { try { File.WriteAllText(Constants.FatalCrashMarker, string.Empty); File.Copy(Constants.DefaultLogPath, Constants.FatalCrashLog, overwrite: true); } catch (Exception ex) { this.Monitor.Log($"SMAPI failed trying to track the crash details: {ex.GetLogSummary()}"); } this.GameInstance.Exit(); } }).Start(); // hook into game events #if SMAPI_FOR_WINDOWS ((Form)Control.FromHandle(this.GameInstance.Window.Handle)).FormClosing += (sender, args) => this.Dispose(); #endif this.GameInstance.Exiting += (sender, e) => this.Dispose(); this.GameInstance.Window.ClientSizeChanged += (sender, e) => GraphicsEvents.InvokeResize(this.Monitor, sender, e); GameEvents.InitializeInternal += (sender, e) => this.InitialiseAfterGameStart(); GameEvents.GameLoadedInternal += (sender, e) => this.CheckForUpdateAsync(); // set window titles this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion}"; Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}"; } catch (Exception ex) { this.Monitor.Log($"SMAPI failed to initialise: {ex.GetLogSummary()}", LogLevel.Error); this.PressAnyKeyToExit(); return; } // show details if game crashed during last session if (File.Exists(Constants.FatalCrashMarker)) { this.Monitor.Log("The game crashed last time you played. That can be due to bugs in the game, but if it happens repeatedly you can ask for help here: http://community.playstarbound.com/threads/108375/.", LogLevel.Error); this.Monitor.Log($"If you ask for help, make sure to attach this file: {Constants.FatalCrashLog}", LogLevel.Error); this.Monitor.Log("Press any key to delete the crash data and continue playing.", LogLevel.Info); Console.ReadKey(); File.Delete(Constants.FatalCrashLog); File.Delete(Constants.FatalCrashMarker); } // start game this.Monitor.Log("Starting game..."); try { this.IsGameRunning = true; this.GameInstance.Run(); } catch (Exception ex) { this.Monitor.Log($"The game failed unexpectedly: {ex.GetLogSummary()}", LogLevel.Error); this.PressAnyKeyToExit(); } finally { this.Dispose(); } }