Compare commits

...

9 Commits

16 changed files with 706 additions and 264 deletions

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.API
{
class Game
{
}
}

View File

@ -20,6 +20,11 @@ namespace StardewModdingAPI
/// </summary>
public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
/// <summary>
/// Execution path to execute the code.
/// </summary>
public static string StardewExePath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Stardew Valley.exe";
/// <summary>
/// Title for the API console
/// </summary>

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.Events
{
public class FarmAnimal
{
public void eatGrass_OnEnter()
{
}
}
}

View File

@ -46,6 +46,8 @@ namespace StardewModdingAPI.Events
{
try
{
Program.IsGameReferenceDirty = true;
var test = Program.gamePtr;
UpdateTick.Invoke(null, EventArgs.Empty);
}
catch (Exception ex)

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.ExtensionMethods
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
//public static void ForEach(Array array, Action<Array, int[]> action)
//{
// if (array.LongLength == 0) return;
// ArrayTraverse walker = new ArrayTraverse(array);
// do action(array, walker.Position);
// while (walker.Step());
//}
}
internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;
public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}
public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace StardewModdingAPI.ExtensionMethods
{
//From https://github.com/jpmikkers/net-object-deep-copy/blob/master/ObjectExtensions.cs
public static class ObjectExtensions
{
public static T Copy<T>(this object original)
{
return (T)new DeepCopyContext().InternalCopy(original, true);
}
private class DeepCopyContext
{
private static readonly Func<object, object> CloneMethod;
private readonly Dictionary<Object, Object> m_Visited;
private readonly Dictionary<Type, FieldInfo[]> m_NonShallowFieldCache;
static DeepCopyContext()
{
MethodInfo cloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
var p1 = Expression.Parameter(typeof(object));
var body = Expression.Call(p1, cloneMethod);
CloneMethod = Expression.Lambda<Func<object, object>>(body, p1).Compile();
//Console.WriteLine("typeof(object) contains {0} nonshallow fields", NonShallowFields(typeof(object)).Count());
}
public DeepCopyContext()
{
m_Visited = new Dictionary<object, object>(new ReferenceEqualityComparer());
m_NonShallowFieldCache = new Dictionary<Type, FieldInfo[]>();
}
private static bool IsPrimitive(Type type)
{
if (type.IsValueType && type.IsPrimitive) return true;
if (type == typeof(String)) return true;
if (type == typeof(Decimal)) return true;
if (type == typeof(DateTime)) return true;
return false;
}
public Object InternalCopy(Object originalObject, bool includeInObjectGraph)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (typeof(XElement).IsAssignableFrom(typeToReflect)) return new XElement(originalObject as XElement);
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
if (includeInObjectGraph)
{
object result;
if (m_Visited.TryGetValue(originalObject, out result)) return result;
}
var cloneObject = CloneMethod(originalObject);
if (includeInObjectGraph)
{
m_Visited.Add(originalObject, cloneObject);
}
if (typeToReflect.IsArray)
{
var arrayElementType = typeToReflect.GetElementType();
if (IsPrimitive(arrayElementType))
{
// for an array of primitives, do nothing. The shallow clone is enough.
}
else if (arrayElementType.IsValueType)
{
// if its an array of structs, there's no need to check and add the individual elements to 'visited', because in .NET it's impossible to create
// references to individual array elements.
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), false), indices));
}
else
{
// it's an array of ref types
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), true), indices));
}
}
else
{
foreach (var fieldInfo in CachedNonShallowFields(typeToReflect))
{
var originalFieldValue = fieldInfo.GetValue(originalObject);
// a valuetype field can never have a reference pointing to it, so don't check the object graph in that case
Log.Error("Replace this with a recurse-less version");
var clonedFieldValue = InternalCopy(originalFieldValue, !fieldInfo.FieldType.IsValueType);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
return cloneObject;
}
private FieldInfo[] CachedNonShallowFields(Type typeToReflect)
{
FieldInfo[] result;
if (!m_NonShallowFieldCache.TryGetValue(typeToReflect, out result))
{
result = NonShallowFields(typeToReflect).ToArray();
m_NonShallowFieldCache[typeToReflect] = result;
}
return result;
}
/// <summary>
/// From the given type hierarchy (i.e. including all base types), return all fields that should be deep-copied
/// </summary>
/// <param name="typeToReflect"></param>
/// <returns></returns>
private static IEnumerable<FieldInfo> NonShallowFields(Type typeToReflect)
{
while (typeToReflect != typeof(object))
{
foreach (var fieldInfo in typeToReflect.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
if (IsPrimitive(fieldInfo.FieldType)) continue; // this is 5% faster than a where clause..
yield return fieldInfo;
}
typeToReflect = typeToReflect.BaseType;
}
}
}
}
public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
// The RuntimeHelpers.GetHashCode method always calls the Object.GetHashCode method non-virtually,
// even if the object's type has overridden the Object.GetHashCode method.
return RuntimeHelpers.GetHashCode(obj);
}
}
}

