From 80852f918a35e83ac888f25bb95a943ed686837d Mon Sep 17 00:00:00 2001
From: BreadFish64 <mohror64@gmail.com>
Date: Sat, 2 Dec 2017 15:35:20 -0600
Subject: [PATCH] add CIA installation to QT frontend

---
 src/citra_qt/main.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++
 src/citra_qt/main.h   | 16 +++++++++--
 src/citra_qt/main.ui  |  6 ++++
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 0e9e8f1c5..1f24a9b2d 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -10,7 +10,9 @@
 #define QT_NO_OPENGL
 #include <QDesktopWidget>
 #include <QFileDialog>
+#include <QFutureWatcher>
 #include <QMessageBox>
+#include <QtConcurrent/QtConcurrentRun>
 #include <QtGui>
 #include <QtWidgets>
 #include "citra_qt/aboutdialog.h"
@@ -92,6 +94,9 @@ void GMainWindow::ShowCallouts() {
 }
 
 GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
+    // register size_t to use in slots and signals
+    qRegisterMetaType<size_t>("size_t");
+
     Pica::g_debug_context = Pica::DebugContext::Construct();
     setAcceptDrops(true);
     ui.setupUi(this);
@@ -158,6 +163,10 @@ void GMainWindow::InitializeWidgets() {
     message_label->setAlignment(Qt::AlignLeft);
     statusBar()->addPermanentWidget(message_label, 1);
 
+    progress_bar = new QProgressBar();
+    progress_bar->hide();
+    statusBar()->addPermanentWidget(progress_bar);
+
     emu_speed_label = new QLabel();
     emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
                                    "indicate emulation is running faster or slower than a 3DS."));
@@ -333,11 +342,14 @@ void GMainWindow::ConnectWidgetEvents() {
     connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping()));
 
     connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
+
+    connect(this, &GMainWindow::UpdateProgress, this, &GMainWindow::OnUpdateProgress);
 }
 
 void GMainWindow::ConnectMenuEvents() {
     // File
     connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
+    connect(ui.action_Install_CIA, &QAction::triggered, this, &GMainWindow::OnMenuInstallCIA);
     connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
             &GMainWindow::OnMenuSelectGameListRoot);
     connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
@@ -696,6 +708,61 @@ void GMainWindow::OnMenuSelectGameListRoot() {
     }
 }
 
