25#include <boost/endian/conversion.hpp>
29#include <wx/strconv.h>
30#include <wx/translation.h>
39 constexpr uint16_t NEW_CLASS_TAG = 0xFFFF;
40 constexpr uint16_t CLASS_TAG = 0x8000;
41 constexpr uint32_t BIG_CLASS_TAG = 0x80000000;
42 constexpr uint16_t BIG_OBJECT_TAG = 0x7FFF;
45 constexpr uint16_t MAX_CLASS_NAME = 64;
67 aResult =
co_await aAr.
Object();
73 co_await aAr.
Embedded( aObject, aClass );
105 THROW_IO_ERROR( wxString::Format(
_(
"Easy-PC archive ends at offset %zu, %zu bytes short of a %zu byte "
136 return boost::endian::load_little_u16(
m_data +
m_pos - 2 );
144 return boost::endian::load_little_u32(
m_data +
m_pos - 4 );
152 return boost::endian::load_little_u64(
m_data +
m_pos - 8 );
158 uint64_t bits =
U64();
160 std::memcpy( &v, &bits,
sizeof( v ) );
168 return n != 0xFFFF ? n :
U32();
203 size_t bytes = unicode ?
static_cast<size_t>( len ) * 2 : len;
211 return wxString(
text, wxMBConvUTF16LE(), bytes );
213 static const wxCSConv cp1252( wxFONTENCODING_CP1252 );
214 return wxString(
text, cp1252, bytes );
222 Skip( unicode ?
static_cast<size_t>( len ) * 2 : len );
230 const size_t tag_pos =
m_pos;
231 const uint16_t short_tag =
U16();
232 uint32_t tag = short_tag;
234 if( short_tag == BIG_OBJECT_TAG )
236 else if( short_tag & CLASS_TAG )
237 tag = BIG_CLASS_TAG | ( short_tag & ~CLASS_TAG );
239 const char* class_name =
nullptr;
240 uint16_t class_schema = 0;
242 if( short_tag == NEW_CLASS_TAG )
244 class_schema =
U16();
245 uint16_t name_length =
U16();
247 if( name_length >= MAX_CLASS_NAME )
249 wxString::Format(
_(
"Easy-PC class name of %u bytes at offset %zu." ), name_length, tag_pos ) );
252 std::string_view
name(
reinterpret_cast<const char*
>(
m_data +
m_pos ), name_length );
253 m_pos += name_length;
255 auto it = registry().find(
name );
257 if( it == registry().
end() )
259 THROW_IO_ERROR( wxString::Format(
_(
"Unsupported Easy-PC object '%s' at offset %zu." ),
260 wxString::FromUTF8(
name.data(),
name.size() ), tag_pos ) );
263 class_name = it->first.c_str();
264 m_load.push_back(
SLOT{ class_name, class_schema,
nullptr } );
266 else if( tag & BIG_CLASS_TAG )
268 uint32_t
index = tag & ~BIG_CLASS_TAG;
272 THROW_IO_ERROR( wxString::Format(
_(
"Easy-PC class reference to slot %u at offset %zu is not a "
287 THROW_IO_ERROR( wxString::Format(
_(
"Easy-PC object reference to slot %u at offset %zu is not an "
292 return m_load[tag].Object;
302 std::unique_ptr<OBJECT> created = registry().find( class_name )->second();
303 OBJECT* obj = created.get();
305 obj->
Schema = class_schema;
309 m_load.push_back(
SLOT{ class_name, class_schema, obj } );
310 m_objects.push_back( std::move( created ) );
369 const size_t base =
m_frames.size();
394 std::exception_ptr error =
top.promise().m_error;
404 wxString ownerName = owner ? wxString::FromUTF8( owner->
ClassName ) : wxString();
405 size_t ownerOffset = owner ? owner->
Offset : 0;
412 std::rethrow_exception( error );
421 THROW_IO_ERROR( wxString::Format( wxS(
"%s at offset 0x%zX: %s" ), ownerName, ownerOffset, e.
Problem() ) );
437 drive( embedRoot( *
this, aObject, aClass ) );
449 auto it = registry().find( std::string_view( aClass ) );
451 if( it == registry().
end() )
452 THROW_IO_ERROR( wxString::Format(
_(
"Easy-PC class '%s' is not registered." ), aClass ) );
454 std::unique_ptr<OBJECT> obj = it->second();
455 obj->ClassName = it->first.c_str();
462 for(
size_t i = 0; i < aOps.size(); )
471 for( ; i < aOps.size() && aOps[i] >=
'0' && aOps[i] <=
'9'; ++i )
472 count = count * 10 + ( aOps[i] -
'0' );
474 for( count = std::max<size_t>( count, 1 ); count > 0; --count )
478 case 'b': aAr.
InBool();
break;
479 case '1': aAr.
Skip( 1 );
break;
480 case 'i': aAr.
Skip( 4 );
break;
481 case 'f': aAr.
Skip( 8 );
break;
483 case 'o':
co_await aAr.
Object();
break;
484 case 'a': aAr.
Skip( 4 *
size_t( aAr.
Count() ) );
break;
485 default: wxFAIL_MSG( wxS(
"Bad SkipFields op" ) );
496 if( aVersion >= row.Since && aVersion <= row.Until )
511 THROW_IO_ERROR( wxString::Format(
_(
"Easy-PC object '%s' at offset %zu is not of the class required there." ),
void await_suspend(std::coroutine_handle<> aAwaiting)
void await_suspend(std::coroutine_handle<> aAwaiting)
One archive load stream; it owns every object it creates.
void pushTask(LOAD_TASK::HANDLE aHandle)
int CurrentReadFormat() const
OBJECT * readTag(bool &aIsNew)
Read a tag; a new object is created and mapped but not loaded (aIsNew)
void MapObject(OBJECT *aObject)
A load-array slot for an object not read through a tag.
EMBEDDED_AWAITER Embedded(OBJECT &aObject, const char *aClass)
static std::unique_ptr< OBJECT > Create(const char *aClass)
An object of a registered class, not yet loaded.
void drive(LOAD_TASK aRoot)
Run aRoot and everything it awaits until it finishes, rethrowing its failure.
void Require(size_t aLen) const
Throw unless aLen more bytes remain.
wxString ReadString()
ANSI (windows-1252) or the 0xFF 0xFFFE UTF-16 form.
void pushObjectLoad(OBJECT &aObject, uint16_t aSchema)
uint32_t stringLength(bool &aUnicode)
The string length prefix; aUnicode for the UTF-16 form.
static constexpr int MAX_DEPTH
std::vector< std::unique_ptr< OBJECT > > m_objects
void throwBadClass(const OBJECT &aObject) const
bool InBool()
one or four bytes, depending on the active format
int OrphanFormat() const
A null parent tag is the document: the header version in a design, else the current read format.
std::vector< SLOT > m_load
slot 0 is the null slot
std::vector< FRAME > m_frames
OBJECT * ReadObject()
ReadObject and LoadEmbedded outside any loader, running the nested loads to completion.
uint32_t Count()
a 16-bit count, 0xFFFF escaping to 32 bits
void LoadEmbedded(OBJECT &aObject, const char *aClass)
ARCHIVE(const uint8_t *aData, size_t aSize, DOC_KIND aKind, int32_t aProduct)
aData must outlive the archive; aProduct is the product key in force
A loader coroutine. Awaiting one queues it on its archive's frame stack instead of nesting the call.
void await_suspend(std::coroutine_handle<> aAwaiting)
std::coroutine_handle< promise_type > HANDLE
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString Problem() const
what was the problem?
Reader for the serialized streams of Easy-PC and DesignSpark PCB.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
std::map< std::string, std::function< std::unique_ptr< OBJECT >()>, std::less<> > REGISTRY
LOAD_TASK SkipRows(ARCHIVE &aAr, int aVersion, std::span< const FIELD_ROW > aRows)
SkipFields for every row aVersion falls in, in order.
void RegisterGeometryClasses(REGISTRY &aReg)
void RegisterLibraryClasses(REGISTRY &aReg)
LOAD_TASK SkipFields(ARCHIVE &aAr, std::string_view aOps)
Read and drop a run of fields described by aOps, each letter optionally followed by a repeat count: b...
DOC_KIND
Document kind, from the FileType stream or the file extension for projects.
void RegisterRootClasses(REGISTRY &aReg)
void RegisterStyleClasses(REGISTRY &aReg)
void RegisterConnectivityClasses(REGISTRY &aReg)
bool ObjectLoad
counts towards MAX_DEPTH
A run of fields present from version since to until inclusive.
Root of every deserialized object.
virtual LOAD_TASK Load(ARCHIVE &aAr)
Read this object's fields.
size_t Offset
stream offset of the tag or of an embedded member's first field
uint16_t Schema
object schema from the class tag, 0 for an embedded member
const char * ClassName
registered class name
virtual int ItemFormat(const ARCHIVE &aAr) const
The version this object gates on; the base is the archive's current read format.
KIBIS top(path, &reporter)
wxString result
Test unit parsing edge cases and error handling.