View File

@ -0,0 +1,163 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.Helpers
{
public enum CecilContextType
{
SMAPI,
Stardew
}
public class CecilContext
{
public CecilContextType ContextType { get; private set;}
private AssemblyDefinition _assemblyDefinition { get; set; }
private bool _isMemoryStreamDirty { get; set; }
private MemoryStream _modifiedAssembly;
public MemoryStream ModifiedAssembly
{
get
{
if(_modifiedAssembly == null)
{
_modifiedAssembly = new MemoryStream();
_assemblyDefinition.Write(_modifiedAssembly);
}
else
{
if(_isMemoryStreamDirty)
{
_modifiedAssembly.Dispose();
_modifiedAssembly = new MemoryStream();
_assemblyDefinition.Write(_modifiedAssembly);
}
}
return _modifiedAssembly;
}
}
public CecilContext(CecilContextType contextType)
{
ContextType = contextType;
if (ContextType == CecilContextType.SMAPI)
_assemblyDefinition = AssemblyDefinition.ReadAssembly(Assembly.GetExecutingAssembly().Location);
else
_assemblyDefinition = AssemblyDefinition.ReadAssembly(Constants.StardewExePath);
}
public ILProcessor GetMethodILProcessor(string type, string method)
{
if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse");
if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(method))
throw new ArgumentNullException("Both type and method must be set");
Mono.Cecil.Cil.ILProcessor ilProcessor = null;
TypeDefinition typeDef = GetTypeDefinition(type);
if (typeDef != null)
{
MethodDefinition methodDef = typeDef.Methods.FirstOrDefault(m => m.Name == method);
if (methodDef != null)
{
ilProcessor = methodDef.Body.GetILProcessor();
}
}
return ilProcessor;
}
public TypeDefinition GetTypeDefinition(string type)
{
if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse");
if (string.IsNullOrWhiteSpace(type))
throw new ArgumentNullException("Both type and method must be set");
TypeDefinition typeDef = _assemblyDefinition.MainModule.Types.FirstOrDefault(n => n.FullName == type);
return typeDef;
}
public MethodDefinition GetMethodDefinition(string type, string method)
{
MethodDefinition methodDef = null;
TypeDefinition typeDef = GetTypeDefinition(type);
if (typeDef != null)
{
methodDef = typeDef.Methods.FirstOrDefault(m => m.Name == method);
}
return methodDef;
}
public ConstructorInfo GetSMAPITypeContructor(string type)
{
if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse");
if (ContextType != CecilContextType.SMAPI)
throw new Exception("GetSMAPIMethodReference can only be called on the SMAPI context");
ConstructorInfo methodInfo = null;
var reflectionType = Assembly.GetExecutingAssembly().GetType(type);
if (reflectionType != null)
{
methodInfo = reflectionType.GetConstructor(Type.EmptyTypes);
}
return methodInfo;
}
public MethodInfo GetSMAPIMethodReference(string type, string method)
{
if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse");
if (ContextType != CecilContextType.SMAPI)
throw new Exception("GetSMAPIMethodReference can only be called on the SMAPI context");
MethodInfo methodInfo = null;
var reflectionType = Assembly.GetExecutingAssembly().GetType(type);
if (reflectionType != null)
{
methodInfo = reflectionType.GetMethod(method);
}
return methodInfo;
}
public MethodReference ImportSMAPIMethodInStardew(CecilContext destinationContext, MethodBase method)
{
if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse");
if (ContextType != CecilContextType.SMAPI)
throw new Exception("ImportSmapiMethodInStardew can only be called on the Stardew context");
MethodReference reference = null;
if (method != null)
{
reference = destinationContext._assemblyDefinition.MainModule.Import(method);
}
return reference;
}
internal void WriteAssembly(string file)
{
_assemblyDefinition.Write(file);
}
}
}

View File