+void GMainWindow::OnMenuInstallCIA() {
+    QString filepath = QFileDialog::getOpenFileName(
+        this, tr("Load File"), UISettings::values.roms_path,
+        tr("3DS Installation File (*.CIA*)") + ";;" + tr("All Files (*.*)"));
+    if (filepath.isEmpty())
+        return;
+
+    ui.action_Install_CIA->setEnabled(false);
+    progress_bar->show();
+    watcher = new QFutureWatcher<Service::AM::InstallStatus>;
+    QFuture<Service::AM::InstallStatus> f = QtConcurrent::run([&, filepath] {
+        const auto cia_progress = [&](size_t written, size_t total) {
+            emit UpdateProgress(written, total);
+        };
+        return Service::AM::InstallCIA(filepath.toStdString(), cia_progress);
+    });
+    connect(watcher, &QFutureWatcher<Service::AM::InstallStatus>::finished, this,
+            &GMainWindow::OnCIAInstallFinished);
+    watcher->setFuture(f);
+}
+
+void GMainWindow::OnUpdateProgress(size_t written, size_t total) {
+    progress_bar->setMaximum(total);
+    progress_bar->setValue(written);
+}
+
+void GMainWindow::OnCIAInstallFinished() {
+    progress_bar->hide();
+    progress_bar->setValue(0);
+    switch (watcher->future()) {
+    case Service::AM::InstallStatus::Success:
+        this->statusBar()->showMessage(tr("The file has been installed successfully."));
+        break;
+    case Service::AM::InstallStatus::ErrorFailedToOpenFile:
+        QMessageBox::critical(this, tr("Unable to open File"),
+                              tr("Could not open the selected file"));
+        break;
+    case Service::AM::InstallStatus::ErrorAborted:
+        QMessageBox::critical(
+            this, tr("Installation aborted"),
+            tr("The installation was aborted. Please see the log for more details"));
+        break;
+    case Service::AM::InstallStatus::ErrorInvalid:
+        QMessageBox::critical(this, tr("Invalid File"), tr("The selected file is not a valid CIA"));
+        break;
+    case Service::AM::InstallStatus::ErrorEncrypted:
+        QMessageBox::critical(this, tr("Encrypted File"),
+                              tr("The file that you are trying to install must be decrypted "
+                                 "before being used with Citra. A real 3DS is required."));
+        break;
+    }
+    delete watcher;
+    ui.action_Install_CIA->setEnabled(true);
+}
+
 void GMainWindow::OnMenuRecentFile() {
     QAction* action = qobject_cast<QAction*>(sender());
     assert(action);
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 878b2becf..6d1eeb3ee 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -8,24 +8,28 @@
 #include <QMainWindow>
 #include <QTimer>
 #include "core/core.h"
+#include "core/hle/service/am/am.h"
 #include "ui_main.h"
 
+class AboutDialog;
 class Config;
 class EmuThread;
 class GameList;
 class GImageInfo;
-class GPUCommandStreamWidget;
 class GPUCommandListWidget;
+class GPUCommandStreamWidget;
 class GraphicsBreakPointsWidget;
 class GraphicsTracingWidget;
 class GraphicsVertexShaderWidget;
 class GRenderWindow;
 class MicroProfileDialog;
 class ProfilerWidget;
+template <typename>
+class QFutureWatcher;
+class QProgressBar;
 class RegistersWidget;
 class Updater;
 class WaitTreeWidget;
-class AboutDialog;
 
 class GMainWindow : public QMainWindow {
     Q_OBJECT
@@ -64,6 +68,7 @@ signals:
      * system emulation handles and memory are still valid, but are about become invalid.
      */
     void EmulationStopping();
+    void UpdateProgress(size_t written, size_t total);
 
 private:
     void InitializeWidgets();
@@ -125,6 +130,9 @@ private slots:
     void OnGameListLoadFile(QString game_path);
     void OnGameListOpenSaveFolder(u64 program_id);
     void OnMenuLoadFile();
+    void OnMenuInstallCIA();
+    void OnUpdateProgress(size_t written, size_t total);
+    void OnCIAInstallFinished();
     /// Called whenever a user selects the "File->Select Game List Root" menu item
     void OnMenuSelectGameListRoot();
     void OnMenuRecentFile();
@@ -149,8 +157,10 @@ private:
 
     GRenderWindow* render_window;
     GameList* game_list;
+    QFutureWatcher<Service::AM::InstallStatus>* watcher = nullptr;
 
     // Status bar elements
+    QProgressBar* progress_bar = nullptr;
     QLabel* message_label = nullptr;
     QLabel* emu_speed_label = nullptr;
     QLabel* game_fps_label = nullptr;
@@ -185,3 +195,5 @@ protected:
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dragMoveEvent(QDragMoveEvent* event) override;
 };
+
+Q_DECLARE_METATYPE(size_t);
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 417a8a6a6..1bc071776 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -58,6 +58,7 @@
      </property>
     </widget>
     <addaction name="action_Load_File"/>
+    <addaction name="action_Install_CIA"/>
     <addaction name="separator"/>
     <addaction name="action_Select_Game_List_Root"/>
     <addaction name="menu_recent_files"/>
@@ -112,6 +113,11 @@
     <string>Load File...</string>
    </property>
   </action>
+  <action name="action_Install_CIA">
+   <property name="text">
+    <string>Install CIA...</string>
+   </property>
+  </action>
   <action name="action_Load_Symbol_Map">
    <property name="text">
     <string>Load Symbol Map...</string>