diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp
index 5e89b33..2fa47fc 100644
--- a/app/mainwindow.cpp
+++ b/app/mainwindow.cpp
@@ -1,6 +1,5 @@
 #include "mainwindow.h"
 
-#include <iostream>
 #include <QFileDialog>
 #include <QLineEdit>
 #include <QMenuBar>
@@ -139,6 +138,8 @@ void MainWindow::InitializeMenuBar()
 
   file_menu->addAction(tr("&View SI File"), tr("Ctrl+I"), this, &MainWindow::ViewSIFile);
 
+  file_menu->addAction(tr("E&xtract All"), this, &MainWindow::ExtractAll);
+
   file_menu->addSeparator();
 
   file_menu->addAction(tr("E&xit"), this, &MainWindow::close);
@@ -244,6 +245,39 @@ QString MainWindow::GetOpenFileName()
   return QFileDialog::getOpenFileName(this, QString(), QString(), kFileFilter);
 }
 
+bool MainWindow::ExtractAllRecursiveInternal(const QDir &dir, const si::Core *obj)
+{
+  if (!dir.mkpath(QStringLiteral("."))) {
+    QMessageBox::critical(this, tr("Extract All Failed"), tr("Failed to create directory \"%1\". Try extracting somewhere else.").arg(dir.absolutePath()));
+    return false;
+  }
+
+  for (const Core *child : obj->GetChildren()) {
+    if (const Object *obj = dynamic_cast<const Object*>(child)) {
+      if (!obj->data().empty()) {
+        QString realFilename = QString::fromStdString(obj->filename());
+        realFilename = realFilename.mid(realFilename.lastIndexOf('\\')+1);
+
+        QString output = dir.filePath(realFilename);
+
+        if (!obj->ExtractToFile(output.toUtf8())) {
+          QMessageBox::critical(this, tr("Extract All Failed"), tr("Failed to create file \"%1\". Try extracting somewhere else.").arg(output));
+          return false;
+        }
+      }
+
+      if (obj->HasChildren()) {
+        // Extract its children too
+        if (!ExtractAllRecursiveInternal(QDir(dir.filePath(QString::fromStdString(obj->name()))), obj)) {
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 void MainWindow::NewFile()
 {
   model_.SetCore(nullptr);
@@ -354,6 +388,22 @@ void MainWindow::ViewSIFile()
   }
 }
 
+void MainWindow::ExtractAll()
+{
+  QString s = QFileDialog::getExistingDirectory(this, tr("Extract All To..."));
+  if (s.isEmpty()) {
+    return;
+  }
+
+  QDir dir(s);
+  if (!dir.exists()) {
+    QMessageBox::critical(this, tr("Extract All Failed"), tr("Directory \"%1\" is not valid. Try extracting somewhere else.").arg(s));
+    return;
+  }
+
+  ExtractAllRecursiveInternal(dir, &interleaf_);
+}
+
 void MainWindow::ExtraChanged()
 {
   if (last_set_data_) {
diff --git a/app/mainwindow.h b/app/mainwindow.h
index 1b26b8b..053265f 100644
--- a/app/mainwindow.h
+++ b/app/mainwindow.h
@@ -8,6 +8,7 @@
 #include <QPlainTextEdit>
 #include <QStackedWidget>
 #include <QTreeView>
+#include <qdir.h>
 
 #include "objectmodel.h"
 #include "panel.h"
@@ -36,6 +37,8 @@ private:
 
   QString GetOpenFileName();
 
+  bool ExtractAllRecursiveInternal(const QDir &dir, const si::Core *obj);
+
   static const QString kFileFilter;
 
   QStackedWidget *config_stack_;
@@ -78,6 +81,7 @@ private slots:
   void ReplaceClicked();
 
   void ViewSIFile();
+  void ExtractAll();
 
   void ExtraChanged();
   void LocationChanged(const si::Vector3 &v);