@ -0,0 +1,97 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.Helpers
{
public static class CecilHelper
{
//System.Void StardewValley.Game1::.ctor()
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
{
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method);
if(method.HasThis)
{
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
ilProcessor.InsertBefore(target, loadObjInstruction);
}
if (method.HasParameters)
{
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
ilProcessor.InsertBefore(target, loadObjInstruction);
ilProcessor.InsertAfter(loadObjInstruction, callEnterInstruction);
}
else
{
ilProcessor.InsertBefore(target, callEnterInstruction);
}
}
private static void InjectMethod(ILProcessor ilProcessor, IEnumerable<Instruction> targets, MethodReference method)
{
foreach(var target in targets.ToList())
{
InjectMethod(ilProcessor, target, method);
}
}
private static List<Instruction> GetMatchingInstructions(Collection<Instruction> instructions, OpCode opcode, object @object)
{
return instructions.Where(n => n.OpCode == opcode && n.Operand == @object).ToList();
}
public static void RedirectConstructor(CecilContext stardewContext, CecilContext smapiContext,
string typeToAlter, string methodToAlter,
string injecteeType, string injecteeMethod,
string injectedType, string injectedMethod)
{
var ilProcessor = stardewContext.GetMethodILProcessor(typeToAlter, methodToAlter);
var methodDefinition = stardewContext.GetMethodDefinition(injecteeType, injecteeMethod);
var methodInfo = smapiContext.GetSMAPITypeContructor(injectedType);
var reference = smapiContext.ImportSMAPIMethodInStardew(stardewContext, methodInfo);
var instructionsToAlter = GetMatchingInstructions(ilProcessor.Body.Instructions, OpCodes.Newobj, methodDefinition);
var newInstruction = ilProcessor.Create(OpCodes.Newobj, reference);
foreach(var instruction in instructionsToAlter)
{
ilProcessor.Replace(instruction, newInstruction);
}
}
// public void ReplaceInstruction(ILProcessor processor, OpCode opcode, string oldOperand, string newOperand)
//{
//var instructions = processor.Body.Instructions.Where(i => i.OpCode == opcode && i.Operand == oldOperand);
// processor.Create()
//}
public static void InjectEntryMethod(CecilContext stardewContext, CecilContext smapiContext, string injecteeType, string injecteeMethod,
string injectedType, string injectedMethod)
{
var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod);
var reference = smapiContext.ImportSMAPIMethodInStardew(stardewContext, methodInfo);
var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod);
InjectMethod(ilProcessor, ilProcessor.Body.Instructions.First(), reference);
}
public static void InjectExitMethod(CecilContext stardewContext, CecilContext smapiContext, string injecteeType, string injecteeMethod,
string injectedType, string injectedMethod)
{
var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod);
var reference = smapiContext.ImportSMAPIMethodInStardew(stardewContext, methodInfo);
var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod);
InjectMethod(ilProcessor, ilProcessor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret), reference);
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.Helpers
{
public static class ReflectionHelper
{
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace StardewModdingAPI.Helpers
{
public static class StardewAssembly
{
private static Assembly ModifiedGameAssembly { get; set; }
private static CecilContext StardewContext { get; set; }
private static CecilContext SmapiContext { get; set; }
public static void ModifyStardewAssembly()
{
StardewContext = new CecilContext(CecilContextType.Stardew);
SmapiContext = new CecilContext(CecilContextType.SMAPI);
CecilHelper.InjectEntryMethod(StardewContext, SmapiContext, "StardewValley.Game1", ".ctor", "StardewModdingAPI.Program", "Test");
CecilHelper.InjectExitMethod(StardewContext, SmapiContext, "StardewValley.Game1", "Initialize", "StardewModdingAPI.Events.GameEvents", "InvokeInitialize");
CecilHelper.InjectExitMethod(StardewContext, SmapiContext, "StardewValley.Game1", "LoadContent", "StardewModdingAPI.Events.GameEvents", "InvokeLoadContent");
CecilHelper.InjectExitMethod(StardewContext, SmapiContext, "StardewValley.Game1", "Update", "StardewModdingAPI.Events.GameEvents", "InvokeUpdateTick");
CecilHelper.InjectExitMethod(StardewContext, SmapiContext, "StardewValley.Game1", "Draw", "StardewModdingAPI.Events.GraphicsEvents", "InvokeDrawTick");
}
public static void LoadStardewAssembly()
{
ModifiedGameAssembly = Assembly.Load(StardewContext.ModifiedAssembly.GetBuffer());
//ModifiedGameAssembly = Assembly.UnsafeLoadFrom(Constants.ExecutionPath + "\\Stardew Valley.exe");
}
internal static void Launch()
{
ModifiedGameAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } });
}
internal static void WriteModifiedExe()
{
StardewContext.WriteAssembly("StardewValley-Modified.exe");
}
}
}

