diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index b383fcce..b983de49 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -114,6 +114,9 @@ namespace StardewModdingAPI.Framework
/// A callback to invoke after the game finishes initialising.
private readonly Action OnGameInitialised;
+ /// A callback to invoke when the game exits.
+ private readonly Action OnGameExiting;
+
/// Simplifies access to private game code.
private readonly Reflector Reflection;
@@ -136,7 +139,8 @@ namespace StardewModdingAPI.Framework
/// Simplifies access to private game code.
/// Manages SMAPI events for mods.
/// A callback to invoke after the game finishes initialising.
- internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised)
+ /// A callback to invoke when the game exits.
+ internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised, Action onGameExiting)
{
// init XNA
Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
@@ -147,6 +151,7 @@ namespace StardewModdingAPI.Framework
this.FirstUpdate = true;
this.Reflection = reflection;
this.OnGameInitialised = onGameInitialised;
+ this.OnGameExiting = onGameExiting;
if (this.ContentCore == null) // shouldn't happen since CreateContentManager is called first, but let's init here just in case
this.ContentCore = new ContentCore(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, this.Monitor, reflection);
@@ -167,6 +172,16 @@ namespace StardewModdingAPI.Framework
});
}
+ /// Perform cleanup logic when the game exits.
+ /// The event sender.
+ /// The event args.
+ /// This overrides the logic in and to let SMAPI clean up before exit.
+ protected override void OnExiting(object sender, EventArgs args)
+ {
+ Game1.multiplayer.Disconnect();
+ this.OnGameExiting?.Invoke();
+ }
+
/****
** Intercepted methods & events
****/
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index cf1c082a..8f91c32b 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -30,6 +31,7 @@ using StardewModdingAPI.Framework.Utilities;
using StardewValley;
using Monitor = StardewModdingAPI.Framework.Monitor;
using SObject = StardewValley.Object;
+using ThreadState = System.Threading.ThreadState;
namespace StardewModdingAPI
{
@@ -197,7 +199,7 @@ namespace StardewModdingAPI
// override game
SGame.MonitorDuringInitialisation = this.Monitor;
SGame.ReflectorDuringInitialisation = this.Reflection;
- this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.InitialiseAfterGameStart);
+ this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.InitialiseAfterGameStart, this.Dispose);
StardewValley.Program.gamePtr = this.GameInstance;
// add exit handler
@@ -221,10 +223,6 @@ namespace StardewModdingAPI
}).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();
ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged();
// set window titles
@@ -271,12 +269,11 @@ namespace StardewModdingAPI
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
{
- this.Monitor.Log("Disposing...", LogLevel.Trace);
-
// skip if already disposed
if (this.IsDisposed)
return;
this.IsDisposed = true;
+ this.Monitor.Log("Disposing...", LogLevel.Trace);
// dispose mod data
foreach (IModMetadata mod in this.ModRegistry.GetAll())
@@ -298,6 +295,9 @@ namespace StardewModdingAPI
this.CancellationTokenSource?.Dispose();
this.GameInstance?.Dispose();
this.LogFile?.Dispose();
+
+ // end game (moved from Game1.OnExiting to let us clean up first)
+ Process.GetCurrentProcess().Kill();
}