diff --git a/Arrays.cs b/Arrays.cs index 04a7010..21f2590 100644 --- a/Arrays.cs +++ b/Arrays.cs @@ -10,21 +10,17 @@ 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; - public virtual AttributeList Owner + public virtual AttributeList? Owner { get => _Owner; internal set @@ -32,21 +28,21 @@ internal set _Owner = value; } } - AttributeList _Owner; + AttributeList? _Owner; - protected Datamodel OwnerDatamodel => Owner?.Owner; + protected Datamodel? OwnerDatamodel => Owner?.Owner; 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) @@ -94,7 +90,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 +98,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 +164,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 @@ -172,7 +180,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(); @@ -186,12 +199,21 @@ 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); + base.Insert_Internal(index, item!); } public override Element this[int index] @@ -203,13 +225,19 @@ 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) { 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..628811d 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. @@ -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; @@ -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 { @@ -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."); } } @@ -197,12 +200,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 28f5bdb..1761300 100644 --- a/AttributeList.cs +++ b/AttributeList.cs @@ -7,8 +7,9 @@ using System.Linq; using System.Numerics; -using AttrKVP = System.Collections.Generic.KeyValuePair; +using AttrKVP = System.Collections.Generic.KeyValuePair; using System.Reflection; +using System.IO; namespace Datamodel { @@ -17,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; @@ -28,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)); @@ -63,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; } } } } @@ -82,7 +88,7 @@ public enum OverrideType Binary, } - public AttributeList(Datamodel owner) + public AttributeList(Datamodel? owner) { var propertyAttributes = GetPropertyDerivedAttributeList(); PropertyInfos = new OrderedDictionary(propertyAttributes?.Count ?? 0); @@ -94,27 +100,27 @@ public AttributeList(Datamodel owner) } } - Inner = new OrderedDictionary(); + Inner = []; Owner = 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. /// /// 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; } - protected virtual ICollection<(string Name, PropertyInfo Property)> GetPropertyDerivedAttributeList() + protected virtual ICollection<(string Name, PropertyInfo Property)>? GetPropertyDerivedAttributeList() { return null; } @@ -127,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; } /// @@ -138,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; + } + } /// @@ -161,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); @@ -171,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) { @@ -199,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(); } } @@ -214,15 +233,15 @@ 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]; + var prop_attr = (PropertyInfo?)PropertyInfos[name]; if (prop_attr != null) { return prop_attr.GetValue(this); @@ -237,23 +256,64 @@ 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]; - if (prop_attr != null) + var prop = (PropertyInfo?)PropertyInfos[name]; + + if (prop != null) { - throw new InvalidOperationException($"Cannot set the value of a property-derived attribute by key. Assign to '{prop_attr.Name}' directly instead."); - } + 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 + if(value != null) + { + var valueType = value.GetType(); + + // 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"); + } + } + + prop.SetValue(this, value); + } + 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"); + } - Attribute old_attr; - Attribute new_attr; + return; + } + + 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) @@ -281,7 +341,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 @@ -296,12 +362,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)); } @@ -374,7 +444,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)); @@ -383,7 +453,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())); @@ -413,12 +483,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 { @@ -442,7 +512,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; @@ -473,7 +543,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; } } @@ -497,12 +567,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 ae6e071..daf1c82 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 { @@ -14,11 +16,10 @@ namespace Datamodel.Codecs [CodecFormat("binary", 4)] [CodecFormat("binary", 5)] [CodecFormat("binary", 9)] - class Binary : IDeferredAttributeCodec, IDisposable + class Binary : IDeferredAttributeCodec { - protected BinaryReader Reader; - - static readonly Dictionary SupportedAttributes = new(); + static readonly Dictionary SupportedAttributes = []; + BinaryReader? Reader; /// /// The number of Datamodel binary ticks in one second. Used to store TimeSpan values. @@ -28,26 +29,21 @@ class Binary : IDeferredAttributeCodec, IDisposable 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) - }; - } - - public void Dispose() - { - Reader?.Dispose(); + ]; } static byte TypeToId(Type type, int version) @@ -77,7 +73,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; @@ -100,7 +96,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) { @@ -108,12 +104,12 @@ Tuple IdToType(byte id) } } - protected string ReadString_Raw() + protected string ReadString_Raw(BinaryReader reader) { - List raw = new(); + List raw = []; while (true) { - byte cur = Reader.ReadByte(); + byte cur = reader.ReadByte(); if (cur == 0) break; else raw.Add(cur); } @@ -126,11 +122,10 @@ protected string ReadString_Raw() class StringDictionary { - readonly Binary Codec; - readonly BinaryWriter Writer; + 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! @@ -140,15 +135,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)); } } @@ -158,23 +153,20 @@ public StringDictionary(Binary codec) public StringDictionary(int encoding_version, BinaryWriter writer, Datamodel dm) { EncodingVersion = encoding_version; - Writer = writer; Dummy = EncodingVersion == 1; if (!Dummy) { - Scraped = new HashSet(); + Scraped = []; ScrapeElement(dm.Root); Strings = Strings.Distinct().ToList(); - - Scraped = null; } } - private readonly HashSet Scraped; + private readonly HashSet Scraped = []; - void ScrapeElement(Element elem) + void ScrapeElement(Element? elem) { if (elem == null || elem.Stub || Scraped.Contains(elem)) return; Scraped.Add(elem); @@ -217,38 +209,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) { @@ -257,78 +249,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], @@ -337,15 +329,17 @@ object ReadValue(Datamodel dm, Type type, bool raw_string) } 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); } - 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, ReflectionParams reflectionParams) { + var types = CodecUtilities.GetReflectionTypes(reflectionParams); + stream.Seek(0, SeekOrigin.Begin); while (true) { @@ -364,96 +358,112 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in { 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); + 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 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()); - var elem = new Element(dm, name, id, type); + if (!CodecUtilities.TryConstructCustomElement(types, dm, type, name, id, out _)) + { + // note: constructing an element, adds it to the datamodel.AllElements + _ = new Element(dm, name, id, type); + } } // 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(); foreach (var i in Enumerable.Range(0, num_attrs)) { - var name = StringDict.ReadString(); - + var name = StringDict.ReadString(Reader); if (defer_mode == DeferredMode.Automatic) { CodecUtilities.AddDeferredAttribute(elem, name, Reader.BaseStream.Position); - SkipAttribute(); + SkipAttribute(Reader); } else { - elem.Add(name, DecodeAttribute(dm, false)); + elem.Add(name, DecodeAttribute(dm, false, Reader)); } } } + return dm; } int EncodingVersion; - public object DeferredDecodeAttribute(Datamodel dm, long offset) + public object? DeferredDecodeAttribute(Datamodel dm, long offset) { + 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); + 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; } @@ -468,19 +478,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; } @@ -496,7 +506,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 @@ -516,8 +526,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 = []; } @@ -528,7 +538,7 @@ public void Encode() if (EncodingVersion >= 9) Writer.Write(0); // Prefix elements - StringDict.WriteSelf(); + StringDict.WriteSelf(Writer); { var counter = new HashSet(); //(Element.IDComparer.Default); @@ -542,8 +552,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); @@ -565,15 +580,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()); @@ -605,7 +620,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); @@ -623,7 +638,7 @@ void WriteBody(Element elem) } } - void WriteAttribute(object value, bool in_array) + void WriteAttribute(object? value, bool in_array) { if (value == null) { @@ -649,7 +664,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 713dffc..ee33199 100644 --- a/Codecs/KeyValues2.cs +++ b/Codecs/KeyValues2.cs @@ -4,6 +4,11 @@ using System.Text; using System.Numerics; using System.IO; +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Reflection; +using System.Xml.Linq; +using System.Collections; namespace Datamodel.Codecs { @@ -15,14 +20,10 @@ 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 readonly Dictionary TypeNames = []; + static readonly Dictionary ValidAttributes = []; static KeyValues2() { TypeNames[typeof(Element)] = "element"; @@ -48,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 { @@ -82,7 +77,7 @@ public void Dispose() static string Sanitise(string value) { - return value?.Replace("\"", "\\\""); + return value.Replace("\"", "\\\""); } /// @@ -134,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 = []; bool SupportsReferenceIds; - void CountReferences(Element elem) + void CountReferences(Element? elem) { + if(elem is null) + { + return; + } + if (ReferenceCount.ContainsKey(elem)) ReferenceCount[elem]++; else @@ -168,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) @@ -188,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); } } } @@ -254,124 +251,134 @@ 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 = Convert.ToHexString((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("x", CultureInfo.InvariantCulture)}"; 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 = string.Join(" ", 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) + else if (value is QAngle castValue) { - value = string.Join(" ", (int)qangle_value.Pitch, (int)qangle_value.Yaw, (int)qangle_value.Roll); + 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}")); } } - 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(); + ReferenceCount = []; - 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) { @@ -379,27 +386,69 @@ 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 #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 = []; + public Dictionary> ListRefs = []; + + 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); + + if (attrList == null) + { + attrList = []; + 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 = []; + ListRefs.Add(list, guidList); + } + + guidList.Add(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) @@ -434,111 +483,107 @@ string Decode_NextToken() } } - Element Decode_ParseElementId() + Element? Decode_ParseElement(string class_name, ReflectionParams reflectionParams, StreamReader reader, Datamodel dataModel, IntermediateData intermediateData) { - 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; - } + string elem_class = class_name ?? Decode_NextToken(reader); + string elem_name = string.Empty; + string elem_id = string.Empty; + Element? elem = null; - Element Decode_ParseElement(string class_name) - { - 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)); + 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); - 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; + CodecUtilities.TryConstructCustomElement(types, dataModel, elem_class, elem_name, id, out elem); + elem ??= new Element(dataModel, elem_name, id, elem_class); } - else if (elem_class != "$prefix_element$") - elem = new Element(DM, elem_name, new Guid(elem_id), elem_class); continue; } 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; } - if (elem == null) - continue; - if (attr_type_s == "element") { - elem.Add(attr_name, Decode_ParseElementId()); + var id_s = Decode_NextToken(reader); + + if (!string.IsNullOrEmpty(id_s)) + { + intermediateData.HandleElementProp(elem, attr_name, new Guid(id_s)); + } continue; } - object attr_value = null; + object? attr_value = null; if (attr_type == null) - attr_value = Decode_ParseElement(attr_type_s); + 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 - 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(reader); + + 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, reader, dataModel, intermediateData)); + } + // normal value + else + { + array.Add(Decode_ParseValue(attr_type, next, reflectionParams, reader, dataModel, intermediateData)); + } } } else - attr_value = Decode_ParseValue(attr_type, Decode_NextToken()); + 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) + object? Decode_ParseValue(Type type, string value, ReflectionParams reflectionParams, StreamReader reader, Datamodel dataModel, IntermediateData intermediateData) { if (type == typeof(string)) return value; @@ -546,37 +591,60 @@ object Decode_ParseValue(Type type, string value) value = value.Trim(); if (type == typeof(Element)) - return Decode_ParseElement(value); + return Decode_ParseElement(value, reflectionParams, reader, dataModel, intermediateData); 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[])) { + // 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.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); + 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)) - 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); + var num_list = value.Split(Array.Empty(), 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]); @@ -591,33 +659,52 @@ 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, 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; + var intermediateData = new IntermediateData(); + while (true) { try - { next = Decode_NextToken(); } + { next = Decode_NextToken(reader); } catch (EndOfStreamException) { break; } try - { Decode_ParseElement(next); } + { Decode_ParseElement(next, reflectionParams, reader, dataModel, intermediateData); } catch (Exception err) { throw new CodecException($"KeyValues2 decode failed on line {Line}:\n\n{err.Message}", err); } } - return DM; + foreach (var propList in intermediateData.PropertiesToAdd) + { + foreach (var prop in propList.Value) + { + propList.Key.Add(prop.Item1, dataModel.AllElements[prop.Item2]); + } + + } + + foreach (var list in intermediateData.ListRefs) + { + foreach (var id in list.Value) + { + list.Key.Add(dataModel.AllElements[id]); + } + } + + return dataModel; } #endregion } diff --git a/Datamodel.ElementList.cs b/Datamodel.ElementList.cs index 5febb9c..e08ad74 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 . @@ -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) @@ -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.NET.csproj b/Datamodel.NET.csproj index b7c9184..988672b 100644 --- a/Datamodel.NET.csproj +++ b/Datamodel.NET.csproj @@ -4,22 +4,23 @@ Library Datamodel KeyValues2 - 0.6 + 0.7 + enable COPYING README.md false - false true true snupkg - IDE0018 + false + IDE0018 True sgKey.snk - + bin\Documentation\ @@ -30,10 +31,11 @@ false + true - - + + diff --git a/Datamodel.NET.sln b/Datamodel.NET.sln index 77491dc..322437d 100644 --- a/Datamodel.NET.sln +++ b/Datamodel.NET.sln @@ -6,8 +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}") = "DmxPad", "DmxPad\DmxPad.csproj", "{B49BF0E9-46DB-4BE3-9890-02FF5C098C7C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,6 +15,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/Datamodel.cs b/Datamodel.cs index 82d235e..edc4fdf 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 { @@ -28,14 +29,14 @@ 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; } #region Attribute types - private static readonly Type[] attributeTypes = { + private static readonly Type[] attributeTypes = [ typeof(Element), typeof(int), typeof(float), @@ -52,7 +53,7 @@ public DebugView(Datamodel dm) typeof(byte), typeof(ulong), typeof(QAngle), - }; + ]; public static Type[] AttributeTypes => attributeTypes; @@ -83,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)) { @@ -109,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(); @@ -120,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."); } @@ -158,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); } /// @@ -233,16 +241,39 @@ public void Save(string path, string encoding, int encoding_version) /// How to handle deferred loading. public static Datamodel Load(Stream stream, DeferredMode defer_mode = DeferredMode.Automatic) { - return Load_Internal(stream, defer_mode); + 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. + /// 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(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), defer_mode); + 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, ReflectionParams? reflectionParams = null) + where T : Element + { + return Load_Internal(new MemoryStream(data, true), Assembly.GetCallingAssembly(), DeferredMode.Disabled, reflectionParams); } /// @@ -253,10 +284,10 @@ public static Datamodel Load(byte[] data, DeferredMode defer_mode = DeferredMode public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode.Automatic) { var stream = File.OpenRead(path); - Datamodel dm = null; + Datamodel? dm = null; try { - dm = Load_Internal(stream, defer_mode); + dm = Load_Internal(stream, Assembly.GetCallingAssembly(), defer_mode); return dm; } finally @@ -264,9 +295,30 @@ public static Datamodel Load(string path, DeferredMode defer_mode = DeferredMode if (defer_mode == DeferredMode.Disabled || (dm != null && dm.Codec == null)) stream.Dispose(); } } + /// + /// Loads a Datamodel from a file path, unserializing the Root as . + /// + /// The source file path. + /// Type hint for what the Root of this datamodel should be when using reflection + public static Datamodel Load(string path, ReflectionParams? reflectionParams = null) + where T : Element + { + using var stream = File.OpenRead(path); + return Load_Internal(stream, Assembly.GetCallingAssembly(), DeferredMode.Disabled, reflectionParams); + } - 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, ReflectionParams? reflectionParams = null) + where T : Element { + reflectionParams ??= new (); + + if(typeof(T) == typeof(Element)) + { + reflectionParams.AttemptReflection = false; + } + + reflectionParams.AssembliesToSearch.Add(callingAssembly); + stream.Seek(0, SeekOrigin.Begin); var header = string.Empty; int b; @@ -292,7 +344,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, reflectionParams); if (defer_mode == DeferredMode.Automatic && codec is IDeferredAttributeCodec deferredCodec) { dm.Stream = stream; @@ -305,16 +357,24 @@ private static Datamodel Load_Internal(Stream stream, DeferredMode defer_mode = dm.Encoding = encoding; dm.EncodingVersion = encoding_version; + dm.Root = (T?)dm.Root; + 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); @@ -326,7 +386,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 @@ -404,13 +464,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. @@ -434,13 +499,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. @@ -455,14 +525,14 @@ 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. /// /// 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 @@ -478,7 +548,7 @@ public Element Root OnPropertyChanged(); } } - Element _Root; + Element? _Root; public AttributeList PrefixAttributes { @@ -511,7 +581,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); @@ -579,11 +649,11 @@ public ImportJob(ImportRecursionMode import_mode, ImportOverwriteMode overwrite_ { ImportMode = import_mode; OverwriteMode = overwrite_mode; - ImportMap = new Dictionary(); + ImportMap = []; } } - private object CopyValue(object value, ImportJob job) + private object? CopyValue(object value, ImportJob job) { if (value == null) return null; var attr_type = value.GetType(); @@ -597,7 +667,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; @@ -608,7 +678,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; } @@ -623,12 +693,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)) @@ -652,7 +722,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; + } + } } } } @@ -682,6 +758,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 @@ -697,6 +779,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)); @@ -711,75 +798,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)); @@ -882,14 +907,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); } diff --git a/Element.cs b/Element.cs index 2f61919..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 = null) + public Element(Datamodel owner, string name, Guid? id = null, string? classNameOverride = null) : 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 @@ -161,7 +161,8 @@ internal set #region Properties - protected override ICollection<(string Name, PropertyInfo Property)> GetPropertyDerivedAttributeList() + // TODO: this could probably be sped up by caching the properties somehow + protected override ICollection<(string Name, PropertyInfo Property)>? GetPropertyDerivedAttributeList() { var type = GetType(); if (type == typeof(Element)) @@ -173,13 +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 - if (property.GetIndexParameters().Length == 0 && property.DeclaringType.IsSubclassOf(typeof(Element))) + var declaringType = property.DeclaringType!; + + if (declaringType.IsSubclassOf(typeof(Element))) { var name = property.Name; - name = property.DeclaringType.GetCustomAttribute()? - .GetAttributeName(name, property.PropertyType) ?? name; + name = declaringType.GetCustomAttribute()?.GetAttributeName(name, property.PropertyType) ?? name; name = property.GetCustomAttribute()?.Name ?? name; - properties.Add((name, property)); } } @@ -199,14 +200,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]; + 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; + return (T?)value; } /// @@ -219,7 +220,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 { @@ -227,7 +228,7 @@ public IList GetArray(string name) } 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)); } } @@ -243,7 +244,7 @@ public IList GetArray(string name) /// 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 { @@ -279,9 +280,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) @@ -289,9 +290,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) @@ -309,9 +310,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) @@ -319,9 +320,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) @@ -339,9 +340,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) @@ -349,9 +350,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) @@ -378,7 +379,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; } @@ -388,13 +389,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; @@ -416,8 +417,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 _)) @@ -425,15 +428,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/Format/Attribute.cs b/Format/Attribute.cs index 87a1802..75b649c 100644 --- a/Format/Attribute.cs +++ b/Format/Attribute.cs @@ -63,12 +63,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 = null, 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 44bb3aa..0c6ff35 100644 --- a/ICodec.cs +++ b/ICodec.cs @@ -2,6 +2,9 @@ using System.Linq; using System.IO; using System.Numerics; +using System.Reflection; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Datamodel.Codecs { @@ -29,9 +32,24 @@ 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, ReflectionParams reflectionParams); } + /// + /// Parameters for reflection based deserialisation + /// By default it will look for types in the calling assembly (the one which made this class) + /// + /// 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 = attemptReflection; + public List AdditionalTypes = additionalTypes ??= []; + public List AssembliesToSearch = assembliesToSearch ??= []; + } + + /// /// Defines methods for the deferred loading of values. /// @@ -48,7 +66,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); } /// @@ -170,6 +188,84 @@ 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 = []; + + if (reflectionParams.AttemptReflection) + { + foreach (var assembly in reflectionParams.AssembliesToSearch) + { + foreach (var classType in assembly.DefinedTypes) + { + if (classType.IsSubclassOf(typeof(Element))) + { + types.TryAdd(classType.Name, classType); + } + } + } + + foreach (var type in reflectionParams.AdditionalTypes) + { + 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? elementConstructor = typeof(Element).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + [typeof(Datamodel), typeof(string), typeof(Guid), typeof(string)], + 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); + + 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; + } } [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] @@ -195,7 +291,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/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/error.vmap b/Tests/Resources/error.vmap index 12a823c..1a686e7 100644 Binary files a/Tests/Resources/error.vmap and b/Tests/Resources/error.vmap differ diff --git a/Tests/Resources/reflectiontest.vmap b/Tests/Resources/reflectiontest.vmap new file mode 100644 index 0000000..26801a2 Binary files /dev/null and b/Tests/Resources/reflectiontest.vmap differ 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 3eaa4d4..4fbc427 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -8,7 +8,8 @@ using Datamodel; using System.Numerics; using DM = Datamodel.Datamodel; -using System.Text; +using System.Globalization; +using VMAP; namespace Datamodel_Tests { @@ -20,13 +21,15 @@ public class DatamodelTests 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"; static readonly string DmxConvertExe = Path.Combine(GameBin, "dmxconvert.exe"); static readonly bool DmxConvertExe_Exists = File.Exists(DmxConvertExe); static DatamodelTests() { + CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("fr-FR"); + CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("fr-FR"); + 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! @@ -51,7 +54,7 @@ static DatamodelTests() TestValues_V3 = TestValues_V1.Concat(new object[] { (byte)0xFF, - (ulong)0xFFFFFFFF, + (UInt64)0xFFFFFFFF, //new QAngle(0, 90, 180) }).ToList(); } @@ -219,6 +222,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() { @@ -252,70 +288,78 @@ 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) + private static void Validate_Vmap_Reflection(Datamodel.Datamodel unserialisedVmap) { - var dm = MakeDatamodel(); - Populate(dm, encoding, version); + Assert.AreEqual(typeof(CMapRootElement), unserialisedVmap.Root.GetType()); - dm.Root["Arr"] = new System.Collections.ObjectModel.ObservableCollection(); - dm.Root.GetArray("Arr"); + CMapRootElement root = (CMapRootElement)unserialisedVmap.Root; - if (memory_save) - dm.Save(new MemoryStream(), encoding, version); - else + 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"); + + 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"); + + 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); + + // iterate all datamodel elements, and verify that all their types are superclasses of Element + foreach (var elem in unserialisedVmap.AllElements) { - dm.Save(DmxSavePath, encoding, version); - if (SaveAndConvert(dm, encoding, version)) + if (elem.Name == "subdivisionBinding") { - ValidatePopulated(encoding, version); + continue; // known case, skip } - Cleanup(); - } - dm.AllElements.Remove(dm.Root.GetArray("ElemArray")[3], DM.ElementList.RemoveMode.MakeStubs); - Assert.AreEqual(true, dm.Root.GetArray("ElemArray")[3].Stub); + // prefix elements, still an Element type + if (elem.ContainsKey("map_asset_references")) + { + continue; + } - dm.AllElements.Remove(dm.Root, DM.ElementList.RemoveMode.MakeStubs); - Assert.AreEqual(true, dm.Root.Stub); + Assert.That(elem, Is.Not.TypeOf(), $"Found object {elem.ID} {elem.ClassName} that is still an Element type."); + } - return dm; } - } - [TestFixture] - public class Functionality : DatamodelTests - { [Test] - public void Create_Datamodel_Vmap() + public void LoadVmap_Reflection_Binary() { - 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")); + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap")); + Validate_Vmap_Reflection(unserialisedVmap); + } - //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"])); + [Test] + public void LoadVmap_Reflection_Text() + { + var unserialisedVmap = DM.Load(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "cs2_map.vmap.txt")); + Validate_Vmap_Reflection(unserialisedVmap); } public class NullOwnerElement @@ -401,16 +445,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() { @@ -458,7 +492,7 @@ public void SerializesText() using var stream = new MemoryStream(); dm.Save(stream, "keyvalues2", 4); - + stream.Position = 0; using (var reader = new StreamReader(stream)) { @@ -478,7 +512,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); @@ -540,7 +574,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); @@ -584,7 +618,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 ac488e4..2559203 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -5,13 +5,15 @@ Datamodel_Tests Datamodel.NET-Tests false - false PreserveNewest + + + diff --git a/Tests/ValveMap.cs b/Tests/ValveMap.cs new file mode 100644 index 0000000..f8fe7e5 --- /dev/null +++ b/Tests/ValveMap.cs @@ -0,0 +1,383 @@ +using Datamodel.Format; +using System.Numerics; +using DMElement = Datamodel.Element; + +namespace VMAP; + +#nullable enable + +/// +/// 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; } = []; + [DMProperty(name: "visbility")] + 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; } = []; +} + + +[CamelCaseProperties] +internal class CMapSelectionSet : DMElement +{ + 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; + } +} + + +internal class CObjectSelectionSetDataElement : DMElement +{ + public Datamodel.ElementArray selectedObjects { get; set; } = []; +} + + +internal class CMapEntity : BaseEntity +{ + public Vector3 hitNormal { get; set; } + public bool isProceduralEntity { get; set; } +} + + +[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); +} + +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 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; } +} 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]; }