System: Support loading m3u playlists
This commit is contained in:
parent
266d70c629
commit
8e1c0a4fe4
|
@ -30,7 +30,7 @@ public:
|
||||||
bool DoState(StateWrapper& sw);
|
bool DoState(StateWrapper& sw);
|
||||||
|
|
||||||
bool HasMedia() const { return m_reader.HasMedia(); }
|
bool HasMedia() const { return m_reader.HasMedia(); }
|
||||||
std::string GetMediaFileName() const { return m_reader.GetMediaFileName(); }
|
const std::string& GetMediaFileName() const { return m_reader.GetMediaFileName(); }
|
||||||
|
|
||||||
void InsertMedia(std::unique_ptr<CDImage> media);
|
void InsertMedia(std::unique_ptr<CDImage> media);
|
||||||
std::unique_ptr<CDImage> RemoveMedia(bool force = false);
|
std::unique_ptr<CDImage> RemoveMedia(bool force = false);
|
||||||
|
|
|
@ -19,7 +19,7 @@ public:
|
||||||
const CDImage::SubChannelQ& GetSectorSubQ() const { return m_subq; }
|
const CDImage::SubChannelQ& GetSectorSubQ() const { return m_subq; }
|
||||||
const bool HasMedia() const { return static_cast<bool>(m_media); }
|
const bool HasMedia() const { return static_cast<bool>(m_media); }
|
||||||
const CDImage* GetMedia() const { return m_media.get(); }
|
const CDImage* GetMedia() const { return m_media.get(); }
|
||||||
const std::string GetMediaFileName() const { return m_media ? m_media->GetFileName() : std::string(); }
|
const std::string& GetMediaFileName() const { return m_media->GetFileName(); }
|
||||||
|
|
||||||
bool IsUsingThread() const { return m_read_thread.joinable(); }
|
bool IsUsingThread() const { return m_read_thread.joinable(); }
|
||||||
void StartThread();
|
void StartThread();
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <fstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <tinyxml2.h>
|
#include <tinyxml2.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -214,6 +215,58 @@ bool GameList::IsPsfFileName(const char* path)
|
||||||
return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0);
|
return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GameList::IsM3UFileName(const char* path)
|
||||||
|
{
|
||||||
|
const char* extension = std::strrchr(path, '.');
|
||||||
|
return (extension && StringUtil::Strcasecmp(extension, ".m3u") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> GameList::ParseM3UFile(const char* path)
|
||||||
|
{
|
||||||
|
std::ifstream ifs(path);
|
||||||
|
if (!ifs.is_open())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open %s", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> entries;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(ifs, line))
|
||||||
|
{
|
||||||
|
u32 start_offset = 0;
|
||||||
|
while (start_offset < line.size() && std::isspace(line[start_offset]))
|
||||||
|
start_offset++;
|
||||||
|
|
||||||
|
// skip comments
|
||||||
|
if (start_offset == line.size() || line[start_offset] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// strip ending whitespace
|
||||||
|
u32 end_offset = static_cast<u32>(line.size()) - 1;
|
||||||
|
while (std::isspace(line[end_offset]) && end_offset > start_offset)
|
||||||
|
end_offset--;
|
||||||
|
|
||||||
|
// anything?
|
||||||
|
if (start_offset == end_offset)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string entry_path(line.begin() + start_offset, line.begin() + end_offset + 1);
|
||||||
|
if (!FileSystem::IsAbsolutePath(entry_path))
|
||||||
|
{
|
||||||
|
SmallString absolute_path;
|
||||||
|
FileSystem::BuildPathRelativeToFile(absolute_path, path, entry_path.c_str());
|
||||||
|
entry_path = absolute_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_DevPrintf("Read path from m3u: '%s'", entry_path.c_str());
|
||||||
|
entries.push_back(std::move(entry_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Loaded %zu paths from m3u '%s'", entries.size(), path);
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating)
|
const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating)
|
||||||
{
|
{
|
||||||
static constexpr std::array<const char*, static_cast<size_t>(GameListCompatibilityRating::Count)> names = {
|
static constexpr std::array<const char*, static_cast<size_t>(GameListCompatibilityRating::Count)> names = {
|
||||||
|
|
|
@ -77,6 +77,12 @@ public:
|
||||||
/// Returns true if the filename is a Portable Sound Format file we can uncompress/load.
|
/// Returns true if the filename is a Portable Sound Format file we can uncompress/load.
|
||||||
static bool IsPsfFileName(const char* path);
|
static bool IsPsfFileName(const char* path);
|
||||||
|
|
||||||
|
/// Returns true if the filename is a M3U Playlist we can handle.
|
||||||
|
static bool IsM3UFileName(const char* path);
|
||||||
|
|
||||||
|
/// Parses an M3U playlist, returning the entries.
|
||||||
|
static std::vector<std::string> ParseM3UFile(const char* path);
|
||||||
|
|
||||||
/// Returns a string representation of a compatibility level.
|
/// Returns a string representation of a compatibility level.
|
||||||
static const char* GetGameListCompatibilityRatingString(GameListCompatibilityRating rating);
|
static const char* GetGameListCompatibilityRatingString(GameListCompatibilityRating rating);
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "timers.h"
|
#include "timers.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <limits>
|
||||||
Log_SetChannel(System);
|
Log_SetChannel(System);
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
@ -175,8 +176,35 @@ bool System::Boot(const SystemBootParameters& params)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("Loading CD image '%s'...", params.filename.c_str());
|
u32 playlist_index;
|
||||||
media = OpenCDImage(params.filename.c_str(), params.override_load_image_to_ram.value_or(false));
|
if (GameList::IsM3UFileName(params.filename.c_str()))
|
||||||
|
{
|
||||||
|
m_media_playlist = GameList::ParseM3UFile(params.filename.c_str());
|
||||||
|
if (m_media_playlist.empty())
|
||||||
|
{
|
||||||
|
m_host_interface->ReportFormattedError("Failed to parse playlist '%s'", params.filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.media_playlist_index >= m_media_playlist.size())
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("Media playlist index %u out of range, using first", params.media_playlist_index);
|
||||||
|
playlist_index = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playlist_index = params.media_playlist_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddMediaPathToPlaylist(params.filename);
|
||||||
|
playlist_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& media_path = m_media_playlist[playlist_index];
|
||||||
|
Log_InfoPrintf("Loading CD image '%s' from playlist index %u...", media_path.c_str(), playlist_index);
|
||||||
|
media = OpenCDImage(media_path.c_str(), params.load_image_to_ram);
|
||||||
if (!media)
|
if (!media)
|
||||||
{
|
{
|
||||||
m_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str());
|
m_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str());
|
||||||
|
@ -518,11 +546,14 @@ bool System::SaveState(ByteStream* state, u32 screenshot_size /* = 128 */)
|
||||||
StringUtil::Strlcpy(header.title, m_running_game_title.c_str(), sizeof(header.title));
|
StringUtil::Strlcpy(header.title, m_running_game_title.c_str(), sizeof(header.title));
|
||||||
StringUtil::Strlcpy(header.game_code, m_running_game_code.c_str(), sizeof(header.game_code));
|
StringUtil::Strlcpy(header.game_code, m_running_game_code.c_str(), sizeof(header.game_code));
|
||||||
|
|
||||||
std::string media_filename = m_cdrom->GetMediaFileName();
|
if (m_cdrom->HasMedia())
|
||||||
header.offset_to_media_filename = static_cast<u32>(state->GetPosition());
|
{
|
||||||
header.media_filename_length = static_cast<u32>(media_filename.length());
|
const std::string& media_filename = m_cdrom->GetMediaFileName();
|
||||||
if (!media_filename.empty() && !state->Write2(media_filename.data(), header.media_filename_length))
|
header.offset_to_media_filename = static_cast<u32>(state->GetPosition());
|
||||||
return false;
|
header.media_filename_length = static_cast<u32>(media_filename.length());
|
||||||
|
if (!media_filename.empty() && !state->Write2(media_filename.data(), header.media_filename_length))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// save screenshot
|
// save screenshot
|
||||||
if (screenshot_size > 0)
|
if (screenshot_size > 0)
|
||||||
|
@ -971,6 +1002,8 @@ bool System::InsertMedia(const char* path)
|
||||||
|
|
||||||
UpdateRunningGame(path, image.get());
|
UpdateRunningGame(path, image.get());
|
||||||
m_cdrom->InsertMedia(std::move(image));
|
m_cdrom->InsertMedia(std::move(image));
|
||||||
|
Log_InfoPrintf("Inserted media from %s (%s, %s)", m_running_game_path.c_str(), m_running_game_code.c_str(),
|
||||||
|
m_running_game_title.c_str());
|
||||||
|
|
||||||
if (GetSettings().HasAnyPerGameMemoryCards())
|
if (GetSettings().HasAnyPerGameMemoryCards())
|
||||||
{
|
{
|
||||||
|
@ -1196,3 +1229,89 @@ void System::UpdateRunningGame(const char* path, CDImage* image)
|
||||||
|
|
||||||
m_host_interface->OnRunningGameChanged();
|
m_host_interface->OnRunningGameChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 System::GetMediaPlaylistIndex() const
|
||||||
|
{
|
||||||
|
if (!m_cdrom->HasMedia())
|
||||||
|
return std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
const std::string& media_path = m_cdrom->GetMediaFileName();
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(m_media_playlist.size()); i++)
|
||||||
|
{
|
||||||
|
if (m_media_playlist[i] == media_path)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::numeric_limits<u32>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool System::AddMediaPathToPlaylist(const std::string_view& path)
|
||||||
|
{
|
||||||
|
if (std::any_of(m_media_playlist.begin(), m_media_playlist.end(),
|
||||||
|
[&path](const std::string& p) { return (path == p); }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_media_playlist.emplace_back(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool System::RemoveMediaPathFromPlaylist(const std::string_view& path)
|
||||||
|
{
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(m_media_playlist.size()); i++)
|
||||||
|
{
|
||||||
|
if (path == m_media_playlist[i])
|
||||||
|
return RemoveMediaPathFromPlaylist(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool System::RemoveMediaPathFromPlaylist(u32 index)
|
||||||
|
{
|
||||||
|
if (index >= static_cast<u32>(m_media_playlist.size()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (GetMediaPlaylistIndex() == index)
|
||||||
|
{
|
||||||
|
m_host_interface->ReportMessage("Removing current media from playlist, removing media from CD-ROM.");
|
||||||
|
m_cdrom->RemoveMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_media_playlist.erase(m_media_playlist.begin() + index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool System::ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path)
|
||||||
|
{
|
||||||
|
if (index >= static_cast<u32>(m_media_playlist.size()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (GetMediaPlaylistIndex() == index)
|
||||||
|
{
|
||||||
|
m_host_interface->ReportMessage("Changing current media from playlist, replacing current media.");
|
||||||
|
m_cdrom->RemoveMedia();
|
||||||
|
|
||||||
|
m_media_playlist[index] = path;
|
||||||
|
InsertMedia(m_media_playlist[index].c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_media_playlist[index] = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool System::SwitchMediaFromPlaylist(u32 index)
|
||||||
|
{
|
||||||
|
if (index >= m_media_playlist.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const std::string& path = m_media_playlist[index];
|
||||||
|
if (m_cdrom->HasMedia() && m_cdrom->GetMediaFileName() == path)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return InsertMedia(path.c_str());
|
||||||
|
}
|
||||||
|
|
|
@ -38,8 +38,9 @@ struct SystemBootParameters
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::optional<bool> override_fast_boot;
|
std::optional<bool> override_fast_boot;
|
||||||
std::optional<bool> override_fullscreen;
|
std::optional<bool> override_fullscreen;
|
||||||
std::optional<bool> override_load_image_to_ram;
|
|
||||||
std::unique_ptr<ByteStream> state_stream;
|
std::unique_ptr<ByteStream> state_stream;
|
||||||
|
u32 media_playlist_index = 0;
|
||||||
|
bool load_image_to_ram = false;
|
||||||
bool force_software_renderer = false;
|
bool force_software_renderer = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,6 +153,28 @@ public:
|
||||||
std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount period, TickCount interval,
|
std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount period, TickCount interval,
|
||||||
TimingEventCallback callback, bool activate);
|
TimingEventCallback callback, bool activate);
|
||||||
|
|
||||||
|
/// Returns the number of entries in the media/disc playlist.
|
||||||
|
ALWAYS_INLINE u32 GetMediaPlaylistCount() const { return static_cast<u32>(m_media_playlist.size()); }
|
||||||
|
|
||||||
|
/// Returns the current image from the media/disc playlist.
|
||||||
|
u32 GetMediaPlaylistIndex() const;
|
||||||
|
|
||||||
|
/// Returns the path to the specified playlist index.
|
||||||
|
const std::string& GetMediaPlaylistPath(u32 index) const { return m_media_playlist[index]; }
|
||||||
|
|
||||||
|
/// Adds a new path to the media playlist.
|
||||||
|
bool AddMediaPathToPlaylist(const std::string_view& path);
|
||||||
|
|
||||||
|
/// Removes a path from the media playlist.
|
||||||
|
bool RemoveMediaPathFromPlaylist(const std::string_view& path);
|
||||||
|
bool RemoveMediaPathFromPlaylist(u32 index);
|
||||||
|
|
||||||
|
/// Changes a path from the media playlist.
|
||||||
|
bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path);
|
||||||
|
|
||||||
|
/// Switches to the specified media/disc playlist index.
|
||||||
|
bool SwitchMediaFromPlaylist(u32 index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
System(HostInterface* host_interface);
|
System(HostInterface* host_interface);
|
||||||
|
|
||||||
|
@ -241,4 +264,7 @@ private:
|
||||||
u32 m_last_global_tick_counter = 0;
|
u32 m_last_global_tick_counter = 0;
|
||||||
Common::Timer m_fps_timer;
|
Common::Timer m_fps_timer;
|
||||||
Common::Timer m_frame_timer;
|
Common::Timer m_frame_timer;
|
||||||
|
|
||||||
|
// Playlist of disc images.
|
||||||
|
std::vector<std::string> m_media_playlist;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue