Pathoschild / SMAPI

The modding API for Stardew Valley.
https://smapi.io/
GNU Lesser General Public License v3.0
1.86k stars 263 forks source link

Update for Stardew Valley 1.2 #231

Closed Pathoschild closed 7 years ago

Pathoschild commented 7 years ago

Update SMAPI to account for the changes in Stardew Valley 1.2.

To do

SMAPI compatibility

Mod compatibility

LeonBlade commented 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.

Fixing the activeClickableMenu Problem

Steps to Success

  1. Iterate through each of the loaded assemblies.
  2. Iterate through each Method of the current assembly.
  3. Iterate through each Instruction from the current Method.
  4. Check for a valid Instruction that matches Game1.activeClickableMenu as a field.
  5. Rewrite the current Instruction with a new one mapped to a property.
  6. Write the assembly changes.

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

Example Code

Constants.cs:24

public static string activeDangus = "dangus";
private static string _activeDrangus = "drangus";
public static string activeDrangus
{
    get { return _activeDrangus; }
    set { _activeDrangus = value; }
}

AssemblyLoader.cs:160

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:

TrainerMod.cs:40~

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.

Important Notes

So, here are the important things to take from this test for implementation...

Finale

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.

Pathoschild commented 7 years ago

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.

Pathoschild commented 7 years ago

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).