Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions pcsx2-qt/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

#include <QtCore/QDateTime>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog>
Expand All @@ -63,7 +64,7 @@
#endif

const char* MainWindow::OPEN_FILE_FILTER =
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump);;"
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump *.m3u);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"Media Descriptor File (*.mdf);;"
Expand All @@ -74,17 +75,19 @@ const char* MainWindow::OPEN_FILE_FILTER =
"ELF Executables (*.elf);;"
"IRX Executables (*.irx);;"
"GS Dumps (*.gs *.gs.xz *.gs.zst);;"
"Block Dumps (*.dump)");
"Block Dumps (*.dump);;"
"M3U Playlists (*.m3u)");

const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.dump);;"
const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.mdf *.chd *.cso *.zso *.gz *.dump *.m3u);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"Media Descriptor File (*.mdf);;"
"MAME CHD Images (*.chd);;"
"CSO Images (*.cso);;"
"ZSO Images (*.zso);;"
"GZ Images (*.gz);;"
"Block Dumps (*.dump)");
"Block Dumps (*.dump);;"
"M3U Playlists (*.m3u)");

MainWindow* g_main_window = nullptr;

Expand Down Expand Up @@ -1685,11 +1688,37 @@ void MainWindow::onRemoveDiscActionTriggered()

void MainWindow::onChangeDiscMenuAboutToShow()
{
// TODO: This is where we would populate the playlist if there is one.
const std::vector<std::string>& playlist = VMManager::GetM3UPlaylistEntries();
if (playlist.empty())
return;

m_change_disc_playlist_actions.clear();
m_change_disc_playlist_actions.append(m_ui.menuChangeDisc->addSeparator());

const int active_index = VMManager::GetM3UPlaylistCurrentIndex();
for (int i = 0; i < static_cast<int>(playlist.size()); ++i)
{
const QString label = tr("%1: %2").arg(i + 1).arg(QFileInfo(QString::fromStdString(playlist[i])).fileName());
QAction* action = m_ui.menuChangeDisc->addAction(label);
action->setCheckable(true);
action->setChecked(i == active_index);
action->setToolTip(QString::fromStdString(playlist[i]));
const std::string target_path = playlist[i];
connect(action, &QAction::triggered, this, [target_path]() {
g_emu_thread->changeDisc(CDVD_SourceType::Iso, QString::fromStdString(target_path));
});
m_change_disc_playlist_actions.append(action);
}
}

void MainWindow::onChangeDiscMenuAboutToHide()
{
for (QAction* action : m_change_disc_playlist_actions)
{
m_ui.menuChangeDisc->removeAction(action);
action->deleteLater();
}
m_change_disc_playlist_actions.clear();
}

void MainWindow::onLoadStateMenuAboutToShow()
Expand Down
2 changes: 2 additions & 0 deletions pcsx2-qt/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ private Q_SLOTS:
InputRecordingViewer* m_input_recording_viewer = nullptr;
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;

QList<QAction*> m_change_disc_playlist_actions;

QProgressBar* m_status_progress_widget = nullptr;
QLabel* m_status_verbose_widget = nullptr;
QLabel* m_status_renderer_widget = nullptr;
Expand Down
48 changes: 45 additions & 3 deletions pcsx2/ImGui/FullscreenUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetOpenFileFilters()

ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
{
return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.zso", "*.gz"};
return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.zso", "*.gz", "*.m3u"};
}

ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetAudioFileFilters()
Expand Down Expand Up @@ -964,9 +964,16 @@ void FullscreenUI::RequestChangeDisc()
{
ConfirmShutdownIfMemcardBusy([](bool result) {
if (result)
DoChangeDiscFromFile();
{
if (!VMManager::GetM3UPlaylistEntries().empty())
OpenPauseSubMenu(PauseSubMenu::ChangeDisc);
else
DoChangeDiscFromFile();
}
else
{
ClosePauseMenu();
}
});
}

Expand Down Expand Up @@ -1634,10 +1641,14 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
11, // None
4, // Exit
3, // Achievements
0, // ChangeDisc placeholder
};

