diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 820f60e61b..66fdbcfed9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1550,8 +1550,9 @@ void GMainWindow::AllowOSSleep() {
 
 bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {
     // Shutdown previous session if the emu thread is still active...
-    if (emu_thread != nullptr)
+    if (emu_thread != nullptr) {
         ShutdownGame();
+    }
 
     if (!render_window->InitRenderTarget()) {
         return false;
@@ -1779,7 +1780,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
     OnStartGame();
 }
 
-void GMainWindow::ShutdownGame() {
+void GMainWindow::OnShutdownBegin() {
     if (!emulation_running) {
         return;
     }
@@ -1802,13 +1803,41 @@ void GMainWindow::ShutdownGame() {
 
     emit EmulationStopping();
 
-    // Wait for emulation thread to complete and delete it
-    if (system->DebuggerEnabled() || !emu_thread->wait(5000)) {
+    shutdown_timer.setSingleShot(true);
+    shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
+    connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
+    connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
+
+    // Disable everything to prevent anything from being triggered here
+    ui->action_Pause->setEnabled(false);
+    ui->action_Restart->setEnabled(false);
+    ui->action_Stop->setEnabled(false);
+}
+
+void GMainWindow::OnShutdownBeginDialog() {
+    shutdown_dialog =
+        new OverlayDialog(render_window, *system, QString{}, tr("Closing software..."), QString{},
+                          QString{}, Qt::AlignHCenter | Qt::AlignVCenter);
+    shutdown_dialog->open();
+}
+
+void GMainWindow::OnEmulationStopTimeExpired() {
+    if (emu_thread) {
         emu_thread->ForceStop();
-        emu_thread->wait();
     }
+}
+
+void GMainWindow::OnEmulationStopped() {
+    shutdown_timer.stop();
+    emu_thread->disconnect();
+    emu_thread->wait();
     emu_thread = nullptr;
 
+    if (shutdown_dialog) {
+        shutdown_dialog->deleteLater();
+        shutdown_dialog = nullptr;
+    }
+
     emulation_running = false;
 
     discord_rpc->Update();
@@ -1854,6 +1883,20 @@ void GMainWindow::ShutdownGame() {
 
     // When closing the game, destroy the GLWindow to clear the context after the game is closed
     render_window->ReleaseRenderTarget();
+
+    Settings::RestoreGlobalState(system->IsPoweredOn());
+    system->HIDCore().ReloadInputDevices();
+    UpdateStatusButtons();
+}
+
+void GMainWindow::ShutdownGame() {
+    if (!emulation_running) {
+        return;
+    }
+
+    OnShutdownBegin();
+    OnEmulationStopTimeExpired();
+    OnEmulationStopped();
 }
 
 void GMainWindow::StoreRecentFile(const QString& filename) {
@@ -2956,11 +2999,8 @@ void GMainWindow::OnStopGame() {
         return;
     }
 
-    ShutdownGame();
-
-    Settings::RestoreGlobalState(system->IsPoweredOn());
-    system->HIDCore().ReloadInputDevices();
-    UpdateStatusButtons();
+    OnShutdownBegin();
+    OnShutdownBeginDialog();
 }
 
 void GMainWindow::OnLoadComplete() {
@@ -4047,10 +4087,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
     // Shutdown session if the emu thread is active...
     if (emu_thread != nullptr) {
         ShutdownGame();
-
-        Settings::RestoreGlobalState(system->IsPoweredOn());
-        system->HIDCore().ReloadInputDevices();
-        UpdateStatusButtons();
     }
 
     render_window->close();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 5b84c7a007..ce1de17eff 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -29,6 +29,7 @@ class GImageInfo;
 class GRenderWindow;
 class LoadingScreen;
 class MicroProfileDialog;
+class OverlayDialog;
 class ProfilerWidget;
 class ControllerDialog;
 class QLabel;
@@ -335,6 +336,10 @@ private slots:
     void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
     void OnLanguageChanged(const QString& locale);
     void OnMouseActivity();
+    void OnShutdownBegin();
+    void OnShutdownBeginDialog();
+    void OnEmulationStopped();
+    void OnEmulationStopTimeExpired();
 
 private:
     QString GetGameListErrorRemoving(InstalledEntryType type) const;
@@ -384,6 +389,8 @@ private:
     GRenderWindow* render_window;
     GameList* game_list;
     LoadingScreen* loading_screen;
+    QTimer shutdown_timer;
+    OverlayDialog* shutdown_dialog;
 
     GameListPlaceholder* game_list_placeholder;
 
diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp
index 3fa3d0afb1..25fa789ac3 100644
--- a/src/yuzu/util/overlay_dialog.cpp
+++ b/src/yuzu/util/overlay_dialog.cpp
@@ -259,3 +259,9 @@ void OverlayDialog::InputThread() {
         std::this_thread::sleep_for(std::chrono::milliseconds(50));
     }
 }
+
+void OverlayDialog::keyPressEvent(QKeyEvent* e) {
+    if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) {
+        QDialog::keyPressEvent(e);
+    }
+}
diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h
index 39c44393c3..872283d61b 100644
--- a/src/yuzu/util/overlay_dialog.h
+++ b/src/yuzu/util/overlay_dialog.h
@@ -94,6 +94,7 @@ private:
 
     /// The thread where input is being polled and processed.
     void InputThread();
+    void keyPressEvent(QKeyEvent* e) override;
 
     std::unique_ptr<Ui::OverlayDialog> ui;