View File

@ -181,40 +181,7 @@ namespace StardewModdingAPI.Inheritance
public SGame()
{
instance = this;
#if DEBUG
SaveGame.serializer = new XmlSerializer(typeof (SaveGame), new Type[28]
{
typeof (Tool),
typeof (GameLocation),
typeof (Crow),
typeof (Duggy),
typeof (Bug),
typeof (BigSlime),
typeof (Fireball),
typeof (Ghost),
typeof (Child),
typeof (Pet),
typeof (Dog),
typeof (StardewValley.Characters.Cat),
typeof (Horse),
typeof (GreenSlime),
typeof (LavaCrab),
typeof (RockCrab),
typeof (ShadowGuy),
typeof (SkeletonMage),
typeof (SquidKid),
typeof (Grub),
typeof (Fly),
typeof (DustSpirit),
typeof (Quest),
typeof (MetalHead),
typeof (ShadowGirl),
typeof (Monster),
typeof (TerrainFeature),
typeof (SObject)
});
#endif
graphics.GraphicsProfile = GraphicsProfile.HiDef;
}
protected override void Initialize()

View File

@ -78,6 +78,16 @@ namespace StardewModdingAPI
/// <param name="message"></param>
/// <param name="values"></param>
public static void Comment(object message, params object[] values)
{
Log.PrintLog(message?.ToString(), false, values);
}
/// <summary>
/// Additional comment to display to console and logging.
/// </summary>
/// <param name="message"></param>
/// <param name="values"></param>
public static void Warning(object message, params object[] values)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Log.PrintLog(message?.ToString(), false, values);

View File

