Removed own memberwise cast function, and replaced with one by jpmikkers. This needs converting to a recursion-less version and having the loop issue I think it has fixed
This commit is contained in:
parent
1ec0570ed9
commit
65f1e5bec4
|
@ -6,7 +6,8 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace StardewModdingAPI.API
|
namespace StardewModdingAPI.API
|
||||||
{
|
{
|
||||||
public class Game
|
class Game
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ namespace StardewModdingAPI.Events
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Program.IsGameReferenceDirty = true;
|
||||||
|
var test = Program.gamePtr;
|
||||||
UpdateTick.Invoke(null, EventArgs.Empty);
|
UpdateTick.Invoke(null, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,13 @@ namespace StardewModdingAPI.Helpers
|
||||||
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
|
private static void InjectMethod(ILProcessor ilProcessor, Instruction target, MethodReference method)
|
||||||
{
|
{
|
||||||
Instruction callEnterInstruction = ilProcessor.Create(OpCodes.Call, 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)
|
if (method.HasParameters)
|
||||||
{
|
{
|
||||||
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
|
Instruction loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0);
|
||||||
|
|
|
@ -9,140 +9,5 @@ namespace StardewModdingAPI.Helpers
|
||||||
{
|
{
|
||||||
public static class ReflectionHelper
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
HiDef
|
|
|
@ -3,6 +3,7 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using StardewModdingAPI.Events;
|
using StardewModdingAPI.Events;
|
||||||
|
using StardewModdingAPI.ExtensionMethods;
|
||||||
using StardewModdingAPI.Helpers;
|
using StardewModdingAPI.Helpers;
|
||||||
using StardewModdingAPI.Inheritance;
|
using StardewModdingAPI.Inheritance;
|
||||||
using StardewModdingAPI.Inheritance.Menus;
|
using StardewModdingAPI.Inheritance.Menus;
|
||||||
|
@ -26,27 +27,31 @@ namespace StardewModdingAPI
|
||||||
|
|
||||||
public static Texture2D DebugPixel { get; private set; }
|
public static Texture2D DebugPixel { get; private set; }
|
||||||
|
|
||||||
private static object modifiedAssemblyGame;
|
public static bool IsGameReferenceDirty { get; set; }
|
||||||
//public static Game1 gamePtr;
|
|
||||||
|
public static object gameInst;
|
||||||
|
|
||||||
|
public static Game1 _gamePtr;
|
||||||
public static Game1 gamePtr
|
public static Game1 gamePtr
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return modifiedAssemblyGame.MemberwiseCast<Game1>();
|
if(IsGameReferenceDirty && gameInst != null)
|
||||||
|
{
|
||||||
|
_gamePtr = gameInst.Copy<Game1>();
|
||||||
|
}
|
||||||
|
return _gamePtr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ready;
|
public static bool ready;
|
||||||
|
|
||||||
public static Assembly StardewAssembly;
|
|
||||||
public static Type StardewProgramType;
|
|
||||||
public static FieldInfo StardewGameInfo;
|
|
||||||
public static Form StardewForm;
|
public static Form StardewForm;
|
||||||
|
|
||||||
public static Thread gameThread;
|
public static Thread gameThread;
|
||||||
public static Thread consoleInputThread;
|
public static Thread consoleInputThread;
|
||||||
|
|
||||||
public static CecilContext StardewContext;
|
|
||||||
public static CecilContext SmapiContext;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -79,25 +84,18 @@ namespace StardewModdingAPI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ConfigureMethodInjection()
|
private static void ConfigureMethodInjection()
|
||||||
{
|
{
|
||||||
StardewContext = new CecilContext(CecilContextType.Stardew);
|
StardewAssembly.ModifyStardewAssembly();
|
||||||
SmapiContext = new CecilContext(CecilContextType.SMAPI);
|
|
||||||
|
|
||||||
//StardewContext.ReplaceMethodInstruction(OpCodes.Newobj, "System.Void StardewValley.Game1::.ctor()");
|
|
||||||
//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
|
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
StardewAssembly.WriteModifiedExe();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void Test(object instance)
|
public static void Test(object instance)
|
||||||
{
|
{
|
||||||
modifiedAssemblyGame = instance;
|
gameInst = instance;
|
||||||
|
IsGameReferenceDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -138,7 +136,8 @@ namespace StardewModdingAPI
|
||||||
|
|
||||||
if (!File.Exists(Constants.ExecutionPath + "\\Stardew Valley.exe"))
|
if (!File.Exists(Constants.ExecutionPath + "\\Stardew Valley.exe"))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(string.Format("Could not found: {0}\\Stardew Valley.exe", Constants.ExecutionPath));
|
StardewModdingAPI.Log.Error("Replace this");
|
||||||
|
//throw new FileNotFoundException(string.Format("Could not found: {0}\\Stardew Valley.exe", Constants.ExecutionPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,44 +149,26 @@ namespace StardewModdingAPI
|
||||||
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.Load(StardewContext.ModifiedAssembly.GetBuffer());
|
StardewAssembly.LoadStardewAssembly();
|
||||||
StardewProgramType = StardewAssembly.GetType("StardewValley.Program", true);
|
StardewModdingAPI.Log.Comment("SDV Loaded Into Memory");
|
||||||
StardewGameInfo = StardewProgramType.GetField("gamePtr");
|
|
||||||
|
|
||||||
// Change the game's version
|
// Change the game's version
|
||||||
StardewModdingAPI.Log.Verbose("Injecting New SDV Version...");
|
StardewModdingAPI.Log.Verbose("Injecting New SDV Version...");
|
||||||
Game1.version += string.Format("-Z_MODDED | SMAPI {0}", Constants.VersionString);
|
Game1.version += string.Format("-Z_MODDED | SMAPI {0}", Constants.VersionString);
|
||||||
|
|
||||||
// Create the thread for the game to run in.
|
// Create the thread for the game to run in.
|
||||||
gameThread = new Thread(RunGame);
|
Application.ThreadException += StardewModdingAPI.Log.Application_ThreadException;
|
||||||
StardewModdingAPI.Log.Info("Starting SDV...");
|
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||||
gameThread.Start();
|
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
|
//Create definition to listen for input
|
||||||
StardewModdingAPI.Log.Verbose("Initializing Console Input Thread...");
|
StardewModdingAPI.Log.Verbose("Initializing Console Input Thread...");
|
||||||
consoleInputThread = new Thread(ConsoleInputThread);
|
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("help", "Lists all commands | 'help <cmd>' returns command description").CommandFired += help_CommandFired;
|
||||||
//Command.RegisterCommand("crash", "crashes sdv").CommandFired += delegate { Game1.player.draw(null); };
|
|
||||||
|
|
||||||
StardewModdingAPI.Log.Verbose("Applying Final SDV Tweaks...");
|
StardewAssembly.Launch();
|
||||||
|
|
||||||
StardewAssembly.EntryPoint.Invoke(null, new object[] { new string[] { } });
|
|
||||||
//StardewInvoke(() =>
|
|
||||||
//{
|
|
||||||
// gamePtr.IsMouseVisible = false;
|
|
||||||
// gamePtr.Window.Title = "Stardew Valley - Version " + Game1.version;
|
|
||||||
// StardewForm.Resize += Events.GraphicsEvents.InvokeResize;
|
|
||||||
//});
|
|
||||||
|
|
||||||
//var test = (Game1)StardewGameInfo.GetValue(StardewProgramType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -241,21 +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
|
|
||||||
{
|
|
||||||
ready = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
StardewModdingAPI.Log.Error("Game failed to start: " + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void StardewForm_Closing(object sender, CancelEventArgs e)
|
static void StardewForm_Closing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -73,8 +73,7 @@
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
<DefineConstants>
|
<DefineConstants>DEBUG</DefineConstants>
|
||||||
</DefineConstants>
|
|
||||||
<UseVSHostingProcess>true</UseVSHostingProcess>
|
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||||
|
@ -106,7 +105,7 @@
|
||||||
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
|
<HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Stardew Valley, Version=1.0.5905.5747, Culture=neutral, processorArchitecture=x86">
|
<Reference Include="Stardew Valley, Version=1.0.5912.41135, Culture=neutral, processorArchitecture=x86">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
|
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\Stardew Valley.exe</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@ -119,8 +118,7 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
<Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
|
<Reference Include="xTile">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
|
<HintPath>..\..\..\..\Games\SteamLibrary\steamapps\common\Stardew Valley\xTile.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -143,10 +141,13 @@
|
||||||
<Compile Include="Events\Mine.cs" />
|
<Compile Include="Events\Mine.cs" />
|
||||||
<Compile Include="Events\Player.cs" />
|
<Compile Include="Events\Player.cs" />
|
||||||
<Compile Include="Events\Time.cs" />
|
<Compile Include="Events\Time.cs" />
|
||||||
|
<Compile Include="ExtensionMethods\Array.cs" />
|
||||||
|
<Compile Include="ExtensionMethods\Object.cs" />
|
||||||
<Compile Include="Extensions.cs" />
|
<Compile Include="Extensions.cs" />
|
||||||
<Compile Include="Helpers\CecilContext.cs" />
|
<Compile Include="Helpers\CecilContext.cs" />
|
||||||
<Compile Include="Helpers\CecilHelper.cs" />
|
<Compile Include="Helpers\CecilHelper.cs" />
|
||||||
<Compile Include="Helpers\ReflectionHelper.cs" />
|
<Compile Include="Helpers\ReflectionHelper.cs" />
|
||||||
|
<Compile Include="Helpers\StardewAssembly.cs" />
|
||||||
<Compile Include="Inheritance\ItemStackChange.cs" />
|
<Compile Include="Inheritance\ItemStackChange.cs" />
|
||||||
<Compile Include="Inheritance\Menus\SBobberBar.cs" />
|
<Compile Include="Inheritance\Menus\SBobberBar.cs" />
|
||||||
<Compile Include="Inheritance\Menus\SGameMenu.cs" />
|
<Compile Include="Inheritance\Menus\SGameMenu.cs" />
|
||||||
|
@ -163,9 +164,6 @@
|
||||||
</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>
|
||||||
|
|
Loading…
Reference in New Issue