KiCad PCB EDA Suite
Loading...
Searching...
No Matches
orcad_cache.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * Based on the dsn2kicad reference implementation and on OrCAD file format
7 * documentation from the OpenOrCadParser project (MIT licensed).
8 *
9 * This program is free software: you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
24
25#include <algorithm>
26#include <utility>
27#include <set>
28
29#include <ki_exception.h>
30
31
32namespace
33{
34
35bool isPrimType( int aType )
36{
37 switch( aType )
38 {
39 case ORCAD_PRIM_RECT:
40 case ORCAD_PRIM_LINE:
41 case ORCAD_PRIM_ARC:
49 case ORCAD_PRIM_OLE_IMAGE: return true;
50 default: return false;
51 }
52}
53
54
55bool isSymbolType( int aTypeId )
56{
57 switch( aTypeId )
58 {
65 case ORCAD_ST_BOOKMARK_SYMBOL: return true;
66
67 // ORCAD_ST_PIN_SHAPE_SYMBOL is absent on purpose. It has no registered prefix depth, so
68 // claiming it here would promise a decode we cannot frame. No corpus file contains one.
69 default: return false;
70 }
71}
72
73
74std::optional<ORCAD_PRIMITIVE> readPrimitiveBody( ORCAD_STREAM& aStream, int aType );
75
76
77// Nested prefix-framed vector graphic inside library parts. Entered on the second of the two
78// type bytes, which is where the vector's own one-long-prefix chain begins.
79std::optional<ORCAD_PRIMITIVE> readSymbolVector( ORCAD_STREAM& aStream )
80{
81 ORCAD_STREAM::NEST_GUARD guard( aStream, wxS( "symbol vector" ) );
82
83 ORCAD_STRUCT_READER reader( aStream );
84 ORCAD_PREFIXES pfx = reader.ReadPrefixes( ORCAD_ST_SYMBOL_VECTOR, ORCAD_STREAM::npos, 1 );
85 ORCAD_STREAM::LIMIT_GUARD limit( aStream, pfx.end );
86
89 group.x1 = aStream.ReadI16();
90 group.y1 = aStream.ReadI16();
91
92 uint16_t count = aStream.ReadU16();
93
94 for( uint16_t i = 0; i < count; i++ )
95 {
96 // Nested prims use 3-byte prefix: type, 0x00, type.
97 int t = aStream.ReadU8();
98 aStream.ExpectByte( 0x00, wxS( "symbol vector prim pad" ) );
99
100 if( !isPrimType( t ) )
101 THROW_IO_ERROR( wxS( "symbol vector prim prefix mismatch" ) );
102
103 std::optional<ORCAD_PRIMITIVE> child;
104
105 if( t == ORCAD_PRIM_SYMBOL_VECTOR )
106 {
107 child = readSymbolVector( aStream );
108 }
109 else
110 {
111 int t2 = aStream.ReadU8();
112
113 if( t != t2 )
114 THROW_IO_ERROR( wxS( "symbol vector prim prefix mismatch" ) );
115
116 child = readPrimitiveBody( aStream, t );
117 }
118
119 if( child )
120 group.children.push_back( std::move( *child ) );
121 }
122
123 aStream.ReadLzt(); // vector name
124
125 if( pfx.end != 0 )
126 aStream.Seek( std::max( aStream.GetOffset(), pfx.end ) );
127
128 return group;
129}
130
131std::optional<ORCAD_PRIMITIVE> readPrimitiveBody( ORCAD_STREAM& aStream, int t1 )
132{
133 size_t start = aStream.GetOffset();
134 size_t size = aStream.ReadU32();
135 size_t available = aStream.Size() - start;
136
137 if( size > available )
138 THROW_IO_ERRORF( wxS( "primitive type %d at 0x%zx exceeds its enclosing structure" ), t1, start );
139
140 // Two byteLength conventions are mixed even within one file: the modern one counts the u32
141 // size and its 4-byte pad, the legacy one excludes them. The bound below admits both.
142 size_t recordSize = size;
143
144 if( t1 != ORCAD_PRIM_OLE_IMAGE && available - size >= 8 )
145 recordSize += 8;
146
147 std::optional<ORCAD_PRIMITIVE> prim;
148
149 {
150 ORCAD_STREAM::LIMIT_GUARD limit( aStream, start + recordSize );
151
152 static const uint8_t pad[4] = { 0x00, 0x00, 0x00, 0x00 };
153 aStream.Expect( pad, 4, wxS( "primitive pad" ) );
154
155 if( t1 == ORCAD_PRIM_RECT || t1 == ORCAD_PRIM_ELLIPSE )
156 {
159 p.x1 = aStream.ReadI32();
160 p.y1 = aStream.ReadI32();
161 p.x2 = aStream.ReadI32();
162 p.y2 = aStream.ReadI32();
163 p.lineStyle = aStream.ReadU32();
164 p.lineWidth = aStream.ReadU32();
165 p.fillStyle = aStream.ReadU32();
166 p.hatchStyle = aStream.ReadU32();
167 prim = std::move( p );
168 }
169 else if( t1 == ORCAD_PRIM_LINE )
170 {
173 p.x1 = aStream.ReadI32();
174 p.y1 = aStream.ReadI32();
175 p.x2 = aStream.ReadI32();
176 p.y2 = aStream.ReadI32();
177 p.lineStyle = aStream.ReadU32();
178 p.lineWidth = aStream.ReadU32();
179 prim = std::move( p );
180 }
181 else if( t1 == ORCAD_PRIM_ARC )
182 {
185 p.x1 = aStream.ReadI32();
186 p.y1 = aStream.ReadI32();
187 p.x2 = aStream.ReadI32();
188 p.y2 = aStream.ReadI32();
189
190 ORCAD_POINT arcStart;
191 arcStart.x = aStream.ReadI32();
192 arcStart.y = aStream.ReadI32();
193
194 ORCAD_POINT arcEnd;
195 arcEnd.x = aStream.ReadI32();
196 arcEnd.y = aStream.ReadI32();
197
198 p.start = arcStart;
199 p.end = arcEnd;
200 p.lineStyle = aStream.ReadU32();
201 p.lineWidth = aStream.ReadU32();
202 prim = std::move( p );
203 }
204 else if( t1 == ORCAD_PRIM_POLYGON || t1 == ORCAD_PRIM_POLYLINE || t1 == ORCAD_PRIM_BEZIER )
205 {
207 p.lineStyle = aStream.ReadU32();
208 p.lineWidth = aStream.ReadU32();
209
210 if( t1 == ORCAD_PRIM_POLYGON )
211 {
213 p.fillStyle = aStream.ReadU32();
214 p.hatchStyle = aStream.ReadU32();
215 }
216 else if( t1 == ORCAD_PRIM_POLYLINE )
217 {
219 }
220 else
221 {
223 }
224
225 uint16_t pointCount = aStream.ReadU16();
226
227 for( uint16_t i = 0; i < pointCount; i++ )
228 {
229 ORCAD_POINT pt;
230 pt.y = aStream.ReadI16();
231 pt.x = aStream.ReadI16();
232 p.points.push_back( pt );
233 }
234
235 prim = std::move( p );
236 }
237 else if( t1 == ORCAD_PRIM_COMMENT_TEXT )
238 {
241 p.x1 = aStream.ReadI32();
242 p.y1 = aStream.ReadI32();
243 p.x2 = aStream.ReadI32();
244 p.y2 = aStream.ReadI32();
245 p.textBoundsStart = ORCAD_POINT{ aStream.ReadI32(), aStream.ReadI32() };
246 p.fontIdx = aStream.ReadU16();
247 aStream.Skip( 2 );
248 p.text = aStream.ReadLzt();
249 prim = std::move( p );
250 }
251 else if( t1 == ORCAD_PRIM_BITMAP )
252 {
255 p.x1 = aStream.ReadI32();
256 p.y1 = aStream.ReadI32();
257 p.x2 = aStream.ReadI32();
258 p.y2 = aStream.ReadI32();
259 aStream.Skip( 8 ); // x1, y1 duplicate corner
260 aStream.Skip( 8 ); // pixel width/height
261
262 uint32_t dataSize = aStream.ReadU32();
263 p.data = aStream.ReadBytes( dataSize );
264 prim = std::move( p );
265 }
266 else if( t1 == ORCAD_PRIM_OLE_IMAGE )
267 {
270 p.x1 = aStream.ReadI32();
271 p.y1 = aStream.ReadI32();
272 p.x2 = aStream.ReadI32();
273 p.y2 = aStream.ReadI32();
274 aStream.Skip( 16 ); // crop/original-extent values
275
276 // OLE compound-document payload fills the rest of the record.
277 size_t from = aStream.GetOffset();
278 size_t to = start + size;
279
280 if( to < from )
281 THROW_IO_ERRORF( wxS( "OLE primitive at 0x%zx is shorter than its header" ), start );
282
283 if( to > from )
284 p.data.assign( aStream.Data() + from, aStream.Data() + to );
285
286 aStream.Seek( to );
287 prim = std::move( p );
288 }
289
290 // CommentText keeps undecoded padding after the string, so its length owns the extent.
291 // Every other type is decoded in full, so its length must agree with one of the conventions.
292 if( t1 == ORCAD_PRIM_COMMENT_TEXT )
293 {
294 aStream.Seek( start + recordSize );
295 }
296 else
297 {
298 size_t physicalSize = aStream.GetOffset() - start;
299 bool validSize = t1 == ORCAD_PRIM_OLE_IMAGE ? size == physicalSize
300 : size == physicalSize || size + 8 == physicalSize;
301
302 if( !validSize )
303 {
304 THROW_IO_ERRORF( wxS( "primitive type %d stores %zu bytes but consumes %zu" ), t1, size, physicalSize );
305 }
306 }
307 }
308
310 return prim;
311}
312
313} // namespace
314
315
316std::optional<ORCAD_PRIMITIVE> OrcadReadPrimitive( ORCAD_STREAM& aStream )
317{
318 int t1 = aStream.ReadU8();
319
320 if( !isPrimType( t1 ) )
321 THROW_IO_ERRORF( wxS( "bad primitive type %d at 0x%zx" ), t1, aStream.GetOffset() - 1 );
322
323 if( t1 == ORCAD_PRIM_SYMBOL_VECTOR )
324 return readSymbolVector( aStream );
325
326 int t2 = aStream.ReadU8();
327
328 if( t1 != t2 )
329 THROW_IO_ERRORF( wxS( "bad primitive prefix %d/%d at 0x%zx" ), t1, t2, aStream.GetOffset() - 2 );
330
331 return readPrimitiveBody( aStream, t1 );
332}
333
334
335std::optional<ORCAD_SYMBOL_PIN> OrcadReadSymbolPin( ORCAD_STRUCT_READER& aReader )
336{
337 ORCAD_STREAM& stream = aReader.Stream();
338
339 // Single 0x00 instead of prefix chain = skipped pin slot.
340 if( stream.PeekU8() == 0x00 )
341 {
342 stream.Skip( 1 );
343 return std::nullopt;
344 }
345
346 ORCAD_PREFIXES pfx = aReader.ReadPrefixes();
347
349 THROW_IO_ERRORF( wxS( "expected symbol pin, got type %d" ), pfx.typeId );
350
352 pin.name = stream.ReadLzt();
353 pin.startX = stream.ReadI32();
354 pin.startY = stream.ReadI32();
355 pin.hotptX = stream.ReadI32();
356 pin.hotptY = stream.ReadI32();
357 pin.shapeBits = stream.ReadU16();
358 stream.Skip( 2 ); // uninitialized junk
359
360 uint32_t portType = stream.ReadU32();
361 pin.portType = portType <= 7 ? static_cast<ORCAD_PORT_TYPE>( portType ) : ORCAD_PORT_TYPE::PASSIVE;
362
363 if( pfx.end != 0 && pfx.end >= stream.GetOffset() + 6 && stream.PeekU8() == pfx.typeId )
364 {
365 stream.ExpectByte( static_cast<uint8_t>( pfx.typeId ), wxS( "symbol pin type echo" ) );
366 stream.Skip( 3 );
367 pin.displayProps = OrcadReadDisplayPropList( aReader );
368 }
369
370 if( pfx.end != 0 && pfx.end >= stream.GetOffset() )
371 stream.Seek( pfx.end );
372
373 return pin;
374}
375
376
377ORCAD_SYMBOL_DEF OrcadReadSymbolDef( ORCAD_STRUCT_READER& aReader, const ORCAD_PREFIXES& aPrefixes, bool aWithPins )
378{
379 ORCAD_STREAM& stream = aReader.Stream();
380
381 std::vector<size_t> stops = aPrefixes.stops;
382 std::sort( stops.begin(), stops.end() );
383
385 sym.typeId = aPrefixes.typeId;
386 sym.name = stream.ReadLzt();
387 sym.sourceLib = stream.ReadLzt();
388 sym.props = aReader.PropsDict( aPrefixes );
389
390 sym.color = static_cast<int>( stream.ReadU32() );
391
392 uint16_t primCount = stream.ReadU16();
393
394 for( uint16_t i = 0; i < primCount; i++ )
395 {
396 std::optional<ORCAD_PRIMITIVE> prim = OrcadReadPrimitive( stream );
397
398 if( prim )
399 sym.primitives.push_back( std::move( *prim ) );
400
401 // Occasional 8 zero bytes between prims.
402 static const uint8_t zeroTrailer[8] = {};
403
404 if( i + 1 < primCount && stream.PeekMatches( zeroTrailer, sizeof( zeroTrailer ) ) )
405 stream.Skip( 8 );
406 }
407
408 // Bbox = last 8 bytes before next checkpoint (gap 8, or 16 w/ 8 legacy-trailer bytes), as 4x i16.
409 auto nextStopIt = std::upper_bound( stops.begin(), stops.end(), stream.GetOffset() );
410
411 if( nextStopIt != stops.end() )
412 {
413 size_t nextStop = *nextStopIt;
414 size_t gap = nextStop - stream.GetOffset();
415
416 if( gap >= 8 )
417 {
418 stream.Seek( nextStop - 8 );
419
420 int x1 = stream.ReadI16();
421 int y1 = stream.ReadI16();
422 int x2 = stream.ReadI16();
423 int y2 = stream.ReadI16();
424
425 if( x1 <= x2 && y1 <= y2 && x2 - x1 <= 4000 && y2 - y1 <= 4000 )
426 {
427 ORCAD_BBOX box;
428 box.x1 = x1;
429 box.y1 = y1;
430 box.x2 = x2;
431 box.y2 = y2;
432 sym.bbox = box;
433 }
434 }
435 else if( gap > 0 )
436 {
437 stream.Seek( nextStop );
438 }
439 }
440
441 if( aWithPins )
442 {
443 uint16_t pinCount = stream.ReadU16();
444
445 for( uint16_t i = 0; i < pinCount; i++ )
446 {
447 std::optional<ORCAD_SYMBOL_PIN> pin = OrcadReadSymbolPin( aReader );
448
449 if( pin )
450 {
451 pin->position = i;
452 sym.pins.push_back( std::move( *pin ) );
453 }
454 }
455
456 uint16_t propCount = stream.ReadU16();
457
458 for( uint16_t i = 0; i < propCount; i++ )
459 aReader.ReadStructure();
460
461 // The fifth prefix bounds LibraryPart metadata. Reject data outside those stops.
462 if( sym.typeId == ORCAD_ST_LIBRARY_PART && aPrefixes.stops.size() >= 2 )
463 {
464 size_t save = stream.GetOffset();
465 size_t tailStart = aPrefixes.stops[1];
466
467 if( stream.GetOffset() == tailStart && tailStart < aPrefixes.end )
468 {
469 std::string implementationPath = stream.ReadLzt();
470 std::string implementation = stream.ReadLzt();
471 stream.ReadLzt(); // reference prefix
472 stream.ReadLzt(); // part value
473
474 int flags = stream.ReadU16();
475
476 // Landing anywhere but the outer stop means the strings were not the tail.
477 if( stream.GetOffset() == aPrefixes.end )
478 {
479 if( !implementationPath.empty() )
480 sym.props["Implementation Path"] = std::move( implementationPath );
481
482 if( !implementation.empty() )
483 sym.props["Implementation"] = std::move( implementation );
484
485 sym.generalFlags = flags;
486 }
487 }
488
489 stream.Seek( save );
490 }
491 }
492
493 if( aPrefixes.end != 0 && aPrefixes.end > stream.GetOffset() )
494 stream.Seek( aPrefixes.end );
495
496 return sym;
497}
498
499
501{
502 return OrcadReadSymbolDef( aReader, aPrefixes, false );
503}
504
505
507{
508 ORCAD_STREAM& stream = aReader.Stream();
509
510 uint32_t nameIdx = stream.ReadU32();
511 stream.ReadU32(); // source library string index
512 stream.ReadLzt(); // name
513
514 uint32_t dbId = stream.ReadU32();
515
516 stream.ReadI16(); // anchor y
517 stream.ReadI16(); // anchor x
518 stream.ReadI16(); // bbox y2
519 stream.ReadI16(); // bbox x2
520
521 int x1 = stream.ReadI16();
522 int y1 = stream.ReadI16();
523
524 stream.ReadU8(); // color
525 uint8_t orientation = stream.ReadU8();
526 stream.Skip( 2 ); // structId, unknown
527
528 std::vector<ORCAD_DISPLAY_PROP> displayProps = OrcadReadDisplayPropList( aReader );
529
530 // Inline LibraryPart carries block's pin interface.
531 uint8_t flag = stream.ReadU8();
532
534 {
535 THROW_IO_ERRORF( wxS( "drawn instance nested flag %d at 0x%zx" ), static_cast<int>( flag ),
536 stream.GetOffset() - 1 );
537 }
538
539 ORCAD_PREFIXES nestedPfx = aReader.ReadPrefixes( ORCAD_ST_LIBRARY_PART, aPrefixes.end );
540 ORCAD_SYMBOL_DEF nested = OrcadReadSymbolDef( aReader, nestedPfx, true );
541
542 ORCAD_BBOX bbox = nested.bbox.value_or( ORCAD_BBOX() );
543
545 block.dbId = dbId;
546 block.name = aReader.Resolve( nameIdx );
547 block.props = aReader.PropsDict( aPrefixes );
548 block.x1 = x1;
549 block.y1 = y1;
550 bool quarterTurn = ( orientation & 1 ) != 0;
551 block.w = quarterTurn ? bbox.y2 - bbox.y1 : bbox.x2 - bbox.x1;
552 block.h = quarterTurn ? bbox.x2 - bbox.x1 : bbox.y2 - bbox.y1;
553 block.displayProps = std::move( displayProps );
554
555 // Block reference starts at second-to-last prefix checkpoint.
556 std::vector<size_t> stops = aPrefixes.stops;
557 std::sort( stops.begin(), stops.end() );
558
559 if( stops.size() >= 2 && stops[stops.size() - 2] >= stream.GetOffset() )
560 stream.Seek( stops[stops.size() - 2] );
561
562 block.reference = stream.ReadLzt();
563 stream.Skip( 14 );
564
565 // Framed T0x10 structs carry absolute pin page positions, in inline LibraryPart pin order.
566 uint16_t pinCount = stream.ReadU16();
567
568 std::vector<ORCAD_PIN_INST> pinInsts;
569
570 for( uint16_t i = 0; i < pinCount; i++ )
571 {
573
574 if( ORCAD_PIN_INST* pin = std::get_if<ORCAD_PIN_INST>( &result.record ) )
575 pinInsts.push_back( std::move( *pin ) );
576 }
577
578 std::set<std::pair<int, int>> placedPoints;
579 std::set<std::pair<int, int>> definitionPoints;
580
581 for( const ORCAD_PIN_INST& pin : pinInsts )
582 placedPoints.emplace( pin.x, pin.y );
583
584 for( const ORCAD_SYMBOL_PIN& pin : nested.pins )
585 definitionPoints.emplace( pin.hotptX, pin.hotptY );
586
587 bool useDefinitionGeometry = placedPoints.size() <= 1 && definitionPoints.size() > 1;
588
589 for( size_t i = 0; i < pinInsts.size() && i < nested.pins.size(); i++ )
590 {
591 const ORCAD_SYMBOL_PIN& pin = nested.pins[i];
592
593 ORCAD_BLOCK_PIN blockPin;
594 blockPin.name = pin.name;
595 blockPin.portType = pin.portType;
596 blockPin.x = useDefinitionGeometry ? x1 + pin.hotptX - bbox.x1 : pinInsts[i].x;
597 blockPin.y = useDefinitionGeometry ? y1 + pin.hotptY - bbox.y1 : pinInsts[i].y;
598 blockPin.noConnect = pinInsts[i].IsNoConnect();
599 block.pins.push_back( std::move( blockPin ) );
600 }
601
602 if( aPrefixes.end != 0 && aPrefixes.end > stream.GetOffset() )
603 stream.Seek( aPrefixes.end );
604
605 return block;
606}
607
608
610{
611 ORCAD_STREAM& stream = aReader.Stream();
613
614 ORCAD_DEVICE device;
615 device.unitRef = stream.ReadLzt();
616 device.refDes = stream.ReadLzt();
617
618 uint16_t pinCount = stream.ReadU16();
619
620 for( uint16_t i = 0; i < pinCount; i++ )
621 {
622 // FF FF = empty pin slot (no number, ignored).
623 static const uint8_t emptyMarker[2] = { 0xFF, 0xFF };
624
625 if( stream.PeekMatches( emptyMarker, 2 ) )
626 {
627 stream.Skip( 2 );
628 device.pinNumbers.emplace_back();
629 device.pinIgnore.push_back( true );
630 continue;
631 }
632
633 device.pinNumbers.push_back( stream.ReadLzt() );
634
635 uint8_t config = stream.ReadU8();
636 device.pinIgnore.push_back( ( config & 0x80 ) != 0 );
637 }
638
639 if( pfx.end != 0 && pfx.end > stream.GetOffset() )
640 stream.Seek( pfx.end );
641
642 return device;
643}
644
645
647{
648 ORCAD_STREAM& stream = aReader.Stream();
649
650 ORCAD_PACKAGE pkg;
651 pkg.name = stream.ReadLzt();
652 pkg.sourceLib = stream.ReadLzt();
653 pkg.refDes = stream.ReadLzt();
654 stream.ReadLzt(); // unknown
655 pkg.pcbFootprint = stream.ReadLzt();
656
657 uint16_t deviceCount = stream.ReadU16();
658
659 for( uint16_t i = 0; i < deviceCount; i++ )
660 pkg.devices.push_back( OrcadReadDevice( aReader ) );
661
662 pkg.props = aReader.PropsDict( aPrefixes );
663
664 if( aPrefixes.end != 0 && aPrefixes.end > stream.GetOffset() )
665 stream.Seek( aPrefixes.end );
666
667 return pkg;
668}
669
670
671// Store one framed Cache/Packages record. The Cache may hold several stale library versions of
672// one name; the first entry wins as the default and later ones become variants.
673static void storeFramedRecord( ORCAD_STRUCT_READER& aReader, const ORCAD_PREFIXES& aPrefixes,
674 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
675 std::map<std::string, ORCAD_PACKAGE>& aPackages )
676{
677 ORCAD_STREAM::LIMIT_GUARD limit( aReader.Stream(), aPrefixes.end );
678
679 if( isSymbolType( aPrefixes.typeId ) )
680 {
681 ORCAD_SYMBOL_DEF symbol = OrcadReadSymbolDef( aReader, aPrefixes, true );
682 auto existing = aSymbols.find( symbol.name );
683
684 if( existing == aSymbols.end() )
685 {
686 std::string key = symbol.name;
687 aSymbols.emplace( std::move( key ), std::move( symbol ) );
688 }
689 else
690 {
691 existing->second.variants.push_back( std::move( symbol ) );
692 }
693 }
694 else if( aPrefixes.typeId == ORCAD_ST_PACKAGE )
695 {
696 ORCAD_PACKAGE package = OrcadReadPackage( aReader, aPrefixes );
697 auto existing = aPackages.find( package.name );
698
699 if( existing == aPackages.end() )
700 {
701 std::string key = package.name;
702 aPackages.emplace( std::move( key ), std::move( package ) );
703 }
704 else
705 {
706 existing->second.variants.push_back( std::move( package ) );
707 }
708 }
709 else
710 {
711 aReader.SkipStructure( aPrefixes, wxString::Format( wxS( "type %d" ), aPrefixes.typeId ) );
712 }
713}
714
715
716// The type each Cache section is allowed to hold. Section membership is what proves the walk
717// is still aligned, so a section carrying the wrong type is a framing error, not a bad record.
718static bool cacheSectionAcceptsType( int aSection, int aTypeId )
719{
720 switch( aSection )
721 {
722 // Type 24 lives in section 1 and carries a deeper prefix chain. Accepting it here would let
723 // a walk that has lost alignment land on a LibraryPart and be waved through.
724 case 0: return aTypeId != ORCAD_ST_LIBRARY_PART && isSymbolType( aTypeId );
725 case 1: return aTypeId == ORCAD_ST_LIBRARY_PART;
726 case 2: return aTypeId == ORCAD_ST_PART_CELL;
727 default: return aTypeId == ORCAD_ST_PACKAGE;
728 }
729}
730
731
732void OrcadParseCache( const std::vector<char>& aData, const std::vector<std::string>& aStrings,
733 const ORCAD_WARN_FN& aWarn, std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
734 std::map<std::string, ORCAD_PACKAGE>& aPackages )
735{
736 ORCAD_STREAM stream( aData );
737 ORCAD_STRUCT_READER reader( stream, &aStrings, aWarn );
738
739 // Keep decoded symbols if the cache fails. The remaining parts use placeholders.
740 try
741 {
742 if( stream.ReadU16() != 0 )
743 THROW_IO_ERROR( wxS( "OrCAD Cache: invalid marker" ) );
744
745 // Four counted sections: loose symbols, LibraryParts, PartCells, Packages. An empty
746 // cache is the marker plus four zero counts, which is the ten-byte stream in the wild.
747 for( int section = 0; section < 4; ++section )
748 {
749 uint16_t groupCount = stream.ReadU16();
750
751 for( uint16_t group = 0; group < groupCount; ++group )
752 {
753 stream.ReadLzt(); // group name
754 uint16_t variantCount = stream.ReadU16();
755
756 for( uint16_t variant = 0; variant < variantCount; ++variant )
757 {
758 stream.ReadLzt(); // source library
759 stream.ReadU32(); // created
760 stream.ReadU32(); // modified
761
762 int typeId = stream.ReadU8();
763
764 stream.ExpectByte( 0, wxS( "Cache entry pad" ) );
765
766 if( !cacheSectionAcceptsType( section, typeId ) )
767 {
768 THROW_IO_ERRORF( wxS( "OrCAD Cache: section %d cannot hold structure type %d" ), section,
769 typeId );
770 }
771
772 ORCAD_PREFIXES prefixes = reader.ReadPrefixes( typeId );
773
774 if( prefixes.end == 0 || prefixes.end > stream.Size() )
775 THROW_IO_ERROR( wxS( "OrCAD Cache: entry frame runs past the stream" ) );
776
777 try
778 {
779 storeFramedRecord( reader, prefixes, aSymbols, aPackages );
780 }
781 catch( const IO_ERROR& e )
782 {
783 // A body we cannot decode is recoverable: the entry's own frame says
784 // where the next one starts.
785 if( aWarn )
786 {
787 aWarn( wxString::Format( wxS( "cache struct type %d at 0x%zx: %s" ), typeId,
788 prefixes.start, e.Problem() ) );
789 }
790 }
791
792 stream.Seek( prefixes.end );
793 }
794 }
795 }
796
797 if( !stream.AtEnd() )
798 THROW_IO_ERRORF( wxS( "OrCAD Cache: %zu trailing bytes" ), stream.Remaining() );
799 }
800 catch( const IO_ERROR& e )
801 {
802 if( aWarn )
803 {
804 aWarn( wxString::Format( wxS( "OrCAD Cache: stopped at 0x%zx (%s); symbols after this point fall "
805 "back to synthesized placeholders" ),
806 stream.GetOffset(), e.Problem() ) );
807 }
808 }
809}
810
811
812void OrcadParseSymbolStream( const std::vector<char>& aData, const std::vector<std::string>& aStrings,
813 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols )
814{
815 ORCAD_STREAM stream( aData );
816 ORCAD_STRUCT_READER reader( stream, &aStrings );
817 ORCAD_PREFIXES prefixes = reader.ReadPrefixes();
818
819 if( !isSymbolType( prefixes.typeId ) )
820 THROW_IO_ERRORF( wxS( "OrCAD symbol stream: expected a symbol structure, got type %d" ), prefixes.typeId );
821
822 std::map<std::string, ORCAD_PACKAGE> unusedPackages;
823 storeFramedRecord( reader, prefixes, aSymbols, unusedPackages );
824
825 if( !stream.AtEnd() )
826 THROW_IO_ERROR( wxS( "OrCAD symbol stream: trailing bytes" ) );
827}
828
829
830void OrcadParsePackageStream( const std::vector<char>& aData, const std::vector<std::string>& aStrings,
831 std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
832 std::map<std::string, ORCAD_PACKAGE>& aPackages )
833{
834 ORCAD_STREAM stream( aData );
835 ORCAD_STRUCT_READER reader( stream, &aStrings );
836
837 uint16_t partCellCount = stream.ReadU16();
838
839 if( partCellCount > 1000 )
840 THROW_IO_ERROR( wxS( "OrCAD package stream: implausible PartCell count" ) );
841
842 for( uint16_t i = 0; i < partCellCount; ++i )
843 {
845
846 std::map<std::string, std::string> cellProps = reader.PropsDict( cellPfx );
847 stream.ReadLzt(); // PartCell name
848 stream.ReadLzt(); // source library
849
850 uint16_t viewCount = stream.ReadU16();
851
852 if( viewCount > 1000 )
853 THROW_IO_ERROR( wxS( "OrCAD package stream: implausible view count" ) );
854
855 for( uint16_t view = 0; view < viewCount; ++view )
856 stream.ReadLzt();
857
858 if( cellPfx.end == 0 || stream.GetOffset() > cellPfx.end )
859 THROW_IO_ERROR( wxS( "OrCAD package stream: PartCell exceeds its frame" ) );
860
861 stream.Seek( cellPfx.end );
862
863 uint16_t symbolCount = stream.ReadU16();
864
865 if( symbolCount > 1000 )
866 THROW_IO_ERROR( wxS( "OrCAD package stream: implausible LibraryPart count" ) );
867
868 for( uint16_t symbolIndex = 0; symbolIndex < symbolCount; ++symbolIndex )
869 {
871 ORCAD_SYMBOL_DEF symbol = OrcadReadSymbolDef( reader, symbolPfx, true );
872
873 for( const auto& [name, value] : cellProps )
874 symbol.props.try_emplace( name, value );
875
876 auto existing = aSymbols.find( symbol.name );
877
878 if( existing == aSymbols.end() )
879 {
880 std::string key = symbol.name;
881 aSymbols.emplace( std::move( key ), std::move( symbol ) );
882 }
883 else
884 {
885 existing->second.variants.push_back( std::move( symbol ) );
886 }
887 }
888 }
889
890 ORCAD_PREFIXES packagePfx = reader.ReadPrefixes( ORCAD_ST_PACKAGE );
891 ORCAD_PACKAGE package = OrcadReadPackage( reader, packagePfx );
892
893 if( stream.Remaining() != 0 )
894 THROW_IO_ERROR( wxS( "OrCAD package stream: trailing bytes" ) );
895
896 auto existing = aPackages.find( package.name );
897
898 if( existing == aPackages.end() )
899 {
900 std::string key = package.name;
901 aPackages.emplace( std::move( key ), std::move( package ) );
902 }
903 else
904 {
905 existing->second.variants.push_back( std::move( package ) );
906 }
907}
908
909
910void OrcadMergeCacheStreams( std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
911 std::map<std::string, ORCAD_PACKAGE>& aPackages,
912 std::map<std::string, ORCAD_SYMBOL_DEF>&& aExtraSymbols,
913 std::map<std::string, ORCAD_PACKAGE>&& aExtraPackages )
914{
915 for( auto& [name, sym] : aExtraSymbols )
916 {
917 auto it = aSymbols.find( name );
918
919 if( it == aSymbols.end() )
920 {
921 aSymbols.emplace( name, std::move( sym ) );
922 }
923 else
924 {
925 // Main cache def stays default; extra stream's default and variants appended in stream order.
926 std::vector<ORCAD_SYMBOL_DEF> extraVariants = std::move( sym.variants );
927 sym.variants.clear();
928
929 if( it->second.generalFlags < 0 && sym.generalFlags >= 0 )
930 it->second.generalFlags = sym.generalFlags;
931
932 it->second.variants.push_back( std::move( sym ) );
933
934 for( ORCAD_SYMBOL_DEF& variant : extraVariants )
935 it->second.variants.push_back( std::move( variant ) );
936 }
937 }
938
939 for( auto& [name, pkg] : aExtraPackages )
940 {
941 auto it = aPackages.find( name );
942
943 if( it == aPackages.end() )
944 {
945 aPackages.emplace( name, std::move( pkg ) );
946 }
947 else
948 {
949 std::vector<ORCAD_PACKAGE> variants = std::move( pkg.variants );
950 pkg.variants.clear();
951 it->second.variants.push_back( std::move( pkg ) );
952
953 for( ORCAD_PACKAGE& variant : variants )
954 it->second.variants.push_back( std::move( variant ) );
955 }
956 }
957}
958
959
960void OrcadMergeSymbolGeneralProperties( std::map<std::string, ORCAD_SYMBOL_DEF>& aSymbols,
961 const std::map<std::string, ORCAD_SYMBOL_DEF>& aMetadataSymbols )
962{
963 for( const auto& [name, metadata] : aMetadataSymbols )
964 {
965 auto symbol = aSymbols.find( name );
966
967 if( symbol != aSymbols.end() && symbol->second.generalFlags < 0 && metadata.generalFlags >= 0 )
968 symbol->second.generalFlags = metadata.generalFlags;
969 }
970}
const char * name
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString Problem() const
what was the problem?
Limit reads to one record so a malformed body cannot consume the next record.
Bound shared parser recursion; entering a level above MAX_NESTING throws IO_ERROR.
The caller owns the buffer.
size_t Remaining() const
Bytes left; 0 when the cursor is at or past the end.
int PeekU8(size_t aAhead=0) const
– non-throwing lookahead ---------------------------------------------------—
void SkipOptionalPreambleBlock()
Legacy primitives have no trailing preamble.
uint8_t ReadU8()
– scalars (little-endian, bounds-checked, throw IO_ERROR on overrun) ----—
static constexpr size_t npos
Generic invalid offset sentinel.
size_t GetOffset() const
uint16_t ReadU16()
std::vector< uint8_t > ReadBytes(size_t aCount)
– buffers / cursor ---------------------------------------------------------—
void Skip(size_t aCount)
Advance the cursor; throws IO_ERROR when aCount exceeds the remaining bytes.
bool AtEnd() const
std::string ReadLzt()
– strings ----------------------------------------------------------------—
size_t Size() const
const uint8_t * Data() const
Raw buffer access for bounded lexical lookahead and payload extraction.
bool PeekMatches(const uint8_t *aBytes, size_t aCount, size_t aAhead=0) const
Returns false if fewer than aCount bytes remain; does not advance the cursor.
void Seek(size_t aOffset)
Set the absolute cursor position; throws IO_ERROR when aOffset exceeds Size().
int32_t ReadI32()
void ExpectByte(uint8_t aValue, const wxString &aWhat)
Consume one byte and require the given value.
uint32_t ReadU32()
void Expect(const uint8_t *aBytes, size_t aCount, const wxString &aWhat)
– validated reads ------------------------------------------------------------—
int16_t ReadI16()
ReadStructure can recover from a body error when the prefix supplies a valid end offset.
std::map< std::string, std::string > PropsDict(const ORCAD_PREFIXES &aPrefixes) const
Resolve the short-prefix (nameIdx, valueIdx) pairs; empty names are dropped.
void SkipStructure(const ORCAD_PREFIXES &aPrefixes, const wxString &aWhat)
Throws IO_ERROR if the end is unknown or behind the cursor.
ORCAD_PREFIXES ReadPrefixes(int aExpectedType=-1, size_t aEnclosingEnd=ORCAD_STREAM::npos, size_t aLongPrefixCount=ORCAD_STREAM::npos)
aLongPrefixCount overrides the type depth; aEnclosingEnd bounds all stops.
ORCAD_STREAM & Stream()
std::string Resolve(uint32_t aIndex) const
String-table lookup; returns "" for out-of-range indices.
ORCAD_READ_RESULT ReadStructure()
Skip unknown bodies.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
void OrcadMergeSymbolGeneralProperties(std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, const std::map< std::string, ORCAD_SYMBOL_DEF > &aMetadataSymbols)
void OrcadMergeCacheStreams(std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages, std::map< std::string, ORCAD_SYMBOL_DEF > &&aExtraSymbols, std::map< std::string, ORCAD_PACKAGE > &&aExtraPackages)
Existing symbols gain variants.
ORCAD_DEVICE OrcadReadDevice(ORCAD_STRUCT_READER &aReader)
std::optional< ORCAD_SYMBOL_PIN > OrcadReadSymbolPin(ORCAD_STRUCT_READER &aReader)
A zero byte marks an empty pin slot and returns nullopt.
static bool cacheSectionAcceptsType(int aSection, int aTypeId)
void OrcadParseSymbolStream(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols)
Throws IO_ERROR for invalid framing or trailing bytes.
ORCAD_PACKAGE OrcadReadPackage(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes)
ORCAD_DRAWN_INSTANCE OrcadReadDrawnInstance(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes)
static void storeFramedRecord(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
std::optional< ORCAD_PRIMITIVE > OrcadReadPrimitive(ORCAD_STREAM &aStream)
Consumes one primitive and its optional preamble.
void OrcadParseCache(const std::vector< char > &aData, const std::vector< std::string > &aStrings, const ORCAD_WARN_FN &aWarn, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
The first entry is the default; later entries become variants.
void OrcadParsePackageStream(const std::vector< char > &aData, const std::vector< std::string > &aStrings, std::map< std::string, ORCAD_SYMBOL_DEF > &aSymbols, std::map< std::string, ORCAD_PACKAGE > &aPackages)
Package streams contain counted PartCells and LibraryParts, followed by one Package.
ORCAD_SYMBOL_DEF OrcadReadSymbolDef(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes, bool aWithPins)
Set aWithPins to read the trailing pin and property lists.
ORCAD_SYMBOL_DEF OrcadReadSthInPages0(ORCAD_STRUCT_READER &aReader, const ORCAD_PREFIXES &aPrefixes)
std::function< void(const wxString &aMsg)> ORCAD_WARN_FN
Coordinates use DBU with Y down.
@ ORCAD_PRIM_ARC
@ ORCAD_PRIM_BEZIER
@ ORCAD_PRIM_POLYLINE
@ ORCAD_PRIM_COMMENT_TEXT
@ ORCAD_PRIM_LINE
@ ORCAD_PRIM_ELLIPSE
@ ORCAD_PRIM_BITMAP
plain DIB (BITMAPINFOHEADER + pixels)
@ ORCAD_PRIM_OLE_IMAGE
OLE compound document embed.
@ ORCAD_PRIM_POLYGON
@ ORCAD_PRIM_RECT
@ ORCAD_PRIM_SYMBOL_VECTOR
nested prefix-framed vector graphic
@ ORCAD_ST_PORT_SYMBOL
@ ORCAD_ST_TITLEBLOCK_SYMBOL
@ ORCAD_ST_SYMBOL_PIN_SCALAR
@ ORCAD_ST_PART_CELL
@ ORCAD_ST_SYMBOL_VECTOR
@ ORCAD_ST_LIBRARY_PART
@ ORCAD_ST_DEVICE
@ ORCAD_ST_GLOBAL_SYMBOL
power symbol definition
@ ORCAD_ST_BOOKMARK_SYMBOL
@ ORCAD_ST_PACKAGE
@ ORCAD_ST_SYMBOL_PIN_BUS
@ ORCAD_ST_ERC_SYMBOL
@ ORCAD_ST_OFFPAGE_SYMBOL
std::vector< ORCAD_DISPLAY_PROP > OrcadReadDisplayPropList(ORCAD_STRUCT_READER &aReader)
Keep only display properties that ReadStructure decodes.
Axis-aligned box in OrCAD DBU; corner order as stored (not normalized).
One interface pin of a hierarchical block, at its absolute page position.
ORCAD_PORT_TYPE portType
std::string name
Device unit names omit the view suffix.
std::vector< std::string > pinNumbers
std::string refDes
std::vector< bool > pinIgnore
std::string unitRef
The inline LibraryPart defines the block interface; placed pin records supply absolute positions.
std::string name
intrinsic Name property used for flat-net scoping
int x1
block rectangle top-left, page DBU
std::vector< ORCAD_BLOCK_PIN > pins
std::vector< ORCAD_DISPLAY_PROP > displayProps
std::map< std::string, std::string > props
std::string refDes
std::map< std::string, std::string > props
Part-level properties shared by every placement (Description, Tolerance, ...).
std::string sourceLib
std::vector< ORCAD_DEVICE > devices
std::string pcbFootprint
std::string name
Pin positions are absolute page connection points.
Integer point in OrCAD DBU.
Prefix lengths supply structure bounds.
std::vector< size_t > stops
< (from the outermost long prefix); < 0 when unknown
int typeId
ORCAD_ST value (u8 in the stream)
size_t end
offset right after the whole structure
size_t start
stream offset where the chain began
Primitive byte lengths can include or exclude the eight-byte size envelope.
int fillStyle
0 solid, 1 none, 2 hatch pattern
std::string text
kind == TEXT
std::vector< ORCAD_POINT > points
polygon/polyline/bezier vertices
std::optional< ORCAD_POINT > textBoundsStart
ORCAD_PRIM_KIND kind
std::vector< uint8_t > data
kind == IMAGE: raw embedded payload
int lineStyle
0 solid, 1 dash, 2 dot, 3 dash-dot, 4 dash-dot-dot, 5 default
std::optional< ORCAD_POINT > start
arc start point
std::optional< ORCAD_POINT > end
arc end point
int lineWidth
Capture width enum: 0 thin, 1 medium, 2 wide, 3 default.
The bounding box occupies the final eight bytes before the next prefix stop.
std::string name
cache name, e.g. "C.Normal"
std::vector< ORCAD_SYMBOL_PIN > pins
std::vector< ORCAD_PRIMITIVE > primitives
std::map< std::string, std::string > props
int typeId
ORCAD_ST value.
std::string sourceLib
std::optional< ORCAD_BBOX > bbox
symbol-space body box
int generalFlags
LibraryPart GeneralProperties flags (-1 = absent); bit0 = pin names visible, bit1 = pin text rotates ...
Pin coordinates use symbol space with Y down.
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.