using System.Buffers.Binary; using System.Text; using AcDream.Core.Items; using AcDream.Core.Net.Messages; namespace AcDream.Core.Net.Tests.Messages; public sealed class CreateObjectTests { [Fact] public void TryParse_WeenieHeaderPrefix_ReturnsNameAndItemType() { byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000002u, name: "Drudge", itemType: (uint)ItemType.Creature); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x50000002u, parsed.Value.Guid); Assert.Equal("Drudge", parsed.Value.Name); Assert.Equal((uint)ItemType.Creature, parsed.Value.ItemType); } // ----------------------------------------------------------------------- // Commit A of 2026-04-29 live-entity collision port: // PhysicsState (post-flags u32) + ObjectDescriptionFlags (PWD bitfield) // must be surfaced for downstream collision registration. // ----------------------------------------------------------------------- [Fact] public void TryParse_PhysicsState_Parsed() { // ETHEREAL_PS = 0x4 + IGNORE_COLLISIONS_PS = 0x10 → 0x14 byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000003u, name: "GhostNpc", itemType: (uint)ItemType.Creature, physicsState: 0x14u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x14u, parsed!.Value.PhysicsState); } [Fact] public void TryParse_ObjectDescriptionFlags_PlayerKillerBitsSurface() { // BF_PLAYER (0x8) | BF_PLAYER_KILLER (0x20) → a PK player. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000004u, name: "+PkPlayer", itemType: (uint)ItemType.Creature, objectDescriptionFlags: 0x8u | 0x20u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x28u, parsed!.Value.ObjectDescriptionFlags); } [Fact] public void TryParse_ObjectDescriptionFlags_PkLiteBit() { // BF_PLAYER (0x8) | BF_PKLITE_PKSTATUS (0x2000000) → a PK-lite player. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000005u, name: "+PklPlayer", itemType: (uint)ItemType.Creature, objectDescriptionFlags: 0x8u | 0x2000000u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x2000008u, parsed!.Value.ObjectDescriptionFlags); } // ----------------------------------------------------------------------- // 2026-05-15: WeenieHeader optional-tail walker landed for Useability + // UseRadius (acclient.h ITEM_USEABLE enum at line 6478). The R-key Use // gate consumes Useability; signs without USEABLE_REMOTE (0x20) silently // ignore Use. // ----------------------------------------------------------------------- [Fact] public void TryParse_NoWeenieFlags_LeavesUseabilityNull() { // Sign-like entity: weenieFlags=0 (no optional fields). // Useability stays null (parser walked past nothing). byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000006u, name: "Holtburg Sign", itemType: 0x8000u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Null(parsed!.Value.Useability); Assert.Null(parsed.Value.UseRadius); } [Fact] public void TryParse_WeenieFlagsUsable_ReadsUseability() { // Useable NPC: weenieFlags has bit 0x10 set, body carries // ITEM_USEABLE = USEABLE_REMOTE (0x20). byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000007u, name: "Tirenia", itemType: (uint)ItemType.Creature, weenieFlags: 0x10u, useability: 0x20u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x20u, parsed!.Value.Useability); } [Fact] public void TryParse_WeenieFlagsUsable_ReadsUseableNoValue() { // Holtburg sign case (observed 2026-05-16): ACE sends // weenieFlags=0x10 + Useability=USEABLE_NO (0x01) for signs. // The parser must read this verbatim — downstream code // distinguishes USEABLE_NO from USEABLE_REMOTE for the // pickup vs use gate. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x7A9B3001u, name: "Holtburg", itemType: 0x80u, // Misc weenieFlags: 0x10u, useability: 0x01u); // USEABLE_NO var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x01u, parsed!.Value.Useability); } [Fact] public void TryParse_WeenieFlagsValueAndUsableAndUseRadius_AllReadInOrder() { // Verify the walker skips Value (bit 0x8, 4 bytes) BEFORE reading // Useability (bit 0x10) and UseRadius (bit 0x20). Wire order in // ACE WorldObject_Networking.cs:99-106 is Value, Useable, UseRadius. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000008u, name: "PriceyDoor", itemType: (uint)ItemType.Misc, weenieFlags: 0x8u | 0x10u | 0x20u, value: 0x12345678u, useability: 0x20u, useRadius: 2.5f); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x20u, parsed!.Value.Useability); Assert.NotNull(parsed.Value.UseRadius); Assert.Equal(2.5f, parsed.Value.UseRadius!.Value, precision: 3); } // ----------------------------------------------------------------------- // D.5.1 (2026-06-16): IconId was discarded at cs:516 — surface it so the // action bar / equipment UI can read icon dat ids from spawn messages. // ----------------------------------------------------------------------- [Fact] public void TryParse_IconId_Surfaced() { // Icon dat id 0x06001234: the wire writer strips the 0x06000000 prefix // before packing (WritePackedDwordOfKnownType strips it), so we write // 0x1234 as the packed value and expect 0x06001234 back. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000009u, name: "SwordIcon", itemType: (uint)ItemType.MeleeWeapon, iconId: 0x1234u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06001234u, parsed!.Value.IconId); } // ----------------------------------------------------------------------- // D.5.1 (2026-06-17): extended WeenieHeader optional-tail walk — the parser // now continues past UseRadius through ALL intervening fields to reach // IconOverlay (weenieFlags bit 0x40000000) and IconUnderlay (weenieFlags2 // bit 0x01, present when objDescFlags bit 0x04000000 is set). // // Two tests: // 1. WithIconOverlay — sets only the IconOverlay bit + the minimum // intervening fields (none in this minimal body, so weenieFlags only has // 0x40000000). Verifies the parse walks to IconOverlay and captures it. // 2. WithIconOverlayAndUnderlay — sets IconOverlay + the IncludesSecondHeader // objDescFlag + weenieFlags2 bit 0x01, writes both ids, asserts both are // captured. // 3. NoOverlayBits_CommonCase — weenieFlags=0, verifies the extended walk // produces no overlay (regression guard for the common spawn path). // ----------------------------------------------------------------------- [Fact] public void TryParse_IconOverlay_CapturedFromExtendedTail() { // Only IconOverlay (0x40000000) bit set in weenieFlags. No intervening // optional fields, so the extended tail immediately reads the overlay id. // ACE WritePackedDwordOfKnownType strips the 0x06000000 prefix before // packing; the reader ORs it back in. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x5000000Au, name: "EnchantedSword", itemType: (uint)ItemType.MeleeWeapon, weenieFlags: 0x40000000u, // IconOverlay iconOverlayId: 0x1ABCu); // will be read back as 0x06001ABC var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06001ABCu, parsed!.Value.IconOverlayId); Assert.Equal(0u, parsed.Value.IconUnderlayId); } [Fact] public void TryParse_IconOverlayAndUnderlay_BothCaptured() { // IncludesSecondHeader in objDescFlags (0x04000000) makes the parser read // weenieFlags2. weenieFlags2 bit 0x01 (IconUnderlay) triggers the underlay // read. Both overlay + underlay are captured. // objectDescriptionFlags: 0x04000000 = IncludesSecondHeader // weenieFlags: 0x40000000 = IconOverlay // weenieFlags2: 0x00000001 = IconUnderlay byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x5000000Bu, name: "MagicRing", itemType: (uint)ItemType.Jewelry, objectDescriptionFlags: 0x04000000u, weenieFlags: 0x40000000u, weenieFlags2: 0x00000001u, iconOverlayId: 0x5678u, // → 0x06005678 iconUnderlayId: 0x9ABCu); // → 0x06009ABC var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06005678u, parsed!.Value.IconOverlayId); Assert.Equal(0x06009ABCu, parsed.Value.IconUnderlayId); } [Fact] public void TryParse_NoOverlayBits_CommonCase_OverlaysStayZero() { // Regression guard: most spawned entities (creatures, scenery, players) // have weenieFlags=0 and no second-header. The extended walk must not // corrupt existing parsed fields and must leave overlay ids at zero. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x5000000Cu, name: "CommonDrudge", itemType: (uint)ItemType.Creature, weenieFlags: 0u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal("CommonDrudge", parsed!.Value.Name); Assert.Equal(0u, parsed.Value.IconOverlayId); Assert.Equal(0u, parsed.Value.IconUnderlayId); Assert.Null(parsed.Value.Useability); } [Fact] public void TryParse_IntermediateFieldsBeforeIconOverlay_SkippedCorrectly() { // Verifies the cursor arithmetic for fields between UseRadius and // IconOverlay. This body sets several intermediate bits (Structure u16, // MaxStructure u16, StackSize u16, Burden u16) plus IconOverlay. // If any skip is wrong, the parser reads the wrong bytes as the // overlay id or throws, both of which the assert would catch. // 0x00000400 = Structure (u16) // 0x00000800 = MaxStructure (u16) // 0x00001000 = StackSize (u16) // 0x00200000 = Burden (u16) // 0x40000000 = IconOverlay const uint flags = 0x40000000u | 0x00200000u | 0x00001000u | 0x00000800u | 0x00000400u; byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x5000000Du, name: "FancySword", itemType: (uint)ItemType.MeleeWeapon, weenieFlags: flags, structure: 50, maxStructure: 100, stackSize: 1, burden: 300, iconOverlayId: 0x2222u); // → 0x06002222 var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06002222u, parsed!.Value.IconOverlayId); } [Fact] public void TryParse_HouseRestrictionsSkipped_ThenIconOverlayCaptured() { // Verifies that the variable-length RestrictionDB skip (weenieFlags bit // 0x04000000) lands the cursor at the correct position so that // IconOverlay (bit 0x40000000) immediately after it is still captured. // // Wire layout per ACE RestrictionDB (16 bytes, zero entries): // Version(u32) + OpenStatus(u32) + MonarchId(u32) = 12 bytes // count(u16) + numBuckets(u16) = 4 bytes // entries: count(0) × 8 = 0 bytes // total = 16 bytes // // Also exercises the IncludesSecondHeader / IconUnderlay path so that // all three optional-tail branches that follow HouseOwner are covered // in a single cursor sweep. // // weenieFlags: 0x04000000 (HouseRestrictions) | 0x40000000 (IconOverlay) // objectDescriptionFlags: 0x04000000 (IncludesSecondHeader → weenieFlags2 present) // weenieFlags2: 0x00000001 (IconUnderlay) byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x5000000Eu, name: "HousePortal", itemType: (uint)ItemType.Portal, objectDescriptionFlags: 0x04000000u, // IncludesSecondHeader weenieFlags: 0x04000000u | 0x40000000u, // HouseRestrictions + IconOverlay weenieFlags2: 0x00000001u, // IconUnderlay iconOverlayId: 0x3333u, // → 0x06003333 iconUnderlayId: 0x4444u); // → 0x06004444 var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06003333u, parsed!.Value.IconOverlayId); Assert.Equal(0x06004444u, parsed.Value.IconUnderlayId); } // ----------------------------------------------------------------------- // D.5.2 (2026-06-17): UiEffects bitfield (weenieFlags bit 0x80) — captured // instead of skipped. Drives the icon's effect-overlay recolor. // ----------------------------------------------------------------------- [Fact] public void TryParse_UiEffects_Captured() { // weenieFlags 0x80 = UiEffects; value 0x1 = Magical. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000010u, name: "MagicWand", itemType: (uint)ItemType.Caster, weenieFlags: 0x80u, uiEffects: 0x1u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x1u, parsed!.Value.UiEffects); } [Fact] public void TryParse_UiEffectsThenIconOverlay_BothCaptured() { // Verifies the cursor still reaches IconOverlay after reading (not skipping) UiEffects. byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000011u, name: "GlowSword", itemType: (uint)ItemType.MeleeWeapon, weenieFlags: 0x80u | 0x40000000u, uiEffects: 0x4u, iconOverlayId: 0x1ABCu); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x4u, parsed!.Value.UiEffects); Assert.Equal(0x06001ABCu, parsed.Value.IconOverlayId); } [Fact] public void TryParse_NoUiEffectsBit_LeavesUiEffectsZero() { byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000012u, name: "PlainRock", itemType: (uint)ItemType.Misc, weenieFlags: 0u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0u, parsed!.Value.UiEffects); } [Fact] public void TryParse_WeenieClassId_Surfaced() { byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000020u, name: "Sword", itemType: (uint)ItemType.MeleeWeapon, weenieClassId: 0xABCDu); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0xABCDu, parsed!.Value.WeenieClassId); } [Fact] public void TryParse_FullItemFields_Captured() { uint flags = 0x00000008u | 0x00001000u | 0x00002000u | 0x00200000u | 0x00000002u | 0x00000004u | 0x00004000u | 0x00008000u | 0x00010000u | 0x00020000u | 0x00040000u | 0x00000400u | 0x00000800u | 0x01000000u; byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000021u, name: "Pack", itemType: (uint)ItemType.Container, weenieFlags: flags, value: 250u, stackSize: 7, maxStackSize: 100u, burden: 42, itemsCapacity: 24, containersCapacity: 7, container: 0x50000099u, wielder: 0x5000009Au, validLocations: 0x02000000u, currentWieldedLocation: 0x02000000u, priority: 8u, structure: 5, maxStructure: 10, workmanship: 7.5f); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); var p = parsed!.Value; Assert.Equal(250, p.Value); Assert.Equal(7, p.StackSize); Assert.Equal(100, p.StackSizeMax); Assert.Equal(42, p.Burden); Assert.Equal(24, p.ItemsCapacity); Assert.Equal(7, p.ContainersCapacity); Assert.Equal(0x50000099u, p.ContainerId); Assert.Equal(0x5000009Au, p.WielderId); Assert.Equal(0x02000000u, p.ValidLocations); Assert.Equal(0x02000000u, p.CurrentWieldedLocation); Assert.Equal(8u, p.Priority); Assert.Equal(5, p.Structure); Assert.Equal(10, p.MaxStructure); Assert.Equal(7.5f, p.Workmanship); } [Fact] public void TryParse_MidTailFieldsSet_StillReachesIconOverlay() { uint flags = 0x00001000u | 0x00004000u | 0x40000000u; byte[] body = BuildMinimalCreateObjectWithWeenieHeader( guid: 0x50000022u, name: "Ring", itemType: (uint)ItemType.Jewelry, weenieFlags: flags, stackSize: 1, container: 0x500000F0u, iconOverlayId: 0x4321u); var parsed = CreateObject.TryParse(body); Assert.NotNull(parsed); Assert.Equal(0x06004321u, parsed!.Value.IconOverlayId); Assert.Equal(0x500000F0u, parsed.Value.ContainerId); } private static byte[] BuildMinimalCreateObjectWithWeenieHeader( uint guid, string name, uint itemType, uint physicsState = 0, uint objectDescriptionFlags = 0, uint weenieFlags = 0, uint weenieFlags2 = 0, uint iconId = 0, uint uiEffects = 0, uint? value = null, uint? useability = null, float? useRadius = null, uint iconOverlayId = 0, uint iconUnderlayId = 0, // intermediate fields for cursor-arithmetic test ushort? structure = null, ushort? maxStructure = null, ushort? stackSize = null, ushort? burden = null, uint weenieClassId = 0x1234, uint? maxStackSize = null, byte? itemsCapacity = null, byte? containersCapacity = null, uint? container = null, uint? wielder = null, uint? validLocations = null, uint? currentWieldedLocation = null, uint? priority = null, float? workmanship = null) { var bytes = new List(); WriteU32(bytes, CreateObject.Opcode); WriteU32(bytes, guid); // ModelData header: marker, subpalette count, texture count, animpart count. bytes.Add(0x11); bytes.Add(0); bytes.Add(0); bytes.Add(0); // PhysicsData: physics flags = 0, then PhysicsState u32, then 9 seq stamps. WriteU32(bytes, 0); WriteU32(bytes, physicsState); for (int i = 0; i < 9; i++) WriteU16(bytes, 0); Align4(bytes); // Fixed WeenieHeader prefix per ACE SerializeCreateObject. WriteU32(bytes, weenieFlags); // weenieFlags WriteString16L(bytes, name); WritePackedDword(bytes, weenieClassId); // WeenieClassId WritePackedDword(bytes, iconId); // IconId via known-type writer (prefix stripped by ACE writer) WriteU32(bytes, itemType); WriteU32(bytes, objectDescriptionFlags); Align4(bytes); // IncludesSecondHeader → weenieFlags2 written immediately after the align, // before any other optional tail field (ACE WorldObject_Networking.cs:84-85). if ((objectDescriptionFlags & 0x04000000u) != 0) WriteU32(bytes, weenieFlags2); // Optional WeenieHeader tail — same order as ACE // WorldObject_Networking.cs:87-206. Each field is written only when // its weenieFlags bit is set, matching the parser's walker exactly. // Fields not parameterized above default to 0. if ((weenieFlags & 0x00000001u) != 0) { /* PluralName — not parameterized */ } if ((weenieFlags & 0x00000002u) != 0) bytes.Add(itemsCapacity ?? 0); // ItemsCapacity u8 if ((weenieFlags & 0x00000004u) != 0) bytes.Add(containersCapacity ?? 0); // ContainersCapacity u8 if ((weenieFlags & 0x00000100u) != 0) WriteU16(bytes, 0); // AmmoType u16 if ((weenieFlags & 0x00000008u) != 0) WriteU32(bytes, value ?? 0u); // Value u32 if ((weenieFlags & 0x00000010u) != 0) WriteU32(bytes, useability ?? 0u); // Usable u32 if ((weenieFlags & 0x00000020u) != 0) // UseRadius f32 { Span tmp = stackalloc byte[4]; BinaryPrimitives.WriteSingleLittleEndian(tmp, useRadius ?? 0f); bytes.AddRange(tmp.ToArray()); } if ((weenieFlags & 0x00080000u) != 0) WriteU32(bytes, 0); // TargetType u32 if ((weenieFlags & 0x00000080u) != 0) WriteU32(bytes, uiEffects); // UiEffects u32 if ((weenieFlags & 0x00000200u) != 0) bytes.Add(0); // CombatUse sbyte/1 byte if ((weenieFlags & 0x00000400u) != 0) WriteU16(bytes, structure ?? 0); // Structure u16 if ((weenieFlags & 0x00000800u) != 0) WriteU16(bytes, maxStructure ?? 0); // MaxStructure u16 if ((weenieFlags & 0x00001000u) != 0) WriteU16(bytes, stackSize ?? 0); // StackSize u16 if ((weenieFlags & 0x00002000u) != 0) WriteU16(bytes, (ushort)(maxStackSize ?? 0)); // MaxStackSize u16 if ((weenieFlags & 0x00004000u) != 0) WriteU32(bytes, container ?? 0); // Container u32 if ((weenieFlags & 0x00008000u) != 0) WriteU32(bytes, wielder ?? 0); // Wielder u32 if ((weenieFlags & 0x00010000u) != 0) WriteU32(bytes, validLocations ?? 0); // ValidLocations u32 if ((weenieFlags & 0x00020000u) != 0) WriteU32(bytes, currentWieldedLocation ?? 0); // CurrentlyWieldedLocation u32 if ((weenieFlags & 0x00040000u) != 0) WriteU32(bytes, priority ?? 0); // Priority u32 if ((weenieFlags & 0x00100000u) != 0) bytes.Add(0); // RadarBlipColor u8 if ((weenieFlags & 0x00800000u) != 0) bytes.Add(0); // RadarBehavior u8 if ((weenieFlags & 0x08000000u) != 0) WriteU16(bytes, 0); // PScript u16 if ((weenieFlags & 0x01000000u) != 0) // Workmanship f32 { Span tmp = stackalloc byte[4]; BinaryPrimitives.WriteSingleLittleEndian(tmp, workmanship ?? 0f); bytes.AddRange(tmp.ToArray()); } if ((weenieFlags & 0x00200000u) != 0) WriteU16(bytes, burden ?? 0); // Burden u16 if ((weenieFlags & 0x00400000u) != 0) WriteU16(bytes, 0); // Spell u16 if ((weenieFlags & 0x02000000u) != 0) WriteU32(bytes, 0); // HouseOwner u32 // HouseRestrictions (0x04000000): not parameterized (zero entries). // Wire: Version(u32) + OpenStatus(u32) + MonarchId(u32) + count(u16) + numBuckets(u16) + entries. // Zero entries → 16 bytes total. if ((weenieFlags & 0x04000000u) != 0) { WriteU32(bytes, 0x10000002u); // Version WriteU32(bytes, 0u); // OpenStatus WriteU32(bytes, 0u); // MonarchId WriteU16(bytes, 0); // count WriteU16(bytes, 768); // numBuckets (retail constant) } if ((weenieFlags & 0x20000000u) != 0) WriteU32(bytes, 0); // HookItemTypes u32 if ((weenieFlags & 0x00000040u) != 0) WriteU32(bytes, 0); // Monarch u32 if ((weenieFlags & 0x10000000u) != 0) WriteU16(bytes, 0); // HookType u16 if ((weenieFlags & 0x40000000u) != 0) WritePackedDword(bytes, iconOverlayId); // IconOverlay if ((weenieFlags2 & 0x00000001u) != 0) WritePackedDword(bytes, iconUnderlayId); // IconUnderlay return bytes.ToArray(); } private static void WriteU32(List bytes, uint value) { Span tmp = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(tmp, value); bytes.AddRange(tmp.ToArray()); } private static void WriteU16(List bytes, ushort value) { Span tmp = stackalloc byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(tmp, value); bytes.AddRange(tmp.ToArray()); } private static void WritePackedDword(List bytes, uint value) { if (value <= 0x7FFF) { WriteU16(bytes, (ushort)value); return; } WriteU16(bytes, (ushort)(((value >> 16) & 0x7FFF) | 0x8000)); WriteU16(bytes, (ushort)(value & 0xFFFF)); } private static void WriteString16L(List bytes, string value) { byte[] encoded = Encoding.GetEncoding(1252).GetBytes(value); WriteU16(bytes, checked((ushort)encoded.Length)); bytes.AddRange(encoded); Align4(bytes); } private static void Align4(List bytes) { while ((bytes.Count & 3) != 0) bytes.Add(0); } }