Hooked up a few callbacks. Need to work on backwards compatability next

This commit is contained in:
ClxS 2016-03-11 20:52:10 +00:00
parent e1bfd54894
commit 1ec0570ed9
8 changed files with 194 additions and 99 deletions

View File

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

View File

@ -154,5 +154,10 @@ namespace StardewModdingAPI.Helpers
}
return reference;
}
internal void WriteAssembly(string file)
{
_assemblyDefinition.Write(file);
}
}
}

View File

@ -16,15 +16,18 @@ namespace StardewModdingAPI.Helpers
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
{
var callTarget = target;
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method);
if (method.HasParameters)
{
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
ilProcessor.InsertBefore(target, loadObjInstruction);
callTarget = loadObjInstruction;
ilProcessor.InsertAfter(loadObjInstruction, callEnterInstruction);
}
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method);
ilProcessor.InsertAfter(callTarget, callEnterInstruction);
else
{
ilProcessor.InsertBefore(target, callEnterInstruction);
}
}
private static void InjectMethod(ILProcessor ilProcessor, IEnumerable<Instruction> targets, MethodReference method)

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -8,6 +9,140 @@ namespace StardewModdingAPI.Helpers
{
public static class ReflectionHelper
{
private class FieldCompatability
{
public List<Tuple<FieldInfo, FieldInfo>> MatchingFields = new List<Tuple<FieldInfo, FieldInfo>>();
public List<Tuple<FieldInfo, FieldInfo>> FixableMismatchingFields = new List<Tuple<FieldInfo, FieldInfo>>();
public List<Tuple<FieldInfo, FieldInfo>> UnfixableMismatchingFields = new List<Tuple<FieldInfo, FieldInfo>>();
public List<FieldInfo> FieldsMissingFromA = new List<FieldInfo>();
public List<FieldInfo> FieldsMissingFromB = new List<FieldInfo>();
}
private static FieldCompatability AssessFieldCompatbility(FieldInfo[] a, FieldInfo[] b)
{
FieldCompatability results = new FieldCompatability();
List<FieldInfo> editableB = new List<FieldInfo>(b);
foreach (var leftField in a)
{
FieldInfo matchingField = null;
foreach(var rightField in editableB)
{
if(leftField.Name == rightField.Name)
{
matchingField = rightField;
if (leftField.FieldType == rightField.FieldType)
{
results.MatchingFields.Add(Tuple.Create(leftField, rightField));
}
else if(leftField.FieldType.FullName == rightField.FieldType.FullName)
{
results.FixableMismatchingFields.Add(Tuple.Create(leftField, rightField));
}
else
{
results.UnfixableMismatchingFields.Add(Tuple.Create(leftField, rightField));
}
break;
}
}
if (matchingField != null)
{
editableB.Remove(matchingField);
}
else
{
results.FieldsMissingFromB.Add(leftField);
}
}
results.FieldsMissingFromA.AddRange(editableB);
return results;
}
private static void WarnMismatch(FieldCompatability compatibility, string source)
{
if (compatibility.UnfixableMismatchingFields.Any())
{
Log.Warning("Unfixable type mismatch in {0}. Mods which depend on these types may fail to work properly", source);
foreach (var mismatch in compatibility.UnfixableMismatchingFields)
{
Log.Warning("- {0} is not of type {1}, is of type {2}", mismatch.Item1.Name, mismatch.Item1.FieldType.FullName, mismatch.Item2.FieldType.FullName);
}
}
}
private static void WarnMissing(FieldCompatability compatibility, string source)
{
if (compatibility.FieldsMissingFromA.Any() || compatibility.FieldsMissingFromB.Any())
{
Log.Warning("The following fields are not present in both objects", source);
foreach (var mismatch in compatibility.FieldsMissingFromA)
Log.Warning("- Left Object Missing {0} ({1})", mismatch.Name, mismatch.FieldType.FullName);
foreach (var mismatch in compatibility.FieldsMissingFromA)
Log.Warning("- Right Object Missing {0} ({1})", mismatch.Name, mismatch.FieldType.FullName);
}
}
private static void RecursiveMemberwiseCast(Type toType, Type fromType, object to, object from)
{
Log.Verbose("Writing {0}", toType.Name);
Stack<Tuple<Type, Type, object, object>> pendingCasts = new Stack<Tuple<Type, Type, object, object>>();
List<object> alreadyProcessed = new List<object>();
pendingCasts.Push(Tuple.Create(toType, fromType,
to, from));
while (pendingCasts.Count > 0)
{
Tuple<Type, Type, object, object> current = pendingCasts.Pop();
Type castTo = current.Item1;
Type castFrom = current.Item2;
object objectToSet = current.Item3;
object baseObject = current.Item4;
var targetFields = castTo
.GetFields().Where(n => !n.IsInitOnly && !n.IsLiteral).ToArray();
var baseFields = castFrom
.GetFields().Where(n => !n.IsInitOnly && !n.IsLiteral).ToArray();
var compatibility = AssessFieldCompatbility(targetFields, baseFields);
WarnMissing(compatibility, from.GetType().FullName);
WarnMismatch(compatibility, from.GetType().FullName);
foreach (var match in compatibility.MatchingFields)
{
var toValue = match.Item1.GetValue(objectToSet);
var fromValue = match.Item2.GetValue(baseObject);
match.Item2.SetValue(objectToSet, fromValue);
}
foreach (var fixableMismatch in compatibility.FixableMismatchingFields)
{
var toValue = fixableMismatch.Item1.GetValue(objectToSet);
var fromValue = fixableMismatch.Item2.GetValue(baseObject);
if (fromValue != null && !alreadyProcessed.Any(n => Object.ReferenceEquals(n, fromValue)))
{
alreadyProcessed.Add(fromValue);
//pendingCasts.Push(Tuple.Create(fixableMismatch.Item1.FieldType, fixableMismatch.Item2.FieldType,
// toValue, fromValue));
}
}
}
}
public static T MemberwiseCast<T>(this object @base) where T : new()
{
T retObj = new T();
RecursiveMemberwiseCast(@base.GetType(), retObj.GetType().BaseType, retObj, @base);
return retObj;
}
}
}

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

