373 lines
14 KiB
C#
373 lines
14 KiB
C#
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<string> _modPaths;
|
|
private static List<string> _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<Game1>();
|
|
}
|
|
return _gamePtr;
|
|
}
|
|
}
|
|
|
|
public static bool ready;
|
|
|
|
public static Form StardewForm;
|
|
|
|
public static Thread gameThread;
|
|
public static Thread consoleInputThread;
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// <summary>
|
|
/// Main method holding the API execution
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures Mono.Cecil injections
|
|
/// </summary>
|
|
private static void ConfigureMethodInjection()
|
|
{
|
|
StardewAssembly.ModifyStardewAssembly();
|
|
|
|
#if DEBUG
|
|
StardewAssembly.WriteModifiedExe();
|
|
#endif
|
|
}
|
|
|
|
|
|
public static void Test(object instance)
|
|
{
|
|
gameInst = instance;
|
|
IsGameReferenceDirty = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set up the console properties
|
|
/// </summary>
|
|
private static void ConfigureUI()
|
|
{
|
|
Console.Title = Constants.ConsoleTitle;
|
|
|
|
#if DEBUG
|
|
Console.Title += " - DEBUG IS NOT FALSE, AUTHOUR NEEDS TO REUPLOAD THIS VERSION";
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup the required paths and logging
|
|
/// </summary>
|
|
private static void ConfigurePaths()
|
|
{
|
|
StardewModdingAPI.Log.Info("Validating api paths...");
|
|
|
|
_modPaths = new List<string>();
|
|
_modContentPaths = new List<string>();
|
|
|
|
|
|
//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));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load Stardev Valley and control features
|
|
/// </summary>
|
|
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 <cmd>' returns command description").CommandFired += help_CommandFired;
|
|
|
|
StardewAssembly.Launch();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrap the 'RunGame' method for console output
|
|
/// </summary>
|
|
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 <cmd>' 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the given directory path if it does not exist
|
|
/// </summary>
|
|
/// <param name="path">Desired directory path</param>
|
|
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("<value> 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("<object> and <value> 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("<value> 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("<object> 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("<value> must be a whole number (Int32)");
|
|
}
|
|
#endregion
|
|
}
|
|
} |