Summary
When reading and re-writing a .SchLib, AltiumSharp can corrupt or silently drop pin data. There are two related root causes, and they affect both the published NuGet release (OriginalCircuit.AltiumSharp 1.0.2, v1) and the current master (v2 rewrite). I hit this on a ~3,300-component library where a single pin had a 268-character description; the file Altium opens fine but AltiumSharp could not round-trip it.
Symptoms
- v1 (NuGet 1.0.2):
new SchLibReader().Read(path) throws
System.IndexOutOfRangeException: Read past the end of the block
in CompoundFileReader.ReadBlock ← SchReader.ReadPrimitives ← SchLibReader.ReadComponent, when a component contains a pin whose binary Data record has an inline string longer than 255 chars.
- v2 (master): no exception, but the pin is silently mis-parsed (wrong description/name/designator/electrical/location), and several pin sibling streams are dropped on write.
Root cause 1 — pin string fields are 1-byte Pascal short strings with no >255 handling
Altium's binary pin record (RECORD=2) stores Description/Name/Designator/etc. as Pascal short strings (single length byte). Altium itself supports descriptions longer than 255 chars by storing the inline copy truncated and the full text in a sibling stream (e.g. PinWideText / PinDesc).
- Reader (v1):
SchReader.ReadPinRecord does ReadPascalShortString() for each field. On disk a 268-char description writes its length as 268 & 0xFF = 12; the reader takes 12 bytes, desyncs, reads the remaining ~256 bytes of text as later fields, and a bogus length byte then runs the cursor past the block → ReadBlock throws "Read past the end of the block".
- Reader (v2):
SchLibReader.ReadBinaryPinRecord (Serialization/Readers/SchLibReader.cs) likewise reads description = reader.ReadPascalShortString(). There's no overrun guard (it only does if (consumed < dataSize) Skip(...)), so instead of throwing it silently mis-parses. It also does not read PinWideText to recover the full description, so descriptions stored via the truncated-inline + sibling-stream convention are silently truncated.
Root cause 2 — writer can't emit >255 strings and drops pin sibling streams
- Writer (v1):
SchWriter.WritePinRecord writes WritePascalShortString(pin.Description). For a >255-char value the length byte wraps ((byte)len), producing a record AltiumSharp's own reader can no longer parse (and that doesn't match Altium's truncate-to-sibling convention).
- Writer (v2):
BinaryFormatWriter.WritePascalShortString writes (byte)Math.Min(bytes.Length, 255) then writes all bytes — i.e. it clamps the length byte to 255 but still emits the full payload, producing an inconsistent record for >255 strings. There is no fallback to a sibling stream.
- Dropped sibling streams: on write, v1 emits
PinFrac/PinWideText/PinTextData/PinSymbolLineWidth; v2 emits only PinFrac/PinSymbolLineWidth. Neither writes PinDesc, PinPackageLength, or PinFunctionData, and v2 additionally omits PinWideText/PinTextData. Any component using those streams loses that data on round-trip, even if nothing in it was edited.
Reproduction
- Take a
.SchLib containing a component whose pin has a >255-char description (stored by Altium as truncated inline + full text in PinWideText), and/or components with PinDesc/PinTextData/PinPackageLength/PinFunctionData streams.
- v1:
new SchLibReader().Read(path) → IndexOutOfRangeException: Read past the end of the block.
- v1/v2: read then
SchLibWriter().Write(...) → re-open in Altium / re-read: the long description and the listed sibling streams are missing/corrupted.
Evidence (real case)
- Component pin: 268-char description. On disk: inline length byte
0xFE (254, truncated) + full text in PinWideText. AltiumSharp v1's writer re-emitted the full 268 chars inline with length 0x0C (12) → its own reader then threw "Read past the end of the block". Comparing a pristine 3,376-component library against an AltiumSharp round-trip showed the streams PinTextData, PinDesc, PinPackageLength, PinFunctionData (and one PinWideText) silently dropped.
Suggested fix
- Reader: when a pin inline string's length is ambiguous (>255 case), recover the true length from the block boundary, and/or read the authoritative value from the
PinWideText/PinDesc sibling stream when present.
- Writer: for strings >255 chars, write the inline copy truncated and the full value to the appropriate sibling stream (matching Altium's own convention) instead of wrapping/clamping the length byte.
- Round-trip: preserve
PinDesc, PinTextData, PinPackageLength, PinFunctionData (and PinWideText in v2) — e.g. read them and re-emit, or pass through verbatim for unmodified components.
Environment
OriginalCircuit.AltiumSharp 1.0.2 (NuGet, net6.0) — confirmed via runtime exception + byte-level analysis.
master / v2 (commit dfe90e4) — confirmed by source inspection (Serialization/Readers/SchLibReader.cs, Serialization/Binary/BinaryFormatReader.cs & BinaryFormatWriter.cs, Serialization/Writers/SchLibWriter.cs); not runtime-tested.
Summary
When reading and re-writing a
.SchLib, AltiumSharp can corrupt or silently drop pin data. There are two related root causes, and they affect both the published NuGet release (OriginalCircuit.AltiumSharp1.0.2, v1) and the currentmaster(v2 rewrite). I hit this on a ~3,300-component library where a single pin had a 268-character description; the file Altium opens fine but AltiumSharp could not round-trip it.Symptoms
new SchLibReader().Read(path)throwsSystem.IndexOutOfRangeException: Read past the end of the blockin
CompoundFileReader.ReadBlock←SchReader.ReadPrimitives←SchLibReader.ReadComponent, when a component contains a pin whose binaryDatarecord has an inline string longer than 255 chars.Root cause 1 — pin string fields are 1-byte Pascal short strings with no >255 handling
Altium's binary pin record (
RECORD=2) stores Description/Name/Designator/etc. as Pascal short strings (single length byte). Altium itself supports descriptions longer than 255 chars by storing the inline copy truncated and the full text in a sibling stream (e.g.PinWideText/PinDesc).SchReader.ReadPinRecorddoesReadPascalShortString()for each field. On disk a 268-char description writes its length as268 & 0xFF = 12; the reader takes 12 bytes, desyncs, reads the remaining ~256 bytes of text as later fields, and a bogus length byte then runs the cursor past the block →ReadBlockthrows "Read past the end of the block".SchLibReader.ReadBinaryPinRecord(Serialization/Readers/SchLibReader.cs) likewise readsdescription = reader.ReadPascalShortString(). There's no overrun guard (it only doesif (consumed < dataSize) Skip(...)), so instead of throwing it silently mis-parses. It also does not readPinWideTextto recover the full description, so descriptions stored via the truncated-inline + sibling-stream convention are silently truncated.Root cause 2 — writer can't emit >255 strings and drops pin sibling streams
SchWriter.WritePinRecordwritesWritePascalShortString(pin.Description). For a >255-char value the length byte wraps ((byte)len), producing a record AltiumSharp's own reader can no longer parse (and that doesn't match Altium's truncate-to-sibling convention).BinaryFormatWriter.WritePascalShortStringwrites(byte)Math.Min(bytes.Length, 255)then writes all bytes — i.e. it clamps the length byte to 255 but still emits the full payload, producing an inconsistent record for >255 strings. There is no fallback to a sibling stream.PinFrac/PinWideText/PinTextData/PinSymbolLineWidth; v2 emits onlyPinFrac/PinSymbolLineWidth. Neither writesPinDesc,PinPackageLength, orPinFunctionData, and v2 additionally omitsPinWideText/PinTextData. Any component using those streams loses that data on round-trip, even if nothing in it was edited.Reproduction
.SchLibcontaining a component whose pin has a >255-char description (stored by Altium as truncated inline + full text inPinWideText), and/or components withPinDesc/PinTextData/PinPackageLength/PinFunctionDatastreams.new SchLibReader().Read(path)→IndexOutOfRangeException: Read past the end of the block.SchLibWriter().Write(...)→ re-open in Altium / re-read: the long description and the listed sibling streams are missing/corrupted.Evidence (real case)
0xFE(254, truncated) + full text inPinWideText. AltiumSharp v1's writer re-emitted the full 268 chars inline with length0x0C(12) → its own reader then threw "Read past the end of the block". Comparing a pristine 3,376-component library against an AltiumSharp round-trip showed the streamsPinTextData,PinDesc,PinPackageLength,PinFunctionData(and onePinWideText) silently dropped.Suggested fix
PinWideText/PinDescsibling stream when present.PinDesc,PinTextData,PinPackageLength,PinFunctionData(andPinWideTextin v2) — e.g. read them and re-emit, or pass through verbatim for unmodified components.Environment
OriginalCircuit.AltiumSharp1.0.2 (NuGet, net6.0) — confirmed via runtime exception + byte-level analysis.master/ v2 (commitdfe90e4) — confirmed by source inspection (Serialization/Readers/SchLibReader.cs,Serialization/Binary/BinaryFormatReader.cs&BinaryFormatWriter.cs,Serialization/Writers/SchLibWriter.cs); not runtime-tested.