KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easypc_file.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 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <cstring>
24#include <set>
25
26#include <boost/endian/conversion.hpp>
27#include <compoundfilereader.h>
28
29#include <ki_exception.h>
30#include <wx/ffile.h>
31#include <wx/translation.h>
32
33
34namespace EASYPC
35{
36
37namespace
38{
39
40 constexpr uint32_t NOSTREAM = 0xFFFFFFFF;
41
42 const char* const FILE_TYPE_TABLE[] = { "PCB Design", "Schematic Design", "PCB Symbol Library",
43 "Schematic Symbol Library", "Component Library" };
44
45 const char16_t ESCAPED_CHARS[] = { u'#', u'!', u'\\', u'/', u':', u'*', u'?', u'"', u'<', u'>', u'|' };
46
47
48 [[noreturn]] void corrupt( const wxString& aWhat )
49 {
50 THROW_IO_ERROR( wxString::Format( _( "Easy-PC compound file is malformed: %s." ), aWhat ) );
51 }
52
53
55 template <typename FN>
56 auto guarded( FN aFn ) -> decltype( aFn() )
57 {
58 try
59 {
60 return aFn();
61 }
62 catch( const IO_ERROR& )
63 {
64 throw;
65 }
66 catch( const std::exception& e )
67 {
68 corrupt( wxString::FromUTF8( e.what() ) );
69 }
70 }
71
72} // namespace
73
74
75const char* FileKindString( FILE_KIND aKind )
76{
77 return FILE_TYPE_TABLE[static_cast<size_t>( aKind )];
78}
79
80
81wxString UnescapeItemName( const std::u16string& aName )
82{
83 wxString result;
84
85 for( char16_t ch : aName )
86 result.Append( static_cast<wxUniChar>( ch >= 0x06 && ch <= 0x10 ? ESCAPED_CHARS[ch - 0x06] : ch ) );
87
88 return result;
89}
90
91
92std::vector<uint8_t> ReadWholeFile( const wxString& aPath )
93{
94 wxFFile file( aPath, wxS( "rb" ) );
95 wxFileOffset len = file.IsOpened() ? file.Length() : -1;
96
97 if( len < 0 )
98 THROW_IO_ERROR( wxString::Format( _( "Cannot read file '%s'." ), aPath ) );
99
100 std::vector<uint8_t> data( static_cast<size_t>( len ) );
101
102 if( len > 0 && file.Read( data.data(), data.size() ) != data.size() )
103 THROW_IO_ERROR( wxString::Format( _( "Cannot read file '%s'." ), aPath ) );
104
105 return data;
106}
107
108
109COMPOUND_FILE::COMPOUND_FILE( const wxString& aPath ) :
110 m_image( ReadWholeFile( aPath ) )
111{
112 static const uint8_t MAGIC[8] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
113
114 if( m_image.size() < 512 || std::memcmp( m_image.data(), MAGIC, 8 ) != 0 )
115 THROW_IO_ERROR( _( "Not an Easy-PC / DesignSpark file: no compound file signature." ) );
116
117 m_reader = guarded(
118 [&]()
119 {
120 return std::make_unique<CFB::CompoundFileReader>( m_image.data(), m_image.size() );
121 } );
122
123 auto raw = [&]( uint32_t aId )
124 {
125 const CFB::COMPOUND_FILE_ENTRY* entry = guarded(
126 [&]()
127 {
128 return m_reader->GetEntry( aId );
129 } );
130
131 if( !entry || entry->nameLen < 2 || entry->nameLen > 64 || entry->nameLen % 2 )
132 corrupt( wxString::Format( wxS( "directory entry %u" ), aId ) );
133
134 return entry;
135 };
136
137 std::set<uint32_t> visited;
138
139 auto add = [&]( uint32_t aId )
140 {
141 if( !visited.insert( aId ).second )
142 corrupt( wxString::Format( wxS( "directory entry %u is reached twice" ), aId ) );
143
144 const CFB::COMPOUND_FILE_ENTRY* entry = raw( aId );
145 m_entries[aId] = SOURCE_FB_ENTRY{ aId, std::u16string( entry->name, entry->name + entry->nameLen / 2 - 1 ),
146 entry->type };
147 };
148
149 // Each storage's red-black tree is walked in order, iteratively since the ids come from the file
150 std::vector<uint32_t> storages = { 0 };
151 add( 0 );
152
153 while( !storages.empty() )
154 {
155 uint32_t storage = storages.back();
156 storages.pop_back();
157
158 std::vector<uint32_t>& members = m_children[storage];
159 std::vector<uint32_t> stack;
160 uint32_t id = raw( storage )->childID;
161
162 while( id != NOSTREAM || !stack.empty() )
163 {
164 for( ; id != NOSTREAM; id = raw( id )->leftSiblingID )
165 {
166 stack.push_back( id );
167 add( id );
168 }
169
170 uint32_t node = stack.back();
171 stack.pop_back();
172 members.push_back( node );
173
174 if( m_entries.at( node ).IsStorage() )
175 storages.push_back( node );
176
177 id = raw( node )->rightSiblingID;
178 }
179 }
180}
181
182
184
185
186std::vector<const SOURCE_FB_ENTRY*> COMPOUND_FILE::Children( const SOURCE_FB_ENTRY& aStorage ) const
187{
188 std::vector<const SOURCE_FB_ENTRY*> out;
189
190 if( auto it = m_children.find( aStorage.Id ); it != m_children.end() )
191 {
192 for( uint32_t id : it->second )
193 out.push_back( &m_entries.at( id ) );
194 }
195
196 return out;
197}
198
199
200const SOURCE_FB_ENTRY* COMPOUND_FILE::FindChild( const SOURCE_FB_ENTRY& aStorage, const std::u16string& aName ) const
201{
202 for( const SOURCE_FB_ENTRY* entry : Children( aStorage ) )
203 {
204 if( entry->Name == aName )
205 return entry;
206 }
207
208 return nullptr;
209}
210
211
212std::vector<uint8_t> COMPOUND_FILE::ReadStream( const SOURCE_FB_ENTRY& aStream ) const
213{
214 if( !aStream.IsStream() )
215 corrupt( wxString::Format( wxS( "directory entry %u is not a stream" ), aStream.Id ) );
216
217 const CFB::COMPOUND_FILE_ENTRY* entry = m_reader->GetEntry( aStream.Id );
218 uint64_t size = m_reader->GetStreamSize( entry );
219
220 if( size > m_image.size() )
221 corrupt( wxString::Format( wxS( "stream %u is larger than the file" ), aStream.Id ) );
222
223 std::vector<uint8_t> out( static_cast<size_t>( size ) );
224
225 if( !out.empty() )
226 guarded(
227 [&]()
228 {
229 m_reader->ReadFile( entry, 0, reinterpret_cast<char*>( out.data() ), out.size() );
230 } );
231
232 return out;
233}
234
235
237{
238 const SOURCE_FB_ENTRY* entry = FindChild( Root(), u"\x05"
239 u"FileType" );
240
241 if( !entry )
242 THROW_IO_ERROR( _( "Easy-PC / DesignSpark file has no FileType stream." ) );
243
244 // The type ends at a newline; libraries from about 2001 have none
245 std::vector<uint8_t> data = ReadStream( *entry );
246 std::string text( data.begin(), std::find( data.begin(), data.end(), '\n' ) );
247
248 for( size_t i = 0; i < std::size( FILE_TYPE_TABLE ); ++i )
249 {
250 if( text == FILE_TYPE_TABLE[i] )
251 return static_cast<FILE_KIND>( i );
252 }
253
254 THROW_IO_ERROR( wxString::Format( _( "Easy-PC / DesignSpark file type '%s' is not supported." ),
255 wxString::From8BitData( text.c_str() ) ) );
256}
257
258
260{
261 const SOURCE_FB_ENTRY* entry = FindChild( Root(), u"\x05"
262 u"LibFormat" );
263 std::vector<uint8_t> data = entry ? ReadStream( *entry ) : std::vector<uint8_t>();
264 int32_t value = 0;
265
266 // A first int, then a second read into the same variable; a 5 to 7 byte stream overwrites only low bytes
267 if( data.size() >= 4 )
268 {
269 uint8_t bytes[4];
270 std::memcpy( bytes, data.data(), 4 );
271 std::memcpy( bytes, data.data() + 4, std::min<size_t>( data.size() - 4, 4 ) );
272 value = boost::endian::load_little_s32( bytes );
273 }
274
275 return value == 0 ? 1 : value;
276}
277
278} // namespace EASYPC
std::map< uint32_t, std::vector< uint32_t > > m_children
storage id to member ids in tree order
std::unique_ptr< CFB::CompoundFileReader > m_reader
std::vector< uint8_t > ReadStream(const SOURCE_FB_ENTRY &aStream) const
const SOURCE_FB_ENTRY & Root() const
Definition easypc_file.h:85
int32_t LibraryProduct() const
The LibFormat product, 1 when missing, short or 0.
FILE_KIND FileKind() const
std::vector< const SOURCE_FB_ENTRY * > Children(const SOURCE_FB_ENTRY &aStorage) const
The members of a storage in directory-tree order.
std::vector< uint8_t > m_image
Definition easypc_file.h:99
COMPOUND_FILE(const wxString &aPath)
std::map< uint32_t, SOURCE_FB_ENTRY > m_entries
const SOURCE_FB_ENTRY * FindChild(const SOURCE_FB_ENTRY &aStorage, const std::u16string &aName) const
The OLE compound file container, over thirdparty/compoundfilereader.
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
std::vector< uint8_t > ReadWholeFile(const wxString &aPath)
The whole content of a file; throws IO_ERROR when it cannot be read.
const char * FileKindString(FILE_KIND aKind)
FILE_KIND
The FileType strings the importer reads.
Definition easypc_file.h:44
wxString UnescapeItemName(const std::u16string &aName)
Undo the item name escaping, which stores # ! \ / : * ? " < > | as 0x06..0x10.
Definition easypc_file.h:65
uint32_t Id
Definition easypc_file.h:66
bool IsStream() const
Definition easypc_file.h:71
wxString result
Test unit parsing edge cases and error handling.