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