diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 8495a83d5..7c5ab9422 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -57,6 +57,7 @@ #include "core/gdbstub/gdbstub.h" #include "core/hle/service/fs/archive.h" #include "core/loader/loader.h" +#include "core/movie.h" #include "core/settings.h" #ifdef USE_DISCORD_PRESENCE @@ -522,6 +523,12 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Screen_Layout_Swap_Screens, &QAction::triggered, this, &GMainWindow::OnSwapScreens); + // Movie + connect(ui.action_Record_Movie, &QAction::triggered, this, &GMainWindow::OnRecordMovie); + connect(ui.action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie); + connect(ui.action_Stop_Recording_Playback, &QAction::triggered, this, + &GMainWindow::OnStopRecordingPlayback); + // Help connect(ui.action_FAQ, &QAction::triggered, []() { QDesktopServices::openUrl(QUrl("https://citra-emu.org/wiki/faq/")); }); @@ -757,6 +764,12 @@ void GMainWindow::BootGame(const QString& filename) { void GMainWindow::ShutdownGame() { discord_rpc->Pause(); + + const bool was_recording = Core::Movie::GetInstance().IsRecordingInput(); + Core::Movie::GetInstance().Shutdown(); + if (was_recording) { + QMessageBox::information(this, "Movie Saved", "The movie is successfully saved."); + } emu_thread->RequestStop(); // Release emu threads from any breakpoints @@ -785,6 +798,9 @@ void GMainWindow::ShutdownGame() { ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(false); ui.action_Restart->setEnabled(false); + ui.action_Record_Movie->setEnabled(false); + ui.action_Play_Movie->setEnabled(false); + ui.action_Stop_Recording_Playback->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); render_window->hide(); if (game_list->isEmpty()) @@ -1059,6 +1075,9 @@ void GMainWindow::OnStartGame() { ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); ui.action_Restart->setEnabled(true); + ui.action_Record_Movie->setEnabled(true); + ui.action_Play_Movie->setEnabled(true); + ui.action_Stop_Recording_Playback->setEnabled(false); ui.action_Report_Compatibility->setEnabled(true); discord_rpc->Update(); @@ -1227,6 +1246,77 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { graphicsSurfaceViewerWidget->show(); } +void GMainWindow::OnRecordMovie() { + const QString path = + QFileDialog::getSaveFileName(this, tr("Record Movie"), "", tr("Citra TAS Movie (*.ctm)")); + if (path.isEmpty()) + return; + Core::Movie::GetInstance().StartRecording(path.toStdString()); + ui.action_Record_Movie->setEnabled(false); + ui.action_Play_Movie->setEnabled(false); + ui.action_Stop_Recording_Playback->setEnabled(true); +} + +void GMainWindow::OnPlayMovie() { + const QString path = + QFileDialog::getOpenFileName(this, tr("Play Movie"), "", tr("Citra TAS Movie (*.ctm)")); + if (path.isEmpty()) + return; + using namespace Core; + Movie::ValidationResult result = Core::Movie::GetInstance().ValidateMovie(path.toStdString()); + const QString revision_dismatch_text = + tr("The movie file you are trying to load was created on a different revision of Citra." + "
Citra has had some changes during the time, and the playback may desync or not " + "work as expected." + "

Are you sure you still want to load the movie file?"); + const QString game_dismatch_text = + tr("The movie file you are trying to load was recorded with a different game." + "
The playback may not work as expected, and it may cause unexpected results." + "

Are you sure you still want to load the movie file?"); + const QString invalid_movie_text = + tr("The movie file you are trying to load is invalid." + "
Either the file is corrupted, or Citra has had made some major changes to the " + "Movie module." + "
Please choose a different movie file and try again."); + int answer; + switch (result) { + case Movie::ValidationResult::RevisionDismatch: + answer = QMessageBox::question(this, tr("Revision Dismatch"), revision_dismatch_text, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (answer != QMessageBox::Yes) + return; + break; + case Movie::ValidationResult::GameDismatch: + answer = QMessageBox::question(this, tr("Game Dismatch"), game_dismatch_text, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (answer != QMessageBox::Yes) + return; + break; + case Movie::ValidationResult::Invalid: + QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); + return; + default: + break; + } + Movie::GetInstance().StartPlayback(path.toStdString(), [this] { + QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); + }); + ui.action_Record_Movie->setEnabled(false); + ui.action_Play_Movie->setEnabled(false); + ui.action_Stop_Recording_Playback->setEnabled(true); +} + +void GMainWindow::OnStopRecordingPlayback() { + const bool was_recording = Core::Movie::GetInstance().IsRecordingInput(); + Core::Movie::GetInstance().Shutdown(); + if (was_recording) { + QMessageBox::information(this, tr("Movie Saved"), tr("The movie is successfully saved.")); + } + ui.action_Record_Movie->setEnabled(true); + ui.action_Play_Movie->setEnabled(true); + ui.action_Stop_Recording_Playback->setEnabled(false); +} + void GMainWindow::UpdateStatusBar() { if (emu_thread == nullptr) { status_bar_update_timer.stop(); @@ -1462,6 +1552,13 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { ui.action_Start->setText(tr("Continue")); } +void GMainWindow::OnMoviePlaybackCompleted() { + QMessageBox::information(this, tr("Playback Completed"), tr("Movie playback completed.")); + ui.action_Record_Movie->setEnabled(true); + ui.action_Play_Movie->setEnabled(true); + ui.action_Stop_Recording_Playback->setEnabled(false); +} + void GMainWindow::SetupUIStrings() { if (game_title.isEmpty()) { setWindowTitle(tr("Citra %1").arg(Common::g_build_fullname)); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 4d35d202f..7fb26a43f 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -175,6 +175,9 @@ private slots: void HideFullscreen(); void ToggleWindowMode(); void OnCreateGraphicsSurfaceViewer(); + void OnRecordMovie(); + void OnPlayMovie(); + void OnStopRecordingPlayback(); void OnCoreError(Core::System::ResultStatus, std::string); /// Called whenever a user selects Help->About Citra void OnMenuAboutCitra(); @@ -184,6 +187,7 @@ private slots: void OnLanguageChanged(const QString& locale); private: + Q_INVOKABLE void OnMoviePlaybackCompleted(); void UpdateStatusBar(); void LoadTranslation(); void SetupUIStrings(); diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index bee688600..01590f882 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -107,6 +107,14 @@ + + + Movie + + + + + true @@ -136,6 +144,7 @@ + @@ -243,6 +252,30 @@ Create Pica Surface Viewer + + + false + + + Record Movie + + + + + false + + + Play Movie + + + + + false + + + Stop Recording / Playback + + true