From de5fe5022d9fb4c19519cdede608491da8766c07 Mon Sep 17 00:00:00 2001 From: mahach666 Date: Wed, 11 Nov 2026 17:32:28 +0300 Subject: [PATCH] Read full-width packed 16- and 32-bit integers little-endian The full-width form of packed shorts and packed integers was read with its bytes reversed. Entity records that store a colour, extended data size or reactor count in that form were then decoded from the wrong bit position, which failed the record or produced NaN and absurd coordinates. Read both forms byte by byte, low byte first, as raw 32-bit integers already are. Tests cover the full-width forms (aligned and unaligned), the unchanged short forms, and the object type of pre-R2010 records, which goes through the same reader. Fixes #7 --- CHANGELOG.md | 5 +++ src/DwgIO/Internal/Container/BitReaderMsb.cs | 4 +- tests/DwgIO.Tests/BitReaderTests.cs | 46 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b6d62..d950a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ API between minor releases. ## [Unreleased] +### Fixed +- Packed 16-bit and 32-bit integers in their full-width form are read little-endian. They were read with their bytes + reversed, so entity records that store a colour, extended data size or reactor count in that form were decoded from + the wrong bit position and failed or gave NaN or absurd coordinates (#7). + ## [0.4.0] - 2026-11-11 ### Added diff --git a/src/DwgIO/Internal/Container/BitReaderMsb.cs b/src/DwgIO/Internal/Container/BitReaderMsb.cs index 2c6166a..d489e4c 100644 --- a/src/DwgIO/Internal/Container/BitReaderMsb.cs +++ b/src/DwgIO/Internal/Container/BitReaderMsb.cs @@ -75,7 +75,7 @@ public uint ReadPackedInt() uint tag = ReadTwoBits(); return tag switch { - 0 => (uint)ReadBits(32), + 0 => ReadRawUInt32(), 1 => (uint)ReadBits(8), _ => 0, }; @@ -89,7 +89,7 @@ public uint ReadPackedShort() uint tag = ReadTwoBits(); return tag switch { - 0 => (uint)ReadBits(16), + 0 => ReadRawByte() | (uint)ReadRawByte() << 8, // operands run left to right: low byte first 1 => (uint)ReadBits(8), 2 => 0, _ => 256, diff --git a/tests/DwgIO.Tests/BitReaderTests.cs b/tests/DwgIO.Tests/BitReaderTests.cs index 4e6b810..0143289 100644 --- a/tests/DwgIO.Tests/BitReaderTests.cs +++ b/tests/DwgIO.Tests/BitReaderTests.cs @@ -24,6 +24,52 @@ public void ReadRawUInt32_LittleEndian() Assert.Equal(0x12345678u, reader.ReadRawUInt32()); } + [Fact] + public void ReadPackedShort_FullWidth_IsLittleEndian() + { + // 6 padding bits, then tag 00 and the bytes 0x34 0x12. + var reader = new BitReaderMsb([0x00, 0x34, 0x12]); + reader.ReadBits(6); + Assert.Equal(0x1234u, reader.ReadPackedShort()); + } + + [Fact] + public void ReadPackedInt_FullWidth_IsLittleEndian() + { + var reader = new BitReaderMsb([0x00, 0x78, 0x56, 0x34, 0x12]); + reader.ReadBits(6); + Assert.Equal(0x12345678u, reader.ReadPackedInt()); + } + + [Fact] + public void ReadPackedShort_FullWidth_Unaligned() + { + var reader = new BitReaderMsb([0x0D, 0x04, 0x80]); + Assert.Equal(0x1234u, reader.ReadPackedShort()); + } + + [Fact] + public void ReadPackedShortAndInt_ShortForms_AreUnchanged() + { + // Tags 01 (one byte 0xAB), 10 and 11. + var shorts = new BitReaderMsb([0x6A, 0xEC]); + Assert.Equal(0xABu, shorts.ReadPackedShort()); + Assert.Equal(0u, shorts.ReadPackedShort()); + Assert.Equal(256u, shorts.ReadPackedShort()); + + var ints = new BitReaderMsb([0x6A, 0xEC]); + Assert.Equal(0xABu, ints.ReadPackedInt()); + Assert.Equal(0u, ints.ReadPackedInt()); + Assert.Equal(0u, ints.ReadPackedInt()); + } + + [Fact] + public void ReadObjectType_BeforeR2010_FullWidthIsLittleEndian() + { + var reader = new BitReaderMsb([0x3D, 0x00, 0x40]); + Assert.Equal(0x1F4, reader.ReadObjectType(r2010Plus: false)); + } + [Fact] public void ReadVarUInt_MultiByteContinuation() {