@ -1,6 +1,10 @@
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;
@ -23,19 +27,31 @@ namespace StardewModdingAPI
public static Texture2D DebugPixel { get; private set; }
public static SGame gamePtr;
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 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; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -49,9 +65,8 @@ namespace StardewModdingAPI
{
ConfigureUI();
ConfigurePaths();
ConfigureInjector();
ConfigureMethodInjection();
ConfigureSDV();
GameRunInvoker();
}
catch (Exception e)
@ -64,6 +79,25 @@ namespace StardewModdingAPI
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>
@ -86,6 +120,7 @@ namespace StardewModdingAPI
_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"));
@ -101,50 +136,8 @@ namespace StardewModdingAPI
if (!File.Exists(Constants.ExecutionPath + "\\Stardew Valley.exe"))
{
throw new FileNotFoundException(string.Format("Could not found: {0}\\Stardew Valley.exe", Constants.ExecutionPath));
}
}
/// <summary>
/// Load the injector.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
private static void ConfigureInjector()
{
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.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);
}
}
StardewModdingAPI.Log.Error("Replace this");
//throw new FileNotFoundException(string.Format("Could not found: {0}\\Stardew Valley.exe", Constants.ExecutionPath));
}
}
@ -156,45 +149,26 @@ namespace StardewModdingAPI
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");
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.
gameThread = new Thread(RunGame);
StardewModdingAPI.Log.Info("Starting SDV...");
gameThread.Start();
Application.ThreadException += StardewModdingAPI.Log.Application_ThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += StardewModdingAPI.Log.CurrentDomain_UnhandledException;
// Wait for the game to load up
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;
Events.GameEvents.LoadContent += Events_LoadContent;
//Events.MenuChanged += Events_MenuChanged; //Idk right now
StardewModdingAPI.Log.Verbose("Applying Final SDV Tweaks...");
StardewInvoke(() =>
{
gamePtr.IsMouseVisible = false;
gamePtr.Window.Title = "Stardew Valley - Version " + Game1.version;
StardewForm.Resize += Events.GraphicsEvents.InvokeResize;
});
StardewAssembly.Launch();
}
/// <summary>
@ -248,48 +222,6 @@ namespace StardewModdingAPI
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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)
{
@ -352,72 +284,6 @@ namespace StardewModdingAPI
}
}
static void Events_LoadContent(object o, EventArgs e)
{
StardewModdingAPI.Log.Info("Initializing Debug Assets...");
DebugPixel = new Texture2D(Game1.graphics.GraphicsDevice, 1, 1);
DebugPixel.SetData(new Color[] { Color.White });
#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)
{
StardewModdingAPI.Log.Verbose("NEW MENU: " + newMenu.GetType());
if (newMenu is GameMenu)
{
Game1.activeClickableMenu = SGameMenu.ConstructFromBaseClass(Game1.activeClickableMenu as GameMenu);
}
}
static void Events_LocationsChanged(List<GameLocation> newLocations)
{
#if DEBUG
SGame.ModLocations = SGameLocation.ConstructFromBaseClasses(Game1.locations);
#endif
}
static void Events_CurrentLocationChanged(GameLocation newLocation)
{
//SGame.CurrentLocation = null;
//System.Threading.Thread.Sleep(10);
#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);

View File

@ -37,16 +37,16 @@
<TargetFrameworkProfile />
</PropertyGroup>
<Choose>
<When Condition="'$(SteamInstallPath)' != ''">
<PropertyGroup>
<SteamPath>$(SteamInstallPath)</SteamPath>
<When Condition="'$(SteamInstallPath)' != ''">
<PropertyGroup>
<SteamPath>$(SteamInstallPath)</SteamPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<SteamPath>..\..\..\..\Games\SteamLibrary</SteamPath>
</PropertyGroup>
</Otherwise>
</When>
<Otherwise>
<PropertyGroup>
<SteamPath>..\..\..\..\Games\SteamLibrary</SteamPath>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
@ -73,8 +73,7 @@
<PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Debug\</OutputPath>
<Prefer32Bit>false</Prefer32Bit>
<DefineConstants>
</DefineConstants>
<DefineConstants>DEBUG</DefineConstants>
<UseVSHostingProcess>true</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
@ -90,9 +89,25 @@
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Stardew Valley, Version=1.0.5905.5747, Culture=neutral, processorArchitecture=x86">
<Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Mdb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Pdb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Stardew Valley, Version=1.0.5912.41135, Culture=neutral, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(SteamPath)\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -103,12 +118,12 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(SteamPath)\steamapps\common\Stardew Valley\xTile.dll</HintPath>
<Reference Include="xTile">
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="API\Game.cs" />
<Compile Include="Command.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Entities\SCharacter.cs" />
@ -118,6 +133,7 @@
<Compile Include="Entities\SPlayer.cs" />
<Compile Include="Events\Controls.cs" />
<Compile Include="Events\EventArgs.cs" />
<Compile Include="Events\FarmAnimal.cs" />
<Compile Include="Events\Game.cs" />
<Compile Include="Events\Graphics.cs" />
<Compile Include="Events\Location.cs" />
@ -125,7 +141,13 @@
<Compile Include="Events\Mine.cs" />
<Compile Include="Events\Player.cs" />
<Compile Include="Events\Time.cs" />
<Compile Include="ExtensionMethods\Array.cs" />
<Compile Include="ExtensionMethods\Object.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="Helpers\CecilContext.cs" />
<Compile Include="Helpers\CecilHelper.cs" />
<Compile Include="Helpers\ReflectionHelper.cs" />
<Compile Include="Helpers\StardewAssembly.cs" />
<Compile Include="Inheritance\ItemStackChange.cs" />
<Compile Include="Inheritance\Menus\SBobberBar.cs" />
<Compile Include="Inheritance\Menus\SGameMenu.cs" />
@ -142,6 +164,7 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="icon.ico" />
@ -159,6 +182,7 @@
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" />
</packages>

View File

@ -34,16 +34,16 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<Choose>
<When Condition="'$(SteamInstallPath)' != ''">
<PropertyGroup>
<SteamPath>$(SteamInstallPath)</SteamPath>
<When Condition="'$(SteamInstallPath)' != ''">
<PropertyGroup>
<SteamPath>$(SteamInstallPath)</SteamPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<SteamPath>..\..\..\..\Games\SteamLibrary</SteamPath>
</PropertyGroup>
</Otherwise>
</When>
<Otherwise>
<PropertyGroup>
<SteamPath>..\..\..\..\Games\SteamLibrary</SteamPath>
</PropertyGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
@ -53,8 +53,7 @@
<Private>False</Private>
</Reference>
<Reference Include="Stardew Valley">
<HintPath>$(SteamPath)\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
<Private>False</Private>
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -65,8 +64,7 @@
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xTile">
<HintPath>$(SteamPath)\steamapps\common\Stardew Valley\xTile.dll</HintPath>
<Private>False</Private>
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>