Encapsulated Cecil functionality. Removed references to stardew injector from program

This commit is contained in:
ClxS 2016-03-09 18:45:04 +00:00
parent c8e09331d2
commit 8201b96034
6 changed files with 182 additions and 73 deletions

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,115 @@
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;
private 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 = _assemblyDefinition.MainModule.Types.FirstOrDefault(n => n.FullName == type);
if (typeDef != null)
{
MethodDefinition methodDef = typeDef.Methods.FirstOrDefault(m => m.Name == method);
if (methodDef != null)
{
ilProcessor = methodDef.Body.GetILProcessor();
}
}
return ilProcessor;
}
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 smapiAssembly = Assembly.GetExecutingAssembly().GetType(type);
if (smapiAssembly != null)
{
methodInfo = smapiAssembly.GetMethod(method);
}
return methodInfo;
}
public MethodReference ImportSMAPIMethodInStardew(MethodInfo 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 = _assemblyDefinition.MainModule.Import(method);
}
return reference;
}
}
}

View File

@ -0,0 +1,47 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
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
{
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
{
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, method);
ilProcessor.InsertBefore(target, callEnterInstruction);
}
private static void InjectMethod(ILProcessor ilProcessor, IEnumerable<Instruction> targets, MethodReference method)
{
foreach(var target in targets)
{
InjectMethod(ilProcessor, target, method);
}
}
public static void InjectEntryMethod(CecilContext stardewContext, CecilContext smapiContext, string injecteeType, string injecteeMethod,
string injectedType, string injectedMethod)
{
var methodInfo = smapiContext.GetSMAPIMethodReference(injectedType, injectedMethod);
var reference = stardewContext.ImportSMAPIMethodInStardew(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 = stardewContext.ImportSMAPIMethodInStardew(methodInfo);
var ilProcessor = stardewContext.GetMethodILProcessor(injecteeType, injecteeMethod);
InjectMethod(ilProcessor, ilProcessor.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret), reference);
}
}
}

View File

@ -3,6 +3,7 @@ using Microsoft.Xna.Framework.Graphics;
using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Events;
using StardewModdingAPI.Helpers;
using StardewModdingAPI.Inheritance;
using StardewModdingAPI.Inheritance.Menus;
using StardewValley;
@ -34,10 +35,10 @@ namespace StardewModdingAPI
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; }
public static Thread consoleInputThread;
public static CecilContext StardewContext;
public static CecilContext SmapiContext;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -52,7 +53,6 @@ namespace StardewModdingAPI
ConfigureUI();
ConfigurePaths();
ConfigureMethodInjection();
ConfigureInjector();
//ConfigureSDV();
//GameRunInvoker();
@ -72,23 +72,8 @@ namespace StardewModdingAPI
/// </summary>
private static void ConfigureMethodInjection()
{
AssemblyDefinition stardewAssembly = AssemblyDefinition.ReadAssembly(Constants.ExecutionPath + "\\Stardew Valley.exe");
TypeDefinition type = stardewAssembly.MainModule.Types.FirstOrDefault(n => n.FullName == "StardewValley.FarmAnimal");
MethodDefinition foundMethod = type.Methods.FirstOrDefault(m => m.Name == "eatGrass");
Mono.Cecil.Cil.ILProcessor ilProcessor = foundMethod.Body.GetILProcessor();
var smapiAssembly = Assembly.GetExecutingAssembly().GetType("StardewModdingAPI.Events.FarmAnimal");
var eventMethod = smapiAssembly.GetMethod("eatGrass_OnEnter");
AssemblyDefinition fstardewAssembly = AssemblyDefinition.ReadAssembly(Assembly.GetExecutingAssembly().Location);
MethodReference ffoundMethod = stardewAssembly.MainModule.Import(eventMethod);
WeaveOnEnterMethod(ilProcessor, foundMethod.Body.Instructions.First(), ffoundMethod);
WeaveOnExitMethod(ilProcessor, foundMethod.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret).ToList(), ffoundMethod);
MemoryStream stream = new MemoryStream();
stardewAssembly.Write(stream);
Assembly.Load(stream.GetBuffer());
StardewContext = new CecilContext(CecilContextType.Stardew);
SmapiContext = new CecilContext(CecilContextType.SMAPI);
}
private static void WeaveOnEnterMethod(Mono.Cecil.Cil.ILProcessor ilProcessor, Instruction target, MethodReference callback)
@ -150,50 +135,7 @@ namespace StardewModdingAPI
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);
}
}
}
}
/// <summary>
/// Load Stardev Valley and control features
/// </summary>

View File

@ -108,7 +108,7 @@
</Reference>
<Reference Include="Stardew Valley, Version=1.0.5905.5747, Culture=neutral, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\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" />
@ -121,7 +121,7 @@
<Reference Include="System.Xml" />
<Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>D:\Games\steamapps\common\Stardew Valley\xTile.dll</HintPath>
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@ -143,6 +143,8 @@
<Compile Include="Events\Player.cs" />
<Compile Include="Events\Time.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="Helpers\CecilContext.cs" />
<Compile Include="Helpers\CecilHelper.cs" />
<Compile Include="Inheritance\ItemStackChange.cs" />
<Compile Include="Inheritance\Menus\SBobberBar.cs" />
<Compile Include="Inheritance\Menus\SGameMenu.cs" />
@ -177,9 +179,7 @@
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Folder Include="Cecil\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View File

@ -53,7 +53,7 @@
<Private>False</Private>
</Reference>
<Reference Include="Stardew Valley">
<HintPath>D:\Games\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" />
@ -64,7 +64,7 @@
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xTile">
<HintPath>D:\Games\steamapps\common\Stardew Valley\xTile.dll</HintPath>
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>