using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.AssemblyRewriters;
using StardewModdingAPI.AssemblyRewriters.Finders;
using StardewModdingAPI.AssemblyRewriters.Rewriters;
using StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers;
using StardewModdingAPI.Events;
using StardewValley;
namespace StardewModdingAPI
{
/// Contains SMAPI's constants and assumptions.
public static class Constants
{
/*********
** Properties
*********/
/// The directory path containing the current save's data (if a save is loaded).
private static string RawSavePath => Context.IsSaveLoaded ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : null;
/// Whether the directory containing the current save's data exists on disk.
private static bool SavePathReady => Context.IsSaveLoaded && Directory.Exists(Constants.RawSavePath);
/*********
** Accessors
*********/
/****
** Public
****/
/// SMAPI's current semantic version.
public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 11, 0);
/// The minimum supported version of Stardew Valley.
public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.26");
/// The maximum supported version of Stardew Valley.
public static ISemanticVersion MaximumGameVersion { get; } = null;
/// 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 directory name containing the current save's data (if a save is loaded and the directory exists).
public static string SaveFolderName => Context.IsSaveLoaded ? Constants.GetSaveFolderName() : "";
/// The directory path containing the current save's data (if a save is loaded and the directory exists).
public static string CurrentSavePath => Constants.SavePathReady ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : "";
/****
** Internal
****/
/// The GitHub repository to check for updates.
internal const string GitHubRepository = "Pathoschild/SMAPI";
/// The file path for the SMAPI configuration file.
internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json");
/// The file path to the log where the latest output should be saved.
internal static string DefaultLogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt");
/// The full path to the folder containing mods.
internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods");
/// The game's current semantic version.
internal static ISemanticVersion GameVersion { get; } = Constants.GetGameVersion();
/// The target game platform.
internal static Platform TargetPlatform { get; } =
#if SMAPI_FOR_WINDOWS
Platform.Windows;
#else
Platform.Mono;
#endif
/*********
** Protected methods
*********/
/// 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.Mono:
removeAssemblyReferences = new[]
{
"Stardew Valley",
"Microsoft.Xna.Framework",
"Microsoft.Xna.Framework.Game",
"Microsoft.Xna.Framework.Graphics"
};
targetAssemblies = new[]
{
typeof(StardewValley.Game1).Assembly,
typeof(Microsoft.Xna.Framework.Vector2).Assembly
};
break;
case Platform.Windows:
removeAssemblyReferences = new[]
{
"StardewValley",
"MonoGame.Framework"
};
targetAssemblies = new[]
{
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);
}
/// Get rewriters which detect or fix incompatible CIL instructions in mod assemblies.
internal static IEnumerable GetRewriters()
{
return new IInstructionRewriter[]
{
/****
** Finders throw an exception when incompatible code is found.
****/
// changes in Stardew Valley 1.2 (with no rewriters)
new FieldFinder("StardewValley.Item", "set_Name"),
// APIs removed in SMAPI 1.9
new TypeFinder("StardewModdingAPI.Advanced.ConfigFile"),
new TypeFinder("StardewModdingAPI.Advanced.IConfigFile"),
new TypeFinder("StardewModdingAPI.Entities.SPlayer"),
new TypeFinder("StardewModdingAPI.Extensions"),
new TypeFinder("StardewModdingAPI.Inheritance.SGame"),
new TypeFinder("StardewModdingAPI.Inheritance.SObject"),
new TypeFinder("StardewModdingAPI.LogWriter"),
new TypeFinder("StardewModdingAPI.Manifest"),
new TypeFinder("StardewModdingAPI.Version"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawDebug"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawTick"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck"),
new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck"),
/****
** Rewriters change CIL as needed to fix incompatible code
****/
// crossplatform
new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchWrapper), onlyIfPlatformChanged: true),
// Stardew Valley 1.2
new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)),
new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.currentMinigame)),
new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)),
new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)),
new FieldReplaceRewriter(typeof(Game1), "borderFont", nameof(Game1.smallFont)),
new FieldReplaceRewriter(typeof(Game1), "smoothFont", nameof(Game1.smallFont)),
// SMAPI 1.9
new TypeReferenceRewriter("StardewModdingAPI.Inheritance.ItemStackChange", typeof(ItemStackChange))
};
}
/// Get game current version as it should be displayed to players.
/// The semantic game version.
internal static ISemanticVersion GetGameDisplayVersion(ISemanticVersion version)
{
switch (version.ToString())
{
case "1.1.1":
return new SemanticVersion(1, 11, 0); // The 1.1 patch was released as 1.11
default:
return version;
}
}
/// Get the name of a save directory for the current player.
private static string GetSaveFolderName()
{
string prefix = new string(Game1.player.name.Where(char.IsLetterOrDigit).ToArray());
return $"{prefix}_{Game1.uniqueIDForThisGame}";
}
/// Get the game's current semantic version.
private static ISemanticVersion GetGameVersion()
{
// get raw version
// 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.");
string version = (string)field.GetValue(null);
// get semantic version
if (version == "1.11")
version = "1.1.1"; // The 1.1 patch was released as 1.11, which means it's out of order for semantic version checks
return new SemanticVersion(version);
}
}
}