using System; using System.Buffers.Binary; using System.Collections.Generic; namespace AcDream.Core.Net.Messages; /// /// Retail Enchantment::UnPack reader shared by PlayerDescription and /// the 0x02C2/0x02C4 magic update events. The record is 60 bytes, plus the /// optional four-byte spell-set id. /// /// /// Retail reference: CM_Magic::DispatchUI_UpdateEnchantment /// (0x006A32F0). Layout cross-checked against ACE /// Network/Structure/Enchantment.cs. /// public static class EnchantmentWireReader { public static PlayerDescriptionParser.EnchantmentEntry Read( ReadOnlySpan source, ref int position, PlayerDescriptionParser.EnchantmentBucket bucket = 0) { if (source.Length - position < 60) throw new FormatException("truncated enchantment record"); ushort spellId = ReadU16(source, ref position); ushort layer = ReadU16(source, ref position); ushort spellCategory = ReadU16(source, ref position); ushort hasSpellSetId = ReadU16(source, ref position); uint powerLevel = ReadU32(source, ref position); double startTime = ReadF64(source, ref position); double duration = ReadF64(source, ref position); uint casterGuid = ReadU32(source, ref position); float degradeModifier = ReadF32(source, ref position); float degradeLimit = ReadF32(source, ref position); double lastDegraded = ReadF64(source, ref position); uint statModType = ReadU32(source, ref position); uint statModKey = ReadU32(source, ref position); float statModValue = ReadF32(source, ref position); uint? spellSetId = hasSpellSetId != 0 ? ReadU32(source, ref position) : null; if (!double.IsFinite(startTime) || !double.IsFinite(duration) || !float.IsFinite(degradeModifier) || !float.IsFinite(degradeLimit) || !double.IsFinite(lastDegraded) || !float.IsFinite(statModValue)) { throw new FormatException("non-finite enchantment value"); } return new PlayerDescriptionParser.EnchantmentEntry( spellId, layer, spellCategory, hasSpellSetId, powerLevel, startTime, duration, casterGuid, degradeModifier, degradeLimit, lastDegraded, statModType, statModKey, statModValue, spellSetId, bucket); } public static IReadOnlyList ReadList( ReadOnlySpan source, ref int position, PlayerDescriptionParser.EnchantmentBucket bucket = 0) { if (source.Length - position < 4) throw new FormatException("truncated enchantment list count"); uint count = ReadU32(source, ref position); if (count > 0x4000) throw new FormatException("unreasonable enchantment list count"); var result = new List((int)count); for (uint i = 0; i < count; i++) result.Add(Read(source, ref position, bucket)); return result; } private static ushort ReadU16(ReadOnlySpan source, ref int position) { Require(source, position, 2); ushort value = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(position, 2)); position += 2; return value; } private static uint ReadU32(ReadOnlySpan source, ref int position) { Require(source, position, 4); uint value = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(position, 4)); position += 4; return value; } private static float ReadF32(ReadOnlySpan source, ref int position) { Require(source, position, 4); float value = BinaryPrimitives.ReadSingleLittleEndian(source.Slice(position, 4)); position += 4; return value; } private static double ReadF64(ReadOnlySpan source, ref int position) { Require(source, position, 8); double value = BinaryPrimitives.ReadDoubleLittleEndian(source.Slice(position, 8)); position += 8; return value; } private static void Require(ReadOnlySpan source, int position, int count) { if (position < 0 || source.Length - position < count) throw new FormatException("truncated enchantment record"); } }