summaryrefslogtreecommitdiff
path: root/src/SMAPI/Metadata/InstructionMetadata.cs
blob: 603a43cb12c5d36598cdc11e782439493d87fd44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.AssemblyRewriters;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Framework.ModLoading.Finders;
using StardewModdingAPI.Framework.ModLoading.Rewriters;
using StardewValley;
#if STARDEW_VALLEY_1_3
using SObject = StardewValley.Object;
#endif

namespace StardewModdingAPI.Metadata
{
    /// <summary>Provides CIL instruction handlers which rewrite mods for compatibility and throw exceptions for incompatible code.</summary>
    internal class InstructionMetadata
    {
        /*********
        ** Properties
        *********/
        /// <summary>The assembly names to which to heuristically detect broken references.</summary>
        /// <remarks>The current implementation only works correctly with assemblies that should always be present.</remarks>
        private readonly string[] ValidateReferencesToAssemblies = { "StardewModdingAPI", "Stardew Valley", "StardewValley" };


        /*********
        ** Public methods
        *********/
        /// <summary>Get rewriters which detect or fix incompatible CIL instructions in mod assemblies.</summary>
        public IEnumerable<IInstructionHandler> GetHandlers()
        {
            return new IInstructionHandler[]
            {
                /****
                ** rewrite CIL to fix incompatible code
                ****/
                // rewrite for crossplatform compatibility
                new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchMethods), onlyIfPlatformChanged: true),

#if !STARDEW_VALLEY_1_3
                // rewrite for Stardew Valley 1.2
                new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.activeClickableMenu)),
                new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.currentMinigame)),
                new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.gameMode)),
                new FieldToPropertyRewriter(typeof(Game1), nameof(Game1.player)),
                new FieldReplaceRewriter(typeof(Game1), "borderFont", nameof(Game1.smallFont)),
                new FieldReplaceRewriter(typeof(Game1), "smoothFont", nameof(Game1.smallFont)),

                // rewrite for SMAPI 1.9
                new TypeReferenceRewriter("StardewModdingAPI.Inheritance.ItemStackChange", typeof(ItemStackChange)),
#endif

                // rewrite for SMAPI 2.0
                new VirtualEntryCallRemover(),

                // rewrite for Stardew Valley 1.3
#if STARDEW_VALLEY_1_3
                new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize),
                new FieldToPropertyRewriter(typeof(Character), nameof(Character.name), nameof(Character.Name)),
                new FieldToPropertyRewriter(typeof(GameLocation), nameof(GameLocation.isFarm), nameof(GameLocation.IsFarm)),
                new FieldToPropertyRewriter(typeof(GameLocation), nameof(GameLocation.isOutdoors), nameof(GameLocation.isOutdoors)),
                new FieldToPropertyRewriter(typeof(GameLocation), nameof(GameLocation.name), nameof(GameLocation.Name)),
                new FieldToPropertyRewriter(typeof(Item), nameof(Item.category), nameof(Item.Category)),
                new FieldToPropertyRewriter(typeof(SObject), nameof(SObject.quality), nameof(SObject.Quality)),
                new FieldToPropertyRewriter(typeof(SObject), nameof(SObject.stack), nameof(SObject.Stack)),
#endif

                /****
                ** detect incompatible code
                ****/
                #if !STARDEW_VALLEY_1_3
                // detect changes in Stardew Valley 1.2
                new FieldFinder("StardewValley.Item", "set_Name", InstructionHandleResult.NotCompatible),

                // detect APIs removed in SMAPI 1.9
                new TypeFinder("StardewModdingAPI.Advanced.ConfigFile", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Advanced.IConfigFile", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Entities.SPlayer", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Extensions", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Inheritance.SGame", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Inheritance.SObject", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.LogWriter", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Manifest", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Version", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawDebug", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "DrawTick", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderHudEventNoCheck", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPostRenderGuiEventNoCheck", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderHudEventNoCheck", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GraphicsEvents", "OnPreRenderGuiEventNoCheck", InstructionHandleResult.NotCompatible),

                // detect APIs removed in SMAPI 2.0
                new TypeFinder("StardewModdingAPI.Command", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Config", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Log", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GameEvents", "Initialize", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GameEvents", "LoadContent", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.GameEvents", "GameLoaded", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.PlayerEvents", "LoadedGame", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.PlayerEvents", "FarmerChanged", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.TimeEvents", "DayOfMonthChanged", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.TimeEvents", "YearOfGameChanged", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.TimeEvents", "SeasonOfYearChanged", InstructionHandleResult.NotCompatible),
                new EventFinder("StardewModdingAPI.Events.TimeEvents", "OnNewDay", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Events.EventArgsCommand", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Events.EventArgsFarmerChanged", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Events.EventArgsLoadedGameChanged", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Events.EventArgsNewDay", InstructionHandleResult.NotCompatible),
                new TypeFinder("StardewModdingAPI.Events.EventArgsStringChanged", InstructionHandleResult.NotCompatible),
                new PropertyFinder("StardewModdingAPI.Mod", "PathOnDisk", InstructionHandleResult.NotCompatible),
                new PropertyFinder("StardewModdingAPI.Mod", "BaseConfigPath", InstructionHandleResult.NotCompatible),
                new PropertyFinder("StardewModdingAPI.Mod", "PerSaveConfigFolder", InstructionHandleResult.NotCompatible),
                new PropertyFinder("StardewModdingAPI.Mod", "PerSaveConfigPath", InstructionHandleResult.NotCompatible),
                #endif

                // detect broken code
                new ReferenceToMissingMemberFinder(this.ValidateReferencesToAssemblies),
                new ReferenceToMemberWithUnexpectedTypeFinder(this.ValidateReferencesToAssemblies),

                /****
                ** detect code which may impact game stability
                ****/
                new TypeFinder("Harmony.HarmonyInstance", InstructionHandleResult.DetectedGamePatch),
                new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic),
                new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerialiser),
                new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerialiser),
                new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.locationSerializer), InstructionHandleResult.DetectedSaveSerialiser),
                new EventFinder(typeof(SpecialisedEvents).FullName, nameof(SpecialisedEvents.UnvalidatedUpdateTick), InstructionHandleResult.DetectedUnvalidatedUpdateTick)
            };
        }
    }
}