using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
#if SMAPI_FOR_WINDOWS
#endif
using StardewModdingAPI.Framework;
using StardewModdingAPI.Internal;
namespace StardewModdingAPI
{
/// The main entry point for SMAPI, responsible for hooking into and launching the game.
internal class Program
{
/*********
** Fields
*********/
/// The absolute path to search for SMAPI's internal DLLs.
/// We can't use directly, since depends on DLLs loaded from this folder.
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "The assembly location is never null in this context.")]
internal static readonly string DllSearchPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "smapi-internal");
/*********
** Public methods
*********/
/// The main entry point which hooks into and launches the game.
/// The command-line arguments.
public static void Main(string[] args)
{
try
{
AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve;
Program.AssertGamePresent();
Program.AssertGameVersion();
Program.Start(args);
}
catch (Exception ex)
{
Console.WriteLine($"SMAPI failed to initialise: {ex}");
Program.PressAnyKeyToExit(true);
}
}
/*********
** Private methods
*********/
/// Method called when assembly resolution fails, which may return a manually resolved assembly.
/// The event sender.
/// The event arguments.
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
{
try
{
AssemblyName name = new AssemblyName(e.Name);
foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll"))
{
if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase))
return Assembly.LoadFrom(dll.FullName);
}
return null;
}
catch (Exception ex)
{
Console.WriteLine($"Error resolving assembly: {ex}");
return null;
}
}
/// Assert that the game is available.
/// This must be checked *before* any references to , and this method should not reference itself to avoid errors in Mono.
private static void AssertGamePresent()
{
Platform platform = EnvironmentUtility.DetectPlatform();
string gameAssemblyName = platform == Platform.Windows ? "Stardew Valley" : "StardewValley";
if (Type.GetType($"StardewValley.Game1, {gameAssemblyName}", throwOnError: false) == null)
{
Program.PrintErrorAndExit(
"Oops! SMAPI can't find the game. "
+ (Assembly.GetCallingAssembly().Location.Contains(Path.Combine("internal", "Windows")) || Assembly.GetCallingAssembly().Location.Contains(Path.Combine("internal", "Mono"))
? "It looks like you're running SMAPI from the download package, but you need to run the installed version instead. "
: "Make sure you're running StardewModdingAPI.exe in your game folder. "
)
+ "See the readme.txt file for details."
);
}
}
/// Assert that the game version is within and .
private static void AssertGameVersion()
{
// min version
if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion))
{
ISemanticVersion suggestedApiVersion = Constants.GetCompatibleApiVersion(Constants.GameVersion);
Program.PrintErrorAndExit(suggestedApiVersion != null
? $"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. You can install SMAPI {suggestedApiVersion} instead to fix this error, or update your game to the latest version."
: $"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI."
);
}
// max version
else if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion))
Program.PrintErrorAndExit($"Oops! You're running Stardew Valley {Constants.GameVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI: https://smapi.io.");
}
/// Initialise SMAPI and launch the game.
/// The command-line arguments.
/// This method is separate from because that can't contain any references to assemblies loaded by (e.g. via ), or Mono will incorrectly show an assembly resolution error before assembly resolution is set up.
private static void Start(string[] args)
{
// get flags from arguments
bool writeToConsole = !args.Contains("--no-terminal");
// get mods path from arguments
string modsPath = null;
{
int pathIndex = Array.LastIndexOf(args, "--mods-path") + 1;
if (pathIndex >= 1 && args.Length >= pathIndex)
{
modsPath = args[pathIndex];
if (!string.IsNullOrWhiteSpace(modsPath) && !Path.IsPathRooted(modsPath))
modsPath = Path.Combine(Constants.ExecutionPath, modsPath);
}
if (string.IsNullOrWhiteSpace(modsPath))
modsPath = Constants.DefaultModsPath;
}
// load SMAPI
using (SCore core = new SCore(modsPath, writeToConsole))
core.RunInteractively();
}
/// Write an error directly to the console and exit.
/// The error message to display.
private static void PrintErrorAndExit(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ResetColor();
Program.PressAnyKeyToExit(showMessage: true);
}
/// Show a 'press any key to exit' message, and exit when they press a key.
/// Whether to print a 'press any key to exit' message to the console.
private static void PressAnyKeyToExit(bool showMessage)
{
if (showMessage)
Console.WriteLine("Game has ended. Press any key to exit.");
Thread.Sleep(100);
Console.ReadKey();
Environment.Exit(0);
}
}
}