@ -26,7 +26,15 @@ namespace StardewModdingAPI
public static Texture2D DebugPixel { get; private set; }
public static SGame gamePtr;
private static object modifiedAssemblyGame;
//public static Game1 gamePtr;
public static Game1 gamePtr
{
get
{
return modifiedAssemblyGame.MemberwiseCast<Game1>();
}
}
public static bool ready;
public static Assembly StardewAssembly;
@ -78,13 +86,18 @@ namespace StardewModdingAPI
//CecilHelper.RedirectConstructor(StardewContext, SmapiContext, "StardewValley.Program", "Main",
// "StardewValley.Game1", ".ctor", "StardewModdingAPI.Inheritance.SGame", ".ctor");
CecilHelper.InjectExitMethod(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");
//TODO - Invoke Resize
}
public static void Test(Game1 instance)
public static void Test(object instance)
{
var inst = instance;
string test = Game1.samBandName;
var test2 = Game1.numberOfSelectedItems;
modifiedAssemblyGame = instance;
}
/// <summary>
@ -137,7 +150,6 @@ namespace StardewModdingAPI
StardewModdingAPI.Log.Info("Initializing SDV Assembly...");
// Load in the assembly - ignores security
//StardewAssembly = Assembly.UnsafeLoadFrom(Constants.ExecutionPath + "\\Stardew Valley.exe");
StardewAssembly = Assembly.Load(StardewContext.ModifiedAssembly.GetBuffer());
StardewProgramType = StardewAssembly.GetType("StardewValley.Program", true);
StardewGameInfo = StardewProgramType.GetField("gamePtr");
@ -165,11 +177,6 @@ namespace StardewModdingAPI
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...");
StardewAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } });
@ -242,19 +249,7 @@ namespace StardewModdingAPI
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;
//Game1 g1 = gamePtr as Game1;
//StardewGameInfo.SetValue(StardewProgramType, g1);
//gamePtr.Run();
}
catch (Exception ex)
{
@ -323,72 +318,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

@ -125,6 +125,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="API\Game.cs" />
<Compile Include="Command.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Entities\SCharacter.cs" />