KiCad PCB EDA Suite
Loading...
Searching...
No Matches
altium_binary_parser.h
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 (C) 2019-2020 Thomas Pointhuber <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include "altium_props_utils.h"
24
25#include <charconv>
26#include <map>
27#include <memory>
28#include <numeric>
29#include <string>
30#include <stdexcept>
31#include <vector>
32
33#include <wx/mstream.h>
34#include <wx/zstream.h>
35#include <math/vector2d.h>
36#include <math/vector3.h>
37
38namespace CFB
39{
40 class CompoundFileReader;
41 struct COMPOUND_FILE_ENTRY;
42} // namespace CFB
43
49std::string FormatPath( const std::vector<std::string>& aVectorPath );
50
51
53{
54public:
55 const CFB::COMPOUND_FILE_ENTRY* m_symbol;
56 const CFB::COMPOUND_FILE_ENTRY* m_pinsFrac;
57 const CFB::COMPOUND_FILE_ENTRY* m_pinsWideText;
58 const CFB::COMPOUND_FILE_ENTRY* m_pinsTextData;
59 const CFB::COMPOUND_FILE_ENTRY* m_pinsSymbolLineWidth;
60};
61
62
64{
66
67public:
70
76 ALTIUM_COMPOUND_FILE( const wxString& aFilePath );
77
85 ALTIUM_COMPOUND_FILE( const void* aBuffer, size_t aLen );
86
87 ALTIUM_COMPOUND_FILE( const ALTIUM_COMPOUND_FILE& temp_obj ) = delete;
90
98 void InitFromBuffer( const void* aBuffer, size_t aLen );
99
100 const CFB::CompoundFileReader& GetCompoundFileReader() const { return *m_reader; }
101
102 bool DecodeIntLibStream( const CFB::COMPOUND_FILE_ENTRY& cfe, ALTIUM_COMPOUND_FILE* aOutput );
103
104 const CFB::COMPOUND_FILE_ENTRY* FindStream( const std::vector<std::string>& aStreamPath ) const;
105
106 const CFB::COMPOUND_FILE_ENTRY* FindStream( const CFB::COMPOUND_FILE_ENTRY* aStart,
107 const std::vector<std::string>& aStreamPath ) const;
108
109 const CFB::COMPOUND_FILE_ENTRY* FindStreamSingleLevel( const CFB::COMPOUND_FILE_ENTRY* aEntry,
110 const std::string aName,
111 const bool aIsStream ) const;
112
113 std::map<wxString, const CFB::COMPOUND_FILE_ENTRY*> EnumDir( const std::wstring& aDir ) const;
114
115 std::map<wxString, ALTIUM_SYMBOL_DATA> GetLibSymbols( const CFB::COMPOUND_FILE_ENTRY* aStart ) const;
116
117private:
118
119 std::unique_ptr<CFB::CompoundFileReader> m_reader;
120 std::vector<char> m_buffer;
121};
122
123
125{
126public:
127 ALTIUM_BINARY_PARSER( const ALTIUM_COMPOUND_FILE& aFile, const CFB::COMPOUND_FILE_ENTRY* aEntry );
128 ALTIUM_BINARY_PARSER( std::unique_ptr<char[]>& aContent, size_t aSize );
130
131 template <typename Type>
132 Type Read()
133 {
134 const size_t remainingBytes = GetRemainingBytes();
135
136 if( remainingBytes >= sizeof( Type ) )
137 {
138 Type val = *(Type*) ( m_pos );
139 m_pos += sizeof( Type );
140 return val;
141 }
142 else
143 {
144 m_pos += remainingBytes; // Ensure remaining bytes are zero
145 m_error = true;
146 return 0;
147 }
148 }
149
150 template <typename Type>
151 Type Peek()
152 {
153 char* const oldPos = m_pos;
154 const bool oldError = m_error;
155 Type result = Read<Type>();
156 m_pos = oldPos;
157 m_error = oldError;
158 return result;
159 }
160
161 wxScopedCharBuffer ReadCharBuffer()
162 {
163 uint8_t len = Read<uint8_t>();
164
165 if( GetRemainingBytes() >= len )
166 {
167 char* buf = static_cast<char*>( malloc( len ) );
168 memcpy( buf, m_pos, len );
169 m_pos += len;
170
171 return wxScopedCharBuffer::CreateOwned( buf, len );
172 }
173 else
174 {
175 m_error = true;
176 return wxScopedCharBuffer();
177 }
178 }
179
180 wxString ReadWxString()
181 {
182 // TODO: Identify where the actual code page is stored. For now, this default code page
183 // has limited impact, because recent Altium files come with a UTF16 string table
184 return wxString( ReadCharBuffer(), wxConvISO8859_1 );
185 }
186
187 std::map<uint32_t, wxString> ReadWideStringTable()
188 {
189 std::map<uint32_t, wxString> table;
190 size_t remaining = GetRemainingBytes();
191
192 while( remaining >= 8 )
193 {
194 uint32_t index = Read<uint32_t>();
195 uint32_t length = Read<uint32_t>();
196 wxString str;
197 remaining -= 8;
198
199 if( length <= 2 )
200 {
201 length = 0; // for empty strings, not even the null bytes are present
202 }
203 else
204 {
205 if( length > remaining )
206 break;
207
208 str = wxString( m_pos, wxMBConvUTF16LE(), length - 2 );
209 }
210
211 table.emplace( index, str );
212 m_pos += length;
213 remaining -= length;
214 }
215
216 return table;
217 }
218
219 std::vector<char> ReadVector( size_t aSize )
220 {
221 if( aSize > GetRemainingBytes() )
222 {
223 m_error = true;
224 return {};
225 }
226 else
227 {
228 std::vector<char> data( m_pos, m_pos + aSize );
229 m_pos += aSize;
230 return data;
231 }
232 }
233
234 int ReadBytes( char* aOut, size_t aSize )
235 {
236 if( aSize > GetRemainingBytes() )
237 {
238 m_error = true;
239 return 0;
240 }
241 else
242 {
243 memcpy( aOut, m_pos, aSize );
244 m_pos += aSize;
245 return aSize;
246 }
247 }
248
253
255 {
256 return ReadKicadUnit();
257 }
258
260 {
261 return -ReadKicadUnit();
262 }
263
265 {
266 int32_t x = ReadKicadUnitX();
267 int32_t y = ReadKicadUnitY();
268 return { x, y };
269 }
270
272 {
273 int32_t x = ReadKicadUnit();
274 int32_t y = ReadKicadUnit();
275 return { x, y };
276 }
277
279 {
280 uint32_t length = Read<uint32_t>();
281 m_subrecord_end = m_pos + length;
282 return length;
283 }
284
285 std::map<wxString, wxString> ReadProperties(
286 std::function<std::map<wxString, wxString>( const std::string& )> handleBinaryData =
287 []( const std::string& )
288 {
289 return std::map<wxString, wxString>();
290 } );
291
292 void Skip( size_t aLength )
293 {
294 if( GetRemainingBytes() < aLength )
295 {
296 m_error = true;
297 return;
298 }
299
300 m_pos += aLength;
301 }
302
304 {
305 if( m_subrecord_end == nullptr || m_subrecord_end < m_pos )
306 {
307 m_error = true;
308 return;
309 }
310
312 };
313
314 size_t GetRemainingBytes() const
315 {
316 if( m_pos == nullptr || m_content.get() + m_size < m_pos )
317 return 0;
318
319 return m_size - ( m_pos - m_content.get() );
320 }
321
323 {
324 if( m_pos == nullptr || m_subrecord_end == nullptr || m_subrecord_end < m_pos )
325 return 0;
326
327 return m_subrecord_end - m_pos;
328 };
329
330 bool HasParsingError() { return m_error; }
331
332private:
333 std::unique_ptr<char[]> m_content;
334 size_t m_size;
335
336 char* m_pos; // current read pointer
337 char* m_subrecord_end; // pointer which points to next subrecord start
339};
340
341
343{
344public:
345 ALTIUM_BINARY_READER( const std::string& binaryData ) :
346 m_data( binaryData ),
347 m_position( 0 )
348 {}
349
350 int32_t ReadInt32()
351 {
352 if( m_position + sizeof( int32_t ) > m_data.size() )
353 throw std::out_of_range( "ALTIUM_BINARY_READER: out of range" );
354
355 int32_t value = *reinterpret_cast<const int32_t*>( &m_data[m_position] );
356 m_position += sizeof( int32_t );
357 return value;
358 }
359
360 int16_t ReadInt16()
361 {
362 if( m_position + sizeof( int16_t ) > m_data.size() )
363 throw std::out_of_range( "ALTIUM_BINARY_READER: out of range" );
364
365 int16_t value = *reinterpret_cast<const int16_t*>( &m_data[m_position] );
366 m_position += sizeof( int16_t );
367 return value;
368 }
369
370 uint8_t ReadByte()
371 {
372 if( m_position + sizeof( uint8_t ) > m_data.size() )
373 throw std::out_of_range( "ALTIUM_BINARY_READER: out of range" );
374
375 uint8_t value = *reinterpret_cast<const uint8_t*>( &m_data[m_position] );
376 m_position += sizeof( uint8_t );
377 return value;
378 }
379
381 {
382 uint8_t length = ReadByte();
383
384 if( m_position + length > m_data.size() )
385 throw std::out_of_range( "ALTIUM_BINARY_READER: out of range" );
386
387 std::string pascalString( &m_data[m_position], &m_data[m_position + length] );
388 m_position += length;
389 return pascalString;
390 }
391
393 {
394 uint32_t length = ReadInt32();
395
396 if( m_position + length > m_data.size() )
397 throw std::out_of_range( "ALTIUM_BINARY_READER: out of range" );
398
399 std::string pascalString( &m_data[m_position], &m_data[m_position + length] );
400 m_position += length;
401 return pascalString;
402 }
403
404private:
405 const std::string& m_data;
407};
408
410{
411public:
412 ALTIUM_COMPRESSED_READER( const std::string& aData ) :
413 ALTIUM_BINARY_READER( aData )
414 {}
415
416 std::pair<int, std::string*> ReadCompressedString()
417 {
418 std::string* result;
419 int id = -1;
420
421 uint8_t byte = ReadByte();
422
423 if( byte != 0xD0 )
424 throw std::runtime_error( "ALTIUM_COMPRESSED_READER: invalid compressed string" );
425
426 std::string str = ReadShortPascalString();
427 std::from_chars( str.data(), str.data() + str.size(), id );
428
429 std::string data = ReadFullPascalString();
430 result = decompressData( data );
431
432 return std::make_pair( id, result );
433 }
434
435private:
436 std::string decompressedData;
437
438 std::string* decompressData( std::string& aData )
439 {
440 // Create a memory input stream with the buffer
441 wxMemoryInputStream memStream( (void*) aData.data(), aData.length() );
442
443 // Create a zlib input stream with the memory input stream
444 wxZlibInputStream zStream( memStream );
445
446 // Read decompressed data from the zlib input stream
447 while( !zStream.Eof() )
448 {
449 char buffer[1024];
450 zStream.Read( buffer, sizeof( buffer ) );
451 size_t bytesRead = zStream.LastRead();
452 decompressedData.append( buffer, bytesRead );
453 }
454
455 return &decompressedData;
456 }
457};
int index
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
~ALTIUM_BINARY_PARSER()=default
void Skip(size_t aLength)
std::vector< char > ReadVector(size_t aSize)
std::unique_ptr< char[]> m_content
wxScopedCharBuffer ReadCharBuffer()
std::map< uint32_t, wxString > ReadWideStringTable()
ALTIUM_BINARY_PARSER(const ALTIUM_COMPOUND_FILE &aFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
size_t GetRemainingSubrecordBytes() const
std::map< wxString, wxString > ReadProperties(std::function< std::map< wxString, wxString >(const std::string &)> handleBinaryData=[](const std::string &) { return std::map< wxString, wxString >();})
int ReadBytes(char *aOut, size_t aSize)
std::string ReadShortPascalString()
const std::string & m_data
ALTIUM_BINARY_READER(const std::string &binaryData)
ALTIUM_COMPOUND_FILE()
Create an uninitialized file for two-step initialization (e.g. with InitFromBuffer)
const CFB::CompoundFileReader & GetCompoundFileReader() const
const CFB::COMPOUND_FILE_ENTRY * FindStreamSingleLevel(const CFB::COMPOUND_FILE_ENTRY *aEntry, const std::string aName, const bool aIsStream) const
~ALTIUM_COMPOUND_FILE()=default
std::map< wxString, const CFB::COMPOUND_FILE_ENTRY * > EnumDir(const std::wstring &aDir) const
void InitFromBuffer(const void *aBuffer, size_t aLen)
Load a CFB file from memory; may throw an IO_ERROR.
ALTIUM_COMPOUND_FILE & operator=(const ALTIUM_COMPOUND_FILE &temp_obj)=delete
std::vector< char > m_buffer
std::unique_ptr< CFB::CompoundFileReader > m_reader
std::map< wxString, ALTIUM_SYMBOL_DATA > GetLibSymbols(const CFB::COMPOUND_FILE_ENTRY *aStart) const
bool DecodeIntLibStream(const CFB::COMPOUND_FILE_ENTRY &cfe, ALTIUM_COMPOUND_FILE *aOutput)
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
friend class ALTIUM_PCB_COMPOUND_FILE
ALTIUM_COMPOUND_FILE(const ALTIUM_COMPOUND_FILE &temp_obj)=delete
std::string * decompressData(std::string &aData)
ALTIUM_COMPRESSED_READER(const std::string &aData)
std::pair< int, std::string * > ReadCompressedString()
static int32_t ConvertToKicadUnit(const double aValue, bool *aOutOfRange=nullptr)
Convert a value in Altium's internal unit (0.1 uinch) to KiCad IU, clamped to the representable range...
const CFB::COMPOUND_FILE_ENTRY * m_pinsSymbolLineWidth
const CFB::COMPOUND_FILE_ENTRY * m_symbol
const CFB::COMPOUND_FILE_ENTRY * m_pinsFrac
const CFB::COMPOUND_FILE_ENTRY * m_pinsWideText
const CFB::COMPOUND_FILE_ENTRY * m_pinsTextData
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683