diff --git a/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj b/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj
index 85ff783..8fe1b6a 100644
--- a/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj
+++ b/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj
@@ -4,8 +4,8 @@
ModernUO.Serialization.Annotations
netstandard2.0
preview
- 4.1.0
- 4.1.0
+ 4.1.1
+ 4.1.1
ModernUO.Serialization.Annotations
ModernUO.Serialization
true
diff --git a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
index 7edd5a3..036f9eb 100644
--- a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
+++ b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
@@ -255,6 +255,55 @@ ImmutableArray CompileErrors
) RunGeneratorAllOutputs(
string sourceCode,
IEnumerable<(string fileName, string content)>? additionalTexts = null)
+ {
+ var (diagnostics, outputCompilation) = RunGeneratorOnCompilation("TestAssembly", sourceCode, additionalTexts);
+
+ var sources = outputCompilation.SyntaxTrees
+ .Where(st => st.FilePath.EndsWith(".Serialization.g.cs"))
+ .Select(st => (Path.GetFileName(st.FilePath), st.GetText().ToString()))
+ .ToImmutableArray();
+
+ var compileErrors = outputCompilation
+ .GetDiagnostics()
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .ToImmutableArray();
+
+ return (diagnostics, sources, compileErrors);
+ }
+
+ ///
+ /// Runs the generator, emits the output compilation and loads it, so tests can execute
+ /// generated code. Fails with every generator and compile error when the output does not build.
+ ///
+ public static Assembly CompileAndLoad(
+ string assemblyName,
+ string sourceCode,
+ IEnumerable<(string fileName, string content)>? additionalTexts = null)
+ {
+ var (diagnostics, outputCompilation) = RunGeneratorOnCompilation(assemblyName, sourceCode, additionalTexts);
+
+ using var stream = new MemoryStream();
+ var result = outputCompilation.Emit(stream);
+
+ var errors = diagnostics
+ .Concat(result.Diagnostics)
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .ToList();
+
+ if (!result.Success || errors.Count > 0)
+ {
+ throw new InvalidOperationException(
+ $"Generated code did not build:\n{string.Join("\n", errors.Take(20))}"
+ );
+ }
+
+ return Assembly.Load(stream.ToArray());
+ }
+
+ private static (ImmutableArray Diagnostics, Compilation OutputCompilation) RunGeneratorOnCompilation(
+ string assemblyName,
+ string sourceCode,
+ IEnumerable<(string fileName, string content)>? additionalTexts)
{
var syntaxTrees = new List
{
@@ -273,7 +322,7 @@ ImmutableArray CompileErrors
.ToList();
var compilation = CSharpCompilation.Create(
- "TestAssembly",
+ assemblyName,
syntaxTrees,
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
@@ -295,17 +344,7 @@ ImmutableArray CompileErrors
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
- var sources = outputCompilation.SyntaxTrees
- .Where(st => st.FilePath.EndsWith(".Serialization.g.cs"))
- .Select(st => (Path.GetFileName(st.FilePath), st.GetText().ToString()))
- .ToImmutableArray();
-
- var compileErrors = outputCompilation
- .GetDiagnostics()
- .Where(d => d.Severity == DiagnosticSeverity.Error)
- .ToImmutableArray();
-
- return (diagnostics, sources, compileErrors);
+ return (diagnostics, outputCompilation);
}
public static bool HasDiagnostic(ImmutableArray diagnostics, string diagnosticId)
diff --git a/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs b/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs
new file mode 100644
index 0000000..62a4372
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs
@@ -0,0 +1,280 @@
+using System.Text;
+using ModernUO.Serialization.Generator.Tests.Helpers;
+using Xunit;
+
+namespace ModernUO.Serialization.Generator.Tests;
+
+///
+/// A migration content struct reads a stream written by the live generator of an older version, so
+/// its save flag enums must be sized and partitioned exactly like the live SaveFlag enums were:
+/// int for up to 32 flags, ulong for 33-64, and several ulong enums past 64.
+///
+public class MigrationSaveFlagTests
+{
+ // Binary reader/writer that sizes enums by their underlying type, like ModernUO's, so a width
+ // mismatch between writer and content struct misaligns the stream instead of going unnoticed.
+ private const string BinaryStreams = """
+ namespace Server.TestContent
+ {
+ using System;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
+ public sealed class BinaryGenericWriter : Server.IGenericWriter
+ {
+ private readonly BinaryWriter _writer;
+ public BinaryGenericWriter(Stream stream) => _writer = new BinaryWriter(stream);
+ public void Flush() => _writer.Flush();
+
+ public void Write(string value)
+ {
+ _writer.Write(value != null);
+ if (value != null) _writer.Write(value);
+ }
+
+ public void Write(int value) => _writer.Write(value);
+ public void Write(uint value) => _writer.Write(value);
+ public void Write(long value) => _writer.Write(value);
+ public void Write(ulong value) => _writer.Write(value);
+ public void Write(short value) => _writer.Write(value);
+ public void Write(ushort value) => _writer.Write(value);
+ public void Write(byte value) => _writer.Write(value);
+ public void Write(sbyte value) => _writer.Write(value);
+ public void Write(bool value) => _writer.Write(value);
+ public void Write(float value) => _writer.Write(value);
+ public void Write(double value) => _writer.Write(value);
+ public void Write(decimal value) => _writer.Write(value);
+ public void Write(DateTime value) => throw new NotSupportedException();
+ public void WriteDeltaTime(DateTime value) => throw new NotSupportedException();
+ public void WriteAnchoredTime(DateTime value) => throw new NotSupportedException();
+ public void Write(TimeSpan value) => throw new NotSupportedException();
+ public void Write(Guid value) => throw new NotSupportedException();
+ public void WriteEncodedInt(int value) => _writer.Write7BitEncodedInt(value);
+ public void Write(T value) where T : struct, Enum => WriteEnum(value);
+
+ public void WriteEnum(T value) where T : struct, Enum
+ {
+ switch (Unsafe.SizeOf())
+ {
+ case 1: _writer.Write(Unsafe.As(ref value)); break;
+ case 2: _writer.Write(Unsafe.As(ref value)); break;
+ case 4: _writer.Write(Unsafe.As(ref value)); break;
+ default: _writer.Write(Unsafe.As(ref value)); break;
+ }
+ }
+
+ public void Write(Serial value) => throw new NotSupportedException();
+ public void Write(Point2D value) => throw new NotSupportedException();
+ public void Write(Point3D value) => throw new NotSupportedException();
+ public void Write(Rectangle2D value) => throw new NotSupportedException();
+ public void Write(Rectangle3D value) => throw new NotSupportedException();
+ }
+
+ public sealed class BinaryGenericReader : Server.IGenericReader
+ {
+ private readonly BinaryReader _reader;
+ public BinaryGenericReader(Stream stream) => _reader = new BinaryReader(stream);
+ public bool AtEnd => _reader.BaseStream.Position == _reader.BaseStream.Length;
+
+ public string ReadString(bool intern = false) => _reader.ReadBoolean() ? _reader.ReadString() : null;
+ public int ReadInt() => _reader.ReadInt32();
+ public uint ReadUInt() => _reader.ReadUInt32();
+ public long ReadLong() => _reader.ReadInt64();
+ public ulong ReadULong() => _reader.ReadUInt64();
+ public short ReadShort() => _reader.ReadInt16();
+ public ushort ReadUShort() => _reader.ReadUInt16();
+ public byte ReadByte() => _reader.ReadByte();
+ public sbyte ReadSByte() => _reader.ReadSByte();
+ public bool ReadBool() => _reader.ReadBoolean();
+ public float ReadFloat() => _reader.ReadSingle();
+ public double ReadDouble() => _reader.ReadDouble();
+ public decimal ReadDecimal() => _reader.ReadDecimal();
+ public DateTime ReadDateTime() => throw new NotSupportedException();
+ public DateTime ReadDeltaTime() => throw new NotSupportedException();
+ public DateTime ReadAnchoredTime() => throw new NotSupportedException();
+ public TimeSpan ReadTimeSpan() => throw new NotSupportedException();
+ public Guid ReadGuid() => throw new NotSupportedException();
+ public int ReadEncodedInt() => _reader.Read7BitEncodedInt();
+
+ public T ReadEnum() where T : struct, Enum
+ {
+ T value = default;
+ switch (Unsafe.SizeOf())
+ {
+ case 1: Unsafe.As(ref value) = _reader.ReadByte(); break;
+ case 2: Unsafe.As(ref value) = _reader.ReadUInt16(); break;
+ case 4: Unsafe.As(ref value) = _reader.ReadUInt32(); break;
+ default: Unsafe.As(ref value) = _reader.ReadUInt64(); break;
+ }
+
+ return value;
+ }
+
+ public Serial ReadSerial() => throw new NotSupportedException();
+ public Point2D ReadPoint2D() => throw new NotSupportedException();
+ public Point3D ReadPoint3D() => throw new NotSupportedException();
+ public Rectangle2D ReadRect2D() => throw new NotSupportedException();
+ public Rectangle3D ReadRect3D() => throw new NotSupportedException();
+ }
+ }
+ """;
+
+ // Field i holds i + 1 when present; 0 (the default) is skipped by its save flag.
+ private static bool IsPresent(int index, int flagCount) =>
+ index is 0 or 1 or 30 or 31 or 32 or 33 or 62 or 63 or 64 or 65 || index == flagCount - 1;
+
+ [Theory]
+ [InlineData(20)] // int SaveFlag
+ [InlineData(40)] // one ulong SaveFlag; bits 31 and 32 straddle the int boundary
+ [InlineData(70)] // SaveFlag + SaveFlag2; bits 63 and 64 straddle the enum boundary
+ public void MigrateFrom_ReadsEveryFieldWrittenByTheLiveGenerator(int flagCount)
+ {
+ var bytes = WriteV0(flagCount);
+ var migrated = ReadAsV1(flagCount, bytes);
+
+ var expected = new StringBuilder();
+ for (var i = 0; i < flagCount; i++)
+ {
+ expected.Append(IsPresent(i, flagCount) ? $"{i + 1}" : "null").Append(',');
+ }
+
+ expected.Append("True");
+
+ Assert.Equal($"tag|{expected}", migrated);
+ }
+
+ private static byte[] WriteV0(int flagCount)
+ {
+ var source = new StringBuilder();
+ source.AppendLine("using ModernUO.Serialization;");
+ source.AppendLine("namespace Server.TestContent {");
+ source.AppendLine("[SerializationGenerator(0)]");
+ source.AppendLine("public partial class WideFlagsItem : Server.ISerializable {");
+ AppendFields(source, flagCount);
+ AppendEntityMembers(source);
+
+ source.AppendLine("public static byte[] WriteSample() {");
+ source.AppendLine("var item = new WideFlagsItem { _tag = \"tag\" };");
+ for (var i = 0; i < flagCount; i++)
+ {
+ if (IsPresent(i, flagCount))
+ {
+ source.AppendLine($"item._f{i} = {i + 1};");
+ }
+ }
+
+ source.AppendLine("item._flag = true;");
+ source.AppendLine("var stream = new System.IO.MemoryStream();");
+ source.AppendLine("var writer = new BinaryGenericWriter(stream);");
+ source.AppendLine("item.Serialize(writer);");
+ source.AppendLine("writer.Flush();");
+ source.AppendLine("return stream.ToArray();");
+ source.AppendLine("}");
+ source.AppendLine("}");
+ source.AppendLine("}");
+
+ var assembly = SourceGeneratorTestHelper.CompileAndLoad(
+ $"WideFlagsV0_{flagCount}",
+ source + BinaryStreams
+ );
+
+ return (byte[])assembly.GetType("Server.TestContent.WideFlagsItem")!
+ .GetMethod("WriteSample")!
+ .Invoke(null, null)!;
+ }
+
+ private static string ReadAsV1(int flagCount, byte[] bytes)
+ {
+ var source = new StringBuilder();
+ source.AppendLine("using ModernUO.Serialization;");
+ source.AppendLine("namespace Server.TestContent {");
+ source.AppendLine("[SerializationGenerator(1)]");
+ source.AppendLine("public partial class WideFlagsItem : Server.ISerializable {");
+ source.AppendLine("[SerializableField(0)] private string _migrated;");
+ AppendEntityMembers(source);
+
+ source.AppendLine("private void MigrateFrom(V0Content content) {");
+ source.AppendLine("var sb = new System.Text.StringBuilder();");
+ source.AppendLine("sb.Append(content.Tag).Append('|');");
+ for (var i = 0; i < flagCount; i++)
+ {
+ source.AppendLine($"sb.Append(content.F{i}?.ToString() ?? \"null\").Append(',');");
+ }
+
+ source.AppendLine("sb.Append(content.Flag);");
+ source.AppendLine("_migrated = sb.ToString();");
+ source.AppendLine("}");
+
+ source.AppendLine("public static string ReadSample(byte[] bytes) {");
+ source.AppendLine("var reader = new BinaryGenericReader(new System.IO.MemoryStream(bytes));");
+ source.AppendLine("var item = new WideFlagsItem();");
+ source.AppendLine("item.Deserialize(reader);");
+ source.AppendLine("if (!reader.AtEnd) throw new System.InvalidOperationException(\"Unread bytes remain\");");
+ source.AppendLine("return item._migrated;");
+ source.AppendLine("}");
+ source.AppendLine("}");
+ source.AppendLine("}");
+
+ var assembly = SourceGeneratorTestHelper.CompileAndLoad(
+ $"WideFlagsV1_{flagCount}",
+ source + BinaryStreams,
+ [("Server.TestContent.WideFlagsItem.v0.json", MigrationJson(flagCount))]
+ );
+
+ return (string)assembly.GetType("Server.TestContent.WideFlagsItem")!
+ .GetMethod("ReadSample")!
+ .Invoke(null, [bytes])!;
+ }
+
+ // One unflagged string, flagCount flagged ints, then a flagged bool (stored in its bit only).
+ private static void AppendFields(StringBuilder source, int flagCount)
+ {
+ source.AppendLine("[SerializableField(0)] private string _tag;");
+ for (var i = 0; i < flagCount; i++)
+ {
+ source.AppendLine($"[SerializableField({i + 1})] [SaveFlag(nameof(ShouldSerializeF{i}))] private int _f{i};");
+ source.AppendLine($"private bool ShouldSerializeF{i}() => _f{i} != 0;");
+ }
+
+ source.AppendLine($"[SerializableField({flagCount + 1})] [SaveFlag(nameof(ShouldSerializeFlag))] private bool _flag;");
+ source.AppendLine("private bool ShouldSerializeFlag() => _flag;");
+ }
+
+ private static void AppendEntityMembers(StringBuilder source)
+ {
+ source.AppendLine("public WideFlagsItem() { }");
+ source.AppendLine("public System.DateTime Created { get; set; }");
+ source.AppendLine("public Server.Serial Serial { get; }");
+ source.AppendLine("public bool Deleted => false;");
+ source.AppendLine("public void Delete() { }");
+ }
+
+ private static string MigrationJson(int flagCount)
+ {
+ var properties = new List
+ {
+ """{ "name": "Tag", "type": "string", "rule": "PrimitiveTypeMigrationRule" }"""
+ };
+
+ for (var i = 0; i < flagCount; i++)
+ {
+ properties.Add(
+ $$"""{ "name": "F{{i}}", "type": "int", "usesSaveFlag": true, "rule": "PrimitiveTypeMigrationRule" }"""
+ );
+ }
+
+ properties.Add(
+ """{ "name": "Flag", "type": "bool", "usesSaveFlag": true, "rule": "PrimitiveTypeMigrationRule" }"""
+ );
+
+ return $$"""
+ {
+ "version": 0,
+ "type": "Server.TestContent.WideFlagsItem",
+ "properties": [
+ {{string.Join(",\n", properties)}}
+ ]
+ }
+ """;
+ }
+}
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs
new file mode 100644
index 0000000..1d0e701
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs
@@ -0,0 +1,823 @@
+//
+// This code was generated by the ModernUO Serialization Generator tool.
+// Version: {VERSION}
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+
+#pragma warning disable
+
+namespace Server.TestContent
+{
+ [System.CodeDom.Compiler.GeneratedCode("ModernUO.Serialization.Generator", "{VERSION}")]
+ public partial class WideMigratingItem
+ {
+ private const int SerializationVersion = 1;
+
+ public string Name
+ {
+ get => _name;
+ set
+ {
+ if (value != _name)
+ {
+ _name = value;
+ Server.ISerializableExtensions.MarkDirty(this);
+ }
+ }
+ }
+
+ public int Total
+ {
+ get => _total;
+ set
+ {
+ if (value != _total)
+ {
+ _total = value;
+ Server.ISerializableExtensions.MarkDirty(this);
+ }
+ }
+ }
+
+ public WideMigratingItem(Server.Serial serial)
+ {
+ Serial = serial;
+ }
+
+ ref struct V0Content
+ {
+ internal readonly string Name;
+ internal readonly int? Field0;
+ internal readonly int? Field1;
+ internal readonly int? Field2;
+ internal readonly int? Field3;
+ internal readonly int? Field4;
+ internal readonly int? Field5;
+ internal readonly int? Field6;
+ internal readonly int? Field7;
+ internal readonly int? Field8;
+ internal readonly int? Field9;
+ internal readonly int? Field10;
+ internal readonly int? Field11;
+ internal readonly int? Field12;
+ internal readonly int? Field13;
+ internal readonly int? Field14;
+ internal readonly int? Field15;
+ internal readonly int? Field16;
+ internal readonly int? Field17;
+ internal readonly int? Field18;
+ internal readonly int? Field19;
+ internal readonly int? Field20;
+ internal readonly int? Field21;
+ internal readonly int? Field22;
+ internal readonly int? Field23;
+ internal readonly int? Field24;
+ internal readonly int? Field25;
+ internal readonly int? Field26;
+ internal readonly int? Field27;
+ internal readonly int? Field28;
+ internal readonly int? Field29;
+ internal readonly int? Field30;
+ internal readonly int? Field31;
+ internal readonly int? Field32;
+ internal readonly int? Field33;
+ internal readonly int? Field34;
+ internal readonly int? Field35;
+ internal readonly int? Field36;
+ internal readonly int? Field37;
+ internal readonly int? Field38;
+ internal readonly int? Field39;
+ internal readonly int? Field40;
+ internal readonly int? Field41;
+ internal readonly int? Field42;
+ internal readonly int? Field43;
+ internal readonly int? Field44;
+ internal readonly int? Field45;
+ internal readonly int? Field46;
+ internal readonly int? Field47;
+ internal readonly int? Field48;
+ internal readonly int? Field49;
+ internal readonly int? Field50;
+ internal readonly int? Field51;
+ internal readonly int? Field52;
+ internal readonly int? Field53;
+ internal readonly int? Field54;
+ internal readonly int? Field55;
+ internal readonly int? Field56;
+ internal readonly int? Field57;
+ internal readonly int? Field58;
+ internal readonly int? Field59;
+ internal readonly int? Field60;
+ internal readonly int? Field61;
+ internal readonly int? Field62;
+ internal readonly int? Field63;
+ internal readonly int? Field64;
+ internal readonly int? Field65;
+
+ [System.Flags]
+ private enum V0SaveFlag : ulong
+ {
+ None = 0x0000000000000000,
+ Field0 = 0x0000000000000001,
+ Field1 = 0x0000000000000002,
+ Field2 = 0x0000000000000004,
+ Field3 = 0x0000000000000008,
+ Field4 = 0x0000000000000010,
+ Field5 = 0x0000000000000020,
+ Field6 = 0x0000000000000040,
+ Field7 = 0x0000000000000080,
+ Field8 = 0x0000000000000100,
+ Field9 = 0x0000000000000200,
+ Field10 = 0x0000000000000400,
+ Field11 = 0x0000000000000800,
+ Field12 = 0x0000000000001000,
+ Field13 = 0x0000000000002000,
+ Field14 = 0x0000000000004000,
+ Field15 = 0x0000000000008000,
+ Field16 = 0x0000000000010000,
+ Field17 = 0x0000000000020000,
+ Field18 = 0x0000000000040000,
+ Field19 = 0x0000000000080000,
+ Field20 = 0x0000000000100000,
+ Field21 = 0x0000000000200000,
+ Field22 = 0x0000000000400000,
+ Field23 = 0x0000000000800000,
+ Field24 = 0x0000000001000000,
+ Field25 = 0x0000000002000000,
+ Field26 = 0x0000000004000000,
+ Field27 = 0x0000000008000000,
+ Field28 = 0x0000000010000000,
+ Field29 = 0x0000000020000000,
+ Field30 = 0x0000000040000000,
+ Field31 = 0x0000000080000000,
+ Field32 = 0x0000000100000000,
+ Field33 = 0x0000000200000000,
+ Field34 = 0x0000000400000000,
+ Field35 = 0x0000000800000000,
+ Field36 = 0x0000001000000000,
+ Field37 = 0x0000002000000000,
+ Field38 = 0x0000004000000000,
+ Field39 = 0x0000008000000000,
+ Field40 = 0x0000010000000000,
+ Field41 = 0x0000020000000000,
+ Field42 = 0x0000040000000000,
+ Field43 = 0x0000080000000000,
+ Field44 = 0x0000100000000000,
+ Field45 = 0x0000200000000000,
+ Field46 = 0x0000400000000000,
+ Field47 = 0x0000800000000000,
+ Field48 = 0x0001000000000000,
+ Field49 = 0x0002000000000000,
+ Field50 = 0x0004000000000000,
+ Field51 = 0x0008000000000000,
+ Field52 = 0x0010000000000000,
+ Field53 = 0x0020000000000000,
+ Field54 = 0x0040000000000000,
+ Field55 = 0x0080000000000000,
+ Field56 = 0x0100000000000000,
+ Field57 = 0x0200000000000000,
+ Field58 = 0x0400000000000000,
+ Field59 = 0x0800000000000000,
+ Field60 = 0x1000000000000000,
+ Field61 = 0x2000000000000000,
+ Field62 = 0x4000000000000000,
+ Field63 = 0x8000000000000000,
+ }
+
+ [System.Flags]
+ private enum V0SaveFlag2 : ulong
+ {
+ None = 0x0000000000000000,
+ Field64 = 0x0000000000000001,
+ Field65 = 0x0000000000000002,
+ }
+ internal V0Content(Server.IGenericReader reader, Server.TestContent.WideMigratingItem entity)
+ {
+ var saveFlags = reader.ReadEnum();
+ var saveFlags2 = reader.ReadEnum();
+ Name = reader.ReadString();
+
+ if ((saveFlags & V0SaveFlag.Field0) != 0)
+ {
+ Field0 = reader.ReadInt();
+ }
+ else
+ {
+ Field0 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field1) != 0)
+ {
+ Field1 = reader.ReadInt();
+ }
+ else
+ {
+ Field1 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field2) != 0)
+ {
+ Field2 = reader.ReadInt();
+ }
+ else
+ {
+ Field2 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field3) != 0)
+ {
+ Field3 = reader.ReadInt();
+ }
+ else
+ {
+ Field3 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field4) != 0)
+ {
+ Field4 = reader.ReadInt();
+ }
+ else
+ {
+ Field4 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field5) != 0)
+ {
+ Field5 = reader.ReadInt();
+ }
+ else
+ {
+ Field5 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field6) != 0)
+ {
+ Field6 = reader.ReadInt();
+ }
+ else
+ {
+ Field6 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field7) != 0)
+ {
+ Field7 = reader.ReadInt();
+ }
+ else
+ {
+ Field7 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field8) != 0)
+ {
+ Field8 = reader.ReadInt();
+ }
+ else
+ {
+ Field8 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field9) != 0)
+ {
+ Field9 = reader.ReadInt();
+ }
+ else
+ {
+ Field9 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field10) != 0)
+ {
+ Field10 = reader.ReadInt();
+ }
+ else
+ {
+ Field10 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field11) != 0)
+ {
+ Field11 = reader.ReadInt();
+ }
+ else
+ {
+ Field11 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field12) != 0)
+ {
+ Field12 = reader.ReadInt();
+ }
+ else
+ {
+ Field12 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field13) != 0)
+ {
+ Field13 = reader.ReadInt();
+ }
+ else
+ {
+ Field13 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field14) != 0)
+ {
+ Field14 = reader.ReadInt();
+ }
+ else
+ {
+ Field14 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field15) != 0)
+ {
+ Field15 = reader.ReadInt();
+ }
+ else
+ {
+ Field15 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field16) != 0)
+ {
+ Field16 = reader.ReadInt();
+ }
+ else
+ {
+ Field16 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field17) != 0)
+ {
+ Field17 = reader.ReadInt();
+ }
+ else
+ {
+ Field17 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field18) != 0)
+ {
+ Field18 = reader.ReadInt();
+ }
+ else
+ {
+ Field18 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field19) != 0)
+ {
+ Field19 = reader.ReadInt();
+ }
+ else
+ {
+ Field19 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field20) != 0)
+ {
+ Field20 = reader.ReadInt();
+ }
+ else
+ {
+ Field20 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field21) != 0)
+ {
+ Field21 = reader.ReadInt();
+ }
+ else
+ {
+ Field21 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field22) != 0)
+ {
+ Field22 = reader.ReadInt();
+ }
+ else
+ {
+ Field22 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field23) != 0)
+ {
+ Field23 = reader.ReadInt();
+ }
+ else
+ {
+ Field23 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field24) != 0)
+ {
+ Field24 = reader.ReadInt();
+ }
+ else
+ {
+ Field24 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field25) != 0)
+ {
+ Field25 = reader.ReadInt();
+ }
+ else
+ {
+ Field25 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field26) != 0)
+ {
+ Field26 = reader.ReadInt();
+ }
+ else
+ {
+ Field26 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field27) != 0)
+ {
+ Field27 = reader.ReadInt();
+ }
+ else
+ {
+ Field27 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field28) != 0)
+ {
+ Field28 = reader.ReadInt();
+ }
+ else
+ {
+ Field28 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field29) != 0)
+ {
+ Field29 = reader.ReadInt();
+ }
+ else
+ {
+ Field29 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field30) != 0)
+ {
+ Field30 = reader.ReadInt();
+ }
+ else
+ {
+ Field30 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field31) != 0)
+ {
+ Field31 = reader.ReadInt();
+ }
+ else
+ {
+ Field31 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field32) != 0)
+ {
+ Field32 = reader.ReadInt();
+ }
+ else
+ {
+ Field32 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field33) != 0)
+ {
+ Field33 = reader.ReadInt();
+ }
+ else
+ {
+ Field33 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field34) != 0)
+ {
+ Field34 = reader.ReadInt();
+ }
+ else
+ {
+ Field34 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field35) != 0)
+ {
+ Field35 = reader.ReadInt();
+ }
+ else
+ {
+ Field35 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field36) != 0)
+ {
+ Field36 = reader.ReadInt();
+ }
+ else
+ {
+ Field36 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field37) != 0)
+ {
+ Field37 = reader.ReadInt();
+ }
+ else
+ {
+ Field37 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field38) != 0)
+ {
+ Field38 = reader.ReadInt();
+ }
+ else
+ {
+ Field38 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field39) != 0)
+ {
+ Field39 = reader.ReadInt();
+ }
+ else
+ {
+ Field39 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field40) != 0)
+ {
+ Field40 = reader.ReadInt();
+ }
+ else
+ {
+ Field40 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field41) != 0)
+ {
+ Field41 = reader.ReadInt();
+ }
+ else
+ {
+ Field41 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field42) != 0)
+ {
+ Field42 = reader.ReadInt();
+ }
+ else
+ {
+ Field42 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field43) != 0)
+ {
+ Field43 = reader.ReadInt();
+ }
+ else
+ {
+ Field43 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field44) != 0)
+ {
+ Field44 = reader.ReadInt();
+ }
+ else
+ {
+ Field44 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field45) != 0)
+ {
+ Field45 = reader.ReadInt();
+ }
+ else
+ {
+ Field45 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field46) != 0)
+ {
+ Field46 = reader.ReadInt();
+ }
+ else
+ {
+ Field46 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field47) != 0)
+ {
+ Field47 = reader.ReadInt();
+ }
+ else
+ {
+ Field47 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field48) != 0)
+ {
+ Field48 = reader.ReadInt();
+ }
+ else
+ {
+ Field48 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field49) != 0)
+ {
+ Field49 = reader.ReadInt();
+ }
+ else
+ {
+ Field49 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field50) != 0)
+ {
+ Field50 = reader.ReadInt();
+ }
+ else
+ {
+ Field50 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field51) != 0)
+ {
+ Field51 = reader.ReadInt();
+ }
+ else
+ {
+ Field51 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field52) != 0)
+ {
+ Field52 = reader.ReadInt();
+ }
+ else
+ {
+ Field52 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field53) != 0)
+ {
+ Field53 = reader.ReadInt();
+ }
+ else
+ {
+ Field53 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field54) != 0)
+ {
+ Field54 = reader.ReadInt();
+ }
+ else
+ {
+ Field54 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field55) != 0)
+ {
+ Field55 = reader.ReadInt();
+ }
+ else
+ {
+ Field55 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field56) != 0)
+ {
+ Field56 = reader.ReadInt();
+ }
+ else
+ {
+ Field56 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field57) != 0)
+ {
+ Field57 = reader.ReadInt();
+ }
+ else
+ {
+ Field57 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field58) != 0)
+ {
+ Field58 = reader.ReadInt();
+ }
+ else
+ {
+ Field58 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field59) != 0)
+ {
+ Field59 = reader.ReadInt();
+ }
+ else
+ {
+ Field59 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field60) != 0)
+ {
+ Field60 = reader.ReadInt();
+ }
+ else
+ {
+ Field60 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field61) != 0)
+ {
+ Field61 = reader.ReadInt();
+ }
+ else
+ {
+ Field61 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field62) != 0)
+ {
+ Field62 = reader.ReadInt();
+ }
+ else
+ {
+ Field62 = default;
+ }
+
+ if ((saveFlags & V0SaveFlag.Field63) != 0)
+ {
+ Field63 = reader.ReadInt();
+ }
+ else
+ {
+ Field63 = default;
+ }
+
+ if ((saveFlags2 & V0SaveFlag2.Field64) != 0)
+ {
+ Field64 = reader.ReadInt();
+ }
+ else
+ {
+ Field64 = default;
+ }
+
+ if ((saveFlags2 & V0SaveFlag2.Field65) != 0)
+ {
+ Field65 = reader.ReadInt();
+ }
+ else
+ {
+ Field65 = default;
+ }
+ }
+ }
+
+ public virtual void Serialize(Server.IGenericWriter writer)
+ {
+ writer.WriteEncodedInt(SerializationVersion);
+
+ writer.Write(_name);
+
+ writer.Write(_total);
+ }
+
+ public virtual void Deserialize(Server.IGenericReader reader)
+ {
+ var version = reader.ReadEncodedInt();
+
+ if (version == 0)
+ {
+ MigrateFrom(new V0Content(reader, this));
+ Server.ISerializableExtensions.MarkDirty(this);
+ return;
+ }
+
+ _name = reader.ReadString();
+
+ _total = reader.ReadInt();
+ }
+ }
+}
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs
new file mode 100644
index 0000000..3665fa6
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs
@@ -0,0 +1,93 @@
+using System;
+using ModernUO.Serialization;
+using Server;
+
+namespace Server.TestContent
+{
+ // v0 had 66 save-flagged fields: a ulong V0SaveFlag holding 64 and a V0SaveFlag2 holding the rest.
+ [SerializationGenerator(1)]
+ public partial class WideMigratingItem : ISerializable
+ {
+ [SerializableField(0)]
+ private string _name;
+
+ [SerializableField(1)]
+ private int _total;
+
+ public DateTime Created { get; set; }
+ public Serial Serial { get; }
+ public bool Deleted => false;
+ public void Delete() { }
+
+ private void MigrateFrom(V0Content content)
+ {
+ _name = content.Name;
+ _total += content.Field0 ?? 0;
+ _total += content.Field1 ?? 0;
+ _total += content.Field2 ?? 0;
+ _total += content.Field3 ?? 0;
+ _total += content.Field4 ?? 0;
+ _total += content.Field5 ?? 0;
+ _total += content.Field6 ?? 0;
+ _total += content.Field7 ?? 0;
+ _total += content.Field8 ?? 0;
+ _total += content.Field9 ?? 0;
+ _total += content.Field10 ?? 0;
+ _total += content.Field11 ?? 0;
+ _total += content.Field12 ?? 0;
+ _total += content.Field13 ?? 0;
+ _total += content.Field14 ?? 0;
+ _total += content.Field15 ?? 0;
+ _total += content.Field16 ?? 0;
+ _total += content.Field17 ?? 0;
+ _total += content.Field18 ?? 0;
+ _total += content.Field19 ?? 0;
+ _total += content.Field20 ?? 0;
+ _total += content.Field21 ?? 0;
+ _total += content.Field22 ?? 0;
+ _total += content.Field23 ?? 0;
+ _total += content.Field24 ?? 0;
+ _total += content.Field25 ?? 0;
+ _total += content.Field26 ?? 0;
+ _total += content.Field27 ?? 0;
+ _total += content.Field28 ?? 0;
+ _total += content.Field29 ?? 0;
+ _total += content.Field30 ?? 0;
+ _total += content.Field31 ?? 0;
+ _total += content.Field32 ?? 0;
+ _total += content.Field33 ?? 0;
+ _total += content.Field34 ?? 0;
+ _total += content.Field35 ?? 0;
+ _total += content.Field36 ?? 0;
+ _total += content.Field37 ?? 0;
+ _total += content.Field38 ?? 0;
+ _total += content.Field39 ?? 0;
+ _total += content.Field40 ?? 0;
+ _total += content.Field41 ?? 0;
+ _total += content.Field42 ?? 0;
+ _total += content.Field43 ?? 0;
+ _total += content.Field44 ?? 0;
+ _total += content.Field45 ?? 0;
+ _total += content.Field46 ?? 0;
+ _total += content.Field47 ?? 0;
+ _total += content.Field48 ?? 0;
+ _total += content.Field49 ?? 0;
+ _total += content.Field50 ?? 0;
+ _total += content.Field51 ?? 0;
+ _total += content.Field52 ?? 0;
+ _total += content.Field53 ?? 0;
+ _total += content.Field54 ?? 0;
+ _total += content.Field55 ?? 0;
+ _total += content.Field56 ?? 0;
+ _total += content.Field57 ?? 0;
+ _total += content.Field58 ?? 0;
+ _total += content.Field59 ?? 0;
+ _total += content.Field60 ?? 0;
+ _total += content.Field61 ?? 0;
+ _total += content.Field62 ?? 0;
+ _total += content.Field63 ?? 0;
+ _total += content.Field64 ?? 0;
+ _total += content.Field65 ?? 0;
+ }
+ }
+}
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json
new file mode 100644
index 0000000..4d546e4
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json
@@ -0,0 +1,407 @@
+{
+ "version": 0,
+ "type": "Server.TestContent.WideMigratingItem",
+ "properties": [
+ {
+ "name": "Name",
+ "type": "string",
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field0",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field1",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field2",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field3",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field4",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field5",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field6",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field7",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field8",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field9",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field10",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field11",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field12",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field13",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field14",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field15",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field16",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field17",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field18",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field19",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field20",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field21",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field22",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field23",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field24",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field25",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field26",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field27",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field28",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field29",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field30",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field31",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field32",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field33",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field34",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field35",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field36",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field37",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field38",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field39",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field40",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field41",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field42",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field43",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field44",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field45",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field46",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field47",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field48",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field49",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field50",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field51",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field52",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field53",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field54",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field55",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field56",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field57",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field58",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field59",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field60",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field61",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field62",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field63",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field64",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ },
+ {
+ "name": "Field65",
+ "type": "int",
+ "usesSaveFlag": true,
+ "rule": "PrimitiveTypeMigrationRule"
+ }
+ ]
+}
diff --git a/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj b/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj
index 186c390..f3f8ca2 100755
--- a/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj
+++ b/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj
@@ -4,8 +4,8 @@
ModernUO.Serialization.Generator
netstandard2.0
preview
- 4.1.0
- 4.1.0
+ 4.1.1
+ 4.1.1
ModernUO.Serialization.Generator
true
false
diff --git a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs
index 2cb27fb..f381280 100644
--- a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs
+++ b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs
@@ -42,52 +42,80 @@ string classDisplayString
var innerIndent = $"{indent} ";
- var usesSaveFlags = properties.Any(p => p.UsesSaveFlag == true);
-
- if (usesSaveFlags)
+ // The stream was written by the live generator of that version, so partition the flags
+ // exactly like the live SaveFlag enums: int up to 32 flags, ulong up to 64, then one more
+ // ulong enum per 64 flags. Readonly fields never reach the schema, so none are skipped here.
+ var saveFlagCount = properties.Count(p => p.UsesSaveFlag == true);
+ var saveFlagUseUlong = saveFlagCount > 32;
+ var saveFlagEnumCount = saveFlagCount == 0 ? 0 : saveFlagCount <= 64 ? 1 : (saveFlagCount + 63) / 64;
+ var bitsPerEnum = saveFlagUseUlong ? 64 : 32;
+
+ var flagIndex = 0;
+ foreach (var property in properties)
{
- source.AppendLine();
- source.GenerateEnumStart(
- $"V{migration.Version}SaveFlag",
- $"{indent} ",
- true,
- Accessibility.Private
- );
+ if (property.UsesSaveFlag != true)
+ {
+ continue;
+ }
- source.GenerateEnumValue(innerIndent, true, "None", -1);
- int index = 0;
- foreach (var property in properties)
+ var bitIndex = flagIndex % bitsPerEnum;
+ if (bitIndex == 0)
{
- if (property.UsesSaveFlag == true)
+ if (flagIndex > 0)
{
- source.GenerateEnumValue(innerIndent, true, property.Name, index++);
+ source.GenerateEnumEnd($"{indent} ");
}
+
+ source.AppendLine();
+ source.GenerateEnumStart(
+ GetContentSaveFlagEnumName(migration.Version, flagIndex / bitsPerEnum),
+ $"{indent} ",
+ true,
+ Accessibility.Private,
+ saveFlagUseUlong ? "ulong" : null
+ );
+
+ source.GenerateContentSaveFlagValue(innerIndent, saveFlagUseUlong, "None", -1);
}
+ source.GenerateContentSaveFlagValue(innerIndent, saveFlagUseUlong, property.Name, bitIndex);
+ flagIndex++;
+ }
+
+ if (flagIndex > 0)
+ {
source.GenerateEnumEnd($"{indent} ");
}
source.AppendLine($"{indent} internal V{migration.Version}Content(Server.IGenericReader reader, {classDisplayString} entity)");
source.AppendLine($"{indent} {{");
- if (usesSaveFlags)
+ // The writer emits every flag enum before any field.
+ for (var i = 0; i < saveFlagEnumCount; i++)
{
- source.AppendLine($"{innerIndent}var saveFlags = reader.ReadEnum();");
+ source.AppendLine(
+ $"{innerIndent}var {GetContentSaveFlagVariableName(i)} = reader.ReadEnum<{GetContentSaveFlagEnumName(migration.Version, i)}>();"
+ );
}
+ flagIndex = 0;
foreach (var property in properties)
{
if (property.UsesSaveFlag == true)
{
+ var enumIndex = flagIndex++ / bitsPerEnum;
+ var flagTest =
+ $"({GetContentSaveFlagVariableName(enumIndex)} & {GetContentSaveFlagEnumName(migration.Version, enumIndex)}.{property.Name}) != 0";
+
source.AppendLine();
// Special case
if (property.Type == "bool")
{
- source.AppendLine($"{innerIndent}{property.Name} = (saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0;");
+ source.AppendLine($"{innerIndent}{property.Name} = {flagTest};");
}
else
{
- source.AppendLine($"{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{");
+ source.AppendLine($"{innerIndent}if ({flagTest})\n{innerIndent}{{");
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
source,
@@ -118,4 +146,28 @@ string classDisplayString
source.AppendLine($"{indent}}}");
}
+
+ private static string GetContentSaveFlagEnumName(int version, int enumIndex) =>
+ enumIndex == 0 ? $"V{version}SaveFlag" : $"V{version}SaveFlag{enumIndex + 1}";
+
+ private static string GetContentSaveFlagVariableName(int enumIndex) =>
+ enumIndex == 0 ? "saveFlags" : $"saveFlags{enumIndex + 1}";
+
+ private static void GenerateContentSaveFlagValue(
+ this StringBuilder source,
+ string indent,
+ bool useUlong,
+ string name,
+ int bitIndex
+ )
+ {
+ if (useUlong)
+ {
+ source.GenerateEnumValueLong(indent, true, name, bitIndex);
+ }
+ else
+ {
+ source.GenerateEnumValue(indent, true, name, bitIndex);
+ }
+ }
}
diff --git a/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj b/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj
index 38e2dc6..5380973 100755
--- a/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj
+++ b/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj
@@ -6,8 +6,8 @@
x64;arm64
preview
Exe
- 4.1.0
- 4.1.0
+ 4.1.1
+ 4.1.1
true
ModernUOSchemaGenerator
true