2016-03-21 03:43:31 +08:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2016-03-05 14:32:04 +08:00
using StardewModdingAPI.Events ;
2016-03-21 03:43:31 +08:00
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 SGame gamePtr ;
public static bool ready ;
public static Assembly StardewAssembly ;
public static Type StardewProgramType ;
public static FieldInfo StardewGameInfo ;
public static Form StardewForm ;
public static Thread gameThread ;
public static Thread consoleInputThread ;
public static bool StardewInjectorLoaded { get ; private set ; }
public static Mod StardewInjectorMod { get ; private set ; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2016-03-07 04:19:59 +08:00
/// <summary>
/// Main method holding the API execution
/// </summary>
2016-03-21 03:43:31 +08:00
/// <param name="args"></param>
private static void Main ( string [ ] args )
{
2016-03-07 04:19:59 +08:00
try
{
ConfigureUI ( ) ;
ConfigurePaths ( ) ;
ConfigureInjector ( ) ;
ConfigureSDV ( ) ;
GameRunInvoker ( ) ;
}
catch ( Exception e )
{
// Catch and display all exceptions.
2016-03-07 04:25:14 +08:00
StardewModdingAPI . Log . Error ( "Critical error: " + e ) ;
2016-03-07 04:19:59 +08:00
}
2016-03-07 04:25:14 +08:00
StardewModdingAPI . Log . Comment ( "The API will now terminate. Press any key to continue..." ) ;
2016-03-07 04:19:59 +08:00
Console . ReadKey ( ) ;
2016-03-21 03:43:31 +08:00
}
2016-03-07 04:19:59 +08:00
/// <summary>
/// Set up the console properties
2016-03-21 03:43:31 +08:00
/// </summary>
2016-03-07 04:19:59 +08:00
private static void ConfigureUI ( )
2016-03-21 03:43:31 +08:00
{
2016-03-07 05:58:40 +08:00
Console . Title = Constants . ConsoleTitle ;
2016-03-21 03:43:31 +08:00
#if DEBUG
2016-03-05 14:32:04 +08:00
Console . Title + = " - DEBUG IS NOT FALSE, AUTHOUR NEEDS TO REUPLOAD THIS VERSION" ;
#endif
2016-03-07 04:19:59 +08:00
}
/// <summary>
/// Setup the required paths and logging
/// </summary>
private static void ConfigurePaths ( )
{
2016-03-21 03:43:31 +08:00
StardewModdingAPI . Log . Info ( "Validating api paths..." ) ;
2016-03-07 04:19:59 +08:00
_modPaths = new List < string > ( ) ;
_modContentPaths = new List < string > ( ) ;
2016-03-05 14:32:04 +08:00
//TODO: Have an app.config and put the paths inside it so users can define locations to load mods from
2016-03-21 03:43:31 +08:00
_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 ) ;
2016-03-07 05:58:40 +08:00
StardewModdingAPI . Log . Initialize ( Constants . LogPath ) ;
2016-03-07 04:19:59 +08:00
2016-03-07 05:58:40 +08:00
if ( ! File . Exists ( Constants . ExecutionPath + "\\Stardew Valley.exe" ) )
2016-03-21 03:43:31 +08:00
{
2016-03-07 05:58:40 +08:00
throw new FileNotFoundException ( string . Format ( "Could not found: {0}\\Stardew Valley.exe" , Constants . ExecutionPath ) ) ;
2016-03-21 03:43:31 +08:00
}
}
2016-03-07 04:19:59 +08:00
/// <summary>
/// Load the injector.
2016-03-21 03:43:31 +08:00
/// </summary>
/// <remarks>
2016-03-07 04:19:59 +08:00
/// This will load the injector before anything else if it sees it
/// It doesn't matter though
/// I'll leave it as a feature in case anyone in the community wants to tinker with it
/// All you need is a DLL that inherits from mod and is called StardewInjector.dll with an Entry() method
2016-03-21 03:43:31 +08:00
/// </remarks>
2016-03-07 04:19:59 +08:00
private static void ConfigureInjector ( )
2016-03-21 03:43:31 +08:00
{
foreach ( string ModPath in _modPaths )
{
foreach ( String s in Directory . GetFiles ( ModPath , "StardewInjector.dll" ) )
{
StardewModdingAPI . Log . Success ( ConsoleColor . Green , "Found Stardew Injector 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 . Success ( "Loading Injector DLL..." ) ;
TypeInfo tar = mod . DefinedTypes . First ( x = > x . BaseType = = typeof ( Mod ) ) ;
Mod m = ( Mod ) mod . CreateInstance ( tar . ToString ( ) ) ;
Console . WriteLine ( "LOADED: {0} by {1} - Version {2} | Description: {3}" , m . Name , m . Authour , m . Version , m . Description ) ;
m . PathOnDisk = Path . GetDirectoryName ( ModPath ) ;
m . Entry ( false ) ;
StardewInjectorLoaded = true ;
StardewInjectorMod = m ;
}
else
{
StardewModdingAPI . Log . Error ( "Invalid Mod DLL" ) ;
}
}
catch ( Exception ex )
{
StardewModdingAPI . Log . Error ( "Failed to load mod '{0}'. Exception details:\n" + ex , s ) ;
}
}
}
}
2016-03-07 04:19:59 +08:00
/// <summary>
/// Load Stardev Valley and control features
2016-03-21 03:43:31 +08:00
/// </summary>
2016-03-07 04:19:59 +08:00
private static void ConfigureSDV ( )
2016-03-21 03:43:31 +08:00
{
StardewModdingAPI . Log . Info ( "Initializing SDV Assembly..." ) ;
// Load in the assembly - ignores security
StardewAssembly = Assembly . UnsafeLoadFrom ( Constants . ExecutionPath + "\\Stardew Valley.exe" ) ;
StardewProgramType = StardewAssembly . GetType ( "StardewValley.Program" , true ) ;
StardewGameInfo = StardewProgramType . GetField ( "gamePtr" ) ;
// Change the game's version
StardewModdingAPI . Log . Verbose ( "Injecting New SDV Version..." ) ;
2016-03-07 05:58:40 +08:00
Game1 . version + = string . Format ( "-Z_MODDED | SMAPI {0}" , Constants . VersionString ) ;
2016-03-21 03:43:31 +08:00
// Create the thread for the game to run in.
gameThread = new Thread ( RunGame ) ;
StardewModdingAPI . Log . Info ( "Starting SDV..." ) ;
2016-03-07 05:58:40 +08:00
gameThread . Start ( ) ;
2016-03-05 14:32:04 +08:00
2016-03-07 05:58:40 +08:00
// Wait for the game to load up
2016-03-21 03:43:31 +08:00
while ( ! ready ) ;
//SDV is running
StardewModdingAPI . Log . Comment ( "SDV Loaded Into Memory" ) ;
//Create definition to listen for input
StardewModdingAPI . Log . Verbose ( "Initializing Console Input Thread..." ) ;
consoleInputThread = new Thread ( ConsoleInputThread ) ;
// The only command in the API (at least it should be, for now)
Command . RegisterCommand ( "help" , "Lists all commands | 'help <cmd>' returns command description" ) . CommandFired + = help_CommandFired ;
//Command.RegisterCommand("crash", "crashes sdv").CommandFired += delegate { Game1.player.draw(null); };
//Subscribe to events
Events . ControlEvents . KeyPressed + = Events_KeyPressed ;
2016-03-05 14:32:04 +08:00
Events . GameEvents . LoadContent + = Events_LoadContent ;
2016-03-21 03:43:31 +08:00
//Events.MenuChanged += Events_MenuChanged; //Idk right now
StardewModdingAPI . Log . Verbose ( "Applying Final SDV Tweaks..." ) ;
StardewInvoke ( ( ) = >
2016-03-05 14:32:04 +08:00
{
gamePtr . IsMouseVisible = false ;
gamePtr . Window . Title = "Stardew Valley - Version " + Game1 . version ;
2016-03-21 03:43:31 +08:00
StardewForm . Resize + = Events . GraphicsEvents . InvokeResize ;
} ) ;
}
2016-03-07 04:19:59 +08:00
/// <summary>
/// Wrap the 'RunGame' method for console output
2016-03-21 03:43:31 +08:00
/// </summary>
2016-03-07 04:19:59 +08:00
private static void GameRunInvoker ( )
2016-03-21 03:43:31 +08:00
{
//Game's in memory now, send the event
StardewModdingAPI . Log . Verbose ( "Game Loaded" ) ;
Events . GameEvents . InvokeGameLoaded ( ) ;
2016-03-07 04:51:22 +08:00
StardewModdingAPI . Log . Comment ( "Type 'help' for help, or 'help <cmd>' for a command's usage" ) ;
2016-03-21 03:43:31 +08:00
//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 ) ;
2016-03-05 14:32:04 +08:00
}
2016-03-07 04:19:59 +08:00
/// <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 )
2016-03-21 03:43:31 +08:00
{
2016-03-07 04:25:14 +08:00
StardewModdingAPI . Log . Error ( "Could not create a path: " + path + "\n\n" + ex ) ;
2016-03-21 03:43:31 +08:00
}
2016-03-07 04:19:59 +08:00
}
2016-03-05 14:32:04 +08:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2016-03-21 03:43:31 +08:00
public static void RunGame ( )
{
Application . ThreadException + = StardewModdingAPI . Log . Application_ThreadException ;
Application . SetUnhandledExceptionMode ( UnhandledExceptionMode . CatchException ) ;
AppDomain . CurrentDomain . UnhandledException + = StardewModdingAPI . Log . CurrentDomain_UnhandledException ;
try
{
gamePtr = new SGame ( ) ;
StardewModdingAPI . Log . Verbose ( "Patching SDV Graphics Profile..." ) ;
Game1 . graphics . GraphicsProfile = GraphicsProfile . HiDef ;
LoadMods ( ) ;
StardewForm = Control . FromHandle ( Program . gamePtr . Window . Handle ) . FindForm ( ) ;
StardewForm . Closing + = StardewForm_Closing ;
ready = true ;
StardewGameInfo . SetValue ( StardewProgramType , gamePtr ) ;
gamePtr . Run ( ) ;
#region deprecated
if ( false )
{
//Nope, I can't get it to work. I depend on Game1 being an SGame, and can't cast a parent to a child
//I'm leaving this here in case the community is interested
//StardewInjectorMod.Entry(true);
Type gt = StardewAssembly . GetType ( "StardewValley.Game1" , true ) ;
gamePtr = ( SGame ) Activator . CreateInstance ( gt ) ;
ready = true ;
StardewGameInfo . SetValue ( StardewProgramType , gamePtr ) ;
gamePtr . Run ( ) ;
}
#endregion
}
catch ( Exception ex )
{
StardewModdingAPI . Log . Error ( "Game failed to start: " + 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 ( ) ) ;
}
}
static void Events_LoadContent ( object o , EventArgs e )
{
StardewModdingAPI . Log . Info ( "Initializing Debug Assets..." ) ;
DebugPixel = new Texture2D ( Game1 . graphics . GraphicsDevice , 1 , 1 ) ;
2016-03-05 14:32:04 +08:00
DebugPixel . SetData ( new Color [ ] { Color . White } ) ;
2016-03-21 03:43:31 +08:00
#if DEBUG
StardewModdingAPI . Log . Verbose ( "REGISTERING BASE CUSTOM ITEM" ) ;
SObject so = new SObject ( ) ;
so . Name = "Mario Block" ;
so . CategoryName = "SMAPI Test Mod" ;
so . Description = "It's a block from Mario!\nLoaded in realtime by SMAPI." ;
so . Texture = Texture2D . FromStream ( Game1 . graphics . GraphicsDevice , new FileStream ( _modContentPaths [ 0 ] + "\\Test.png" , FileMode . Open ) ) ;
so . IsPassable = true ;
so . IsPlaceable = true ;
StardewModdingAPI . Log . Verbose ( "REGISTERED WITH ID OF: " + SGame . RegisterModItem ( so ) ) ;
//StardewModdingAPI.Log.Verbose("REGISTERING SECOND CUSTOM ITEM");
//SObject so2 = new SObject();
//so2.Name = "Mario Painting";
//so2.CategoryName = "SMAPI Test Mod";
//so2.Description = "It's a painting of a creature from Mario!\nLoaded in realtime by SMAPI.";
//so2.Texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, new FileStream(_modContentPaths[0] + "\\PaintingTest.png", FileMode.Open));
//so2.IsPassable = true;
//so2.IsPlaceable = true;
//StardewModdingAPI.Log.Verbose("REGISTERED WITH ID OF: " + SGame.RegisterModItem(so2));
Command . CallCommand ( "load" ) ;
#endif
}
static void Events_KeyPressed ( object o , EventArgsKeyPressed e )
{
}
static void Events_MenuChanged ( IClickableMenu newMenu )
2016-03-05 14:32:04 +08:00
{
2016-03-21 03:43:31 +08:00
StardewModdingAPI . Log . Verbose ( "NEW MENU: " + newMenu . GetType ( ) ) ;
if ( newMenu is GameMenu )
{
Game1 . activeClickableMenu = SGameMenu . ConstructFromBaseClass ( Game1 . activeClickableMenu as GameMenu ) ;
}
}
2016-03-05 14:32:04 +08:00
2016-03-21 03:43:31 +08:00
static void Events_LocationsChanged ( List < GameLocation > newLocations )
2016-03-05 14:32:04 +08:00
{
2016-03-21 03:43:31 +08:00
#if DEBUG
SGame . ModLocations = SGameLocation . ConstructFromBaseClasses ( Game1 . locations ) ;
#endif
}
static void Events_CurrentLocationChanged ( GameLocation newLocation )
2016-03-05 14:32:04 +08:00
{
//SGame.CurrentLocation = null;
//System.Threading.Thread.Sleep(10);
2016-03-21 03:43:31 +08:00
#if DEBUG
Console . WriteLine ( newLocation . name ) ;
SGame . CurrentLocation = SGame . LoadOrCreateSGameLocationFromName ( newLocation . name ) ;
#endif
//Game1.currentLocation = SGame.CurrentLocation;
//Log.LogComment(((SGameLocation) newLocation).name);
//Log.LogComment("LOC CHANGED: " + SGame.currentLocation.name);
}
public static void StardewInvoke ( Action a )
{
StardewForm . Invoke ( a ) ;
2016-03-05 14:32:04 +08:00
}
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 )
2016-03-06 23:54:09 +08:00
StardewModdingAPI . Log . Error ( "The command specified could not be found" ) ;
2016-03-05 14:32:04 +08:00
else
{
if ( fnd . CommandArgs . Length > 0 )
2016-03-06 23:54:09 +08:00
StardewModdingAPI . Log . Info ( "{0}: {1} - {2}" , fnd . CommandName , fnd . CommandDesc , fnd . CommandArgs . ToSingular ( ) ) ;
2016-03-05 14:32:04 +08:00
else
2016-03-06 23:54:09 +08:00
StardewModdingAPI . Log . Info ( "{0}: {1}" , fnd . CommandName , fnd . CommandDesc ) ;
2016-03-05 14:32:04 +08:00
}
}
else
2016-03-06 23:54:09 +08:00
StardewModdingAPI . Log . Info ( "Commands: " + Command . RegisteredCommands . Select ( x = > x . CommandName ) . ToSingular ( ) ) ;
2016-03-05 14:32:04 +08:00
}
2016-03-06 23:54:09 +08:00
2016-03-21 03:43:31 +08:00
#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 ( )
2016-03-06 23:54:09 +08:00
{
2016-03-21 03:43:31 +08:00
StardewModdingAPI . Log . Error ( "<value> must be a whole number (Int32)" ) ;
2016-03-05 14:32:04 +08:00
}
2016-03-06 23:54:09 +08:00
#endregion
2016-03-21 03:43:31 +08:00
}
2016-03-04 04:06:25 +08:00
}