1 // Copyright 2020 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <QFileDialog>
6 #include <QPushButton>
7 #include <QTime>
8 #include "citra_qt/game_list.h"
9 #include "citra_qt/game_list_p.h"
10 #include "citra_qt/movie/movie_play_dialog.h"
11 #include "citra_qt/uisettings.h"
12 #include "core/core.h"
13 #include "core/core_timing.h"
14 #include "core/hle/service/hid/hid.h"
15 #include "core/movie.h"
16 #include "ui_movie_play_dialog.h"
17
MoviePlayDialog(QWidget * parent,GameList * game_list_)18 MoviePlayDialog::MoviePlayDialog(QWidget* parent, GameList* game_list_)
19 : QDialog(parent), ui(std::make_unique<Ui::MoviePlayDialog>()), game_list(game_list_) {
20 ui->setupUi(this);
21
22 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
23
24 connect(ui->filePathButton, &QToolButton::clicked, this, &MoviePlayDialog::OnToolButtonClicked);
25 connect(ui->filePath, &QLineEdit::editingFinished, this, &MoviePlayDialog::UpdateUIDisplay);
26 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &MoviePlayDialog::accept);
27 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MoviePlayDialog::reject);
28
29 if (Core::System::GetInstance().IsPoweredOn()) {
30 QString note_text;
31 note_text = tr("Current running game will be stopped.");
32 if (Core::Movie::GetInstance().GetPlayMode() == Core::Movie::PlayMode::Recording) {
33 note_text.append(tr("<br>Current recording will be discarded."));
34 }
35 ui->note2Label->setText(note_text);
36 }
37 }
38
39 MoviePlayDialog::~MoviePlayDialog() = default;
40
GetMoviePath() const41 QString MoviePlayDialog::GetMoviePath() const {
42 return ui->filePath->text();
43 }
44
GetGamePath() const45 QString MoviePlayDialog::GetGamePath() const {
46 const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(GetMoviePath().toStdString());
47 return game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::FullPathRole);
48 }
49
OnToolButtonClicked()50 void MoviePlayDialog::OnToolButtonClicked() {
51 const QString path =
52 QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path,
53 tr("Citra TAS Movie (*.ctm)"));
54 if (path.isEmpty()) {
55 return;
56 }
57 ui->filePath->setText(path);
58 UISettings::values.movie_playback_path = path;
59 UpdateUIDisplay();
60 }
61
UpdateUIDisplay()62 void MoviePlayDialog::UpdateUIDisplay() {
63 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
64 ui->gameLineEdit->clear();
65 ui->authorLineEdit->clear();
66 ui->rerecordCountLineEdit->clear();
67 ui->lengthLineEdit->clear();
68 ui->note1Label->setVisible(true);
69
70 const auto path = GetMoviePath().toStdString();
71
72 const auto validation_result = Core::Movie::GetInstance().ValidateMovie(path);
73 if (validation_result == Core::Movie::ValidationResult::Invalid) {
74 ui->note1Label->setText(tr("Invalid movie file."));
75 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
76 return;
77 }
78
79 ui->note2Label->setVisible(true);
80 ui->infoGroupBox->setVisible(true);
81
82 switch (validation_result) {
83 case Core::Movie::ValidationResult::OK:
84 ui->note1Label->setText(QString{});
85 break;
86 case Core::Movie::ValidationResult::RevisionDismatch:
87 ui->note1Label->setText(tr("Revision dismatch, playback may desync."));
88 break;
89 case Core::Movie::ValidationResult::InputCountDismatch:
90 ui->note1Label->setText(tr("Indicated length is incorrect, file may be corrupted."));
91 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
92 break;
93 default:
94 UNREACHABLE();
95 }
96
97 const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(path);
98
99 // Format game title
100 const auto title =
101 game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::TitleRole);
102 if (title.isEmpty()) {
103 ui->gameLineEdit->setText(tr("(unknown)"));
104 ui->note1Label->setText(tr("Game used in this movie is not in game list."));
105 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
106 } else {
107 ui->gameLineEdit->setText(title);
108 }
109
110 ui->authorLineEdit->setText(metadata.author.empty() ? tr("(unknown)")
111 : QString::fromStdString(metadata.author));
112 ui->rerecordCountLineEdit->setText(
113 metadata.rerecord_count == 0 ? tr("(unknown)") : QString::number(metadata.rerecord_count));
114
115 // Format length
116 if (metadata.input_count == 0) {
117 ui->lengthLineEdit->setText(tr("(unknown)"));
118 } else {
119 if (metadata.input_count >
120 BASE_CLOCK_RATE_ARM11 * 24 * 60 * 60 / Service::HID::Module::pad_update_ticks) {
121 // More than a day
122 ui->lengthLineEdit->setText(tr("(>1 day)"));
123 } else {
124 const u64 msecs = Service::HID::Module::pad_update_ticks * metadata.input_count * 1000 /
125 BASE_CLOCK_RATE_ARM11;
126 ui->lengthLineEdit->setText(
127 QTime::fromMSecsSinceStartOfDay(msecs).toString(QStringLiteral("hh:mm:ss.zzz")));
128 }
129 }
130 }
131