app/lib: implemented partial re-weave functionality

This commit is contained in:
itsmattkc 2022-07-17 16:25:06 -07:00
parent 172aef0c9d
commit a976634681
17 changed files with 334 additions and 149 deletions

View file

@ -52,6 +52,7 @@ endif()
target_link_libraries(si-edit PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia libweaver)
target_include_directories(si-edit PRIVATE "${CMAKE_SOURCE_DIR}/lib")
target_compile_options(si-edit PRIVATE -Werror)
set_target_properties(si-edit PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER com.mattkc.SIEdit

View file

@ -181,6 +181,10 @@ void MainWindow::SelectionChanged(const QModelIndex &index)
case MxOb::STL:
p = panel_bmp_;
break;
case MxOb::SMK:
case MxOb::FLC:
case MxOb::OBJ:
break;
}
}

View file

@ -1,5 +1,6 @@
#include "siview.h"
#include <QFileDialog>
#include <QPushButton>
#include <QSplitter>
#include <QTreeView>
@ -9,6 +10,7 @@ using namespace si;
SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) :
QDialog(parent),
root_(riff),
last_set_data_(nullptr)
{
setWindowTitle(mode == Import ? tr("Import SI File") : tr("Export SI File"));
@ -61,6 +63,12 @@ SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) :
connect(reject_btn, &QPushButton::clicked, this, &SIViewDialog::reject);
btn_layout->addWidget(reject_btn);
if (mode == Import) {
auto imm_re_btn = new QPushButton(tr("Immediate Re-Weave"));
connect(imm_re_btn, &QPushButton::clicked, this, &SIViewDialog::ImmediateReweave);
btn_layout->addWidget(imm_re_btn);
}
btn_layout->addStretch();
}
@ -107,3 +115,11 @@ void SIViewDialog::SelectionChanged(const QModelIndex &index)
SetPanel(p, c);
}
}
void SIViewDialog::ImmediateReweave()
{
QString s = QFileDialog::getSaveFileName(this);
if (!s.isEmpty()) {
root_->Write(s.toStdString());
}
}

View file

@ -38,10 +38,13 @@ private:
MxObPanel *panel_mxob_;
si::Chunk *last_set_data_;
si::Chunk *root_;
private slots:
void SelectionChanged(const QModelIndex &index);
void ImmediateReweave();
};
#endif // SIVIEW_H

View file

@ -40,7 +40,7 @@ void WavPanel::OnOpeningData(void *data)
si::Object *o = static_cast<si::Object*>(data);
// Find fmt and data
header_ = *o->GetFileHeader().cast<si::WAVFormatHeader>();
header_ = *o->GetFileHeader().cast<si::WAVFmt>();
playhead_slider_->setMaximum(o->GetFileBodySize()/GetSampleSize());
}

View file

@ -1,7 +1,7 @@
#ifndef WAVPANEL_H
#define WAVPANEL_H
#include <sitypes.h>
#include <othertypes.h>
#include <QAudioOutput>
#include <QBuffer>
#include <QByteArray>
@ -31,7 +31,7 @@ private:
QBuffer buffer_;
QByteArray array_;
QTimer *playback_timer_;
si::WAVFormatHeader header_;
si::WAVFmt header_;
QByteArray play_buffer_;
private slots:

View file