const std::vector<std::string>& change_disc_playlist = VMManager::GetM3UPlaylistEntries();
const u32 change_disc_item_count = static_cast<u32>(change_disc_playlist.size()) + 2; // +2 for back and from file
const bool just_focused = ResetFocusHere();
BeginMenuButtons(submenu_item_count[static_cast<u32>(s_current_pause_submenu)], 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
BeginMenuButtons((s_current_pause_submenu == PauseSubMenu::ChangeDisc) ? change_disc_item_count : submenu_item_count[static_cast<u32>(s_current_pause_submenu)],
1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);

if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup))
Expand Down Expand Up @@ -1668,6 +1679,10 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
first_id = ImGui::GetID(FSUI_ICONSTR(ICON_PF_BACKWARD, "Back To Pause Menu"));
last_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_STOPWATCH, "Leaderboards"));
break;
case PauseSubMenu::ChangeDisc:
first_id = ImGui::GetID(FSUI_ICONSTR(ICON_PF_BACKWARD, "Back To Pause Menu"));
last_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "From File..."));
break;
}

if (first_id != 0 && last_id != 0)
Expand Down Expand Up @@ -1798,6 +1813,33 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
OpenLeaderboardsWindow();
}
break;


case PauseSubMenu::ChangeDisc:
{
if (ActiveButton(FSUI_ICONSTR(ICON_PF_BACKWARD, "Back To Pause Menu"), false) || WantsToCloseMenu())
OpenPauseSubMenu(PauseSubMenu::None);

const std::vector<std::string>& playlist = VMManager::GetM3UPlaylistEntries();
const int active_index = VMManager::GetM3UPlaylistCurrentIndex();

for (int i = 0; i < static_cast<int>(playlist.size()); ++i)
{
const std::string label = fmt::format("{}: {}", i + 1, Path::GetFileName(playlist[i]));
const bool is_active = (i == active_index);
if (ActiveButton(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, label.c_str()), is_active))
{
Host::RunOnCPUThread([path = playlist[i]]() { VMManager::ChangeDisc(CDVD_SourceType::Iso, path); });
ClosePauseMenu();
}
}

if (ActiveButton(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "From File..."), false))
{
DoChangeDiscFromFile();
}
}
break;
}

EndMenuButtons();
Expand Down
1 change: 1 addition & 0 deletions pcsx2/ImGui/FullscreenUI_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ namespace FullscreenUI
None,
Exit,
Achievements,
ChangeDisc,
};

enum class SettingsPage
Expand Down
118 changes: 116 additions & 2 deletions pcsx2/VMManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "common/Console.h"
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/FPControl.h"
#include "common/ScopedGuard.h"
#include "common/SettingsWrapper.h"
Expand Down Expand Up @@ -1235,16 +1236,83 @@ bool VMManager::HasBootedELF()
return s_current_crc != 0 && s_elf_executed;
}

static std::string s_current_m3u_playlist_source;
static std::vector<std::string> s_current_m3u_playlist_entries;
static int s_current_m3u_playlist_index = -1;

static void ClearM3UPlaylist()
{
s_current_m3u_playlist_source.clear();
s_current_m3u_playlist_entries.clear();
s_current_m3u_playlist_index = -1;
}

static void SetM3UPlaylist(const std::string& m3u_path, std::vector<std::string> entries, int current_index)
{
s_current_m3u_playlist_source = m3u_path;
s_current_m3u_playlist_entries = std::move(entries);
s_current_m3u_playlist_index = current_index;
}

static void UpdateM3UPlaylistCurrentIndex(const std::string& current_disc_path)
{
s_current_m3u_playlist_index = -1;
for (size_t i = 0; i < s_current_m3u_playlist_entries.size(); ++i)
{
if (s_current_m3u_playlist_entries[i] == current_disc_path)
{
s_current_m3u_playlist_index = static_cast<int>(i);
return;
}
}
}

static std::vector<std::string> ParseM3UPlaylist(const std::string& m3u_path)
{
std::vector<std::string> disc_paths;

const std::optional<std::string> content = FileSystem::ReadFileToString(m3u_path.c_str());
if (!content.has_value())
return disc_paths;

const std::string_view m3u_dir = Path::GetDirectory(m3u_path);

const std::vector<std::string_view> lines = StringUtil::SplitString(*content, '\n', false);
for (const std::string_view line : lines)
{
// Skip empty lines and comments
const std::string_view trimmed = StringUtil::StripWhitespace(line);
if (trimmed.empty() || trimmed[0] == '#')
continue;

// Resolve relative paths
std::string disc_path;
if (Path::IsAbsolute(trimmed))
{
disc_path = std::string(trimmed);
}
else
{
disc_path = Path::Combine(m3u_dir, trimmed);
}

disc_paths.push_back(std::move(disc_path));
}

return disc_paths;
}

