diff --git a/src/Imcodec.CoreObject/BehaviorInstance.cs b/src/Imcodec.CoreObject/BehaviorInstance.cs index 1185e61..419f10d 100644 --- a/src/Imcodec.CoreObject/BehaviorInstance.cs +++ b/src/Imcodec.CoreObject/BehaviorInstance.cs @@ -18,10 +18,12 @@ contributors may be used to endorse or promote products derived from this software without specific prior written permission. */ +using Imcodec.ObjectProperty; + namespace Imcodec.CoreObject; -public partial record BehaviorInstance { +public partial record BehaviorInstance : PropertyClass { + public override uint GetHash() => 1578701589; - } \ No newline at end of file diff --git a/src/Imcodec.CoreObject/BehaviorTemplate.cs b/src/Imcodec.CoreObject/BehaviorTemplate.cs index db9cd98..a29d145 100644 --- a/src/Imcodec.CoreObject/BehaviorTemplate.cs +++ b/src/Imcodec.CoreObject/BehaviorTemplate.cs @@ -18,10 +18,12 @@ contributors may be used to endorse or promote products derived from this software without specific prior written permission. */ +using Imcodec.ObjectProperty; + namespace Imcodec.CoreObject; -public partial record BehaviorTemplate { +public partial record BehaviorTemplate : PropertyClass { - + public override uint GetHash() => 360231646; } \ No newline at end of file diff --git a/src/Imcodec.CoreObject/CoreObject.cs b/src/Imcodec.CoreObject/CoreObject.cs index ffc1f8c..e340798 100644 --- a/src/Imcodec.CoreObject/CoreObject.cs +++ b/src/Imcodec.CoreObject/CoreObject.cs @@ -31,12 +31,4 @@ public partial record CoreObject : PropertyClass { public CoreObject(CoreTemplate coreTemplate) => m_coreTemplate = coreTemplate; - public override void EncodeIdentifier(BitWriter writer) { - var id = m_coreTemplate.m_templateID; - - writer.WriteUInt8(id.MParts.Block); - writer.WriteUInt8(id.MParts.Type); - writer.WriteUInt32(id.MParts.TemplateId); - } - } diff --git a/src/Imcodec.CoreObject/CoreObjectSerializer.cs b/src/Imcodec.CoreObject/CoreObjectSerializer.cs index 1e05aae..3f9b7ea 100644 --- a/src/Imcodec.CoreObject/CoreObjectSerializer.cs +++ b/src/Imcodec.CoreObject/CoreObjectSerializer.cs @@ -32,11 +32,23 @@ namespace Imcodec.CoreObject; /// States whether the object is versionable. /// States the behaviors of the serializer. /// The type registry to use for serialization. -public sealed class CoreObjectSerializer(bool Versionable = false, - SerializerFlags Behaviors = SerializerFlags.UseFlags | SerializerFlags.Compress, - TypeRegistry? typeRegistry = null) : ObjectSerializer(Versionable, Behaviors, typeRegistry) { - - protected override bool PreloadObject(BitReader inputBuffer, out PropertyClass? propertyClass) { +public sealed class CoreObjectSerializer( + bool versionable = false, + SerializerFlags behaviors = SerializerFlags.UseFlags | SerializerFlags.Compress, + TypeRegistry? typeRegistry = null +) : ObjectSerializer(versionable, behaviors, typeRegistry) { + + private static readonly Dictionary s_blockAndTypeMap = new() { + { 350837933, (2, 2) }, // ClientObject + { 766500222, (104, 2) }, // WizClientObject + { 1653772158, (115, 9) }, // WizClientObjectItem + { 1167581154, (106, 2) }, // WizClientPet + { 2109552587, (108, 2) }, // WizClientMount + { 398229815, (132, 9) }, // ClientReagentItem + { 958775582, (131, 131) } // ClientRecipe + }; + + public override bool PreloadObject(BitReader inputBuffer, out PropertyClass? propertyClass) { propertyClass = null; var block = inputBuffer.ReadUInt8(); var type = inputBuffer.ReadUInt8(); @@ -49,18 +61,20 @@ protected override bool PreloadObject(BitReader inputBuffer, out PropertyClass? return propertyClass != null; } - // Otherwise, treat it as a CoreObject. // We can dispatch the type based on the template ID. - return false; // todo: fixme + var hash = GetHashFromBlockAndType(block, type); + propertyClass = DispatchType(hash); + + return propertyClass != null; } - protected override bool PreWriteObject(BitWriter writer, PropertyClass propertyClass) { + public override bool PreWriteObject(BitWriter writer, PropertyClass propertyClass) { if (propertyClass is null) { writer.WriteUInt8(0); writer.WriteUInt8(0); writer.WriteUInt32(0); - return true; + return false; } var (block, type) = GetBlockAndType(propertyClass); @@ -84,23 +98,17 @@ private static (byte, byte) GetBlockAndType(PropertyClass propClass) { return (0, 0); } - return propClass.GetHash() switch { - 350837933 => // ClientObject - (2, 2), - 766500222 => // WizClientObject - (104, 2), - 1653772158 => // WizClientObjectItem - (115, 9), - 1167581154 => // WizClientPet - (106, 2), - 2109552587 => // WizClientMount - (108, 2), - 398229815 => // ClientReagentItem - (132, 9), - 958775582 => // ClientRecipe - (131, 131), - _ => (0, 0) - }; + return ((byte, byte)) (s_blockAndTypeMap.TryGetValue((int) propClass.GetHash(), out var blockAndType) ? blockAndType : (0, 0)); + } + + private static uint GetHashFromBlockAndType(byte block, byte type) { + foreach (var (key, value) in s_blockAndTypeMap) { + if (value == (block, type)) { + return (uint) key; + } + } + + return 0; } } \ No newline at end of file diff --git a/src/Imcodec.CoreObject/CoreTemplate.cs b/src/Imcodec.CoreObject/CoreTemplate.cs index f32e0dd..02d3f9a 100644 --- a/src/Imcodec.CoreObject/CoreTemplate.cs +++ b/src/Imcodec.CoreObject/CoreTemplate.cs @@ -17,11 +17,14 @@ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. */ +using Imcodec.ObjectProperty; using Imcodec.Types; namespace Imcodec.CoreObject; -public partial record CoreTemplate { +public partial record CoreTemplate : PropertyClass { + + public override uint GetHash() => 1068918295; public GID m_templateID { get; init; } diff --git a/src/Imcodec.ObjectProperty/BindSerializer.cs b/src/Imcodec.ObjectProperty/BindSerializer.cs index 7b4d74c..d3351ba 100644 --- a/src/Imcodec.ObjectProperty/BindSerializer.cs +++ b/src/Imcodec.ObjectProperty/BindSerializer.cs @@ -74,8 +74,8 @@ public override bool Serialize(PropertyClass input, PropertyFlags propertyMask, return false; } - // Create a new buffer with the size of the output buffer plus the size of the magic. - var buffer = new byte[baseOutput!.Length + sizeof(uint)]; + // Create a new buffer with the size of the output buffer plus the size of the magic and flags. + var buffer = new byte[baseOutput!.Length + sizeof(uint) * 2]; // Write the magic header and serializer flags. BinaryPrimitives.WriteUInt32LittleEndian(buffer, BiNDMagic); @@ -114,7 +114,7 @@ public override bool Deserialize(byte[] inputBuffer, PropertyFlags propertyMa var reader = new BitReader(inputBuffer); // Check if the input buffer is too small to contain the magic header. - if (inputBuffer.Length < sizeof(uint)) { + if (inputBuffer.Length < sizeof(uint) * 2) { return false; } diff --git a/src/Imcodec.ObjectProperty/ObjectSerializer.cs b/src/Imcodec.ObjectProperty/ObjectSerializer.cs index bf0ea55..6b6b03a 100644 --- a/src/Imcodec.ObjectProperty/ObjectSerializer.cs +++ b/src/Imcodec.ObjectProperty/ObjectSerializer.cs @@ -34,7 +34,7 @@ public enum SerializerFlags { /// /// States the serializer should use these flags for deserialization. /// - UseFlags = 1 << 0, + UseFlags = 1 << 0, /// /// States the serializer should use compact length prefixes. @@ -44,17 +44,17 @@ public enum SerializerFlags { /// /// States the serializer should use string enums. /// - StringEnums = 1 << 2, + StringEnums = 1 << 2, /// /// States the serializer should use ZLib compression. /// - Compress = 1 << 3, + Compress = 1 << 3, /// /// Properties are dirty encoded. /// - DirtyEncode = 1 << 4, + DirtyEncode = 1 << 4, } @@ -202,7 +202,47 @@ public virtual bool Deserialize(byte[] inputBuffer, propertyClass?.Decode(reader, this); - output = (T)propertyClass!; + output = (T) propertyClass!; + + return true; + } + + /// + /// Preloads an object from the input buffer based on the provided hash value. + /// + /// The input buffer containing the serialized data. + /// The loaded property class, if found. + /// true if the object was preloaded successfully; otherwise, false. + public virtual bool PreloadObject(BitReader inputBuffer, + out PropertyClass? propertyClass) { + var hash = inputBuffer.ReadUInt32(); + if (hash == 0) { + propertyClass = null; + + return false; + } + + propertyClass = DispatchType(hash); + + return propertyClass != null; + } + + /// + /// Writes the object identifier to the specified . + /// + /// The to write to. + /// The to write. + /// true if the object identifier was written successfully; otherwise, false. + public virtual bool PreWriteObject(BitWriter writer, + PropertyClass propertyClass) { + if (propertyClass == null) { + writer.WriteUInt32(0); + + return false; + } + + writer.WriteUInt32(propertyClass.GetHash()); + return true; } @@ -247,33 +287,6 @@ protected virtual BitWriter Compress(BitWriter writer) { return new BitReader(decompressedData); } - /// - /// Preloads an object from the input buffer based on the provided hash value. - /// - /// The input buffer containing the serialized data. - /// The loaded property class, if found. - /// true if the object was preloaded successfully; otherwise, false. - protected virtual bool PreloadObject(BitReader inputBuffer, - out PropertyClass? propertyClass) { - var hash = inputBuffer.ReadUInt32(); - propertyClass = DispatchType(hash); - - return propertyClass != null; - } - - /// - /// Writes the object identifier to the specified . - /// - /// The to write to. - /// The to write. - /// true if the object identifier was written successfully; otherwise, false. - protected virtual bool PreWriteObject(BitWriter writer, - PropertyClass propertyClass) { - propertyClass.EncodeIdentifier(writer); - - return true; - } - /// /// Dispatches the type based on the provided hash value. /// @@ -285,7 +298,7 @@ protected virtual bool PreWriteObject(BitWriter writer, return null; } - return (PropertyClass)Activator.CreateInstance(lookupType)!; + return (PropertyClass) Activator.CreateInstance(lookupType)!; } } diff --git a/src/Imcodec.ObjectProperty/Property.cs b/src/Imcodec.ObjectProperty/Property.cs index c8667ee..a2d7b51 100644 --- a/src/Imcodec.ObjectProperty/Property.cs +++ b/src/Imcodec.ObjectProperty/Property.cs @@ -105,17 +105,11 @@ private static Type InnerType bool IProperty.Encode(BitWriter writer, ObjectSerializer serializer) { if (IsVector) { var list = Getter?.Invoke(TargetObject, null); - if (!EncodeVector(writer, list, serializer)) { - return false; - } + return EncodeVector(writer, list, serializer); } else { - if (!EncodeElement(writer, serializer, Getter?.Invoke(TargetObject, null))) { - return false; - } + return EncodeElement(writer, serializer, Getter?.Invoke(TargetObject, null)); } - - return true; } bool IProperty.Decode(BitReader reader, ObjectSerializer serializer) { @@ -159,7 +153,7 @@ private static bool EncodeVector(BitWriter writer, object? val, ObjectSerializer private static bool EncodeElement(BitWriter writer, ObjectSerializer serializer, object? val) { if (InnerType.IsSubclassOf(typeof(PropertyClass))) { - return Property.EncodePropertyClass(writer,(PropertyClass) val!, serializer); + return Property.EncodePropertyClass(writer, (PropertyClass) val!, serializer); } else if (IsEnum) { return Property.EncodeEnum(writer, val!, serializer); @@ -188,10 +182,8 @@ private static bool EncodeEnum(BitWriter writer, object val, ObjectSerializer se private static bool EncodePropertyClass(BitWriter writer, PropertyClass propertyClass, ObjectSerializer serializer) { - if (propertyClass == null) { - writer.WriteUInt32(0); - - return true; + if (!serializer.PreWriteObject(writer, propertyClass)) { + return false; } return propertyClass.Encode(writer, serializer); @@ -297,23 +289,11 @@ private static bool DecodeEnum(BitReader reader, out object val, ObjectSerialize private static bool DecodePropertyClass(BitReader reader, ObjectSerializer serializer, out PropertyClass? propertyClass) { - propertyClass = null; - - var hash = reader.ReadUInt32(); - if (hash == 0) { - return true; - } - - // Dispatch this hash and see what property class we need to create. - var fetchedType = serializer.TypeRegistry.LookupType(hash); - if (fetchedType == null) { + if (!serializer.PreloadObject(reader, out propertyClass)) { return false; } - // Create a new instance of the property class. - propertyClass = (PropertyClass) Activator.CreateInstance(fetchedType)!; - - return propertyClass.Decode(reader, serializer); + return propertyClass!.Decode(reader, serializer); } private static object? CastDecodedValue(object? value) { diff --git a/src/Imcodec.ObjectProperty/PropertyClass.cs b/src/Imcodec.ObjectProperty/PropertyClass.cs index 30ff267..a8271fe 100644 --- a/src/Imcodec.ObjectProperty/PropertyClass.cs +++ b/src/Imcodec.ObjectProperty/PropertyClass.cs @@ -132,14 +132,6 @@ internal bool Decode(BitReader reader, ObjectSerializer serializer) { return true; } - /// - /// Encodes the object identifier using the specified . - /// - /// The used to write the - /// encoded identifier. - public virtual void EncodeIdentifier(BitWriter writer) - => writer.WriteUInt32(GetHash()); - private bool EncodeVersionable(BitWriter writer, ObjectSerializer serializer) { writer.WriteUInt32(0); // Placeholder for the size. diff --git a/test/CodeGen/CodeGenTest.cs b/test/CodeGen/CodeGenTest.cs index 530745e..fe9bdb8 100644 --- a/test/CodeGen/CodeGenTest.cs +++ b/test/CodeGen/CodeGenTest.cs @@ -35,6 +35,11 @@ public void GenerateFromJsonManifestTest() { var classDefinitions = compiler.Compile(jsonDump); Assert.NotEmpty(classDefinitions); + Assert.Equal(2239, classDefinitions.Length); + + var classDefinition = classDefinitions[23]; + Assert.Equal("PetSnackBehaviorTemplate", classDefinition.Name); + Assert.Equal((double) 1956226406, classDefinition.Hash); } private static string? GetJsonDump() { diff --git a/test/CoreObjectTest/CoreObjectTests.cs b/test/CoreObjectTest/CoreObjectTests.cs new file mode 100644 index 0000000..6cc0ef8 --- /dev/null +++ b/test/CoreObjectTest/CoreObjectTests.cs @@ -0,0 +1,61 @@ +/* +BSD 3-Clause License + +Copyright (c) 2024, Jooty + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +*/ + +using Imcodec.IO; +using Imcodec.ObjectProperty; +using Imcodec.CoreObject; +using Xunit; +using Imcodec.Test.CoreObjectTest; + +namespace Imcodec.CoreObject.Tests; + +public class CoreObjectSerializerTest { + + private const string CoreObjectBlob + = """ + 70 00 00 00 78 DA 2B E6 3C CC CE C2 C0 C8 00 02 3B 97 F4 BB + 27 4D 99 29 C6 72 6F 93 CF 17 7D 6F E5 C4 2B CB 97 33 E0 04 + 0D F6 20 9D A4 02 00 57 AC 0C A5 + """; + + [Fact] + public void Serializer_Deserialize() { + + // Arrange + var blobBytes = CoreObjectBlob.Split([' ', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries) + .Select(hex => Convert.ToByte(hex, 16)) + .ToArray(); + var serializer = new CoreObjectSerializer(typeRegistry: new DummyTypeRegistry()); + + // Act + if (!serializer.Deserialize(blobBytes, 0u, out var coreObject)) { + Assert.True(false, "Failed to serialize object."); + } + + // Assert + Assert.NotNull(coreObject); + Assert.Equal(1, coreObject.m_fScale); + Assert.Equal((ulong) 264131, coreObject.m_templateID); + Assert.NotNull(coreObject.m_inactiveBehaviors); + Assert.Single(coreObject.m_inactiveBehaviors); + + } + +} \ No newline at end of file diff --git a/test/CoreObjectTest/DummyTypeRegistry.cs b/test/CoreObjectTest/DummyTypeRegistry.cs new file mode 100644 index 0000000..5c5f047 --- /dev/null +++ b/test/CoreObjectTest/DummyTypeRegistry.cs @@ -0,0 +1,88 @@ +/* +BSD 3-Clause License + +Copyright (c) 2024, Jooty + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +*/ + +using Imcodec.ObjectProperty; +using Imcodec.IO; +using Imcodec.Types; +using Imcodec.Math; +using Imcodec.CoreObject; + +namespace Imcodec.Test.CoreObjectTest; + +public class DummyTypeRegistry : TypeRegistry { + + private static Dictionary TypeMap { get; set; } = new() { + { 350837933, typeof(ClientObject) }, + { 1152306685, typeof(CoreObject) }, + { 1200596153, typeof(RenderBehavior) }, + { 1653772158, typeof(WizClientObjectItem) } + }; + + public override void RegisterType(uint hash, System.Type t) + => TypeMap[hash] = t; + + public override System.Type? LookupType(uint type) + => TypeMap.TryGetValue(type, out var t) ? t : null; + +} + +public partial record WizClientObjectItem : ClientObject { + + public override uint GetHash() => 1653772158; + + [AutoProperty(900965981, 31)] public int m_primaryColor { get; set; } + [AutoProperty(1337683384, 31)] public int m_pattern { get; set; } + [AutoProperty(1616550081, 63)] public int m_secondaryColor { get; set; } + [AutoProperty(1022427803, 63)] public GID m_displayID { get; set; } + [AutoProperty(2004128457, 31)] public uint m_itemFlags { get; set; } + +} + +public partial record ClientObject : CoreObject { + + public override uint GetHash() => 350837933; + + [AutoProperty(210498418, 31)] public GID m_characterId { get; set; } +} + +public partial record CoreObject : PropertyClass { + + public override uint GetHash() => 1152306685; + + [AutoProperty(1850812559, 31)] public List? m_inactiveBehaviors { get; set; } + [AutoProperty(2312465444, 16777247)] public ulong m_globalID { get; set; } + [AutoProperty(1298909658, 16777247)] public ulong m_permID { get; set; } + [AutoProperty(2239683611, 31)] public Vector3 m_location { get; set; } + [AutoProperty(2344058766, 31)] public Vector3 m_orientation { get; set; } + [AutoProperty(503137701, 31)] public float m_fScale { get; set; } + [AutoProperty(633907631, 31)] public ulong m_templateID { get; set; } + [AutoProperty(3553984419, 31)] public ByteString m_debugName { get; set; } + [AutoProperty(3023276954, 31)] public ByteString m_displayKey { get; set; } + [AutoProperty(965291410, 31)] public uint m_zoneTagID { get; set; } + [AutoProperty(123130076, 31)] public short m_speedMultiplier { get; set; } + [AutoProperty(1054318939, 31)] public ushort m_nMobileID { get; set; } + +} + +public partial record RenderBehavior : BehaviorInstance { + + public override uint GetHash() => 1200596153; + +} \ No newline at end of file diff --git a/test/Imcodec.Test.csproj b/test/Imcodec.Test.csproj index b09408a..65c0854 100644 --- a/test/Imcodec.Test.csproj +++ b/test/Imcodec.Test.csproj @@ -27,6 +27,7 @@ + diff --git a/test/ObjectPropertyTest/DummyTypeRegistry.cs b/test/ObjectPropertyTest/DummyTypeRegistry.cs index 304d1f1..fbdab85 100644 --- a/test/ObjectPropertyTest/DummyTypeRegistry.cs +++ b/test/ObjectPropertyTest/DummyTypeRegistry.cs @@ -82,8 +82,7 @@ public partial record LootInfoBase : PropertyClass { } -public enum LOOT_TYPE -{ +public enum LOOT_TYPE { LOOT_TYPE_NONE = 0, LOOT_TYPE_GOLD = 1,