using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Events; using StardewModdingAPI.ExtensionMethods; using StardewModdingAPI.Helpers; using StardewModdingAPI.Inheritance; using StardewModdingAPI.Inheritance.Menus; using StardewValley; using StardewValley.Menus; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Windows.Forms; namespace StardewModdingAPI { public class Program { private static List _modPaths; private static List _modContentPaths; public static Texture2D DebugPixel { get; private set; } public static bool IsGameReferenceDirty { get; set; } public static object gameInst; public static Game1 _gamePtr; public static Game1 gamePtr { get { if(IsGameReferenceDirty && gameInst != null) { _gamePtr = gameInst.Copy(); } return _gamePtr; } } public static bool ready; public static Form StardewForm; public static Thread gameThread; public static Thread consoleInputThread; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Main method holding the API execution /// /// private static void Main(string[] args) { try { ConfigureUI(); ConfigurePaths(); ConfigureMethodInjection(); ConfigureSDV(); GameRunInvoker(); } catch (Exception e) { // Catch and display all exceptions. StardewModdingAPI.Log.Error("Critical error: " + e); } StardewModdingAPI.Log.Comment("The API will now terminate. Press any key to continue..."); Console.ReadKey(); } /// /// Configures Mono.Cecil injections /// private static void ConfigureMethodInjection() { StardewAssembly.ModifyStardewAssembly(); #if DEBUG StardewAssembly.WriteModifiedExe(); #endif } public static void Test(object instance) { gameInst = instance; IsGameReferenceDirty = true; } /// /// Set up the console properties /// private static void ConfigureUI() { Console.Title = Constants.ConsoleTitle; #if DEBUG Console.Title += " - DEBUG IS NOT FALSE, AUTHOUR NEEDS TO REUPLOAD THIS VERSION"; #endif } /// /// Setup the required paths and logging /// private static void ConfigurePaths() { StardewModdingAPI.Log.Info("Validating api paths..."); _modPaths = new List(); _modContentPaths = new List(); //TODO: Have an app.config and put the paths inside it so users can define locations to load mods from _modPaths.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "Mods")); _modPaths.Add(Path.Combine(Constants.ExecutionPath, "Mods")); _modContentPaths.Add(Path.Combine(Constants.ExecutionPath, "Mods", "Content")); _modContentPaths.Add(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "Mods", "Content")); //Checks that all defined modpaths exist as directories _modPaths.ForEach(path => VerifyPath(path)); _modContentPaths.ForEach(path => VerifyPath(path)); VerifyPath(Constants.LogPath); StardewModdingAPI.Log.Initialize(Constants.LogPath); if (!File.Exists(Constants.ExecutionPath + "\\Stardew Valley.exe")) { StardewModdingAPI.Log.Error("Replace this"); //throw new FileNotFoundException(string.Format("Could not found: {0}\\Stardew Valley.exe", Constants.ExecutionPath)); } } /// /// Load Stardev Valley and control features /// private static void ConfigureSDV() { StardewModdingAPI.Log.Info("Initializing SDV Assembly..."); // Load in the assembly - ignores security StardewAssembly.LoadStardewAssembly(); StardewModdingAPI.Log.Comment("SDV Loaded Into Memory"); // Change the game's version StardewModdingAPI.Log.Verbose("Injecting New SDV Version..."); Game1.version += string.Format("-Z_MODDED | SMAPI {0}", Constants.VersionString); // Create the thread for the game to run in. Application.ThreadException += StardewModdingAPI.Log.Application_ThreadException; Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); AppDomain.CurrentDomain.UnhandledException += StardewModdingAPI.Log.CurrentDomain_UnhandledException; //Create definition to listen for input StardewModdingAPI.Log.Verbose("Initializing Console Input Thread..."); consoleInputThread = new Thread(ConsoleInputThread); Command.RegisterCommand("help", "Lists all commands | 'help ' returns command description").CommandFired += help_CommandFired; StardewAssembly.Launch(); } /// /// Wrap the 'RunGame' method for console output /// private static void GameRunInvoker() { //Game's in memory now, send the event StardewModdingAPI.Log.Verbose("Game Loaded"); Events.GameEvents.InvokeGameLoaded(); StardewModdingAPI.Log.Comment("Type 'help' for help, or 'help ' for a command's usage"); //Begin listening to input consoleInputThread.Start(); while (ready) { //Check if the game is still running 10 times a second Thread.Sleep(1000 / 10); } //abort the thread, we're closing if (consoleInputThread != null && consoleInputThread.ThreadState == ThreadState.Running) consoleInputThread.Abort(); StardewModdingAPI.Log.Verbose("Game Execution Finished"); StardewModdingAPI.Log.Verbose("Shutting Down..."); Thread.Sleep(100); Environment.Exit(0); } /// /// Create the given directory path if it does not exist /// /// Desired directory path private static void VerifyPath(string path) { try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } catch (Exception ex) { StardewModdingAPI.Log.Error("Could not create a path: " + path + "\n\n" + ex); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void StardewForm_Closing(object sender, CancelEventArgs e) { e.Cancel = true; if (true || MessageBox.Show("Are you sure you would like to quit Stardew Valley?\nUnsaved progress will be lost!", "Confirm Exit", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.Yes) { gamePtr.Exit(); gamePtr.Dispose(); StardewForm.Hide(); ready = false; } } public static void LoadMods() { StardewModdingAPI.Log.Verbose("LOADING MODS"); int loadedMods = 0; foreach (string ModPath in _modPaths) { foreach (String s in Directory.GetFiles(ModPath, "*.dll")) { if (s.Contains("StardewInjector")) continue; StardewModdingAPI.Log.Success("Found DLL: " + s); try { Assembly mod = Assembly.UnsafeLoadFrom(s); //to combat internet-downloaded DLLs if (mod.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) > 0) { StardewModdingAPI.Log.Verbose("Loading Mod DLL..."); TypeInfo tar = mod.DefinedTypes.First(x => x.BaseType == typeof(Mod)); Mod m = (Mod)mod.CreateInstance(tar.ToString()); Console.WriteLine("LOADED MOD: {0} by {1} - Version {2} | Description: {3}", m.Name, m.Authour, m.Version, m.Description); loadedMods += 1; m.Entry(); } else { StardewModdingAPI.Log.Error("Invalid Mod DLL"); } } catch (Exception ex) { StardewModdingAPI.Log.Error("Failed to load mod '{0}'. Exception details:\n" + ex, s); } } } StardewModdingAPI.Log.Success("LOADED {0} MODS", loadedMods); } public static void ConsoleInputThread() { string input = string.Empty; while (true) { Command.CallCommand(Console.ReadLine()); } } public static void StardewInvoke(Action a) { StardewForm.Invoke(a); } static void help_CommandFired(object o, EventArgsCommand e) { if (e.Command.CalledArgs.Length > 0) { Command fnd = Command.FindCommand(e.Command.CalledArgs[0]); if (fnd == null) StardewModdingAPI.Log.Error("The command specified could not be found"); else { if (fnd.CommandArgs.Length > 0) StardewModdingAPI.Log.Info("{0}: {1} - {2}", fnd.CommandName, fnd.CommandDesc, fnd.CommandArgs.ToSingular()); else StardewModdingAPI.Log.Info("{0}: {1}", fnd.CommandName, fnd.CommandDesc); } } else StardewModdingAPI.Log.Info("Commands: " + Command.RegisteredCommands.Select(x => x.CommandName).ToSingular()); } #region Logging [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void Log(object o, params object[] format) { StardewModdingAPI.Log.Info(o, format); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogColour(ConsoleColor c, object o, params object[] format) { StardewModdingAPI.Log.Info(o, format); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogInfo(object o, params object[] format) { StardewModdingAPI.Log.Info(o, format); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogError(object o, params object[] format) { StardewModdingAPI.Log.Error(o, format); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogDebug(object o, params object[] format) { StardewModdingAPI.Log.Debug(o, format); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogValueNotSpecified() { StardewModdingAPI.Log.Error(" must be specified"); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogObjectValueNotSpecified() { StardewModdingAPI.Log.Error(" and must be specified"); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogValueInvalid() { StardewModdingAPI.Log.Error(" is invalid"); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogObjectInvalid() { StardewModdingAPI.Log.Error(" is invalid"); } [Obsolete("This method is obsolete and will be removed in v0.39, please use the appropriate methods in the Log class")] public static void LogValueNotInt32() { StardewModdingAPI.Log.Error(" must be a whole number (Int32)"); } #endregion } }