Closed Pathoschild closed 7 years ago
@Pathoschild Here's what I came up with for fixing the references to Game1.activeClickableMenu
, as stated on Discord, I figured I'd just let you handle the implementation yourself.
Method
of the current assembly.Instruction
from the current Method
.Instruction
that matches Game1.activeClickableMenu
as a field.Instruction
with a new one mapped to a property.Some of the code can be used that's already in AssemblyLoader
like shown here: https://github.com/Pathoschild/SMAPI/blob/develop/src/StardewModdingAPI/Framework/AssemblyLoader.cs#L196
public static string activeDangus = "dangus";
private static string _activeDrangus = "drangus";
public static string activeDrangus
{
get { return _activeDrangus; }
set { _activeDrangus = value; }
}
foreach (var type in module.Types)
{
if (type.HasMethods)
{
foreach (var method in type.Methods)
{
if (method.HasBody)
{
var il = method.Body.GetILProcessor();
foreach (var instruction in method.Body.Instructions.ToArray())
{
if ((instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stsfld) &&
instruction.Operand is FieldReference &&
(instruction.Operand as FieldReference).FullName == "System.String StardewModdingAPI.Constants::activeDangus")
{
string which = (instruction.OpCode == OpCodes.Ldsfld) ? "get" : "set";
Instruction call = il.Create(
OpCodes.Call,
method.Module.Import(typeof(Constants).GetMethod($"{which}_activeDrangus"))
);
il.Replace(instruction, call);
}
}
}
}
}
}
This code assuming that you allow the method to return with true
will rewrite the assembly and give you the intended results. I tested this on trainer mod with the following code:
private string refDrangus = Constants.activeDrangus;
private string refDangus = Constants.activeDangus;
public override void Entry(IModHelper helper)
{
this.RegisterCommands();
GameEvents.UpdateTick += this.ReceiveUpdateTick;
Console.WriteLine("refDrangus: " + refDrangus);
Console.WriteLine("refDangus: " + refDangus);
Constants.activeDrangus = "sample";
Constants.activeDangus = "simple";
Console.WriteLine("Constants.activeDrangus: " + Constants.activeDrangus);
Console.WriteLine("Constants.activeDangus: " + Constants.activeDangus);
}
The output of this will be:
refDrangus: drangus
refDangus: drangus
...
Constants.activeDrangus: simple
Constants.activeDangus: simple
Provided you can read my obtuse testing name differences of drangus
with an r
and dangus
without the r
, you can see that all occurrences of dangus
get rewritten as drangus
.
So, here are the important things to take from this test for implementation...
OpCodes.Ldsfld
and OpCodes.Stsfld
stand for Load Static Field and Set Static Field respectively. These opcodes are used for static fields (go figure) and are going to be the same ones references for Game1.activeClickableMenu
.
instruction.Operand is FieldReference
is important as the Operand needs to be a FieldReference
, nuff said.
The string System.String StardewModdingAPI.Constants::activeDangus
is matched against the FieldReference.FullName
to ensure that the right Instruction
is being matched. For Game1.activeClickableMenu
the string will be something like StardewValley.Menus.IClickableMenu StardewValley.Game1::activeClickableMenu
or something similar.
OpCodes.Call
is used when constructing the new Instruction
to reference the property, the important pair to this is the actual MethodReference
which is generated by this line:
method.Module.Import(typeof(Constants).GetMethod("xxx_activeDrangus"))
. The call to method.Module.Import
returns a MethodReference
which is used when creating the Instruction
. The get
and set
used in the property will generate the methods get_{PropertyName}
and set_{PropertyName}
respectively.
il.Replace(instruction, call)
is used to replace the current iterating Instruction
with the new one generated. One line, easy to replace, the only thing of course is to not iterate over the actual enumerable, and instead create an array or something from it (something you're fully aware of and already doing later on in the file).
As for the implementation, I did try to do something with the Method Rewriter classes you had, but found that it didn't suit my needs and I figured I'd let you handle the implementation (sorry). More importantly however, I foresee this being a potential problem again in the future if SDV updates again and changes something like this. I believe that some sort of rewriter can be written to easily take these key points I mentioned above in the notes and rewrite Mod assemblies really easily and dynamically based on a new class added to SMAPI much like you're already doing with the SpriteBatchRewriter
.
Problems with the current implementation are that for that you need to rewrite instructions, not methods, even though you technically are rewriting a method. More importantly, the ShouldRewrite
call accepts a MethodReference
and not a FieldReference
which is what's needed here, plus the rewrite isn't exactly the same either.
Hopefully you have everything here needed to implement this in some elegant way that I can't be bothered to mess with right now and instead took the time to write you this lovely comment.
Ping me on Discord if you have any questions.
Done in the upcoming SMAPI 1.9 release. It seems like Stardew Valley 1.2 development is winding down, so I don't expect any other major changes going forward.
Split between SMAPI 1.9 (to be released for Stardew Valley 1.11) and SMAPI 1.10 (to be released for Stardew Valley 1.2 beta).
Update SMAPI to account for the changes in Stardew Valley 1.2.
To do
SMAPI compatibility
Since Stardew Valley 1.2 breaks many mods anyway, remove the oldest deprecations and fix the issues that are easiest for mods to update.
Farmer
.SDV 1.2 introduces a root
Farmer
namespace, so all references toFarmer
are suddenly ambiguous.SGame.Draw
code.SDV 1.2 significantly changed the
Game1.Draw
code; rewrite SMAPI's override to compensate.In particular, players can now exit to the main menu which means we can no longer assume the game is only loaded once.
Mod compatibility
[x] Mark incompatible mods:
StardewModdingAPI.Extensions
.StardewModdingAPI.Extensions
.StardewModdingAPI.Inheritance.ItemStackChange
.Game1.borderFont
.Game1.borderFont
.GraphicsEvents.DrawTick
.(not latest)
StardewModdingAPI.Inheritance.SObject
until 1.6.1; then crashes until 1.6.4 ("Entoarox Framework requested an immediate game shutdown: Fatal error attempting to update player tick properties System.NullReferenceException: Object reference not set to an instance of an object. at Entoarox.Framework.PlayerHelper.Update(Object s, EventArgs e)".(1.0 in manifest)
NullReferenceException
inGameEvents.UpdateTick
.FormatException
when looking up NPCs.GraphicsEvents.OnPreRenderHudEventNoCheck
.Assembly.GetExecutingAssembly().Location
.StardewModdingAPI.Extensions
.StardewModdingAPI.Inheritance.ItemStackChange
.SGame
class.SGame
class.SGame
class.SGame
class.SGame
class.SGame
class.Game1.activeClickableMenu
as a field (now a property).Game1.player
as a field (now a property).Game1.gameMode
as a field (now a property).