mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	citra_qt, movie: allow recording/playback before emulation starts
This commit is contained in:
		
							parent
							
								
									a9ad8daf47
								
							
						
					
					
						commit
						3b459f6eb3
					
				
					 7 changed files with 126 additions and 30 deletions
				
			
		|  | @ -628,6 +628,24 @@ void GameList::RefreshGameDirectory() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(u64 program_id) { | ||||
|     return FindGameByProgramID(item_model->invisibleRootItem(), program_id); | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_id) { | ||||
|     if (current_item->type() == static_cast<int>(GameListItemType::Game) && | ||||
|         current_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { | ||||
|         return current_item->data(GameListItemPath::FullPathRole).toString(); | ||||
|     } else if (current_item->hasChildren()) { | ||||
|         for (int child_id = 0; child_id < current_item->rowCount(); child_id++) { | ||||
|             QString path = FindGameByProgramID(current_item->child(child_id, 0), program_id); | ||||
|             if (!path.isEmpty()) | ||||
|                 return path; | ||||
|         } | ||||
|     } | ||||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, | ||||
|                                              GameListDir* parent_dir) { | ||||
|     const auto callback = [this, recursion, parent_dir](u64* num_entries_out, | ||||
|  |  | |||
|  | @ -59,6 +59,8 @@ public: | |||
| 
 | ||||
|     QStandardItemModel* GetModel() const; | ||||
| 
 | ||||
|     QString FindGameByProgramID(u64 program_id); | ||||
| 
 | ||||
|     static const QStringList supported_file_extensions; | ||||
| 
 | ||||
| signals: | ||||
|  | @ -91,6 +93,8 @@ private: | |||
|     void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
|     void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
| 
 | ||||
|     QString FindGameByProgramID(QStandardItem* current_item, u64 program_id); | ||||
| 
 | ||||
|     GameListSearchField* search_field; | ||||
|     GMainWindow* main_window = nullptr; | ||||
|     QVBoxLayout* layout = nullptr; | ||||
|  |  | |||
|  | @ -769,6 +769,9 @@ void GMainWindow::ShutdownGame() { | |||
|     Core::Movie::GetInstance().Shutdown(); | ||||
|     if (was_recording) { | ||||
|         QMessageBox::information(this, "Movie Saved", "The movie is successfully saved."); | ||||
|         ui.action_Record_Movie->setEnabled(true); | ||||
|         ui.action_Play_Movie->setEnabled(true); | ||||
|         ui.action_Stop_Recording_Playback->setEnabled(false); | ||||
|     } | ||||
|     emu_thread->RequestStop(); | ||||
| 
 | ||||
|  | @ -798,9 +801,6 @@ 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()) | ||||
|  | @ -1064,6 +1064,13 @@ void GMainWindow::OnMenuRecentFile() { | |||
| 
 | ||||
| void GMainWindow::OnStartGame() { | ||||
|     Camera::QtMultimediaCameraHandler::ResumeCameras(); | ||||
| 
 | ||||
|     if (movie_record_on_start) { | ||||
|         Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString()); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|     } | ||||
| 
 | ||||
|     emu_thread->SetRunning(true); | ||||
|     qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); | ||||
|     qRegisterMetaType<std::string>("std::string"); | ||||
|  | @ -1075,9 +1082,6 @@ 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(); | ||||
|  | @ -1251,19 +1255,23 @@ void GMainWindow::OnRecordMovie() { | |||
|         QFileDialog::getSaveFileName(this, tr("Record Movie"), "", tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|         return; | ||||
|     Core::Movie::GetInstance().StartRecording(path.toStdString()); | ||||
|     if (emulation_running) { | ||||
|         Core::Movie::GetInstance().StartRecording(path.toStdString()); | ||||
|     } else { | ||||
|         movie_record_on_start = true; | ||||
|         movie_record_path = path; | ||||
|         QMessageBox::information(this, tr("Record Movie"), | ||||
|                                  tr("Recording will start once you boot a game.")); | ||||
|     } | ||||
|     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; | ||||
| bool GMainWindow::ValidateMovie(const QString& path, u64 program_id) { | ||||
|     using namespace Core; | ||||
|     Movie::ValidationResult result = Core::Movie::GetInstance().ValidateMovie(path.toStdString()); | ||||
|     Movie::ValidationResult result = | ||||
|         Core::Movie::GetInstance().ValidateMovie(path.toStdString(), program_id); | ||||
|     const QString revision_dismatch_text = | ||||
|         tr("The movie file you are trying to load was created on a different revision of Citra." | ||||
|            "<br/>Citra has had some changes during the time, and the playback may desync or not " | ||||
|  | @ -1284,21 +1292,56 @@ void GMainWindow::OnPlayMovie() { | |||
|         answer = QMessageBox::question(this, tr("Revision Dismatch"), revision_dismatch_text, | ||||
|                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
|         if (answer != QMessageBox::Yes) | ||||
|             return; | ||||
|             return false; | ||||
|         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; | ||||
|             return false; | ||||
|         break; | ||||
|     case Movie::ValidationResult::Invalid: | ||||
|         QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|         return; | ||||
|         return false; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|     Movie::GetInstance().StartPlayback(path.toStdString(), [this] { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnPlayMovie() { | ||||
|     const QString path = | ||||
|         QFileDialog::getOpenFileName(this, tr("Play Movie"), "", tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|         return; | ||||
| 
 | ||||
|     if (emulation_running) { | ||||
|         if (!ValidateMovie(path)) | ||||
|             return; | ||||
|     } else { | ||||
|         const QString invalid_movie_text = | ||||
|             tr("The movie file you are trying to load is invalid." | ||||
|                "<br/>Either the file is corrupted, or Citra has had made some major changes to the " | ||||
|                "Movie module." | ||||
|                "<br/>Please choose a different movie file and try again."); | ||||
|         u64 program_id = Core::Movie::GetInstance().GetMovieProgramID(path.toStdString()); | ||||
|         if (!program_id) { | ||||
|             QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|             return; | ||||
|         } | ||||
|         QString game_path = game_list->FindGameByProgramID(program_id); | ||||
|         if (game_path.isEmpty()) { | ||||
|             QMessageBox::warning(this, tr("Game Not Found"), | ||||
|                                  tr("The movie you are trying to play is from a game that is not " | ||||
|                                     "in the game list. If you own the game, please add the game " | ||||
|                                     "folder to the game list and try to play the movie again.")); | ||||
|             return; | ||||
|         } | ||||
|         if (!ValidateMovie(path, program_id)) | ||||
|             return; | ||||
|         BootGame(game_path); | ||||
|     } | ||||
|     Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] { | ||||
|         QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); | ||||
|     }); | ||||
|     ui.action_Record_Movie->setEnabled(false); | ||||
|  | @ -1307,10 +1350,17 @@ void GMainWindow::OnPlayMovie() { | |||
| } | ||||
| 
 | ||||
| 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.")); | ||||
|     if (movie_record_on_start) { | ||||
|         QMessageBox::information(this, tr("Record Movie"), tr("Movie recording cancelled.")); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|     } else { | ||||
|         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); | ||||
|  |  | |||
|  | @ -187,6 +187,7 @@ private slots: | |||
|     void OnLanguageChanged(const QString& locale); | ||||
| 
 | ||||
| private: | ||||
|     bool ValidateMovie(const QString& path, u64 program_id = 0); | ||||
|     Q_INVOKABLE void OnMoviePlaybackCompleted(); | ||||
|     void UpdateStatusBar(); | ||||
|     void LoadTranslation(); | ||||
|  | @ -218,6 +219,10 @@ private: | |||
|     // The path to the game currently running
 | ||||
|     QString game_path; | ||||
| 
 | ||||
|     // Movie
 | ||||
|     bool movie_record_on_start = false; | ||||
|     QString movie_record_path; | ||||
| 
 | ||||
|     // Debugger panes
 | ||||
|     ProfilerWidget* profilerWidget; | ||||
|     MicroProfileDialog* microProfileDialog; | ||||
|  |  | |||
|  | @ -254,7 +254,7 @@ | |||
|   </action> | ||||
|   <action name="action_Record_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Record Movie</string> | ||||
|  | @ -262,7 +262,7 @@ | |||
|   </action> | ||||
|   <action name="action_Play_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Play Movie</string> | ||||
|  |  | |||
|  | @ -344,7 +344,7 @@ void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) { | |||
|     Record(s); | ||||
| } | ||||
| 
 | ||||
| Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header) const { | ||||
| Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header, u64 program_id) const { | ||||
|     if (header_magic_bytes != header.filetype) { | ||||
|         LOG_ERROR(Movie, "Playback file does not have valid header"); | ||||
|         return ValidationResult::Invalid; | ||||
|  | @ -354,8 +354,8 @@ Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header) const { | |||
|         Common::ArrayToString(header.revision.data(), header.revision.size(), 21, false); | ||||
|     revision = Common::ToLower(revision); | ||||
| 
 | ||||
|     u64 program_id; | ||||
|     Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id); | ||||
|     if (!program_id) | ||||
|         Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id); | ||||
|     if (program_id != header.program_id) { | ||||
|         LOG_WARNING(Movie, "This movie was recorded using a ROM with a different program id"); | ||||
|         return ValidationResult::GameDismatch; | ||||
|  | @ -424,7 +424,7 @@ void Movie::StartRecording(const std::string& movie_file) { | |||
|     record_movie_file = movie_file; | ||||
| } | ||||
| 
 | ||||
| Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file) const { | ||||
| Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file, u64 program_id) const { | ||||
|     LOG_INFO(Movie, "Validating Movie file '{}'", movie_file); | ||||
|     FileUtil::IOFile save_record(movie_file, "rb"); | ||||
|     const u64 size = save_record.GetSize(); | ||||
|  | @ -435,7 +435,25 @@ Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file) cons | |||
| 
 | ||||
|     CTMHeader header; | ||||
|     save_record.ReadArray(&header, 1); | ||||
|     return ValidateHeader(header); | ||||
|     return ValidateHeader(header, program_id); | ||||
| } | ||||
| 
 | ||||
| u64 Movie::GetMovieProgramID(const std::string& movie_file) const { | ||||
|     FileUtil::IOFile save_record(movie_file, "rb"); | ||||
|     const u64 size = save_record.GetSize(); | ||||
| 
 | ||||
|     if (!save_record || size <= sizeof(CTMHeader)) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     CTMHeader header; | ||||
|     save_record.ReadArray(&header, 1); | ||||
| 
 | ||||
|     if (header_magic_bytes != header.filetype) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return static_cast<u64>(header.program_id); | ||||
| } | ||||
| 
 | ||||
| void Movie::Shutdown() { | ||||
|  |  | |||
|  | @ -44,7 +44,8 @@ public: | |||
|     void StartPlayback(const std::string& movie_file, | ||||
|                        std::function<void()> completion_callback = {}); | ||||
|     void StartRecording(const std::string& movie_file); | ||||
|     ValidationResult ValidateMovie(const std::string& movie_file) const; | ||||
|     ValidationResult ValidateMovie(const std::string& movie_file, u64 program_id = 0) const; | ||||
|     u64 GetMovieProgramID(const std::string& movie_file) const; | ||||
| 
 | ||||
|     void Shutdown(); | ||||
| 
 | ||||
|  | @ -111,7 +112,7 @@ private: | |||
|     void Record(const Service::IR::PadState& pad_state, const s16& c_stick_x, const s16& c_stick_y); | ||||
|     void Record(const Service::IR::ExtraHIDResponse& extra_hid_response); | ||||
| 
 | ||||
|     ValidationResult ValidateHeader(const CTMHeader& header) const; | ||||
|     ValidationResult ValidateHeader(const CTMHeader& header, u64 program_id = 0) const; | ||||
| 
 | ||||
|     void SaveMovie(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue