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