Further test on injecting. Injecting a callback to the constructor and capturing the instance that way seems to work.

This commit is contained in:
ClxS 2016-03-10 20:13:57 +00:00
parent 792cfcd796
commit e1bfd54894
6 changed files with 104 additions and 88 deletions

View File

@ -100,7 +100,26 @@ namespace StardewModdingAPI.Helpers
return methodDef; 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) public MethodInfo GetSMAPIMethodReference(string type, string method)
{ {
if (_assemblyDefinition == null) if (_assemblyDefinition == null)
@ -111,16 +130,16 @@ namespace StardewModdingAPI.Helpers
MethodInfo methodInfo = null; MethodInfo methodInfo = null;
var smapiAssembly = Assembly.GetExecutingAssembly().GetType(type); var reflectionType = Assembly.GetExecutingAssembly().GetType(type);
if (smapiAssembly != null) if (reflectionType != null)
{ {
methodInfo = smapiAssembly.GetMethod(method); methodInfo = reflectionType.GetMethod(method);
} }
return methodInfo; return methodInfo;
} }
public MethodReference ImportSMAPIMethodInStardew(MethodInfo method) public MethodReference ImportSMAPIMethodInStardew(CecilContext destinationContext, MethodBase method)
{ {
if (_assemblyDefinition == null) if (_assemblyDefinition == null)
throw new Exception("ERROR Assembly not properly read. Cannot parse"); throw new Exception("ERROR Assembly not properly read. Cannot parse");
@ -131,7 +150,7 @@ namespace StardewModdingAPI.Helpers
MethodReference reference = null; MethodReference reference = null;
if (method != null) if (method != null)
{ {
reference = _assemblyDefinition.MainModule.Import(method); reference = destinationContext._assemblyDefinition.MainModule.Import(method);
} }
return reference; return reference;
} }

View File

@ -1,5 +1,6 @@
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -15,18 +16,49 @@ namespace StardewModdingAPI.Helpers
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method) private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
{ {
var callTarget = target;
if (method.HasParameters)
{
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
ilProcessor.InsertBefore(target, loadObjInstruction);
callTarget = loadObjInstruction;
}
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method); Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method);
ilProcessor.InsertBefore(target, callEnterInstruction); ilProcessor.InsertAfter(callTarget, callEnterInstruction);
} }
private static void InjectMethod(ILProcessor ilProcessor, IEnumerable<Instruction> targets, MethodReference method) private static void InjectMethod(ILProcessor ilProcessor, IEnumerable<Instruction> targets, MethodReference method)
{ {
foreach(var target in targets) foreach(var target in targets.ToList())
{ {
InjectMethod(ilProcessor, target, method); 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) // public void ReplaceInstruction(ILProcessor processor, OpCode opcode, string oldOperand, string newOperand)
//{ //{
@ -38,7 +70,7 @@ namespace StardewModdingAPI.Helpers
string injectedType, string injectedMethod) string injectedType, string injectedMethod)
{ {
var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod); var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod);
var reference = stardewContext.ImportSMAPIMethodInStardew(methodInfo); var reference = smapiContext.ImportSMAPIMethodInStardew(stardewContext, methodInfo);
var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod); var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod);
InjectMethod(ilProcessor, ilProcessor.Body.Instructions.First(), reference); InjectMethod(ilProcessor, ilProcessor.Body.Instructions.First(), reference);
} }
@ -47,8 +79,8 @@ namespace StardewModdingAPI.Helpers
string injectedType, string injectedMethod) string injectedType, string injectedMethod)
{ {
var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod); var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod);
var reference = stardewContext.ImportSMAPIMethodInStardew(methodInfo); var reference = smapiContext.ImportSMAPIMethodInStardew(stardewContext, methodInfo);
var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod); var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod);
InjectMethod(ilProcessor, ilProcessor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret), reference); InjectMethod(ilProcessor, ilProcessor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret), reference);
} }
} }

View File

@ -179,42 +179,9 @@ namespace StardewModdingAPI.Inheritance
public Farmer CurrentFarmer { get { return player; } } public Farmer CurrentFarmer { get { return player; } }
public SGame() public SGame()
{ {
instance = this; instance = this;
graphics.GraphicsProfile = GraphicsProfile.HiDef;
#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
} }
protected override void Initialize() protected override void Initialize()

View File

