Jump to content

Devs: please make every class in your dlls public


Recommended Posts

Simply put, I'm trying to create a mod that modifies quite a bit how spawn points work, and this involves monkey patching Vintagestory.Client.NoObf.GuiDialogDead so the constructor doesn't register  callbacks and ticking - this way I can replace it with my own GUI. Unfortunately this is made very difficult by this type being internal to the dll and therefore much harder to monkey patch - the type isn't available to use in Harmony annotations, for starters, and you can't use it in a postfix method signature for easy access to properties and the like.

So I'd like to humbly request from @Tyron and the dev team that they set all such types to be public. Given that we have the API interfaces to keep a clean separation between dlls, I don't think using internal should be all that important?

Thoughts welcome.

Edited by BearWrestler
Link to comment
Share on other sites

Well, it seems to me that the best for modders would be if you regex search and replace every "internal class" in a Vintagestory.*.NoObf.* namespace, to put the public keyword instead of the internal keyword. And then use this principle for future classes. Since you never know what modders might need to monkey patch.

If you're asking what I need for my specific project, I'd rather hold on and make a list that's as exhaustive as possible, so I'll get back to you. But the initial intent of this post was to make a request that benefits everybody, not just me.

BTW here are my various attempts to work around the internal class and unregister GuiDialogDead's tick function, if anyone is curious:

Manual patching approach, "works" but doesn't skip the constructor body as expected. Most likely fails because constructors are a strange beast to patch:

    public static class Patcher
    {
        public static void DoPatching()
        {
            // Apply all attribute-driven patches
            var harmony = new Harmony("multispawn");
            harmony.PatchAll();

            // Manual patching required to get to the type, since typeof doesn't work with internal types from other assemblies
            // However, it seems to fail to properly skip constructor body.
            var type = AccessTools.TypeByName("Vintagestory.Client.NoObf.GuiDialogDead");
            var constructor = AccessTools.Constructor(type);
            var prefix = AccessTools.Method(typeof(Patcher), nameof(GuiDialogDeadConstructorPrefix));
            harmony.Patch(constructor, prefix: new HarmonyMethod(prefix));
        }

        static bool GuiDialogDeadConstructorPrefix() { return false; }
    }

Various failed attribute-based patches:

 // Doesn't work because type Vintagestory.Client.NoObf.GuiDialogDead is internal.
    [HarmonyPatch(typeof(Vintagestory.Client.NoObf.GuiDialogDead))]
    [HarmonyPatch("GuiDialogDead")]
    class PatchGuiDialogDead
    {

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    //Custom attribute to allow class to be looked up by string name, doesn't seem to be picked up correctly by Harmony
    public class HarmonyPatchStringTypeAttribute : HarmonyPatch
    {
        public HarmonyPatchStringTypeAttribute(string typeName) : base(AccessTools.TypeByName(typeName))
        {
        }
    }

    [HarmonyPatchStringType("Vintagestory.Client.NoObf.GuiDialogDead")]
    [HarmonyPatch(MethodType.Constructor)]
    class PatchGuiDialogDead
    {
        //Attempt to skip whole constructor body
        static bool Prefix()
        {
            return false;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Attempt to unregister GuiDialogDead. Doesn't actually destroy the gui, probably because it's kept alive by tick/callback registrations.
    [HarmonyPatch(typeof(GuiManager))]
    [HarmonyPatch("RegisterDefaultDialogs")]
    public class PatchGuiManager
    {
        [HarmonyPostfix]
        static void Postfix(GuiManager __instance)
        {
            ClientMain client = (ClientMain)__instance.World;
            var guiDialogDeadType = AccessTools.TypeByName("Vintagestory.Client.NoObf.GuiDialogDead");
            List<GuiDialog> loadedGuis = Traverse.Create(client).Property<List<GuiDialog>>("LoadedGuis").Value;
            var guiDialogDead = loadedGuis.First(x => x.GetType() == guiDialogDeadType);
            Debug.Assert(guiDialogDead != null);
            client.UnregisterDialog(guiDialogDead);
        }
    }

 

Link to comment
Share on other sites

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.