diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index dfa50006a..58e23fc69 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1603,6 +1603,7 @@ void GMainWindow::ConnectMenuEvents() {
     // Help
     connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
     connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
+    connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware);
     connect_menu(ui->action_About, &GMainWindow::OnAbout);
 }
 
@@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() {
         action->setEnabled(emulation_running);
     }
 
+    ui->action_Install_Firmware->setEnabled(!emulation_running);
+
     for (QAction* action : applet_actions) {
         action->setEnabled(is_firmware_available && !emulation_running);
     }
@@ -4150,6 +4153,116 @@ void GMainWindow::OnVerifyInstalledContents() {
     }
 }
 
+void GMainWindow::OnInstallFirmware() {
+    // Don't do this while emulation is running, that'd probably be a bad idea.
+    if (emu_thread != nullptr && emu_thread->IsRunning()) {
+        return;
+    }
+
+    // Check for installed keys, error out, suggest restart?
+    if (!ContentManager::AreKeysPresent()) {
+        QMessageBox::information(this, tr("Keys not installed"),
+                                 tr("Install decryption keys and restart yuzu before attempting to install firmware."));
+        return;
+    }
+
+    QString firmware_source_location = QFileDialog::getExistingDirectory(this,
+        tr("Select Dumped Firmware Source Location"), QString::fromStdString(""), QFileDialog::ShowDirsOnly);
+    if (firmware_source_location.isEmpty()) {
+        return;
+    }
+
+    QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this);
+    progress.setWindowModality(Qt::WindowModal);
+    progress.setMinimumDuration(100);
+    progress.setAutoClose(false);
+    progress.setAutoReset(false);
+    progress.show();
+
+    // Declare progress callback.
+    auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
+        progress.setValue(static_cast<int>((processed_size * 100) / total_size));
+        return progress.wasCanceled();
+    };
+
+    LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString());
+
+    // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in there.
+    std::filesystem::path firmware_source_path = firmware_source_location.toStdString();
+    if (!Common::FS::IsDir(firmware_source_path)) {
+        progress.close();
+        return;
+    }
+
+    std::vector<std::filesystem::path> out;
+
+    const Common::FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
+        if (entry.path().has_extension() && entry.path().extension() == ".nca")
+            out.emplace_back(entry.path());
+
+        return true;
+    };
+
+    QtProgressCallback(100, 10);
+
+    Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
+    if (out.size() <= 0) {
+        progress.close();
+        QMessageBox::warning(this, tr("Firmware install failed"),
+                             tr("Unable to locate potential firmware NCA files"));
+        return;
+    }
+
+    // Locate and erase the content of nand/system/Content/registered/*.nca, if any.
+    auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
+    if (sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
+        LOG_INFO(Frontend,
+                 "Cleaned nand/system/Content/registered folder in preparation for new firmware.");
+    }
+
+    QtProgressCallback(100, 20);
+
+    auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
+
+    bool success = true;
+    bool cancelled = false;
+    int i = 0;
+    for (const auto& firmware_src_path : out) {
+        i++;
+        auto firmware_src_vfile =
+            vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
+        auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
+
+        if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
+            LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
+                      firmware_src_path.generic_string(), firmware_src_path.filename().string());
+            success = false;
+        }
+
+        if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0)))
+        {
+            success = false;
+            cancelled = true;
+            break;
+        }
+    }
+
+    if (!success && !cancelled) {
+        progress.close();
+        QMessageBox::critical(this, tr("Firmware install failed"),
+                              tr("One or more firmware files failed to copy into NAND."));
+        return;
+    } else if (cancelled) {
+        progress.close();
+        QMessageBox::warning(this, tr("Firmware install failed"),
+                              tr("Firmware installation cancelled, firmware may be in bad state, restart yuzu or re-install firmware."));
+        return;
+    }
+
+    progress.close();
+    OnCheckFirmwareDecryption();
+}
+
 void GMainWindow::OnAbout() {
     AboutDialog aboutDialog(this);
     aboutDialog.exec();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index aba61e388..1f0e35c67 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -380,6 +380,7 @@ private slots:
     void OnLoadAmiibo();
     void OnOpenYuzuFolder();
     void OnVerifyInstalledContents();
+    void OnInstallFirmware();
     void OnAbout();
     void OnToggleFilterBar();
     void OnToggleStatusBar();
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 6a6b0821f..6ff444a22 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -25,7 +25,16 @@
   </property>
   <widget class="QWidget" name="centralwidget">
    <layout class="QHBoxLayout" name="horizontalLayout">
-    <property name="margin" stdset="0">
+    <property name="leftMargin">
+     <number>0</number>
+    </property>
+    <property name="topMargin">
+     <number>0</number>
+    </property>
+    <property name="rightMargin">
+     <number>0</number>
+    </property>
+    <property name="bottomMargin">
      <number>0</number>
     </property>
    </layout>
@@ -156,8 +165,8 @@
      <addaction name="separator"/>
      <addaction name="action_Configure_Tas"/>
     </widget>
-    <addaction name="action_Rederive"/>
     <addaction name="action_Verify_installed_contents"/>
+    <addaction name="action_Install_Firmware"/>
     <addaction name="separator"/>
     <addaction name="menu_cabinet_applet"/>
     <addaction name="action_Load_Album"/>
@@ -455,6 +464,11 @@
     <string>Open &amp;Controller Menu</string>
    </property>
   </action>
+  <action name="action_Install_Firmware">
+   <property name="text">
+    <string>Install Firmware</string>
+   </property>
+  </action>
  </widget>
  <resources>
   <include location="yuzu.qrc"/>