bool VMManager::AutoDetectSource(const std::string& filename, Error* error)
{
ClearM3UPlaylist();
if (!filename.empty())
{
if (!FileSystem::FileExists(filename.c_str()))
{
Error::SetStringFmt(error, TRANSLATE_FS("VMManager", "Requested filename '{}' does not exist."), filename);
return false;
}

if (IsGSDumpFileName(filename))
{
CDVDsys_ChangeSource(CDVD_SourceType::NoDisc);
Expand All @@ -1268,6 +1336,20 @@ bool VMManager::AutoDetectSource(const std::string& filename, Error* error)
s_elf_override = filename;
return true;
}
else if (StringUtil::EndsWithNoCase(filename, ".m3u"))
{
const std::vector<std::string> disc_paths = ParseM3UPlaylist(filename);
if (disc_paths.empty())
{
Error::SetStringFmt(error, TRANSLATE_FS("VMManager", "M3U playlist '{}' does not contain any valid disc paths."), filename);
return false;
}

SetM3UPlaylist(filename, disc_paths, 0);
CDVDsys_SetFile(CDVD_SourceType::Iso, s_current_m3u_playlist_entries[0]);
CDVDsys_ChangeSource(CDVD_SourceType::Iso);
return true;
}
else
{
// TODO: Maybe we should check if it's a valid iso here...
Expand Down Expand Up @@ -2317,6 +2399,28 @@ bool VMManager::ChangeDisc(CDVD_SourceType source, std::string path)
const CDVD_SourceType old_type = CDVDsys_GetSourceType();
const std::string old_path(CDVDsys_GetFile(old_type));

if (source == CDVD_SourceType::Iso && StringUtil::EndsWithNoCase(path, ".m3u"))
{
const std::vector<std::string> disc_paths = ParseM3UPlaylist(path);
if (disc_paths.empty())
{
return false;
}

SetM3UPlaylist(path, disc_paths, 0);
path = s_current_m3u_playlist_entries[0];
}
else if (source != CDVD_SourceType::Iso)
{
ClearM3UPlaylist();
}
else if (!path.empty())
{
UpdateM3UPlaylistCurrentIndex(path);
if (s_current_m3u_playlist_index < 0)
ClearM3UPlaylist();
}

CDVDsys_ChangeSource(source);
if (!path.empty())
CDVDsys_SetFile(source, path);
Expand Down Expand Up @@ -2371,6 +2475,16 @@ bool VMManager::ChangeDisc(CDVD_SourceType source, std::string path)
return result;
}

const std::vector<std::string>& VMManager::GetM3UPlaylistEntries()
{
return s_current_m3u_playlist_entries;
}

int VMManager::GetM3UPlaylistCurrentIndex()
{
return s_current_m3u_playlist_index;
}

bool VMManager::SetELFOverride(std::string path)
{
if (!HasValidVM() || (!path.empty() && !FileSystem::FileExists(path.c_str())))
Expand Down Expand Up @@ -2424,7 +2538,7 @@ bool VMManager::IsSaveStateFileName(const std::string_view path)

bool VMManager::IsDiscFileName(const std::string_view path)
{
static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".zso", ".chd"};
static const char* extensions[] = {".iso", ".bin", ".img", ".mdf", ".gz", ".cso", ".zso", ".chd", ".m3u"};

for (const char* test_extension : extensions)
{
Expand Down
6 changes: 6 additions & 0 deletions pcsx2/VMManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ namespace VMManager
/// Returns the path of the disc currently running.
std::string GetDiscPath();

/// Returns the list of disc paths from the currently loaded M3U playlist.
const std::vector<std::string>& GetM3UPlaylistEntries();

/// Returns the current selected disc index for the loaded M3U playlist, or -1 if none.
int GetM3UPlaylistCurrentIndex();

/// Returns the serial of the disc currently running.
std::string GetDiscSerial();

Expand Down
Loading