diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b921032..5b4f6e4 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -4,20 +4,12 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia) set(PROJECT_SOURCES -# siview/chunkmodel.cpp -# siview/chunkmodel.h -# siview/panels/mxch.cpp -# siview/panels/mxch.h -# siview/panels/mxhd.cpp -# siview/panels/mxhd.h -# siview/panels/mxob.cpp -# siview/panels/mxob.h -# siview/panels/mxof.cpp -# siview/panels/mxof.h -# siview/panels/riff.cpp -# siview/panels/riff.h -# siview/siview.cpp -# siview/siview.h + siview/chunkmodel.cpp + siview/chunkmodel.h + siview/infopanel.cpp + siview/infopanel.h + siview/siview.cpp + siview/siview.h viewer/bitmappanel.cpp viewer/bitmappanel.h diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index b3f04d5..729bb00 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -6,8 +6,12 @@ #include <QMessageBox> #include <QSplitter> +#include "siview/siview.h" + using namespace si; +const QString MainWindow::kFileFilter = tr("Interleaf Files (*.si)"); + MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, last_set_data_(nullptr) @@ -41,6 +45,7 @@ MainWindow::MainWindow(QWidget *parent) : auto replace_btn = new QPushButton(tr("Replace")); action_layout->addWidget(replace_btn); + connect(replace_btn, &QPushButton::clicked, this, &MainWindow::ReplaceClicked); action_layout->addStretch(); @@ -67,18 +72,8 @@ void MainWindow::OpenFilename(const QString &s) { model_.SetCore(nullptr); - bool r = -#ifdef Q_OS_WINDOWS - interleaf_.Read(s.toStdWString().c_str()); -#else - interleaf_.Read(s.toUtf8()); -#endif - ; - - if (r) { + if (OpenInterleafFileInternal(this, &interleaf_, s)) { model_.SetCore(&interleaf_); - } else { - QMessageBox::critical(this, QString(), tr("Failed to load Interleaf file.")); } } @@ -88,22 +83,21 @@ void MainWindow::InitializeMenuBar() auto file_menu = menubar->addMenu(tr("&File")); - auto new_action = file_menu->addAction(tr("&New")); + file_menu->addAction(tr("&New"), this, &MainWindow::NewFile, tr("Ctrl+N")); - auto open_action = file_menu->addAction(tr("&Open"), this, &MainWindow::OpenFile, tr("Ctrl+O")); + file_menu->addAction(tr("&Open"), this, &MainWindow::OpenFile, tr("Ctrl+O")); - auto save_action = file_menu->addAction(tr("&Save")); - auto save_as_action = file_menu->addAction(tr("Save &As")); + file_menu->addAction(tr("&Save"), this, &MainWindow::SaveFile, tr("Ctrl+S")); + + file_menu->addAction(tr("Save &As"), this, &MainWindow::SaveFileAs, tr("Ctrl+Shift+S")); file_menu->addSeparator(); - auto export_action = file_menu->addAction(tr("&Export")); - connect(export_action, &QAction::triggered, this, &MainWindow::ExportFile); + file_menu->addAction(tr("&View SI File"), this, &MainWindow::ViewSIFile); file_menu->addSeparator(); - auto exit_action = file_menu->addAction(tr("E&xit")); - connect(exit_action, &QAction::triggered, this, &MainWindow::close); + file_menu->addAction(tr("E&xit"), this, &MainWindow::close); setMenuBar(menubar); } @@ -136,27 +130,102 @@ void MainWindow::ExtractObject(si::Object *obj) QString s = QFileDialog::getSaveFileName(this, tr("Export Object"), filename); if (!s.isEmpty()) { - QFile f(s); - if (f.open(QFile::WriteOnly)) { - bytearray b = obj->GetNormalizedData(); - f.write(b.data(), b.size()); - f.close(); - } else { + if (!obj->ExtractToFile( +#ifdef Q_OS_WINDOWS + s.toStdWString().c_str() +#else + s.toUtf8() +#endif + )) { QMessageBox::critical(this, QString(), tr("Failed to write to file \"%1\".").arg(s)); } } } +void MainWindow::ReplaceObject(si::Object *obj) +{ + QString s = QFileDialog::getOpenFileName(this, tr("Replace Object")); + if (!s.isEmpty()) { + if (!obj->ReplaceWithFile( +#ifdef Q_OS_WINDOWS + s.toStdWString().c_str() +#else + s.toUtf8() +#endif + )) { + QMessageBox::critical(this, QString(), tr("Failed to open to file \"%1\".").arg(s)); + } + } +} + +bool MainWindow::OpenInterleafFileInternal(QWidget *parent, si::Interleaf *interleaf, const QString &s) +{ + Interleaf::Error r = interleaf->Read( +#ifdef Q_OS_WINDOWS + s.toStdWString().c_str() +#else + s.toUtf8() +#endif + ); + + if (r == Interleaf::ERROR_SUCCESS) { + return true; + } else { + QMessageBox::critical(parent, QString(), tr("Failed to load Interleaf file: %1").arg(r)); + return false; + } +} + +QString MainWindow::GetOpenFileName() +{ + return QFileDialog::getOpenFileName(this, QString(), QString(), kFileFilter); +} + +void MainWindow::NewFile() +{ + model_.SetCore(nullptr); + interleaf_.Clear(); + model_.SetCore(&interleaf_); +} + void MainWindow::OpenFile() { - QString s = QFileDialog::getOpenFileName(this, QString(), QString(), tr("Interleaf Files (*.si)")); + QString s = GetOpenFileName(); if (!s.isEmpty()) { OpenFilename(s); } } -void MainWindow::ExportFile() +bool MainWindow::SaveFile() { + if (current_filename_.isEmpty()) { + return SaveFileAs(); + } else { + Interleaf::Error r = interleaf_.Write( +#ifdef Q_OS_WINDOWS + current_filename_.toStdWString().c_str() +#else + current_filename_.toUtf8() +#endif + ); + + if (r == Interleaf::ERROR_SUCCESS) { + return true; + } else { + QMessageBox::critical(this, QString(), tr("Failed to write SI file: %1").arg(r)); + return false; + } + } +} + +bool MainWindow::SaveFileAs() +{ + current_filename_ = QFileDialog::getSaveFileName(this, QString(), QString(), kFileFilter); + if (!current_filename_.isEmpty()) { + return SaveFile(); + } + + return false; } void MainWindow::SelectionChanged(const QModelIndex &index) @@ -212,3 +281,22 @@ void MainWindow::ExtractClicked() { ExtractObject(last_set_data_); } + +void MainWindow::ReplaceClicked() +{ + ReplaceObject(last_set_data_); +} + +void MainWindow::ViewSIFile() +{ + QString s = GetOpenFileName(); + if (!s.isEmpty()) { + std::unique_ptr<Interleaf> temp = std::make_unique<Interleaf>(); + if (OpenInterleafFileInternal(this, temp.get(), s)) { + SIViewDialog *v = new SIViewDialog(temp->GetInformation(), this); + v->temp = std::move(temp); + v->setAttribute(Qt::WA_DeleteOnClose); + v->show(); + } + } +} diff --git a/app/mainwindow.h b/app/mainwindow.h index 83af820..886fef6 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -24,13 +24,18 @@ public: signals: private: - //bool CloseFile(); - void InitializeMenuBar(); void SetPanel(Panel *panel, si::Object *chunk); void ExtractObject(si::Object *obj); + void ReplaceObject(si::Object *obj); + + static bool OpenInterleafFileInternal(QWidget *parent, si::Interleaf *interleaf, const QString &s); + + QString GetOpenFileName(); + + static const QString kFileFilter; QStackedWidget *config_stack_; @@ -47,21 +52,25 @@ private: si::Object *last_set_data_; -private slots: - void OpenFile(); - //bool SaveFile(); - //bool SaveFileAs(); + QString current_filename_; - void ExportFile(); +private slots: + void NewFile(); + void OpenFile(); + bool SaveFile(); + bool SaveFileAs(); void SelectionChanged(const QModelIndex &index); void ShowContextMenu(const QPoint &p); void ExtractSelectedItems(); - void ExtractClicked(); + void ReplaceClicked(); + + void ViewSIFile(); + }; #endif // MAINWINDOW_H diff --git a/app/siview/chunkmodel.cpp b/app/siview/chunkmodel.cpp index f164be0..49ffb6f 100644 --- a/app/siview/chunkmodel.cpp +++ b/app/siview/chunkmodel.cpp @@ -1,6 +1,7 @@ #include "chunkmodel.h" #include <iostream> +#include <sitypes.h> #define super Model @@ -18,7 +19,7 @@ int ChunkModel::columnCount(const QModelIndex &parent) const QVariant ChunkModel::data(const QModelIndex &index, int role) const { - Chunk *c = static_cast<Chunk*>(GetCoreFromIndex(index)); + Info *c = static_cast<Info*>(GetCoreFromIndex(index)); if (!c) { return QVariant(); } @@ -29,16 +30,15 @@ QVariant ChunkModel::data(const QModelIndex &index, int role) const switch (index.column()) { case kColType: // Convert 4-byte ID to QString - return QString::fromLatin1(reinterpret_cast<const char *>(&c->id()), sizeof(uint32_t)); + return QString::fromLatin1(reinterpret_cast<const char *>(&c->GetType()), sizeof(uint32_t)); case kColOffset: - return QStringLiteral("0x%1").arg(QString::number(c->offset(), 16).toUpper()); + return QStringLiteral("0x%1").arg(QString::number(c->GetOffset(), 16).toUpper()); case kColDesc: - return QString::fromUtf8(c->GetTypeDescription()); + return QString::fromUtf8(RIFF::GetTypeDescription(static_cast<RIFF::Type>(c->GetType()))); case kColObjectID: - if (c->type() == Chunk::TYPE_MxOb) { - return c->data("ID").toU32(); - } else if (c->type() == Chunk::TYPE_MxCh) { - return c->data("Object").toU32(); + uint32_t i = c->GetObjectID(); + if (i != Info::NULL_OBJECT_ID) { + return QString::number(i); } break; } diff --git a/app/siview/chunkmodel.h b/app/siview/chunkmodel.h index 6b1160f..4dbdc9f 100644 --- a/app/siview/chunkmodel.h +++ b/app/siview/chunkmodel.h @@ -1,7 +1,7 @@ #ifndef CHUNKMODEL_H #define CHUNKMODEL_H -#include <chunk.h> +#include <info.h> #include "model.h" diff --git a/app/siview/infopanel.cpp b/app/siview/infopanel.cpp new file mode 100644 index 0000000..b47b1f8 --- /dev/null +++ b/app/siview/infopanel.cpp @@ -0,0 +1,56 @@ +#include "infopanel.h" + +#include <info.h> +#include <QDebug> + +InfoPanel::InfoPanel(QWidget *parent) : + Panel(parent) +{ + int row = 0; + + m_Lbl = new QLabel(); + layout()->addWidget(m_Lbl, row, 0); + + row++; + + m_ShowDataBtn = new QPushButton(tr("Show Data")); + connect(m_ShowDataBtn, &QPushButton::clicked, this, &InfoPanel::ShowData); + layout()->addWidget(m_ShowDataBtn, row, 0); + m_ShowDataBtn->hide(); + + row++; + + m_DataView = new QPlainTextEdit(); + m_DataView->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + layout()->addWidget(m_DataView, row, 0); + m_DataView->hide(); + + FinishLayout(); +} + +void InfoPanel::OnOpeningData(void *data) +{ + auto info = static_cast<si::Info*>(data); + m_Lbl->setText(QString::fromStdString(info->GetDescription())); + + if (!info->GetData().empty()) { + m_ShowDataBtn->show(); + } +} + +void InfoPanel::OnClosingData(void *data) +{ + m_Lbl->setText(QString()); + m_DataView->hide(); + m_DataView->clear(); + m_ShowDataBtn->hide(); +} + +void InfoPanel::ShowData() +{ + const si::bytearray &s = static_cast<si::Info*>(this->GetData())->GetData(); + + m_ShowDataBtn->hide(); + m_DataView->setPlainText(QByteArray(s.data(), s.size()).toHex()); + m_DataView->show(); +} diff --git a/app/siview/infopanel.h b/app/siview/infopanel.h new file mode 100644 index 0000000..2a2917d --- /dev/null +++ b/app/siview/infopanel.h @@ -0,0 +1,32 @@ +#ifndef INFOPANEL_H +#define INFOPANEL_H + +#include <QLabel> +#include <QPlainTextEdit> +#include <QPushButton> + +#include "panel.h" + +class InfoPanel : public Panel +{ + Q_OBJECT +public: + InfoPanel(QWidget *parent = nullptr); + +protected: + virtual void OnOpeningData(void *data) override; + virtual void OnClosingData(void *data) override; + +private: + QLabel *m_Lbl; + + QPushButton *m_ShowDataBtn; + + QPlainTextEdit *m_DataView; + +private slots: + void ShowData(); + +}; + +#endif // INFOPANEL_H diff --git a/app/siview/panels/mxch.cpp b/app/siview/panels/mxch.cpp deleted file mode 100644 index c59730a..0000000 --- a/app/siview/panels/mxch.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "mxch.h" - -#include <chunk.h> -#include <QFontDatabase> -#include <QGroupBox> -#include <QLabel> - -using namespace si; - -MxChPanel::MxChPanel(QWidget *parent) : - Panel(parent) -{ - int row = 0; - - auto flag_group = new QGroupBox(tr("Flags")); - layout()->addWidget(flag_group, row, 0, 1, 2); - - { - auto flag_layout = new QGridLayout(flag_group); - - int flag_row = 0; - - flag_layout->addWidget(new QLabel(tr("Value")), flag_row, 0); - - flag_edit_ = new QLineEdit(); - flag_layout->addWidget(flag_edit_, flag_row, 1); - - flag_row++; - - auto split_chunk = new QCheckBox(tr("Split Chunk")); - split_chunk->setProperty("flag", MxCh::FLAG_SPLIT); - connect(split_chunk, &QCheckBox::clicked, this, &MxChPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(split_chunk); - flag_layout->addWidget(split_chunk, flag_row, 0, 1, 2); - - flag_row++; - - auto end_chunk = new QCheckBox(tr("End Chunk")); - end_chunk->setProperty("flag", MxCh::FLAG_END); - connect(end_chunk, &QCheckBox::clicked, this, &MxChPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(end_chunk); - flag_layout->addWidget(end_chunk, flag_row, 0, 1, 2); - - flag_row++; - } - - row++; - - layout()->addWidget(new QLabel(tr("Object ID")), row, 0); - - obj_edit_ = new QSpinBox(); - obj_edit_->setMinimum(0); - obj_edit_->setMaximum(INT_MAX); - layout()->addWidget(obj_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Millisecond Offset")), row, 0); - - ms_offset_edit_ = new QSpinBox(); - ms_offset_edit_->setMinimum(0); - ms_offset_edit_->setMaximum(INT_MAX); - layout()->addWidget(ms_offset_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Data Size")), row, 0); - - data_sz_edit_ = new QSpinBox(); - data_sz_edit_->setMinimum(0); - data_sz_edit_->setMaximum(INT_MAX); - layout()->addWidget(data_sz_edit_, row, 1); - - row++; - - show_data_btn_ = new QPushButton(tr("Show Data")); - connect(show_data_btn_, &QPushButton::clicked, this, &MxChPanel::ShowDataField); - layout()->addWidget(show_data_btn_, row, 0, 1, 2); - - data_edit_ = new QTextEdit(); - data_edit_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - layout()->addWidget(data_edit_, row, 0, 1, 2); - data_edit_->hide(); - - FinishLayout(); -} - -void MxChPanel::OnOpeningData(void *d) -{ - Chunk *chunk = static_cast<Chunk*>(d); - - flag_edit_->setText(QString::number(chunk->data("Flags"), 16)); - obj_edit_->setValue(chunk->data("Object")); - ms_offset_edit_->setValue(chunk->data("Time")); - data_sz_edit_->setValue(chunk->data("DataSize")); - - for (QCheckBox *cb : qAsConst(flag_checkboxes_)) { - cb->setChecked(cb->property("flag").toUInt() & chunk->data("Flags")); - } -} - -void MxChPanel::OnClosingData(void *data) -{ - /*bool ok; - u16 flags = flag_edit_->text().toUShort(&ok, 16); - if (ok) { - chunk->data("Flags") = flags; - } - - chunk->data("Object") = u32(obj_edit_->value()); - chunk->data("Time") = u32(ms_offset_edit_->value()); - chunk->data("DataSize") = u32(data_sz_edit_->value());*/ - - show_data_btn_->show(); - data_edit_->clear(); - data_edit_->hide(); -} - -void MxChPanel::FlagCheckBoxClicked(bool e) -{ - uint16_t flag = sender()->property("flag").toUInt(); - bool ok; - uint16_t current = flag_edit_->text().toUShort(&ok, 16); - if (ok) { - if (e) { - current |= flag; - } else { - current &= ~flag; - } - - flag_edit_->setText(QString::number(current, 16)); - } -} - -void MxChPanel::ShowDataField() -{ - show_data_btn_->hide(); - data_edit_->show(); - - Chunk *chunk = static_cast<Chunk*>(GetData()); - - const Data &data = chunk->data("Data"); - QByteArray ba(data.data(), int(data.size())); - data_edit_->setPlainText(ba.toHex()); -} diff --git a/app/siview/panels/mxch.h b/app/siview/panels/mxch.h deleted file mode 100644 index 8ab1a56..0000000 --- a/app/siview/panels/mxch.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MXCHPANEL_H -#define MXCHPANEL_H - -#include <QCheckBox> -#include <QLineEdit> -#include <QPushButton> -#include <QSpinBox> -#include <QTextEdit> - -#include "panel.h" - -class MxChPanel : public Panel -{ - Q_OBJECT -public: - explicit MxChPanel(QWidget *parent = nullptr); - -protected: - virtual void OnOpeningData(void *data) override; - virtual void OnClosingData(void *data) override; - -private: - QLineEdit *flag_edit_; - QSpinBox *obj_edit_; - QSpinBox *ms_offset_edit_; - QSpinBox *data_sz_edit_; - QTextEdit *data_edit_; - QPushButton *show_data_btn_; - - QVector<QCheckBox*> flag_checkboxes_; - -private slots: - void FlagCheckBoxClicked(bool e); - - void ShowDataField(); - -}; - -#endif // MXCHPANEL_H diff --git a/app/siview/panels/mxhd.cpp b/app/siview/panels/mxhd.cpp deleted file mode 100644 index c691ccd..0000000 --- a/app/siview/panels/mxhd.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "mxhd.h" - -#include <chunk.h> -#include <QLabel> - -using namespace si; - -MxHdPanel::MxHdPanel(QWidget *parent) : - Panel{parent} -{ - int row = 0; - - auto version_layout = new QHBoxLayout(); - layout()->addLayout(version_layout, row, 0, 1, 2); - - version_layout->addWidget(new QLabel(tr("Major Version"))); - - major_version_edit_ = new QSpinBox(); - major_version_edit_->setMinimum(0); - major_version_edit_->setMaximum(UINT16_MAX); - version_layout->addWidget(major_version_edit_); - - version_layout->addWidget(new QLabel(tr("Minor Version"))); - - minor_version_edit_ = new QSpinBox(); - minor_version_edit_->setMinimum(0); - minor_version_edit_->setMaximum(UINT16_MAX); - version_layout->addWidget(minor_version_edit_); - - row++; - - layout()->addWidget(new QLabel(tr("Buffer Alignment")), row, 0); - - buffer_alignment_edit_ = new QSpinBox(); - buffer_alignment_edit_->setMinimum(0); - buffer_alignment_edit_->setMaximum(INT_MAX); // Technically this is UINT32_MAX but QSpinBox only supports int - layout()->addWidget(buffer_alignment_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Buffer Count")), row, 0); - - buffer_count_edit_ = new QSpinBox(); - buffer_count_edit_->setMinimum(0); - buffer_count_edit_->setMaximum(INT_MAX); // Technically this is UINT32_MAX but QSpinBox only supports int - layout()->addWidget(buffer_count_edit_, row, 1); - - FinishLayout(); -} - -void MxHdPanel::OnOpeningData(void *data) -{ - Chunk *chunk = static_cast<Chunk*>(data); - uint32_t version = chunk->data("Version"); - uint16_t major_ver = version >> 16; - uint16_t minor_ver = version; - - major_version_edit_->setValue(major_ver); - minor_version_edit_->setValue(minor_ver); - buffer_alignment_edit_->setValue(chunk->data("BufferSize")); - buffer_count_edit_->setValue(chunk->data("BufferCount")); -} - -void MxHdPanel::OnClosingData(void *data) -{ - /*chunk->data("Version") = (major_version_edit_->value() << 16 | (minor_version_edit_->value() & 0xFFFF)); - chunk->data("BufferSize") = buffer_alignment_edit_->value(); - chunk->data("BufferCount") = buffer_count_edit_->value();*/ -} diff --git a/app/siview/panels/mxhd.h b/app/siview/panels/mxhd.h deleted file mode 100644 index c6cbac6..0000000 --- a/app/siview/panels/mxhd.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef MXHDPANEL_H -#define MXHDPANEL_H - -#include <QLineEdit> -#include <QSpinBox> - -#include "panel.h" - -class MxHdPanel : public Panel -{ - Q_OBJECT -public: - explicit MxHdPanel(QWidget *parent = nullptr); - -signals: - -protected: - virtual void OnOpeningData(void *data) override; - virtual void OnClosingData(void *data) override; - -private: - QSpinBox *major_version_edit_; - QSpinBox *minor_version_edit_; - QSpinBox *buffer_alignment_edit_; - QSpinBox *buffer_count_edit_; - -}; - -#endif // MXHDPANEL_H diff --git a/app/siview/panels/mxob.cpp b/app/siview/panels/mxob.cpp deleted file mode 100644 index e80bd73..0000000 --- a/app/siview/panels/mxob.cpp +++ /dev/null @@ -1,217 +0,0 @@ -#include "mxob.h" - -#include <chunk.h> -#include <sitypes.h> -#include <QGroupBox> -#include <QLabel> - -using namespace si; - -MxObPanel::MxObPanel(QWidget *parent) : - Panel(parent) -{ - int row = 0; - - layout()->addWidget(new QLabel(tr("Type")), row, 0); - - type_combo_ = new QComboBox(); - for (int i=0; i<MxOb::TYPE_COUNT; i++) { - type_combo_->addItem(MxOb::GetTypeName(MxOb::Type(i))); - } - layout()->addWidget(type_combo_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Name")), row, 0); - - name_edit_ = new QLineEdit(); - layout()->addWidget(name_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Filename")), row, 0); - - filename_edit_ = new QLineEdit(); - layout()->addWidget(filename_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Object ID")), row, 0); - - obj_id_edit_ = new QSpinBox(); - obj_id_edit_->setMinimum(0); - obj_id_edit_->setMaximum(INT_MAX); - layout()->addWidget(obj_id_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Presenter")), row, 0); - - presenter_edit_ = new QLineEdit(); - layout()->addWidget(presenter_edit_, row, 1); - - row++; - - auto flag_group = new QGroupBox(tr("Flags")); - layout()->addWidget(flag_group, row, 0, 1, 2); - - row++; - - { - auto flag_layout = new QGridLayout(flag_group); - - int flag_row = 0; - - flag_layout->addWidget(new QLabel(tr("Value")), flag_row, 0); - - flag_edit_ = new QLineEdit(); - flag_layout->addWidget(flag_edit_, flag_row, 1); - - flag_row++; - - auto loop_cache = new QCheckBox(tr("Loop from Cache")); - loop_cache->setProperty("flag", 0x01); - connect(loop_cache, &QCheckBox::clicked, this, &MxObPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(loop_cache); - flag_layout->addWidget(loop_cache, flag_row, 0, 1, 2); - - flag_row++; - - auto no_loop = new QCheckBox(tr("No Loop")); - no_loop->setProperty("flag", 0x02); - connect(no_loop, &QCheckBox::clicked, this, &MxObPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(no_loop); - flag_layout->addWidget(no_loop, flag_row, 0, 1, 2); - - flag_row++; - - auto loop_stream = new QCheckBox(tr("Loop from Stream")); - loop_stream->setProperty("flag", 0x04); - connect(loop_stream, &QCheckBox::clicked, this, &MxObPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(loop_stream); - flag_layout->addWidget(loop_stream, flag_row, 0, 1, 2); - - flag_row++; - - auto transparent = new QCheckBox(tr("Transparent")); - transparent->setProperty("flag", 0x08); - connect(transparent, &QCheckBox::clicked, this, &MxObPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(transparent); - flag_layout->addWidget(transparent, flag_row, 0, 1, 2); - - flag_row++; - - auto unknown = new QCheckBox(tr("Unknown")); - unknown->setProperty("flag", 0x20); - connect(unknown, &QCheckBox::clicked, this, &MxObPanel::FlagCheckBoxClicked); - flag_checkboxes_.append(unknown); - flag_layout->addWidget(unknown, flag_row, 0, 1, 2); - - flag_row++; - - } - - layout()->addWidget(new QLabel(tr("Duration")), row, 0); - - duration_edit_ = new QSpinBox(); - duration_edit_->setMinimum(0); - duration_edit_->setMaximum(INT_MAX); - layout()->addWidget(duration_edit_, row, 1); - - row++; - - layout()->addWidget(new QLabel(tr("Loops")), row, 0); - - loops_edit_ = new QSpinBox(); - loops_edit_->setMinimum(0); - loops_edit_->setMaximum(INT_MAX); - layout()->addWidget(loops_edit_, row, 1); - - row++; - - auto pos_grp = new QGroupBox(tr("Position")); - auto pos_lyt = new QVBoxLayout(pos_grp); - pos_lyt->setMargin(0); - pos_edit_ = new Vector3Edit(); - pos_lyt->addWidget(pos_edit_); - layout()->addWidget(pos_grp, row, 0, 1, 2); - - row++; - - auto dir_grp = new QGroupBox(tr("Direction")); - auto dir_lyt = new QVBoxLayout(dir_grp); - dir_lyt->setMargin(0); - dir_edit_ = new Vector3Edit(); - dir_lyt->addWidget(dir_edit_); - layout()->addWidget(dir_grp, row, 0, 1, 2); - - row++; - - auto up_grp = new QGroupBox(tr("Up")); - auto up_lyt = new QVBoxLayout(up_grp); - up_lyt->setMargin(0); - up_edit_ = new Vector3Edit(); - up_lyt->addWidget(up_edit_); - layout()->addWidget(up_grp, row, 0, 1, 2); - - FinishLayout(); -} - -void MxObPanel::OnOpeningData(void *data) -{ - Chunk *chunk = static_cast<Chunk*>(data); - - type_combo_->setCurrentIndex(chunk->data("Type")); - name_edit_->setText(QString(chunk->data("Name"))); - filename_edit_->setText(QString(chunk->data("FileName"))); - presenter_edit_->setText(QString(chunk->data("Presenter"))); - obj_id_edit_->setValue(chunk->data("ID")); - - flag_edit_->setText(QString::number(chunk->data("Flags"), 16)); - - for (QCheckBox* cb : qAsConst(flag_checkboxes_)) { - cb->setChecked(cb->property("flag").toUInt() & chunk->data("Flags")); - } - - duration_edit_->setValue(chunk->data("Duration")); - loops_edit_->setValue(chunk->data("Loops")); - - pos_edit_->SetValue(chunk->data("Position")); - dir_edit_->SetValue(chunk->data("Direction")); - up_edit_->SetValue(chunk->data("Up")); -} - -void MxObPanel::OnClosingData(void *data) -{ - /*chunk->data("Type") = type_combo_->currentIndex(); - - bool ok; - u32 flags = flag_edit_->text().toUInt(&ok, 16); - if (ok) { - chunk->data("Flags") = flags; - } - - chunk->data("Duration") = duration_edit_->value(); - chunk->data("Loops") = loops_edit_->value(); - - chunk->data("Position") = pos_edit_->GetValue(); - chunk->data("Direction") = dir_edit_->GetValue(); - chunk->data("Up") = up_edit_->GetValue();*/ -} - -void MxObPanel::FlagCheckBoxClicked(bool e) -{ - uint32_t flag = sender()->property("flag").toUInt(); - bool ok; - uint32_t current = flag_edit_->text().toUInt(&ok, 16); - if (ok) { - if (e) { - current |= flag; - } - else { - current &= ~flag; - } - - flag_edit_->setText(QString::number(current, 16)); - } -} diff --git a/app/siview/panels/mxob.h b/app/siview/panels/mxob.h deleted file mode 100644 index 5db6584..0000000 --- a/app/siview/panels/mxob.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef MXOBPANEL_H -#define MXOBPANEL_H - -#include <QCheckBox> -#include <QComboBox> -#include <QLineEdit> -#include <QSpinBox> - -#include "panel.h" -#include "vector3edit.h" - -class MxObPanel : public Panel -{ - Q_OBJECT -public: - explicit MxObPanel(QWidget *parent = nullptr); - -protected: - virtual void OnOpeningData(void *data) override; - virtual void OnClosingData(void *data) override; - -private: - QComboBox *type_combo_; - QLineEdit *name_edit_; - QLineEdit *presenter_edit_; - QLineEdit *filename_edit_; - QSpinBox *obj_id_edit_; - - QLineEdit* flag_edit_; - - QSpinBox *duration_edit_; - QSpinBox *loops_edit_; - - Vector3Edit *pos_edit_; - Vector3Edit *dir_edit_; - Vector3Edit *up_edit_; - - QVector<QCheckBox*> flag_checkboxes_; - -private slots: - void FlagCheckBoxClicked(bool e); - -}; - -#endif // MXOBPANEL_H diff --git a/app/siview/panels/mxof.cpp b/app/siview/panels/mxof.cpp deleted file mode 100644 index 6ebd841..0000000 --- a/app/siview/panels/mxof.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "mxof.h" - -#include <chunk.h> -#include <QLabel> - -using namespace si; - -MxOfPanel::MxOfPanel(QWidget *parent) : - Panel(parent) -{ - int row = 0; - - layout()->addWidget(new QLabel(tr("Object Count")), row, 0); - - obj_count_edit_ = new QSpinBox(); - obj_count_edit_->setMinimum(0); - obj_count_edit_->setMaximum(INT_MAX); - layout()->addWidget(obj_count_edit_, row, 1); - - row++; - - list_ = new QListWidget(); - layout()->addWidget(list_, row, 0, 1, 2); - - FinishLayout(); -} - -void MxOfPanel::OnOpeningData(void *data) -{ - Chunk *chunk = static_cast<Chunk*>(data); - const Data &offsets_bytes = chunk->data("Offsets"); - const uint32_t *offsets = reinterpret_cast<const uint32_t*>(offsets_bytes.data()); - size_t offset_count = offsets_bytes.size() / sizeof(uint32_t); - - obj_count_edit_->setValue(chunk->data("Count")); - - for (size_t i=0; i<offset_count; i++) { - QString addr = QStringLiteral("0x%1").arg(offsets[i], 8, 16, QChar('0')); - - list_->addItem(QStringLiteral("%1: %2").arg(QString::number(i), addr)); - } -} - -void MxOfPanel::OnClosingData(void *data) -{ - list_->clear(); -} diff --git a/app/siview/panels/mxof.h b/app/siview/panels/mxof.h deleted file mode 100644 index f8c07cb..0000000 --- a/app/siview/panels/mxof.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MXOFPANEL_H -#define MXOFPANEL_H - -#include <QListWidget> -#include <QLineEdit> -#include <QSpinBox> - -#include "panel.h" - -class MxOfPanel : public Panel -{ - Q_OBJECT -public: - explicit MxOfPanel(QWidget *parent = nullptr); - -protected: - virtual void OnOpeningData(void *data) override; - virtual void OnClosingData(void *data) override; - -private: - QSpinBox *obj_count_edit_; - - QListWidget *list_; - -}; - -#endif // MXOFPANEL_H diff --git a/app/siview/panels/riff.cpp b/app/siview/panels/riff.cpp deleted file mode 100644 index 7d48362..0000000 --- a/app/siview/panels/riff.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "riff.h" - -#include <chunk.h> -#include <QLabel> - -using namespace si; - -RIFFPanel::RIFFPanel(QWidget *parent) : - Panel(parent) -{ - int row = 0; - - layout()->addWidget(new QLabel(tr("ID")), row, 0); - - id_edit_ = new QLineEdit(); - id_edit_->setMaxLength(sizeof(uint32_t)); - layout()->addWidget(id_edit_, row, 1); - - FinishLayout(); -} - -void RIFFPanel::OnOpeningData(void *data) -{ - Chunk *chunk = static_cast<Chunk*>(data); - QString s = QString::fromLatin1(chunk->data("Format").c_str(), sizeof(uint32_t)); - id_edit_->setText(s); -} - -void RIFFPanel::OnClosingData(void *data) -{ - /*QByteArray d = id_edit_->text().toLatin1(); - - const int target_sz = sizeof(u32); - if (d.size() != target_sz) { - int old_sz = d.size(); - d.resize(target_sz); - for (int i=old_sz; i<target_sz; i++) { - // Fill any empty space with spaces - d[i] = ' '; - } - } - - chunk->data("Format") = *(u32*)d.data();*/ -} diff --git a/app/siview/panels/riff.h b/app/siview/panels/riff.h deleted file mode 100644 index 4cdda78..0000000 --- a/app/siview/panels/riff.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef RIFFPANEL_H -#define RIFFPANEL_H - -#include <QLineEdit> - -#include "panel.h" - -class RIFFPanel : public Panel -{ - Q_OBJECT -public: - explicit RIFFPanel(QWidget *parent = nullptr); - -protected: - virtual void OnOpeningData(void *data) override; - virtual void OnClosingData(void *data) override; - -private: - QLineEdit *id_edit_; - -}; - -#endif // RIFFPANEL_H diff --git a/app/siview/siview.cpp b/app/siview/siview.cpp index d79e64b..c0c4768 100644 --- a/app/siview/siview.cpp +++ b/app/siview/siview.cpp @@ -8,12 +8,12 @@ using namespace si; -SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) : - QDialog(parent), +SIViewDialog::SIViewDialog(Info *riff, QWidget *parent) : + QWidget(parent, Qt::Window), root_(riff), last_set_data_(nullptr) { - setWindowTitle(mode == Import ? tr("Import SI File") : tr("Export SI File")); + setWindowTitle(tr("View SI File")); auto layout = new QVBoxLayout(this); @@ -31,95 +31,13 @@ SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) : config_stack_ = new QStackedWidget(); splitter->addWidget(config_stack_); - panel_blank_ = new Panel(); - config_stack_->addWidget(panel_blank_); + panel_ = new InfoPanel(); + config_stack_->addWidget(panel_); - panel_mxhd_ = new MxHdPanel(); - config_stack_->addWidget(panel_mxhd_); - - panel_riff_ = new RIFFPanel(); - config_stack_->addWidget(panel_riff_); - - panel_mxch_ = new MxChPanel(); - config_stack_->addWidget(panel_mxch_); - - panel_mxof_ = new MxOfPanel(); - config_stack_->addWidget(panel_mxof_); - - panel_mxob_ = new MxObPanel(); - config_stack_->addWidget(panel_mxob_); - - auto btn_layout = new QHBoxLayout(); - layout->addLayout(btn_layout); - - btn_layout->addStretch(); - - auto accept_btn = new QPushButton(mode == Import ? tr("De-Weave") : tr("Weave")); - accept_btn->setDefault(true); - connect(accept_btn, &QPushButton::clicked, this, &SIViewDialog::accept); - btn_layout->addWidget(accept_btn); - - auto reject_btn = new QPushButton(tr("Cancel")); - 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(); -} - -void SIViewDialog::SetPanel(Panel *panel, si::Chunk *chunk) -{ - auto current = static_cast<Panel*>(config_stack_->currentWidget()); - current->SetData(nullptr); - - config_stack_->setCurrentWidget(panel); - panel->SetData(chunk); - last_set_data_ = chunk; + splitter->setSizes({99999, 99999}); } void SIViewDialog::SelectionChanged(const QModelIndex &index) { - Panel *p = panel_blank_; - Chunk *c = static_cast<Chunk*>(index.internalPointer()); - - if (c) { - switch (c->type()) { - case Chunk::TYPE_MxHd: - p = panel_mxhd_; - break; - case Chunk::TYPE_RIFF: - case Chunk::TYPE_LIST: - p = panel_riff_; - break; - case Chunk::TYPE_MxCh: - p = panel_mxch_; - break; - case Chunk::TYPE_MxOf: - p = panel_mxof_; - break; - case Chunk::TYPE_MxOb: - p = panel_mxob_; - break; - case Chunk::TYPE_MxSt: - case Chunk::TYPE_pad_: - break; - } - } - - if (p != config_stack_->currentWidget() || c != last_set_data_) { - SetPanel(p, c); - } -} - -void SIViewDialog::ImmediateReweave() -{ - QString s = QFileDialog::getSaveFileName(this); - if (!s.isEmpty()) { - root_->Write(s.toStdString()); - } + panel_->SetData(static_cast<Info*>(index.internalPointer())); } diff --git a/app/siview/siview.h b/app/siview/siview.h index c4a3bcb..229888c 100644 --- a/app/siview/siview.h +++ b/app/siview/siview.h @@ -1,50 +1,36 @@ #ifndef SIVIEW_H #define SIVIEW_H +#include <interleaf.h> #include <QDialog> #include <QStackedWidget> #include "chunkmodel.h" -#include "panels/mxch.h" -#include "panels/mxhd.h" -#include "panels/mxob.h" -#include "panels/mxof.h" -#include "panels/riff.h" -#include "panel.h" +#include "infopanel.h" -class SIViewDialog : public QDialog +class SIViewDialog : public QWidget { Q_OBJECT public: - enum Mode { - Import, - Export - }; + SIViewDialog(si::Info *info, QWidget *parent = nullptr); - SIViewDialog(Mode mode, si::Chunk *riff, QWidget *parent = nullptr); + std::unique_ptr<si::Interleaf> temp; private: - void SetPanel(Panel *panel, si::Chunk *chunk); - QStackedWidget *config_stack_; ChunkModel chunk_model_; - Panel *panel_blank_; - RIFFPanel *panel_riff_; - MxHdPanel *panel_mxhd_; - MxChPanel *panel_mxch_; - MxOfPanel *panel_mxof_; - MxObPanel *panel_mxob_; + InfoPanel *panel_; - si::Chunk *last_set_data_; - si::Chunk *root_; + const si::Info *last_set_data_; + const si::Info *root_; + + std::unique_ptr<si::Interleaf> temp_interleaf_; private slots: void SelectionChanged(const QModelIndex &index); - void ImmediateReweave(); - }; #endif // SIVIEW_H diff --git a/app/viewer/bitmappanel.cpp b/app/viewer/bitmappanel.cpp index 6e4ab4f..5bfb441 100644 --- a/app/viewer/bitmappanel.cpp +++ b/app/viewer/bitmappanel.cpp @@ -27,12 +27,11 @@ BitmapPanel::BitmapPanel(QWidget *parent) void BitmapPanel::OnOpeningData(void *data) { si::Object *o = static_cast<si::Object*>(data); - - si::bytearray processed = o->GetNormalizedData(); - QByteArray b(processed.data(), processed.size()); - QBuffer buf(&b); + si::bytearray d = o->ExtractToMemory(); + QByteArray b(d.data(), d.size()); + QBuffer read_buf(&b); QImage img; - img.load(&buf, "BMP"); + img.load(&read_buf, "BMP"); img_lbl_->setPixmap(QPixmap::fromImage(img)); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c3a047d..f52d6bb 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,9 +1,9 @@ option(LIBWEAVER_BUILD_DOXYGEN "Build Doxygen documentation" OFF) set(LIBWEAVER_SOURCES - common.h core.cpp core.h + info.h interleaf.cpp interleaf.h object.cpp diff --git a/lib/common.h b/lib/common.h deleted file mode 100644 index aa87d0d..0000000 --- a/lib/common.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef LIBWEAVER_GLOBAL_H -#define LIBWEAVER_GLOBAL_H - -#if defined(__GNUC__) -#define LIBWEAVER_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) -#elif defined(_MSC_VER) -#define LIBWEAVER_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) -#endif - -#ifdef _MSC_VER -#define LIBWEAVER_EXPORT __declspec(dllexport) -#else -#define LIBWEAVER_EXPORT -#endif - -#if defined(_WIN32) -#define LIBWEAVER_OS_WINDOWS -#elif defined(__APPLE__) -#define LIBWEAVER_OS_MACOS -#elif defined(__linux__) -#define LIBWEAVER_OS_LINUX -#endif - -#endif // LIBWEAVER_GLOBAL_H diff --git a/lib/core.h b/lib/core.h index dcf0fb0..4db95ff 100644 --- a/lib/core.h +++ b/lib/core.h @@ -3,7 +3,6 @@ #include <vector> -#include "common.h" #include "types.h" namespace si { diff --git a/lib/info.h b/lib/info.h new file mode 100644 index 0000000..65e00aa --- /dev/null +++ b/lib/info.h @@ -0,0 +1,54 @@ +#ifndef INFO_H +#define INFO_H + +#include "core.h" + +namespace si { + +class Info : public Core +{ +public: + static const uint32_t NULL_OBJECT_ID = 0xFFFFFFFF; + + Info() + { + m_ObjectID = NULL_OBJECT_ID; + } + + void clear() + { + m_Desc.clear(); + DeleteChildren(); + } + + const uint32_t &GetType() const { return m_Type; } + void SetType(const uint32_t &t) { m_Type = t; } + + const uint32_t &GetOffset() const { return m_Offset; } + void SetOffset(const uint32_t &t) { m_Offset = t; } + + const uint32_t &GetObjectID() const { return m_ObjectID; } + void SetObjectID(const uint32_t &t) { m_ObjectID = t; } + + const uint32_t &GetSize() const { return m_Size; } + void SetSize(const uint32_t &t) { m_Size = t; } + + const std::string &GetDescription() const { return m_Desc; } + void SetDescription(const std::string &d) { m_Desc = d; } + + const bytearray &GetData() const { return m_Data; } + void SetData(const bytearray &d) { m_Data = d; } + +private: + uint32_t m_Type; + uint32_t m_Offset; + uint32_t m_Size; + uint32_t m_ObjectID; + std::string m_Desc; + bytearray m_Data; + +}; + +} + +#endif // INFO_H diff --git a/lib/interleaf.cpp b/lib/interleaf.cpp index e906513..538e234 100644 --- a/lib/interleaf.cpp +++ b/lib/interleaf.cpp @@ -2,6 +2,7 @@ #include <cmath> #include <iostream> +#include <sstream> #include "object.h" #include "othertypes.h" @@ -10,170 +11,220 @@ namespace si { +static const uint32_t kMinimumChunkSize = 8; + Interleaf::Interleaf() { } void Interleaf::Clear() { + m_Info.clear(); m_BufferSize = 0; - m_OffsetTable.clear(); - m_ObjectIndexTable.clear(); + m_ObjectIDTable.clear(); + m_ObjectList.clear(); DeleteChildren(); } -bool Interleaf::Read(const char *f) +Interleaf::Error Interleaf::Read(const char *f) { std::ifstream is(f); + if (!is.is_open() || !is.good()) { + return ERROR_IO; + } + return Read(is); +} + +Interleaf::Error Interleaf::Write(const char *f) const +{ + std::ofstream os(f); + if (!os.is_open() || !os.good()) { + return ERROR_IO; + } + return Write(os); +} + +#ifdef _WIN32 +Interleaf::Error Interleaf::Read(const wchar_t *f) +{ + std::istream is(f); if (!is.is_open() || !is.good()) { return false; } return Read(is); } -bool Interleaf::Read(const wchar_t *f) +Interleaf::Error Interleaf::Write(const wchar_t *f) const { - std::ifstream is(f); - if (!is.is_open() || !is.good()) { - return false; - } - return Read(is); -} - -/*bool Interleaf::Write(const char *f) const -{ - std::ofstream os(f); + std::ostream os(f); if (!os.is_open() || !os.good()) { return false; } return Write(os); } +#endif -bool Interleaf::Write(const wchar_t *f) const -{ - std::ofstream os(f); - if (!os.is_open() || !os.good()) { - return false; - } - return Write(os); -}*/ - -inline std::string PrintU32AsString(uint32_t u) -{ - return std::string((const char *) &u, sizeof(u)); -} - -bool Interleaf::ReadChunk(Core *parent, std::ifstream &is) +Interleaf::Error Interleaf::ReadChunk(Core *parent, std::istream &is, Info *info) { uint32_t offset = is.tellg(); uint32_t id = ReadU32(is); uint32_t size = ReadU32(is); uint32_t end = uint32_t(is.tellg()) + size; - std::cout << PrintU32AsString(id) - << " Offset: 0x" << std::hex << offset - << " Size: " << std::dec << size << std::endl; + info->SetType(id); + info->SetOffset(offset); + info->SetSize(size); - switch (static_cast<SI::Type>(id)) { - case SI::RIFF: + std::stringstream desc; + + switch (static_cast<RIFF::Type>(id)) { + case RIFF::RIFF_: { // Require RIFF type to be OMNI uint32_t riff_type = ReadU32(is); - std::cout << " Type: " << PrintU32AsString(riff_type) << std::endl; if (riff_type != RIFF::OMNI) { - return false; + return ERROR_INVALID_INPUT; } + + desc << "Type: " << RIFF::PrintU32AsString(riff_type); break; } - case SI::MxHd: + case RIFF::MxHd: { m_Version = ReadU32(is); - std::cout << " Version: " << m_Version << std::endl; + desc << "Version: " << m_Version << std::endl; m_BufferSize = ReadU32(is); - std::cout << " Buffer Size: " << m_BufferSize << std::endl; + desc << "Buffer Size: 0x" << std::hex << m_BufferSize; - if (m_Version < 0x00020002) { + if (m_Version == 0x00020002) { m_BufferCount = ReadU32(is); - std::cout << " Buffer Count: " << m_BufferCount << std::endl; + desc << std::endl << "Buffer Count: " << std::dec << m_BufferCount << std::endl; } break; } - case SI::pad_: + case RIFF::pad_: is.seekg(size, std::ios::cur); break; - case SI::MxOf: + case RIFF::MxOf: { - m_OffsetCount = ReadU32(is); - std::cout << " Count: " << m_OffsetCount << std::endl; + uint32_t offset_count = ReadU32(is); - uint32_t i = 0; - while (is.tellg() < end) { - uint32_t offset = ReadU32(is); - std::cout << " " << i << ": 0x" << std::hex << offset << std::endl; - m_OffsetTable.push_back(offset); - i++; + desc << "Count: " << offset_count; + + uint32_t real_count = (size - sizeof(uint32_t)) / sizeof(uint32_t); + m_ObjectList.resize(real_count); + for (uint32_t i = 0; i < real_count; i++) { + Object *o = new Object(); + parent->AppendChild(o); + + uint32_t choffset = ReadU32(is); + m_ObjectList[i] = choffset; + desc << std::endl << i << ": 0x" << std::hex << choffset; } break; } - case SI::LIST: + case RIFF::LIST: { uint32_t list_type = ReadU32(is); - std::cout << " Type: " << PrintU32AsString(list_type) << std::endl; + desc << "Type: " << RIFF::PrintU32AsString(list_type) << std::endl; uint32_t list_count = 0; - if (list_type == SI::MxCh) { + if (list_type == RIFF::MxCh) { list_count = ReadU32(is); - std::cout << " Count: " << list_count << std::endl; + desc << "Count: " << list_count << std::endl; } break; } - case SI::MxSt: - case SI::MxDa: + case RIFF::MxSt: + case RIFF::MxDa: + case RIFF::WAVE: + case RIFF::fmt_: + case RIFF::data: + case RIFF::OMNI: // Types with no data break; - case SI::MxOb: + case RIFF::MxOb: { - Object *o = ReadObject(is); - parent->AppendChild(o); - m_ObjectIndexTable[o->id()] = o; + Object *o = NULL; + + for (size_t i=0; i<m_ObjectList.size(); i++) { + if (m_ObjectList[i] == offset-kMinimumChunkSize) { + o = static_cast<Object*>(GetChildAt(i)); + break; + } + } + + if (!o) { + o = new Object(); + parent->AppendChild(o); + } + + ReadObject(is, o, desc); + + info->SetObjectID(o->id()); + + m_ObjectIDTable[o->id()] = o; + parent = o; break; } - case SI::MxCh: + case RIFF::MxCh: { uint16_t flags = ReadU16(is); + desc << "Flags: 0x" << std::hex << flags << std::endl; + uint32_t object = ReadU32(is); + desc << "Object: " << std::dec << object << std::endl; + uint32_t time = ReadU32(is); + desc << "Time: " << time << std::endl; + uint32_t data_sz = ReadU32(is); + desc << "Size: " << data_sz << std::endl; + bytearray data = ReadBytes(is, size - MxCh::HEADER_SIZE); - Object *o = m_ObjectIndexTable.at(object); - if (!o) { - return false; - } - o->data_.push_back(data); - break; - } - } + info->SetObjectID(object); + info->SetData(data); - std::cout << "Reading children at 0x" << std::hex << is.tellg() << std::endl; + if (!(flags & MxCh::FLAG_END)) { + Object *o = m_ObjectIDTable.at(object); + if (!o) { + return ERROR_INVALID_INPUT; + } + + if (flags & MxCh::FLAG_SPLIT && o->last_chunk_split_) { + o->data_.back().append(data); + } else { + o->data_.push_back(data); + o->last_chunk_split_ = (flags & MxCh::FLAG_SPLIT); + } + break; + } + } + } // Assume any remaining data is this chunk's children - while (is.good() && (size_t(is.tellg()) + 4) < end) { + while (is.good() && (size_t(is.tellg()) + kMinimumChunkSize) < end) { // Check alignment, if there's not enough room to for another segment, skip ahead if (m_BufferSize > 0) { uint32_t offset_in_buffer = is.tellg()%m_BufferSize; - if (offset_in_buffer + sizeof(uint32_t)*2 > m_BufferSize) { + if (offset_in_buffer + kMinimumChunkSize > m_BufferSize) { is.seekg(m_BufferSize-offset_in_buffer, std::ios::cur); } } // Read next child - if (!ReadChunk(parent, is)) { - return false; + Info *subinfo = new Info(); + info->AppendChild(subinfo); + Error e = ReadChunk(parent, is, subinfo); + if (e != ERROR_SUCCESS) { + return e; } } + info->SetDescription(desc.str()); + if (is.tellg() < end) { is.seekg(end, std::ios::beg); } @@ -182,315 +233,294 @@ bool Interleaf::ReadChunk(Core *parent, std::ifstream &is) is.seekg(1, std::ios::cur); } - return true; + return ERROR_SUCCESS; } -Object *Interleaf::ReadObject(std::ifstream &is) +Object *Interleaf::ReadObject(std::istream &is, Object *o, std::stringstream &desc) { - Object *o = new Object(); - o->type_ = static_cast<MxOb::Type>(ReadU16(is)); - std::cout << " Type: " << o->type_ << std::endl; + desc << "Type: " << o->type_ << std::endl; o->presenter_ = ReadString(is); - std::cout << " Presenter: " << o->presenter_ << std::endl; + desc << "Presenter: " << o->presenter_ << std::endl; o->unknown1_ = ReadU32(is); - std::cout << " Unknown1: " << o->unknown1_ << std::endl; + desc << "Unknown1: " << o->unknown1_ << std::endl; o->name_ = ReadString(is); - std::cout << " Name: " << o->name_ << std::endl; + desc << "Name: " << o->name_ << std::endl; o->id_ = ReadU32(is); - std::cout << " ID: " << o->id_ << std::endl; + desc << "ID: " << o->id_ << std::endl; o->flags_ = ReadU32(is); - std::cout << " Flags: " << o->flags_ << std::endl; + desc << "Flags: " << o->flags_ << std::endl; o->unknown4_ = ReadU32(is); - std::cout << " Unknown4: " << o->unknown4_ << std::endl; + desc << "Unknown4: " << o->unknown4_ << std::endl; o->duration_ = ReadU32(is); - std::cout << " Duration: " << o->duration_ << std::endl; + desc << "Duration: " << o->duration_ << std::endl; o->loops_ = ReadU32(is); - std::cout << " Loops: " << o->loops_ << std::endl; + desc << "Loops: " << o->loops_ << std::endl; o->position_ = ReadVector3(is); - std::cout << " Position: " << o->position_.x << " " << o->position_.y << " " << o->position_.z << std::endl; + desc << "Position: " << o->position_.x << " " << o->position_.y << " " << o->position_.z << std::endl; o->direction_ = ReadVector3(is); - std::cout << " Direction: " << o->direction_.x << " " << o->direction_.y << " " << o->direction_.z << std::endl; + desc << "Direction: " << o->direction_.x << " " << o->direction_.y << " " << o->direction_.z << std::endl; o->up_ = ReadVector3(is); - std::cout << " Up: " << o->up_.x << " " << o->up_.y << " " << o->up_.z << std::endl; + desc << "Up: " << o->up_.x << " " << o->up_.y << " " << o->up_.z << std::endl; uint16_t extra_sz = ReadU16(is); - std::cout << " Extra Size: " << extra_sz << std::endl; + desc << "Extra Size: " << extra_sz << std::endl; o->extra_ = ReadBytes(is, extra_sz); if (o->type_ != MxOb::Presenter && o->type_ != MxOb::World) { o->filename_ = ReadString(is); - std::cout << " Filename: " << o->filename_ << std::endl; + desc << "Filename: " << o->filename_ << std::endl; o->unknown26_ = ReadU32(is); - std::cout << " Unknown26: " << o->unknown26_ << std::endl; + desc << "Unknown26: " << o->unknown26_ << std::endl; o->unknown27_ = ReadU32(is); - std::cout << " Unknown27: " << o->unknown27_ << std::endl; + desc << "Unknown27: " << o->unknown27_ << std::endl; o->unknown28_ = ReadU32(is); - std::cout << " Unknown28: " << o->unknown28_ << std::endl; + desc << "Unknown28: " << o->unknown28_ << std::endl; o->filetype_ = static_cast<MxOb::FileType>(ReadU32(is)); - std::cout << " File Type: " << PrintU32AsString(o->filetype_) << std::endl; + desc << "File Type: " << RIFF::PrintU32AsString(o->filetype_) << std::endl; o->unknown29_ = ReadU32(is); - std::cout << " Unknown29: " << o->unknown29_ << std::endl; + desc << "Unknown29: " << o->unknown29_ << std::endl; o->unknown30_ = ReadU32(is); - std::cout << " Unknown30: " << o->unknown30_ << std::endl; + desc << "Unknown30: " << o->unknown30_ << std::endl; if (o->filetype_ == MxOb::WAV) { o->unknown31_ = ReadU32(is); - std::cout << " Unknown31: " << o->unknown31_ << std::endl; + desc << "Unknown31: " << o->unknown31_ << std::endl; } } - //std::cout << "Next ID: 0x" << std::hex << is.tellg() << " "; - //std::cout << PrintU32AsString(ReadU32(is)); - //std::cout << " After: 0x" << std::hex << is.tellg() << std::endl; - //is.seekg(-4, std::ios::cur); - return o; } -bool Interleaf::Read(std::ifstream &is) +Interleaf::Error Interleaf::Read(std::istream &is) { Clear(); - return ReadChunk(this, is); + return ReadChunk(this, is, &m_Info); } -/*bool Interleaf::Parse(Chunk *riff) +Interleaf::Error Interleaf::Write(std::ostream &os) const { - if (riff->id() != Chunk::TYPE_RIFF) { - return false; + if (m_BufferSize == 0) { + LogError() << "Buffer size must be set to write" << std::endl; + return ERROR_INVALID_BUFFER_SIZE; } - Chunk *hd = riff->FindChildWithType(Chunk::TYPE_MxHd); - if (!hd) { - return false; - } + RIFF::Chk riff = RIFF::BeginChunk(os, RIFF::RIFF_); + WriteU32(os, RIFF::OMNI); - version_ = hd->data("Version"); - if (version_ == 0) { - // Unknown version - return false; - } + std::ios::pos_type offset_table_pos; - buffer_size_ = hd->data("BufferSize"); - buffer_count_ = hd->data("BufferCount"); + { + // MxHd + RIFF::Chk mxhd = RIFF::BeginChunk(os, RIFF::MxHd); - Chunk *of = riff->FindChildWithType(Chunk::TYPE_MxOf); - if (!of) { - return false; - } + WriteU32(os, m_Version); + WriteU32(os, m_BufferSize); - const Data &offset_data = of->data("Offsets"); - const uint32_t *offset_table = reinterpret_cast<const uint32_t *>(offset_data.data()); - size_t offset_count = offset_data.size() / sizeof(uint32_t); - DeleteChildren(); - for (size_t i=0; i<offset_count; i++) { - if (offset_table[i]) { - Chunk *st = riff->FindChildWithOffset(offset_table[i]); - if (!ParseStream(st)) { - return false; - } - } else { - Object *nullobj = new Object(); - AppendChild(nullobj); + if (m_Version == 0x00020002) { + WriteU32(os, m_BufferCount); } + + RIFF::EndChunk(os, mxhd); } - return true; -} + { + // MxOf + RIFF::Chk mxof = RIFF::BeginChunk(os, RIFF::MxOf); -Chunk *Interleaf::Export() const -{ - Chunk *riff = new Chunk(Chunk::TYPE_RIFF); - riff->data("Format") = RIFF::OMNI; + WriteU32(os, GetChildCount()); - Chunk *mxhd = new Chunk(Chunk::TYPE_MxHd); - mxhd->data("Version") = version_; - mxhd->data("BufferSize") = buffer_size_; - mxhd->data("BufferCount") = buffer_count_; - riff->AppendChild(mxhd); + offset_table_pos = os.tellp(); - Chunk *mxof = new Chunk(Chunk::TYPE_MxOf); + for (size_t i = 0; i < GetChildCount(); i++) { + WriteU32(os, 0); + } - // FIXME: This appears to not always be correct, sometimes an MxOf with only one entry will have - // a count of 3, seemingly due to embedded objects (e.g. a movie with an SMK + WAV)? - uint32_t obj_count = this->GetChildCount(); - for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) { - Object *obj = static_cast<Object*>(*it); - obj_count += obj->GetChildCount(); - } - mxof->data("Count") = obj_count; - - // This however is correct. - mxof->data("Offsets") = bytearray(this->GetChildCount() * sizeof(uint32_t)); - - riff->AppendChild(mxof); - - Chunk *list = new Chunk(Chunk::TYPE_LIST); - list->data("Format") = Chunk::TYPE_MxSt; - riff->AppendChild(list); - - for (Children::const_iterator it=GetChildren().begin(); it!=GetChildren().end(); it++) { - list->AppendChild(ExportStream(static_cast<Object*>(*it))); + RIFF::EndChunk(os, mxof); } - // FIXME: Fill in MxOf table - // FIXME: Split MxCh chunks over alignment + { + // LIST + RIFF::Chk list_mxst = RIFF::BeginChunk(os, RIFF::LIST); - return riff; -} + WriteU32(os, RIFF::MxSt); -bool Interleaf::ParseStream(Chunk *chunk) -{ - if (chunk->type() != Chunk::TYPE_MxSt) { - return false; - } + for (size_t i = 0; i < GetChildCount(); i++) { + Object *child = static_cast<Object*>(GetChildAt(i)); - Chunk *obj_chunk = static_cast<Chunk*>(chunk->GetChildAt(0)); - if (!obj_chunk) { - return false; - } + uint32_t mxst_offset = os.tellp(); - Object *obj = new Object(); - if (!obj->Parse(obj_chunk)) { - return false; - } + os.seekp(size_t(offset_table_pos) + i * sizeof(uint32_t)); + WriteU32(os, mxst_offset); + os.seekp(mxst_offset); - AppendChild(obj); + // MxSt + RIFF::Chk mxst = RIFF::BeginChunk(os, RIFF::MxSt); - Chunk *list = static_cast<Chunk*>(chunk->GetChildAt(1)); - if (list) { - typedef std::map<uint32_t, Object::ChunkedData> ChunkMap; - ChunkMap data; + { + // MxOb + WriteObject(os, child); + } - uint32_t joining_chunk = 0; + { + // LIST + RIFF::Chk list_mxda = RIFF::BeginChunk(os, RIFF::LIST); - for (Children::const_iterator it=list->GetChildren().begin(); it!=list->GetChildren().end(); it++) { - Chunk *mxch = static_cast<Chunk*>(*it); - if (mxch->id() == Chunk::TYPE_pad_) { - // Ignore this chunk - } else if (mxch->id() == Chunk::TYPE_MxCh) { - uint16_t flags = mxch->data("Flags"); - if (!(flags & MxCh::FLAG_END)) { - uint32_t obj_id = mxch->data("Object"); - const Data &chunk_data = mxch->data("Data"); + WriteU32(os, RIFF::MxDa); - // For split chunks, join them together - if (joining_chunk > 0) { - data[obj_id].back().append(chunk_data); - if (data[obj_id].back().size() == joining_chunk) { - joining_chunk = 0; - } - } else { - if (flags & MxCh::FLAG_SPLIT) { - joining_chunk = mxch->data("DataSize"); - } - - data[obj_id].push_back(chunk_data); - } + // First, interleave headers + std::vector<Object*> objects; + objects.reserve(child->GetChildCount() + 1); + objects.push_back(child); + for (size_t j=0; j<child->GetChildCount(); j++) { + objects.push_back(static_cast<Object*>(child->GetChildAt(j))); } + + InterleaveObjects(os, objects); + + RIFF::EndChunk(os, list_mxda); } + + RIFF::EndChunk(os, mxst); } - for (ChunkMap::const_iterator it=data.begin(); it!=data.end(); it++) { - Object *o = obj->FindSubObjectWithID(it->first); - if (o) { - o->SetChunkedData(it->second); - } else { - std::cout << "Failed to find object with ID " << it->first << std::endl; - } + // Fill remainder with padding + if (os.tellp()%m_BufferSize != 0) { + uint32_t current_buf = os.tellp() / m_BufferSize; + uint32_t target_sz = (current_buf + 1) * m_BufferSize; + + WritePadding(os, target_sz - os.tellp()); + } + + RIFF::EndChunk(os, list_mxst); + } + + RIFF::EndChunk(os, riff); + + return ERROR_SUCCESS; +} + +void Interleaf::WriteObject(std::ostream &os, const Object *o) const +{ + RIFF::Chk mxob = RIFF::BeginChunk(os, RIFF::MxOb); + + WriteU16(os, o->type_); + WriteString(os, o->presenter_); + WriteU32(os, o->unknown1_); + WriteString(os, o->name_); + WriteU32(os, o->id_); + WriteU32(os, o->flags_); + WriteU32(os, o->unknown4_); + WriteU32(os, o->duration_); + WriteU32(os, o->loops_); + WriteVector3(os, o->position_); + WriteVector3(os, o->direction_); + WriteVector3(os, o->up_); + + WriteU16(os, o->extra_.size()); + WriteBytes(os, o->extra_); + + if (o->type_ != MxOb::Presenter && o->type_ != MxOb::World) { + WriteString(os, o->filename_); + WriteU32(os, o->unknown26_); + WriteU32(os, o->unknown27_); + WriteU32(os, o->unknown28_); + WriteU32(os, o->filetype_); + WriteU32(os, o->unknown29_); + WriteU32(os, o->unknown30_); + + if (o->filetype_ == MxOb::WAV) { + WriteU32(os, o->unknown31_); } } - return true; + if (o->HasChildren()) { + // Child list + RIFF::Chk list_mxch = RIFF::BeginChunk(os, RIFF::LIST); + + WriteU32(os, RIFF::MxCh); + WriteU32(os, o->GetChildCount()); + + for (size_t i = 0; i < o->GetChildCount(); i++) { + WriteObject(os, static_cast<Object*>(o->GetChildAt(i))); + } + + RIFF::EndChunk(os, list_mxch); + } + + RIFF::EndChunk(os, mxob); } 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 +void Interleaf::InterleaveObjects(std::ostream &os, const std::vector<Object *> &objects) const { - Chunk *mxst = new Chunk(Chunk::TYPE_MxSt); + std::vector<ChunkStatus> status(objects.size()); - 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)); + // Set up status vector + for (size_t i=0; i<objects.size(); i++) { + status[i].object = objects.at(i); + status[i].index = 0; + status[i].time = 0; + status[i].end_chunk = false; } - // 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())); + // First, interleave headers + for (size_t i=0; i<status.size(); i++) { + ChunkStatus &s = status[i]; + Object *o = s.object; + if (!o->data().empty()) { + WriteSubChunk(os, 0, o->id(), 0xFFFFFFFF, o->data().front()); + s.index++; } - it->index++; } - // Next, interleave everything by time + // Next, interleave the rest based on time while (true) { // Find next chunk - ChunkStatus *status = NULL; + ChunkStatus *s = NULL; - for (std::vector<ChunkStatus>::iterator it=chunk_status.begin(); it!=chunk_status.end(); it++) { + for (std::vector<ChunkStatus>::iterator it=status.begin(); it!=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); + if (!s || it->time < s->time) { + s = &(*it); } } - if (!status) { + if (!s) { // Assume chunks are all done break; } - Object *working_obj = status->object; - const bytearray &data = working_obj->data().at(status->index); + Object *obj = s->object; + const bytearray &data = obj->data().at(s->index); - chunklst->AppendChild(ExportMxCh(0, working_obj->id(), status->time, data)); + WriteSubChunk(os, 0, obj->id(), s->time, data); - status->index++; + s->index++; // Increment time - switch (working_obj->filetype()) { + switch (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; + const WAVFmt *fmt = obj->GetFileHeader().cast<WAVFmt>(); + s->time += round(double(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 frame_rate = obj->GetFileHeader().cast<SMK2>()->FrameRate; int32_t fps; if (frame_rate > 0) { fps = 1000/frame_rate; @@ -499,37 +529,64 @@ Chunk *Interleaf::ExportStream(Object *obj) const } else { fps = 10; } - status->time += 1000/fps; + s->time += 1000/fps; break; } case MxOb::FLC: - status->time += working_obj->GetFileHeader().cast<FLIC>()->speed; + s->time += 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; + for (size_t i=1; i<status.size(); i++) { + const ChunkStatus &s = status.at(i); + WriteSubChunk(os, MxCh::FLAG_END, s.object->id(), s.time); + } + WriteSubChunk(os, MxCh::FLAG_END, status.front().object->id(), status.front().time); } -Chunk *Interleaf::ExportMxCh(uint16_t flags, uint32_t object_id, uint32_t time, const bytearray &data) const +void Interleaf::WriteSubChunk(std::ostream &os, uint16_t flags, uint32_t object, 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; -}*/ + uint32_t total_sz = data.size() + MxCh::HEADER_SIZE + kMinimumChunkSize; + + uint32_t start_buffer = os.tellp() / m_BufferSize; + uint32_t stop_buffer = (uint32_t(os.tellp()) + total_sz) / m_BufferSize; + + if (start_buffer != stop_buffer) { + // This chunk won't fit in our buffer alignment. We must make a decision to either insert + // padding or split the clip. + WritePadding(os, (stop_buffer * m_BufferSize) - os.tellp()); + } + + RIFF::Chk mxch = RIFF::BeginChunk(os, RIFF::MxCh); + + WriteU16(os, flags); + WriteU32(os, object); + WriteU32(os, time); + WriteU32(os, data.size()); + WriteBytes(os, data); + + RIFF::EndChunk(os, mxch); +} + +void Interleaf::WritePadding(std::ostream &os, uint32_t size) const +{ + if (size < kMinimumChunkSize) { + return; + } + + size -= kMinimumChunkSize; + + WriteU32(os, RIFF::pad_); + WriteU32(os, size); + + bytearray b(size); + b.fill(0xCD); + WriteBytes(os, b); +} } diff --git a/lib/interleaf.h b/lib/interleaf.h index b74c007..42ed232 100644 --- a/lib/interleaf.h +++ b/lib/interleaf.h @@ -4,6 +4,7 @@ #include <fstream> #include "core.h" +#include "info.h" #include "object.h" namespace si { @@ -11,32 +12,50 @@ namespace si { class Interleaf : public Core { public: + enum Error + { + ERROR_SUCCESS, + ERROR_IO, + ERROR_INVALID_INPUT, + ERROR_INVALID_BUFFER_SIZE + }; + LIBWEAVER_EXPORT Interleaf(); LIBWEAVER_EXPORT void Clear(); - LIBWEAVER_EXPORT bool Read(const char *f); - LIBWEAVER_EXPORT bool Read(const wchar_t *f); + LIBWEAVER_EXPORT Error Read(const char *f); + LIBWEAVER_EXPORT Error Write(const char *f) const; - //LIBWEAVER_EXPORT bool Write(const char *f) const; - //LIBWEAVER_EXPORT bool Write(const wchar_t *f) const; +#ifdef _WIN32 + LIBWEAVER_EXPORT Error Read(const wchar_t *f); + LIBWEAVER_EXPORT Error Write(const wchar_t *f) const; +#endif + + Error Read(std::istream &is); + Error Write(std::ostream &os) const; + + Info *GetInformation() { return &m_Info; } private: - bool Read(std::ifstream &is); - //bool Write(std::ofstream &os) const; - bool ReadChunk(Core *parent, std::ifstream &is); + Error ReadChunk(Core *parent, std::istream &is, Info *info); - Object *ReadObject(std::ifstream &is); + Object *ReadObject(std::istream &is, Object *o, std::stringstream &desc); + void WriteObject(std::ostream &os, const Object *o) const; + + void InterleaveObjects(std::ostream &os, const std::vector<Object*> &objects) const; + void WriteSubChunk(std::ostream &os, uint16_t flags, uint32_t object, uint32_t time, const bytearray &data = bytearray()) const; + void WritePadding(std::ostream &os, uint32_t size) const; + + Info m_Info; uint32_t m_Version; uint32_t m_BufferSize; uint32_t m_BufferCount; - uint32_t m_OffsetCount; - std::vector<uint32_t> m_OffsetTable; - - std::map<uint32_t, Object*> m_ObjectIndexTable; + std::vector<uint32_t> m_ObjectList; + std::map<uint32_t, Object*> m_ObjectIDTable; }; diff --git a/lib/object.cpp b/lib/object.cpp index 91c242c..d9e7a05 100644 --- a/lib/object.cpp +++ b/lib/object.cpp @@ -2,6 +2,7 @@ #include <iostream> +#include "othertypes.h" #include "util.h" namespace si { @@ -10,166 +11,213 @@ Object::Object() { type_ = MxOb::Null; id_ = 0; + last_chunk_split_ = false; } -/*bool Object::Read(std::ifstream &is) +bool Object::ReplaceWithFile(const char *f) { - - - if (chunk->HasChildren()) { - Chunk *child = static_cast<Chunk*>(chunk->GetChildAt(0)); - if (child->id() == Chunk::TYPE_LIST) { - for (Children::const_iterator it=child->GetChildren().begin(); it!=child->GetChildren().end(); it++) { - Object *o = new Object(); - if (!o->Parse(static_cast<Chunk*>(*it))) { - return false; - } - AppendChild(o); - } - } + std::ifstream is(f); + if (!is.is_open() || !is.good()) { + return false; } - - return true; + return ReplaceWithFile(is); } -void Object::Write(std::ofstream &os) const +bool Object::ExtractToFile(const char *f) const { - WriteU16(os, type_); - WriteString(os, presenter_); - WriteU32(os, unknown1_); - WriteString(os, name_); - WriteU32(os, id_); - WriteU32(os, flags_); - WriteU32(os, unknown4_); - WriteU32(os, duration_); - WriteU32(os, loops_); - WriteVector3(os, position_); - WriteVector3(os, direction_); - WriteVector3(os, up_); - WriteU16(os, extra_.size()); - WriteBytes(os, extra_); - WriteString(os, filename_); - WriteU32(os, unknown26_); - WriteU32(os, unknown27_); - WriteU32(os, unknown28_); - WriteU32(os, filetype_); - WriteU32(os, unknown29_); - WriteU32(os, unknown30_); - WriteU32(os, unknown31_); - - 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++) { - Object *child = static_cast<Object*>(*it); - list->AppendChild(child->Export()); - } + std::ofstream os(f); + if (!os.is_open() || !os.good()) { + return false; } - - return chunk; -}*/ - -bytearray Object::GetNormalizedData() const -{ - return ToPackedData(filetype(), data_); + return ExtractToFile(os); } -void Object::SetNormalizedData(const bytearray &d) +bool Object::ReplaceWithFile(std::istream &is) { - SetChunkedData(ToChunkedData(filetype(), d)); -} + data_.clear(); -bytearray Object::ToPackedData(MxOb::FileType filetype, const ChunkedData &chunks) -{ - bytearray data; - - switch (filetype) { + switch (this->filetype()) { case MxOb::WAV: { - // Make space for WAVE header - data.resize(0x2C); - - // Merge all chunks after the first one - for (size_t i=1; i<chunks.size(); i++) { - data.append(chunks[i]); + if (ReadU32(is) != RIFF::RIFF_) { + return false; } - // Copy boilerplate bytes for header - uint32_t *header = reinterpret_cast<uint32_t *>(data.data()); - header[0] = SI::RIFF; // "RIFF" - header[1] = data.size() - 8; // Size of total file - header[2] = 0x45564157; // "WAVE" - header[3] = 0x20746D66; // "fmt " - header[4] = 16; // Size of fmt chunk - header[9] = 0x61746164; // "data" - header[10] = data.size() - 0x2C; // Size of data chunk + // Skip total size + ReadU32(is); - // Copy fmt header from chunk 1 - memcpy(&header[5], chunks[0].data(), 16); + if (ReadU32(is) != RIFF::WAVE) { + return false; + } + + bytearray fmt; + bytearray data; + + while (is.good()) { + uint32_t id = ReadU32(is); + uint32_t sz = ReadU32(is); + if (id == RIFF::fmt_) { + fmt.resize(sz); + is.read(fmt.data(), fmt.size()); + } else if (id == RIFF::data) { + data.resize(sz); + is.read(data.data(), data.size()); + } else { + is.seekg(sz, std::ios::cur); + } + } + + if (fmt.empty() || data.empty()) { + return false; + } + + data_.push_back(fmt); + WAVFmt *fmt_info = fmt.cast<WAVFmt>(); + size_t second_in_bytes = fmt_info->Channels * fmt_info->SampleRate * (fmt_info->BitsPerSample/8); + size_t max; + for (size_t i=0; i<data.size(); i+=max) { + max = std::min(data.size() - i, second_in_bytes); + data_.push_back(bytearray(data.data() + i, max)); + } + + return true; + } + case MxOb::SMK: + { + // Read header + bytearray hdr(sizeof(SMK2)); + is.read(hdr.data(), hdr.size()); + + // Read frame sizes + SMK2 smk = *hdr.cast<SMK2>(); + bytearray frame_sizes(smk.Frames * sizeof(uint32_t)); + is.read(frame_sizes.data(), frame_sizes.size()); + hdr.append(frame_sizes); + + // Read frame types + bytearray frame_types(smk.Frames); + is.read(frame_types.data(), frame_types.size()); + hdr.append(frame_types); + + // Read Huffman trees + bytearray huffman(smk.TreesSize); + is.read(huffman.data(), huffman.size()); + hdr.append(huffman); + + // Place header into data vector + data_.resize(smk.Frames + 1); + data_[0] = hdr; + + uint32_t *real_sizes = frame_sizes.cast<uint32_t>(); + for (uint32_t i=0; i<smk.Frames; i++) { + uint32_t sz = real_sizes[i]; + if (sz > 0) { + bytearray &d = data_[i+1]; + d.resize(sz); + is.read(d.data(), d.size()); + } + } + return true; + } + default: + LogWarning() << "Don't yet know how to chunk type " << RIFF::PrintU32AsString(this->filetype()) << std::endl; + break; + } + + return false; +} + +bool Object::ExtractToFile(std::ostream &os) const +{ + switch (this->filetype()) { + case MxOb::WAV: + { + // Write RIFF header + RIFF::Chk riff = RIFF::BeginChunk(os, RIFF::RIFF_); + + WriteU32(os, RIFF::WAVE); + + { + RIFF::Chk fmt = RIFF::BeginChunk(os, RIFF::fmt_); + + WriteBytes(os, data_.at(0)); + + RIFF::EndChunk(os, fmt); + } + + { + RIFF::Chk data = RIFF::BeginChunk(os, RIFF::data); + // Merge all chunks after the first one + for (size_t i=1; i<data_.size(); i++) { + WriteBytes(os, data_.at(i)); + } + RIFF::EndChunk(os, data); + } + + RIFF::EndChunk(os, riff); break; } case MxOb::STL: { - // Make space for BMP header - data.resize(14); + static const uint32_t BMP_HDR_SZ = 14; - // Merge all chunks after the first one - for (size_t i=0; i<chunks.size(); i++) { - data.append(chunks[i]); + // Write BMP header + WriteU16(os, 0x4D42); + + // Write placeholder for size + std::ios::pos_type sz_loc = os.tellp(); + WriteU32(os, 0); + + // Write "reserved" bytes + WriteU32(os, 0); + + // Write data offset + WriteU32(os, data_.at(0).size() + BMP_HDR_SZ); + + for (size_t i=0; i<data_.size(); i++) { + WriteBytes(os, data_.at(i)); } - // Set BM identifier - *(uint16_t *)(data.data()) = 0x4D42; - - // Set file size - *(uint32_t*)(data.data()+2) = data.size(); - - // Set reserved bytes - *(uint32_t*)(data.data()+6) = 0; - - // Set offset - *(uint32_t*)(data.data()+10) = chunks.at(0).size() + 14; + std::ios::pos_type len = os.tellp(); + os.seekp(sz_loc); + WriteU32(os, len); break; } case MxOb::FLC: { // First chunk is a complete FLIC header, so add it as-is - data.append(chunks[0]); + WriteBytes(os, data_.at(0)); // Subsequent chunks are FLIC frames with an additional 20 byte header that needs to be stripped const int CUSTOM_HEADER_SZ = 20; - for (size_t i=1; i<chunks.size(); i++) { - data.append(chunks.at(i).data() + CUSTOM_HEADER_SZ, chunks.at(i).size() - CUSTOM_HEADER_SZ); - } - break; - } - case MxOb::SMK: - case MxOb::OBJ: - { - // Simply merge - for (size_t i=0; i<chunks.size(); i++) { - data.append(chunks[i]); + for (size_t i=1; i<data_.size(); i++) { + os.write(data_.at(i).data() + CUSTOM_HEADER_SZ, data_.at(i).size() - CUSTOM_HEADER_SZ); } 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++) { - data.append(chunks[i]); + LogWarning() << "Didn't know how to extract type '" << RIFF::PrintU32AsString(filetype()) << "', merging..." << std::endl; + /* fall-through */ + case MxOb::SMK: + case MxOb::OBJ: + // Simply merge + for (size_t i=0; i<data_.size(); i++) { + WriteBytes(os, data_.at(i)); } break; } - return data; + return true; } -Object::ChunkedData Object::ToChunkedData(MxOb::FileType filetype, const bytearray &chunks) +bytearray Object::ExtractToMemory() const { - // FIXME: STUB - return ChunkedData(); + memorybuf buf; + std::ostream os(&buf); + + ExtractToFile(os); + + return buf.data(); } const bytearray &Object::GetFileHeader() const diff --git a/lib/object.h b/lib/object.h index 93662a5..0bf05c9 100644 --- a/lib/object.h +++ b/lib/object.h @@ -14,13 +14,18 @@ public: Object(); - void SetChunkedData(const ChunkedData &cd) { data_ = cd; } +#if defined(_WIN32) + LIBWEAVER_EXPORT bool ReplaceWithFile(const wchar_t *f); + LIBWEAVER_EXPORT bool ExtractToFile(const wchar_t *f) const; +#endif - LIBWEAVER_EXPORT bytearray GetNormalizedData() const; - LIBWEAVER_EXPORT void SetNormalizedData(const bytearray &d); + LIBWEAVER_EXPORT bool ReplaceWithFile(const char *f); + LIBWEAVER_EXPORT bool ExtractToFile(const char *f) const; - LIBWEAVER_EXPORT static bytearray ToPackedData(MxOb::FileType filetype, const ChunkedData &chunks); - LIBWEAVER_EXPORT static ChunkedData ToChunkedData(MxOb::FileType filetype, const bytearray &chunks); + LIBWEAVER_EXPORT bool ReplaceWithFile(std::istream &is); + LIBWEAVER_EXPORT bool ExtractToFile(std::ostream &os) const; + + LIBWEAVER_EXPORT bytearray ExtractToMemory() const; LIBWEAVER_EXPORT const bytearray &GetFileHeader() const; LIBWEAVER_EXPORT bytearray GetFileBody() const; @@ -59,6 +64,10 @@ public: ChunkedData data_; + bool last_chunk_split_; + +private: + }; } diff --git a/lib/othertypes.h b/lib/othertypes.h index 320daa6..607d3fe 100644 --- a/lib/othertypes.h +++ b/lib/othertypes.h @@ -25,31 +25,31 @@ public: 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 */ + uint32_t size; // Size of FLIC including this headerdesc << " + uint16_t type; // File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ...desc << " + uint16_t frames; // Number of frames in first segmentdesc << " + uint16_t width; // FLIC width in pixelsdesc << " + uint16_t height; // FLIC height in pixelsdesc << " + uint16_t depth; // Bits per pixel (usually 8)desc << " + uint16_t flags; // Set to zero or to threedesc << " + uint32_t speed; // Delay between framesdesc << " + uint16_t reserved1; // Set to zerodesc << " + uint32_t created; // Date of FLIC creation (FLC only)desc << " + uint32_t creator; // Serial number or compiler id (FLC only)desc << " + uint32_t updated; // Date of FLIC update (FLC only)desc << " + uint32_t updater; // Serial number (FLC only), see creatordesc << " + uint16_t aspect_dx; // Width of square rectangle (FLC only)desc << " + uint16_t aspect_dy; // Height of square rectangle (FLC only)desc << " + uint16_t ext_flags; // EGI: flags for specific EGI extensionsdesc << " + uint16_t keyframes; // EGI: key-image frequencydesc << " + uint16_t totalframes; // EGI: total number of frames (segments)desc << " + uint32_t req_memory; // EGI: maximum chunk size (uncompressed)desc << " + uint16_t max_regions; // EGI: max. number of regions in a CHK_REGION chunkdesc << " + uint16_t transp_num; // EGI: number of transparent levelsdesc << " + uint8_t reserved2[24]; // Set to zerodesc << " + uint32_t oframe1; // Offset to frame 1 (FLC only)desc << " + uint32_t oframe2; // Offset to frame 2 (FLC only)desc << " + uint8_t reserved3[40]; // Set to zerodesc << " }; // Copied from https://wiki.multimedia.cx/index.php/Smacker#Header diff --git a/lib/ptr.h b/lib/ptr.h deleted file mode 100644 index 4314d43..0000000 --- a/lib/ptr.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef PTR_H -#define PTR_H - -namespace si { - -/** - * @brief Smart pointer implementation for versions of C++ < 11 - */ -template <typename T> -class Ptr -{ -public: - Ptr(T *ptr = 0) - { - data_ = ptr; - ref_count_ = new size_t; - *ref_count_ = 1; - } - - ~Ptr() - { - *ref_count_--; - if (*ref_count_ == 0) { - delete data_; - delete ref_count_; - } - } - - Ptr(const Ptr<T> &other) - { - data_ = other.data_; - ref_count_ = other.ref_count_; - *ref_count_++; - } - - Ptr<T> &operator=(const Ptr<T> &other) - { - if (this != other) { - *ref_count_--; - if (*ref_count_ == 0) { - delete data_; - delete ref_count_; - } - - data_ = other.data_; - ref_count_ = other.ref_count_; - *ref_count_++; - } - } - -private: - T *data_; - size_t *ref_count_; - -}; - -} - -#endif // PTR_H diff --git a/lib/sitypes.cpp b/lib/sitypes.cpp index 710eda1..eb068f8 100644 --- a/lib/sitypes.cpp +++ b/lib/sitypes.cpp @@ -1,5 +1,7 @@ #include "sitypes.h" +#include "util.h" + namespace si { const char *MxOb::GetTypeName(Type type) @@ -56,4 +58,67 @@ std::vector<const char*> MxOb::GetFlagsName(Flags flags) return names; } +const char *RIFF::GetTypeDescription(Type t) +{ + switch (t) { + case RIFF_: + return "Resource Interchange File Format"; + case LIST: + return "List of sub-elements"; + case MxSt: + return "Stream"; + case MxHd: + return "Interleaf Header"; + case MxCh: + return "Data Chunk"; + case MxOf: + return "Offset Table"; + case pad_: + return "Padding"; + case MxOb: + return "Streamable Object"; + case MxDa: + return "Data"; + case WAVE: + return "WAVE"; + case fmt_: + return "WAVE Format"; + case OMNI: + return "OMNI"; + case data: + return "WAVE Data"; + } + + return "Unknown"; +} + +RIFF::Chk RIFF::BeginChunk(std::ostream &os, uint32_t type) +{ + Chk stat; + + WriteU32(os, type); + stat.size_position = os.tellp(); + WriteU32(os, 0); + stat.data_start = os.tellp(); + + return stat; +} + +void RIFF::EndChunk(std::ostream &os, const Chk &stat) +{ + std::ios::pos_type now = os.tellp(); + + uint32_t sz = now - stat.data_start; + + os.seekp(stat.size_position); + WriteU32(os, sz); + + os.seekp(now); + + if (sz%2 == 1) { + char nullterm = 0; + os.write(&nullterm, 1); + } +} + } diff --git a/lib/sitypes.h b/lib/sitypes.h index 50c70da..d64841f 100644 --- a/lib/sitypes.h +++ b/lib/sitypes.h @@ -3,28 +3,10 @@ #include <fstream> -#include "common.h" #include "types.h" namespace si { -class SI -{ -public: - enum Type - { - RIFF = 0x46464952, - LIST = 0x5453494c, - MxSt = 0x7453784d, - MxHd = 0x6448784d, - MxCh = 0x6843784d, - MxOf = 0x664f784d, - MxOb = 0x624f784d, - MxDa = 0x6144784d, - pad_ = 0x20646170 - }; -}; - /** * @brief RIFF chunk type * @@ -35,9 +17,38 @@ public: class RIFF { public: - enum { - OMNI = 0x494e4d4f + enum Type { + RIFF_ = 0x46464952, + LIST = 0x5453494c, + MxSt = 0x7453784d, + MxHd = 0x6448784d, + MxCh = 0x6843784d, + MxOf = 0x664f784d, + MxOb = 0x624f784d, + MxDa = 0x6144784d, + pad_ = 0x20646170, + OMNI = 0x494e4d4f, + WAVE = 0x45564157, + fmt_ = 0x20746D66, + data = 0x61746164 }; + + struct Chk + { + std::ios::pos_type size_position; + std::ios::pos_type data_start; + }; + + static Chk BeginChunk(std::ostream &os, uint32_t type); + static void EndChunk(std::ostream &os, const Chk &stat); + + static inline std::string PrintU32AsString(uint32_t u) + { + return std::string((const char *) &u, sizeof(u)); + } + + static const char *GetTypeDescription(Type t); + }; /** @@ -121,7 +132,7 @@ public: class pad_ : public RIFF { public: - static void WriteArbitraryPadding(std::ofstream &os, uint32_t size); + static void WriteArbitraryPadding(std::ostream &os, uint32_t size); }; /** diff --git a/lib/types.h b/lib/types.h index 0c3f3cc..e035292 100644 --- a/lib/types.h +++ b/lib/types.h @@ -1,11 +1,34 @@ #ifndef TYPES_H #define TYPES_H +#include <cstdio> #include <cstring> #include <map> +#include <streambuf> #include <string> +#include <ostream> #include <vector> +#if defined(__GNUC__) +#define LIBWEAVER_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) +#elif defined(_MSC_VER) +#define LIBWEAVER_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) +#endif + +#ifdef _MSC_VER +#define LIBWEAVER_EXPORT __declspec(dllexport) +#else +#define LIBWEAVER_EXPORT +#endif + +#if defined(_WIN32) +#define LIBWEAVER_OS_WINDOWS +#elif defined(__APPLE__) +#define LIBWEAVER_OS_MACOS +#elif defined(__linux__) +#define LIBWEAVER_OS_LINUX +#endif + #if defined(_MSC_VER) && (_MSC_VER < 1600) // Declare types for MSVC versions less than 2010 (1600) which lacked a stdint.h typedef unsigned char uint8_t; @@ -30,6 +53,11 @@ public: { resize(size); } + bytearray(const char *data, size_t size) + { + resize(size); + memcpy(this->data(), data, size); + } template <typename T> T *cast() { return reinterpret_cast<T*>(data()); } @@ -58,6 +86,27 @@ public: }; +LIBWEAVER_EXPORT class memorybuf : public std::streambuf +{ +public: + memorybuf(){} + + virtual int_type overflow(int_type c) + { + if (c != EOF) { + char c2 = c; + m_Internal.append(&c2, 1); + } + return c; + } + + const bytearray &data() const { return m_Internal; } + +private: + bytearray m_Internal; + +}; + class Vector3 { public: diff --git a/lib/util.h b/lib/util.h index 5b28787..9390090 100644 --- a/lib/util.h +++ b/lib/util.h @@ -2,60 +2,61 @@ #define UTIL_H #include <fstream> +#include <iostream> #include "types.h" namespace si { -inline uint32_t ReadU32(std::ifstream &is) +inline uint32_t ReadU32(std::istream &is) { uint32_t u; is.read((char *) &u, sizeof(u)); return u; } -inline void WriteU32(std::ofstream &os, uint32_t u) +inline void WriteU32(std::ostream &os, uint32_t u) { os.write((const char *) &u, sizeof(u)); } -inline uint16_t ReadU16(std::ifstream &is) +inline uint16_t ReadU16(std::istream &is) { uint16_t u; is.read((char *) &u, sizeof(u)); return u; } -inline void WriteU16(std::ofstream &os, uint16_t u) +inline void WriteU16(std::ostream &os, uint16_t u) { os.write((const char *) &u, sizeof(u)); } -inline uint8_t ReadU8(std::ifstream &is) +inline uint8_t ReadU8(std::istream &is) { uint8_t u; is.read((char *) &u, sizeof(u)); return u; } -inline void WriteU8(std::ofstream &os, uint8_t u) +inline void WriteU8(std::ostream &os, uint8_t u) { os.write((const char *) &u, sizeof(u)); } -inline Vector3 ReadVector3(std::ifstream &is) +inline Vector3 ReadVector3(std::istream &is) { Vector3 u; is.read((char *) &u, sizeof(u)); return u; } -inline void WriteVector3(std::ofstream &os, Vector3 v) +inline void WriteVector3(std::ostream &os, Vector3 v) { os.write((const char *) &v, sizeof(v)); } -inline std::string ReadString(std::ifstream &is) +inline std::string ReadString(std::istream &is) { std::string d; @@ -71,7 +72,7 @@ inline std::string ReadString(std::ifstream &is) return d; } -inline void WriteString(std::ofstream &os, const std::string &d) +inline void WriteString(std::ostream &os, const std::string &d) { os.write(d.c_str(), d.size()); @@ -80,7 +81,7 @@ inline void WriteString(std::ofstream &os, const std::string &d) os.write(&nullterm, 1); } -inline bytearray ReadBytes(std::ifstream &is, size_t size) +inline bytearray ReadBytes(std::istream &is, size_t size) { bytearray d; @@ -90,11 +91,26 @@ inline bytearray ReadBytes(std::ifstream &is, size_t size) return d; } -inline void WriteBytes(std::ofstream &os, const bytearray &ba) +inline void WriteBytes(std::ostream &os, const bytearray &ba) { os.write(ba.data(), ba.size()); } +inline std::ostream &LogDebug() +{ + return std::cout << "[DEBUG] "; +} + +inline std::ostream &LogWarning() +{ + return std::cerr << "[WARNING] "; +} + +inline std::ostream &LogError() +{ + return std::cerr << "[ERROR] "; +} + } #endif // UTIL_H