using System; using System.IO; using System.Linq; using System.Reflection; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Internal; using StardewValley; namespace StardewModdingAPI { /// Contains SMAPI's constants and assumptions. public static class Constants { /********* ** Accessors *********/ /**** ** Public ****/ /// SMAPI's current semantic version. public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.11.0"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.36"); /// The maximum supported version of Stardew Valley. public static ISemanticVersion MaximumGameVersion { get; } = new GameVersion("1.3.36"); /// The target game platform. public static GamePlatform TargetPlatform => (GamePlatform)Constants.Platform; /// The path to the game folder. public static string ExecutionPath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); /// The directory path containing Stardew Valley's app data. public static string DataPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); /// The directory path in which error logs should be stored. public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs"); /// The directory path where all saves are stored. public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves"); /// The name of the current save folder (if save info is available, regardless of whether the save file exists yet). public static string SaveFolderName { get { return Constants.GetSaveFolderName() #if SMAPI_3_0_STRICT ; #else ?? ""; #endif } } /// The absolute path to the current save folder (if save info is available and the save file exists). public static string CurrentSavePath { get { return Constants.GetSaveFolderPathIfExists() #if SMAPI_3_0_STRICT ; #else ?? ""; #endif } } /**** ** Internal ****/ /// The URL of the SMAPI home page. internal const string HomePageUrl = "https://smapi.io"; /// The absolute path to the folder containing SMAPI's internal files. internal static readonly string InternalFilesPath = Program.DllSearchPath; /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.config.json"); /// The file path for the SMAPI metadata file. internal static string ApiMetadataPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.metadata.json"); /// The filename prefix used for all SMAPI logs. internal static string LogNamePrefix { get; } = "SMAPI-"; /// The filename for SMAPI's main log, excluding the . internal static string LogFilename { get; } = $"{Constants.LogNamePrefix}latest"; /// The filename extension for SMAPI log files. internal static string LogExtension { get; } = "txt"; /// The file path for the log containing the previous fatal crash, if any. internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt"); /// The file path which stores a fatal crash message for the next run. internal static string FatalCrashMarker => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.crash.marker"); /// The file path which stores the detected update version for the next run. internal static string UpdateMarker => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.update.marker"); /// The default full path to search for mods. internal static string DefaultModsPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); /// The actual full path to search for mods. internal static string ModsPath { get; set; } /// The game's current semantic version. internal static ISemanticVersion GameVersion { get; } = new GameVersion(Constants.GetGameVersion()); /// The target game platform. internal static Platform Platform { get; } = EnvironmentUtility.DetectPlatform(); /// The game's assembly name. internal static string GameAssemblyName => Constants.Platform == Platform.Windows ? "Stardew Valley" : "StardewValley"; /********* ** Internal methods *********/ /// Get the SMAPI version to recommend for an older game version, if any. /// The game version to search. /// Returns the compatible SMAPI version, or null if none was found. internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version) { switch (version.ToString()) { case "1.3.28": return new SemanticVersion(2, 7, 0); case "1.2.30": case "1.2.31": case "1.2.32": case "1.2.33": return new SemanticVersion(2, 5, 5); } return null; } /// Get metadata for mapping assemblies to the current platform. /// The target game platform. internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform) { // get assembly changes needed for platform string[] removeAssemblyReferences; Assembly[] targetAssemblies; switch (targetPlatform) { case Platform.Linux: case Platform.Mac: removeAssemblyReferences = new[] { "Netcode", "Stardew Valley", "Microsoft.Xna.Framework", "Microsoft.Xna.Framework.Game", "Microsoft.Xna.Framework.Graphics", "Microsoft.Xna.Framework.Xact" }; targetAssemblies = new[] { typeof(StardewValley.Game1).Assembly, // note: includes Netcode types on Linux/Mac typeof(Microsoft.Xna.Framework.Vector2).Assembly }; break; case Platform.Windows: removeAssemblyReferences = new[] { "StardewValley", "MonoGame.Framework" }; targetAssemblies = new[] { typeof(Netcode.NetBool).Assembly, typeof(StardewValley.Game1).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly, typeof(Microsoft.Xna.Framework.Game).Assembly, typeof(Microsoft.Xna.Framework.Graphics.SpriteBatch).Assembly }; break; default: throw new InvalidOperationException($"Unknown target platform '{targetPlatform}'."); } return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); } /********* ** Private methods *********/ /// Get the game's current version string. private static string GetGameVersion() { // we need reflection because it's a constant, so SMAPI's references to it are inlined at compile-time FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); if (field == null) throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); return (string)field.GetValue(null); } /// Get the name of the save folder, if any. internal static string GetSaveFolderName() { // save not available if (Context.LoadStage == LoadStage.None) return null; // get basic info string playerName; ulong saveID; if (Context.LoadStage == LoadStage.SaveParsed) { playerName = SaveGame.loaded.player.Name; saveID = SaveGame.loaded.uniqueIDForThisGame; } else { playerName = Game1.player.Name; saveID = Game1.uniqueIDForThisGame; } // build folder name return $"{new string(playerName.Where(char.IsLetterOrDigit).ToArray())}_{saveID}"; } /// Get the path to the current save folder, if any. internal static string GetSaveFolderPathIfExists() { string folderName = Constants.GetSaveFolderName(); if (folderName == null) return null; string path = Path.Combine(Constants.SavesPath, folderName); return Directory.Exists(path) ? path : null; } } }