SMAPI/StardewInjector/StardewHooker.cs

190 lines
6.1 KiB
C#

using Microsoft.Xna.Framework;
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;
using StardewModdingAPI;
namespace StardewInjector
{
public class Stardew_Hooker
{
private AssemblyDefinition m_vAsmDefinition = null;
private ModuleDefinition m_vModDefinition = null;
private Assembly m_vAssembly = null;
public bool Initialize()
{
Console.WriteLine("Initiating StarDew_Injector....");
try
{
this.m_vAsmDefinition = AssemblyDefinition.ReadAssembly(@"Stardew Valley.exe");
this.m_vModDefinition = this.m_vAsmDefinition.MainModule;
return true;
}
catch (Exception ex)
{
Program.LogError(ex);
return false;
}
}
public bool Finalize()
{
Console.WriteLine("Finalizing StarDew_Injector....");
try
{
if (this.m_vAsmDefinition == null)
return false;
using (MemoryStream mStream = new MemoryStream())
{
// Write the edited data to the memory stream..
this.m_vAsmDefinition.Write(mStream);
// Load the new assembly from the memory stream buffer..
this.m_vAssembly = Assembly.Load(mStream.GetBuffer());
Program.StardewAssembly = m_vAssembly;
return true;
}
}
catch (Exception ex)
{
Program.LogError(ex);
return false;
}
}
public bool Run()
{
if (this.m_vAssembly == null)
return false;
Console.WriteLine("Starting Stardew Valley...");
m_vAssembly.EntryPoint.Invoke(null, new object[] {new string[0]});
return true;
}
public void ApplyHooks()
{
Console.WriteLine("Applying StarDew_Injector....");
try
{
InjectMovementSpeed();
if (Config.SecondsPerTenMinutes != 7)
InjectClockScale();
if (Config.EnableEasyFishing)
InjectEasyFishing();
if (Config.EnableAlwaysSpawnFishingBubble)
InjectMoreBubbles();
/*
if (Config.EnableDebugMode)
InjectDebugMode();
*/
}
catch (Exception ex)
{
Program.LogError(ex);
}
}
private void InjectDebugMode()
{
this.m_vModDefinition.FindMethod("StardewValley.Program::.cctor")
.FindSetField("releaseBuild").Previous()
.ReplaceCreate(OpCodes.Ldc_I4_0);
Console.WriteLine("Enabled debug mode.");
}
private void InjectMoreBubbles()
{
this.m_vModDefinition.FindMethod("StardewValley.GameLocation::performTenMinuteUpdate")
.FindLoadField("currentLocation").Next(i => i.ToString().Contains("NextDouble")).Next()
.ReplaceCreate(OpCodes.Ldc_R8, 1.1);
Console.WriteLine("Forced each area to always spawn a fishing bubble.");
}
private void InjectEasyFishing()
{
this.m_vModDefinition.FindMethod("StardewValley.Menus.BobberBar::update")
.FindLoadConstant(694)
.Next(i => i.OpCode == OpCodes.Ldc_R4)
.ReplaceCreate(OpCodes.Ldc_R4, 0.001f)
.Next(i => i.OpCode == OpCodes.Ldc_R4)
.ReplaceCreate(OpCodes.Ldc_R4, 0.001f);
Console.WriteLine("Replaced fish escape constants for all bobbers & bobber id 694 with 0.001, slowing it down.");
}
private void InjectClockScale()
{
int timeScale = Config.SecondsPerTenMinutes;
timeScale *= 1000;
this.m_vModDefinition.FindMethod("StardewValley.Game1::UpdateGameClock")
.FindLoadConstant(7000f)
.ReplaceCreate(OpCodes.Ldc_R4, timeScale*1.0f)
.Next(i => i.OpCode == OpCodes.Ldc_R4 && (float) i.Operand == 7000f)
.ReplaceCreate(OpCodes.Ldc_R4, timeScale*1.0f)
.Next(i => i.OpCode == OpCodes.Ldc_I4 && (int) i.Operand == 7000)
.ReplaceCreate(OpCodes.Ldc_I4, timeScale);
Console.WriteLine("Updated lighting for new timescale ({0}).", timeScale);
}
private void InjectMovementSpeed()
{
if (Config.EnableTweakedDiagonalMovement)
{
this.m_vModDefinition.FindMethod("StardewValley.Farmer::getMovementSpeed")
.FindLoadField("movementDirections").Next(i => i.OpCode == OpCodes.Ldc_I4_1)
.ReplaceCreate(OpCodes.Ldc_I4_4);
Console.WriteLine("Removed diagonal movement check.");
}
if (Config.RunSpeed > 0)
{
this.m_vModDefinition.FindMethod("StardewValley.Farmer::getMovementSpeed")
.FindLoadField("movementDirections").Last().CreateBefore(OpCodes.Ldc_R4, (float) Config.RunSpeed).CreateAfter(OpCodes.Add);
Console.WriteLine("Added run speed: " + Config.RunSpeed);
}
}
private void DumpInstructionsToFile(MethodDefinition methodDefinition)
{
var fileName = string.Format("{0}.{1}.txt", methodDefinition.DeclaringType.Name, methodDefinition.Name);
using (var stream = File.OpenWrite(Path.Combine(".", fileName)))
using (var writer = new StreamWriter(stream))
{
var ilProcessor = methodDefinition.Body.GetILProcessor();
for (int i = 0; i < ilProcessor.Body.Instructions.Count; i++)
writer.WriteLine((i) + ":" + ilProcessor.Body.Instructions[i]);
}
}
}
}