@ -20,6 +20,7 @@ add_library(libweaver SHARED
)
target_compile_definitions(libweaver PRIVATE LIBWEAVER_LIBRARY)
target_compile_options(libweaver PRIVATE -Werror -Wall -Wextra -Wno-unused-parameter)
set_target_properties(libweaver PROPERTIES
CXX_STANDARD 98
CXX_STANDARD_REQUIRED ON

View file

@ -90,8 +90,33 @@ bool Chunk::Read(std::ifstream &f, uint32_t &version, uint32_t &alignment)
return true;
}
bool Chunk::Write(std::ofstream &f, const uint32_t &version) const
bool Chunk::Write(std::ofstream &f, uint32_t &version, uint32_t &alignment) const
{
RIFF *writer = GetReaderFromType(type());
if (alignment != 0) {
// Determine if we have enough space left to write this chunk without the alignment
size_t expected_write = 8;
for (DataMap::const_iterator it=data_.begin(); it!=data_.end(); it++) {
expected_write += it->second.size();
}
size_t start_chunk = f.tellp()/alignment;
size_t end_chunk = (size_t(f.tellp())+expected_write)/alignment;
if (start_chunk != end_chunk) {
// This chunk is going to cross a boundary. We could write padding or split the chunk.
// I'm not exactly sure how Weaver decides which, but I suppose it doesn't matter.
size_t diff = (end_chunk * alignment) - f.tellp();
pad_::WriteArbitraryPadding(f, diff - 8);
/*if (id_ != Chunk::TYPE_MxCh || diff < 0x200) {
// Make padding
pad_::WriteArbitraryPadding(f, diff - 8);
} else {
// Attempt to split chunk
}*/
}
}
// Write 4-byte ID
f.write((const char *) &id_, sizeof(id_));
@ -100,13 +125,19 @@ bool Chunk::Write(std::ofstream &f, const uint32_t &version) const
std::ios::pos_type size_pos = f.tellp();
f.write((const char *) &chunk_size, sizeof(chunk_size));
if (RIFF *writer = GetReaderFromType(type())) {
if (writer) {
writer->Write(f, data_, version);
if (type() == TYPE_MxHd) {
version = data_.at("Version");
alignment = data_.at("BufferSize");
}
delete writer;
}
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
static_cast<Chunk*>(*it)->Write(f, version);
static_cast<Chunk*>(*it)->Write(f, version, alignment);
}
// Backtrack and write chunk size
@ -171,9 +202,9 @@ bool Chunk::Write(const char *f)
return false;
}
uint32_t version = 0;
uint32_t version = 0, alignment = 0;
return Write(file, version);
return Write(file, version, alignment);
}
const char *Chunk::GetTypeDescription(Type type)
@ -204,8 +235,8 @@ const char *Chunk::GetTypeDescription(Type type)
Chunk *Chunk::FindChildWithType(Type type) const
{
for (Core *child : GetChildren()) {
Chunk *chunk = static_cast<Chunk*>(child);
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
Chunk *chunk = static_cast<Chunk*>(*it);
if (chunk->type() == type) {
return chunk;
} else if (Chunk *grandchild = chunk->FindChildWithType(type)) {
@ -218,8 +249,8 @@ Chunk *Chunk::FindChildWithType(Type type) const
Chunk *Chunk::FindChildWithOffset(uint32_t offset) const
{
for (Core *child : GetChildren()) {
Chunk *chunk = static_cast<Chunk*>(child);
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
Chunk *chunk = static_cast<Chunk*>(*it);
if (chunk->offset() == offset) {
return chunk;
} else if (Chunk *grandchild = chunk->FindChildWithOffset(offset)) {

View file

@ -43,7 +43,7 @@ public:
LIBWEAVER_EXPORT const uint32_t &offset() const { return offset_; }
LIBWEAVER_EXPORT Data &data(const std::string &key) { return data_[key]; }
LIBWEAVER_EXPORT const Data &data(const std::string &key) const { return data_.at(key); }
LIBWEAVER_EXPORT Data data(const std::string &key) const { return data_.at(key); }
LIBWEAVER_EXPORT static const char *GetTypeDescription(Type type);
LIBWEAVER_EXPORT const char *GetTypeDescription() const
@ -56,13 +56,13 @@ public:
private:
bool Read(std::ifstream &f, uint32_t &version, uint32_t &alignment);
bool Write(std::ofstream &f, const uint32_t &version) const;
bool Write(std::ofstream &f, uint32_t &version, uint32_t &alignment) const;
static RIFF *GetReaderFromType(Type type);
uint32_t id_;
uint32_t offset_;
std::map<std::string, Data> data_;
DataMap data_;
};

View file

@ -1,8 +1,10 @@
#include "interleaf.h"
#include <cmath>
#include <iostream>
#include "object.h"
#include "othertypes.h"
namespace si {
@ -54,17 +56,6 @@ bool Interleaf::Parse(Chunk *riff)
return true;
}
struct ChunkStatus {
ChunkStatus()
{
index = 0;
time = 0;
}
size_t index;
uint32_t time;
};
Chunk *Interleaf::Export() const
{
Chunk *riff = new Chunk(Chunk::TYPE_RIFF);
@ -97,80 +88,7 @@ Chunk *Interleaf::Export() const
riff->AppendChild(list);
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
Chunk *mxst = new Chunk(Chunk::TYPE_MxSt);
list->AppendChild(mxst);
Object *obj = static_cast<Object*>(*it);
Chunk *mxob = obj->Export();
mxst->AppendChild(mxob);
Chunk *chunklst = new Chunk(Chunk::TYPE_LIST);
chunklst->data("Format") = Chunk::TYPE_MxDa;
mxst->AppendChild(chunklst);
// First, interleave all headers (first chunk)
for (ssize_t i=-1; i<ssize_t(obj->GetChildCount()); i++) {
Object *working_obj = static_cast<Object*>((i == -1) ? obj : obj->GetChildAt(i));
if (!working_obj->data().empty()) {
const bytearray &data = working_obj->data().front();
Chunk *mxch = new Chunk(Chunk::TYPE_MxCh);
mxch->data("Flags") = 0;
mxch->data("Object") = working_obj->id();
mxch->data("Time") = 0;
mxch->data("DataSize") = data.size();
mxch->data("Data") = data;
chunklst->AppendChild(mxch);
}
}
// Next, interleave everything by time
std::vector<ChunkStatus> chunk_status(obj->GetChildCount() + 1);
bool all_done;
do {
all_done = true;
for (ssize_t i=-1; i<ssize_t(obj->GetChildCount()); i++) {
Object *working_obj = static_cast<Object*>((i == -1) ? obj : obj->GetChildAt(i));
ChunkStatus &status = chunk_status[i+1];
if (status.index < working_obj->data().size()) {
const bytearray &data = working_obj->data().at(status.index);
all_done = false;
Chunk *mxch = new Chunk(Chunk::TYPE_MxCh);
mxch->data("Flags") = 0;
mxch->data("Object") = working_obj->id();
mxch->data("Time") = status.time;
mxch->data("DataSize") = data.size();
mxch->data("Data") = data;
chunklst->AppendChild(mxch);
status.index++;
// Increment time
switch (working_obj->filetype()) {
case MxOb::WAV:
status.time += 1000;
break;
case MxOb::STL:
// Unaffected by time
break;
}
}
}
} while (!all_done);
// Finally interleave end chunks
for (ssize_t i=obj->GetChildCount()-1; i>=-1; i--) {
Object *working_obj = static_cast<Object*>((i == -1) ? obj : obj->GetChildAt(i));
ChunkStatus &status = chunk_status[i+1];
Chunk *mxch = new Chunk(Chunk::TYPE_MxCh);
mxch->data("Flags") = MxCh::FLAG_END;
mxch->data("Object") = working_obj->id();
mxch->data("Time") = status.time;
mxch->data("DataSize") = 0;
chunklst->AppendChild(mxch);
}
list->AppendChild(ExportStream(static_cast<Object*>(*it)));
}
// FIXME: Fill in MxOf table
@ -244,4 +162,132 @@ bool Interleaf::ParseStream(Chunk *chunk)
return true;
}
struct ChunkStatus
{
ChunkStatus()
{
object = NULL;
index = 0;
time = 0;
end_chunk = false;
}
Object *object;
size_t index;
uint32_t time;
bool end_chunk;
};
Chunk *Interleaf::ExportStream(Object *obj) const
{
Chunk *mxst = new Chunk(Chunk::TYPE_MxSt);
Chunk *mxob = obj->Export();
mxst->AppendChild(mxob);
Chunk *chunklst = new Chunk(Chunk::TYPE_LIST);
chunklst->data("Format") = Chunk::TYPE_MxDa;
mxst->AppendChild(chunklst);
// Set up chunking status vector
std::vector<ChunkStatus> chunk_status(obj->GetChildCount() + 1);
chunk_status[0].object = obj;
for (size_t i=0; i<obj->GetChildCount(); i++) {
chunk_status[i+1].object = static_cast<Object*>(obj->GetChildAt(i));
}
// First, interleave all headers (first chunk)
for (std::vector<ChunkStatus>::iterator it=chunk_status.begin(); it!=chunk_status.end(); it++) {
Object *working_obj = it->object;
if (!working_obj->data().empty()) {
chunklst->AppendChild(ExportMxCh(0, working_obj->id(), 0, working_obj->data().front()));
}
it->index++;
}
// Next, interleave everything by time
while (true) {
// Find next chunk
ChunkStatus *status = NULL;
for (std::vector<ChunkStatus>::iterator it=chunk_status.begin(); it!=chunk_status.end(); it++) {
// Check if we've already written all these chunks
if (it->index >= it->object->data().size()) {
/*if (!it->end_chunk) {
chunklst->AppendChild(ExportMxCh(MxCh::FLAG_END, it->object->id(), it->time));
it->end_chunk = true;
}*/
continue;
}
// Find earliest chunk to write
if (!status || it->time < status->time) {
status = it.base();
}
}
if (!status) {
// Assume chunks are all done
break;
}
Object *working_obj = status->object;
const bytearray &data = working_obj->data().at(status->index);
chunklst->AppendChild(ExportMxCh(0, working_obj->id(), status->time, data));
status->index++;
// Increment time
switch (working_obj->filetype()) {
case MxOb::WAV:
{
const WAVFmt *fmt = working_obj->GetFileHeader().cast<WAVFmt>();
status->time += (data.size() * 1000) / (fmt->BitsPerSample/8) / fmt->Channels / fmt->SampleRate;
break;
}
case MxOb::SMK:
{
int32_t frame_rate = working_obj->GetFileHeader().cast<SMK2>()->FrameRate;
int32_t fps;
if (frame_rate > 0) {
fps = 1000/frame_rate;
} else if (frame_rate < 0) {
fps = 100000/-frame_rate;
} else {
fps = 10;
}
status->time += 1000/fps;
break;
}
case MxOb::FLC:
status->time += working_obj->GetFileHeader().cast<FLIC>()->speed;
break;
case MxOb::STL:
case MxOb::OBJ:
// Unaffected by time
break;
}
};
for (size_t i=1; i<chunk_status.size(); i++) {
const ChunkStatus &s = chunk_status.at(i);
chunklst->AppendChild(ExportMxCh(MxCh::FLAG_END, s.object->id(), s.time));
}
chunklst->AppendChild(ExportMxCh(MxCh::FLAG_END, chunk_status.front().object->id(), chunk_status.front().time));
return mxst;
}
Chunk *Interleaf::ExportMxCh(uint16_t flags, uint32_t object_id, uint32_t time, const bytearray &data) const
{
Chunk *mxch = new Chunk(Chunk::TYPE_MxCh);
mxch->data("Flags") = flags;
mxch->data("Object") = object_id;
mxch->data("Time") = time;
mxch->data("DataSize") = data.size();
mxch->data("Data") = data;
return mxch;
}
}

View file

@ -3,6 +3,7 @@
#include "chunk.h"
#include "core.h"
#include "object.h"
namespace si {
@ -16,6 +17,8 @@ public:
private:
bool ParseStream(Chunk *chunk);
Chunk *ExportStream(Object *obj) const;
Chunk *ExportMxCh(uint16_t flags, uint32_t object_id, uint32_t time, const bytearray &data = bytearray()) const;
uint32_t version_;
uint32_t buffer_size_;

View file

@ -37,9 +37,9 @@ bool Object::Parse(Chunk *chunk)
if (chunk->HasChildren()) {
Chunk *child = static_cast<Chunk*>(chunk->GetChildAt(0));
if (child->id() == Chunk::TYPE_LIST) {
for (Core *entry : child->GetChildren()) {
for (Children::const_iterator it=child->GetChildren().begin(); it!=child->GetChildren().end(); it++) {
Object *o = new Object();
if (!o->Parse(static_cast<Chunk*>(entry))) {
if (!o->Parse(static_cast<Chunk*>(*it))) {
return false;
}
AppendChild(o);
@ -79,6 +79,7 @@ Chunk *Object::Export() const
if (HasChildren()) {
Chunk *list = new Chunk(Chunk::TYPE_LIST);
list->data("Format") = Chunk::TYPE_MxCh;
list->data("Count") = list->GetChildCount();
chunk->AppendChild(list);
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
@ -164,6 +165,15 @@ bytearray Object::ToPackedData(MxOb::FileType filetype, const ChunkedData &chunk
}
break;
}
case MxOb::SMK:
case MxOb::OBJ:
{
// Simply merge
for (size_t i=0; i<chunks.size(); i++) {
data.append(chunks[i]);
}
break;
}
default:
std::cout << "Didn't know how to extract type '" << std::string((const char *)&filetype, sizeof(filetype)) << "', merging..." << std::endl;
for (size_t i=0; i<chunks.size(); i++) {
@ -181,28 +191,17 @@ Object::ChunkedData Object::ToChunkedData(MxOb::FileType filetype, const bytearr
return ChunkedData();
}
bytearray Object::GetFileHeader() const
const bytearray &Object::GetFileHeader() const
{
switch (filetype()) {
case MxOb::WAV:
case MxOb::STL:
return data_.at(0);
}
return bytearray();
return data_.at(0);
}
bytearray Object::GetFileBody() const
{
bytearray b;
switch (filetype()) {
case MxOb::WAV:
case MxOb::STL:
for (size_t i=1; i<data_.size(); i++) {
b.append(data_.at(i));
}
break;
for (size_t i=1; i<data_.size(); i++) {
b.append(data_.at(i));
}
return b;
@ -211,13 +210,9 @@ bytearray Object::GetFileBody() const
size_t Object::GetFileBodySize() const
{
size_t s = 0;
switch (filetype()) {
case MxOb::WAV:
case MxOb::STL:
for (size_t i=1; i<data_.size(); i++) {
s += data_.at(i).size();
}
break;
for (size_t i=1; i<data_.size(); i++) {
s += data_.at(i).size();
}
return s;
@ -229,8 +224,8 @@ Object *Object::FindSubObjectWithID(uint32_t id)
return this;
}
for (Core *child : GetChildren()) {
if (Object *o = static_cast<Object*>(child)->FindSubObjectWithID(id)) {
for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) {
if (Object *o = static_cast<Object*>(*it)->FindSubObjectWithID(id)) {
return o;
}
}

View file

@ -25,7 +25,7 @@ public:
LIBWEAVER_EXPORT static bytearray ToPackedData(MxOb::FileType filetype, const ChunkedData &chunks);
LIBWEAVER_EXPORT static ChunkedData ToChunkedData(MxOb::FileType filetype, const bytearray &chunks);
LIBWEAVER_EXPORT bytearray GetFileHeader() const;
LIBWEAVER_EXPORT const bytearray &GetFileHeader() const;
LIBWEAVER_EXPORT bytearray GetFileBody() const;
LIBWEAVER_EXPORT size_t GetFileBodySize() const;

77
lib/othertypes.h Normal file
View file

@ -0,0 +1,77 @@
#ifndef OTHERTYPES_H
#define OTHERTYPES_H
#include "types.h"
namespace si {
class WAVFmt
{
public:
// Standard WAV header
uint16_t Format;
uint16_t Channels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample;
// Mindscape extensions (not confirmed yet)
uint32_t DataSize;
uint32_t Flags;
};
// Copied from https://www.compuphase.com/flic.htm#FLICHEADER
class FLIC
{
public:
uint32_t size; /* Size of FLIC including this header */
uint16_t type; /* File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ... */
uint16_t frames; /* Number of frames in first segment */
uint16_t width; /* FLIC width in pixels */
uint16_t height; /* FLIC height in pixels */
uint16_t depth; /* Bits per pixel (usually 8) */
uint16_t flags; /* Set to zero or to three */
uint32_t speed; /* Delay between frames */
uint16_t reserved1; /* Set to zero */
uint32_t created; /* Date of FLIC creation (FLC only) */
uint32_t creator; /* Serial number or compiler id (FLC only) */
uint32_t updated; /* Date of FLIC update (FLC only) */
uint32_t updater; /* Serial number (FLC only), see creator */
uint16_t aspect_dx; /* Width of square rectangle (FLC only) */
uint16_t aspect_dy; /* Height of square rectangle (FLC only) */
uint16_t ext_flags; /* EGI: flags for specific EGI extensions */
uint16_t keyframes; /* EGI: key-image frequency */
uint16_t totalframes; /* EGI: total number of frames (segments) */
uint32_t req_memory; /* EGI: maximum chunk size (uncompressed) */
uint16_t max_regions; /* EGI: max. number of regions in a CHK_REGION chunk */
uint16_t transp_num; /* EGI: number of transparent levels */
uint8_t reserved2[24]; /* Set to zero */
uint32_t oframe1; /* Offset to frame 1 (FLC only) */
uint32_t oframe2; /* Offset to frame 2 (FLC only) */
uint8_t reserved3[40]; /* Set to zero */
};
// Copied from https://wiki.multimedia.cx/index.php/Smacker#Header
class SMK2
{
public:
uint32_t Signature;
uint32_t Width;
uint32_t Height;
uint32_t Frames;
uint32_t FrameRate;
uint32_t Flags;
uint32_t AudioSize[7];
uint32_t TreesSize;
uint32_t MMap_Size;
uint32_t MClr_Size;
uint32_t Full_Size;
uint32_t Type_Size;
uint32_t AudioRate[7];
uint32_t Dummy;
};
}
#endif // OTHERTYPES_H

View file

@ -202,6 +202,16 @@ void pad_::Write(std::ofstream &os, const DataMap &data, uint32_t version)
}
}
void pad_::WriteArbitraryPadding(std::ofstream &os, uint32_t size)
{
uint32_t pad_id = Chunk::TYPE_pad_;
os.write((const char *) &pad_id, sizeof(uint32_t));
os.write((const char *) &size, sizeof(uint32_t));
bytearray b(size);
b.fill(0xCD);
WriteBytes(os, b);
}
const char *MxOb::GetTypeName(Type type)
{
switch (type) {
@ -211,9 +221,9 @@ const char *MxOb::GetTypeName(Type type)
return "WAV";
case Presenter:
return "MxPresenter";
case BMP:
case Bitmap:
return "BMP";
case OBJ:
case Object:
return "3D Object";
case World:
return "World";
@ -221,6 +231,7 @@ const char *MxOb::GetTypeName(Type type)
return "Event";
case Animation:
return "Animation";
case Null:
case TYPE_COUNT:
break;
}

View file

@ -26,6 +26,7 @@ public:
virtual void Read(std::ifstream &is, DataMap &data, uint32_t version, uint32_t size);
virtual void Write(std::ofstream &os, const DataMap &data, uint32_t version);
};
/**
@ -122,6 +123,8 @@ class pad_ : public RIFF
public:
virtual void Read(std::ifstream &is, DataMap &data, uint32_t version, uint32_t size);
virtual void Write(std::ofstream &os, const DataMap &data, uint32_t version);
static void WriteArbitraryPadding(std::ofstream &os, uint32_t size);
};
/**
@ -180,10 +183,10 @@ public:
Animation = 0x09,
/// Bitmap image
BMP = 0x0A,
Bitmap = 0x0A,
/// 3D Object
OBJ = 0x0B,
Object = 0x0B,
/// Total number of types (not a real type)
TYPE_COUNT
@ -220,6 +223,12 @@ public:
/// FLIC animation
FLC = 0x434c4620,
/// SMK video
SMK = 0x4b4d5320,
/// 3D Object
OBJ = 0x4a424f20,
};
// FIXME: sitypes.h probably won't be part of the public API, so this should
@ -231,22 +240,6 @@ public:
virtual void Write(std::ofstream &os, const DataMap &data, uint32_t version);
};
class WAVFormatHeader
{
public:
// Standard WAV header
uint16_t Format;
uint16_t Channels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample;
// Mx extensions (not confirmed yet)
uint32_t DataSize;
uint32_t Flags;
};
}
#endif // SI_H

View file

@ -109,8 +109,12 @@ public:
inline size_t size() const { return data_.size(); }
inline const std::string toString() const
{
// Subtract 1 from size, assuming the last character is a null terminator
return std::string(data_.data(), std::max(size_t(0), data_.size()-1));
if (data_.empty()) {
return std::string();
} else {
// Subtract 1 from size, assuming the last character is a null terminator
return std::string(data_.data(), std::max(size_t(0), data_.size()-1));
}
}
inline bool operator==(int u) const