From b54aa3631ba62020b2c7f3276525f3737d00cafb Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 18 May 2025 18:58:56 +0200 Subject: [PATCH 01/24] fix tests --- Datamodel.NET.csproj | 4 ++-- Datamodel.NET.sln | 2 ++ Tests/Tests.cs | 9 +++++++-- Tests/Tests.csproj | 1 - 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Datamodel.NET.csproj b/Datamodel.NET.csproj index b7c9184..c6fedb5 100644 --- a/Datamodel.NET.csproj +++ b/Datamodel.NET.csproj @@ -8,11 +8,11 @@ COPYING README.md false - false true true snupkg - IDE0018 + false + IDE0018 True diff --git a/Datamodel.NET.sln b/Datamodel.NET.sln index 77491dc..bfc772b 100644 --- a/Datamodel.NET.sln +++ b/Datamodel.NET.sln @@ -17,6 +17,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {075743A9-B292-410C-B68F-6E6CF588D60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {075743A9-B292-410C-B68F-6E6CF588D60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {075743A9-B292-410C-B68F-6E6CF588D60A}.Documentation|Any CPU.ActiveCfg = Release|Any CPU + {075743A9-B292-410C-B68F-6E6CF588D60A}.Documentation|Any CPU.Build.0 = Release|Any CPU {075743A9-B292-410C-B68F-6E6CF588D60A}.Release|Any CPU.ActiveCfg = Release|Any CPU {075743A9-B292-410C-B68F-6E6CF588D60A}.Release|Any CPU.Build.0 = Release|Any CPU {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 3eaa4d4..cb81df9 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -9,6 +9,7 @@ using System.Numerics; using DM = Datamodel.Datamodel; using System.Text; +using System.Globalization; namespace Datamodel_Tests { @@ -19,14 +20,18 @@ public class DatamodelTests protected FileStream Binary_4_File = File.OpenRead(TestContext.CurrentContext.TestDirectory + "/Resources/binary4.dmx"); protected FileStream KeyValues2_1_File = File.OpenRead(TestContext.CurrentContext.TestDirectory + "/Resources/taunt05.dmx"); - const string GameBin = @"C:/Program Files (x86)/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; - //const string GameBin = @"D:/Games/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; + // TODO: would be nice if this could find this path automatically + //const string GameBin = @"C:/Program Files (x86)/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; + const string GameBin = @"D:/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; static readonly string DmxConvertExe = Path.Combine(GameBin, "dmxconvert.exe"); static readonly bool DmxConvertExe_Exists = File.Exists(DmxConvertExe); static DatamodelTests() { + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; + var binary = new byte[16]; Random.Shared.NextBytes(binary); var quat = Quaternion.Normalize(new Quaternion(1, 2, 3, 4)); // dmxconvert will normalise this if I don't! diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index ac488e4..68c1b65 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -5,7 +5,6 @@ Datamodel_Tests Datamodel.NET-Tests false - false From f2c986cbc67ebf7281f95b1d7f4e276126f93d8f Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 18 May 2025 18:59:23 +0200 Subject: [PATCH 02/24] fix quaternion test failing due to culture mismatch --- Codecs/KeyValues2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 713dffc..ccf3de9 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -287,7 +287,7 @@ void WriteAttribute(string name, Type type, object value, bool in_array) else if (type == typeof(Quaternion)) { var q = (Quaternion)value; - value = string.Join(" ", q.X, q.Y, q.Z, q.W); + value = FormattableString.Invariant($"{q.X} {q.Y} {q.Z} {q.W}"); } else if (type == typeof(Matrix4x4)) { From 96c5a6baa190bc31217bde1d1d2acb5f186f4018 Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 18 May 2025 19:26:07 +0200 Subject: [PATCH 03/24] further make everything use invariant culture --- Codecs/KeyValues2.cs | 50 +++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index ccf3de9..75c4314 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -4,6 +4,8 @@ using System.Text; using System.Numerics; using System.IO; +using System.Runtime.CompilerServices; +using System.Globalization; namespace Datamodel.Codecs { @@ -254,55 +256,59 @@ void WriteAttribute(string name, Type type, object value, bool in_array) if (type == typeof(bool)) value = (bool)value ? 1 : 0; else if (type == typeof(float)) - value = (float)value; + value = FormattableString.Invariant($"{(float)value}"); else if (type == typeof(byte[])) - value = BitConverter.ToString((byte[])value).Replace("-", string.Empty); + value = BitConverter.ToString((byte[])value).Replace("-", string.Empty, false, CultureInfo.InvariantCulture); else if (type == typeof(TimeSpan)) - value = ((TimeSpan)value).TotalSeconds; + value = ((TimeSpan)value).TotalSeconds.ToString(CultureInfo.InvariantCulture); else if (type == typeof(Color)) { - var c = (Color)value; - value = string.Join(" ", new int[] { c.R, c.G, c.B, c.A }); + var castValue = (Color)value; + value = FormattableString.Invariant($"{castValue.R} {castValue.G} {castValue.B} {castValue.A}"); } else if (value is ulong ulong_value) - value = $"0x{ulong_value:X}"; + value = $"0x{ulong_value.ToString(CultureInfo.InvariantCulture):X}"; else if (type == typeof(Vector2)) { - var arr = new float[2]; - ((Vector2)value).CopyTo(arr); - value = string.Join(" ", arr); + var castValue = (Vector2)value; + value = FormattableString.Invariant($"{castValue.X} {castValue.Y}"); } else if (type == typeof(Vector3)) { - var arr = new float[3]; - ((Vector3)value).CopyTo(arr); - value = string.Join(" ", arr); + var castValue = (Vector3)value; + value = FormattableString.Invariant($"{castValue.X} {castValue.Y} {castValue.Z}"); } else if (type == typeof(Vector4)) { - var arr = new float[4]; - ((Vector4)value).CopyTo(arr); - value = string.Join(" ", arr); + var castValue = (Vector4)value; + value = FormattableString.Invariant($"{castValue.X} {castValue.Y} {castValue.Z} {castValue.W}"); } else if (type == typeof(Quaternion)) { - var q = (Quaternion)value; - value = FormattableString.Invariant($"{q.X} {q.Y} {q.Z} {q.W}"); + var castValue = (Quaternion)value; + value = FormattableString.Invariant($"{castValue.X} {castValue.Y} {castValue.Z} {castValue.W}"); } else if (type == typeof(Matrix4x4)) { - var m = (Matrix4x4)value; - value = string.Join(" ", m.M11, m.M12, m.M13, m.M14, m.M21, m.M22, m.M23, m.M24, m.M31, m.M32, m.M33, m.M34, m.M41, m.M42, m.M43, m.M44); + var castValue = (Matrix4x4)value; + var matrixString = + $"{castValue.M11} {castValue.M12} {castValue.M13} {castValue.M14}" + + $" {castValue.M21} {castValue.M22} {castValue.M23} {castValue.M24}" + + $" {castValue.M31} {castValue.M32} {castValue.M33} {castValue.M34}" + + $" {castValue.M41} {castValue.M42} {castValue.M43} {castValue.M44}"; + + value = FormattableString.Invariant(FormattableStringFactory.Create(matrixString)); } else if (value is QAngle qangle_value) { - value = string.Join(" ", (int)qangle_value.Pitch, (int)qangle_value.Yaw, (int)qangle_value.Roll); + var castValue = (QAngle)value; + value = FormattableString.Invariant($"{castValue.Pitch} {castValue.Yaw} {castValue.Roll}"); } if (in_array) - Writer.Write(String.Format(" \"{0}\",", value.ToString())); + Writer.Write(FormattableString.Invariant($" \"{value}\",")); else - Writer.WriteTokenLine(name, TypeNames[type], value.ToString()); + Writer.WriteTokenLine(name, TypeNames[type], FormattableString.Invariant($"{value}")); } } From 5c5ad6d465e66073713d240f73ef29f53d3621d3 Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 18 May 2025 20:43:21 +0200 Subject: [PATCH 04/24] also fix reading, fix all tests --- Codecs/KeyValues2.cs | 22 +++++++------- Tests/Tests.cs | 69 ++++++++++++++++++++++---------------------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 75c4314..4e92007 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -258,7 +258,7 @@ void WriteAttribute(string name, Type type, object value, bool in_array) else if (type == typeof(float)) value = FormattableString.Invariant($"{(float)value}"); else if (type == typeof(byte[])) - value = BitConverter.ToString((byte[])value).Replace("-", string.Empty, false, CultureInfo.InvariantCulture); + value = Convert.ToHexString((byte[])value).Replace("-", string.Empty, false, CultureInfo.InvariantCulture); else if (type == typeof(TimeSpan)) value = ((TimeSpan)value).TotalSeconds.ToString(CultureInfo.InvariantCulture); else if (type == typeof(Color)) @@ -267,7 +267,7 @@ void WriteAttribute(string name, Type type, object value, bool in_array) value = FormattableString.Invariant($"{castValue.R} {castValue.G} {castValue.B} {castValue.A}"); } else if (value is ulong ulong_value) - value = $"0x{ulong_value.ToString(CultureInfo.InvariantCulture):X}"; + value = $"0x{ulong_value.ToString("x", CultureInfo.InvariantCulture)}"; else if (type == typeof(Vector2)) { var castValue = (Vector2)value; @@ -554,35 +554,35 @@ object Decode_ParseValue(Type type, string value) if (type == typeof(Element)) return Decode_ParseElement(value); if (type == typeof(int)) - return int.Parse(value); + return int.Parse(value, CultureInfo.InvariantCulture); else if (type == typeof(float)) - return float.Parse(value); + return float.Parse(value, CultureInfo.InvariantCulture); else if (type == typeof(bool)) - return byte.Parse(value) == 1; + return byte.Parse(value, CultureInfo.InvariantCulture) == 1; else if (type == typeof(byte[])) { byte[] result = new byte[value.Length / 2]; for (int i = 0; i * 2 < value.Length; i++) { - result[i] = byte.Parse(value.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); + result[i] = byte.Parse(value.AsSpan(i * 2, 2), System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); } return result; } else if (type == typeof(TimeSpan)) - return TimeSpan.FromTicks((long)(double.Parse(value) * TimeSpan.TicksPerSecond)); + return TimeSpan.FromTicks((long)(double.Parse(value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond)); var num_list = value.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); if (type == typeof(Color)) { - var rgba = num_list.Select(i => byte.Parse(i)).ToArray(); + var rgba = num_list.Select(i => byte.Parse(i, CultureInfo.InvariantCulture)).ToArray(); return Color.FromBytes(rgba); } - if (type == typeof(ulong)) return ulong.Parse(value.Remove(0, 2), System.Globalization.NumberStyles.HexNumber); - if (type == typeof(byte)) return byte.Parse(value); + if (type == typeof(ulong)) return ulong.Parse(value.Remove(0, 2), System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); + if (type == typeof(byte)) return byte.Parse(value, CultureInfo.InvariantCulture); - var f_list = num_list.Select(i => float.Parse(i)).ToArray(); + var f_list = num_list.Select(i => float.Parse(i, CultureInfo.InvariantCulture)).ToArray(); if (type == typeof(Vector2)) return new Vector2(f_list[0], f_list[1]); else if (type == typeof(Vector3)) return new Vector3(f_list[0], f_list[1], f_list[2]); else if (type == typeof(Vector4)) return new Vector4(f_list[0], f_list[1], f_list[2], f_list[3]); diff --git a/Tests/Tests.cs b/Tests/Tests.cs index cb81df9..a648650 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -56,7 +56,7 @@ static DatamodelTests() TestValues_V3 = TestValues_V1.Concat(new object[] { (byte)0xFF, - (ulong)0xFFFFFFFF, + (UInt64)0xFFFFFFFF, //new QAngle(0, 90, 180) }).ToList(); } @@ -224,6 +224,39 @@ protected static void ValidatePopulated(string encoding_name, int encoding_versi dm.Dispose(); } + protected static DM Create(string encoding, int version, bool memory_save = false) + { + var dm = MakeDatamodel(); + Populate(dm, encoding, version); + + dm.Root["Arr"] = new System.Collections.ObjectModel.ObservableCollection(); + dm.Root.GetArray("Arr"); + + if (memory_save) + dm.Save(new MemoryStream(), encoding, version); + else + { + dm.Save(DmxSavePath, encoding, version); + if (SaveAndConvert(dm, encoding, version)) + { + ValidatePopulated(encoding, version); + } + Cleanup(); + } + + dm.AllElements.Remove(dm.Root.GetArray("ElemArray")[3], DM.ElementList.RemoveMode.MakeStubs); + Assert.AreEqual(true, dm.Root.GetArray("ElemArray")[3].Stub); + + dm.AllElements.Remove(dm.Root, DM.ElementList.RemoveMode.MakeStubs); + Assert.AreEqual(true, dm.Root.Stub); + + return dm; + } + } + + [TestFixture] + public class Functionality : DatamodelTests + { [Test] public static void TypedArrayAddingRemoving() { @@ -257,43 +290,11 @@ public static void TypedArrayAddingRemoving() array.Remove(elementA); array.Remove(elementB); - + Assert.AreEqual(0, array.Count); } - protected static DM Create(string encoding, int version, bool memory_save = false) - { - var dm = MakeDatamodel(); - Populate(dm, encoding, version); - dm.Root["Arr"] = new System.Collections.ObjectModel.ObservableCollection(); - dm.Root.GetArray("Arr"); - - if (memory_save) - dm.Save(new MemoryStream(), encoding, version); - else - { - dm.Save(DmxSavePath, encoding, version); - if (SaveAndConvert(dm, encoding, version)) - { - ValidatePopulated(encoding, version); - } - Cleanup(); - } - - dm.AllElements.Remove(dm.Root.GetArray("ElemArray")[3], DM.ElementList.RemoveMode.MakeStubs); - Assert.AreEqual(true, dm.Root.GetArray("ElemArray")[3].Stub); - - dm.AllElements.Remove(dm.Root, DM.ElementList.RemoveMode.MakeStubs); - Assert.AreEqual(true, dm.Root.Stub); - - return dm; - } - } - - [TestFixture] - public class Functionality : DatamodelTests - { [Test] public void Create_Datamodel_Vmap() { From 85216570367a7ff01ba99e092f5d24df4e7c509d Mon Sep 17 00:00:00 2001 From: Angel Date: Sun, 18 May 2025 20:43:21 +0200 Subject: [PATCH 05/24] reflection based deserialisation, binary only for now --- AttributeList.cs | 15 +- Codecs/Binary.cs | 71 +- Codecs/KeyValues2.cs | 3 +- Datamodel.NET.csproj | 7 +- Datamodel.NET.sln | 11 +- Datamodel.cs | 17 +- Element.cs | 3 +- Format/Attribute.cs | 57 +- ICodec.cs | 3 +- Tests/Resources/cs2_map.vmap.txt | 2 +- Tests/Resources/reflectiontest.vmap | Bin 0 -> 54850 bytes Tests/Resources/reflectiontest.vmap.txt | 2669 +++++++++++++++++++++++ Tests/Resources/vmaptest1.dmx | 21 - Tests/Tests.cs | 48 +- Tests/Tests.csproj | 6 + Tests/vmap.cs | 370 ++++ 16 files changed, 3184 insertions(+), 119 deletions(-) create mode 100644 Tests/Resources/reflectiontest.vmap create mode 100644 Tests/Resources/reflectiontest.vmap.txt delete mode 100644 Tests/Resources/vmaptest1.dmx create mode 100644 Tests/vmap.cs diff --git a/AttributeList.cs b/AttributeList.cs index 28f5bdb..f2fa1ec 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -9,6 +9,7 @@ using AttrKVP = System.Collections.Generic.KeyValuePair; using System.Reflection; +using System.IO; namespace Datamodel { @@ -243,11 +244,21 @@ public virtual object this[string name] throw new AttributeTypeException("Elements are not supported as prefix attributes."); var prop_attr = (PropertyInfo)PropertyInfos[name]; + if (prop_attr != null) { - throw new InvalidOperationException($"Cannot set the value of a property-derived attribute by key. Assign to '{prop_attr.Name}' directly instead."); - } + PropertyInfo prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance); + if (null != prop && prop.CanWrite) + { + prop.SetValue(this, value); + } + else + { + throw new InvalidDataException("Property of deserialisation class must be writeable, make sure it's public and has a public setter"); + } + } + Attribute old_attr; Attribute new_attr; int old_index = -1; diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index ae6e071..d706cfc 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -5,6 +5,8 @@ using System.Numerics; using System.IO; using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; namespace Datamodel.Codecs { @@ -344,8 +346,15 @@ object ReadValue(Datamodel dm, Type type, bool raw_string) throw new ArgumentException(type == null ? "No type provided to GetValue()" : "Cannot read value of type " + type.Name); } - public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode) + public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection) { + Dictionary callingTypes = new(); + + foreach (var classType in callingAssembly.DefinedTypes) + { + callingTypes.Add(classType.Name, classType); + } + stream.Seek(0, SeekOrigin.Begin); while (true) { @@ -383,7 +392,39 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in var id_bits = Reader.ReadBytes(16); var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray()); - var elem = new Element(dm, name, id, type); + Element? elem = null; + var matchedType = callingTypes.TryGetValue(type, out var classType); + + if(matchedType) + { + var isElementDerived = IsElementDerived(classType); + if (isElementDerived && classType.Name == type) + { + Type derivedType = classType; + + ConstructorInfo? constructor = typeof(Element).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + new Type[] { typeof(Datamodel), typeof(string), typeof(Guid), typeof(string) }, + null + ); + + if (constructor == null) + { + throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); + } + + object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); + constructor.Invoke(uninitializedObject, new object[] { dm, name, id, type }); + + elem = (Element?)uninitializedObject; + } + } + + if (elem == null) + { + elem = new Element(dm, name, id, type); + } } // read attributes (or not, if we're deferred) @@ -396,8 +437,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in foreach (var i in Enumerable.Range(0, num_attrs)) { var name = StringDict.ReadString(); - - if (defer_mode == DeferredMode.Automatic) + if (defer_mode == DeferredMode.Automatic && attemptReflection == false) { CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); SkipAttribute(); @@ -408,6 +448,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in } } } + return dm; } @@ -762,6 +803,28 @@ void WriteAttribute(object value, bool in_array) } } + bool IsElementDerived(Type type) + { + var elementType = typeof(Element); + + while (type.BaseType != elementType) + { + var baseType = type.BaseType; + + if (baseType != null) + { + type = baseType; + } + else + { + return type == elementType ? true : false; + } + + } + + return type.BaseType == elementType ? true : false; + } + class DmxBinaryWriter : BinaryWriter { public DmxBinaryWriter(Stream output) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 4e92007..d01cfaf 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Globalization; +using System.Reflection; namespace Datamodel.Codecs { @@ -597,7 +598,7 @@ object Decode_ParseValue(Type type, string value) else throw new ArgumentException($"Internal error: ParseValue passed unsupported type: {type}."); } - public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode) + public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection) { DM = new Datamodel(format, format_version); diff --git a/Datamodel.NET.csproj b/Datamodel.NET.csproj index c6fedb5..032fdd5 100644 --- a/Datamodel.NET.csproj +++ b/Datamodel.NET.csproj @@ -19,7 +19,7 @@ sgKey.snk - + bin\Documentation\ @@ -30,10 +30,11 @@ false + true - - + + diff --git a/Datamodel.NET.sln b/Datamodel.NET.sln index bfc772b..d415882 100644 --- a/Datamodel.NET.sln +++ b/Datamodel.NET.sln @@ -6,7 +6,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datamodel.NET", "Datamodel. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{4C928D60-5E48-4C0D-9C7E-C75D9734CD58}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DmxPad", "DmxPad\DmxPad.csproj", "{B49BF0E9-46DB-4BE3-9890-02FF5C098C7C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "..\ConsoleApp1\ConsoleApp1.csproj", "{B4982001-13F5-4CD3-953E-F438FC42699E}" + ProjectSection(ProjectDependencies) = postProject + {075743A9-B292-410C-B68F-6E6CF588D60A} = {075743A9-B292-410C-B68F-6E6CF588D60A} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +30,12 @@ Global {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Documentation|Any CPU.Build.0 = Debug|Any CPU {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Release|Any CPU.Build.0 = Release|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Documentation|Any CPU.ActiveCfg = Release|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Documentation|Any CPU.Build.0 = Release|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4982001-13F5-4CD3-953E-F438FC42699E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Datamodel.cs b/Datamodel.cs index 82d235e..e179448 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -9,6 +9,7 @@ using System.Security; using System.Numerics; using CodecRegistration = System.Tuple; +using System.Reflection; namespace Datamodel { @@ -231,18 +232,18 @@ public void Save(string path, string encoding, int encoding_version) /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic) + public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) { - return Load_Internal(stream, defer_mode); + return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection); } /// /// Loads a Datamodel from a byte array. /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic) + public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) { - return Load_Internal(new MemoryStream(data, true), defer_mode); + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, attemptReflection); } /// @@ -250,13 +251,13 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode /// /// The source file path. /// How to handle deferred loading. - public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic) + public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) { var stream = File.OpenRead(path); Datamodel dm = null; try { - dm = Load_Internal(stream, defer_mode); + dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection); return dm; } finally @@ -265,7 +266,7 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode } } - private static Datamodel Load_Internal(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic) + private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) { stream.Seek(0, SeekOrigin.Begin); var header = string.Empty; @@ -292,7 +293,7 @@ private static Datamodel Load_Internal(Stream stream, DeferredMode defer_mode = ICodec codec = GetCodec(encoding, encoding_version); - var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode); + var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode, callingAssembly, attemptReflection); if (defer_mode == DeferredMode.Automatic && codec is IDeferredAttributeCodec deferredCodec) { dm.Stream = stream; diff --git a/Element.cs b/Element.cs index 2f61919..f91eab1 100644 --- a/Element.cs +++ b/Element.cs @@ -161,6 +161,7 @@ internal set #region Properties + // TODO: this could probably be sped up by caching the properties somehow protected override ICollection<(string Name, PropertyInfo Property)> GetPropertyDerivedAttributeList() { var type = GetType(); @@ -176,8 +177,6 @@ internal set if (property.GetIndexParameters().Length == 0 && property.DeclaringType.IsSubclassOf(typeof(Element))) { var name = property.Name; - name = property.DeclaringType.GetCustomAttribute()? - .GetAttributeName(name, property.PropertyType) ?? name; name = property.GetCustomAttribute()?.Name ?? name; properties.Add((name, property)); diff --git a/Format/Attribute.cs b/Format/Attribute.cs index 87a1802..eae8549 100644 --- a/Format/Attribute.cs +++ b/Format/Attribute.cs @@ -3,67 +3,12 @@ namespace Datamodel.Format; -/// -/// Subclass this attribute to define a custom attribute name convention. -/// -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public abstract class AttributeNamingConventionAttribute : System.Attribute -{ - public abstract string GetAttributeName(string propertyName, Type propertyType); -} - -/// -/// This class' property names are mostly lowercase. -/// -public class LowercasePropertiesAttribute : AttributeNamingConventionAttribute -{ - public override string GetAttributeName(string propertyName, Type _) - => propertyName.ToLower(); -} - -/// -/// This class' property names are mostly camelCase. -/// -public class CamelCasePropertiesAttribute : AttributeNamingConventionAttribute -{ - public override string GetAttributeName(string propertyName, Type _) - => char.ToLowerInvariant(propertyName.AsSpan()[0]) + propertyName[1..]; -} - -/// -/// This class' property names are mostly m_hungarian. -/// -public class HungarianPropertiesAttribute : CamelCasePropertiesAttribute -{ - public override string GetAttributeName(string propertyName, Type propertyType) - { - var typeAnnotation = propertyType switch - { - _ when propertyType == typeof(int) => "n", - _ when propertyType == typeof(float) => "fl", - _ when propertyType == typeof(bool) => "b", - _ when propertyType == typeof(Vector2) => "v", - _ when propertyType == typeof(Vector3) => "v", - _ when propertyType == typeof(Vector4) => "v", - _ when propertyType == typeof(Matrix4x4) => "mat", - _ => string.Empty, - }; - - if (typeAnnotation == string.Empty) - { - return "m_" + base.GetAttributeName(propertyName, propertyType); - } - - return "m_" + typeAnnotation + propertyName; - } -} - [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public sealed class DMProperty : System.Attribute { /// The name to use for serialization. /// Ignore serialization if property is on the default value. - public DMProperty(string name = null, bool optional = false) + public DMProperty(string name = "", bool optional = false) { Name = name; Optional = optional; diff --git a/ICodec.cs b/ICodec.cs index 44bb3aa..ad6b06d 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -2,6 +2,7 @@ using System.Linq; using System.IO; using System.Numerics; +using System.Reflection; namespace Datamodel.Codecs { @@ -29,7 +30,7 @@ public interface ICodec /// The input stream. Its position will always be 0. Do not dispose. /// The deferred loading mode specified by the caller. Only relevant to implementers of /// - Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode); + Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection); } /// diff --git a/Tests/Resources/cs2_map.vmap.txt b/Tests/Resources/cs2_map.vmap.txt index 1947ed5..12006f1 100644 --- a/Tests/Resources/cs2_map.vmap.txt +++ b/Tests/Resources/cs2_map.vmap.txt @@ -91,7 +91,7 @@ "nextDecalID" "int" "0" "fixupEntityNames" "bool" "1" - "mapUsageType" "string" "standard" + "mapUsageType" "string" "hellyea" "origin" "vector3" "0 0 0" "angles" "qangle" "0 0 0" "scales" "vector3" "1 1 1" diff --git a/Tests/Resources/reflectiontest.vmap b/Tests/Resources/reflectiontest.vmap new file mode 100644 index 0000000000000000000000000000000000000000..26801a23799def0a2ea0959ad230857351d14571 GIT binary patch literal 54850 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 Xg$KHYwztHFF6zqe={&x&TWI?q-1a?# literal 0 HcmV?d00001 diff --git a/Tests/Resources/reflectiontest.vmap.txt b/Tests/Resources/reflectiontest.vmap.txt new file mode 100644 index 0000000..3d6a3fe --- /dev/null +++ b/Tests/Resources/reflectiontest.vmap.txt @@ -0,0 +1,2669 @@ + +"$prefix_element$" +{ + "id" "elementid" "d5547f30-69f9-4f12-96c1-f45e95deb8fc" + "asset_preview_thumbnail" "binary" + " + FFD8FFE000104A46494600010101006000600000FFDB004300030202030202030303030403030405 + 0805050404050A070706080C0A0C0C0B0A0B0B0D0E12100D0E110E0B0B1016101113141515150C0F + 171816141812141514FFDB00430103040405040509050509140D0B0D141414141414141414141414 + 1414141414141414141414141414141414141414141414141414141414141414141414141414FFC0 + 0011080200020003012200021101031101FFC4001F00000105010101010101000000000000000001 + 02030405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221 + 31410613516107227114328191A1082342B1C11552D1F02433627282090A161718191A2526272829 + 2A3435363738393A434445464748494A535455565758595A636465666768696A737475767778797A + 838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6 + C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F010003 + 0101010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407 + 05040400010277000102031104052131061241510761711322328108144291A1B1C109233352F015 + 6272D10A162434E125F11718191A262728292A35363738393A434445464748494A53545556575859 + 5A636465666768696A737475767778797A82838485868788898A92939495969798999AA2A3A4A5A6 + A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EA + F2F3F4F5F6F7F8F9FAFFDA000C03010002110311003F00FCAAA28A2800A28A2800A28A2800A28A28 + 00A28A2800A28A2800A28A2800A28A2803B4F1E7FC4DFC35E0CD7D7F7B24DA7B6977B71F7775CDA3 + 9448F6F1F72CDEC065460E7A97DF5C5D769A57FC4EFE13EBB647F7D73A1EA106A9027DDF26DA606D + EEE4CF01B74A34D5C1C91D5401E61AE2EB7ABAB52EEBFE03FC51851D138766FF00CD7E0D05145158 + 1B851451400514514005145140051451400514514005145140051451400514514005145140051451 + 40051451400514514005145140051451400514514005145140051451400514514005145140051451 + 40051451400514514005145140051451400514514005145140051451400514514005145140051451 + 401DA7C26FF4FF0012DD680DFBC8FC41A7DC696B6FD3ED172C9BECA3DDFC39BC8ED4E72071F31D85 + AB8BABBA26B379E1CD6AC356D3A6FB3EA161711DD5B4DB55B64A8C191B0C0838201C10456D7C4ED1 + ACF41F1F6B76DA643E468CF706EB4C5DCCD9B19809AD5B2C4B730C919C37CC3386C3022B77EF524F + B3FCF6FC9FDE60BDDAAD775F96FF009AFB8E628A28AC0DC28A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A002BB4F1E7FC4DFC35E0CD7D7F7B24DA7B6 + 977B71F7775CDA39448F6F1F72CDEC065460E7A97DF5C5D769A57FC4EFE13EBB647F7D73A1EA106A + 9027DDF26DA606DEEE4CF01B74A34D5C1C91D5401E61ADE9EAA51EEBF2D7F2B9855D1C67D9FE7A7E + 76FB8E2E8A28AC0DC28A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0 + 028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0 + 028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0 + 028A28A0028A28A002BB4F84DFE9FE25BAD01BF791F8834FB8D2D6DFA7DA2E5937D947BBF873791D + A9CE40E3E63B0B571757744D66F3C39AD586ADA74DF67D42C2E23BAB69B6AB6C9518323618107040 + 38208AD29C9426A4F632AB173838ADFF005E852A2BAEF8B9E1AB6F08FC4BF11E99611C51E94978F2 + E9C20B813C6D6727EF2D99640CDB8342F1B0249383CF39AE4694E0E9C9C1EEB42A135520A71D9EA1 + 451454161451450014515B7E0EF03EBFF10B5B8B48F0DE9175ACEA3260F936B196D8A59577B9E888 + 0B282EC428C8C9153292827293B22652515CD276462515DF7C54F815E35F8357C62F1368B2DBD9B4 + 9E5C3A9C1FBDB39CE5C2ED94700B08D98236D7DBC9515C0D4D3A90AB153A6EE9F544D3A90AB15383 + BAF20A28A2B4340A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A + 28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A + 28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2803BBF899711EB5A3F81 + F5C8EDA28E5B9D0E3B1BC9ADCB98DAE2D2492D955B2C42C82DA3B366518E2456DA038CF095DA5A7F + C4E7E0FDF41F7A7D07588EEE28E2E5BC9BB8BCBB89641C9D88F6B6481860069F0725D31C5D6F5B59 + 29F74BFC9FE299CF47DD8B8766FF00CD7E0D051451581D0145145007D09FB1CFECF7A4FC73F14EB3 + 3F886494E85A24703C96D6D3F9525C4B239288C769FDD948A50DB595B25769EA47E8EF83BC0FA07C + 3DD122D23C37A45AE8DA74783E4DAC6177B0555DEE7ABB90AA0BB12C70324D7CE9FF0004F2F04FF6 + 1FC22D4FC433D979175AEEA2DE55CF9BBBCFB5846C4F94310BB6537239018F7C8DB5F53D7E539E62 + E75B173A7CDEE474B74BADFF001B9F99E738A9D6C54E9F37BB1D2DD3CFF120BFB0B6D52C6E2CAF6D + E2BBB3B98DA19ADE740F1CA8C30CACA7820824107820D7C95FB467EC3DE1CD4BC3DAC7897C056B2E + 91AEDB46F76DA35B8692DAF00DEEE9146016491B2022A7C9F22A845DDB87D794579585C656C1CD4E + 8CADE5D1FA9E661B175B09353A52B79747EA7E21515EB5FB53FC3093E14FC6BD7B4E4862874CBF90 + EA9A72C11A451ADBCCCC422A293B446E1E200E3223C800115E4B5FB2D1AB1AF4E3561B3573F5AA35 + 635A9C6A4366AE1451456C6A14514500145145001451450014514500145145001451450014514500 + 14514500145145001451450014514500145145001451450014514500145145001451450014514500 + 1451450014514500145145001451450014514500145145001451450014514500769F0B3FE261A96B + 7A037DCD7347BAB4554FF5B24D1A8BAB68A2F5792E2DA08F6E09612155C33291C5D69F85BC4373E1 + 1F13E91AED9A4525DE99790DEC2938263678DC3A860082465467041C77157BE21787ADBC29E39D77 + 49B17966D36D6F245B1B898866B8B52C4C136E00060F19470CA36B060470456EFDEA49F67F9EDFA9 + 82F76AB5DD5FEED1FE873D45145606E145145007E997EC1BE31D375FF807A7E8D6B2FF00C4C741B8 + 9EDEF20765DC3CC99E68DC2824EC612150481968E4033B735F45D7E58FEC83F18FFE1517C5DB2FB7 + 5D791E1DD6B1A7EA3E649B628F71FDD4ED97541B1F1976CED8DE5C0C9AFD54B73E5933119D98C03D + DBB7F53F857E4F9E611E171729749EABE7BFE27E619CE15E1B15297496ABE7BFE232541190BFC407 + CDEC7D3FCF7CD329492C492724F249A4AF9F3C33E0BFF82935E692FE29F04DA42B10D762B2B996E9 + 843890DBB3A08017C7CC37A5C61727692C703773F1B57B47ED85E3E93C7DF1FBC4AE1E5367A449FD + 8D6D1CD1A2346202564036F506633302493871D31B478BD7EC795D1743054A12DED7FBF5FD4FD672 + DA4E8E0E9C1EF6BFDFAFEA1451457AA7A41451450014514500145145001457B158FEC85F16753B2B + 7BCB3F0BC57769711ACB0DC41AB593C72A30CAB2B09B041041047041A9FF00E18D3E30FF00D0A1FF + 00953B3FFE3D5DBF52C53FF9752FFC05FF0091C3F5FC22D1D68FFE04BFCCF16A2BDA7FE18D3E30FF + 00D0A1FF00953B3FFE3D47FC31A7C61FFA143FF2A767FF00C7A8FA8E2BFE7D4BFF00017FE42FAFE0 + FF00E7F47FF025FE678B515ED3FF000C69F187FE850FFCA9D9FF00F1EA3FE18D3E30FF00D0A1FF00 + 953B3FFE3D47D4715FF3EA5FF80BFF0020FAFE0FFE7F47FF00025FE678B515D0F8EBE1FF00887E19 + EBEFA2F89B4B974AD496359445232BABA37465752558704654900AB03C82073D5C928CA0DC64ACD1 + DB194671528BBA614514549414514500145145001451450014514500145145001451450014514500 + 14514500145145001451450014514500145145001451450014514500145145001451450014514500 + 15DA7C45FF0089A69BE10D7C7CEF7DA3C56972D1F314735A335AAC43D1FECF15A48CA49399C370AE + A07175DA5A7FC4E7E0FDF41F7A7D07588EEE28E2E5BC9BB8BCBB89641C9D88F6B6481860069F0725 + D31BD3D54A3E5F96BF95CC2A6928CFCEDF7E9F9D8E2E8A28AC0DC28A28A009F4FD3EEB56BFB6B1B1 + B69AF2F6E655860B6B78CC924B23101515472CC490001C926BEC1BDFDB37C63F043C6117833529ED + 3C7F6BE1EB75D3354BB94BC12DCDEC664F35A394A061B19D202D22BF982D7CC1B4CAC6BE73F87FFF + 00148E83AB78DE5FDDDDDB674DD0F3C137F229DF709D0FFA3425A40E8DBA29E4B26C10D5C1D79B5F + 0D4B1B2E5AD1BC63F9BF3DF45DBBF91E7D6C3D2C64B96AC6F15F9BF3DF4FEB63F51FC39FB6D7C23D + 7F467BF9FC432E8B2C519965B1D46CE513A0F304600F2C3AB93B95B6C6CC429248015B6FCF5F16FF + 00E0A1BAD6B1E658FC3DD33FB02D4E3FE26BA9A24D76DF70FCB17CD1C7C8914EEF3372B02361AF8F + 68AF3A864182A33E769CBB27B2FF003F9DCE0A3926128CF9DA72F27B2FF3F9DC9EFEFEE754BEB8BD + BDB896EEF2E6469A6B89DCBC92BB1CB3331E49249249E4935051457D1EDA23DFD828A28A0028A28A + 0028A28A0028A28A00FD07FD853E2F7FC25FE019FC1FA8DCF99ABF87F1F66F364CBCB64C7E4C6E72 + CDE5B650E142AA98477AFA7ABF257E087C50B9F83FF12F47F1244656B48A4F2AFEDE22499ED5F891 + 76EE50C40F99431DBBD109E95F667FC3C27E1D7FD017C51FF80B6DFF00C915FA06579AD1FAB28622 + 769474D7AAE9FE47E739B6515DE25D4C3C2F196BA747D7FCCFA7A8AF987FE1E13F0EBFE80BE28FFC + 05B6FF00E48A3FE1E13F0EBFE80BE28FFC05B6FF00E48AF5FF00B5305FF3F51E37F64E3BFE7D33E9 + EA2BE61FF8784FC3AFFA02F8A3FF00016DBFF9228FF8784FC3AFFA02F8A3FF00016DBFF9228FED4C + 17FCFD41FD938EFF009F4CF5CF8D9F04F42F8E1E136D27565FB35EC3BA4D3F548D034B67291D474D + C8D80190901801C860ACBF987F123E1BEBBF0A7C5975E1EF10DAFD9AF61F9924425A2B88893B6589 + B0372360F3C10410406040FB83FE1E13F0EBFE80BE28FF00C05B6FFE48AF3DF8D9FB4EFC1DF8E1E1 + 36D2756D0FC516D7B0EE934FD523B2B6696CE523A8FF00481B91B0032120300390C1597E7B34581C + 6C7DA53A894D7E3E4FF467D1E52F30C0CBD955A4DD37F879AFD57F4FE3AA28A2BE24FBC0A28A2800 + A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800 + A28A2800A28A2800A28A2800A28A2800A28A2800A28AF7DF815FB1678EBE3FF83EEBC4DA35FE89A3 + 69304ED00935B96E2269B1B4178C470C9B93736DDC38DCAC0F22B2A9561463CD51D97F9E8BEF34A7 + 4E755F2C15D9E055DA7C2CFF008986A5ADE80DF735CD1EEAD1553FD6C9346A2EADA28BD5E4B8B682 + 3DB8258485570CCA47D13FF0EC9F889FF439781FFF0002AFFF00F90EB53C2DFF0004EAF899E11F13 + E91AED9F8BFC0525DE97790DEC293DC6A063678DC3A8602D01232A33820E3B8A2962E8C669B6EDD7 + 47B75E85D5C0E22706941DFA7AF4FC4F8BA8AFB1F53FF8267F8CAE353BC9347F17F84E3D25A67368 + 9A8DD5E7DA561DC7609765A15F302E036D38CE71C556FF008764FC44FF00A1CBC0FF00F8157FFF00 + C87533AF4E127093D5793FF22A186AD522A7183B3D4F906AFF0087F42BEF146BDA6E8DA641F69D4B + 51B98ECED60DEA9E64B230445DCC4019620649007735F57FFC3B27E227FD0E5E07FF00C0ABFF00FE + 43AE8FC21FF04F1F1E784ED75E9D7C53E107D66F2C1F4FB0B91737BE4DBA4E0C774E49B5CB3180C9 + 08428462E19C3A34481B9EA6329C63EEBD5E8B47BFF5ABF233AB42BD34AD0D5E8BD7FAD5F9267CB5 + F163C6D6FE20B9D1FC3BA34BBFC27E16B66D334B646982DDFEF19E6BE31C8C4C6F7123190A00362F + 969CF9609E0EBEBEFF0087647C44FF00A1CBC0FF00F8157FFF00C875E27FB45FECE9E27FD98BC776 + DE14F15DCE9979A85CD847A945369333CB0989DE441CBA23060D13820AF61D735A509D2495383FBF + AF77EBD599BC3CB0C9424ADBEFD7BBFBDEBEA796D14515D620A28A2800A28A2800A28A2800A28A28 + 00A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A28 + 00A28A2800A28A2800A28A2800A28AED7C35F05BC6FE2DB5373A6F872EDEDCA248B35C6DB74915C1 + 2AC8642A1C103395CF51EA2A6528C15E4EC6F46855C44B928C1C9F649BFC8E2A8A28AA300A28A280 + 0A28A2800A28A2800A28A2800A28A2800A28A2800A28AF72FD96BF659D6FF68EF12CAE5A5D27C1BA + 6306D5B5ADA3E5EE20873C34CC3A0E8A0EE6EC1A2738D38B9CDD917084AA49462B5675BFB1D7EC7D + 3FC73D47FE128F1579BA67C3CD3E41E6329D936A92027F710F70B9043C9DB040F9B257F4F7C3A965 + A6DDE9BA7D95B5BE95A2C082C20B48008E2B5B62A63DA8070A1558E3DF939E739B61656DA469565A + 569F04567A7594421B7B6B78C471C6806005518000000FA01536315E756C23C652A91ACEDCE9C57F + 75356FBFFE19756FB2A548D1FDD51775D5FF0035BF4EDF7BF275CDAC967712C12AEC962728EB9CE0 + 8382322A3DB815A7AE0F36E22BCFF9FC884CC4F52F92B2123B65D5C803B11D3A0CF0BEF5B60AB4B1 + 186855A9A49AF797692D24BE4EE8E292B368AF00DA654F46C8CFA1E7F993F954C062A3384BC5E47E + F108E7D8F6FCCD4D8E6BD5ACAED4FBABFE8FF14CE5A3A270ECEDFAAFC1A11137301D09A573B9B23A + 0E07D29FF713B6E6FD0537B579F0FDE4DCFA2D17EAFF004F93E8C23EFCDCBA2D17EAFF004F93E8CD + 9F0769F6D79ABB5C5F3AA69F6113DEDC6E05B31A7246DC1CF38C8C7233DEBE01FF00829EDFCBAFD9 + 7C30D6EE00FB5DE4BAC2CC413F795AD480324E142BAA819EDF9FDE3E32BA8F40F87D6B60AEC97FAD + 4A2E278F68E2DE32760209C805B0C18019C30CF1CFC6FF00B7969ED7FF00B35D85C45034B2D8789E + DDA57452DB217B6B85DCD8FBAA2428B93D4CAA3AE33CF3F7AA42AF452B7E0D7E6FF0476E0BF79EDE + 5FDDB2F94937F93FB91F9D3451457A26414514500145145001451450014514500145145001451450 + 01451450014514500145145001451450014514500145145001451450014514500145145001451450 + 014515F4E7C14FD97FFE3C3C43E314F5962D0A48FE9B0CE49FA93163FBBB8FDE4AC2AD68518F34CF + 532FCBB1199D5F65417ABE8979FF005732FF0067AFD9EBFB77ECDE29F14DB7FC4AF892C74E997FE3 + EBB896407FE59F70A7EFF53F2E37FD614515F315AB4ABCB9A47EE796E5B432BA0A8D15AF57D5BFEB + 65D0FCC7A28A2BEB8FE780A28A2800A28A2800A28A2800A28A2800A28A2800A28A2803D5FF0066AF + 805AAFED09F122D742B582F57468079FAAEA1670EF36B0807032780CEC022F5C64B60856AFD7CF0E + 7812DFC0FE14D33C2DE1AD025D27C3FA6C7E5DBDAC3030DCDFC5239392CEC724B124927AD7E6DFFC + 13A3E274DE12F8BF7BE14F3CD9DBF8A6D4A0BA8F7068E6B749254CB29C852865180092C63E8326BF + 435BC45AC23E0EAB7C08E08FB4BFF8D714A3ED2B6BF6755F3EBEBD3CBE67AD1F674F0B196BEF369B + 5E56D3F1BF9FC8E8FF00B1351FF9F0BAFF00BF2DFE140D1351FF009F0B9FFBF2DFE15CD9F126AE47 + FC856FBF0B97FF001A51E24D5CFF00CC56F7FF00021FFC6BA7DF38BF71E7F81DA3E9BA85CE83144F + 6D73BAD673B11A3624AC8B93818E0298FF0037EDDF3FFB1350FF009F1B9FFBF2DFE159FE1ED7B53B + ABC7B17D4AF1DAEE268622D3B9612F58F6927825D5549F466E9D466FFC247AB649FED5BDFF00C097 + FF001AF33091542AD5C3415ACF9EDFE36DBFBE6A6DDFBF6B23497B069377FC3A1B57BA3DFC4B1486 + CEE176CAA3E685B0771DB8FF00C7AA73A45E465049693C7B9B682D130C9F41C75F6AC05D5F55D437 + 40FA9DDB46EA43AB4EE4153C1C8CF3F4A9E6D6B545B385CEA578CCC43EE33B653E5E3073EE73F857 + BB568626A61154A6B66D5FB2D35F3B6A734B0ADA9D4A51959ADF4D2DA49FC938F95FE66D9D1B502C + 4FD82E71D8792DFE157346F0C5F6A5AB5A5B4967731C3248048E50AED4FE239231D335C90F11EADF + F414BDFF00C087FF001AD3F0CF882F5F5BB54BAD46EA581D8A949667752482178CFA915CF4693938 + D24D25A2F4E9F804A34234DC69DD34B4DBE473BF10BC431789BC557B776D95B15C416A809DAB120D + ABB410368382DB71C6E35E39FB49E973EBDFB317C4BB1B687CFBA4B4B5BD8D4100ED82EA2966393C + 6044B2311D4EC0064E01F46BBB792CEE258265D92C4C51D739C1070471F4AA5ABE8B71E29F06F8CB + C3B69B4DD6B5E1FD434D843E42F9B2C0EB1962012143ED62403C29E0F4AE5C443D9D1B7F2D9FDCD3 + FD0E8CBE2A35234E3D535F7C5AFEBB9F8EF45145761C614514500145145001451450014514500145 + 14500145145001451450014514500145145001451450014514500145145001451450014514500145 + 14500145145007BF7EC93F0F6DB5FF0010EA1E23D42D7CF834AD8966258898CDC373BC36705A30A3 + 8C1C1915B8214D7D755E63FB37F867FE11AF847A3F996DF66BAD437DFCDFBCDDE66F3FBB7EA40CC4 + 22E063DC6735E9D5F2B8AA9ED2AC9F45A1FBE64183583CBE9C6D6949733F57AEBE8ACBE414514572 + 1F427E63D14515F6A7F3105145140051451400514514005145140051451400514514017743D6AF7C + 37AD69FAB69B39B6D46C2E23BAB69C28631CA8C191B0410704038208AFD92D23C456BE34F0E68BE2 + 5D3D0C165AD58C3A843038F9A159103046E4F20119E4FD4D7E3057E84FFC13DFE24C7E22F85FACF8 + 1AE24DFA968172DA85B06C022CA52372A807242CC5CB315E0CF18DDC8039AAFB928D4EDA3F47FF00 + 06C7A385FDED3A987EEAEBD637FCD5D7AD8FA81738E4E4D28C8CE4D30139E9C7D69771C8E3EB5D47 + 9E58B4BB9AC6F21BA81F64D0BAC88D8070C0E41E78EA2B7B5CF0ECD3EAF772DB3585BDBC92B48904 + 979044D12B1CF96C8CE0AB2E76918E0822A86936F1DBDA5C6A771E5B2C3FBBB782504F9F311C6382 + 084C8760783F2A91F3D66CB3C97123CB2BB4B2B92CCEED92C4F2493DCD7CF4E357118D75708D4791 + 38C9B8B69B7695959C6FCBDEED272696AA56D348C6D2EA6D5BE817B6F2075974F3D883A95BF23FEF + E532FBC2EF7F2798F1692643F79DAFED598F181CEFF4AC65638E4734AA49CF15E8DF33F67EC5D687 + 2FFD7B7FFCB343B2963AB51A6E9539349F67E9D6D75B742F0F0411D63D20FF00DBEDAFFF0017525B + F834C1711C9E4E8D20460DB1EF2D4AB60F4237F22B379CF4E3D6979C8C5732C3E393BFB4A7FF0082 + DFFF002C27EB95BF9E5FF8132DF89FC0AB16BB762CD74D8ADCBEF549AF6DA265C8048D8CC0800938 + E3A62AB68BE14363AA4171730693776D192D2C02FED5CC8B8390143939C7A027D39AB1E38433FF00 + 64DF28DCB7166A8D31EAF2A121F3DC91F28C9EB5C4F8AFC52DF0F7E1F78C3C62AE90CBA1E9535CDA + CD37118BB2A45B2B1C8C06976280082C485182722F32A1898CEA72CE0A0DE8B91DED2D95F9F7D52B + DBCEC75E031759D7A6E539593BBF79ECB57F823F31BC6FE3AF1DF837C69AFF0087FF00E13FF10DEF + F656A17161F695D4E75137952326F03CC38CEDCE327AF5AC4FF85BDE3BFF00A1D7C45FF8359FFF00 + 8BAE4A8AF47D9C3B2385E33117D2A4BEF675BFF0B7BC77FF0043AF88BFF06B3FFF001747FC2DEF1D + FF00D0EBE22FFC1ACFFF00C5D725451ECE1D90BEB789FF009F92FBD9D6FF00C2DEF1DFFD0EBE22FF + 00C1ACFF00FC5D1FF0B7BC77FF0043AF88BFF06B3FFF00175C95147B387641F5BC4FFCFC97DECEB7 + FE16F78EFF00E875F117FE0D67FF00E2E8FF0085BDE3BFFA1D7C45FF008359FF00F8BAE4A8A3D9C3 + B20FADE27FE7E4BEF675BFF0B7BC77FF0043AF88BFF06B3FFF001747FC2DEF1DFF00D0EBE22FFC1A + CFFF00C5D725451ECE1D907D6F13FF003F25F7B3ADFF0085BDE3BFFA1D7C45FF008359FF00F8BA3F + E16F78EFFE875F117FE0D67FFE2EB92A28F670EC83EB789FF9F92FBD9D6FFC2DEF1DFF00D0EBE22F + FC1ACFFF00C5D1FF000B7BC77FF43AF88BFF0006B3FF00F175C95147B387641F5BC4FF00CFC97DEC + EB7FE16F78EFFE875F117FE0D67FFE2E8FF85BDE3BFF00A1D7C45FF8359FFF008BAE4A8A3D9C3B20 + FADE27FE7E4BEF675BFF000B7BC77FF43AF88BFF0006B3FF00F1747FC2DEF1DFFD0EBE22FF00C1AC + FF00FC5D725451ECE1D907D6F13FF3F25F7B3ADFF85BDE3BFF00A1D7C45FF8359FFF008BA3FE16F7 + 8EFF00E875F117FE0D67FF00E2EB92A28F670EC83EB789FF009F92FBD9D6FF00C2DEF1DFFD0EBE22 + FF00C1ACFF00FC5D1FF0B7BC77FF0043AF88BFF06B3FFF00175C95147B387641F5BC4FFCFC97DECE + B7FE16F78EFF00E875F117FE0D67FF00E2E8FF0085BDE3BFFA1D7C45FF008359FF00F8BAE4A8A3D9 + C3B20FADE27FE7E4BEF675BFF0B7BC77FF0043AF88BFF06B3FFF001747FC2DEF1DFF00D0EBE22FFC + 1ACFFF00C5D725451ECE1D907D6F13FF003F25F7B3ADFF0085BDE3BFFA1D7C45FF008359FF00F8BA + 3FE16F78EFFE875F117FE0D67FFE2EB92A28F670EC83EB789FF9F92FBD9D6FFC2DEF1DFF00D0EBE2 + 2FFC1ACFFF00C5D1FF000B7BC77FF43AF88BFF0006B3FF00F175C95147B387641F5BC4FF00CFC97D + ECEB7FE16F78EFFE875F117FE0D67FFE2E8FF85BDE3BFF00A1D7C45FF8359FFF008BAE4A8A3D9C3B + 20FADE27FE7E4BEF675BFF000B7BC77FF43AF88BFF0006B3FF00F175B1E0EF1D7C42F18F8AB49D12 + DFC6DE2657BEB9480C91EA371218D49F9A4DA1C642AE58F23853C8EB5E755EA7FB336A561A6FC60D + 20DFC51B19D2582DA7927F2C4133210A7D1CB0DD1853DE40472056756318425251D523BB035AB623 + 154A954AAD294927ABEACFBAFEDB71FF003DE5FF00BECD1F6DB8FF009EF2FF00DF66A1A2BE44FE86 + BB26FB6DC7FCF797FEFB3513BB48C59D8B31EA58E4D25140AED9F98F451457DB1FCC214514500145 + 1450014514500145145001457B0F84FF00643F8B9E37F0E69FAF68FE0E9A7D2EFE3135B4D3DEDB5B + B49193C384924560A7A82461810464104EB7FC30D7C6EFFA127FF2AD63FF00C7E803C228AF77FF00 + 861AF8DDFF00424FFE55AC7FF8FD1FF0C33F1BCFFCC93FF956B1FF00E3F401E115EB7FB2AFC4F3F0 + A3E37F877529AE12DB48BE9974CD51E565445B599D433B330E046C125EA33E5E09009ADCFF008619 + F8DFFF00424FFE55AC7FF8FD2FFC30C7C6FF00FA127FF2AD63FF00C7EA651538B8BEA6B4AA4A8CE3 + 521BA773F4D2FA06B4B878D9194820852307046475F6228B5B596FA758604DF236703200000C9249 + E00001249E00049E2A8FC3BD0BC63A8FC28F0CEA3E2DD39ACFC52B09B5D560F3A19713212AB26F89 + B67EF54799818DA58AF3B735D7C5A35EE8BA33EEB302FEFC611DDB0D0C1DD979C7EF395F50AA7B3D + 7975B173A54A308ABD593E54BCFABDD7BA92727ADDA565AB49F4E221055A4E9FC1BAF47AA5EAB6F5 + 3375CBF867962B5B35516368A628D9370F38FF0014CC0F467C038EC02AFF000D662B647208ABA343 + BEC9FDC7FE3EBFE34BFD897D9FF51FF8FAFF008D76E1B0F1C2D18D1836D2EAF56DEEDB7D5B7ABF36 + 71B7CCEE5204E4F14B9E7A7E3574E8B7C3FE587FE3EBFE34CB8D32EAD6232C90ED45C64EE07DBD6B + A492B16E4606694B71C0CD309C0E84FD29C0F1D2803435E5FB578374D9F3B3EC97325BEDEBBF78DF + BBDB18C62BE52FDBDFC5EBE1BF817A4787A2B8F26FBC47AA09648C2EEF36CEDD4B3A9E30B898DBB7 + 2431E31C6ECFD676486F7C29AD5B8532CAA619A288F2570F87603B71D4FA75AFCDAFF8283F8C935E + F8E306836F2486D7C35A5C162C99FDCB4CF999E5419EACB2C4AC480731E31855A317EFCE8F9ABBFF + 00B77DDFF23BF0DFBBA356AFA457FDBDABFC135F33E63A28AEF3E16FC0AF1C7C6AFED3FF00843344 + FED9FECCF2BED7FE97041E5F99BF67FAD75CE7CB7E99C639EA28380E0E8AF79FF8616F8E1FF424FF + 00E55AC7FF008FD1FF000C2BF1C3FE848FFCAB58FF00F1FA00F06A2BDE7FE185BE381FF9927FF2AD + 63FF00C7E83FB0AFC701D7C11FF956B1FF00E3F401E0D457BCFF00C30AFC70FF00A123FF002AD63F + FC7E8FF8616F8E1FF424FF00E55AC7FF008FD00783515EF3FF000C2DF1C3FE849FFCAB58FF00F1FA + 3FE185BE387FD091FF00956B1FFE3F401E0D457BCFFC30B7C70FFA127FF2AD63FF00C7E8FF008616 + F8E1FF004247FE55AC7FF8FD00783515EF3FF0C2DF1C0FFCC93FF956B1FF00E3F5E0D40051451400 + 514514005145140051451400514514005145140051451400514514005145140054B6B75358DD4373 + 6D3496F710B89229A262AE8C0E43291C8208C822A2A281A6D3BA3F46BC0BE2DB6F1DF8474BD7AD17 + 6457B08768F24F96E09574C9033B5C32E7001C64706B7ABE4FFD957E2C693E168752F0D6B777069B + 05C4DF6CB5BB9CEC8F7EC0B223B9385E110AE40190C3392A0FD39A278A746F12F9DFD91ABD86ABE4 + EDF37EC572937979CE376D2719C1C67D0D7C9E228BA536ADA1FD0194E654F30C2D3A8E4B9DAD575B + ADF4FC7D0D4A28A74513CD22A229666E0015CC7BA7E62D14515F6A7F308514514005145140051451 + 40057D67FB0EFECB9FF0B2F5D5F1AF8BB47F3FC1965BBEC30DC9C47A8DD2B01CA107CC8530FBB90A + CE157E70245193FB217EC8575F1AEFE2F1478A229ACFC076D210AA098E4D56453831C6472B102087 + 907390510EEDCD1FE9D58585A691636D63636D0D9D9DB44B0C16D6F1848E28D400A8AA0615400000 + 38005004DC6719C9A50A00F4A785A0609C0ED400C0037BD38E00E4D3BA500E466801028EBDE8F949 + C7E94A4802940A00D9F0DDED9C7F68B2D418258CFB5CB1DD8574391F7413C82CBC74DD9ED52DD69F + A65F5CBCEFAFDA02E72156DA6DAA3B28F978006001E80560F19C77A7002B8DE160EBFD6136A56B7C + BD1DED7B2BF7B2EC69CFA72B46B9D1B4B1FF003305B7FE03CDFF00C4528D134B1FF31FB61FF6EF37 + FF001158E30C78A52001935B7B397F3BFC3FC85CCBB7E66B1D174A271FF0905B13E9F679BFF88AC2 + F11E97A24CD1DBBF8AED21319DE53EC93B64F6E89C77FCEA59244862795CE11416271D00AF3BBBB8 + 7BB9E49E43977393FE03DA97B397F3BFC3FC839976FCFF00CCF36FF86B2F81809FF8B9F667DC68DA + A7FF0022D03F6B4F81BFF453ECFF00F04DA9FF00F22D7E7A7ED2FE1CFF00845BE3CF8D6CBED1F69F + 36FDAFB7ECD98FB428B8DB8C9FBBE6EDCF7DB9C0CE0799D47B19FF00CFD97FE4BFFC89D3EDE9FF00 + CF98FDF2FF00E48FD69D0FF6CCF811A2CB752CDF11E0BA8A4B7789A28348D49646CF3852D6B8C9C6 + 06481CF35F959E2FF13DEF8DBC59AD788B51D9FDA1ABDECFA85CF96085F36591A47C64938CB1EA49 + F7AC9A2B78C5A56949CADDEDFA246552AF3A518C5457657DFBEAD857EA3FFC13A3C3175A0FECEAB7 + D71242F16B7ABDD5FDB88989648D425B90F9030DBEDDCF19182BCE7207E5C57ECEFECCBA0D87873F + 67BF8776BA7C1F67B79343B5BC64DECD99678C4D2B65893F34923B63A0CE00000156607A4E14B638 + CD2E0538609341000A006285C64631436DEF8A78C119A18818FF000A006E052617776CD498A4C8DD + 8A0069031CD22ED238A93029AB839A006B05182714BB4529C019E6803233400CC00D8E86BF1B7F6A + 5F035AFC38FDA07C6DA15898459477DF6A822B780411C11CE8B70B0AA024058C4A10630084CE0670 + 3F6509031EF5F9FBFF00053FF04791AF7827C5F14576FF0069B69B49B9976E6DA2F2DBCD857217E5 + 77F3AE0E09E447C01B58900F86A8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A + 0028A28A0028A28A0028A28A0028A28A00E8B42F88BE29F0CC76B1697E21D4ACADED5F7C56D1DD3F + 90A776EFF579DA41249208C1C9C839AD0F16FC62F1978E34F163ACEBD3DCD973BADE24482393956F + 9D63550F828A46ECE08C8C64D71B4567ECE17E6B2B9D6B178854DD25525CAFA5DDBEED828A28AD0E + 40A28A2800A28A2800AF71FD97BF65ED63F686F12196533697E0EB0942EA3AAAA8DCCD807ECF0646 + 1A520824E08404336728AF83FB3A7C07D63E3E7C41B4D22CEDE65D0EDA58E5D67514611ADADB16F9 + 8072AC3CD701822E0E48C91B55C8FD78F09F84F47F01F86F4FD0341B0874BD1EC2310DBDAC23E541 + 9C9249E59892496249624924924D0027853C27A3780FC39A7E81A0D843A5E9163188ADAD60076A8E + A4927259892496249624924924D6C014B8A0E077E6801370CE3BD28A70141C0E33CD0034609C0ED4 + EC014A168E338E33400D077723A538E14649A750304F5A0068008CD292171DB34EC0A1483D318F6A + 004A4C8DD8CF352718C938A41CF4A00C0F16DE2C56A96CADF3CA7730E3EE8FFEBE3F235C830E2AFE + AF79FDA1A84B36EDC99DA879FBA3A75FCFF1AA0DDE803E16FF0082867877ECBE33F096BDF68DDF6E + D3E5B1FB3ECC6CF224DFBB7679DDF69C63031B3A9CF1F2657E8CFEDC7E1CFEDCF80D757BF68F23FB + 1EFEDAFB66CDDE7658DBEDCE46DFF5FBB3CFDDC639C8FCE6A0028A28A00D7F07F862EBC6DE2DD13C + 3B6324315EEAF7D069F04970C446B24B22A2962012141619C0271D8D7EEAE057E3FF00EC59A0D8F8 + 8FF69FF01DA6A307DA2DE3B99AF1537B2E2582DE59A26CA907E592346C7438C104122BF608E00A00 + 62E1B914AD85049A78C119E2918803B500340045236062A4CD0580C72298C660521C0603BD494848 + C8E473400DC0A418248CF4A938A4C8DD8E29086E0535486E41E2A5C0A68C124679A006B600CE6BC2 + FF006D5F8771FC45FD9CFC50812137BA2C5FDB76B2CF23A2C6D6E0B4A46DFBCC6033A00C08CB8CE3 + 0187BBE3DEAB5FD85A6B16173637B6F0DED95CC6D04F6D708248E58D810C8EA7865209041E083401 + F835456FFC40F0AFFC20BE3DF12786BED5F6EFEC6D4EE74EFB5797E5F9DE4CAD1EFDB93B73B738C9 + C67A9AC0A0028A28A0028A28A0028A28A0028A28A0028ABFFF0008FEA9FD83FDB9FD9B77FD8BF69F + B17F69790DF66F3F6EFF002BCCC6DDFB7E6DB9CE39C62A8500145145001451450014514500145145 + 001451450014514500145145001451450015E89F01BE096B1F1F7E215BF863489E1B35111BABDBE9 + F056D6D95955E409905DB2EAA10752C32557732E47C2CF859E22F8C9E33B3F0CF866CFED57F3FCF2 + 48E4AC36D102034D2B60ED45C8C9C12490AA0B32A9FD6CF807F00BC3DFB3F7831746D193ED57F3ED + 9352D5A540B35ECA01E4F5DA8B92123048504F2599998037BE167C2BF0EFC1BF0659F867C3367F65 + B0B7F9E496421A6B994801A695801B9DB0327000002A80AAA075B807A81414DC475E3DE9C1698C36 + D205CF38A528188393C7BD3B148426DA028CE71CD2EC04E727F3A76298C6E314A140EDCD1B0120E4 + FE74EDB4008463B50AA3B0C528404E493F9D3B148434AD285005023F9B764FE74FDB400CDA0F18CD + 65F892F059E98E80ED926F9140C74EFF008638FC456B0419CE4FE75C5789EF45DEA65549D900F2F1 + 938CF738FD3F0A00C6230302A36E0548D9DC3D298DD4D0060F8DFC3BFF0009878375ED03ED1F64FE + D4B09EC7ED1B37F95E6C6C9BB6E4671BB38C8CE3A8AFC80AFD9926BF2E3F6A1F0FDBF867E3EF8CEC + ED5E592292ED6F49988277CF1A4EE380380D2B01EC06493C900F2DA28A2803ED1FF825EF85EEAF3E + 2678C7C4492422CAC3484D3E58D89F30C97132C8854631B40B5933920E4AE01C9C7E8F9418F5AF8F + FF00E0991E0F8F4BF83BE20F1049A7CD6D7BABEAED0ADDCA1D56E6DA089046501F94AACB25CAEE51 + C9DC093B401F6132038049FC0D00205A42A0FBD49B4535901C727F034EC3B09B450541C714FDA29A + C8091C91F4340060534A8C8A9368A6B2024727F3A004DB49B79FEB526D14D68C12393F9D0026DA4D + B83D29FB69AD1FCC0E4FE74006DA6E00278C53F6D34A02C0E4FE74847E5CFF00C147FC11FF0008E7 + C7D8F5C8A1BCFB3F8874C82E649E65FDC9B88B303C7130503E58E381997248326780CA2BE55AFD3F + FF0082927C398FC4DF042D7C511A422F7C317C9234B2C8E1BECD70CB0C888A3E566329B66F9B1811 + B60F386FCC0A00FAB3F640FD93FC23F1E7C25ACEB7E2AD6F55D3FECF7C6CADA1D364861036C68EEC + EF223EE27CD5000031B1B24E463DF8FF00C1377E11E3FE469F13FF00E0C2D3FF0091EBB4FD9F7C27 + 1F827E0BF83F4A8A5F382E9E9712389A3994CB3666936491FCAC81E460A5490571CB753E84492383 + 8A00F09FF876EFC22FFA1A7C4FFF00831B3FFE47AF807E2BFC3CBBF851F11B5FF09DECCB732E9972 + 624B95DA04F1101A2970ACC177C6C8DB7712BBB07906BF583C51E21B7F08F86357D72F12592D34CB + 39AF66480032324685D828240270A7192067B8AFC80D6F59BCF11EB57FAB6A337DA350BFB892EAE2 + 6DA177CAEC59DB0A0019249C00050052A28A2800AF63FD96FF00679BAFDA27E211D29EE26D3B40D3 + E2175AA5FC5116658F700B0C6D82A25939DBBB801246C36CDA727E00FC01F117ED07E345D17455FB + 2D85BED9352D5E542D0D8C449C13D373B60848C105883CAAAB32FEB8FC2BF853E1DF835E0BB3F0CF + 866CFECB616FF3C92B90D35CCA400D34AD81B9DB0327000002A8555550016F42F00F877C33E0C83C + 25A768D676FE1A86D9ACC698630F0B44C087570D9DFBB73162D92C598B124935F117EDF5F067E13F + C3DF0C5A6B9A4E95FD81E2ED467115AD8E8F2470DACAA026F924B73F75551080610B979017DD9AFB + 87C55E298BC3B6FB4012DE483F75167FF1E6F6FE7F991F8F9FB44FC569BE31FC56D5B5E339B8B143 + F64B17618CDBA13B5BEEA91B8967C30CAEFDBD1450079AD145140051451400514514005145140051 + 451400514514005145140056B784BC25AC78EFC49A7E81A069F36A9AC5FCA22B7B5800DCED8C9249 + E15400496240500924004D50D3F4FBAD5AFEDAC6C6DA6BCBDB9956182DADE332492C8C4054551CB3 + 1240007249AFD6EFD973F65CD1BF677F0D996568754F195FC406A3AB2AFCA8B907ECF06465620402 + 4E0172033000222006B7ECCBF00ACBF67EF86F6DA3ECB4B9F115D7EFF57D4ED9187DA65CB155058E + 4A46ADB17EE83866DAACEC2BD6BE627B629430638041A7E3DE80105210C4F18C7BD2EE19DA08CD38 + 63D698C4C6290EE246318EF4E6651819193EF4E000A42105236E38000C77A7170BD481F534A07BD0 + 02018A1831C6DC7BE69C4AA0C9207D6941046739CD0026DA1B77418A76540C923148A4372082298C + 3B50C1B1C019F7A7F029158372083F4A422A6A97C34DB192738DC06114F76EDFE7D01AF3B1D39EB5 + D278C351F3274B343C47877FF78F4FD3F9FB57379C1C77A00693C671F85464F19A918F34C23A8A00 + 8DABF27FE38F8E23F891F16BC51E2181E292D2EAF0A5AC90C6C824B78C08A17DADF302D1A2139C72 + 4F03A0FD20FDA03C5BFF00083FC18F17EAEB25D4132583DBC1359B6D9629A6C4313A9C8236BC8AC4 + 8390012324015F94D40051454FA7E9F75ABDFDB58D8DB4D7B7B752AC105B5BC66492591880A88A32 + 598920003924D007EC47EC71E17BBF08FECC9E00B1BB92196696C5B50568092A23B995EE6307207C + C126507B020E091C9F6421B70E98AA9E1ED06C3C2BA0699A2E9707D974CD36DA2B3B5837B3F97146 + 8111773124E1540C9249EE6AF6E5DC46E19F4CD001834D60DB8600C77A7E69372EE03233E99A0030 + 7D29AC1B23007E3527E229ACCAA46580FC6800C1A6B06E30075EF527E2291885192401F5A004C1A6 + B06C0C63F1A7E4119C8A4620039200A004C1C53583638C67DEA40C08C8208A09183CD00379C7BD35 + F7638C67DE9CACAC320834E38F6A00E57E27780ED7E277C3BF117852F0C0916AF632DAACD35B89D6 + 09194F973042465A37DAE39072A0820E0D7E1957EFA2B06E01048F7AFC80FDB6BE1CC9F0E7F68EF1 + 4A049858EB52FF006E5ACB3CA8ED22DC12D291B7EEA89C4E80300708339C862018BA27ED5FF153C3 + 9A2D8693A778A7ECFA7D85BC76B6F0FF0067DAB6C8D142A2E5A224E00032493573FE1B23E307FD0D + FF00F94CB3FF00E335E3145007A778CBF696F891F103C3779A06BFE23FB7E9377B3CEB7FB0DB47BF + 63ABAFCC9186186553C1ED5E6345140055DD0B44BDF12EB7A7E91A6C3F69D4750B88ED2DA1DEABE6 + 4AEC15172C4019240C9207AD52AFB03FE09EBF097FB5FC4FA97C41BD8F36BA46EB0D3B9FBD74E9FB + D7E1C1F92270B8652ADE7E41CA5007DBFF0004FC11A2FC17F87FA5F8574DB58638EDA356BABC8222 + 8D79725544971202CC773119FBC7680AA3E550076DAD789ACF48D345D1904A5F88A243F339F4F61C + 8C9EDF5C0385B8138CF35E59F15BE23699E0FD0752F116A924ABA4E9D182444859DC960AA157D59D + 95467039192002400785FEDC7F19EE74FF000B2787E2BBC6AFAF644E227C182C94FCC000C1943B61 + 06415651303CD7C1D5D47C4BF883A97C4FF196A1AFEA52C85A7722DEDDE4DE2D60C9290A9000C283 + D40193963CB135CBD00145145001451450014514500145145001451450014514500145145007D45F + B06F8DBC15E10F88570BAC58C4BE2FBC020D1B53BE6DD047B861E18C71E5CCF9C0724EE04A2ED248 + 97F438F8E2FF001C436FFF007CB7FF00155F89F5F7CFEC85FB44FF00C27DA3AF84FC4FAA79DE2AB4 + CFD925B8187BEB6550797CFCF2A61B77009501BE62246001F5BA78E6FF001FEA2D97DB6B7FF154EF + F84E2FBFE78DB7FDF2DFFC55739F4A01CD007449E38BF39CC16CA7FDD6FF001A7FFC2717DFF3C6DF + FEF96FFE2AB9BA50D401D08F1C5FEE3FE8F6DF5DADFF00C553BFE139BFFF009E36DFF7CB7FF155CE + D00E6803A1FF0084EAFF0070FF0047B623D76B7FF154FF00F84EAFFF00E78DB7FDF2DFFC55737450 + 07447C737FC7FA3DB11FEEB7FF00154F1E39BFFF009E36DFF7CB7FF155CDD01A803A37F1D5FE3882 + D8FB6D6FFE2A9C3C757F8FF536C3DB6B7FF155CE039A4CF3401D29F1D6A183FB8B63FF00016FFE2A + 84F1D5FE07EE2D87B056FF00E2AB9C068CE0FB5319A57374D7D34970FCBC8727DBDBE83A543DF351 + 5BB8C15FC454A3A5210D6EB4C27A9A791818FD6A3238C5007C87FF00050AF185B43E1AF0BF855445 + 25DDC5DB6A721130F32148D1A34CC78CE1CCAF86C8198980CF38F87EBDB7F6C5F1CA78DBE3A6AE90 + 3C525A68B1A6911491C6E8498CB34A1B7752B33CAB9000215719EA7C4A800AEE3E05CED6BF1BBE1E + CC80178FC45A7B80DD322E633CD70F45007EE07FC2757FFF003C2DBFEF96FF001A6AF8EAFF0071FF + 0047B603D76B7FF155C5785FC456DE2DF0CE91AE5A24B15A6A7670DEC2938024549103A8600900E1 + 867048F735A59F9A98CE97FE13ABFF00F9E36DFF007CB7FF00154DFF0084EAFF007FFC7BDB631D76 + B7FF00155CF645267E6A423A4FF84EAFFF00E78DB7FDF2DFFC5535BC757F918B7B63FF00016FF1AE + 7A909C11401D27FC2777FF00F3C6DBFEF96FFE2A9AFE3ABFC7105B1E7FBADFFC5573D484D007483C + 777F8FF536DFF7CB7F8D2378EAFF0069FDC5B1F6DADFFC5573B9148C462803A35F1D6A18FF00516C + 3DB6B7FF001541F1D5FF00FCF0B6FF00BE5BFF008AAE7375233605007449E39D431FF1EF6CBFF016 + FF00E2A97FE139BFFF009E36DFF7CB7FF155CE66909E2803A25F1D5FE4FF00A3DB01EBB5BFF8AAFC + FF00FF008290EBFA7EB7F117C28BF6230EBD1694C6E6E90B7972DB199BC8400B91957170490A3FD6 + 2F2D8017EDE078AFCABFDA2FC616FE3CF8DBE2ED62CC446D1EF3ECD0C904C268E64851615955C000 + 87118718CE376327A900F39A28A2800A28A2802EE85A2DEF8975BD3F48D361FB4EA3A85C47696D0E + E55F325760A8B96200C92064903D6BF607E17F802CBE15FC3DD0FC2D62FE65BE996E2369B0C3CE94 + 92D2CB82CC577C8CEDB7242EEC0E00AFCA1F843E3CB6F861F11F44F14DDE8B178822D324798584D2 + 08D5DFCB65460C51B69472AE0ED241418C1E47E9D7C1FF00DA2FC13F1B609C787EFE48751B688CF7 + 3A5EA11F95730C7B8AEF232559780494660BBD036D2C0500763E30D6EDF43D1279EE278EDA1F2D9E + 49E660A91C6A32EECC78000EA4E31D7B57E5D7C7BF8F7A97C66D6C471892C3C3368E4D969E4F2C79 + 1E74B8E0C841381C8404819CB33777FB607ED1F3FC4CF155EF86B40BE8DFC2164EA867B47246A122 + E09666C0CC6AD9DA0641237E5BE4DBF3750014514500145145001451450014514500145145001451 + 45001451450014514500153595EDC69B7905DDA4F2DADDDBC8B2C33C2E51E3753956561C82080411 + D3150D1401FA15FB317ED396FF00162CE2F0F788648AD7C656F19DAC0044D491464C880702400659 + 07A165F9772A7D041402480326BF1E6CAF6E34DBC82EED2796D6EEDE459619E1728F1BA9CAB2B0E4 + 1040208E98AFD1CFD98FE3DDBFC61F08C569A95EC47C63A7C645FC02311199036167450704105436 + 31B5C9F955593201ED808346C5077639F5A660E73BB8F4C53B3400FA4DA339C0CFAD3390D9CF1E98 + A7E73DE801C08341519CE067D699820E73C7A629D93400EA42A09048191DE9A320939E3D29C0E680 + 1682A09048048A4C104F3C7A52D002E47A5295070480714C00E4F391E94ECD003D1B6B023B55B0D9 + C639079CD501904F3C55AB67DCBB7B8A0091B391E9593E2AF105BF84BC35AC6BB7892C969A659CD7 + B3240019192342EC14120138538C9033DC56B1391915F3A7EDD1E31B7D03E08CBA3B88A4BBD7AF21 + B68E33305911237133CA1304B8063443D3065524F40403F3DB5BD66F3C45ACDFEABA84DF68BFBEB8 + 92EAE26DA177C8EC59DB0A0019249C000553A28A0028A28A00FD39FD927C4CFE27F803E1692E2F62 + BDBBB28E4B09446533088A4658A360BD088445D79208639CE4FAF955DC090335F25FFC13D7C47F69 + F06F8B741FB3ECFB0DFC57DF68DF9DFE7C7B36EDC71B7ECD9CE4E77F418E7EB23938C363F0A00938 + A42A0904814DFC691B271F3628024C523282464034838A46C9C738A007E291954E3201A4A69C923E + 6C50049C523007008CD379F5A420E47CD8A0078C523852304645267DE9A7248E78FA5318FE31D291 + 802304706933487248E7F0A4238EF8CBE301F0FF00E14F8A75E4BBFB05CDA584BF65B8F2BCCD972C + 3641F2E083995907231CF3C66BF26EBEEAFF00828178C2E74BF04786FC390F9B1C5ABDDC9733CB1C + C55592055C44C807CE0B4C8FC9C0312F04E08F856800A28A2800A28A2800A28A2800A28A2800A28A + 2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800A28A2800AD2F0D789753 + F07EBB65ACE8D7B2E9FA9D9C9E6417111E54F4230782082415208209041048ACDA2803F4BBF67BFD + A174CF8DBA118E41169FE28B38C1BED3437CAC381E7439E4C649191C9424039CAB37AE6581E00C7D + 6BF21FC37E24D4FC1FAED96B3A35ECBA7EA767279905C447953D0F0782082415208209041048AFD2 + EF807F19AD3E347816DF53DD6D06B76FFBAD4EC2DDC9F224C90AC037211C0DCBD40E5773146A00F4 + CDD4809CF418F5CD33760E3048F5A7668024CD22B1C9C818EDCD3031CF438A76EA0076EA14B77007 + D0D315B3D4629C58E381400F2723A50AC71C819FAD3431EE3143363A026801E58E38EB4292473C1F + AD3435049CF4A0071CE38009FAD490BEC704F1EB5186A40C7711838F5A00D03C1C57C01FB7EF8E63 + D73E25693E1A81E2922D06D0BCC446EB225C4FB599093C30F2D2061B4705D8124F03EFA85C3A0F55 + E2BF247E2D78EA4F897F12BC47E256795A2D42F1DEDC4F1A24896E3E585182F1958D514F27246492 + 79201C95145140051451401F48FEC1BE264D23E30DE6973DECB047AB6992C70DA82C639EE2364914 + 9038CAC6B390CDD32C01CB60FE811271C75FAD7E5BFECD5E22FF00845FE3BF82EF3ECFF69F36FD6C + 766FD98FB4298376707EEF9BBB1DF6E323391FA8F400F04E3DE918B63800FE34D0723A6282481D33 + EC298C7826918B71800FE34809A4663C719A421F9229096C8E063EB480D21273D0FD6801F9229327 + 774E3EB49499249E3F1A007669371DDD063EB494039EA31400EDD4D04E4E40A0F03A66B33C4DE20B + 7F0A786756D72F2395ED74DB49AF66484032324685D828240270A719207BD007E75FED85E384F1B7 + C72D5D2078A4B4D1A34D2629238D909319669436EEA56679572000428C67A9F14AB9AD6B179E22D6 + 6FF55D426FB45FDF5C49757136D0BBE4762CCD85000C924E0002A9D0014514500145145001451450 + 01451450014514500145145001451450014514500145145001451450014514500145145001451450 + 01451450015D27C3CF887ADFC2FF0014DAEBFA05D7D9EF21F95E3704C571192374522E46E4381C70 + 410082180239BA2803EEFB2FDBE7C0EF67035DE85E2082E9A3533450C504888F8F982B9954B00738 + 25573D703A54DFF0DEFF000FC7FCC1FC4BFF0080B6FF00FC7EBE0AA2803EF6FF0086F8F87FFF0040 + 7F12FF00E02DBFFF001FA3FE1BE3E1FF00FD01FC4BFF0080B6FF00FC7EBE09A2803EF6FF0086F8F8 + 7FFF00407F12FF00E02DBFFF001FA51FB7CFC3E1FF00307F12FF00E02DBFFF001FAF8228A00FBDFF + 00E1BE7E1FE7FE40FE25FF00C05B7FFE3F4BFF000DF5F0FBFE80FE25FF00C05B7FFE3F5F03D1401F + 7C7FC37D7C3EFF00A03F897FF016DFFF008FD2FF00C37DFC3EFF00A037897FF016DFFF008FD7C0D4 + 5007DF23F6FBF87DFF00407F12FF00E02DBFFF001FA3FE1BEFE1F7FD01FC4DFF0080B6FF00FC7EBE + 06A2803EDDF1E7EDDFE17D4FC13AED8787B4CF10DB6B3796725B5ADC4A22B7103BA9512891256605 + 32586072540CAE723E22A28A0028A28A0028A28A009ACAF6E34DBC82EED2796D6EEDE459619E0729 + 246EA72ACAC3904100823A62BEF5FF0086FDF87BFF00406F137FE02DBFFF001FAF8128A00FBEFF00 + E1BF7E1EFF00D01FC4DFF80B6FFF00C7E8FF0086FDF87BFF00406F137FE02DBFFF001FAF8128A00F + BEFF00E1BF7E1EFF00D01BC4DFF80B6FFF00C7E8FF0086FDF87BFF00406F137FE02DBFFF001FAF81 + 28A00FBEFF00E1BF7E1EFF00D01BC4DFF80B6FFF00C7E8FF0086FDF87BFF00406F137FE02DBFFF00 + 1FAF8128A00FBEFF00E1BF7E1EFF00D01BC4DFF80B6FFF00C7E8FF0086FDF87BFF00406F137FE02D + BFFF001FAF8128A00FBEFF00E1BF7E1EFF00D01BC4DFF80B6FFF00C7E8FF0086FDF87BFF00407F13 + 7FE02DBFFF001FAF8128A00FBECFEDFBF0FBFE80FE26FF00C05B7FFE3F5E75FB407ED81E16F89FF0 + B754F0BE89A46AF1DDEA12421A6D41228A38912559491B2472C49455C71F789CF183F24D14005145 + 14005145140051451400514514005145140051451400514514005145140051451400514514005145 + 14005145140051451400514514005145140051451400514514005145140051451400514514005145 + 14005145140051451400514514005145140051451400514514005145140051451400514514005145 + 14005145140051451400514514005145140051451400514514005145140051451400514514005145 + 14005145140051451400514514005145140051451400514514005145140051451400514514005145 + 140051451400514514005145140051451400514514005145140051451400515EE3F133F624F8D3F0 + 77C177FE2CF17F833FB23C3F626317179FDAB653EC3248B1A7C91CCCC72CEA3807AF3C5787500145 + 14500145145001451450014514500145145001451450014514500145145001451450014514500145 + 14500145145001451450014514500145145001451450014514500145145001451450014514500145 + 14500145145001451450014514500145145001451450014514500145145001451450014514500145 + 145007DF3FB577FC148742F8FBF03F5BF02699E1CD4209F5596DB75CDDC51C0B02C53A4C586D9A42 + E498C2EDC2FDE2777CBB5BE06A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A002 + 8A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A0028A28A00FFFD9 + " + "asset_preview_thumbnail_format" "string" "jpg" + "map_asset_references" "string_array" + [ + "materials/dev/reflectivity_30.vmat", + "maps/prefabs/asdf.vmap" + ] +} +"CMapRootElement" +{ + "id" "elementid" "391aab3f-ca0a-4064-9db9-375eafa06d65" + "isprefab" "bool" "0" + "editorbuild" "int" "10430" + "editorversion" "int" "400" + "itemFile" "string" "" + "defaultcamera" "CStoredCamera" + { + "id" "elementid" "b5496b5d-19c0-4cee-b377-18b2c30d70da" + "position" "vector3" "0 -1000 1000" + "lookat" "vector3" "-0.0000000309 -999.2929077148 999.2929077148" + } + + "3dcameras" "CStoredCameras" + { + "id" "elementid" "747e7c4c-829a-47eb-a7b5-8f396f0de675" + "activecamera" "int" "-1" + "cameras" "element_array" + [ + ] + } + + "world" "CMapWorld" + { + "id" "elementid" "933c0300-3280-46f1-84e2-346e761e1ecc" + "nodeID" "int" "1" + "referenceID" "uint64" "0x0" + "children" "element_array" + [ + "CMapMesh" + { + "id" "elementid" "858c77bd-8d27-41f8-93e8-da009564b281" + "nodeID" "int" "2" + "referenceID" "uint64" "0x8ec2e1c4bee05053" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "cubeMapName" "string" "" + "visexclude" "bool" "0" + "disablemerging" "bool" "0" + "renderwithdynamic" "bool" "0" + "disableHeightDisplacement" "bool" "0" + "fademindist" "float" "-1" + "fademaxdist" "float" "0" + "bakelighting" "bool" "1" + "renderToCubemaps" "bool" "1" + "emissiveLightingEnabled" "bool" "1" + "emissiveLightingBoost" "float" "1" + "disableShadows" "int" "0" + "lightingDummy" "bool" "0" + "keep_vertices" "bool" "0" + "smoothingAngle" "float" "40" + "tintColor" "color" "255 255 255 255" + "renderAmt" "int" "255" + "physicsType" "string" "default" + "physicsGroup" "string" "" + "physicsInteractsAs" "string" "" + "physicsInteractsWith" "string" "" + "physicsInteractsExclude" "string" "" + "meshData" "CDmePolygonMesh" + { + "id" "elementid" "72913a29-7888-4d6a-abd4-3384be5ca2db" + "name" "string" "meshData" + "vertexEdgeIndices" "int_array" + [ + "0", + "1", + "22", + "15", + "8", + "14", + "6", + "18" + ] + "vertexDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + "edgeVertexIndices" "int_array" + [ + "1", + "0", + "5", + "1", + "2", + "5", + "1", + "6", + "3", + "4", + "6", + "3", + "7", + "6", + "3", + "5", + "7", + "4", + "0", + "7", + "2", + "0", + "4", + "2" + ] + "edgeOppositeIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "7", + "6", + "9", + "8", + "11", + "10", + "13", + "12", + "15", + "14", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "edgeNextIndices" "int_array" + [ + "2", + "19", + "4", + "7", + "21", + "14", + "1", + "11", + "10", + "23", + "12", + "15", + "17", + "6", + "9", + "3", + "18", + "8", + "20", + "13", + "22", + "0", + "16", + "5" + ] + "edgeFaceIndices" "int_array" + [ + "0", + "5", + "0", + "3", + "0", + "4", + "5", + "3", + "1", + "4", + "1", + "3", + "1", + "5", + "4", + "3", + "2", + "1", + "2", + "5", + "2", + "0", + "2", + "4" + ] + "edgeDataIndices" "int_array" + [ + "0", + "0", + "1", + "1", + "2", + "2", + "3", + "3", + "4", + "4", + "5", + "5", + "6", + "6", + "7", + "7", + "8", + "8", + "9", + "9", + "10", + "10", + "11", + "11" + ] + "edgeVertexDataIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "6", + "7", + "9", + "8", + "11", + "10", + "13", + "12", + "14", + "15", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "faceEdgeIndices" "int_array" + [ + "21", + "17", + "22", + "15", + "14", + "6" + ] + "faceDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5" + ] + "materials" "string_array" + [ + "materials/dev/reflectivity_30.vmat" + ] + "vertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "573c90f4-b86f-4cc7-8171-7329fa22aaba" + "size" "int" "8" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "825e573b-f372-49ea-8d90-25d066c4e0be" + "name" "string" "position:0" + "standardAttributeName" "string" "position" + "semanticName" "string" "position" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "-230.5 -241.5 162.5", + "230.5 -241.5 162.5", + "-230.5 241.5 162.5", + "230.5 241.5 -162.5", + "-230.5 241.5 -162.5", + "230.5 241.5 162.5", + "230.5 -241.5 -162.5", + "-230.5 -241.5 -162.5" + ] + } + ] + } + + "faceVertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "1e44c4fb-b6a8-48d3-9219-210680be8e4c" + "size" "int" "24" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "db8dde10-6c32-47ac-bcfc-901de4e4a8f5" + "name" "string" "texcoord:0" + "standardAttributeName" "string" "texcoord" + "semanticName" "string" "texcoord" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "-13.625 -5.078125", + "-6.421875 5.359375", + "-5.359375 -5.078125", + "-6.421875 -2.1875", + "-6.421875 -5.078125", + "-13.625 -2.1875", + "-6.421875 -5.078125", + "-5.359375 -0", + "-13.625 0", + "-6.421875 -2.1875", + "2.1875 0", + "-6.421875 5.359375", + "-6.421875 -0", + "-13.625 5.359375", + "-6.421875 0", + "2.1875 -5.078125", + "-13.625 -2.1875", + "-5.359375 -0", + "-13.625 -0", + "-5.359375 -5.078125", + "-13.625 5.359375", + "2.1875 -5.078125", + "-13.625 -5.078125", + "2.1875 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "99a83a12-d32e-4beb-b5be-af5f84adf8dd" + "name" "string" "normal:0" + "standardAttributeName" "string" "normal" + "semanticName" "string" "normal" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "0 -1 0", + "0 0 1", + "1 0 0", + "0 0 1", + "0 1 0", + "0 0 1", + "0 -1 0", + "1 0 0", + "0 1 0", + "0 0 -1", + "1 0 0", + "0 0 -1", + "0 -1 0", + "0 0 -1", + "0 1 0", + "1 0 0", + "0 0 -1", + "-1 0 0", + "0 -1 0", + "-1 0 0", + "0 0 1", + "-1 0 0", + "0 1 0", + "-1 0 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "d52ad7b0-6b9a-4f60-a810-e0e3272c56ab" + "name" "string" "tangent:0" + "standardAttributeName" "string" "tangent" + "semanticName" "string" "tangent" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 0 0 -1", + "1 -0 0 1", + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 -0 0 1", + "1 0 0 1", + "0 1 0 -1", + "1 0 0 1", + "1 0 0 -1", + "1 0 0 1", + "1 -0 0 1", + "0 1 0 -1", + "1 0 0 1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 -0 0 1", + "0 1 0 1" + ] + } + ] + } + + "edgeData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "361dd960-652c-48b9-b846-b61f24e0eed7" + "size" "int" "12" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "e41628ea-31c1-4e9a-9f67-8acfa38e7376" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "faceData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "e7e78c8d-9a87-4525-a7fb-762d5a0d486a" + "size" "int" "6" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "3fa73364-c909-404d-83ea-a3063614bd3d" + "name" "string" "textureScale:0" + "standardAttributeName" "string" "textureScale" + "semanticName" "string" "textureScale" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "e29ee27f-9ac1-47c7-b0b6-d01c6069268f" + "name" "string" "textureAxisU:0" + "standardAttributeName" "string" "textureAxisU" + "semanticName" "string" "textureAxisU" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 0", + "1 0 0 0", + "0 1 0 0", + "0 1 0 0", + "1 0 0 0", + "1 0 0 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "b1bd9358-70df-4f9a-acb7-d07df75d4313" + "name" "string" "textureAxisV:0" + "standardAttributeName" "string" "textureAxisV" + "semanticName" "string" "textureAxisV" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "0 -1 0 0", + "0 -1 0 0", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "d0d26c1f-bae6-4aa0-bdd7-fa01e8dcc2a6" + "name" "string" "materialindex:0" + "standardAttributeName" "string" "materialindex" + "semanticName" "string" "materialindex" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "8" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "778c0c38-41fa-4091-97ea-024b1dfa7f20" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "905a62f6-2056-4b8c-a56c-b39c2198853a" + "name" "string" "lightmapScaleBias:0" + "standardAttributeName" "string" "lightmapScaleBias" + "semanticName" "string" "lightmapScaleBias" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "subdivisionData" "CDmePolygonMeshSubdivisionData" + { + "id" "elementid" "7c270dd1-0a68-4733-a5ba-4474fbac665f" + "subdivisionLevels" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + "streams" "element_array" + [ + ] + } + + } + + "physicsSimplificationOverride" "bool" "0" + "physicsSimplificationError" "float" "0" + "origin" "vector3" "-641.5 -101.5 162.5" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + }, + "CMapMesh" + { + "id" "elementid" "4c6cf1a1-c067-4f60-b477-4fa57da36a67" + "nodeID" "int" "3" + "referenceID" "uint64" "0x5d4906b8871ececa" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "cubeMapName" "string" "" + "visexclude" "bool" "0" + "disablemerging" "bool" "0" + "renderwithdynamic" "bool" "0" + "disableHeightDisplacement" "bool" "0" + "fademindist" "float" "-1" + "fademaxdist" "float" "0" + "bakelighting" "bool" "1" + "renderToCubemaps" "bool" "1" + "emissiveLightingEnabled" "bool" "1" + "emissiveLightingBoost" "float" "1" + "disableShadows" "int" "0" + "lightingDummy" "bool" "0" + "keep_vertices" "bool" "0" + "smoothingAngle" "float" "40" + "tintColor" "color" "255 255 255 255" + "renderAmt" "int" "255" + "physicsType" "string" "default" + "physicsGroup" "string" "" + "physicsInteractsAs" "string" "" + "physicsInteractsWith" "string" "" + "physicsInteractsExclude" "string" "" + "meshData" "CDmePolygonMesh" + { + "id" "elementid" "99861d39-7646-4c79-8a4c-c1a3a0f11a76" + "name" "string" "meshData" + "vertexEdgeIndices" "int_array" + [ + "0", + "1", + "22", + "15", + "8", + "14", + "6", + "18" + ] + "vertexDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + "edgeVertexIndices" "int_array" + [ + "1", + "0", + "5", + "1", + "2", + "5", + "1", + "6", + "3", + "4", + "6", + "3", + "7", + "6", + "3", + "5", + "7", + "4", + "0", + "7", + "2", + "0", + "4", + "2" + ] + "edgeOppositeIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "7", + "6", + "9", + "8", + "11", + "10", + "13", + "12", + "15", + "14", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "edgeNextIndices" "int_array" + [ + "2", + "19", + "4", + "7", + "21", + "14", + "1", + "11", + "10", + "23", + "12", + "15", + "17", + "6", + "9", + "3", + "18", + "8", + "20", + "13", + "22", + "0", + "16", + "5" + ] + "edgeFaceIndices" "int_array" + [ + "0", + "5", + "0", + "3", + "0", + "4", + "5", + "3", + "1", + "4", + "1", + "3", + "1", + "5", + "4", + "3", + "2", + "1", + "2", + "5", + "2", + "0", + "2", + "4" + ] + "edgeDataIndices" "int_array" + [ + "0", + "0", + "1", + "1", + "2", + "2", + "3", + "3", + "4", + "4", + "5", + "5", + "6", + "6", + "7", + "7", + "8", + "8", + "9", + "9", + "10", + "10", + "11", + "11" + ] + "edgeVertexDataIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "6", + "7", + "9", + "8", + "11", + "10", + "13", + "12", + "14", + "15", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "faceEdgeIndices" "int_array" + [ + "21", + "17", + "22", + "15", + "14", + "6" + ] + "faceDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5" + ] + "materials" "string_array" + [ + "materials/dev/reflectivity_30.vmat" + ] + "vertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "dd18d9a1-9c24-429f-bf2d-4e0d1342e258" + "size" "int" "8" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "79665c96-79c4-4209-b864-ed9173ac8054" + "name" "string" "position:0" + "standardAttributeName" "string" "position" + "semanticName" "string" "position" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "-230.5 -241.5 162.5", + "230.5 -241.5 162.5", + "-230.5 241.5 162.5", + "230.5 241.5 -162.5", + "-230.5 241.5 -162.5", + "230.5 241.5 162.5", + "230.5 -241.5 -162.5", + "-230.5 -241.5 -162.5" + ] + } + ] + } + + "faceVertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "d83445b0-ff47-417c-a3c0-5cef2742f3ff" + "size" "int" "24" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "67a90860-dc12-4c14-b5b7-a3d9677c2bc4" + "name" "string" "texcoord:0" + "standardAttributeName" "string" "texcoord" + "semanticName" "string" "texcoord" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "-13.625 -5.078125", + "-6.421875 5.359375", + "-5.359375 -5.078125", + "-6.421875 -2.1875", + "-6.421875 -5.078125", + "-13.625 -2.1875", + "-6.421875 -5.078125", + "-5.359375 -0", + "-13.625 0", + "-6.421875 -2.1875", + "2.1875 0", + "-6.421875 5.359375", + "-6.421875 -0", + "-13.625 5.359375", + "-6.421875 0", + "2.1875 -5.078125", + "-13.625 -2.1875", + "-5.359375 -0", + "-13.625 -0", + "-5.359375 -5.078125", + "-13.625 5.359375", + "2.1875 -5.078125", + "-13.625 -5.078125", + "2.1875 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "a27d6a82-2505-49e5-b93c-5a2817d811a0" + "name" "string" "normal:0" + "standardAttributeName" "string" "normal" + "semanticName" "string" "normal" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "0 -1 0", + "0 0 1", + "1 0 0", + "0 0 1", + "0 1 0", + "0 0 1", + "0 -1 0", + "1 0 0", + "0 1 0", + "0 0 -1", + "1 0 0", + "0 0 -1", + "0 -1 0", + "0 0 -1", + "0 1 0", + "1 0 0", + "0 0 -1", + "-1 0 0", + "0 -1 0", + "-1 0 0", + "0 0 1", + "-1 0 0", + "0 1 0", + "-1 0 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "6ac3ea84-aed4-4e0a-8b73-d1f884ef7e8f" + "name" "string" "tangent:0" + "standardAttributeName" "string" "tangent" + "semanticName" "string" "tangent" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 0 0 -1", + "1 -0 0 1", + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 -0 0 1", + "1 0 0 1", + "0 1 0 -1", + "1 0 0 1", + "1 0 0 -1", + "1 0 0 1", + "1 -0 0 1", + "0 1 0 -1", + "1 0 0 1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 -0 0 1", + "0 1 0 1" + ] + } + ] + } + + "edgeData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "26fbe21f-6e3c-419a-bf62-ad237b0515ea" + "size" "int" "12" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "c3de2b64-c5fb-4f33-8bee-303e1233c362" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "faceData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "f12a4333-1f5b-4f72-9ff8-7bdac862e2b5" + "size" "int" "6" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "d8715b0b-1d3f-449a-a0de-e2426f3cdfb4" + "name" "string" "textureScale:0" + "standardAttributeName" "string" "textureScale" + "semanticName" "string" "textureScale" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "2e6de4f6-a1a9-4ffb-a0ca-53dac9fdf26e" + "name" "string" "textureAxisU:0" + "standardAttributeName" "string" "textureAxisU" + "semanticName" "string" "textureAxisU" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 108", + "1 0 0 108", + "0 1 0 0", + "0 1 0 0", + "1 0 0 108", + "1 0 0 108" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "223bfd23-066e-4be3-aa99-90dce0132dfb" + "name" "string" "textureAxisV:0" + "standardAttributeName" "string" "textureAxisV" + "semanticName" "string" "textureAxisV" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "0 -1 0 0", + "0 -1 0 0", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "c13e7eb8-e3d5-4bad-a20f-674add7bc563" + "name" "string" "materialindex:0" + "standardAttributeName" "string" "materialindex" + "semanticName" "string" "materialindex" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "8" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "18e6bf9b-6c05-4dac-aa9e-cc4346ae4ab6" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "c459864d-b83d-45aa-a01e-02950c5291b3" + "name" "string" "lightmapScaleBias:0" + "standardAttributeName" "string" "lightmapScaleBias" + "semanticName" "string" "lightmapScaleBias" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "subdivisionData" "CDmePolygonMeshSubdivisionData" + { + "id" "elementid" "4a323c2b-449f-42d3-815a-c1ee09305706" + "subdivisionLevels" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + "streams" "element_array" + [ + ] + } + + } + + "physicsSimplificationOverride" "bool" "0" + "physicsSimplificationError" "float" "0" + "origin" "vector3" "49 -101.5 162.5" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + }, + "CMapEntity" + { + "id" "elementid" "0b47fd27-47d0-4d69-87bd-d01f9d8c65d3" + "nodeID" "int" "4" + "referenceID" "uint64" "0xc851420d56bfc9d2" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "relayPlugData" "DmePlugList" + { + "id" "elementid" "e89c6573-b958-4cfe-a260-cace02909212" + "names" "string_array" + [ + ] + "dataTypes" "int_array" + [ + ] + "plugTypes" "int_array" + [ + ] + "descriptions" "string_array" + [ + ] + } + + "connectionsData" "element_array" + [ + ] + "entity_properties" "EditGameClassProps" + { + "id" "elementid" "1c688cd2-0a30-4f41-bd33-d32d54f75c91" + "classname" "string" "prop_static" + "model" "string" "" + "rendercolor" "string" "255 255 255" + "skin" "string" "default" + "solid" "string" "6" + "bakelighting" "string" "-1" + "disableshadows" "string" "0" + "emissive" "string" "1" + "emissive_lighting_boost" "string" "1.0" + "lightmapscalebias" "string" "0" + "lightingorigin" "string" "" + "bakelightdoublesided" "string" "0" + "visoccluder" "string" "0" + "materialoverride" "string" "" + "lodlevel" "string" "-1" + "baketoworld" "string" "0" + "disablemerging" "string" "0" + "rendertocubemaps" "string" "1" + } + + "hitNormal" "vector3" "0 0 1" + "isProceduralEntity" "bool" "0" + "origin" "vector3" "-92.3866424561 -644 205.8106079102" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + }, + "CMapPrefab" + { + "id" "elementid" "35402bb0-9865-4447-8e3e-96fe55fe2647" + "nodeID" "int" "6" + "referenceID" "uint64" "0x79c0a412ab1ebadc" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "relayPlugData" "DmePlugList" + { + "id" "elementid" "1b6311c2-6df0-4e00-951f-e4a529c8222b" + "names" "string_array" + [ + ] + "dataTypes" "int_array" + [ + ] + "plugTypes" "int_array" + [ + ] + "descriptions" "string_array" + [ + ] + } + + "connectionsData" "element_array" + [ + ] + "target" "element" "" + "variableOverrideNames" "string_array" + [ + ] + "variableOverrideValues" "string_array" + [ + ] + "origin" "vector3" "777 60 0" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + "tintColor" "color" "255 255 255 255" + "targetMapPath" "string" "maps/prefabs/asdf.vmap" + "targetName" "string" "" + "fixupEntityNames" "bool" "1" + "useTargetNameAsPrefix" "bool" "0" + "loadIfNested" "bool" "1" + "visexclude" "bool" "0" + "prefabRuntimeEntity" "bool" "0" + "loadAtRuntime" "bool" "0" + }, + "element" "1ca9f8ea-7d4f-4486-b577-26b7187c1bbd", + "CMapInstance" + { + "id" "elementid" "116da3b7-6a22-40a6-ba50-20240cc5927c" + "nodeID" "int" "9" + "referenceID" "uint64" "0xa5cf7fc954e8e479" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "relayPlugData" "DmePlugList" + { + "id" "elementid" "f33819c3-018e-4008-b646-fca658286322" + "names" "string_array" + [ + ] + "dataTypes" "int_array" + [ + ] + "plugTypes" "int_array" + [ + ] + "descriptions" "string_array" + [ + ] + } + + "connectionsData" "element_array" + [ + ] + "target" "element" "1ca9f8ea-7d4f-4486-b577-26b7187c1bbd" + "origin" "vector3" "-641.5 837 162.5" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + "tintColor" "color" "255 255 255 255" + } + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "relayPlugData" "DmePlugList" + { + "id" "elementid" "d37f83ba-0fc5-48c5-8a15-04a0b47aa3e2" + "names" "string_array" + [ + ] + "dataTypes" "int_array" + [ + ] + "plugTypes" "int_array" + [ + ] + "descriptions" "string_array" + [ + ] + } + + "connectionsData" "element_array" + [ + ] + "entity_properties" "EditGameClassProps" + { + "id" "elementid" "fe6f6410-f2dc-418b-a264-251c80e83704" + "classname" "string" "worldspawn" + "targetname" "string" "" + "skyname" "string" "sky_day01_01" + "startdark" "string" "0" + "startcolor" "string" "0 0 0" + "pvstype" "string" "10" + "newunit" "string" "0" + "maxpropscreenwidth" "string" "-1" + "minpropscreenwidth" "string" "0" + "vrchaperone" "string" "0" + "vrmovement" "string" "0" + "max_lightmap_resolution" "string" "0" + "lightmap_queries" "string" "1" + "steamaudio_reverb_rebake_option" "string" "1" + "steamaudio_reverb_grid_type" "string" "0" + "steamaudio_reverb_grid_spacing" "string" "6" + "steamaudio_reverb_height_above_floor" "string" "1.5" + "steamaudio_reverb_rays" "string" "32768" + "steamaudio_reverb_bounces" "string" "32" + "steamaudio_reverb_ir_duration" "string" "1.0" + "steamaudio_reverb_ambisonic_order" "string" "1" + "steamaudio_pathing_rebake_option" "string" "1" + "steamaudio_pathing_grid_type" "string" "0" + "steamaudio_pathing_grid_spacing" "string" "6" + "steamaudio_pathing_height_above_floor" "string" "1.5" + "steamaudio_pathing_visibility_samples" "string" "1" + "steamaudio_pathing_visibility_radius" "string" "0.0" + "steamaudio_pathing_visibility_threshold" "string" "0.1" + "steamaudio_pathing_visibility_pathrange" "string" "100.0" + } + + "nextDecalID" "int" "0" + "fixupEntityNames" "bool" "1" + "mapUsageType" "string" "standard" + "origin" "vector3" "0 0 0" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + } + + "visbility" "CVisibilityMgr" + { + "id" "elementid" "abe46680-c7e9-4065-b561-da42c2e90664" + "nodeID" "int" "0" + "referenceID" "uint64" "0x0" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "nodes" "element_array" + [ + ] + "hiddenFlags" "int_array" + [ + ] + "origin" "vector3" "0 0 0" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + } + + "mapVariables" "CMapVariableSet" + { + "id" "elementid" "a0d6a04c-48d0-4411-aee4-1c6a4c7162da" + "variableNames" "string_array" + [ + ] + "variableValues" "string_array" + [ + ] + "variableTypeNames" "string_array" + [ + ] + "variableTypeParameters" "string_array" + [ + ] + "m_ChoiceGroups" "element_array" + [ + ] + } + + "rootSelectionSet" "CMapSelectionSet" + { + "id" "elementid" "b18006df-ac4c-42c8-b23e-2e8391f98f46" + "children" "element_array" + [ + ] + "selectionSetName" "string" "" + "selectionSetData" "element" "" + } + + "m_ReferencedMeshSnapshots" "element_array" + [ + ] + "m_bIsCordoning" "bool" "0" + "m_bCordonsVisible" "bool" "0" + "nodeInstanceData" "element_array" + [ + ] +} + +"CMapGroup" +{ + "id" "elementid" "1ca9f8ea-7d4f-4486-b577-26b7187c1bbd" + "nodeID" "int" "8" + "referenceID" "uint64" "0xd181ab0f310a9c8c" + "children" "element_array" + [ + "CMapMesh" + { + "id" "elementid" "989f7049-4201-4331-ac8f-cb6189c29c02" + "nodeID" "int" "7" + "referenceID" "uint64" "0xd33492819d96a240" + "children" "element_array" + [ + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "cubeMapName" "string" "" + "visexclude" "bool" "0" + "disablemerging" "bool" "0" + "renderwithdynamic" "bool" "0" + "disableHeightDisplacement" "bool" "0" + "fademindist" "float" "-1" + "fademaxdist" "float" "0" + "bakelighting" "bool" "1" + "renderToCubemaps" "bool" "1" + "emissiveLightingEnabled" "bool" "1" + "emissiveLightingBoost" "float" "1" + "disableShadows" "int" "0" + "lightingDummy" "bool" "0" + "keep_vertices" "bool" "0" + "smoothingAngle" "float" "40" + "tintColor" "color" "255 255 255 255" + "renderAmt" "int" "255" + "physicsType" "string" "default" + "physicsGroup" "string" "" + "physicsInteractsAs" "string" "" + "physicsInteractsWith" "string" "" + "physicsInteractsExclude" "string" "" + "meshData" "CDmePolygonMesh" + { + "id" "elementid" "9ab6f89a-e767-4870-9b22-b633ceed1ed0" + "name" "string" "meshData" + "vertexEdgeIndices" "int_array" + [ + "0", + "1", + "22", + "15", + "8", + "14", + "6", + "18" + ] + "vertexDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + "edgeVertexIndices" "int_array" + [ + "1", + "0", + "5", + "1", + "2", + "5", + "1", + "6", + "3", + "4", + "6", + "3", + "7", + "6", + "3", + "5", + "7", + "4", + "0", + "7", + "2", + "0", + "4", + "2" + ] + "edgeOppositeIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "7", + "6", + "9", + "8", + "11", + "10", + "13", + "12", + "15", + "14", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "edgeNextIndices" "int_array" + [ + "2", + "19", + "4", + "7", + "21", + "14", + "1", + "11", + "10", + "23", + "12", + "15", + "17", + "6", + "9", + "3", + "18", + "8", + "20", + "13", + "22", + "0", + "16", + "5" + ] + "edgeFaceIndices" "int_array" + [ + "0", + "5", + "0", + "3", + "0", + "4", + "5", + "3", + "1", + "4", + "1", + "3", + "1", + "5", + "4", + "3", + "2", + "1", + "2", + "5", + "2", + "0", + "2", + "4" + ] + "edgeDataIndices" "int_array" + [ + "0", + "0", + "1", + "1", + "2", + "2", + "3", + "3", + "4", + "4", + "5", + "5", + "6", + "6", + "7", + "7", + "8", + "8", + "9", + "9", + "10", + "10", + "11", + "11" + ] + "edgeVertexDataIndices" "int_array" + [ + "1", + "0", + "3", + "2", + "5", + "4", + "6", + "7", + "9", + "8", + "11", + "10", + "13", + "12", + "14", + "15", + "17", + "16", + "19", + "18", + "21", + "20", + "23", + "22" + ] + "faceEdgeIndices" "int_array" + [ + "21", + "17", + "22", + "15", + "14", + "6" + ] + "faceDataIndices" "int_array" + [ + "0", + "1", + "2", + "3", + "4", + "5" + ] + "materials" "string_array" + [ + "materials/dev/reflectivity_30.vmat" + ] + "vertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "5f975ebb-c068-4e25-8058-d297bcc0e71b" + "size" "int" "8" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "7e75b3f0-82c8-4f87-8574-f2ff5920c562" + "name" "string" "position:0" + "standardAttributeName" "string" "position" + "semanticName" "string" "position" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "-230.5 -241.5 162.5", + "230.5 -241.5 162.5", + "-230.5 241.5 162.5", + "230.5 241.5 -162.5", + "-230.5 241.5 -162.5", + "230.5 241.5 162.5", + "230.5 -241.5 -162.5", + "-230.5 -241.5 -162.5" + ] + } + ] + } + + "faceVertexData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "51974202-1146-4672-bd24-cd0320dcfdc4" + "size" "int" "24" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "5ba74c38-f3ce-4405-bf65-6b5573cde2f4" + "name" "string" "texcoord:0" + "standardAttributeName" "string" "texcoord" + "semanticName" "string" "texcoord" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "-13.625 -5.078125", + "-6.421875 5.359375", + "-5.359375 -5.078125", + "-6.421875 -2.1875", + "-6.421875 -5.078125", + "-13.625 -2.1875", + "-6.421875 -5.078125", + "-5.359375 -0", + "-13.625 0", + "-6.421875 -2.1875", + "2.1875 0", + "-6.421875 5.359375", + "-6.421875 -0", + "-13.625 5.359375", + "-6.421875 0", + "2.1875 -5.078125", + "-13.625 -2.1875", + "-5.359375 -0", + "-13.625 -0", + "-5.359375 -5.078125", + "-13.625 5.359375", + "2.1875 -5.078125", + "-13.625 -5.078125", + "2.1875 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "3d2f76d8-35c8-4013-ad15-a721d66af3f0" + "name" "string" "normal:0" + "standardAttributeName" "string" "normal" + "semanticName" "string" "normal" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector3_array" + [ + "0 -1 0", + "0 0 1", + "1 0 0", + "0 0 1", + "0 1 0", + "0 0 1", + "0 -1 0", + "1 0 0", + "0 1 0", + "0 0 -1", + "1 0 0", + "0 0 -1", + "0 -1 0", + "0 0 -1", + "0 1 0", + "1 0 0", + "0 0 -1", + "-1 0 0", + "0 -1 0", + "-1 0 0", + "0 0 1", + "-1 0 0", + "0 1 0", + "-1 0 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "b1a52552-f27e-410f-bfec-53d823b4cc3b" + "name" "string" "tangent:0" + "standardAttributeName" "string" "tangent" + "semanticName" "string" "tangent" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 0 0 -1", + "1 -0 0 1", + "1 0 0 -1", + "1 0 0 -1", + "0 1 0 -1", + "1 -0 0 1", + "1 0 0 1", + "0 1 0 -1", + "1 0 0 1", + "1 0 0 -1", + "1 0 0 1", + "1 -0 0 1", + "0 1 0 -1", + "1 0 0 1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 0 0 -1", + "0 1 0 1", + "1 -0 0 1", + "0 1 0 1" + ] + } + ] + } + + "edgeData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "1b209b5c-a569-409d-b32b-8a5c0d8be61f" + "size" "int" "12" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "94b59a53-40cd-4710-92ed-97e184e42e26" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "faceData" "CDmePolygonMeshDataArray" + { + "id" "elementid" "afd9fce6-cb9f-49ee-9840-c1247530fc10" + "size" "int" "6" + "streams" "element_array" + [ + "CDmePolygonMeshDataStream" + { + "id" "elementid" "ab3007d5-80e5-4699-9714-c92906cc7d5f" + "name" "string" "textureScale:0" + "standardAttributeName" "string" "textureScale" + "semanticName" "string" "textureScale" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector2_array" + [ + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125", + "0.125 0.125" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "58560e85-c521-4024-9448-3fdaadc19d7d" + "name" "string" "textureAxisU:0" + "standardAttributeName" "string" "textureAxisU" + "semanticName" "string" "textureAxisU" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "1 0 0 0", + "1 0 0 0", + "0 1 0 172", + "0 1 0 172", + "1 0 0 0", + "1 0 0 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "8c772add-89f0-4747-9fb4-81d4f115e15a" + "name" "string" "textureAxisV:0" + "standardAttributeName" "string" "textureAxisV" + "semanticName" "string" "textureAxisV" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "0" + "subdivisionBinding" "element" "" + "data" "vector4_array" + [ + "0 -1 0 340", + "0 -1 0 340", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0", + "0 0 -1 0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "c91cb92c-39de-4050-b3fc-d02241ff2093" + "name" "string" "materialindex:0" + "standardAttributeName" "string" "materialindex" + "semanticName" "string" "materialindex" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "8" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "25be7f49-ad63-469d-95f7-1c88d5c17d3f" + "name" "string" "flags:0" + "standardAttributeName" "string" "flags" + "semanticName" "string" "flags" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "3" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + }, + "CDmePolygonMeshDataStream" + { + "id" "elementid" "bb9d9a2d-f671-4f17-b7f0-1791e73e3ec8" + "name" "string" "lightmapScaleBias:0" + "standardAttributeName" "string" "lightmapScaleBias" + "semanticName" "string" "lightmapScaleBias" + "semanticIndex" "int" "0" + "vertexBufferLocation" "int" "0" + "dataStateFlags" "int" "1" + "subdivisionBinding" "element" "" + "data" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0" + ] + } + ] + } + + "subdivisionData" "CDmePolygonMeshSubdivisionData" + { + "id" "elementid" "dcd80f0c-88cc-41e0-a001-5fd58db97812" + "subdivisionLevels" "int_array" + [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ] + "streams" "element_array" + [ + ] + } + + } + + "physicsSimplificationOverride" "bool" "0" + "physicsSimplificationError" "float" "0" + "origin" "vector3" "-641.5 837 162.5" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + } + ] + "variableTargetKeys" "string_array" + [ + ] + "variableNames" "string_array" + [ + ] + "origin" "vector3" "-641.5 837 162.5" + "angles" "qangle" "0 0 0" + "scales" "vector3" "1 1 1" + "transformLocked" "bool" "0" + "force_hidden" "bool" "0" + "editorOnly" "bool" "0" + "deformationMode" "int" "1" +} + diff --git a/Tests/Resources/vmaptest1.dmx b/Tests/Resources/vmaptest1.dmx deleted file mode 100644 index c317539..0000000 --- a/Tests/Resources/vmaptest1.dmx +++ /dev/null @@ -1,21 +0,0 @@ - -"$prefix_element$" -{ - "map_asset_references" "string_array" [ ] -} -"CMapRoot" -{ - "id" "elementid" "19f231ba-3ad6-4d36-b9d3-b3ae84c4fb69" - "name" "string" "root" - "isprefab" "bool" "0" - "showgrid" "bool" "1" - "snaprotationangle" "int" "15" - "gridspacing" "int" "64" - "show3dgrid" "bool" "1" - "itemFile" "bool" "1" - "world" "CMapWorld" - { - "id" "elementid" "3405a56b-2a71-48f6-8128-e9c82446b712" - "name" "string" "world" - } -} diff --git a/Tests/Tests.cs b/Tests/Tests.cs index a648650..4b22446 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -10,6 +10,8 @@ using DM = Datamodel.Datamodel; using System.Text; using System.Globalization; +using ValveResourceFormat.IO.ContentFormats.ValveMap; +using System.ComponentModel.DataAnnotations; namespace Datamodel_Tests { @@ -298,30 +300,38 @@ public static void TypedArrayAddingRemoving() [Test] public void Create_Datamodel_Vmap() { - using var datamodel = new DM("vmap", 29); - datamodel.PrefixAttributes.Add("map_asset_references", new List()); - datamodel.Root = new Element(datamodel, "root", classNameOverride: "CMapRoot") - { - ["isprefab"] = false, - ["showgrid"] = true, - ["snaprotationangle"] = 15, - ["gridspacing"] = 64, - ["show3dgrid"] = true, - ["itemFile"] = true, - ["world"] = new Element(datamodel, "world", classNameOverride: "CMapWorld"), - }; - - using var stream = new MemoryStream(); - datamodel.Save(stream, "keyvalues2", 4); - Assert.That(stream.Length, Is.GreaterThan(0)); - - using var actual = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "vmaptest1.dmx")); + //using var datamodel = new DM("vmap", 29); + //datamodel.PrefixAttributes.Add("map_asset_references", new List()); + //datamodel.Root = new Element(datamodel, "root", classNameOverride: "CMapRootElement") + //{ + // ["isprefab"] = false, + // ["showgrid"] = true, + // ["snaprotationangle"] = 15, + // ["gridspacing"] = 64, + // ["show3dgrid"] = true, + // ["itemFile"] = true, + // ["world"] = new Element(datamodel, "world", classNameOverride: "CMapWorld"), + //}; + // + //using var stream = new MemoryStream(); + //datamodel.Save(stream, "keyvalues2", 4); + //Assert.That(stream.Length, Is.GreaterThan(0)); + + using var actual = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); + + CMapRootElement root = (CMapRootElement)actual.Root; + var world = root.world; + var prop = (CMapEntity)world.children[1]; + var propProperties = prop.EntityProperties; + + var classname = propProperties.Get("classname"); + + var meshes = world.children.Where(i => i.ClassName == "CMapMesh"); //Assert.That(actual.PrefixAttributes.ContainsKey("map_asset_references"), Is.True); //Assert.That(actual.PrefixAttributes["map_asset_references"], Is.Empty); Assert.That(actual.Root, Is.Not.Null); Assert.That(actual.Root["world"], Is.Not.Null); - Assert.That(actual.Root["world"], Is.EqualTo(datamodel.Root["world"])); } public class NullOwnerElement diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 68c1b65..6c3874e 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -11,6 +11,12 @@ PreserveNewest + + + + + + diff --git a/Tests/vmap.cs b/Tests/vmap.cs new file mode 100644 index 0000000..520591b --- /dev/null +++ b/Tests/vmap.cs @@ -0,0 +1,370 @@ +using Datamodel.Format; +using System.Numerics; +using DMElement = Datamodel.Element; + +namespace ValveResourceFormat.IO.ContentFormats.ValveMap; + +/// +/// Valve Map (VMAP) format version 29. +/// +internal class CMapRootElement : DMElement +{ + public bool IsPrefab { get; set; } + public int EditorBuild { get; set; } = 8600; + public int EditorVersion { get; set; } = 400; + public bool ShowGrid { get; set; } = true; + public int SnapRotationAngle { get; set; } = 15; + public float GridSpacing { get; set; } = 64; + public bool Show3DGrid { get; set; } = true; + [DMProperty(name: "itemFile")] + public string ItemFile { get; set; } = string.Empty; + public CStoredCamera DefaultCamera { get; set; } = []; + [DMProperty(name: "3dcameras")] + public CStoredCameras Cameras { get; set; } = []; + public CMapWorld world { get; set; } = []; + public CVisibilityMgr Visibility { get; set; } = []; + [DMProperty(name: "mapVariables")] + public CMapVariableSet MapVariables { get; set; } = []; + [DMProperty(name: "rootSelectionSet")] + public CMapSelectionSet RootSelectionSet { get; set; } = []; + [DMProperty(name: "m_ReferencedMeshSnapshots")] + public Datamodel.ElementArray ReferencedMeshSnapshots { get; set; } = []; + [DMProperty(name: "m_bIsCordoning")] + public bool IsCordoning { get; set; } + [DMProperty(name: "m_bCordonsVisible")] + public bool CordonsVisible { get; set; } + [DMProperty(name: "nodeInstanceData")] + public Datamodel.ElementArray NodeInstanceData { get; set; } = []; +} + + +internal class CStoredCamera : DMElement +{ + public Vector3 Position { get; set; } = new Vector3(0, -1000, 1000); + public Vector3 LookAt { get; set; } +} + + +internal class CStoredCameras : DMElement +{ + [DMProperty(name: "activecamera")] + public int ActiveCameraIndex { get; set; } = -1; + public Datamodel.ElementArray Cameras { get; set; } = []; +} + + +internal abstract class MapNode : DMElement +{ + public Vector3 origin { get; set; } + public Datamodel.QAngle angles { get; set; } + public Vector3 scales { get; set; } = new Vector3(1, 1, 1); + + public int nodeID { get; set; } + public ulong referenceID { get; set; } + + public Datamodel.ElementArray children { get; set; } = []; + + public bool editorOnly { get; set; } + [DMProperty(name: "force_hidden")] + public bool ForceHidden { get; set; } + public bool transformLocked { get; set; } + public Datamodel.StringArray variableTargetKeys { get; set; } = []; + public Datamodel.StringArray variableNames { get; set; } = []; +} + +internal class CMapPrefab : MapNode +{ + public bool FixupEntityNames { get; set; } = true; + public bool LoadAtRuntime { get; set; } + public bool LoadIfNested { get; set; } = true; + public string TargetMapPath { get; set; } = string.Empty; + public string TargetName { get; set; } = string.Empty; +} + + +internal abstract class BaseEntity : MapNode +{ + public DmePlugList RelayPlugData { get; set; } = []; + public Datamodel.ElementArray ConnectionsData { get; set; } = []; + [DMProperty(name: "entity_properties")] + public EditGameClassProps EntityProperties { get; set; } = []; + + public BaseEntity WithProperty(string name, string value) + { + EntityProperties[name] = value; + return this; + } + + public BaseEntity WithProperties(params (string name, string value)[] properties) + { + foreach (var (name, value) in properties) + { + EntityProperties[name] = value; + } + + return this; + } + + public BaseEntity WithClassName(string className) + => WithProperty("classname", className); +} + + +internal class DmePlugList : DMElement +{ + public Datamodel.StringArray Names { get; set; } = []; + public Datamodel.IntArray DataTypes { get; set; } = []; + public Datamodel.IntArray PlugTypes { get; set; } = []; + public Datamodel.StringArray Descriptions { get; set; } = []; +} + + +internal class DmeConnectionData : DMElement +{ + public string OutputName { get; set; } = string.Empty; + public int TargetType { get; set; } + public string TargetName { get; set; } = string.Empty; + public string InputName { get; set; } = string.Empty; + public string OverrideParam { get; set; } = string.Empty; + public float Delay { get; set; } + public int TimesToFire { get; set; } = -1; +} + +/// +/// A string->string dictionary. This stores entity KeyValues. +/// +internal class EditGameClassProps : DMElement +{ +} + +/// +/// The world entity. +/// + +internal class CMapWorld : BaseEntity +{ + public int NextDecalID { get; set; } + public bool FixupEntityNames { get; set; } = true; + public string MapUsageType { get; set; } = "standard"; + + public CMapWorld() + { + EntityProperties["classname"] = "worldspawn"; + } +} + + +internal class CVisibilityMgr : MapNode +{ + public Datamodel.ElementArray Nodes { get; set; } = []; + public Datamodel.IntArray HiddenFlags { get; set; } = []; +} + + +internal class CMapVariableSet : DMElement +{ + public Datamodel.StringArray VariableNames { get; set; } = []; + public Datamodel.StringArray VariableValues { get; set; } = []; + public Datamodel.StringArray VariableTypeNames { get; set; } = []; + public Datamodel.StringArray VariableTypeParameters { get; set; } = []; + [DMProperty(name: "m_ChoiceGroups")] + public Datamodel.ElementArray ChoiceGroups { get; set; } = []; +} + + +internal class CMapSelectionSet : DMElement +{ + public Datamodel.ElementArray Children { get; set; } = []; + public string SelectionSetName { get; set; } = string.Empty; + public CObjectSelectionSetDataElement SelectionSetData { get; set; } = []; + + public CMapSelectionSet() { } + public CMapSelectionSet(string name) + { + SelectionSetName = name; + } +} + + +internal class CObjectSelectionSetDataElement : DMElement +{ + public Datamodel.ElementArray SelectedObjects { get; set; } = []; +} + + +internal class CMapEntity : BaseEntity +{ + public Vector3 HitNormal { get; set; } + public bool IsProceduralEntity { get; set; } +} + + +internal class CMapInstance : BaseEntity +{ + /// + /// A target to instance. With custom tint and transform. + /// + public DMElement? Target { get; set; } + public Datamodel.Color TintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); +} + +internal class CMapGroup : MapNode +{ +} + + +internal class CMapWorldLayer : CMapGroup +{ + public string WorldLayerName { get; set; } = string.Empty; +} + + +internal class CMapMesh : MapNode +{ + public string CubeMapName { get; set; } = string.Empty; + public string LightGroup { get; set; } = string.Empty; + [DMProperty(name: "visexclude")] + public bool VisExclude { get; set; } + [DMProperty(name: "renderwithdynamic")] + public bool RenderWithDynamic { get; set; } + public bool DisableHeightDisplacement { get; set; } + [DMProperty(name: "fademindist")] + public float FadeMinDist { get; set; } = -1; + [DMProperty(name: "fademaxdist")] + public float FadeMaxDist { get; set; } + [DMProperty(name: "bakelighting")] + public bool BakeLighting { get; set; } = true; + [DMProperty(name: "precomputelightprobes")] + public bool PrecomputeLightProbes { get; set; } = true; + public bool RenderToCubemaps { get; set; } = true; + public int DisableShadows { get; set; } + public float SmoothingAngle { get; set; } = 40f; + public Datamodel.Color TintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); + [DMProperty(name: "renderAmt")] + public int RenderAmount { get; set; } = 255; + public string PhysicsType { get; set; } = "default"; + public string PhysicsGroup { get; set; } = string.Empty; + public string PhysicsInteractsAs { get; set; } = string.Empty; + public string PhysicsInteractWsith { get; set; } = string.Empty; + public string PhysicsInteractsExclude { get; set; } = string.Empty; + public CDmePolygonMesh meshData { get; set; } = []; + public bool UseAsOccluder { get; set; } + public bool PhysicsSimplificationOverride { get; set; } + public float PhysicsSimplificationError { get; set; } +} + + +internal class CDmePolygonMesh : MapNode +{ + /// + /// Index to one of the edges stemming from this vertex. + /// + public Datamodel.IntArray VertexEdgeIndices { get; set; } = []; + + /// + /// Index to the streams. + /// + public Datamodel.IntArray VertexDataIndices { get; set; } = []; + + /// + /// The destination vertex of this edge. + /// + public Datamodel.IntArray EdgeVertexIndices { get; set; } = []; + + /// + /// Index to the opposite/twin edge. + /// + public Datamodel.IntArray EdgeOppositeIndices { get; set; } = []; + + /// + /// Index to the next edge in the loop, in counter-clockwise order. + /// + public Datamodel.IntArray EdgeNextIndices { get; set; } = []; + + /// + /// Per half-edge index to the adjacent face. -1 if void (open edge). + /// + public Datamodel.IntArray EdgeFaceIndices { get; set; } = []; + + /// + /// Per half-edge index to the streams. + /// + public Datamodel.IntArray EdgeDataIndices { get; set; } = []; + + /// + /// Per half-edge index to the streams. + /// + public Datamodel.IntArray EdgeVertexDataIndices { get; set; } = []; + + /// + /// Per face index to one of the *inner* edges encapsulating this face. + /// + public Datamodel.IntArray FaceEdgeIndices { get; set; } = []; + + /// + /// Per face index to the streams. + /// + public Datamodel.IntArray FaceDataIndices { get; set; } = []; + + /// + /// List of material names. Indexed by the 'meshindex' stream. + /// + public Datamodel.StringArray Materials { get; set; } = []; + + /// + /// Stores vertex positions. + /// + public CDmePolygonMeshDataArray VertexData { get; set; } = []; + + /// + /// Stores vertex uv, normal, tangent, etc. Two per vertex (for each half?). + /// + public CDmePolygonMeshDataArray FaceVertexData { get; set; } = []; + + /// + /// Stores edge data such as soft or hard normals. + /// + public CDmePolygonMeshDataArray EdgeData { get; set; } = []; + + /// + /// Stores face data such as texture scale, UV offset, material, lightmap bias. + /// + public CDmePolygonMeshDataArray FaceData { get; set; } = []; + + public CDmePolygonMeshSubdivisionData SubdivisionData { get; set; } = []; +} + + +internal class CDmePolygonMeshDataArray : DMElement +{ + public int Size { get; set; } + /// + /// Array of . + /// + public Datamodel.ElementArray Streams { get; set; } = []; +} + + +internal class CDmePolygonMeshSubdivisionData : DMElement +{ + public Datamodel.IntArray SubdivisionLevels { get; set; } = []; + /// + /// Array of . + /// + public Datamodel.ElementArray Streams { get; set; } = []; +} + + +internal class CDmePolygonMeshDataStream : DMElement +{ + public string StandardAttributeName { get; set; } = string.Empty; + public string SemanticName { get; set; } = string.Empty; + public int SemanticIndex { get; set; } + public int VertexBufferLocation { get; set; } + public int DataStateFlags { get; set; } + public DMElement? SubdivisionBinding { get; set; } + /// + /// An int, vector2, vector3, or vector4 array. + /// + public required Datamodel.Array Data { get; set; } +} From a0263674279c6aae24b83be77ddefb47deb789ae Mon Sep 17 00:00:00 2001 From: Angel Date: Mon, 19 May 2025 23:47:36 +0200 Subject: [PATCH 06/24] match vmap field names --- Tests/vmap.cs | 183 +++++++++++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/Tests/vmap.cs b/Tests/vmap.cs index 520591b..dfd9e44 100644 --- a/Tests/vmap.cs +++ b/Tests/vmap.cs @@ -2,26 +2,27 @@ using System.Numerics; using DMElement = Datamodel.Element; -namespace ValveResourceFormat.IO.ContentFormats.ValveMap; +namespace consoleTestApp.VMAP; /// /// Valve Map (VMAP) format version 29. /// internal class CMapRootElement : DMElement { - public bool IsPrefab { get; set; } - public int EditorBuild { get; set; } = 8600; - public int EditorVersion { get; set; } = 400; - public bool ShowGrid { get; set; } = true; - public int SnapRotationAngle { get; set; } = 15; - public float GridSpacing { get; set; } = 64; - public bool Show3DGrid { get; set; } = true; + public bool isprefab { get; set; } + public int editorbuild { get; set; } = 8600; + public int editorversion { get; set; } = 400; + public bool showgrid { get; set; } = true; + public int snaprotationangle { get; set; } = 15; + public float gridspacing { get; set; } = 64; + public bool show3dgrid { get; set; } = true; [DMProperty(name: "itemFile")] - public string ItemFile { get; set; } = string.Empty; - public CStoredCamera DefaultCamera { get; set; } = []; + public string Itemfile { get; set; } = string.Empty; + public CStoredCamera defaultcamera { get; set; } = []; [DMProperty(name: "3dcameras")] public CStoredCameras Cameras { get; set; } = []; public CMapWorld world { get; set; } = []; + [DMProperty(name: "visbility")] public CVisibilityMgr Visibility { get; set; } = []; [DMProperty(name: "mapVariables")] public CMapVariableSet MapVariables { get; set; } = []; @@ -40,8 +41,8 @@ internal class CMapRootElement : DMElement internal class CStoredCamera : DMElement { - public Vector3 Position { get; set; } = new Vector3(0, -1000, 1000); - public Vector3 LookAt { get; set; } + public Vector3 position { get; set; } = new Vector3(0, -1000, 1000); + public Vector3 lookat { get; set; } } @@ -49,7 +50,7 @@ internal class CStoredCameras : DMElement { [DMProperty(name: "activecamera")] public int ActiveCameraIndex { get; set; } = -1; - public Datamodel.ElementArray Cameras { get; set; } = []; + public Datamodel.ElementArray cameras { get; set; } = []; } @@ -74,18 +75,18 @@ internal abstract class MapNode : DMElement internal class CMapPrefab : MapNode { - public bool FixupEntityNames { get; set; } = true; - public bool LoadAtRuntime { get; set; } - public bool LoadIfNested { get; set; } = true; - public string TargetMapPath { get; set; } = string.Empty; - public string TargetName { get; set; } = string.Empty; + public bool fixupEntityNames { get; set; } = true; + public bool loadAtRuntime { get; set; } + public bool loadIfNested { get; set; } = true; + public string targetMapPath { get; set; } = string.Empty; + public string targetName { get; set; } = string.Empty; } internal abstract class BaseEntity : MapNode { - public DmePlugList RelayPlugData { get; set; } = []; - public Datamodel.ElementArray ConnectionsData { get; set; } = []; + public DmePlugList relayPlugData { get; set; } = []; + public Datamodel.ElementArray connectionsData { get; set; } = []; [DMProperty(name: "entity_properties")] public EditGameClassProps EntityProperties { get; set; } = []; @@ -112,22 +113,22 @@ public BaseEntity WithClassName(string className) internal class DmePlugList : DMElement { - public Datamodel.StringArray Names { get; set; } = []; - public Datamodel.IntArray DataTypes { get; set; } = []; - public Datamodel.IntArray PlugTypes { get; set; } = []; - public Datamodel.StringArray Descriptions { get; set; } = []; + public Datamodel.StringArray names { get; set; } = []; + public Datamodel.IntArray dataTypes { get; set; } = []; + public Datamodel.IntArray plugTypes { get; set; } = []; + public Datamodel.StringArray descriptions { get; set; } = []; } internal class DmeConnectionData : DMElement { - public string OutputName { get; set; } = string.Empty; - public int TargetType { get; set; } - public string TargetName { get; set; } = string.Empty; - public string InputName { get; set; } = string.Empty; - public string OverrideParam { get; set; } = string.Empty; - public float Delay { get; set; } - public int TimesToFire { get; set; } = -1; + public string outputName { get; set; } = string.Empty; + public int targetType { get; set; } + public string targetName { get; set; } = string.Empty; + public string inputName { get; set; } = string.Empty; + public string overrideParam { get; set; } = string.Empty; + public float delay { get; set; } + public int timesToFire { get; set; } = -1; } /// @@ -143,9 +144,9 @@ internal class EditGameClassProps : DMElement internal class CMapWorld : BaseEntity { - public int NextDecalID { get; set; } - public bool FixupEntityNames { get; set; } = true; - public string MapUsageType { get; set; } = "standard"; + public int nextDecalID { get; set; } + public bool fixupEntityNames { get; set; } = true; + public string mapUsageType { get; set; } = "standard"; public CMapWorld() { @@ -156,17 +157,17 @@ public CMapWorld() internal class CVisibilityMgr : MapNode { - public Datamodel.ElementArray Nodes { get; set; } = []; - public Datamodel.IntArray HiddenFlags { get; set; } = []; + public Datamodel.ElementArray nodes { get; set; } = []; + public Datamodel.IntArray hiddenFlags { get; set; } = []; } internal class CMapVariableSet : DMElement { - public Datamodel.StringArray VariableNames { get; set; } = []; - public Datamodel.StringArray VariableValues { get; set; } = []; - public Datamodel.StringArray VariableTypeNames { get; set; } = []; - public Datamodel.StringArray VariableTypeParameters { get; set; } = []; + public Datamodel.StringArray variableNames { get; set; } = []; + public Datamodel.StringArray variableValues { get; set; } = []; + public Datamodel.StringArray variableTypeNames { get; set; } = []; + public Datamodel.StringArray variableTypeParameters { get; set; } = []; [DMProperty(name: "m_ChoiceGroups")] public Datamodel.ElementArray ChoiceGroups { get; set; } = []; } @@ -174,28 +175,28 @@ internal class CMapVariableSet : DMElement internal class CMapSelectionSet : DMElement { - public Datamodel.ElementArray Children { get; set; } = []; - public string SelectionSetName { get; set; } = string.Empty; - public CObjectSelectionSetDataElement SelectionSetData { get; set; } = []; + public Datamodel.ElementArray children { get; set; } = []; + public string selectionSetName { get; set; } = string.Empty; + public CObjectSelectionSetDataElement selectionSetData { get; set; } = []; public CMapSelectionSet() { } public CMapSelectionSet(string name) { - SelectionSetName = name; + selectionSetName = name; } } internal class CObjectSelectionSetDataElement : DMElement { - public Datamodel.ElementArray SelectedObjects { get; set; } = []; + public Datamodel.ElementArray selectedObjects { get; set; } = []; } internal class CMapEntity : BaseEntity { - public Vector3 HitNormal { get; set; } - public bool IsProceduralEntity { get; set; } + public Vector3 hitNormal { get; set; } + public bool isProceduralEntity { get; set; } } @@ -204,8 +205,8 @@ internal class CMapInstance : BaseEntity /// /// A target to instance. With custom tint and transform. /// - public DMElement? Target { get; set; } - public Datamodel.Color TintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); + public DMElement? target { get; set; } + public Datamodel.Color tintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); } internal class CMapGroup : MapNode @@ -215,19 +216,19 @@ internal class CMapGroup : MapNode internal class CMapWorldLayer : CMapGroup { - public string WorldLayerName { get; set; } = string.Empty; + public string worldLayerName { get; set; } = string.Empty; } internal class CMapMesh : MapNode { - public string CubeMapName { get; set; } = string.Empty; - public string LightGroup { get; set; } = string.Empty; + public string cubeMapName { get; set; } = string.Empty; + public string lightGroup { get; set; } = string.Empty; [DMProperty(name: "visexclude")] public bool VisExclude { get; set; } [DMProperty(name: "renderwithdynamic")] public bool RenderWithDynamic { get; set; } - public bool DisableHeightDisplacement { get; set; } + public bool disableHeightDisplacement { get; set; } [DMProperty(name: "fademindist")] public float FadeMinDist { get; set; } = -1; [DMProperty(name: "fademaxdist")] @@ -236,21 +237,21 @@ internal class CMapMesh : MapNode public bool BakeLighting { get; set; } = true; [DMProperty(name: "precomputelightprobes")] public bool PrecomputeLightProbes { get; set; } = true; - public bool RenderToCubemaps { get; set; } = true; - public int DisableShadows { get; set; } - public float SmoothingAngle { get; set; } = 40f; - public Datamodel.Color TintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); + public bool renderToCubemaps { get; set; } = true; + public int disableShadows { get; set; } + public float smoothingAngle { get; set; } = 40f; + public Datamodel.Color tintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); [DMProperty(name: "renderAmt")] public int RenderAmount { get; set; } = 255; - public string PhysicsType { get; set; } = "default"; - public string PhysicsGroup { get; set; } = string.Empty; - public string PhysicsInteractsAs { get; set; } = string.Empty; - public string PhysicsInteractWsith { get; set; } = string.Empty; - public string PhysicsInteractsExclude { get; set; } = string.Empty; + public string physicsType { get; set; } = "default"; + public string physicsGroup { get; set; } = string.Empty; + public string physicsInteractsAs { get; set; } = string.Empty; + public string physicsInteractWsith { get; set; } = string.Empty; + public string physicsInteractsExclude { get; set; } = string.Empty; public CDmePolygonMesh meshData { get; set; } = []; - public bool UseAsOccluder { get; set; } - public bool PhysicsSimplificationOverride { get; set; } - public float PhysicsSimplificationError { get; set; } + public bool useAsOccluder { get; set; } + public bool physicsSimplificationOverride { get; set; } + public float physicsSimplificationError { get; set; } } @@ -259,79 +260,79 @@ internal class CDmePolygonMesh : MapNode /// /// Index to one of the edges stemming from this vertex. /// - public Datamodel.IntArray VertexEdgeIndices { get; set; } = []; + public Datamodel.IntArray vertexEdgeIndices { get; set; } = []; /// /// Index to the streams. /// - public Datamodel.IntArray VertexDataIndices { get; set; } = []; + public Datamodel.IntArray vertexDataIndices { get; set; } = []; /// /// The destination vertex of this edge. /// - public Datamodel.IntArray EdgeVertexIndices { get; set; } = []; + public Datamodel.IntArray edgeVertexIndices { get; set; } = []; /// /// Index to the opposite/twin edge. /// - public Datamodel.IntArray EdgeOppositeIndices { get; set; } = []; + public Datamodel.IntArray edgeOppositeIndices { get; set; } = []; /// /// Index to the next edge in the loop, in counter-clockwise order. /// - public Datamodel.IntArray EdgeNextIndices { get; set; } = []; + public Datamodel.IntArray edgeNextIndices { get; set; } = []; /// /// Per half-edge index to the adjacent face. -1 if void (open edge). /// - public Datamodel.IntArray EdgeFaceIndices { get; set; } = []; + public Datamodel.IntArray edgeFaceIndices { get; set; } = []; /// /// Per half-edge index to the streams. /// - public Datamodel.IntArray EdgeDataIndices { get; set; } = []; + public Datamodel.IntArray edgeDataIndices { get; set; } = []; /// /// Per half-edge index to the streams. /// - public Datamodel.IntArray EdgeVertexDataIndices { get; set; } = []; + public Datamodel.IntArray edgeVertexDataIndices { get; set; } = []; /// /// Per face index to one of the *inner* edges encapsulating this face. /// - public Datamodel.IntArray FaceEdgeIndices { get; set; } = []; + public Datamodel.IntArray faceEdgeIndices { get; set; } = []; /// /// Per face index to the streams. /// - public Datamodel.IntArray FaceDataIndices { get; set; } = []; + public Datamodel.IntArray faceDataIndices { get; set; } = []; /// /// List of material names. Indexed by the 'meshindex' stream. /// - public Datamodel.StringArray Materials { get; set; } = []; + public Datamodel.StringArray materials { get; set; } = []; /// /// Stores vertex positions. /// - public CDmePolygonMeshDataArray VertexData { get; set; } = []; + public CDmePolygonMeshDataArray vertexData { get; set; } = []; /// /// Stores vertex uv, normal, tangent, etc. Two per vertex (for each half?). /// - public CDmePolygonMeshDataArray FaceVertexData { get; set; } = []; + public CDmePolygonMeshDataArray faceVertexData { get; set; } = []; /// /// Stores edge data such as soft or hard normals. /// - public CDmePolygonMeshDataArray EdgeData { get; set; } = []; + public CDmePolygonMeshDataArray edgeData { get; set; } = []; /// /// Stores face data such as texture scale, UV offset, material, lightmap bias. /// - public CDmePolygonMeshDataArray FaceData { get; set; } = []; + public CDmePolygonMeshDataArray faceData { get; set; } = []; - public CDmePolygonMeshSubdivisionData SubdivisionData { get; set; } = []; + public CDmePolygonMeshSubdivisionData subdivisionData { get; set; } = []; } @@ -341,30 +342,30 @@ internal class CDmePolygonMeshDataArray : DMElement /// /// Array of . /// - public Datamodel.ElementArray Streams { get; set; } = []; + public Datamodel.ElementArray streams { get; set; } = []; } internal class CDmePolygonMeshSubdivisionData : DMElement { - public Datamodel.IntArray SubdivisionLevels { get; set; } = []; + public Datamodel.IntArray subdivisionLevels { get; set; } = []; /// /// Array of . /// - public Datamodel.ElementArray Streams { get; set; } = []; + public Datamodel.ElementArray streams { get; set; } = []; } internal class CDmePolygonMeshDataStream : DMElement { - public string StandardAttributeName { get; set; } = string.Empty; - public string SemanticName { get; set; } = string.Empty; - public int SemanticIndex { get; set; } - public int VertexBufferLocation { get; set; } - public int DataStateFlags { get; set; } - public DMElement? SubdivisionBinding { get; set; } + public string standardAttributeName { get; set; } = string.Empty; + public string semanticName { get; set; } = string.Empty; + public int semanticIndex { get; set; } + public int vertexBufferLocation { get; set; } + public int dataStateFlags { get; set; } + public DMElement? subdivisionBinding { get; set; } /// /// An int, vector2, vector3, or vector4 array. /// - public required Datamodel.Array Data { get; set; } + public required Datamodel.Array data { get; set; } } From c3dfe424171557b3e2a04d8189f33b0f5ff300c7 Mon Sep 17 00:00:00 2001 From: Angel Date: Tue, 20 May 2025 23:23:54 +0200 Subject: [PATCH 07/24] small improvements plus fix tests --- AttributeList.cs | 15 +++++++- Codecs/Binary.cs | 15 +++----- Codecs/KeyValues2.cs | 2 +- Datamodel.cs | 19 +++++----- ICodec.cs | 48 +++++++++++++++++++++++++- Tests/Resources/error.vmap | Bin 477772 -> 412272 bytes Tests/Tests.cs | 61 ++++++++++----------------------- Tests/Tests.csproj | 3 -- Tests/{vmap.cs => ValveMap.cs} | 4 +-- 9 files changed, 98 insertions(+), 69 deletions(-) rename Tests/{vmap.cs => ValveMap.cs} (99%) diff --git a/AttributeList.cs b/AttributeList.cs index f2fa1ec..15deda2 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -249,14 +249,27 @@ public virtual object this[string name] { PropertyInfo prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance); - if (null != prop && prop.CanWrite) + if (prop != null && prop.CanWrite) { + // were actually fine with this being null, it will just set the value to null + // but need to check so the type check doesn't fail if it is null + if(value != null) + { + var valueType = value.GetType(); + if (prop.PropertyType != valueType) + { + throw new InvalidDataException($"class property '{prop.Name}' with type '{prop.PropertyType}' does not match the type '{valueType}' of the value being set, this is likely a mismatch between the real class and the class from the datamodel"); + } + } + prop.SetValue(this, value); } else { throw new InvalidDataException("Property of deserialisation class must be writeable, make sure it's public and has a public setter"); } + + return; } Attribute old_attr; diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index d706cfc..951c22d 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -346,14 +346,9 @@ object ReadValue(Datamodel dm, Type type, bool raw_string) throw new ArgumentException(type == null ? "No type provided to GetValue()" : "Cannot read value of type " + type.Name); } - public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection) + public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams) { - Dictionary callingTypes = new(); - - foreach (var classType in callingAssembly.DefinedTypes) - { - callingTypes.Add(classType.Name, classType); - } + var types = CodecUtilities.GetReflectionTypes(reflectionParams); stream.Seek(0, SeekOrigin.Begin); while (true) @@ -393,9 +388,9 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray()); Element? elem = null; - var matchedType = callingTypes.TryGetValue(type, out var classType); + var matchedType = types.TryGetValue(type, out var classType); - if(matchedType) + if(matchedType && reflectionParams.AttemptReflection) { var isElementDerived = IsElementDerived(classType); if (isElementDerived && classType.Name == type) @@ -437,7 +432,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in foreach (var i in Enumerable.Range(0, num_attrs)) { var name = StringDict.ReadString(); - if (defer_mode == DeferredMode.Automatic && attemptReflection == false) + if (defer_mode == DeferredMode.Automatic && reflectionParams.AttemptReflection == false) { CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); SkipAttribute(); diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index d01cfaf..801ffdb 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -598,7 +598,7 @@ object Decode_ParseValue(Type type, string value) else throw new ArgumentException($"Internal error: ParseValue passed unsupported type: {type}."); } - public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection) + public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams) { DM = new Datamodel(format, format_version); diff --git a/Datamodel.cs b/Datamodel.cs index e179448..6112689 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -232,18 +232,18 @@ public void Save(string path, string encoding, int encoding_version) /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) + public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) { - return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection); + return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); } /// /// Loads a Datamodel from a byte array. /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) + public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) { - return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, attemptReflection); + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, reflectionParams); } /// @@ -251,13 +251,13 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode /// /// The source file path. /// How to handle deferred loading. - public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) + public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) { var stream = File.OpenRead(path); Datamodel dm = null; try { - dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, attemptReflection); + dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); return dm; } finally @@ -266,8 +266,11 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode } } - private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, bool attemptReflection = false) + private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) { + reflectionParams ??= new(); + reflectionParams.AssembliesToSearch.Add(callingAssembly); + stream.Seek(0, SeekOrigin.Begin); var header = string.Empty; int b; @@ -293,7 +296,7 @@ private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, ICodec codec = GetCodec(encoding, encoding_version); - var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode, callingAssembly, attemptReflection); + var dm = codec.Decode(encoding, encoding_version, format, format_version, stream, defer_mode, reflectionParams); if (defer_mode == DeferredMode.Automatic && codec is IDeferredAttributeCodec deferredCodec) { dm.Stream = stream; diff --git a/ICodec.cs b/ICodec.cs index ad6b06d..8cda330 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -3,6 +3,7 @@ using System.IO; using System.Numerics; using System.Reflection; +using System.Collections.Generic; namespace Datamodel.Codecs { @@ -30,9 +31,31 @@ public interface ICodec /// The input stream. Its position will always be 0. Do not dispose. /// The deferred loading mode specified by the caller. Only relevant to implementers of /// - Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, Assembly callingAssembly, bool attemptReflection); + Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams); } + /// + /// Parameters for reflection based deserialisation + /// By default it will look for types in the calling assembly (the one which made this class) + /// + public class ReflectionParams + { + public bool AttemptReflection; + public List AdditionalTypes; + public List AssembliesToSearch; + + /// If to use reflection or not. + /// Additional types to consider when matching. + /// Additional assemblies to look for types in. + public ReflectionParams(bool attemptReflection = true, List? additionalTypes = null, List? assembliesToSearch = null) + { + AttemptReflection = attemptReflection; + AdditionalTypes = additionalTypes ??= new(); + AssembliesToSearch = assembliesToSearch ??= new(); + } + } + + /// /// Defines methods for the deferred loading of values. /// @@ -171,6 +194,29 @@ public static void AddDeferredAttribute(Element elem, string key, long offset) if (offset <= 0) throw new ArgumentOutOfRangeException(nameof(offset), "Address must be greater than 0."); elem.Add(key, offset); } + + public static Dictionary GetReflectionTypes(ReflectionParams reflectionParams) + { + Dictionary types = new(); + + if (reflectionParams.AttemptReflection) + { + foreach (var assembly in reflectionParams.AssembliesToSearch) + { + foreach (var classType in assembly.DefinedTypes) + { + types.TryAdd(classType.Name, classType); + } + } + + foreach (var type in reflectionParams.AdditionalTypes) + { + types.TryAdd(type.Name, type); + } + } + + return types; + } } [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] diff --git a/Tests/Resources/error.vmap b/Tests/Resources/error.vmap index 12a823c4aa82acc952efdf1f49154384668fe815..1a686e79d56fb394d26d2f90081c04b474712df7 100644 GIT binary patch delta 80709 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
  • 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 aC?)wOCEo&HAf@1ze@AXm%IGO2|Nj8XdIFsQ delta 148044 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
  • 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 z()); - //datamodel.Root = new Element(datamodel, "root", classNameOverride: "CMapRootElement") - //{ - // ["isprefab"] = false, - // ["showgrid"] = true, - // ["snaprotationangle"] = 15, - // ["gridspacing"] = 64, - // ["show3dgrid"] = true, - // ["itemFile"] = true, - // ["world"] = new Element(datamodel, "world", classNameOverride: "CMapWorld"), - //}; - // - //using var stream = new MemoryStream(); - //datamodel.Save(stream, "keyvalues2", 4); - //Assert.That(stream.Length, Is.GreaterThan(0)); - - using var actual = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); - - CMapRootElement root = (CMapRootElement)actual.Root; + public void LoadVmap_Reflection_Binary() + { + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); + + Assert.AreEqual(unserialisedVmap.Root.GetType(), typeof(CMapRootElement)); + + CMapRootElement root = (CMapRootElement)unserialisedVmap.Root; + + Assert.AreEqual(root.world.GetType(), typeof(CMapWorld)); + var world = root.world; - var prop = (CMapEntity)world.children[1]; - var propProperties = prop.EntityProperties; + var props = world.children.Where(i => i.ClassName == "CMapEntity").OfType().ToList(); + + var propProperties = props[0].EntityProperties; var classname = propProperties.Get("classname"); - var meshes = world.children.Where(i => i.ClassName == "CMapMesh"); + var meshes = world.children.Where(i => i.ClassName == "CMapMesh").OfType().ToList(); + var mesh = meshes[0]; - //Assert.That(actual.PrefixAttributes.ContainsKey("map_asset_references"), Is.True); - //Assert.That(actual.PrefixAttributes["map_asset_references"], Is.Empty); - Assert.That(actual.Root, Is.Not.Null); - Assert.That(actual.Root["world"], Is.Not.Null); + Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty); } public class NullOwnerElement @@ -417,16 +402,6 @@ public void PropertyAccessByKey() Assert.That(myprop, Is.EqualTo(1337)); } - [Test] - public void PropertySetByKey_Throws() - { - var elem = new CustomElement(); - - var ex = Assert.Throws(typeof(InvalidOperationException), () => elem["MyProperty"] = 5); - - Assert.That(ex.Message, Does.Contain("Cannot set the value of a property-derived attribute by key")); - } - [Test] public void CanBeAssignedToDatamodelRoot() { @@ -600,7 +575,7 @@ public void TF2_KeyValues2_1() [Test, TestCaseSource(nameof(GetDmxFiles))] public void Unserialize(string path) { - var dm = DM.Load(path); + var dm = DM.Load(path, Datamodel.Codecs.DeferredMode.Automatic); PrintContents(dm); dm.Dispose(); } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 6c3874e..2559203 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -14,9 +14,6 @@ - - - diff --git a/Tests/vmap.cs b/Tests/ValveMap.cs similarity index 99% rename from Tests/vmap.cs rename to Tests/ValveMap.cs index dfd9e44..c1da7c7 100644 --- a/Tests/vmap.cs +++ b/Tests/ValveMap.cs @@ -2,7 +2,7 @@ using System.Numerics; using DMElement = Datamodel.Element; -namespace consoleTestApp.VMAP; +namespace VMAP; /// /// Valve Map (VMAP) format version 29. @@ -205,7 +205,7 @@ internal class CMapInstance : BaseEntity /// /// A target to instance. With custom tint and transform. /// - public DMElement? target { get; set; } + public CMapGroup? target { get; set; } public Datamodel.Color tintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); } From 4b9afabfa7356d43ed5a5eb2e981cd1b5edeae48 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 01:51:56 +0200 Subject: [PATCH 08/24] finish reflection based deserialisation (add for text codec) --- Codecs/Binary.cs | 27 +----- Codecs/KeyValues2.cs | 192 ++++++++++++++++++++++++++++++--------- Datamodel.ElementList.cs | 2 +- Element.cs | 21 +++++ Tests/Tests.cs | 35 +++++-- 5 files changed, 201 insertions(+), 76 deletions(-) diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index 951c22d..33dd20e 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -392,7 +392,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in if(matchedType && reflectionParams.AttemptReflection) { - var isElementDerived = IsElementDerived(classType); + var isElementDerived = Element.IsElementDerived(classType); if (isElementDerived && classType.Name == type) { Type derivedType = classType; @@ -425,7 +425,8 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in // read attributes (or not, if we're deferred) foreach (var elem in dm.AllElements.ToArray()) { - System.Diagnostics.Debug.Assert(!elem.Stub); + // assert if stub + Debug.Assert(!elem.Stub); var num_attrs = Reader.ReadInt32(); @@ -798,28 +799,6 @@ void WriteAttribute(object value, bool in_array) } } - bool IsElementDerived(Type type) - { - var elementType = typeof(Element); - - while (type.BaseType != elementType) - { - var baseType = type.BaseType; - - if (baseType != null) - { - type = baseType; - } - else - { - return type == elementType ? true : false; - } - - } - - return type.BaseType == elementType ? true : false; - } - class DmxBinaryWriter : BinaryWriter { public DmxBinaryWriter(Stream output) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 801ffdb..3a61f49 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -7,6 +7,8 @@ using System.Runtime.CompilerServices; using System.Globalization; using System.Reflection; +using System.Xml.Linq; +using System.Collections; namespace Datamodel.Codecs { @@ -292,7 +294,7 @@ void WriteAttribute(string name, Type type, object value, bool in_array) else if (type == typeof(Matrix4x4)) { var castValue = (Matrix4x4)value; - var matrixString = + var matrixString = $"{castValue.M11} {castValue.M12} {castValue.M13} {castValue.M14}" + $" {castValue.M21} {castValue.M22} {castValue.M23} {castValue.M24}" + $" {castValue.M31} {castValue.M32} {castValue.M33} {castValue.M34}" + @@ -397,6 +399,44 @@ public void Encode(Datamodel dm, string encoding, int encoding_version, Stream s #endregion #region Decode + + private class IntermediateData + { + // these store element refs while we process the elements, once were done + // we can go trough these and actually create the attributes + // and add the elements to lists + public Dictionary> PropertiesToAdd = new(); + public Dictionary> ListRefs = new(); + + public void HandleElementProp(Element element, string attrName, Guid id) + { + + PropertiesToAdd.TryGetValue(element, out var attrList); + + if (attrList == null) + { + attrList = new List<(string, Guid)>(); + PropertiesToAdd.Add(element, attrList); + } + + attrList.Add((attrName, id)); + + } + + public void HandleListRefs(IList list, Guid id) + { + ListRefs.TryGetValue(list, out var guidList); + + if (guidList == null) + { + guidList = new List(); + ListRefs.Add(list, guidList); + } + + guidList.Add(id); + } + } + readonly StringBuilder TokenBuilder = new(); int Line = 0; string Decode_NextToken() @@ -441,31 +481,17 @@ string Decode_NextToken() } } - Element Decode_ParseElementId() - { - Element elem; - var id_s = Decode_NextToken(); - - if (string.IsNullOrEmpty(id_s)) - elem = null; - else - { - Guid id = new(id_s); - elem = DM.AllElements[id]; - elem ??= new Element(DM, id); - } - return elem; - } - - Element Decode_ParseElement(string class_name) + Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams, IntermediateData intermediateData) { string elem_class = class_name ?? Decode_NextToken(); string elem_name = null; string elem_id = null; Element elem = null; + var types = CodecUtilities.GetReflectionTypes(reflectionParams); + string next = Decode_NextToken(); - if (next != "{") throw new CodecException(String.Format("Expected Element opener, got '{0}'.", next)); + if (next != "{") throw new CodecException($"Expected Element opener, got '{next}'."); while (true) { next = Decode_NextToken(); @@ -479,16 +505,41 @@ Element Decode_ParseElement(string class_name) { elem_id = Decode_NextToken(); var id = new Guid(elem_id); - var local_element = DM.AllElements[id]; - if (local_element != null) + if (elem_class != "$prefix_element$") { - elem = local_element; - elem.Name = elem_name; - elem.ClassName = elem_class; - elem.Stub = false; + var matchedType = types.TryGetValue(elem_class, out var classType); + + if (matchedType && reflectionParams.AttemptReflection) + { + var isElementDerived = Element.IsElementDerived(classType); + if (isElementDerived && classType.Name == elem_class) + { + Type derivedType = classType; + + ConstructorInfo? constructor = typeof(Element).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + new Type[] { typeof(Datamodel), typeof(string), typeof(Guid), typeof(string) }, + null + ); + + if (constructor == null) + { + throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); + } + + object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); + constructor.Invoke(uninitializedObject, new object[] { DM, elem_name, new Guid(elem_id), elem_class }); + + elem = (Element?)uninitializedObject; + } + } + + if (elem == null) + { + elem = new Element(DM, elem_name, new Guid(elem_id), elem_class); + } } - else if (elem_class != "$prefix_element$") - elem = new Element(DM, elem_name, new Guid(elem_id), elem_class); continue; } @@ -501,19 +552,21 @@ Element Decode_ParseElement(string class_name) continue; } - if (elem == null) - continue; - if (attr_type_s == "element") { - elem.Add(attr_name, Decode_ParseElementId()); + var id_s = Decode_NextToken(); + + if (!string.IsNullOrEmpty(id_s)) + { + intermediateData.HandleElementProp(elem, attr_name, new Guid(id_s)); + } continue; } object attr_value = null; if (attr_type == null) - attr_value = Decode_ParseElement(attr_type_s); + attr_value = Decode_ParseElement(attr_type_s, reflectionParams, intermediateData); else if (attr_type_s.EndsWith("_array")) { var array = CodecUtilities.MakeList(attr_type, 5); // assume 5 items @@ -527,15 +580,28 @@ Element Decode_ParseElement(string class_name) if (next == "]") break; if (next == "element") // Element ID reference - array.Add(Decode_ParseElementId()); - else if (attr_type == typeof(Element)) // inline Element - array.Add(Decode_ParseElement(next)); - else // normal value - array.Add(Decode_ParseValue(attr_type, next)); + { + var id_s = Decode_NextToken(); + + if (!string.IsNullOrEmpty(id_s)) + { + intermediateData.HandleListRefs(array, new Guid(id_s)); + } + } + // inline Element + else if (attr_type == typeof(Element)) + { + array.Add(Decode_ParseElement(next, reflectionParams, intermediateData)); + } + // normal value + else + { + array.Add(Decode_ParseValue(attr_type, next, reflectionParams, intermediateData)); + } } } else - attr_value = Decode_ParseValue(attr_type, Decode_NextToken()); + attr_value = Decode_ParseValue(attr_type, Decode_NextToken(), reflectionParams, intermediateData); if (elem != null) elem.Add(attr_name, attr_value); @@ -545,7 +611,7 @@ Element Decode_ParseElement(string class_name) return elem; } - object Decode_ParseValue(Type type, string value) + object Decode_ParseValue(Type type, string value, ReflectionParams reflectionParams, IntermediateData intermediateData) { if (type == typeof(string)) return value; @@ -553,7 +619,7 @@ object Decode_ParseValue(Type type, string value) value = value.Trim(); if (type == typeof(Element)) - return Decode_ParseElement(value); + return Decode_ParseElement(value, reflectionParams, intermediateData); if (type == typeof(int)) return int.Parse(value, CultureInfo.InvariantCulture); else if (type == typeof(float)) @@ -562,11 +628,34 @@ object Decode_ParseValue(Type type, string value) return byte.Parse(value, CultureInfo.InvariantCulture) == 1; else if (type == typeof(byte[])) { + // need to sanitise input here because for example when exporting map as txt, + // hammer will format the binary data to fit nicer on the screen by inserting two tabs + var sb = new StringBuilder(value.Length); + foreach (char c in value) + { + switch (c) + { + case '\\': + case '\r': + case '\n': + case '\t': + case ' ': + continue; + default: + sb.Append(c); + break; + } + } + value = sb.ToString(); + byte[] result = new byte[value.Length / 2]; + for (int i = 0; i * 2 < value.Length; i++) { - result[i] = byte.Parse(value.AsSpan(i * 2, 2), System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); + var slice = value.AsSpan(i * 2, 2); + result[i] = byte.Parse(slice, System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); } + return result; } else if (type == typeof(TimeSpan)) @@ -611,6 +700,8 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in Line = 1; string next; + var intermediateData = new IntermediateData(); + while (true) { try @@ -619,11 +710,28 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in { break; } try - { Decode_ParseElement(next); } + { Decode_ParseElement(next, reflectionParams, intermediateData); } catch (Exception err) { throw new CodecException($"KeyValues2 decode failed on line {Line}:\n\n{err.Message}", err); } } + foreach (var propList in intermediateData.PropertiesToAdd) + { + foreach (var prop in propList.Value) + { + propList.Key.Add(prop.Item1, DM.AllElements[prop.Item2]); + } + + } + + foreach (var list in intermediateData.ListRefs) + { + foreach (var id in list.Value) + { + list.Key.Add(DM.AllElements[id]); + } + } + return DM; } #endregion diff --git a/Datamodel.ElementList.cs b/Datamodel.ElementList.cs index 5febb9c..8764942 100644 --- a/Datamodel.ElementList.cs +++ b/Datamodel.ElementList.cs @@ -8,7 +8,7 @@ namespace Datamodel { -public partial class Datamodel + public partial class Datamodel { /// /// A collection of s owned by a single . diff --git a/Element.cs b/Element.cs index f91eab1..b48a722 100644 --- a/Element.cs +++ b/Element.cs @@ -381,6 +381,27 @@ public bool Equals(Element other) { return other != null && ID == other.ID; } + + public static bool IsElementDerived(Type type) + { + var elementType = typeof(Element); + + while (type.BaseType != elementType) + { + var baseType = type.BaseType; + + if (baseType != null) + { + type = baseType; + } + else + { + return type == elementType ? true : false; + } + } + + return type.BaseType == elementType ? true : false; + } } namespace TypeConverters diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 4037921..1cbea46 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -294,22 +294,24 @@ public static void TypedArrayAddingRemoving() Assert.AreEqual(0, array.Count); } - - [Test] - public void LoadVmap_Reflection_Binary() + private void Test_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) { - var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); - - Assert.AreEqual(unserialisedVmap.Root.GetType(), typeof(CMapRootElement)); + Assert.AreEqual(typeof(CMapRootElement), unserialisedVmap.Root.GetType()); CMapRootElement root = (CMapRootElement)unserialisedVmap.Root; - Assert.AreEqual(root.world.GetType(), typeof(CMapWorld)); + Assert.AreEqual(typeof(CMapWorld), root.world.GetType()); var world = root.world; var props = world.children.Where(i => i.ClassName == "CMapEntity").OfType().ToList(); + var prop = props[0]; + var propclass = prop.ClassName; + var proptype = prop.GetType(); + var entityprop = prop; + + var propProperties = props[0].EntityProperties; var classname = propProperties.Get("classname"); @@ -317,6 +319,21 @@ public void LoadVmap_Reflection_Binary() var mesh = meshes[0]; Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty); + + } + + [Test] + public void LoadVmap_Reflection_Binary() + { + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "de_inferno_d.vmap")); + Test_Vmap_Reflection(unserialisedVmap); + } + + [Test] + public void LoadVmap_Reflection_Text() + { + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "de_mirage_d.vmap.txt")); + Test_Vmap_Reflection(unserialisedVmap); } public class NullOwnerElement @@ -449,7 +466,7 @@ public void SerializesText() using var stream = new MemoryStream(); dm.Save(stream, "keyvalues2", 4); - + stream.Position = 0; using (var reader = new StreamReader(stream)) { @@ -469,7 +486,7 @@ public void SerializesText() // binary using var stream2 = new MemoryStream(); dm.Save(stream2, "binary", 9); - + stream2.Position = 0; using var reader2 = new BinaryReader(stream2); var bytes = reader2.ReadBytes((int)stream2.Length); From 19ad0a07147327afdf9aa95db18ee6b95f291461 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 01:53:12 +0200 Subject: [PATCH 09/24] bump ver --- Datamodel.NET.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datamodel.NET.csproj b/Datamodel.NET.csproj index 032fdd5..aacd499 100644 --- a/Datamodel.NET.csproj +++ b/Datamodel.NET.csproj @@ -4,7 +4,7 @@ Library Datamodel KeyValues2 - 0.6 + 0.7 COPYING README.md false From b4adf7cc56c27c043771dbc3fd08843b9594068e Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 02:33:35 +0200 Subject: [PATCH 10/24] make some files follow nullability --- Arrays.cs | 42 ++++++++++++++++++++--------- Attribute.cs | 14 +++++----- AttributeList.cs | 6 ++--- Codecs/Binary.cs | 6 ++--- Datamodel.NET.csproj | 1 + Datamodel.NET.sln | 11 -------- Element.cs | 63 ++++++++++++++++++++++++++------------------ Tests/Tests.cs | 4 +-- 8 files changed, 83 insertions(+), 64 deletions(-) diff --git a/Arrays.cs b/Arrays.cs index 04a7010..b587764 100644 --- a/Arrays.cs +++ b/Arrays.cs @@ -24,7 +24,7 @@ public DebugView(Array arr) protected List Inner; - public virtual AttributeList Owner + public virtual AttributeList? Owner { get => _Owner; internal set @@ -32,9 +32,9 @@ internal set _Owner = value; } } - AttributeList _Owner; + AttributeList? _Owner; - protected Datamodel OwnerDatamodel => Owner?.Owner; + protected Datamodel? OwnerDatamodel => Owner?.Owner; internal Array() { @@ -94,7 +94,7 @@ public void CopyTo(T[] array, int offset) public object SyncRoot => throw new NotImplementedException(); - object IList.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + object? IList.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public bool Remove(T item) => Inner.Remove(item); @@ -102,32 +102,44 @@ public void CopyTo(T[] array, int offset) IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator(); #region IList - int IList.Add(object value) + int IList.Add(object? value) { - Add((T)value); + if (value is not null) + Add((T)value); return Count; } - bool IList.Contains(object value) + bool IList.Contains(object? value) { + if (value is null) + return false; return Contains((T)value); } - int IList.IndexOf(object value) + int IList.IndexOf(object? value) { + if (value is null) + throw new InvalidOperationException("Trying to get the index of a null object"); + return IndexOf((T)value); } - void IList.Insert(int index, object value) + void IList.Insert(int index, object? value) { + if (value is null) + throw new InvalidOperationException("Trying to insert a null object"); + Insert(index, (T)value); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return false; } } - void IList.Remove(object value) + void IList.Remove(object? value) { + if (value is null) + throw new InvalidOperationException("Trying to remove a null object"); + Remove((T)value); } @@ -156,7 +168,7 @@ public ElementArray(int capacity) /// internal IEnumerable RawList { get { foreach (var elem in Inner) yield return elem; } } - public override AttributeList Owner + public override AttributeList? Owner { get => base.Owner; internal set @@ -191,7 +203,7 @@ protected override void Insert_Internal(int index, Element item) throw new ElementOwnershipException(); } - base.Insert_Internal(index, item); + base.Insert_Internal(index, item!); } public override Element this[int index] @@ -210,6 +222,12 @@ public override Element this[int index] throw new DestubException(this, index, err); } } + + if (elem is null) + { + throw new InvalidOperationException("Element at specified index is null"); + } + return elem; } set => base[index] = value; diff --git a/Attribute.cs b/Attribute.cs index 6d9055a..71049f6 100644 --- a/Attribute.cs +++ b/Attribute.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Numerics; -using AttrKVP = System.Collections.Generic.KeyValuePair; +using AttrKVP = System.Collections.Generic.KeyValuePair; namespace Datamodel { @@ -16,7 +16,7 @@ class Attribute /// /// The name of the Attribute, which must be unique to its owner. /// The value of the Attribute, which must be of a supported Datamodel type. - public Attribute(string name, AttributeList owner, object value) + public Attribute(string name, AttributeList owner, object? value) { ArgumentNullException.ThrowIfNull(name); @@ -48,7 +48,7 @@ public Attribute(string name, AttributeList owner, long defer_offset) /// /// Gets the Type of this Attribute's Value. /// - public Type ValueType { get; private set; } + public Type ValueType { get; private set; } = typeof(Element); /// /// Gets or sets the OverrideType of this Attributes. @@ -97,7 +97,7 @@ internal set } AttributeList _Owner; - Datamodel OwnerDatamodel { get { return Owner?.Owner; } } + Datamodel? OwnerDatamodel { get { return Owner.Owner; } } /// /// Gets whether the value of this Attribute has yet to be decoded. @@ -138,7 +138,7 @@ public void DeferredLoad() /// /// Thrown when deferred value loading fails. /// Thrown when Element destubbing fails. - public object Value + public object? Value { get { @@ -197,12 +197,12 @@ public object Value Offset = 0; } } - object _Value; + object? _Value = null; /// /// Gets the Attribute's Value without attempting deferred loading or destubbing. /// - public object RawValue { get { return _Value; } } + public object? RawValue { get { return _Value; } } #endregion diff --git a/AttributeList.cs b/AttributeList.cs index 15deda2..0038eb7 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -83,7 +83,7 @@ public enum OverrideType Binary, } - public AttributeList(Datamodel owner) + public AttributeList(Datamodel? owner) { var propertyAttributes = GetPropertyDerivedAttributeList(); PropertyInfos = new OrderedDictionary(propertyAttributes?.Count ?? 0); @@ -102,7 +102,7 @@ public AttributeList(Datamodel owner) /// /// Gets the that this AttributeList is owned by. /// - public virtual Datamodel Owner { get; internal set; } + public virtual Datamodel? Owner { get; internal set; } /// /// Adds a new attribute to this AttributeList. @@ -115,7 +115,7 @@ public void Add(string key, object value) } - protected virtual ICollection<(string Name, PropertyInfo Property)> GetPropertyDerivedAttributeList() + protected virtual ICollection<(string Name, PropertyInfo Property)>? GetPropertyDerivedAttributeList() { return null; } diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index 33dd20e..804fa68 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -267,7 +267,7 @@ float[] ReadVector(int dim) return output; } - object ReadValue(Datamodel dm, Type type, bool raw_string) + object? ReadValue(Datamodel dm, Type type, bool raw_string) { if (type == typeof(Element)) { @@ -450,13 +450,13 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in int EncodingVersion; - public object DeferredDecodeAttribute(Datamodel dm, long offset) + public object? DeferredDecodeAttribute(Datamodel dm, long offset) { Reader.BaseStream.Seek(offset, SeekOrigin.Begin); return DecodeAttribute(dm, false); } - object DecodeAttribute(Datamodel dm, bool prefix) + object? DecodeAttribute(Datamodel dm, bool prefix) { var types = IdToType(Reader.ReadByte()); diff --git a/Datamodel.NET.csproj b/Datamodel.NET.csproj index aacd499..988672b 100644 --- a/Datamodel.NET.csproj +++ b/Datamodel.NET.csproj @@ -5,6 +5,7 @@ Datamodel KeyValues2 0.7 + enable COPYING README.md false diff --git a/Datamodel.NET.sln b/Datamodel.NET.sln index d415882..322437d 100644 --- a/Datamodel.NET.sln +++ b/Datamodel.NET.sln @@ -6,11 +6,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datamodel.NET", "Datamodel. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{4C928D60-5E48-4C0D-9C7E-C75D9734CD58}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "..\ConsoleApp1\ConsoleApp1.csproj", "{B4982001-13F5-4CD3-953E-F438FC42699E}" - ProjectSection(ProjectDependencies) = postProject - {075743A9-B292-410C-B68F-6E6CF588D60A} = {075743A9-B292-410C-B68F-6E6CF588D60A} - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,12 +25,6 @@ Global {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Documentation|Any CPU.Build.0 = Debug|Any CPU {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C928D60-5E48-4C0D-9C7E-C75D9734CD58}.Release|Any CPU.Build.0 = Release|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Documentation|Any CPU.ActiveCfg = Release|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Documentation|Any CPU.Build.0 = Release|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4982001-13F5-4CD3-953E-F438FC42699E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Element.cs b/Element.cs index b48a722..6bd7143 100644 --- a/Element.cs +++ b/Element.cs @@ -27,7 +27,7 @@ public class Element : AttributeList, IEquatable /// An arbitrary string. Does not have to be unique, and can be null. /// An arbitrary string which loosely defines the type of Element this is. Cannot be null. /// Thrown when the owner already contains the maximum number of Elements allowed in a Datamodel. - public Element(Datamodel owner, string name, Guid? id = null, string classNameOverride = null) + public Element(Datamodel owner, string name, Guid? id = null, string classNameOverride = "") : base(owner) { ArgumentNullException.ThrowIfNull(owner); @@ -104,7 +104,7 @@ public string Name _Name = value; OnPropertyChanged(); } } - string _Name; + string _Name = string.Empty; /// /// Gets or sets the class of this Element. This is a string which loosely defines what s the Element contains. @@ -137,7 +137,7 @@ public bool Stub /// /// Gets the that this Element is owned by. /// - public new Datamodel Owner + public new Datamodel? Owner { get => base.Owner; internal set @@ -162,7 +162,7 @@ internal set #region Properties // TODO: this could probably be sped up by caching the properties somehow - protected override ICollection<(string Name, PropertyInfo Property)> GetPropertyDerivedAttributeList() + protected override ICollection<(string Name, PropertyInfo Property)>? GetPropertyDerivedAttributeList() { var type = GetType(); if (type == typeof(Element)) @@ -174,7 +174,14 @@ internal set foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { // Check if the property is an auto-property and is declared by a subclass of Element - if (property.GetIndexParameters().Length == 0 && property.DeclaringType.IsSubclassOf(typeof(Element))) + var declaringType = property.DeclaringType; + + if(declaringType is null) + { + throw new InvalidOperationException("Declaring type is null"); + } + + if (property.GetIndexParameters().Length == 0 && declaringType.IsSubclassOf(typeof(Element))) { var name = property.Name; name = property.GetCustomAttribute()?.Name ?? name; @@ -198,14 +205,14 @@ internal set /// Thrown when the value of name is null. /// Thrown when the value of the requested Attribute is not compatible with T. /// Thrown when an attempt is made to get a name that is not present on this Element. - public T Get(string name) + public T? Get(string name) { object value = this[name]; if (value is not T && !(typeof(T) == typeof(Element) && value == null)) throw new AttributeTypeException(string.Format("Attribute \"{0}\" ({1}) does not implement {2}.", name, value.GetType().Name, typeof(T).Name)); - return (T)value; + return (T?)value; } /// @@ -218,7 +225,7 @@ public T Get(string name) /// Thrown when the value of name is null. /// Thrown when the value of the requested Attribute is not compatible with IList<T>. /// Thrown when an attempt is made to get a name that is not present on this Element. - public IList GetArray(string name) + public IList? GetArray(string name) { try { @@ -278,9 +285,9 @@ public class NameComparer : IEqualityComparer, IEqualityComparer /// public static NameComparer Default { get; } = new NameComparer(); - public bool Equals(Element x, Element y) + public bool Equals(Element? x, Element? y) { - return x.Name == y.Name; + return x?.Name == y?.Name; } public int GetHashCode(Element obj) @@ -288,9 +295,9 @@ public int GetHashCode(Element obj) return obj.Name.GetHashCode(); } - bool IEqualityComparer.Equals(object x, object y) + bool IEqualityComparer.Equals(object? x, object? y) { - return Equals((Element)x, (Element)y); + return Equals((Element?)x, (Element?)y); } int IEqualityComparer.GetHashCode(object obj) @@ -308,9 +315,9 @@ public class ClassNameComparer : IEqualityComparer, IEqualityComparer /// public static ClassNameComparer Default { get; } = new ClassNameComparer(); - public bool Equals(Element x, Element y) + public bool Equals(Element? x, Element? y) { - return x.ClassName == y.ClassName; + return x?.ClassName == y?.ClassName; } public int GetHashCode(Element obj) @@ -318,9 +325,9 @@ public int GetHashCode(Element obj) return obj.ClassName.GetHashCode(); } - bool IEqualityComparer.Equals(object x, object y) + bool IEqualityComparer.Equals(object? x, object? y) { - return Equals((Element)x, (Element)y); + return Equals((Element?)x, (Element?)y); } int IEqualityComparer.GetHashCode(object obj) @@ -338,9 +345,9 @@ public class IDComparer : IEqualityComparer, IEqualityComparer /// public static IDComparer Default { get; } = new IDComparer(); - public bool Equals(Element x, Element y) + public bool Equals(Element? x, Element? y) { - return x.ID == y.ID; + return x?.ID == y?.ID; } public int GetHashCode(Element obj) @@ -348,9 +355,9 @@ public int GetHashCode(Element obj) return obj.ID.GetHashCode(); } - bool IEqualityComparer.Equals(object x, object y) + bool IEqualityComparer.Equals(object? x, object? y) { - return Equals((Element)x, (Element)y); + return Equals((Element?)x, (Element?)y); } int IEqualityComparer.GetHashCode(object obj) @@ -377,7 +384,7 @@ public override bool ContainsKey(string key) return base.ContainsKey(key); } - public bool Equals(Element other) + public bool Equals(Element? other) { return other != null && ID == other.ID; } @@ -408,13 +415,13 @@ namespace TypeConverters { public class ElementConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { if (sourceType == typeof(string) || sourceType == typeof(Guid)) return true; return base.CanConvertFrom(context, sourceType); } - public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + public override object? ConvertFrom(ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) { Guid guid_value; @@ -436,8 +443,10 @@ public override object ConvertFrom(ITypeDescriptorContext context, System.Global return result; } - public override bool IsValid(ITypeDescriptorContext context, object value) + public override bool IsValid(ITypeDescriptorContext? context, object? value) { + if (value is null) + return false; if (value is Guid) return true; if (value is string str_value && Guid.TryParse(str_value, out _)) @@ -445,15 +454,17 @@ public override bool IsValid(ITypeDescriptorContext context, object value) return false; } - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) { if (destinationType == typeof(Guid) || destinationType == typeof(string)) return true; return base.CanConvertTo(context, destinationType); } - public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + public override object? ConvertTo(ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, Type destinationType) { + if (value is null) + return null; var element = (Element)value; if (destinationType == typeof(Guid)) return element.ID; diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 1cbea46..328be18 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -325,14 +325,14 @@ private void Test_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) [Test] public void LoadVmap_Reflection_Binary() { - var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "de_inferno_d.vmap")); + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); Test_Vmap_Reflection(unserialisedVmap); } [Test] public void LoadVmap_Reflection_Text() { - var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "de_mirage_d.vmap.txt")); + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); Test_Vmap_Reflection(unserialisedVmap); } From cfc83401c3a9bac5414444c7615d0a06e580b6aa Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 02:46:47 +0200 Subject: [PATCH 11/24] more --- AttributeList.cs | 4 ++-- Datamodel.ElementList.cs | 32 ++++++++++++++++++++------------ Datamodel.cs | 4 ++-- Element.cs | 8 ++++---- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/AttributeList.cs b/AttributeList.cs index 0038eb7..6da35ab 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -215,12 +215,12 @@ public ICollection Values /// Thrown when an attempt is made to set the value of the attribute to an Element from a different . /// Thrown when an attempt is made to set a value that is not of a valid Datamodel attribute type. /// Thrown when the maximum number of Attributes allowed in an AttributeList has been reached. - public virtual object this[string name] + public virtual object? this[string name] { get { ArgumentNullException.ThrowIfNull(name); - var attr = (Attribute)Inner[name]; + var attr = (Attribute?)Inner[name]; if (attr == null) { var prop_attr = (PropertyInfo)PropertyInfos[name]; diff --git a/Datamodel.ElementList.cs b/Datamodel.ElementList.cs index 8764942..0250768 100644 --- a/Datamodel.ElementList.cs +++ b/Datamodel.ElementList.cs @@ -45,7 +45,7 @@ internal void Add(Element item) ChangeLock.EnterUpgradeableReadLock(); try { - Element existing = (Element)store[item.ID]; + Element? existing = (Element?)store[item.ID]; if (existing != null && !existing.Stub) { throw new ElementIdException($"Element ID {item.ID} already in use in this Datamodel."); @@ -82,14 +82,14 @@ internal void Add(Element item) /// The order of this list has no meaning to a Datamodel. This accessor is intended for implementers. /// The index to look up. /// The Element found at the index. - public Element this[int index] + public Element? this[int index] { get { ChangeLock.EnterReadLock(); try { - return (Element)store[index]; + return (Element?)store[index]; } finally { ChangeLock.ExitReadLock(); } } @@ -100,14 +100,14 @@ public Element this[int index] /// /// The ID to search for. /// The Element with the given ID, or null if none is found. - public Element this[Guid id] + public Element? this[Guid id] { get { ChangeLock.EnterReadLock(); try { - return (Element)store[id]; + return (Element?)store[id]; } finally { ChangeLock.ExitReadLock(); } } @@ -168,7 +168,7 @@ public bool Remove(Element item, RemoveMode mode) try { store.Remove(item.ID); - Element replacement = (mode == RemoveMode.MakeStubs) ? new Element(Owner, item.ID) : (Element)null; + Element? replacement = (mode == RemoveMode.MakeStubs) ? new Element(Owner, item.ID) : null; foreach (Element elem in store.Values) { @@ -179,7 +179,7 @@ public bool Remove(Element item, RemoveMode mode) elem[attr.Key] = replacement; } - foreach (var array in elem.Select(a => a.Value).OfType>()) + foreach (var array in elem.Select(a => a.Value).OfType>()) for (int i = 0; i < array.Count; i++) if (array[i] == item) array[i] = replacement; @@ -210,7 +210,7 @@ public void Trim() ChangeLock.EnterUpgradeableReadLock(); try { - var used = new HashSet(); + var used = new HashSet(); WalkElemTree(Owner.Root, used); if (used.Count == Count) return; @@ -219,8 +219,11 @@ public void Trim() { foreach (var elem in this.Except(used).ToArray()) { - store.Remove(elem.ID); - elem.Owner = null; + if(elem != null) + { + store.Remove(elem.ID); + elem.Owner = null; + } } CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (System.Collections.IList)used)); @@ -236,8 +239,13 @@ public void Trim() } } - protected void WalkElemTree(Element elem, HashSet found) + protected void WalkElemTree(Element? elem, HashSet found) { + if(elem is null) + { + return; + } + found.Add(elem); foreach (var value in elem.Inner.Values.Cast().Select(a => a.RawValue)) { @@ -279,7 +287,7 @@ public IEnumerator GetEnumerator() /// /// Raised when an is added, removed, or replaced. /// - public event NotifyCollectionChangedEventHandler CollectionChanged; + public event NotifyCollectionChangedEventHandler? CollectionChanged; #endregion public void Dispose() diff --git a/Datamodel.cs b/Datamodel.cs index 6112689..27906d4 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -466,7 +466,7 @@ public int EncodingVersion /// Gets or sets the first Element of the Datamodel. Only Elements referenced by the Root element or one of its children are considered a part of the Datamodel. /// /// Thown when an attempt is made to assign an Element from another Datamodel to this property. - public Element Root + public Element? Root { get => _Root; set @@ -482,7 +482,7 @@ public Element Root OnPropertyChanged(); } } - Element _Root; + Element? _Root; public AttributeList PrefixAttributes { diff --git a/Element.cs b/Element.cs index 6bd7143..b583ef0 100644 --- a/Element.cs +++ b/Element.cs @@ -207,10 +207,10 @@ internal set /// Thrown when an attempt is made to get a name that is not present on this Element. public T? Get(string name) { - object value = this[name]; + object? value = this[name]; if (value is not T && !(typeof(T) == typeof(Element) && value == null)) - throw new AttributeTypeException(string.Format("Attribute \"{0}\" ({1}) does not implement {2}.", name, value.GetType().Name, typeof(T).Name)); + throw new AttributeTypeException(string.Format("Attribute \"{0}\" ({1}) does not implement {2}.", name, value?.GetType().Name, typeof(T).Name)); return (T?)value; } @@ -233,7 +233,7 @@ internal set } catch (AttributeTypeException) { - throw new AttributeTypeException(string.Format("Attribute \"{0}\" ({1}) is not an array.", name, this[name].GetType().Name)); + throw new AttributeTypeException(string.Format("Attribute \"{0}\" ({1}) is not an array.", name, this[name]?.GetType().Name)); } } @@ -249,7 +249,7 @@ internal set /// Thrown when an attempt is made to set the value of the attribute to an Element from a different . /// Thrown when an attempt is made to set a value that is not of a valid Datamodel attribute type. /// Thrown when the maximum number of Attributes allowed in an Element has been reached. - public override object this[string name] + public override object? this[string name] { get { From 20c36baf181183c88eb5f357b0a5e09e2b6c8cdd Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 14:25:11 +0200 Subject: [PATCH 12/24] finish nullable --- Arrays.cs | 20 +++- Attribute.cs | 8 +- AttributeList.cs | 101 ++++++++++++------- Codecs/Binary.cs | 196 ++++++++++++++++++------------------ Codecs/KeyValues2.cs | 231 ++++++++++++++++++++++--------------------- Datamodel.cs | 162 ++++++++++++++---------------- 6 files changed, 377 insertions(+), 341 deletions(-) diff --git a/Arrays.cs b/Arrays.cs index b587764..726fb3f 100644 --- a/Arrays.cs +++ b/Arrays.cs @@ -184,7 +184,12 @@ internal set if (elem == null) continue; if (elem.Owner == null) { - Inner[i] = OwnerDatamodel.ImportElement(elem, Datamodel.ImportRecursionMode.Stubs, Datamodel.ImportOverwriteMode.Stubs); + var importedElement = OwnerDatamodel.ImportElement(elem, Datamodel.ImportRecursionMode.Stubs, Datamodel.ImportOverwriteMode.Stubs); + + if(importedElement is not null) + { + Inner[i] = importedElement; + } } else if (elem.Owner != OwnerDatamodel) throw new ElementOwnershipException(); @@ -198,9 +203,18 @@ protected override void Insert_Internal(int index, Element item) if (item != null && OwnerDatamodel != null) { if (item.Owner == null) - item = OwnerDatamodel.ImportElement(item, Datamodel.ImportRecursionMode.Recursive, Datamodel.ImportOverwriteMode.Stubs); + { + var importedElement = OwnerDatamodel.ImportElement(item, Datamodel.ImportRecursionMode.Recursive, Datamodel.ImportOverwriteMode.Stubs); + + if(importedElement is not null) + { + item = importedElement; + } + } else if (item.Owner != OwnerDatamodel) + { throw new ElementOwnershipException(); + } } base.Insert_Internal(index, item!); @@ -215,7 +229,7 @@ public override Element this[int index] { try { - elem = Inner[index] = elem.Owner.OnStubRequest(elem.ID); + elem = Inner[index] = elem.Owner.OnStubRequest(elem.ID)!; } catch (Exception err) { diff --git a/Attribute.cs b/Attribute.cs index 71049f6..664a40a 100644 --- a/Attribute.cs +++ b/Attribute.cs @@ -84,7 +84,7 @@ public AttributeList.OverrideType? OverrideType /// /// Gets the which this Attribute is a member of. /// - public AttributeList Owner + public AttributeList? Owner { get { return _Owner; } internal set @@ -95,9 +95,9 @@ internal set _Owner = value; } } - AttributeList _Owner; + AttributeList? _Owner; - Datamodel? OwnerDatamodel { get { return Owner.Owner; } } + Datamodel? OwnerDatamodel { get { return Owner?.Owner; } } /// /// Gets whether the value of this Attribute has yet to be decoded. @@ -125,7 +125,7 @@ public void DeferredLoad() } catch (Exception err) { - throw new CodecException($"Deferred loading of attribute \"{Name}\" on element {((Element)Owner).ID} using {OwnerDatamodel.Codec} codec threw an exception.", err); + throw new CodecException($"Deferred loading of attribute \"{Name}\" on element {((Element?)Owner)?.ID} using {OwnerDatamodel.Codec} codec threw an exception.", err); } Offset = 0; diff --git a/AttributeList.cs b/AttributeList.cs index 6da35ab..c036ae9 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Numerics; -using AttrKVP = System.Collections.Generic.KeyValuePair; +using AttrKVP = System.Collections.Generic.KeyValuePair; using System.Reflection; using System.IO; @@ -18,7 +18,7 @@ namespace Datamodel /// [DebuggerTypeProxy(typeof(DebugView))] [DebuggerDisplay("Count = {Count}")] - public class AttributeList : IDictionary, IDictionary + public class AttributeList : IDictionary, IDictionary { internal OrderedDictionary PropertyInfos; internal OrderedDictionary Inner; @@ -29,6 +29,11 @@ private ICollection GetPropertyBasedAttributes(bool useSerializationN var result = new List(); foreach (DictionaryEntry entry in PropertyInfos) { + if(entry.Value is null) + { + throw new InvalidDataException("Property value can not be null"); + } + var prop = (PropertyInfo)entry.Value; var name = useSerializationName ? (string)entry.Key : prop.Name; var attr = new Attribute(name, this, prop.GetValue(this)); @@ -64,7 +69,7 @@ public DebugAttribute(Attribute attr) readonly Attribute Attr; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - object Value { get { return Attr.Value; } } + object? Value { get { return Attr.Value; } } } } @@ -109,7 +114,7 @@ public AttributeList(Datamodel? owner) /// /// The name of the attribute. Must be unique to this AttributeList. /// The value of the Attribute. Must be of a valid Datamodel type. - public void Add(string key, object value) + public void Add(string key, object? value) { this[key] = value; } @@ -128,7 +133,14 @@ public void Add(string key, object value) /// Thrown when the given attribute is not present in the list. public OverrideType? GetOverrideType(string key) { - return ((Attribute)Inner[key]).OverrideType; + var attrib = Inner[key]; + + if(attrib is null) + { + return null; + } + + return ((Attribute)attrib).OverrideType; } /// @@ -139,7 +151,13 @@ public void Add(string key, object value) /// Thrown when the attribute's CLR type does not map to the value given in . public void SetOverrideType(string key, OverrideType? type) { - ((Attribute)Inner[key]).OverrideType = type; + var attrib = Inner[key]; + + if (attrib is not null) + { + ((Attribute)attrib).OverrideType = type; + } + } /// @@ -162,7 +180,7 @@ public bool Remove(string key) { lock (Attribute_ChangeLock) { - var attr = (Attribute)Inner[key]; + var attr = (Attribute?)Inner[key]; if (attr == null) return false; var index = IndexOf(key); @@ -172,11 +190,11 @@ public bool Remove(string key) } } - public bool TryGetValue(string key, out object value) + public bool TryGetValue(string key, out object? value) { - Attribute result; + Attribute? result; lock (Attribute_ChangeLock) - result = (Attribute)Inner[key]; + result = (Attribute?)Inner[key]; if (result != null) { @@ -200,7 +218,7 @@ public ICollection Keys { get { lock (Attribute_ChangeLock) return Inner.Keys.Cast().ToArray(); } } - public ICollection Values + public ICollection Values { get { lock (Attribute_ChangeLock) return Inner.Values.Cast().Select(attr => attr.Value).ToArray(); } } @@ -223,7 +241,7 @@ public virtual object? this[string name] var attr = (Attribute?)Inner[name]; if (attr == null) { - var prop_attr = (PropertyInfo)PropertyInfos[name]; + var prop_attr = (PropertyInfo?)PropertyInfos[name]; if (prop_attr != null) { return prop_attr.GetValue(this); @@ -238,16 +256,16 @@ public virtual object? this[string name] { ArgumentNullException.ThrowIfNull(name); if (value != null && !Datamodel.IsDatamodelType(value.GetType())) - throw new AttributeTypeException(String.Format("{0} is not a valid Datamodel attribute type. (If this is an array, it must implement IList).", value.GetType().FullName)); + throw new AttributeTypeException($"{value.GetType().FullName} is not a valid Datamodel attribute type. (If this is an array, it must implement IList)."); - if (Owner != null && this == Owner.PrefixAttributes && value.GetType() == typeof(Element)) + if (Owner != null && this == Owner.PrefixAttributes && value?.GetType() == typeof(Element)) throw new AttributeTypeException("Elements are not supported as prefix attributes."); - var prop_attr = (PropertyInfo)PropertyInfos[name]; + var prop_attr = (PropertyInfo?)PropertyInfos[name]; if (prop_attr != null) { - PropertyInfo prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance); + PropertyInfo? prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance); if (prop != null && prop.CanWrite) { @@ -272,12 +290,12 @@ public virtual object? this[string name] return; } - Attribute old_attr; - Attribute new_attr; + Attribute? old_attr; + Attribute? new_attr; int old_index = -1; lock (Attribute_ChangeLock) { - old_attr = (Attribute)Inner[name]; + old_attr = (Attribute?)Inner[name]; new_attr = new Attribute(name, this, value); if (old_attr != null) @@ -305,7 +323,13 @@ public AttrKVP this[int index] { get { - var attr = (Attribute)Inner[index]; + var attr = (Attribute?)Inner[index]; + + if(attr is null) + { + throw new InvalidOperationException($"attribute at index {index} doesn't exist"); + } + return attr.ToKeyValuePair(); } set @@ -320,12 +344,16 @@ public AttrKVP this[int index] /// public void RemoveAt(int index) { - Attribute attr; + Attribute? attr; lock (Attribute_ChangeLock) { - attr = (Attribute)Inner[index]; - attr.Owner = null; - Inner.RemoveAt(index); + attr = (Attribute?)Inner[index]; + + if(attr is not null) + { + attr.Owner = null; + Inner.RemoveAt(index); + } } OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, attr, index)); } @@ -398,7 +426,7 @@ IEnumerator IEnumerable.GetEnumerator() /// Raised when , , or /// a custom Element property has changed. /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName()] string property = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); @@ -407,7 +435,7 @@ protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.Caller /// /// Raised when an is added, removed, or replaced. /// - public event NotifyCollectionChangedEventHandler CollectionChanged; + public event NotifyCollectionChangedEventHandler? CollectionChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { Debug.Assert(!(e.NewItems != null && e.NewItems.OfType().Any()) && !(e.OldItems != null && e.OldItems.OfType().Any())); @@ -437,12 +465,12 @@ void IDictionary.Remove(object key) Remove((string)key); } - void IDictionary.Add(object key, object value) + void IDictionary.Add(object key, object? value) { Add((string)key, value); } - object IDictionary.this[object key] + object? IDictionary.this[object key] { get { @@ -466,7 +494,7 @@ bool ICollection.Remove(AttrKVP item) { lock (Attribute_ChangeLock) { - var attr = (Attribute)Inner[item.Key]; + var attr = (Attribute?)Inner[item.Key]; if (attr == null || attr.Value != item.Value) return false; Remove(attr.Name); return true; @@ -497,7 +525,7 @@ bool ICollection.Contains(AttrKVP item) { lock (Attribute_ChangeLock) { - var attr = (Attribute)Inner[item.Key]; + var attr = (Attribute?)Inner[item.Key]; return attr != null && attr.Value == item.Value; } } @@ -521,12 +549,17 @@ public static ValueComparer Default return _Default; } } - static ValueComparer _Default; + static ValueComparer? _Default; - public new bool Equals(object x, object y) + public new bool Equals(object? x, object? y) { - var type_x = x?.GetType(); - var type_y = y?.GetType(); + if(x is null || y is null) + { + return false; + } + + var type_x = x.GetType(); + var type_y = y.GetType(); if (type_x == null && type_y == null) return true; diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index 804fa68..5a73213 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -16,11 +16,9 @@ namespace Datamodel.Codecs [CodecFormat("binary", 4)] [CodecFormat("binary", 5)] [CodecFormat("binary", 9)] - class Binary : IDeferredAttributeCodec, IDisposable + class Binary : ICodec { - protected BinaryReader Reader; - - static readonly Dictionary SupportedAttributes = new(); + static readonly Dictionary SupportedAttributes = new(); /// /// The number of Datamodel binary ticks in one second. Used to store TimeSpan values. @@ -30,7 +28,7 @@ class Binary : IDeferredAttributeCodec, IDisposable static Binary() { SupportedAttributes[1] = - SupportedAttributes[2] = new Type[] { + SupportedAttributes[2] = new Type?[] { typeof(Element), typeof(int), typeof(float), typeof(bool), typeof(string), typeof(byte[]), null /* ObjectID */, typeof(Color), typeof(Vector2), typeof(Vector3), typeof(Vector4), typeof(Vector3) /* angle*/, typeof(Quaternion), typeof(Matrix4x4) }; @@ -47,11 +45,6 @@ static Binary() }; } - public void Dispose() - { - Reader?.Dispose(); - } - static byte TypeToId(Type type, int version) { bool array = Datamodel.IsDatamodelArrayType(type); @@ -79,7 +72,7 @@ static byte TypeToId(Type type, int version) return ++i; } - Tuple IdToType(byte id) + Tuple IdToType(byte id) { var type_list = SupportedAttributes[EncodingVersion]; bool array = false; @@ -102,7 +95,7 @@ Tuple IdToType(byte id) try { - return new Tuple((array ? type_list[id].MakeListType() : type_list[id]), (array ? type_list[id] : null)); + return new Tuple((array ? type_list[id]?.MakeListType() : type_list[id]), (array ? type_list[id] : null)); } catch (IndexOutOfRangeException) { @@ -110,12 +103,12 @@ Tuple IdToType(byte id) } } - protected string ReadString_Raw() + protected string ReadString_Raw(BinaryReader reader) { List raw = new(); while (true) { - byte cur = Reader.ReadByte(); + byte cur = reader.ReadByte(); if (cur == 0) break; else raw.Add(cur); } @@ -128,8 +121,7 @@ protected string ReadString_Raw() class StringDictionary { - readonly Binary Codec; - readonly BinaryWriter Writer; + readonly Binary? Codec; readonly int EncodingVersion; readonly List Strings = new(); @@ -142,15 +134,15 @@ class StringDictionary /// /// Constructs a new from a Binary stream. /// - public StringDictionary(Binary codec) + public StringDictionary(Binary codec, BinaryReader reader) { Codec = codec; EncodingVersion = codec.EncodingVersion; Dummy = EncodingVersion == 1; if (!Dummy) { - foreach (var i in Enumerable.Range(0, LengthSize == sizeof(short) ? Codec.Reader.ReadInt16() : Codec.Reader.ReadInt32())) - AddString(Codec.ReadString_Raw()); + foreach (var i in Enumerable.Range(0, LengthSize == sizeof(short) ? reader.ReadInt16() : reader.ReadInt32())) + AddString(Codec.ReadString_Raw(reader)); } } @@ -160,7 +152,6 @@ public StringDictionary(Binary codec) public StringDictionary(int encoding_version, BinaryWriter writer, Datamodel dm) { EncodingVersion = encoding_version; - Writer = writer; Dummy = EncodingVersion == 1; if (!Dummy) @@ -169,14 +160,12 @@ public StringDictionary(int encoding_version, BinaryWriter writer, Datamodel dm) ScrapeElement(dm.Root); Strings = Strings.Distinct().ToList(); - - Scraped = null; } } - private readonly HashSet Scraped; + private readonly HashSet Scraped = new(); - void ScrapeElement(Element elem) + void ScrapeElement(Element? elem) { if (elem == null || elem.Stub || Scraped.Contains(elem)) return; Scraped.Add(elem); @@ -219,38 +208,38 @@ int GetIndex(string value) return Strings.IndexOf(value); } - public string ReadString() + public string ReadString(BinaryReader reader) { - if (Dummy) return Codec.ReadString_Raw(); - return Strings[IndiceSize == sizeof(short) ? Codec.Reader.ReadInt16() : Codec.Reader.ReadInt32()]; + if (Dummy) return Codec!.ReadString_Raw(reader); + return Strings[IndiceSize == sizeof(short) ? reader.ReadInt16() : reader.ReadInt32()]; } - public void WriteString(string value) + public void WriteString(string value, BinaryWriter writer) { if (Dummy) - Writer.Write(value); + writer.Write(value); else { var index = GetIndex(value); - if (IndiceSize == sizeof(short)) Writer.Write((short)index); - else Writer.Write(index); + if (IndiceSize == sizeof(short)) writer.Write((short)index); + else writer.Write(index); } } - public void WriteSelf() + public void WriteSelf(BinaryWriter writer) { if (Dummy) return; if (LengthSize == sizeof(short)) - Writer.Write((short)Strings.Count); + writer.Write((short)Strings.Count); else - Writer.Write(Strings.Count); + writer.Write(Strings.Count); foreach (var str in Strings) - Writer.Write(str); + writer.Write(str); } } - StringDictionary StringDict; + StringDictionary? StringDict; public void Encode(Datamodel dm, string encoding, int encoding_version, Stream stream) { @@ -259,78 +248,78 @@ public void Encode(Datamodel dm, string encoding, int encoding_version, Stream s encoder.Encode(); } - float[] ReadVector(int dim) + float[] ReadVector(int dim, BinaryReader reader) { var output = new float[dim]; foreach (int i in Enumerable.Range(0, dim)) - output[i] = Reader.ReadSingle(); + output[i] = reader.ReadSingle(); return output; } - object? ReadValue(Datamodel dm, Type type, bool raw_string) + object? ReadValue(Datamodel dm, Type? type, bool raw_string, BinaryReader reader) { if (type == typeof(Element)) { - var index = Reader.ReadInt32(); + var index = reader.ReadInt32(); if (index == -1) return null; else if (index == -2) { - var id = new Guid(ReadString_Raw()); // yes, it's in ASCII! + var id = new Guid(ReadString_Raw(reader)); // yes, it's in ASCII! return dm.AllElements[id] ?? new Element(dm, id); } else return dm.AllElements[index]; } if (type == typeof(int)) - return Reader.ReadInt32(); + return reader.ReadInt32(); if (type == typeof(float)) - return Reader.ReadSingle(); + return reader.ReadSingle(); if (type == typeof(bool)) - return Reader.ReadBoolean(); + return reader.ReadBoolean(); if (type == typeof(string)) - return raw_string ? ReadString_Raw() : StringDict.ReadString(); + return raw_string ? ReadString_Raw(reader) : StringDict!.ReadString(reader); if (type == typeof(byte[])) - return Reader.ReadBytes(Reader.ReadInt32()); + return reader.ReadBytes(reader.ReadInt32()); if (type == typeof(TimeSpan)) - return TimeSpan.FromTicks(Reader.ReadInt32() * (TimeSpan.TicksPerSecond / DatamodelTicksPerSecond)); + return TimeSpan.FromTicks(reader.ReadInt32() * (TimeSpan.TicksPerSecond / DatamodelTicksPerSecond)); if (type == typeof(Color)) { - var rgba = Reader.ReadBytes(4); + var rgba = reader.ReadBytes(4); return Color.FromBytes(rgba); } if (type == typeof(Vector2)) { - var ords = ReadVector(2); + var ords = ReadVector(2, reader); return new Vector2(ords[0], ords[1]); } if (type == typeof(Vector3)) { - var ords = ReadVector(3); + var ords = ReadVector(3, reader); return new Vector3(ords[0], ords[1], ords[2]); } if (type == typeof(QAngle)) { - var ords = ReadVector(3); + var ords = ReadVector(3, reader); return new QAngle(ords[0], ords[1], ords[2]); } if (type == typeof(Vector4)) { - var ords = ReadVector(4); + var ords = ReadVector(4, reader); return new Vector4(ords[0], ords[1], ords[2], ords[3]); } if (type == typeof(Quaternion)) { - var ords = ReadVector(4); + var ords = ReadVector(4, reader); return new Quaternion(ords[0], ords[1], ords[2], ords[3]); } if (type == typeof(Matrix4x4)) { - var ords = ReadVector(4 * 4); + var ords = ReadVector(4 * 4, reader); return new Matrix4x4( ords[0], ords[1], ords[2], ords[3], ords[4], ords[5], ords[6], ords[7], @@ -339,9 +328,9 @@ float[] ReadVector(int dim) } if (type == typeof(byte)) - return Reader.ReadByte(); + return reader.ReadByte(); if (type == typeof(UInt64)) - return Reader.ReadUInt64(); + return reader.ReadUInt64(); throw new ArgumentException(type == null ? "No type provided to GetValue()" : "Cannot read value of type " + type.Name); } @@ -359,38 +348,38 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in var dm = new Datamodel(format, format_version); EncodingVersion = encoding_version; - Reader = new BinaryReader(stream, Datamodel.TextEncoding); + BinaryReader reader = new BinaryReader(stream, Datamodel.TextEncoding); if (EncodingVersion >= 9) { // Read prefix elements - foreach (int prefix_elem in Enumerable.Range(0, Reader.ReadInt32())) + foreach (int prefix_elem in Enumerable.Range(0, reader.ReadInt32())) { - foreach (int attr_index in Enumerable.Range(0, Reader.ReadInt32())) + foreach (int attr_index in Enumerable.Range(0, reader.ReadInt32())) { - var name = ReadString_Raw(); - var value = DecodeAttribute(dm, true); + var name = ReadString_Raw(reader); + var value = DecodeAttribute(dm, true, reader); if (prefix_elem == 0) // skip subsequent elements...are they considered "old versions"? dm.PrefixAttributes[name] = value; } } } - StringDict = new StringDictionary(this); - var num_elements = Reader.ReadInt32(); + StringDict = new StringDictionary(this, reader); + var num_elements = reader.ReadInt32(); // read index foreach (var i in Enumerable.Range(0, num_elements)) { - var type = StringDict.ReadString(); - var name = EncodingVersion >= 4 ? StringDict.ReadString() : ReadString_Raw(); - var id_bits = Reader.ReadBytes(16); + var type = StringDict.ReadString(reader); + var name = EncodingVersion >= 4 ? StringDict.ReadString(reader) : ReadString_Raw(reader); + var id_bits = reader.ReadBytes(16); var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray()); Element? elem = null; var matchedType = types.TryGetValue(type, out var classType); - if(matchedType && reflectionParams.AttemptReflection) + if(matchedType && classType != null && reflectionParams.AttemptReflection) { var isElementDerived = Element.IsElementDerived(classType); if (isElementDerived && classType.Name == type) @@ -428,19 +417,19 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in // assert if stub Debug.Assert(!elem.Stub); - var num_attrs = Reader.ReadInt32(); + var num_attrs = reader.ReadInt32(); foreach (var i in Enumerable.Range(0, num_attrs)) { - var name = StringDict.ReadString(); + var name = StringDict.ReadString(reader); if (defer_mode == DeferredMode.Automatic && reflectionParams.AttemptReflection == false) { - CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); - SkipAttribute(); + CodecUtilities.AddDeferredAttribute(elem, name, reader.BaseStream.Position); + SkipAttribute(reader); } else { - elem.Add(name, DecodeAttribute(dm, false)); + elem.Add(name, DecodeAttribute(dm, false, reader)); } } } @@ -450,47 +439,53 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in int EncodingVersion; - public object? DeferredDecodeAttribute(Datamodel dm, long offset) + public object? DeferredDecodeAttribute(Datamodel dm, long offset, BinaryReader reader) { - Reader.BaseStream.Seek(offset, SeekOrigin.Begin); - return DecodeAttribute(dm, false); + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + return DecodeAttribute(dm, false, reader); } - object? DecodeAttribute(Datamodel dm, bool prefix) + object? DecodeAttribute(Datamodel dm, bool prefix, BinaryReader reader) { - var types = IdToType(Reader.ReadByte()); + var types = IdToType(reader.ReadByte()); if (types.Item2 == null) - return ReadValue(dm, types.Item1, EncodingVersion < 4 || prefix); + return ReadValue(dm, types.Item1, EncodingVersion < 4 || prefix, reader); else { - var count = Reader.ReadInt32(); + var count = reader.ReadInt32(); var inner_type = types.Item2; var array = CodecUtilities.MakeList(inner_type, count); foreach (var x in Enumerable.Range(0, count)) - array.Add(ReadValue(dm, inner_type, true)); + array.Add(ReadValue(dm, inner_type, true, reader)); return array; } } - void SkipAttribute() + void SkipAttribute(BinaryReader reader) { - var types = IdToType(Reader.ReadByte()); + var types = IdToType(reader.ReadByte()); int count = 1; - Type type = types.Item1; + Type? type = types.Item1; + + if(type is null) + { + throw new InvalidDataException("Failed to match id to type"); + } + if (types.Item2 != null) { - count = Reader.ReadInt32(); + count = reader.ReadInt32(); type = types.Item2; } if (type == typeof(Element)) { foreach (int i in Enumerable.Range(0, count)) - if (Reader.ReadInt32() == -2) Reader.BaseStream.Seek(37, SeekOrigin.Current); // skip GUID + null terminator if a stub + if (reader.ReadInt32() == -2) reader.BaseStream.Seek(37, SeekOrigin.Current); // skip GUID + null terminator if a stub return; } @@ -505,19 +500,19 @@ void SkipAttribute() else if (type == typeof(byte[])) { foreach (var i in Enumerable.Range(0, count)) - Reader.BaseStream.Seek(Reader.ReadInt32(), SeekOrigin.Current); + reader.BaseStream.Seek(reader.ReadInt32(), SeekOrigin.Current); return; } else if (type == typeof(string)) { - if (!StringDict.Dummy && types.Item2 == null && EncodingVersion >= 4) + if (!StringDict!.Dummy && types.Item2 == null && EncodingVersion >= 4) length = StringDict.IndiceSize; else { foreach (var i in Enumerable.Range(0, count)) { byte b; - do { b = Reader.ReadByte(); } while (b != 0); + do { b = reader.ReadByte(); } while (b != 0); } return; } @@ -533,7 +528,7 @@ void SkipAttribute() else length = System.Runtime.InteropServices.Marshal.SizeOf(type); - Reader.BaseStream.Seek(length * count, SeekOrigin.Current); + reader.BaseStream.Seek(length * count, SeekOrigin.Current); } readonly struct Encoder @@ -565,7 +560,7 @@ public void Encode() if (EncodingVersion >= 9) Writer.Write(0); // Prefix elements - StringDict.WriteSelf(); + StringDict.WriteSelf(Writer); { var counter = new HashSet(); //(Element.IDComparer.Default); @@ -579,8 +574,13 @@ public void Encode() WriteBody(e); } - int CountChildren(Element elem, HashSet counter) + int CountChildren(Element? elem, HashSet counter) { + if(elem is null) + { + return 0; + } + if (elem.Stub) return 0; int num_elems = 1; counter.Add(elem); @@ -602,15 +602,15 @@ int CountChildren(Element elem, HashSet counter) return num_elems; } - void WriteIndex(Element elem) + void WriteIndex(Element? elem) { - if (elem.Stub) return; + if (elem is null || elem.Stub) return; ElementIndices[elem] = ElementIndices.Count; ElementOrder.Add(elem); - StringDict.WriteString(elem.ClassName); - if (EncodingVersion >= 4) StringDict.WriteString(elem.Name); + StringDict.WriteString(elem.ClassName, Writer); + if (EncodingVersion >= 4) StringDict.WriteString(elem.Name, Writer); else Writer.Write(elem.Name); Writer.Write(elem.ID.ToByteArray()); @@ -642,7 +642,7 @@ void WriteBody(Element elem) Writer.Write(attributesIterated.Length); foreach (var attr in attributesIterated) { - StringDict.WriteString(attr.Key); + StringDict.WriteString(attr.Key, Writer); var attr_type = attr.Value == null ? typeof(Element) : attr.Value.GetType(); var attr_type_id = TypeToId(attr_type, EncodingVersion); Writer.Write(attr_type_id); @@ -660,7 +660,7 @@ void WriteBody(Element elem) } } - void WriteAttribute(object value, bool in_array) + void WriteAttribute(object? value, bool in_array) { if (value == null) { @@ -686,7 +686,7 @@ void WriteAttribute(object value, bool in_array) if (EncodingVersion < 4 || in_array) Writer.Write(string_value); else - StringDict.WriteString(string_value); + StringDict.WriteString(string_value, Writer); return; } diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 3a61f49..bcdece3 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -20,12 +20,8 @@ namespace Datamodel.Codecs [CodecFormat("keyvalues2_noids", 2)] [CodecFormat("keyvalues2_noids", 3)] [CodecFormat("keyvalues2_noids", 4)] - class KeyValues2 : ICodec, IDisposable + class KeyValues2 : ICodec { - TextReader Reader; - KV2Writer Writer; - Datamodel DM; - static readonly Dictionary TypeNames = new(); static readonly Dictionary ValidAttributes = new(); static KeyValues2() @@ -53,12 +49,6 @@ static KeyValues2() ValidAttributes[4] = TypeNames.Select(kv => kv.Key).ToArray(); } - public void Dispose() - { - Reader?.Dispose(); - Writer?.Dispose(); - } - #region Encode class KV2Writer : IDisposable { @@ -87,7 +77,7 @@ public void Dispose() static string Sanitise(string value) { - return value?.Replace("\"", "\\\""); + return value.Replace("\"", "\\\""); } /// @@ -139,17 +129,19 @@ public void Flush() } } - string Encoding; - int EncodingVersion; - // Multi-referenced elements are written out as a separate block at the end of the file. // In-line only the id is written. - Dictionary ReferenceCount; + Dictionary ReferenceCount = new(); bool SupportsReferenceIds; - void CountReferences(Element elem) + void CountReferences(Element? elem) { + if(elem is null) + { + return; + } + if (ReferenceCount.ContainsKey(elem)) ReferenceCount[elem]++; else @@ -173,11 +165,11 @@ void CountReferences(Element elem) } } - void WriteAttribute(string name, Type type, object value, bool in_array) + void WriteAttribute(string name, int encodingVersion, Type type, object value, bool in_array, KV2Writer writer) { bool is_element = type == typeof(Element) || type.IsSubclassOf(typeof(Element)); - Type inner_type = null; + Type? inner_type = null; if (!in_array) { // TODO: subclass check in this method like above - and in all other places with == typeof(Element) @@ -193,64 +185,64 @@ void WriteAttribute(string name, Type type, object value, bool in_array) } // Elements are supported by all. - if (!is_element && !ValidAttributes[EncodingVersion].Contains(inner_type ?? type)) - throw new CodecException(type.Name + " is not valid in KeyValues2 " + EncodingVersion); + if (!is_element && !ValidAttributes[encodingVersion].Contains(inner_type ?? type)) + throw new CodecException(type.Name + " is not valid in KeyValues2 " + encodingVersion); if (inner_type != null) { is_element = inner_type == typeof(Element); - Writer.WriteTokenLine(name, TypeNames[inner_type] + "_array"); + writer.WriteTokenLine(name, TypeNames[inner_type] + "_array"); if (((System.Collections.IList)value).Count == 0) { - Writer.Write(" [ ]"); + writer.Write(" [ ]"); return; } - if (is_element) Writer.WriteLine("["); - else Writer.Write(" ["); + if (is_element) writer.WriteLine("["); + else writer.Write(" ["); - Writer.Indent++; + writer.Indent++; foreach (var array_value in (System.Collections.IList)value) - WriteAttribute(null, inner_type, array_value, true); - Writer.Indent--; - Writer.TrimEnd(1); // remove trailing comma + WriteAttribute(string.Empty, encodingVersion, inner_type, array_value, true, writer); + writer.Indent--; + writer.TrimEnd(1); // remove trailing comma - if (inner_type == typeof(Element)) Writer.WriteLine("]"); - else Writer.Write(" ]"); + if (inner_type == typeof(Element)) writer.WriteLine("]"); + else writer.Write(" ]"); return; } if (is_element) { - var elem = value as Element; - var id = elem == null ? "" : elem.ID.ToString(); + var elem = (Element)value; + var id = elem.ID.ToString(); if (in_array) { if (ShouldBeReferenced(elem)) { - Writer.WriteTokenLine("element", id); + writer.WriteTokenLine("element", id); } else { - Writer.WriteLine(); - WriteElement(elem); + writer.WriteLine(); + WriteElement(elem, encodingVersion, writer); } - Writer.Write(","); + writer.Write(","); } else { if (ShouldBeReferenced(elem)) { - Writer.WriteTokenLine(name, "element", id); + writer.WriteTokenLine(name, "element", id); } else { - Writer.WriteLine($"\"{name}\" "); - WriteElement(elem); + writer.WriteLine($"\"{name}\" "); + WriteElement(elem, encodingVersion, writer); } } } @@ -309,78 +301,85 @@ void WriteAttribute(string name, Type type, object value, bool in_array) } if (in_array) - Writer.Write(FormattableString.Invariant($" \"{value}\",")); + writer.Write(FormattableString.Invariant($" \"{value}\",")); else - Writer.WriteTokenLine(name, TypeNames[type], FormattableString.Invariant($"{value}")); + writer.WriteTokenLine(name, TypeNames[type], FormattableString.Invariant($"{value}")); } } - private bool ShouldBeReferenced(Element elem) + private bool ShouldBeReferenced(Element? elem) { + if(elem is null) + { + return false; + } + return SupportsReferenceIds && (elem == null || ReferenceCount.TryGetValue(elem, out var refCount) && refCount > 1); } - void WriteElement(Element element) + void WriteElement(Element element, int encodingVersion, KV2Writer writer) { if (TypeNames.ContainsValue(element.ClassName)) throw new CodecException($"Element {element.ID} uses reserved type name \"{element.ClassName}\""); - Writer.WriteTokens(element.ClassName); - Writer.WriteLine("{"); - Writer.Indent++; + writer.WriteTokens(element.ClassName); + writer.WriteLine("{"); + writer.Indent++; if (SupportsReferenceIds) - Writer.WriteTokenLine("id", "elementid", element.ID.ToString()); + writer.WriteTokenLine("id", "elementid", element.ID.ToString()); // Skip empty names right now. if (!string.IsNullOrEmpty(element.Name)) { - Writer.WriteTokenLine("name", "string", element.Name); + writer.WriteTokenLine("name", "string", element.Name); } foreach (var attr in element.GetAllAttributesForSerialization()) { - if (attr.Value == null) - WriteAttribute(attr.Key, typeof(Element), null, false); - else - WriteAttribute(attr.Key, attr.Value.GetType(), attr.Value, false); + if (attr.Value != null) + WriteAttribute(attr.Key, encodingVersion, attr.Value.GetType(), attr.Value, false, writer); } - Writer.Indent--; - Writer.WriteLine("}"); + writer.Indent--; + writer.WriteLine("}"); } - public void Encode(Datamodel dm, string encoding, int encoding_version, Stream stream) + public void Encode(Datamodel dm, string encoding, int encodingVersion, Stream stream) { - Writer = new KV2Writer(stream); - Encoding = encoding; - EncodingVersion = encoding_version; + var writer = new KV2Writer(stream); - SupportsReferenceIds = Encoding != "keyvalues2_noids"; + SupportsReferenceIds = encoding != "keyvalues2_noids"; - Writer.Write(String.Format(CodecUtilities.HeaderPattern, encoding, encoding_version, dm.Format, dm.FormatVersion)); - Writer.WriteLine(); + writer.Write(String.Format(CodecUtilities.HeaderPattern, encoding, encodingVersion, dm.Format, dm.FormatVersion)); + writer.WriteLine(); ReferenceCount = new Dictionary(); - if (EncodingVersion >= 4 && dm.PrefixAttributes.Count > 0) + if (encodingVersion >= 4 && dm.PrefixAttributes.Count > 0) { - Writer.WriteTokens("$prefix_element$"); - Writer.WriteLine("{"); - Writer.Indent++; - Writer.WriteTokenLine("id", "elementid", Guid.NewGuid().ToString()); + writer.WriteTokens("$prefix_element$"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteTokenLine("id", "elementid", Guid.NewGuid().ToString()); foreach (var attr in dm.PrefixAttributes) - WriteAttribute(attr.Key, attr.Value.GetType(), attr.Value, false); - Writer.Indent--; - Writer.WriteLine("}"); - Writer.WriteLine(); + if(attr.Value != null) + { + WriteAttribute(attr.Key, encodingVersion, attr.Value.GetType(), attr.Value, false, writer); + } + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLine(); } if (SupportsReferenceIds) CountReferences(dm.Root); - WriteElement(dm.Root); - Writer.WriteLine(); + if(dm.Root != null) + { + WriteElement(dm.Root, encodingVersion, writer); + } + writer.WriteLine(); if (SupportsReferenceIds) { @@ -388,13 +387,13 @@ public void Encode(Datamodel dm, string encoding, int encoding_version, Stream s { if (pair.Key == dm.Root) continue; - Writer.WriteLine(); - WriteElement(pair.Key); - Writer.WriteLine(); + writer.WriteLine(); + WriteElement(pair.Key, encodingVersion, writer); + writer.WriteLine(); } } - Writer.Flush(); + writer.Flush(); } #endregion @@ -408,8 +407,12 @@ private class IntermediateData public Dictionary> PropertiesToAdd = new(); public Dictionary> ListRefs = new(); - public void HandleElementProp(Element element, string attrName, Guid id) + public void HandleElementProp(Element? element, string attrName, Guid id) { + if(element is null) + { + throw new InvalidDataException("Trying to handle the propery of an invalid element"); + } PropertiesToAdd.TryGetValue(element, out var attrList); @@ -439,14 +442,14 @@ public void HandleListRefs(IList list, Guid id) readonly StringBuilder TokenBuilder = new(); int Line = 0; - string Decode_NextToken() + string Decode_NextToken(StreamReader reader) { TokenBuilder.Clear(); bool escaped = false; bool in_block = false; while (true) { - var read = Reader.Read(); + var read = reader.Read(); if (read == -1) throw new EndOfStreamException(); var c = (char)read; if (escaped) @@ -481,35 +484,35 @@ string Decode_NextToken() } } - Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams, IntermediateData intermediateData) + Element? Decode_ParseElement(string class_name, ReflectionParams reflectionParams, StreamReader reader, Datamodel dataModel, IntermediateData intermediateData) { - string elem_class = class_name ?? Decode_NextToken(); - string elem_name = null; - string elem_id = null; - Element elem = null; + string elem_class = class_name ?? Decode_NextToken(reader); + string elem_name = string.Empty; + string elem_id = string.Empty; + Element? elem = null; var types = CodecUtilities.GetReflectionTypes(reflectionParams); - string next = Decode_NextToken(); + string next = Decode_NextToken(reader); if (next != "{") throw new CodecException($"Expected Element opener, got '{next}'."); while (true) { - next = Decode_NextToken(); + next = Decode_NextToken(reader); if (next == "}") break; var attr_name = next; - var attr_type_s = Decode_NextToken(); + var attr_type_s = Decode_NextToken(reader); var attr_type = TypeNames.FirstOrDefault(kv => kv.Value == attr_type_s.Split('_')[0]).Key; if (elem == null && attr_name == "id" && attr_type_s == "elementid") { - elem_id = Decode_NextToken(); + elem_id = Decode_NextToken(reader); var id = new Guid(elem_id); if (elem_class != "$prefix_element$") { var matchedType = types.TryGetValue(elem_class, out var classType); - if (matchedType && reflectionParams.AttemptReflection) + if (matchedType && classType != null && reflectionParams.AttemptReflection) { var isElementDerived = Element.IsElementDerived(classType); if (isElementDerived && classType.Name == elem_class) @@ -529,7 +532,7 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams } object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, new object[] { DM, elem_name, new Guid(elem_id), elem_class }); + constructor.Invoke(uninitializedObject, new object[] { dataModel, elem_name, new Guid(elem_id), elem_class }); elem = (Element?)uninitializedObject; } @@ -537,7 +540,7 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams if (elem == null) { - elem = new Element(DM, elem_name, new Guid(elem_id), elem_class); + elem = new Element(dataModel, elem_name, new Guid(elem_id), elem_class); } } @@ -546,7 +549,7 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams if (attr_name == "name" && attr_type == typeof(string)) { - elem_name = Decode_NextToken(); + elem_name = Decode_NextToken(reader); if (elem != null) elem.Name = elem_name; continue; @@ -554,7 +557,7 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams if (attr_type_s == "element") { - var id_s = Decode_NextToken(); + var id_s = Decode_NextToken(reader); if (!string.IsNullOrEmpty(id_s)) { @@ -563,25 +566,25 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams continue; } - object attr_value = null; + object? attr_value = null; if (attr_type == null) - attr_value = Decode_ParseElement(attr_type_s, reflectionParams, intermediateData); + attr_value = Decode_ParseElement(attr_type_s, reflectionParams, reader, dataModel, intermediateData); else if (attr_type_s.EndsWith("_array")) { var array = CodecUtilities.MakeList(attr_type, 5); // assume 5 items attr_value = array; - next = Decode_NextToken(); + next = Decode_NextToken(reader); if (next != "[") throw new CodecException(String.Format("Expected array opener, got '{0}'.", next)); while (true) { - next = Decode_NextToken(); + next = Decode_NextToken(reader); if (next == "]") break; if (next == "element") // Element ID reference { - var id_s = Decode_NextToken(); + var id_s = Decode_NextToken(reader); if (!string.IsNullOrEmpty(id_s)) { @@ -591,27 +594,27 @@ Element Decode_ParseElement(string class_name, ReflectionParams reflectionParams // inline Element else if (attr_type == typeof(Element)) { - array.Add(Decode_ParseElement(next, reflectionParams, intermediateData)); + array.Add(Decode_ParseElement(next, reflectionParams, reader, dataModel, intermediateData)); } // normal value else { - array.Add(Decode_ParseValue(attr_type, next, reflectionParams, intermediateData)); + array.Add(Decode_ParseValue(attr_type, next, reflectionParams, reader, dataModel, intermediateData)); } } } else - attr_value = Decode_ParseValue(attr_type, Decode_NextToken(), reflectionParams, intermediateData); + attr_value = Decode_ParseValue(attr_type, Decode_NextToken(reader), reflectionParams, reader, dataModel, intermediateData); if (elem != null) elem.Add(attr_name, attr_value); else - DM.PrefixAttributes[attr_name] = attr_value; + dataModel.PrefixAttributes[attr_name] = attr_value; } return elem; } - object Decode_ParseValue(Type type, string value, ReflectionParams reflectionParams, IntermediateData intermediateData) + object? Decode_ParseValue(Type type, string value, ReflectionParams reflectionParams, StreamReader reader, Datamodel dataModel, IntermediateData intermediateData) { if (type == typeof(string)) return value; @@ -619,7 +622,7 @@ object Decode_ParseValue(Type type, string value, ReflectionParams reflectionPar value = value.Trim(); if (type == typeof(Element)) - return Decode_ParseElement(value, reflectionParams, intermediateData); + return Decode_ParseElement(value, reflectionParams, reader, dataModel, intermediateData); if (type == typeof(int)) return int.Parse(value, CultureInfo.InvariantCulture); else if (type == typeof(float)) @@ -661,7 +664,7 @@ object Decode_ParseValue(Type type, string value, ReflectionParams reflectionPar else if (type == typeof(TimeSpan)) return TimeSpan.FromTicks((long)(double.Parse(value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond)); - var num_list = value.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + var num_list = value.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); if (type == typeof(Color)) { @@ -689,14 +692,14 @@ object Decode_ParseValue(Type type, string value, ReflectionParams reflectionPar public Datamodel Decode(string encoding, int encoding_version, string format, int format_version, Stream stream, DeferredMode defer_mode, ReflectionParams reflectionParams) { - DM = new Datamodel(format, format_version); + var dataModel = new Datamodel(format, format_version); if (encoding == "keyvalues2_noids") throw new NotImplementedException("KeyValues2_noids decoding not implemented."); stream.Seek(0, SeekOrigin.Begin); - Reader = new StreamReader(stream, Datamodel.TextEncoding); - Reader.ReadLine(); // skip DMX header + var reader = new StreamReader(stream, Datamodel.TextEncoding); + reader.ReadLine(); // skip DMX header Line = 1; string next; @@ -705,12 +708,12 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in while (true) { try - { next = Decode_NextToken(); } + { next = Decode_NextToken(reader); } catch (EndOfStreamException) { break; } try - { Decode_ParseElement(next, reflectionParams, intermediateData); } + { Decode_ParseElement(next, reflectionParams, reader, dataModel, intermediateData); } catch (Exception err) { throw new CodecException($"KeyValues2 decode failed on line {Line}:\n\n{err.Message}", err); } } @@ -719,7 +722,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in { foreach (var prop in propList.Value) { - propList.Key.Add(prop.Item1, DM.AllElements[prop.Item2]); + propList.Key.Add(prop.Item1, dataModel.AllElements[prop.Item2]); } } @@ -728,11 +731,11 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in { foreach (var id in list.Value) { - list.Key.Add(DM.AllElements[id]); + list.Key.Add(dataModel.AllElements[id]); } } - return DM; + return dataModel; } #endregion } diff --git a/Datamodel.cs b/Datamodel.cs index 27906d4..48381ea 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -29,7 +29,7 @@ public DebugView(Datamodel dm) readonly Datamodel DM; - public Element Root => DM.Root; + public Element? Root => DM.Root; public ElementList AllElements => DM.AllElements; public AttributeList PrefixAttributes => DM.PrefixAttributes; } @@ -84,7 +84,7 @@ public static bool IsDatamodelArrayType(Type t) /// Returns the inner Type of an object which implements IList<T>, or null if there is no inner Type. /// /// The Type to check. - public static Type GetArrayInnerType(Type t) + public static Type? GetArrayInnerType(Type t) { if (t == typeof(Element)) { @@ -121,7 +121,7 @@ static Datamodel() /// The ICodec implementation being registered. public static void RegisterCodec(Type type) { - if (type.GetInterface(typeof(ICodec).FullName) == null) + if (type.GetInterface(typeof(ICodec).FullName!) == null) { throw new CodecException($"{type.Name} does not implement Datamodel.Codecs.ICodec."); } @@ -159,13 +159,20 @@ static void AddCodec(Type type, CodecFormatAttribute format_attr, CodecRegistrat private static ICodec GetCodec(string encoding, int encoding_version) { - Type codec_type; + Type? codec_type; if (!Codecs.TryGetValue(new CodecRegistration(encoding, encoding_version), out codec_type)) { throw new CodecException($"No codec found for {encoding} version {encoding_version}."); } - return (ICodec)codec_type.GetConstructor(Type.EmptyTypes).Invoke(null); + var codecConstructor = codec_type.GetConstructor(Type.EmptyTypes); + + if(codecConstructor is null) + { + throw new InvalidOperationException("Failed to get codec constructor."); + } + + return (ICodec)codecConstructor.Invoke(null); } /// @@ -254,7 +261,7 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) { var stream = File.OpenRead(path); - Datamodel dm = null; + Datamodel? dm = null; try { dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); @@ -312,13 +319,19 @@ private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, return dm; } - internal Element OnStubRequest(Guid id) + internal Element? OnStubRequest(Guid id) { - Element result = null; + Element? result = null; if (StubRequest != null) { result = StubRequest(id); - if (result != null && result.ID != id) + + if(result is null) + { + throw new InvalidDataException("Stub request failed, result was null"); + } + + if (result.ID != id) throw new InvalidOperationException("Datamodel.StubRequest returned an Element with a an ID different from the one requested."); if (result.Owner != this) result = ImportElement(result, ImportRecursionMode.Stubs, ImportOverwriteMode.Stubs); @@ -330,7 +343,7 @@ internal Element OnStubRequest(Guid id) /// /// Occurs when an attempt is made to access a stub elment. /// - public event StubRequestHandler StubRequest; + public event StubRequestHandler? StubRequest; #endregion @@ -408,13 +421,18 @@ public string Format get => _Format; set { - if (value != null && value.Contains(' ')) + if(value is null) + { + throw new InvalidDataException("Format can not be null"); + } + + if (value.Contains(' ')) throw new ArgumentException("Format name cannot contain spaces."); _Format = value; OnPropertyChanged(); } } - string _Format; + string _Format = ""; /// /// Gets or sets the version of the in use. @@ -438,13 +456,18 @@ public string Encoding get => _Encoding; set { - if (value != null && value.Contains(' ')) + if(value is null) + { + throw new InvalidDataException("Encoding can not be null"); + } + + if (value.Contains(' ')) throw new ArgumentException("Encoding name cannot contain spaces."); _Encoding = value; OnPropertyChanged(); } } - string _Encoding; + string _Encoding = ""; /// /// Gets or sets the version of the in use. @@ -459,8 +482,8 @@ public int EncodingVersion } int _EncodingVersion; - Stream Stream; - internal IDeferredAttributeCodec Codec; + Stream? Stream; + internal IDeferredAttributeCodec? Codec; /// /// Gets or sets the first Element of the Datamodel. Only Elements referenced by the Root element or one of its children are considered a part of the Datamodel. @@ -515,7 +538,7 @@ public ElementList AllElements /// Thrown when the maximum number of Elements allowed in a Datamodel has been reached. /// /// - public Element ImportElement(Element foreign_element, ImportRecursionMode import_mode, ImportOverwriteMode overwrite_mode) + public Element? ImportElement(Element foreign_element, ImportRecursionMode import_mode, ImportOverwriteMode overwrite_mode) { ArgumentNullException.ThrowIfNull(foreign_element); @@ -587,7 +610,7 @@ public ImportJob(ImportRecursionMode import_mode, ImportOverwriteMode overwrite_ } } - private object CopyValue(object value, ImportJob job) + private object? CopyValue(object value, ImportJob job) { if (value == null) return null; var attr_type = value.GetType(); @@ -601,7 +624,7 @@ private object CopyValue(object value, ImportJob job) { var foreign_element = (Element)value; var local_element = AllElements[foreign_element.ID]; - Element best_element; + Element? best_element; if (local_element != null && !local_element.Stub) best_element = local_element; @@ -612,7 +635,7 @@ private object CopyValue(object value, ImportJob job) job.Depth--; } else - best_element = local_element ?? (job.ImportMode == ImportRecursionMode.Stubs ? new Element(this, foreign_element.ID) : (Element)null); + best_element = local_element ?? (job.ImportMode == ImportRecursionMode.Stubs ? new Element(this, foreign_element.ID) : null); return best_element; } @@ -627,12 +650,12 @@ private object CopyValue(object value, ImportJob job) else throw new ArgumentException("CopyValue: unhandled type."); } - private Element ImportElement_internal(Element foreign_element, ImportJob job) + private Element? ImportElement_internal(Element? foreign_element, ImportJob job) { if (foreign_element == null) return null; if (foreign_element.Owner == this) return foreign_element; - Element local_element; + Element? local_element; // don't import the same Element twice if (job.ImportMap.TryGetValue(foreign_element, out local_element)) @@ -656,7 +679,13 @@ private Element ImportElement_internal(Element foreign_element, ImportJob job) { var item = elem_array[i]; if (item != null && item.Owner != null) - elem_array[i] = ImportElement_internal(item, job); + { + var importedElement = ImportElement_internal(item, job); + if (importedElement is not null) + { + elem_array[i] = importedElement; + } + } } } } @@ -686,6 +715,12 @@ private Element ImportElement_internal(Element foreign_element, ImportJob job) else local_element = null; } + + if(local_element is null) + { + return null; + } + job.ImportMap.Add(foreign_element, local_element); // Copy attributes @@ -701,6 +736,11 @@ private Element ImportElement_internal(Element foreign_element, ImportJob job) var list = (System.Collections.ICollection)attr.Value; var inner_type = GetArrayInnerType(list.GetType()); + if(inner_type is null) + { + throw new InvalidOperationException("Failed to get inner_type while importing element"); + } + var copied_array = CodecUtilities.MakeList(inner_type, list.Count); foreach (var item in list) copied_array.Add(CopyValue(item, job)); @@ -715,75 +755,13 @@ private Element ImportElement_internal(Element foreign_element, ImportJob job) } } - #region Deprecated methods - - /// - /// Creates a new stub Element. - /// - /// - /// The ID of the stub. Must be unique within the Datamodel. - /// A new stub Element owned by this Datamodel. - /// Thrown when the maximum number of Elements allowed in a Datamodel has been reached. - [Obsolete("Elements should now be constructed directly.")] - public Element CreateStubElement(Guid id) - { - return CreateElement(null, id, true); - } - - /// - /// Creates a new Element with a random ID. - /// - /// The Element's name. Duplicates allowed. - /// The Element's class. - /// A new Element owned by this Datamodel. - /// Thrown when Datamodel.AllowRandomIDs is false. - /// Thrown when the maximum number of Elements allowed in a Datamodel has been reached. - [Obsolete("Elements should now be constructed directly.")] - public Element CreateElement(string name, string class_name = "DmElement") - { - if (!AllowRandomIDs) - throw new InvalidOperationException("Random IDs are not allowed in this Datamodel."); - - Guid id; - - do - { - id = Guid.NewGuid(); - } - while (AllElements[id] != null); - - return CreateElement(name, id, class_name); - } - - /// - /// Creates a new Element. - /// - /// The Element's name. Duplicates allowed. - /// The Element's ID. Must be unique within the Datamodel. - /// The Element's class. - /// A new Element owned by this Datamodel. - /// Thrown when the maximum number of Elements allowed in a Datamodel has been reached. - [Obsolete("Elements should now be constructed directly.")] - public Element CreateElement(string name, Guid id, string class_name = "DmElement") - { - return CreateElement(name, id, false, class_name); - } - - [Obsolete("Elements should now be constructed directly.")] - internal Element CreateElement(string name, Guid id, bool stub, string classname = "DmElement") - { - return stub ? new Element(this, id) : new Element(this, name, id, classname); - } - - #endregion - #endregion #region Events /// /// Raised when the Datamodel's , , or changes. /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName()] string property = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); @@ -886,14 +864,22 @@ public class DestubException : Exception internal DestubException(Attribute attr, Exception innerException) : base("An exception occured while destubbing the value of an attribute.", innerException) { - Data.Add("Element", ((Element)attr.Owner).ID); + Data.Add("Element", ((Element?)attr.Owner)?.ID); Data.Add("Attribute", attr.Name); } internal DestubException(ElementArray array, int index, Exception innerException) : base("An exception occured while destubbing an array item.", innerException) { - Data.Add("Element", ((Element)array.Owner).ID); + var arrayOwner = array.Owner; + if(arrayOwner is not null) + { + Data.Add("Element", ((Element)arrayOwner).ID); + } + else + { + Data.Add("Element", null); + } Data.Add("Index", index); } From 5d5bf7a20e610fc198fa5057f0e352c71372b920 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 14:46:18 +0200 Subject: [PATCH 13/24] fix nonsense --- Codecs/KeyValues2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index bcdece3..fbb480b 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -664,7 +664,7 @@ string Decode_NextToken(StreamReader reader) else if (type == typeof(TimeSpan)) return TimeSpan.FromTicks((long)(double.Parse(value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond)); - var num_list = value.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); + var num_list = value.Split(); if (type == typeof(Color)) { From b71a6f8382414ed093b82af7497aeea647403563 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 14:53:35 +0200 Subject: [PATCH 14/24] actually stupidity seems to not be so stupid --- Codecs/KeyValues2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index fbb480b..cf5790a 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -664,7 +664,7 @@ string Decode_NextToken(StreamReader reader) else if (type == typeof(TimeSpan)) return TimeSpan.FromTicks((long)(double.Parse(value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond)); - var num_list = value.Split(); + var num_list = value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); if (type == typeof(Color)) { From 0c487f38a4ae30548deb0640838e6dffcb5341a7 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 15:08:00 +0200 Subject: [PATCH 15/24] modernise code --- Arrays.cs | 16 ++++++---------- AttributeList.cs | 2 +- Codecs/Binary.cs | 30 +++++++++++++++--------------- Codecs/KeyValues2.cs | 30 +++++++++++++----------------- Datamodel.ElementList.cs | 2 +- Datamodel.cs | 8 ++++---- Element.cs | 4 ++-- ICodec.cs | 25 +++++++++---------------- Types/Color.cs | 2 +- 9 files changed, 52 insertions(+), 67 deletions(-) diff --git a/Arrays.cs b/Arrays.cs index 726fb3f..21f2590 100644 --- a/Arrays.cs +++ b/Arrays.cs @@ -10,16 +10,12 @@ namespace Datamodel [DebuggerDisplay("Count = {Inner.Count}")] public abstract class Array : IList, IList { - internal class DebugView + internal class DebugView(Array arr) { - public DebugView(Array arr) - { - Arr = arr; - } - Array Arr; + readonly Array Arr = arr; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] Items { get { return Arr.Inner.ToArray(); } } + public T[] Items { get { return [.. Arr.Inner]; } } } protected List Inner; @@ -38,15 +34,15 @@ internal set internal Array() { - Inner = new List(); + Inner = []; } internal Array(IEnumerable enumerable) { if (enumerable != null) - Inner = new List(enumerable); + Inner = [.. enumerable]; else - Inner = new List(); + Inner = []; } internal Array(int capacity) diff --git a/AttributeList.cs b/AttributeList.cs index c036ae9..d0dfb04 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -100,7 +100,7 @@ public AttributeList(Datamodel? owner) } } - Inner = new OrderedDictionary(); + Inner = []; Owner = owner; } diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index 5a73213..979f3b5 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -18,7 +18,7 @@ namespace Datamodel.Codecs [CodecFormat("binary", 9)] class Binary : ICodec { - static readonly Dictionary SupportedAttributes = new(); + static readonly Dictionary SupportedAttributes = []; /// /// The number of Datamodel binary ticks in one second. Used to store TimeSpan values. @@ -28,21 +28,21 @@ class Binary : ICodec static Binary() { SupportedAttributes[1] = - SupportedAttributes[2] = new Type?[] { + SupportedAttributes[2] = [ typeof(Element), typeof(int), typeof(float), typeof(bool), typeof(string), typeof(byte[]), null /* ObjectID */, typeof(Color), typeof(Vector2), typeof(Vector3), typeof(Vector4), typeof(Vector3) /* angle*/, typeof(Quaternion), typeof(Matrix4x4) - }; + ]; SupportedAttributes[3] = SupportedAttributes[4] = - SupportedAttributes[5] = new Type[] { + SupportedAttributes[5] = [ typeof(Element), typeof(int), typeof(float), typeof(bool), typeof(string), typeof(byte[]), typeof(TimeSpan), typeof(Color), typeof(Vector2), typeof(Vector3), typeof(Vector4), typeof(Vector3) /* angle*/, typeof(Quaternion), typeof(Matrix4x4) - }; - SupportedAttributes[9] = new Type[] { + ]; + SupportedAttributes[9] = [ typeof(Element), typeof(int), typeof(float), typeof(bool), typeof(string), typeof(byte[]), typeof(TimeSpan), typeof(Color), typeof(Vector2), typeof(Vector3), typeof(Vector4), typeof(QAngle), typeof(Quaternion), typeof(Matrix4x4), typeof(ulong), typeof(byte) - }; + ]; } static byte TypeToId(Type type, int version) @@ -105,7 +105,7 @@ static byte TypeToId(Type type, int version) protected string ReadString_Raw(BinaryReader reader) { - List raw = new(); + List raw = []; while (true) { byte cur = reader.ReadByte(); @@ -124,7 +124,7 @@ class StringDictionary readonly Binary? Codec; readonly int EncodingVersion; - readonly List Strings = new(); + readonly List Strings = []; public bool Dummy; // binary 4 uses int for dictionary length, but short for dictionary indices. Whoops! @@ -156,14 +156,14 @@ public StringDictionary(int encoding_version, BinaryWriter writer, Datamodel dm) Dummy = EncodingVersion == 1; if (!Dummy) { - Scraped = new HashSet(); + Scraped = []; ScrapeElement(dm.Root); Strings = Strings.Distinct().ToList(); } } - private readonly HashSet Scraped = new(); + private readonly HashSet Scraped = []; void ScrapeElement(Element? elem) { @@ -389,7 +389,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in ConstructorInfo? constructor = typeof(Element).GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new Type[] { typeof(Datamodel), typeof(string), typeof(Guid), typeof(string) }, + [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], null ); @@ -399,7 +399,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in } object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, new object[] { dm, name, id, type }); + constructor.Invoke(uninitializedObject, [dm, name, id, type]); elem = (Element?)uninitializedObject; } @@ -548,8 +548,8 @@ public Encoder(BinaryWriter writer, Datamodel dm, int version) Datamodel = dm; StringDict = new StringDictionary(version, writer, dm); - ElementIndices = new Dictionary(); - ElementOrder = new List(); + ElementIndices = []; + ElementOrder = []; } diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index cf5790a..9b2f8d1 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -22,8 +22,8 @@ namespace Datamodel.Codecs [CodecFormat("keyvalues2_noids", 4)] class KeyValues2 : ICodec { - static readonly Dictionary TypeNames = new(); - static readonly Dictionary ValidAttributes = new(); + static readonly Dictionary TypeNames = []; + static readonly Dictionary ValidAttributes = []; static KeyValues2() { TypeNames[typeof(Element)] = "element"; @@ -131,7 +131,7 @@ public void Flush() // Multi-referenced elements are written out as a separate block at the end of the file. // In-line only the id is written. - Dictionary ReferenceCount = new(); + Dictionary ReferenceCount = []; bool SupportsReferenceIds; @@ -294,9 +294,8 @@ void WriteAttribute(string name, int encodingVersion, Type type, object value, b value = FormattableString.Invariant(FormattableStringFactory.Create(matrixString)); } - else if (value is QAngle qangle_value) + else if (value is QAngle castValue) { - var castValue = (QAngle)value; value = FormattableString.Invariant($"{castValue.Pitch} {castValue.Yaw} {castValue.Roll}"); } @@ -354,7 +353,7 @@ public void Encode(Datamodel dm, string encoding, int encodingVersion, Stream st writer.Write(String.Format(CodecUtilities.HeaderPattern, encoding, encodingVersion, dm.Format, dm.FormatVersion)); writer.WriteLine(); - ReferenceCount = new Dictionary(); + ReferenceCount = []; if (encodingVersion >= 4 && dm.PrefixAttributes.Count > 0) { @@ -404,8 +403,8 @@ private class IntermediateData // these store element refs while we process the elements, once were done // we can go trough these and actually create the attributes // and add the elements to lists - public Dictionary> PropertiesToAdd = new(); - public Dictionary> ListRefs = new(); + public Dictionary> PropertiesToAdd = []; + public Dictionary> ListRefs = []; public void HandleElementProp(Element? element, string attrName, Guid id) { @@ -418,7 +417,7 @@ public void HandleElementProp(Element? element, string attrName, Guid id) if (attrList == null) { - attrList = new List<(string, Guid)>(); + attrList = []; PropertiesToAdd.Add(element, attrList); } @@ -432,7 +431,7 @@ public void HandleListRefs(IList list, Guid id) if (guidList == null) { - guidList = new List(); + guidList = []; ListRefs.Add(list, guidList); } @@ -522,7 +521,7 @@ string Decode_NextToken(StreamReader reader) ConstructorInfo? constructor = typeof(Element).GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new Type[] { typeof(Datamodel), typeof(string), typeof(Guid), typeof(string) }, + [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], null ); @@ -532,16 +531,13 @@ string Decode_NextToken(StreamReader reader) } object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, new object[] { dataModel, elem_name, new Guid(elem_id), elem_class }); + constructor.Invoke(uninitializedObject, [dataModel, elem_name, new Guid(elem_id), elem_class]); elem = (Element?)uninitializedObject; } } - if (elem == null) - { - elem = new Element(dataModel, elem_name, new Guid(elem_id), elem_class); - } + elem ??= new Element(dataModel, elem_name, new Guid(elem_id), elem_class); } continue; @@ -664,7 +660,7 @@ string Decode_NextToken(StreamReader reader) else if (type == typeof(TimeSpan)) return TimeSpan.FromTicks((long)(double.Parse(value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond)); - var num_list = value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + var num_list = value.Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries); if (type == typeof(Color)) { diff --git a/Datamodel.ElementList.cs b/Datamodel.ElementList.cs index 0250768..e08ad74 100644 --- a/Datamodel.ElementList.cs +++ b/Datamodel.ElementList.cs @@ -32,7 +32,7 @@ public DebugView(ElementList item) public Element[] Elements => Item.store.Values.Cast().ToArray(); } - private readonly OrderedDictionary store = new(); + private readonly OrderedDictionary store = []; private readonly Datamodel Owner; internal ElementList(Datamodel owner) diff --git a/Datamodel.cs b/Datamodel.cs index 48381ea..d77f686 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -36,7 +36,7 @@ public DebugView(Datamodel dm) #region Attribute types - private static readonly Type[] attributeTypes = { + private static readonly Type[] attributeTypes = [ typeof(Element), typeof(int), typeof(float), @@ -53,7 +53,7 @@ public DebugView(Datamodel dm) typeof(byte), typeof(ulong), typeof(QAngle), - }; + ]; public static Type[] AttributeTypes => attributeTypes; @@ -110,7 +110,7 @@ static Datamodel() } #region Codecs - public static readonly Dictionary Codecs = new(); + public static readonly Dictionary Codecs = []; public static IEnumerable CodecsRegistered => Codecs.Keys.OrderBy(reg => string.Join(null, reg.Item1, reg.Item2)).ToArray(); @@ -606,7 +606,7 @@ public ImportJob(ImportRecursionMode import_mode, ImportOverwriteMode overwrite_ { ImportMode = import_mode; OverwriteMode = overwrite_mode; - ImportMap = new Dictionary(); + ImportMap = []; } } diff --git a/Element.cs b/Element.cs index b583ef0..c200dcc 100644 --- a/Element.cs +++ b/Element.cs @@ -403,11 +403,11 @@ public static bool IsElementDerived(Type type) } else { - return type == elementType ? true : false; + return type == elementType; } } - return type.BaseType == elementType ? true : false; + return type.BaseType == elementType; } } diff --git a/ICodec.cs b/ICodec.cs index 8cda330..a836278 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -38,21 +38,14 @@ public interface ICodec /// Parameters for reflection based deserialisation /// By default it will look for types in the calling assembly (the one which made this class) /// - public class ReflectionParams + /// If to use reflection or not. + /// Additional types to consider when matching. + /// Additional assemblies to look for types in. + public class ReflectionParams(bool attemptReflection = true, List? additionalTypes = null, List? assembliesToSearch = null) { - public bool AttemptReflection; - public List AdditionalTypes; - public List AssembliesToSearch; - - /// If to use reflection or not. - /// Additional types to consider when matching. - /// Additional assemblies to look for types in. - public ReflectionParams(bool attemptReflection = true, List? additionalTypes = null, List? assembliesToSearch = null) - { - AttemptReflection = attemptReflection; - AdditionalTypes = additionalTypes ??= new(); - AssembliesToSearch = assembliesToSearch ??= new(); - } + public bool AttemptReflection = attemptReflection; + public List AdditionalTypes = additionalTypes ??= []; + public List AssembliesToSearch = assembliesToSearch ??= []; } @@ -197,7 +190,7 @@ public static void AddDeferredAttribute(Element elem, string key, long offset) public static Dictionary GetReflectionTypes(ReflectionParams reflectionParams) { - Dictionary types = new(); + Dictionary types = []; if (reflectionParams.AttemptReflection) { @@ -242,7 +235,7 @@ public CodecFormatAttribute(string name, params int[] versions) public CodecFormatAttribute(string name, int version) { Name = name; - Versions = new int[] { version }; + Versions = [version]; } public string Name { get; private set; } diff --git a/Types/Color.cs b/Types/Color.cs index 798ddd8..f0413c5 100644 --- a/Types/Color.cs +++ b/Types/Color.cs @@ -13,5 +13,5 @@ public void ToBytes(Span bytes) } public byte[] ToBytes() - => new byte[] { R, G, B, A }; + => [R, G, B, A]; } From 52fac055ffab02d1c22eb5b4bd7470dd53305ed7 Mon Sep 17 00:00:00 2001 From: Angel Date: Thu, 22 May 2025 16:07:15 +0200 Subject: [PATCH 16/24] handle loading using reflection nicer --- Codecs/Binary.cs | 44 +++++++++++++++++------------- Datamodel.cs | 71 ++++++++++++++++++++++++++++++++++++++++++------ ICodec.cs | 2 +- Tests/Tests.cs | 6 ++-- 4 files changed, 91 insertions(+), 32 deletions(-) diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index 979f3b5..e46a61f 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -16,9 +16,10 @@ namespace Datamodel.Codecs [CodecFormat("binary", 4)] [CodecFormat("binary", 5)] [CodecFormat("binary", 9)] - class Binary : ICodec + class Binary : IDeferredAttributeCodec { static readonly Dictionary SupportedAttributes = []; + BinaryReader? Reader; /// /// The number of Datamodel binary ticks in one second. Used to store TimeSpan values. @@ -348,32 +349,32 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in var dm = new Datamodel(format, format_version); EncodingVersion = encoding_version; - BinaryReader reader = new BinaryReader(stream, Datamodel.TextEncoding); + Reader = new BinaryReader(stream, Datamodel.TextEncoding); if (EncodingVersion >= 9) { // Read prefix elements - foreach (int prefix_elem in Enumerable.Range(0, reader.ReadInt32())) + foreach (int prefix_elem in Enumerable.Range(0, Reader.ReadInt32())) { - foreach (int attr_index in Enumerable.Range(0, reader.ReadInt32())) + foreach (int attr_index in Enumerable.Range(0, Reader.ReadInt32())) { - var name = ReadString_Raw(reader); - var value = DecodeAttribute(dm, true, reader); + var name = ReadString_Raw(Reader); + var value = DecodeAttribute(dm, true, Reader); if (prefix_elem == 0) // skip subsequent elements...are they considered "old versions"? dm.PrefixAttributes[name] = value; } } } - StringDict = new StringDictionary(this, reader); - var num_elements = reader.ReadInt32(); + StringDict = new StringDictionary(this, Reader); + var num_elements = Reader.ReadInt32(); // read index foreach (var i in Enumerable.Range(0, num_elements)) { - var type = StringDict.ReadString(reader); - var name = EncodingVersion >= 4 ? StringDict.ReadString(reader) : ReadString_Raw(reader); - var id_bits = reader.ReadBytes(16); + var type = StringDict.ReadString(Reader); + var name = EncodingVersion >= 4 ? StringDict.ReadString(Reader) : ReadString_Raw(Reader); + var id_bits = Reader.ReadBytes(16); var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray()); Element? elem = null; @@ -417,19 +418,19 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in // assert if stub Debug.Assert(!elem.Stub); - var num_attrs = reader.ReadInt32(); + var num_attrs = Reader.ReadInt32(); foreach (var i in Enumerable.Range(0, num_attrs)) { - var name = StringDict.ReadString(reader); + var name = StringDict.ReadString(Reader); if (defer_mode == DeferredMode.Automatic && reflectionParams.AttemptReflection == false) { - CodecUtilities.AddDeferredAttribute(elem, name, reader.BaseStream.Position); - SkipAttribute(reader); + CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); + SkipAttribute(Reader); } else { - elem.Add(name, DecodeAttribute(dm, false, reader)); + elem.Add(name, DecodeAttribute(dm, false, Reader)); } } } @@ -439,10 +440,15 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in int EncodingVersion; - public object? DeferredDecodeAttribute(Datamodel dm, long offset, BinaryReader reader) + public object? DeferredDecodeAttribute(Datamodel dm, long offset) { - reader.BaseStream.Seek(offset, SeekOrigin.Begin); - return DecodeAttribute(dm, false, reader); + if(Reader is null) + { + throw new InvalidDataException("Tried to read a deferred attribute but the reader is invalid"); + } + + Reader.BaseStream.Seek(offset, SeekOrigin.Begin); + return DecodeAttribute(dm, false, Reader); } object? DecodeAttribute(Datamodel dm, bool prefix, BinaryReader reader) diff --git a/Datamodel.cs b/Datamodel.cs index d77f686..063c406 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -239,18 +239,41 @@ public void Save(string path, string encoding, int encoding_version) /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic) { - return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); + return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, null); } /// - /// Loads a Datamodel from a byte array. + /// Loads a Datamodel from a . /// /// The input Stream. /// How to handle deferred loading. - public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + /// Type hint for what the Root of this datamodel should be when using reflection + public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + where T : Element { - return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, reflectionParams); + return Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); + } + + /// + /// Loads a Datamodel from a byte array. + /// + /// The input byte array. + /// How to handle deferred loading. + public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic) + { + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode); + } + /// + /// Loads a Datamodel from a byte array. + /// + /// The input byte array. + /// How to handle deferred loading. + /// Type hint for what the Root of this datamodel should be when using reflection + public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + where T : Element + { + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, reflectionParams); } /// @@ -258,13 +281,34 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode /// /// The source file path. /// How to handle deferred loading. - public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic) { var stream = File.OpenRead(path); Datamodel? dm = null; try { - dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); + dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode); + return dm; + } + finally + { + if (defer_mode == DeferredMode.Disabled || (dm != null && dm.Codec == null)) stream.Dispose(); + } + } + /// + /// Loads a Datamodel from a file path. + /// + /// The source file path. + /// How to handle deferred loading. + /// Type hint for what the Root of this datamodel should be when using reflection + public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + where T : Element + { + var stream = File.OpenRead(path); + Datamodel? dm = null; + try + { + dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); return dm; } finally @@ -273,9 +317,16 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode } } - private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + where T : Element { - reflectionParams ??= new(); + reflectionParams ??= new (); + + if(typeof(T) == typeof(Element)) + { + reflectionParams.AttemptReflection = false; + } + reflectionParams.AssembliesToSearch.Add(callingAssembly); stream.Seek(0, SeekOrigin.Begin); @@ -316,6 +367,8 @@ private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, dm.Encoding = encoding; dm.EncodingVersion = encoding_version; + dm.Root = (T?)dm.Root; + return dm; } diff --git a/ICodec.cs b/ICodec.cs index a836278..bb9c892 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -65,7 +65,7 @@ public interface IDeferredAttributeCodec : ICodec /// The to which the Attribute belongs. /// The offset at which the Attribute begins in the source . /// The Attribute's value. - object DeferredDecodeAttribute(Datamodel dm, long offset); + object? DeferredDecodeAttribute(Datamodel dm, long offset); } /// diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 328be18..af7f3b5 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -325,14 +325,14 @@ private void Test_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) [Test] public void LoadVmap_Reflection_Binary() { - var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); Test_Vmap_Reflection(unserialisedVmap); } [Test] public void LoadVmap_Reflection_Text() { - var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); Test_Vmap_Reflection(unserialisedVmap); } @@ -548,7 +548,7 @@ void Get_TF2(Datamodel.Datamodel dm) [Test] public void Dota2_Binary_9() { - var dm = DM.Load(Binary_9_File); + var dm = DM.Load(Binary_9_File); PrintContents(dm); dm.Root.Get("skeleton").GetArray("children")[0].Any(); SaveAndConvert(dm, "binary", 9); From ff86824563fae580cb085611d659bb2a054a3b04 Mon Sep 17 00:00:00 2001 From: kristiker Date: Fri, 23 May 2025 13:35:11 +0200 Subject: [PATCH 17/24] Remove deferredmode argument from Load --- Datamodel.cs | 22 ++++++---------------- Tests/Tests.cs | 10 ++++------ Tests/ValveMap.cs | 2 ++ 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Datamodel.cs b/Datamodel.cs index 063c406..edc4fdf 100644 --- a/Datamodel.cs +++ b/Datamodel.cs @@ -270,10 +270,10 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode /// The input byte array. /// How to handle deferred loading. /// Type hint for what the Root of this datamodel should be when using reflection - public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + public static Datamodel Load(byte[] data, ReflectionParams? reflectionParams = null) where T : Element { - return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), defer_mode, reflectionParams); + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), DeferredMode.Disabled, reflectionParams); } /// @@ -296,25 +296,15 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode } } /// - /// Loads a Datamodel from a file path. + /// Loads a Datamodel from a file path, unserializing the Root as . /// /// The source file path. - /// How to handle deferred loading. /// Type hint for what the Root of this datamodel should be when using reflection - public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) + public static Datamodel Load(string path, ReflectionParams? reflectionParams = null) where T : Element { - var stream = File.OpenRead(path); - Datamodel? dm = null; - try - { - dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode, reflectionParams); - return dm; - } - finally - { - if (defer_mode == DeferredMode.Disabled || (dm != null && dm.Codec == null)) stream.Dispose(); - } + using var stream = File.OpenRead(path); + return Load_Internal(stream, Assembly.GetCallingAssembly(), DeferredMode.Disabled, reflectionParams); } private static Datamodel Load_Internal(Stream stream, Assembly callingAssembly, DeferredMode defer_mode = DeferredMode.Automatic, ReflectionParams? reflectionParams = null) diff --git a/Tests/Tests.cs b/Tests/Tests.cs index af7f3b5..f84d2a5 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -20,9 +20,7 @@ public class DatamodelTests protected FileStream Binary_4_File = File.OpenRead(TestContext.CurrentContext.TestDirectory + "/Resources/binary4.dmx"); protected FileStream KeyValues2_1_File = File.OpenRead(TestContext.CurrentContext.TestDirectory + "/Resources/taunt05.dmx"); - // TODO: would be nice if this could find this path automatically - //const string GameBin = @"C:/Program Files (x86)/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; - const string GameBin = @"D:/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; + const string GameBin = @"C:/Program Files (x86)/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/win64"; static readonly string DmxConvertExe = Path.Combine(GameBin, "dmxconvert.exe"); static readonly bool DmxConvertExe_Exists = File.Exists(DmxConvertExe); @@ -294,7 +292,7 @@ public static void TypedArrayAddingRemoving() Assert.AreEqual(0, array.Count); } - private void Test_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) + private static void Validate_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) { Assert.AreEqual(typeof(CMapRootElement), unserialisedVmap.Root.GetType()); @@ -326,14 +324,14 @@ private void Test_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) public void LoadVmap_Reflection_Binary() { var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); - Test_Vmap_Reflection(unserialisedVmap); + Validate_Vmap_Reflection(unserialisedVmap); } [Test] public void LoadVmap_Reflection_Text() { var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); - Test_Vmap_Reflection(unserialisedVmap); + Validate_Vmap_Reflection(unserialisedVmap); } public class NullOwnerElement diff --git a/Tests/ValveMap.cs b/Tests/ValveMap.cs index c1da7c7..4accf26 100644 --- a/Tests/ValveMap.cs +++ b/Tests/ValveMap.cs @@ -4,6 +4,8 @@ namespace VMAP; +#nullable enable + /// /// Valve Map (VMAP) format version 29. /// From 3358086c1c626346038e33628d79d83bc0d6b30a Mon Sep 17 00:00:00 2001 From: kristiker Date: Fri, 23 May 2025 14:10:17 +0200 Subject: [PATCH 18/24] Unduplicate some code --- Codecs/Binary.cs | 36 ++++-------------------------------- Codecs/KeyValues2.cs | 31 ++----------------------------- Element.cs | 21 --------------------- Format/Attribute.cs | 4 ++-- ICodec.cs | 42 ++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 48 insertions(+), 86 deletions(-) diff --git a/Codecs/Binary.cs b/Codecs/Binary.cs index e46a61f..daf1c82 100644 --- a/Codecs/Binary.cs +++ b/Codecs/Binary.cs @@ -377,38 +377,10 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in var id_bits = Reader.ReadBytes(16); var id = new Guid(BitConverter.IsLittleEndian ? id_bits : id_bits.Reverse().ToArray()); - Element? elem = null; - var matchedType = types.TryGetValue(type, out var classType); - - if(matchedType && classType != null && reflectionParams.AttemptReflection) - { - var isElementDerived = Element.IsElementDerived(classType); - if (isElementDerived && classType.Name == type) - { - Type derivedType = classType; - - ConstructorInfo? constructor = typeof(Element).GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, - [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], - null - ); - - if (constructor == null) - { - throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); - } - - object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, [dm, name, id, type]); - - elem = (Element?)uninitializedObject; - } - } - - if (elem == null) + if (!CodecUtilities.TryConstructCustomElement(types, dm, type, name, id, out _)) { - elem = new Element(dm, name, id, type); + // note: constructing an element, adds it to the datamodel.AllElements + _ = new Element(dm, name, id, type); } } @@ -423,7 +395,7 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in foreach (var i in Enumerable.Range(0, num_attrs)) { var name = StringDict.ReadString(Reader); - if (defer_mode == DeferredMode.Automatic && reflectionParams.AttemptReflection == false) + if (defer_mode == DeferredMode.Automatic) { CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); SkipAttribute(Reader); diff --git a/Codecs/KeyValues2.cs b/Codecs/KeyValues2.cs index 9b2f8d1..ee33199 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -509,35 +509,8 @@ string Decode_NextToken(StreamReader reader) var id = new Guid(elem_id); if (elem_class != "$prefix_element$") { - var matchedType = types.TryGetValue(elem_class, out var classType); - - if (matchedType && classType != null && reflectionParams.AttemptReflection) - { - var isElementDerived = Element.IsElementDerived(classType); - if (isElementDerived && classType.Name == elem_class) - { - Type derivedType = classType; - - ConstructorInfo? constructor = typeof(Element).GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, - [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], - null - ); - - if (constructor == null) - { - throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); - } - - object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, [dataModel, elem_name, new Guid(elem_id), elem_class]); - - elem = (Element?)uninitializedObject; - } - } - - elem ??= new Element(dataModel, elem_name, new Guid(elem_id), elem_class); + CodecUtilities.TryConstructCustomElement(types, dataModel, elem_class, elem_name, id, out elem); + elem ??= new Element(dataModel, elem_name, id, elem_class); } continue; diff --git a/Element.cs b/Element.cs index c200dcc..bbb759a 100644 --- a/Element.cs +++ b/Element.cs @@ -388,27 +388,6 @@ public bool Equals(Element? other) { return other != null && ID == other.ID; } - - public static bool IsElementDerived(Type type) - { - var elementType = typeof(Element); - - while (type.BaseType != elementType) - { - var baseType = type.BaseType; - - if (baseType != null) - { - type = baseType; - } - else - { - return type == elementType; - } - } - - return type.BaseType == elementType; - } } namespace TypeConverters diff --git a/Format/Attribute.cs b/Format/Attribute.cs index eae8549..69a2538 100644 --- a/Format/Attribute.cs +++ b/Format/Attribute.cs @@ -8,12 +8,12 @@ public sealed class DMProperty : System.Attribute { /// The name to use for serialization. /// Ignore serialization if property is on the default value. - public DMProperty(string name = "", bool optional = false) + public DMProperty(string? name = null, bool optional = false) { Name = name; Optional = optional; } - public string Name { get; } + public string? Name { get; } public bool Optional { get; } } diff --git a/ICodec.cs b/ICodec.cs index bb9c892..7b2b71e 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Reflection; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Datamodel.Codecs { @@ -198,18 +199,55 @@ public static Dictionary GetReflectionTypes(ReflectionParams refle { foreach (var classType in assembly.DefinedTypes) { - types.TryAdd(classType.Name, classType); + if (classType.IsSubclassOf(typeof(Element))) + { + types.TryAdd(classType.Name, classType); + } } } foreach (var type in reflectionParams.AdditionalTypes) { - types.TryAdd(type.Name, type); + if (type.IsSubclassOf(typeof(Element))) + { + types.TryAdd(type.Name, type); + } } } return types; } + + public static bool TryConstructCustomElement(Dictionary types, Datamodel dataModel, string elem_class, string elem_name, Guid elem_id, out Element? elem) + { + var matchedType = types.TryGetValue(elem_class, out var classType); + + if (!matchedType || classType is null) + { + elem = null; + return false; + } + + Type derivedType = classType; + + ConstructorInfo? constructor = typeof(Element).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], + null + ); + + if (constructor == null) + { + throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); + } + + object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); + constructor.Invoke(uninitializedObject, [dataModel, elem_name, elem_id, elem_class]); + + elem = (Element?)uninitializedObject; + return true; + } } [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] From 0cfecabeccfc9b6f0e7a3f6bbb160f9753d7f60b Mon Sep 17 00:00:00 2001 From: kristiker Date: Fri, 23 May 2025 14:29:13 +0200 Subject: [PATCH 19/24] Bring back naming convention attribute --- Element.cs | 13 ++++------- Format/Attribute.cs | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Element.cs b/Element.cs index bbb759a..20ccbfc 100644 --- a/Element.cs +++ b/Element.cs @@ -27,7 +27,7 @@ public class Element : AttributeList, IEquatable /// An arbitrary string. Does not have to be unique, and can be null. /// An arbitrary string which loosely defines the type of Element this is. Cannot be null. /// Thrown when the owner already contains the maximum number of Elements allowed in a Datamodel. - public Element(Datamodel owner, string name, Guid? id = null, string classNameOverride = "") + public Element(Datamodel owner, string name, Guid? id = null, string? classNameOverride = null) : base(owner) { ArgumentNullException.ThrowIfNull(owner); @@ -174,18 +174,13 @@ internal set foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { // Check if the property is an auto-property and is declared by a subclass of Element - var declaringType = property.DeclaringType; + var declaringType = property.DeclaringType!; - if(declaringType is null) - { - throw new InvalidOperationException("Declaring type is null"); - } - - if (property.GetIndexParameters().Length == 0 && declaringType.IsSubclassOf(typeof(Element))) + if (declaringType.IsSubclassOf(typeof(Element))) { var name = property.Name; + name = declaringType.GetCustomAttribute()?.GetAttributeName(name, property.PropertyType) ?? name; name = property.GetCustomAttribute()?.Name ?? name; - properties.Add((name, property)); } } diff --git a/Format/Attribute.cs b/Format/Attribute.cs index 69a2538..75b649c 100644 --- a/Format/Attribute.cs +++ b/Format/Attribute.cs @@ -3,6 +3,61 @@ namespace Datamodel.Format; +/// +/// Subclass this attribute to define a custom attribute name convention. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public abstract class AttributeNamingConventionAttribute : System.Attribute +{ + public abstract string GetAttributeName(string propertyName, Type propertyType); +} + +/// +/// This class' property names are mostly lowercase. +/// +public class LowercasePropertiesAttribute : AttributeNamingConventionAttribute +{ + public override string GetAttributeName(string propertyName, Type _) + => propertyName.ToLower(); +} + +/// +/// This class' property names are mostly camelCase. +/// +public class CamelCasePropertiesAttribute : AttributeNamingConventionAttribute +{ + public override string GetAttributeName(string propertyName, Type _) + => char.ToLowerInvariant(propertyName.AsSpan()[0]) + propertyName[1..]; +} + +/// +/// This class' property names are mostly m_hungarian. +/// +public class HungarianPropertiesAttribute : CamelCasePropertiesAttribute +{ + public override string GetAttributeName(string propertyName, Type propertyType) + { + var typeAnnotation = propertyType switch + { + _ when propertyType == typeof(int) => "n", + _ when propertyType == typeof(float) => "fl", + _ when propertyType == typeof(bool) => "b", + _ when propertyType == typeof(Vector2) => "v", + _ when propertyType == typeof(Vector3) => "v", + _ when propertyType == typeof(Vector4) => "v", + _ when propertyType == typeof(Matrix4x4) => "mat", + _ => string.Empty, + }; + + if (typeAnnotation == string.Empty) + { + return "m_" + base.GetAttributeName(propertyName, propertyType); + } + + return "m_" + typeAnnotation + propertyName; + } +} + [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public sealed class DMProperty : System.Attribute { From a7f561882ea8d903c5329f920c2d4d1a3b79af6c Mon Sep 17 00:00:00 2001 From: kristiker Date: Fri, 23 May 2025 15:10:02 +0200 Subject: [PATCH 20/24] fr fr tests --- Tests/Tests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Tests.cs b/Tests/Tests.cs index f84d2a5..bdf62be 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -27,8 +27,8 @@ public class DatamodelTests static DatamodelTests() { - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("fr-FR"); + CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("fr-FR"); var binary = new byte[16]; Random.Shared.NextBytes(binary); From 71f6ab0d8770efa552f7b42904c0770842f6ea47 Mon Sep 17 00:00:00 2001 From: kristiker Date: Fri, 23 May 2025 15:18:26 +0200 Subject: [PATCH 21/24] Support getter only properties such as ElementArray in typed deserializer --- AttributeList.cs | 26 +++++++++++++++++++++----- ICodec.cs | 24 +++++++++++++++++++++--- Tests/ValveMap.cs | 14 ++++++++------ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/AttributeList.cs b/AttributeList.cs index d0dfb04..3f67953 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -261,13 +261,11 @@ public virtual object? this[string name] if (Owner != null && this == Owner.PrefixAttributes && value?.GetType() == typeof(Element)) throw new AttributeTypeException("Elements are not supported as prefix attributes."); - var prop_attr = (PropertyInfo?)PropertyInfos[name]; + var prop = (PropertyInfo?)PropertyInfos[name]; - if (prop_attr != null) + if (prop != null) { - PropertyInfo? prop = GetType().GetProperty(prop_attr.Name, BindingFlags.Public | BindingFlags.Instance); - - if (prop != null && prop.CanWrite) + if (prop.CanWrite) { // were actually fine with this being null, it will just set the value to null // but need to check so the type check doesn't fail if it is null @@ -284,6 +282,24 @@ public virtual object? this[string name] } else { + var existingArray = prop.GetValue(this) as Array; + var incomingArray = value as Array; + + if (existingArray is not null && incomingArray is not null) + { + // special case for reflection based deserialization + if (existingArray.Count == 0) + { + existingArray.AddRange(incomingArray); + return; + } + else + { + throw new InvalidOperationException($"Attribute '{name}' modifies property {prop.DeclaringType!.Name}.{prop.Name}, which is write only and can't be replaced."); + } + } + + throw new InvalidDataException("Property of deserialisation class must be writeable, make sure it's public and has a public setter"); } diff --git a/ICodec.cs b/ICodec.cs index 7b2b71e..0c6ff35 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -230,20 +230,38 @@ public static bool TryConstructCustomElement(Dictionary types, Dat Type derivedType = classType; - ConstructorInfo? constructor = typeof(Element).GetConstructor( + ConstructorInfo? elementConstructor = typeof(Element).GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], null ); - if (constructor == null) + var customClassInitializer = derivedType.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + [], + null + ); + + if (elementConstructor == null) { throw new InvalidOperationException("Failed to get constructor while attemption reflection based deserialisation"); } + if (customClassInitializer == null) + { + throw new InvalidOperationException("Failed to get custom element constructor."); + } + object uninitializedObject = RuntimeHelpers.GetUninitializedObject(derivedType); - constructor.Invoke(uninitializedObject, [dataModel, elem_name, elem_id, elem_class]); + + elementConstructor.Invoke(uninitializedObject, [dataModel, elem_name, elem_id, elem_class]); + + // this will initialize values such as + // public Datamodel.ElementArray Children { get; } = []; + customClassInitializer.Invoke(uninitializedObject, []); + elem = (Element?)uninitializedObject; return true; diff --git a/Tests/ValveMap.cs b/Tests/ValveMap.cs index 4accf26..0af162f 100644 --- a/Tests/ValveMap.cs +++ b/Tests/ValveMap.cs @@ -175,16 +175,17 @@ internal class CMapVariableSet : DMElement } +[CamelCaseProperties] internal class CMapSelectionSet : DMElement { - public Datamodel.ElementArray children { get; set; } = []; - public string selectionSetName { get; set; } = string.Empty; - public CObjectSelectionSetDataElement selectionSetData { get; set; } = []; + public Datamodel.ElementArray Children { get; } = []; + public string SelectionSetName { get; set; } = string.Empty; + public CObjectSelectionSetDataElement SelectionSetData { get; set; } = []; public CMapSelectionSet() { } public CMapSelectionSet(string name) { - selectionSetName = name; + SelectionSetName = name; } } @@ -202,13 +203,14 @@ internal class CMapEntity : BaseEntity } +[LowercaseProperties] internal class CMapInstance : BaseEntity { /// /// A target to instance. With custom tint and transform. /// - public CMapGroup? target { get; set; } - public Datamodel.Color tintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); + public CMapGroup? Target { get; set; } + public Datamodel.Color TintColor { get; set; } = new Datamodel.Color(255, 255, 255, 255); } internal class CMapGroup : MapNode From 5b127dc9de9664403aa654dfc99160357172a273 Mon Sep 17 00:00:00 2001 From: Kristi K Date: Fri, 23 May 2025 23:23:06 +0200 Subject: [PATCH 22/24] Add note for `CDmePolygonMeshDataStream` --- Attribute.cs | 7 +++++-- Tests/Tests.cs | 9 +++++++++ Tests/ValveMap.cs | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Attribute.cs b/Attribute.cs index 664a40a..628811d 100644 --- a/Attribute.cs +++ b/Attribute.cs @@ -188,8 +188,11 @@ public object? Value if (arr_elem == null) continue; else if (arr_elem.Owner == null) arr_elem.Owner = OwnerDatamodel; - else if (arr_elem.Owner != OwnerDatamodel) - throw new ElementOwnershipException("One or more Elements in the assigned collection are from a different Datamodel. Use ImportElement() to copy them to this one before assigning."); + + // todo: remove ownership from values + // this is being printed on a debuggerdisplay output for some reason + // else if (arr_elem.Owner != OwnerDatamodel) + // throw new ElementOwnershipException("One or more Elements in the assigned collection are from a different Datamodel. Use ImportElement() to copy them to this one before assigning."); } } diff --git a/Tests/Tests.cs b/Tests/Tests.cs index bdf62be..2d2d257 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -10,6 +10,7 @@ using DM = Datamodel.Datamodel; using System.Globalization; using VMAP; +using Vector3Data = VMAP.CDmePolygonMeshDataStream; namespace Datamodel_Tests { @@ -316,6 +317,14 @@ private static void Validate_Vmap_Reflection(Datamodel.Datamodel unserialisedVma var meshes = world.children.Where(i => i.ClassName == "CMapMesh").OfType().ToList(); var mesh = meshes[0]; + var vertexData = mesh.meshData.vertexData; + + Assert.AreEqual(vertexData.size, 8); + Assert.AreEqual(vertexData.streams[0]["semanticName"], "position"); + + // todo: vertexData.streams[0] is not a instance of CDmePolygonMeshDataStream + // Assert.AreEqual(((Vector3Data)vertexData.streams[0]).semanticName, "position"); + Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty); } diff --git a/Tests/ValveMap.cs b/Tests/ValveMap.cs index 0af162f..1681378 100644 --- a/Tests/ValveMap.cs +++ b/Tests/ValveMap.cs @@ -342,7 +342,7 @@ internal class CDmePolygonMesh : MapNode internal class CDmePolygonMeshDataArray : DMElement { - public int Size { get; set; } + public int size { get; set; } /// /// Array of . /// From 6087c71b6b8edc0e4efec6cef0f27634751a58b1 Mon Sep 17 00:00:00 2001 From: kristiker Date: Sat, 24 May 2025 12:49:35 +0200 Subject: [PATCH 23/24] Create non-generic CDmePolygonMeshDataStream for deserializer --- AttributeList.cs | 4 +++- Tests/Tests.cs | 8 +++++--- Tests/ValveMap.cs | 14 +++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/AttributeList.cs b/AttributeList.cs index 3f67953..1761300 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -272,7 +272,9 @@ public virtual object? this[string name] if(value != null) { var valueType = value.GetType(); - if (prop.PropertyType != valueType) + + // type must be equal, or a superclass + if (prop.PropertyType != valueType && valueType.IsSubclassOf(prop.PropertyType)) { throw new InvalidDataException($"class property '{prop.Name}' with type '{prop.PropertyType}' does not match the type '{valueType}' of the value being set, this is likely a mismatch between the real class and the class from the datamodel"); } diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 2d2d257..8712cec 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -10,7 +10,6 @@ using DM = Datamodel.Datamodel; using System.Globalization; using VMAP; -using Vector3Data = VMAP.CDmePolygonMeshDataStream; namespace Datamodel_Tests { @@ -322,8 +321,11 @@ private static void Validate_Vmap_Reflection(Datamodel.Datamodel unserialisedVma Assert.AreEqual(vertexData.size, 8); Assert.AreEqual(vertexData.streams[0]["semanticName"], "position"); - // todo: vertexData.streams[0] is not a instance of CDmePolygonMeshDataStream - // Assert.AreEqual(((Vector3Data)vertexData.streams[0]).semanticName, "position"); + var typedPolygonMeshData = (VMAP.CDmePolygonMeshDataStream)vertexData.streams[0]; + Assert.AreEqual(typedPolygonMeshData.semanticName, "position"); + + var typedPolygonMeshDataStream = typedPolygonMeshData.data as Vector3Array; + Assert.IsNotNull(typedPolygonMeshDataStream); Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty); diff --git a/Tests/ValveMap.cs b/Tests/ValveMap.cs index 1681378..f8fe7e5 100644 --- a/Tests/ValveMap.cs +++ b/Tests/ValveMap.cs @@ -359,8 +359,7 @@ internal class CDmePolygonMeshSubdivisionData : DMElement public Datamodel.ElementArray streams { get; set; } = []; } - -internal class CDmePolygonMeshDataStream : DMElement +internal class CDmePolygonMeshDataStream : DMElement { public string standardAttributeName { get; set; } = string.Empty; public string semanticName { get; set; } = string.Empty; @@ -371,5 +370,14 @@ internal class CDmePolygonMeshDataStream : DMElement /// /// An int, vector2, vector3, or vector4 array. /// - public required Datamodel.Array data { get; set; } + public System.Collections.IList? data { get; set; } +} + +/// +/// Note: The deserializer does not support generic types, but the serializer does. +/// +/// Int, Vector2, Vector3, or Vector4 +internal class CDmePolygonMeshDataStream : CDmePolygonMeshDataStream +{ + public new required Datamodel.Array data { get; set; } } From b675e836a64d3f797c678daba1bdccb8e422f700 Mon Sep 17 00:00:00 2001 From: kristiker Date: Sat, 24 May 2025 13:14:14 +0200 Subject: [PATCH 24/24] Validate element types --- Tests/Tests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 8712cec..4fbc427 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -329,6 +329,23 @@ private static void Validate_Vmap_Reflection(Datamodel.Datamodel unserialisedVma Assert.That(unserialisedVmap.PrefixAttributes["map_asset_references"], Is.Not.Empty); + // iterate all datamodel elements, and verify that all their types are superclasses of Element + foreach (var elem in unserialisedVmap.AllElements) + { + if (elem.Name == "subdivisionBinding") + { + continue; // known case, skip + } + + // prefix elements, still an Element type + if (elem.ContainsKey("map_asset_references")) + { + continue; + } + + Assert.That(elem, Is.Not.TypeOf(), $"Found object {elem.ID} {elem.ClassName} that is still an Element type."); + } + } [Test]