@ -74,30 +74,19 @@ namespace StardewModdingAPI
StardewContext = new CecilContext(CecilContextType.Stardew); StardewContext = new CecilContext(CecilContextType.Stardew);
SmapiContext = new CecilContext(CecilContextType.SMAPI); SmapiContext = new CecilContext(CecilContextType.SMAPI);
//StardewContext.ReplaceMethodInstruction(OpCodes.Newobj, "System.Void StardewValley.Game1::.ctor()") //StardewContext.ReplaceMethodInstruction(OpCodes.Newobj, "System.Void StardewValley.Game1::.ctor()");
var test = StardewContext.GetMethodILProcessor("StardewValley.Program", "Main"); //CecilHelper.RedirectConstructor(StardewContext, SmapiContext, "StardewValley.Program", "Main",
StardewContext.GetTypeDefinition("StardewValley.Game1"); // "StardewValley.Game1", ".ctor", "StardewModdingAPI.Inheritance.SGame", ".ctor");
CecilHelper.InjectExitMethod(StardewContext, SmapiContext, "StardewValley.Game1", ".ctor", "StardewModdingAPI.Program", "Test");
} }
private static void WeaveOnEnterMethod(Mono.Cecil.Cil.ILProcessor ilProcessor, Instruction target, MethodReference callback) public static void Test(Game1 instance)
{ {
// Instruction loadNameInstruction = ilProcessor.Create(OpCodes.Ldstr, name); var inst = instance;
//ilProcessor.InsertBefore(target, loadNameInstruction); string test = Game1.samBandName;
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, callback); var test2 = Game1.numberOfSelectedItems;
ilProcessor.InsertBefore(target, callEnterInstruction);
} }
private static void WeaveOnExitMethod(Mono.Cecil.Cil.ILProcessor ilProcessor, List<Instruction> targets, MethodReference callback)
{
// Instruction loadNameInstruction = ilProcessor.Create(OpCodes.Ldstr, name);
//ilProcessor.InsertBefore(target, loadNameInstruction);
foreach (var returnInstruction in targets)
{
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, callback);
ilProcessor.InsertBefore(returnInstruction, callEnterInstruction);
}
}
/// <summary> /// <summary>
/// Set up the console properties /// Set up the console properties
/// </summary> /// </summary>
@ -120,6 +109,7 @@ namespace StardewModdingAPI
_modPaths = new List<string>(); _modPaths = new List<string>();
_modContentPaths = 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 //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(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "Mods"));
_modPaths.Add(Path.Combine(Constants.ExecutionPath, "Mods")); _modPaths.Add(Path.Combine(Constants.ExecutionPath, "Mods"));
@ -145,10 +135,10 @@ namespace StardewModdingAPI
private static void ConfigureSDV() private static void ConfigureSDV()
{ {
StardewModdingAPI.Log.Info("Initializing SDV Assembly..."); StardewModdingAPI.Log.Info("Initializing SDV Assembly...");
// Load in the assembly - ignores security // Load in the assembly - ignores security
StardewAssembly = Assembly.UnsafeLoadFrom(Constants.ExecutionPath + "\\Stardew Valley.exe"); //StardewAssembly = Assembly.UnsafeLoadFrom(Constants.ExecutionPath + "\\Stardew Valley.exe");
//StardewAssembly = Assembly.Load(StardewContext.ModifiedAssembly.GetBuffer()); StardewAssembly = Assembly.Load(StardewContext.ModifiedAssembly.GetBuffer());
StardewProgramType = StardewAssembly.GetType("StardewValley.Program", true); StardewProgramType = StardewAssembly.GetType("StardewValley.Program", true);
StardewGameInfo = StardewProgramType.GetField("gamePtr"); StardewGameInfo = StardewProgramType.GetField("gamePtr");
@ -163,7 +153,7 @@ namespace StardewModdingAPI
// Wait for the game to load up // Wait for the game to load up
while (!ready) ; while (!ready) ;
//SDV is running //SDV is running
StardewModdingAPI.Log.Comment("SDV Loaded Into Memory"); StardewModdingAPI.Log.Comment("SDV Loaded Into Memory");
@ -181,12 +171,16 @@ namespace StardewModdingAPI
//Events.MenuChanged += Events_MenuChanged; //Idk right now //Events.MenuChanged += Events_MenuChanged; //Idk right now
StardewModdingAPI.Log.Verbose("Applying Final SDV Tweaks..."); StardewModdingAPI.Log.Verbose("Applying Final SDV Tweaks...");
StardewInvoke(() =>
{ StardewAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } });
gamePtr.IsMouseVisible = false; //StardewInvoke(() =>
gamePtr.Window.Title = "Stardew Valley - Version " + Game1.version; //{
StardewForm.Resize += Events.GraphicsEvents.InvokeResize; // gamePtr.IsMouseVisible = false;
}); // gamePtr.Window.Title = "Stardew Valley - Version " + Game1.version;
// StardewForm.Resize += Events.GraphicsEvents.InvokeResize;
//});
//var test = (Game1)StardewGameInfo.GetValue(StardewProgramType);
} }
/// <summary> /// <summary>
@ -248,19 +242,19 @@ namespace StardewModdingAPI
try try
{ {
gamePtr = new SGame(); //gamePtr = new SGame();
StardewModdingAPI.Log.Verbose("Patching SDV Graphics Profile..."); //StardewModdingAPI.Log.Verbose("Patching SDV Graphics Profile...");
Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; //Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
LoadMods(); //LoadMods();
StardewForm = Control.FromHandle(Program.gamePtr.Window.Handle).FindForm(); //StardewForm = Control.FromHandle(Program.gamePtr.Window.Handle).FindForm();
StardewForm.Closing += StardewForm_Closing; //StardewForm.Closing += StardewForm_Closing;
ready = true; ready = true;
StardewAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } }); //Game1 g1 = gamePtr as Game1;
//StardewGameInfo.SetValue(StardewProgramType, gamePtr); //StardewGameInfo.SetValue(StardewProgramType, g1);
gamePtr.Run(); //gamePtr.Run();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -162,6 +162,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
<Content Include="Manifest Resources\Microsoft.Xna.Framework.RuntimeProfile.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>