Solved Enum in the plugin code in c#

Joined
Jan 8, 2024
Messages
14
Reactions
0
Has anyone encountered such a glitch?
I'm writing a plugin in c#. As soon as I try to use my enum in the code (in any form), the game crashes without an error message and is unloaded.

Are enums prohibited in the code, or is this my personal local glitch?
 
Understood. Has accepted.
If only someone could explain what’s so dangerous about enums in C# code that they had to be removed from the VAM sandbox, and in such a brutal way…
 
Upvote 0
what’s so dangerous about enums in C# code that they had to be removed from the VAM sandbox, and in such a brutal way…

You really think that it was a wanted effect haha?
That's not a wanted situation, that's the realtime compiler which apparently cannot compile enums properly, something meshed never found a way to fix.
 
Upvote 0
You really think that it was a wanted effect haha?
That's not a wanted situation, that's the realtime compiler which apparently cannot compile enums properly, something meshed never found a way to fix.
OK. It fits into the picture of the world. It would be strange if this was done on purpose.
 
Upvote 0
I ran into this during plugin development and tracked down the cause a while ago with headless tests (Unity 2018.1.9f2, running VaM's own Assembly-CSharp.dll and mcs.dll), and how it could be fixed.

I'll share my findings here since the fix seems quite small, and I've now confirmed it inside VaM itself. Anyone interested can also have a look.

Here is the report I wrote a while ago diagnosing the issue:



The issue

Declaring any enum in a plugin crashes VaM natively while the plugin compiles. It doesn't need to be used, and : byte or nested enums crash the same way. Because it's an access violation inside the Mono runtime, nothing catches it: no compiler error, VaM just dies with the unit crash handler. Minimal repro:

C#:
public class EnumRepro : MVRScript { }

public enum Mode { A, B, C }   // this line alone is enough

Stack at the crash:

Code:
mono_class_layout_fields              mono/metadata/class.c:1637   <- access violation
typebuilder_setup_fields              mono/metadata/reflection.c
mono_reflection_create_runtime_class  mono/metadata/reflection.c
System.Reflection.Emit.TypeBuilder.CreateType()
Mono.CSharp.TypeDefinition.CloseContainer()
DynamicCSharp.Compiler.McsDriver.Compile(...)

Cause

The embedded mcs builds an enum in this order:
  1. DefineType(name, attrs, null): no parent yet.
  2. DefineField("value__", ...): this is the first field.
  3. SetParent(typeof(System.Enum)).

This Mono version (Unity's legacy fork, branch unity-2018.1) records an enum's underlying type only once: when the first field is added, and only if the type is already an enum at that point. Here it isn't yet, so the type ends up as an enum with no underlying type. When the runtime then lays out the enum's members, it reads a NULL pointer at class.c:1637. Structs and classes never take this path.

You can reproduce it without VaM using plain Reflection.Emit on the Unity 2018.1 runtime:

C#:
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Repro"), AssemblyBuilderAccess.Run);
var mb = ab.DefineDynamicModule("Repro");
var tb = mb.DefineType("Mode", TypeAttributes.Public | TypeAttributes.Sealed, null);

tb.DefineField("value__", typeof(int),
    FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RTSpecialName);
tb.SetParent(typeof(Enum)); // mcs's order: parent AFTER value__

tb.DefineField("A", tb, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal)
  .SetConstant(0);

tb.CreateType(); // native crash here

Move SetParent(typeof(Enum)) above DefineField("value__", ...), or pass typeof(Enum) to DefineType directly, and you get a working Int32 enum.

Likely fix

In mcs, Mono.CSharp.Enum.DoDefineContainer(), swap two adjacent statements so the parent is set before value__ is defined:

C#:
// current (crashes on this runtime)
TypeBuilder.DefineField(UnderlyingValueField, UnderlyingType.GetMetaInfo(),
    FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RTSpecialName);
DefineBaseTypes(); // -> SetParent(System.Enum)

// fixed
DefineBaseTypes();
TypeBuilder.DefineField(UnderlyingValueField, UnderlyingType.GetMetaInfo(),
    FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RTSpecialName);

In the shipped VaM_Data/Managed/mcs.dll, that comes down to moving 7 bytes of IL within that one method. Nothing else in the file changes. I tested a patched copy headless through DynamicCSharp's compile-and-load path. Unused, used, byte-backed and nested enums all compiled, loaded and ran, and so did one using explicit values, an enum field, switch, ToString and Enum.Parse. The unpatched mcs.dll crashed on every one. An equivalent alternative is to pass System.Enum as the parent to DefineType when mcs creates an enum's type.

Confirmed in VaM

I swapped the patched mcs.dll into VaM_Data/Managed and added this test plugin to an atom:

C#:
using System;
using System.Collections.Generic;

public class HkEnumTest : MVRScript
{
    public enum Nested { First, Second }

    public HkTestMode Mode = HkTestMode.Low;
    HkTestFlags _flags = HkTestFlags.A | HkTestFlags.C;

    public override void Init()
    {
        string sw;
        switch (Mode)
        {
            case HkTestMode.Low: sw = "low"; break;
            default: sw = "other"; break;
        }

        var counts = new Dictionary<HkTestMode, int>();
        counts[HkTestMode.High] = 7;
        var list = new List<Nested> { Nested.Second };
        HkTestMode parsed = (HkTestMode)Enum.Parse(typeof(HkTestMode), "High");

        SuperController.LogMessage("enum test OK switch=" + sw + " toString=" + Mode + " cast=" + (int)Mode
            + " flags=" + (byte)_flags + " dict=" + counts[HkTestMode.High]
            + " listContains=" + list.Contains(Nested.Second) + " parse=" + parsed
            + " values=" + Enum.GetValues(typeof(HkTestMode)).Length);
    }
}

// Top-level enums go below the MVRScript class.
public enum HkTestMode { Off, Low, High }

public enum HkTestFlags : byte { None = 0, A = 1, B = 2, C = 4 }

It compiled and logged:

Code:
enum test OK switch=low toString=Low cast=1 flags=5 dict=7 listContains=True parse=High values=3

With the stock mcs.dll, the same plugin crashes VaM at compile.
 
Upvote 0
Back
Top Bottom