From 50d712c3fe3aec2ec90dc6c2d3f224909712a3a9 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 27 Mar 2021 02:19:23 +1000 Subject: [PATCH] CDImage: Support sub-images, use subimages for m3u --- src/common/CMakeLists.txt | 1 + src/common/cd_image.cpp | 74 ++++++-- src/common/cd_image.h | 31 +++- src/common/cd_image_bin.cpp | 8 +- src/common/cd_image_chd.cpp | 8 +- src/common/cd_image_cue.cpp | 8 +- src/common/cd_image_ecm.cpp | 8 +- src/common/cd_image_m3u.cpp | 173 ++++++++++++++++++ src/common/cd_image_mds.cpp | 8 +- src/common/cd_image_memory.cpp | 8 +- src/common/cd_image_pbp.cpp | 8 +- src/common/common.vcxproj | 1 + src/common/common.vcxproj.filters | 1 + src/core/cdrom.cpp | 11 +- src/core/cdrom.h | 2 + src/core/host_interface.cpp | 2 +- src/core/save_state_version.h | 7 +- src/core/system.cpp | 232 ++++++++----------------- src/core/system.h | 30 +--- src/duckstation-qt/qthostinterface.cpp | 12 +- src/frontend-common/cheevos.cpp | 28 ++- src/frontend-common/fullscreen_ui.cpp | 22 +-- src/frontend-common/game_list.cpp | 72 +++----- 23 files changed, 443 insertions(+), 312 deletions(-) create mode 100644 src/common/cd_image_m3u.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9824b7aa2..beebd4280 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(common cd_image_ecm.cpp cd_image_hasher.cpp cd_image_hasher.h + cd_image_m3u.cpp cd_image_memory.cpp cd_image_mds.cpp cd_image_pbp.cpp diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index 73e99e6d7..d0ec5b876 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -1,5 +1,6 @@ #include "cd_image.h" #include "assert.h" +#include "file_system.h" #include "log.h" #include Log_SetChannel(CDImage); @@ -54,6 +55,10 @@ std::unique_ptr CDImage::Open(const char* filename, Common::Error* erro { return OpenPBPImage(filename, error); } + else if (CASE_COMPARE(extension, ".m3u") == 0) + { + return OpenM3uImage(filename, error); + } #undef CASE_COMPARE @@ -254,12 +259,12 @@ bool CDImage::ReadRawSector(void* buffer) bool CDImage::ReadSubChannelQ(SubChannelQ* subq) { - // handle case where we're at the end of the track/index - if (!m_current_index || m_position_in_index == m_current_index->length) - return GenerateSubChannelQ(subq, m_position_on_disc); + return ReadSubChannelQ(subq, *m_current_index, m_position_in_index); +} - // otherwise save the index lookup - GenerateSubChannelQ(subq, m_current_index, m_position_in_index); +bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) +{ + GenerateSubChannelQ(subq, index, lba_in_index); return true; } @@ -268,6 +273,51 @@ bool CDImage::HasNonStandardSubchannel() const return false; } +std::string CDImage::GetMetadata(const std::string_view& type) const +{ + std::string result; + if (type == "title") + result = FileSystem::GetFileTitleFromPath(m_filename); + + return result; +} + +bool CDImage::HasSubImages() const +{ + return false; +} + +u32 CDImage::GetSubImageCount() const +{ + return 0; +} + +u32 CDImage::GetCurrentSubImage() const +{ + return 0; +} + +bool CDImage::SwitchSubImage(u32 index, Common::Error* error) +{ + return false; +} + +std::string CDImage::GetSubImageMetadata(u32 index, const std::string_view& type) const +{ + return {}; +} + +void CDImage::CopyTOC(const CDImage* image) +{ + m_lba_count = image->m_lba_count; + m_indices = image->m_indices; + m_tracks = image->m_tracks; + m_current_index = nullptr; + m_position_in_index = 0; + m_position_in_track = 0; + m_position_on_disc = 0; +} + const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) { for (const Index& index : m_indices) @@ -304,23 +354,23 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) return false; const u32 index_offset = index->start_lba_on_disc - lba; - GenerateSubChannelQ(subq, index, index_offset); + GenerateSubChannelQ(subq, *index, index_offset); return true; } -void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset) +void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) { - subq->control.bits = index->control.bits; + subq->control.bits = index.control.bits; subq->track_number_bcd = - (index->track_number <= m_tracks.size() ? BinaryToBCD(index->track_number) : index->track_number); - subq->index_number_bcd = BinaryToBCD(index->index_number); + (index.track_number <= m_tracks.size() ? BinaryToBCD(index.track_number) : index.track_number); + subq->index_number_bcd = BinaryToBCD(index.index_number); const Position relative_position = - Position::FromLBA(std::abs(static_cast(index->start_lba_in_track + index_offset))); + Position::FromLBA(std::abs(static_cast(index.start_lba_in_track + index_offset))); std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD(); subq->reserved = 0; - const Position absolute_position = Position::FromLBA(index->start_lba_on_disc + index_offset); + const Position absolute_position = Position::FromLBA(index.start_lba_on_disc + index_offset); std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD(); subq->crc = SubChannelQ::ComputeCRC(subq->data); } diff --git a/src/common/cd_image.h b/src/common/cd_image.h index 981bfb9d1..bc8e7bfd2 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -135,6 +135,8 @@ public: BitField digital_copy_permitted; BitField data; BitField four_channel_audio; + + Control& operator=(const Control& c) { bits = c.bits; return *this; } }; struct @@ -202,6 +204,7 @@ public: static std::unique_ptr OpenEcmImage(const char* filename, Common::Error* error); static std::unique_ptr OpenMdsImage(const char* filename, Common::Error* error); static std::unique_ptr OpenPBPImage(const char* filename, Common::Error* error); + static std::unique_ptr OpenM3uImage(const char* filename, Common::Error* error); static std::unique_ptr CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback); @@ -247,7 +250,10 @@ public: bool ReadRawSector(void* buffer); // Reads sub-channel Q for the current LBA. - virtual bool ReadSubChannelQ(SubChannelQ* subq); + bool ReadSubChannelQ(SubChannelQ* subq); + + // Reads sub-channel Q for the specified index+LBA. + virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index); // Returns true if the image has replacement subchannel data. virtual bool HasNonStandardSubchannel() const; @@ -255,7 +261,27 @@ public: // Reads a single sector from an index. virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0; + // Retrieve image metadata. + virtual std::string GetMetadata(const std::string_view& type) const; + + // Returns true if this image type has sub-images (e.g. m3u). + virtual bool HasSubImages() const; + + // Returns the number of sub-images in this image, if the format supports multiple. + virtual u32 GetSubImageCount() const; + + // Returns the current sub-image index, if any. + virtual u32 GetCurrentSubImage() const; + + // Changes the current sub-image. If this fails, the image state is unchanged. + virtual bool SwitchSubImage(u32 index, Common::Error* error); + + // Retrieve sub-image metadata. + virtual std::string GetSubImageMetadata(u32 index, const std::string_view& type) const; + protected: + void CopyTOC(const CDImage* image); + const Index* GetIndexForDiscPosition(LBA pos); const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos); @@ -263,7 +289,7 @@ protected: bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba); /// Generates sub-channel Q from the given index and index-offset. - void GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset); + void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset); /// Synthesis of lead-out data. void AddLeadOutIndex(); @@ -274,6 +300,7 @@ protected: std::vector m_tracks; std::vector m_indices; +private: // Position on disc. LBA m_position_on_disc = 0; diff --git a/src/common/cd_image_bin.cpp b/src/common/cd_image_bin.cpp index abd75d91b..b94f34667 100644 --- a/src/common/cd_image_bin.cpp +++ b/src/common/cd_image_bin.cpp @@ -14,7 +14,7 @@ public: bool Open(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -99,12 +99,12 @@ bool CDImageBin::Open(const char* filename, Common::Error* error) return Seek(1, Position{0, 0, 0}); } -bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageBin::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_chd.cpp b/src/common/cd_image_chd.cpp index 552d0338a..5b1972840 100644 --- a/src/common/cd_image_chd.cpp +++ b/src/common/cd_image_chd.cpp @@ -49,7 +49,7 @@ public: bool Open(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -284,14 +284,14 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error) return Seek(1, Position{0, 0, 0}); } -bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; // TODO: Read subchannel data from CHD - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageCHD::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_cue.cpp b/src/common/cd_image_cue.cpp index 4d484b1ad..d5cca562a 100644 --- a/src/common/cd_image_cue.cpp +++ b/src/common/cd_image_cue.cpp @@ -18,7 +18,7 @@ public: bool OpenAndParse(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -277,12 +277,12 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Common::Error* error) return Seek(1, Position{0, 0, 0}); } -bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageCueSheet::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_ecm.cpp b/src/common/cd_image_ecm.cpp index 6a2e3ce76..85222e2d1 100644 --- a/src/common/cd_image_ecm.cpp +++ b/src/common/cd_image_ecm.cpp @@ -164,7 +164,7 @@ public: bool Open(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -492,12 +492,12 @@ bool CDImageEcm::ReadChunks(u32 disc_offset, u32 size) return true; } -bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageEcm::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_m3u.cpp b/src/common/cd_image_m3u.cpp new file mode 100644 index 000000000..e733ce023 --- /dev/null +++ b/src/common/cd_image_m3u.cpp @@ -0,0 +1,173 @@ +#include "assert.h" +#include "cd_image.h" +#include "cd_subchannel_replacement.h" +#include "file_system.h" +#include "log.h" +#include +#include +#include +#include +Log_SetChannel(CDImageMemory); + +class CDImageM3u : public CDImage +{ +public: + CDImageM3u(); + ~CDImageM3u() override; + + bool Open(const char* path, Common::Error* Error); + + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; + bool HasNonStandardSubchannel() const override; + + bool HasSubImages() const override; + u32 GetSubImageCount() const override; + u32 GetCurrentSubImage() const override; + std::string GetSubImageMetadata(u32 index, const std::string_view& type) const override; + bool SwitchSubImage(u32 index, Common::Error* error) override; + +protected: + bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; + +private: + struct Entry + { + // TODO: Worth storing any other data? + std::string filename; + std::string title; + }; + + std::vector m_entries; + std::unique_ptr m_current_image; + u32 m_current_image_index = UINT32_C(0xFFFFFFFF); +}; + +CDImageM3u::CDImageM3u() = default; + +CDImageM3u::~CDImageM3u() = default; + +bool CDImageM3u::Open(const char* path, Common::Error* error) +{ + std::ifstream ifs(path); + if (!ifs.is_open()) + { + Log_ErrorPrintf("Failed to open %s", path); + return false; + } + + m_filename = path; + + std::vector 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(line.size()) - 1; + while (std::isspace(line[end_offset]) && end_offset > start_offset) + end_offset--; + + // anything? + if (start_offset == end_offset) + continue; + + Entry entry; + entry.filename.assign(line.begin() + start_offset, line.begin() + end_offset + 1); + entry.title = FileSystem::GetFileTitleFromPath(entry.filename); + if (!FileSystem::IsAbsolutePath(entry.filename)) + { + SmallString absolute_path; + FileSystem::BuildPathRelativeToFile(absolute_path, path, entry.filename.c_str()); + entry.filename = absolute_path; + } + + Log_DevPrintf("Read path from m3u: '%s'", entry.filename.c_str()); + m_entries.push_back(std::move(entry)); + } + + Log_InfoPrintf("Loaded %zu paths from m3u '%s'", m_entries.size(), path); + return !m_entries.empty() && SwitchSubImage(0, error); +} + +bool CDImageM3u::HasNonStandardSubchannel() const +{ + return m_current_image->HasNonStandardSubchannel(); +} + +bool CDImageM3u::HasSubImages() const +{ + return true; +} + +u32 CDImageM3u::GetSubImageCount() const +{ + return static_cast(m_entries.size()); +} + +u32 CDImageM3u::GetCurrentSubImage() const +{ + return m_current_image_index; +} + +bool CDImageM3u::SwitchSubImage(u32 index, Common::Error* error) +{ + if (index >= m_entries.size()) + return false; + else if (index == m_current_image_index) + return true; + + const Entry& entry = m_entries[index]; + std::unique_ptr new_image = CDImage::Open(entry.filename.c_str(), error); + if (!new_image) + { + Log_ErrorPrintf("Failed to load subimage %u (%s)", index, entry.filename.c_str()); + return false; + } + + CopyTOC(new_image.get()); + m_current_image = std::move(new_image); + m_current_image_index = index; + if (!Seek(1, Position{0, 0, 0})) + Panic("Failed to seek to start after sub-image change."); + + return true; +} + +std::string CDImageM3u::GetSubImageMetadata(u32 index, const std::string_view& type) const +{ + if (index > m_entries.size()) + return {}; + + if (type == "title") + return m_entries[index].title; + else if (type == "file_title") + return std::string(FileSystem::GetFileTitleFromPath(m_entries[index].filename)); + + return CDImage::GetSubImageMetadata(index, type); +} + +bool CDImageM3u::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +{ + return m_current_image->ReadSectorFromIndex(buffer, index, lba_in_index); +} + +bool CDImageM3u::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) +{ + return m_current_image->ReadSubChannelQ(subq, index, lba_in_index); +} + +std::unique_ptr CDImage::OpenM3uImage(const char* filename, Common::Error* error) +{ + std::unique_ptr image = std::make_unique(); + if (!image->Open(filename, error)) + return {}; + + return image; +} diff --git a/src/common/cd_image_mds.cpp b/src/common/cd_image_mds.cpp index 3bbb188c8..ccb3b71c8 100644 --- a/src/common/cd_image_mds.cpp +++ b/src/common/cd_image_mds.cpp @@ -37,7 +37,7 @@ public: bool OpenAndParse(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -255,12 +255,12 @@ bool CDImageMds::OpenAndParse(const char* filename, Common::Error* error) return Seek(1, Position{0, 0, 0}); } -bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageMds::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_memory.cpp b/src/common/cd_image_memory.cpp index 1f8c281c5..c8be96949 100644 --- a/src/common/cd_image_memory.cpp +++ b/src/common/cd_image_memory.cpp @@ -17,7 +17,7 @@ public: bool CopyImage(CDImage* image, ProgressCallback* progress); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -116,12 +116,12 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress) return Seek(1, Position{0, 0, 0}); } -bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq) +bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImageMemory::HasNonStandardSubchannel() const diff --git a/src/common/cd_image_pbp.cpp b/src/common/cd_image_pbp.cpp index ac51eba8c..98bd7643e 100644 --- a/src/common/cd_image_pbp.cpp +++ b/src/common/cd_image_pbp.cpp @@ -22,7 +22,7 @@ public: bool Open(const char* filename, Common::Error* error); - bool ReadSubChannelQ(SubChannelQ* subq) override; + bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; bool HasNonStandardSubchannel() const override; protected: @@ -698,12 +698,12 @@ bool CDImagePBP::DecompressBlock(BlockInfo block_info) return true; } -bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq) +bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - return CDImage::ReadSubChannelQ(subq); + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); } bool CDImagePBP::HasNonStandardSubchannel() const diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 6aa37d67b..0e1306abe 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -134,6 +134,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 64a72cf9b..f9c3e31ab 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -216,6 +216,7 @@ + diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 904d9483a..34dc0c08a 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -2389,7 +2389,16 @@ void CDROM::DrawDebugWindow() const CDImage* media = m_reader.GetMedia(); const CDImage::Position disc_position = CDImage::Position::FromLBA(m_current_lba); - ImGui::Text("Filename: %s", media->GetFileName().c_str()); + if (media->HasSubImages()) + { + ImGui::Text("Filename: %s [Subimage %u of %u]", media->GetFileName().c_str(), media->GetCurrentSubImage() + 1u, + media->GetSubImageCount()); + } + else + { + ImGui::Text("Filename: %s", media->GetFileName().c_str()); + } + ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_position.minute, disc_position.second, disc_position.frame, disc_position.ToLBA()); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 8affe7683..d8300df64 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -8,6 +8,7 @@ #include "types.h" #include #include +#include #include #include @@ -27,6 +28,7 @@ public: bool HasMedia() const { return m_reader.HasMedia(); } const std::string& GetMediaFileName() const { return m_reader.GetMediaFileName(); } + const CDImage* GetMedia() const { return m_reader.GetMedia(); } bool IsMediaPS1Disc() const; bool DoesMediaRegionMatchConsole() const; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index bbb65fe1f..6b1ab732d 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -837,7 +837,7 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) if (g_settings.memory_card_types != old_settings.memory_card_types || g_settings.memory_card_paths != old_settings.memory_card_paths || (g_settings.memory_card_use_playlist_title != old_settings.memory_card_use_playlist_title && - System::HasMediaPlaylist())) + System::HasMediaSubImages())) { System::UpdateMemoryCards(); } diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index ac8420c71..9a261d34f 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -2,7 +2,7 @@ #include "types.h" static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; -static constexpr u32 SAVE_STATE_VERSION = 50; +static constexpr u32 SAVE_STATE_VERSION = 51; static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42; static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION); @@ -23,9 +23,8 @@ struct SAVE_STATE_HEADER u32 media_filename_length; u32 offset_to_media_filename; - - u32 playlist_filename_length; - u32 offset_to_playlist_filename; + u32 media_subimage_index; + u32 unused_offset_to_playlist_filename; // Unused as of version 51. u32 screenshot_width; u32 screenshot_height; diff --git a/src/core/system.cpp b/src/core/system.cpp index 9818d2a0e..b704b31f8 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -119,10 +119,6 @@ static u32 s_last_global_tick_counter = 0; static Common::Timer s_fps_timer; static Common::Timer s_frame_timer; -// Playlist of disc images. -static std::vector s_media_playlist; -static std::string s_media_playlist_filename; - static std::unique_ptr s_cheat_list; static bool s_memory_saves_enabled = false; @@ -289,12 +285,6 @@ bool IsPsfFileName(const char* path) (StringUtil::Strcasecmp(extension, ".psf") == 0 || StringUtil::Strcasecmp(extension, ".minipsf") == 0)); } -bool IsM3UFileName(const char* path) -{ - const char* extension = std::strrchr(path, '.'); - return (extension && StringUtil::Strcasecmp(extension, ".m3u") == 0); -} - bool IsLoadableFilename(const char* path) { static constexpr auto extensions = make_array(".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs @@ -727,7 +717,6 @@ std::unique_ptr OpenCDImage(const char* path, Common::Error* error, boo bool Boot(const SystemBootParameters& params) { Assert(s_state == State::Shutdown); - Assert(s_media_playlist.empty()); s_state = State::Starting; s_startup_cancelled.store(false); s_region = g_settings.region; @@ -750,6 +739,7 @@ bool Boot(const SystemBootParameters& params) } // Load CD image up and detect region. + Common::Error error; std::unique_ptr media; bool exe_boot = false; bool psf_boot = false; @@ -769,38 +759,8 @@ bool Boot(const SystemBootParameters& params) } else { - u32 playlist_index; - if (IsM3UFileName(params.filename.c_str())) - { - s_media_playlist = ParseM3UFile(params.filename.c_str()); - s_media_playlist_filename = params.filename; - if (s_media_playlist.empty()) - { - g_host_interface->ReportFormattedError("Failed to parse playlist '%s'", params.filename.c_str()); - Shutdown(); - return false; - } - - if (params.media_playlist_index >= s_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; - } - - Common::Error error; - const std::string& media_path = s_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(), &error, params.load_image_to_ram); + Log_InfoPrintf("Loading CD image '%s'...", params.filename.c_str()); + media = OpenCDImage(params.filename.c_str(), &error, params.load_image_to_ram); if (!media) { g_host_interface->ReportFormattedError("Failed to load CD image '%s': %s", params.filename.c_str(), @@ -857,6 +817,15 @@ bool Boot(const SystemBootParameters& params) return false; } + // Switch subimage. + if (media && params.media_playlist_index != 0 && !media->SwitchSubImage(params.media_playlist_index, &error)) + { + g_host_interface->ReportFormattedError("Failed to switch to subimage %u in '%s': %s", params.media_playlist_index, + params.filename.c_str(), error.GetCodeAndMessage().GetCharArray()); + Shutdown(); + return false; + } + // Component setup. if (!Initialize(params.force_software_renderer)) { @@ -1006,8 +975,6 @@ void Shutdown() s_running_game_code.clear(); s_running_game_path.clear(); s_running_game_title.clear(); - s_media_playlist.clear(); - s_media_playlist_filename.clear(); s_cheat_list.reset(); s_state = State::Shutdown; @@ -1199,6 +1166,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di return false; } + Common::Error error; std::string media_filename; std::unique_ptr media; if (header.media_filename_length > 0) @@ -1218,7 +1186,6 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di } else { - Common::Error error; media = OpenCDImage(media_filename.c_str(), &error, false); if (!media) { @@ -1242,27 +1209,27 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di } } - std::string playlist_filename; - std::vector playlist_entries; - if (header.playlist_filename_length > 0) + UpdateRunningGame(media_filename.c_str(), media.get()); + + if (media && header.version >= 51) { - playlist_filename.resize(header.offset_to_playlist_filename); - if (!state->SeekAbsolute(header.offset_to_playlist_filename) || - !state->Read2(playlist_filename.data(), header.playlist_filename_length)) + const u32 num_subimages = media->HasSubImages() ? media->GetSubImageCount() : 0; + if (header.media_subimage_index >= num_subimages || + (media->HasSubImages() && media->GetCurrentSubImage() != header.media_subimage_index && + !media->SwitchSubImage(header.media_subimage_index, &error))) { + g_host_interface->ReportFormattedError( + g_host_interface->TranslateString("System", + "Failed to switch to subimage %u in CD image '%s' used by save state: %s."), + header.media_subimage_index + 1u, media_filename.c_str(), error.GetCodeAndMessage().GetCharArray()); return false; } - - playlist_entries = ParseM3UFile(playlist_filename.c_str()); - if (playlist_entries.empty()) + else { - g_host_interface->ReportFormattedError("Failed to load save state playlist entries from '%s'", - playlist_filename.c_str()); - return false; + Log_InfoPrintf("Switched to subimage %u in '%s'", header.media_subimage_index, media_filename.c_str()); } } - UpdateRunningGame(media_filename.c_str(), media.get()); ClearMemorySaveStates(); if (s_state == State::Starting) @@ -1273,9 +1240,6 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di if (media) g_cdrom.InsertMedia(std::move(media)); - s_media_playlist_filename = std::move(playlist_filename); - s_media_playlist = std::move(playlist_entries); - UpdateControllers(); UpdateMemoryCards(); UpdateMultitaps(); @@ -1288,9 +1252,6 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di else g_cdrom.RemoveMedia(); - s_media_playlist_filename = std::move(playlist_filename); - s_media_playlist = std::move(playlist_entries); - // ensure the correct card is loaded if (g_settings.HasAnyPerGameMemoryCards()) UpdateMemoryCards(); @@ -1338,18 +1299,11 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) const std::string& media_filename = g_cdrom.GetMediaFileName(); header.offset_to_media_filename = static_cast(state->GetPosition()); header.media_filename_length = static_cast(media_filename.length()); + header.media_subimage_index = g_cdrom.GetMedia()->HasSubImages() ? g_cdrom.GetMedia()->GetCurrentSubImage() : 0; if (!media_filename.empty() && !state->Write2(media_filename.data(), header.media_filename_length)) return false; } - if (!s_media_playlist_filename.empty()) - { - header.offset_to_playlist_filename = static_cast(state->GetPosition()); - header.playlist_filename_length = static_cast(s_media_playlist_filename.length()); - if (!state->Write2(s_media_playlist_filename.data(), header.playlist_filename_length)) - return false; - } - // save screenshot if (screenshot_size > 0) { @@ -1847,12 +1801,7 @@ void UpdateMemoryCards() case MemoryCardType::PerGameTitle: { - if (!s_media_playlist_filename.empty() && g_settings.memory_card_use_playlist_title) - { - const std::string playlist_title(GetTitleForPath(s_media_playlist_filename.c_str())); - card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(playlist_title.c_str(), i)); - } - else if (s_running_game_title.empty()) + if (s_running_game_title.empty()) { g_host_interface->AddFormattedOSDMessage( 5.0f, @@ -2021,6 +1970,13 @@ void UpdateRunningGame(const char* path, CDImage* image) { s_running_game_path = path; g_host_interface->GetGameInfo(path, image, &s_running_game_code, &s_running_game_title); + + if (image->HasSubImages() && g_settings.memory_card_use_playlist_title) + { + std::string image_title(image->GetMetadata("title")); + if (!image_title.empty()) + s_running_game_title = std::move(image_title); + } } g_texture_replacements.SetGameID(s_running_game_code); @@ -2050,116 +2006,80 @@ bool CheckForSBIFile(CDImage* image) .c_str()); } -bool HasMediaPlaylist() +bool HasMediaSubImages() { - return !s_media_playlist_filename.empty(); + const CDImage* cdi = g_cdrom.GetMedia(); + return cdi ? cdi->HasSubImages() : false; } -u32 GetMediaPlaylistCount() +u32 GetMediaSubImageCount() { - return static_cast(s_media_playlist.size()); + const CDImage* cdi = g_cdrom.GetMedia(); + return cdi ? cdi->GetSubImageCount() : 0; } -const std::string& GetMediaPlaylistPath(u32 index) +u32 GetMediaSubImageIndex() { - return s_media_playlist[index]; + const CDImage* cdi = g_cdrom.GetMedia(); + return cdi ? cdi->GetCurrentSubImage() : 0; } -u32 GetMediaPlaylistIndex() +u32 GetMediaSubImageIndexForTitle(const std::string_view& title) { - if (!g_cdrom.HasMedia()) - return std::numeric_limits::max(); + const CDImage* cdi = g_cdrom.GetMedia(); + if (!cdi) + return 0; - const std::string& media_path = g_cdrom.GetMediaFileName(); - return GetMediaPlaylistIndexForPath(media_path); -} - -u32 GetMediaPlaylistIndexForPath(const std::string& path) -{ - for (u32 i = 0; i < static_cast(s_media_playlist.size()); i++) + const u32 count = cdi->GetSubImageCount(); + for (u32 i = 0; i < count; i++) { - if (s_media_playlist[i] == path) + if (title == cdi->GetSubImageMetadata(i, "title")) return i; } return std::numeric_limits::max(); } -bool AddMediaPathToPlaylist(const std::string_view& path) +std::string GetMediaSubImageTitle(u32 index) { - if (std::any_of(s_media_playlist.begin(), s_media_playlist.end(), - [&path](const std::string& p) { return (path == p); })) - { - return false; - } + const CDImage* cdi = g_cdrom.GetMedia(); + if (!cdi) + return {}; - s_media_playlist.emplace_back(path); - return true; + return cdi->GetSubImageMetadata(index, "title"); } -bool RemoveMediaPathFromPlaylist(const std::string_view& path) +bool SwitchMediaSubImage(u32 index) { - for (u32 i = 0; i < static_cast(s_media_playlist.size()); i++) - { - if (path == s_media_playlist[i]) - return RemoveMediaPathFromPlaylist(i); - } - - return false; -} - -bool RemoveMediaPathFromPlaylist(u32 index) -{ - if (index >= static_cast(s_media_playlist.size())) + if (!g_cdrom.HasMedia()) return false; - if (GetMediaPlaylistIndex() == index) + std::unique_ptr image = g_cdrom.RemoveMedia(); + Assert(image); + + Common::Error error; + if (!image->SwitchSubImage(index, &error)) { g_host_interface->AddFormattedOSDMessage( - 10.0f, - g_host_interface->TranslateString("System", "Removing current media from playlist, removing media from CD-ROM.")); - g_cdrom.RemoveMedia(); - } - - s_media_playlist.erase(s_media_playlist.begin() + index); - return true; -} - -bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path) -{ - if (index >= static_cast(s_media_playlist.size())) + 10.0f, g_host_interface->TranslateString("OSDMessage", "Failed to switch to subimage %u in '%s': %s."), + index + 1u, image->GetFileName().c_str(), error.GetCodeAndMessage().GetCharArray()); + g_cdrom.InsertMedia(std::move(image)); return false; - - if (GetMediaPlaylistIndex() == index) - { - g_host_interface->AddFormattedOSDMessage( - 10.0f, - g_host_interface->TranslateString("System", "Changing current media from playlist, replacing current media.")); - g_cdrom.RemoveMedia(); - - s_media_playlist[index] = path; - InsertMedia(s_media_playlist[index].c_str()); - } - else - { - s_media_playlist[index] = path; } + g_host_interface->AddFormattedOSDMessage( + 20.0f, g_host_interface->TranslateString("OSDMessage", "Switched to sub-image %s (%u) in '%s'."), + image->GetSubImageMetadata(index, "title").c_str(), index + 1u, image->GetMetadata("title").c_str()); + g_cdrom.InsertMedia(std::move(image)); + + // reinitialize recompiler, because especially with preloading this might overlap the fastmem area + if (g_settings.IsUsingCodeCache()) + CPU::CodeCache::Reinitialize(); + + ClearMemorySaveStates(); return true; } -bool SwitchMediaFromPlaylist(u32 index) -{ - if (index >= s_media_playlist.size()) - return false; - - const std::string& path = s_media_playlist[index]; - if (g_cdrom.HasMedia() && g_cdrom.GetMediaFileName() == path) - return true; - - return InsertMedia(path.c_str()); -} - bool HasCheatList() { return static_cast(s_cheat_list); diff --git a/src/core/system.h b/src/core/system.h index 84446b425..adda418e0 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -63,15 +63,9 @@ bool IsExeFileName(const char* path); /// Returns true if the filename is a Portable Sound Format file we can uncompress/load. bool IsPsfFileName(const char* path); -/// Returns true if the filename is a M3U Playlist we can handle. -bool IsM3UFileName(const char* path); - /// Returns true if the filename is one we can load. bool IsLoadableFilename(const char* path); -/// Parses an M3U playlist, returning the entries. -std::vector ParseM3UFile(const char* path); - /// Returns the preferred console type for a disc. ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); @@ -203,33 +197,23 @@ std::string GetMediaFileName(); bool InsertMedia(const char* path); void RemoveMedia(); -/// Returns true if a playlist is being used. -bool HasMediaPlaylist(); +/// Returns true if this is a multi-subimage image (e.g. m3u). +bool HasMediaSubImages(); /// Returns the number of entries in the media/disc playlist. -u32 GetMediaPlaylistCount(); +u32 GetMediaSubImageCount(); /// Returns the current image from the media/disc playlist. -u32 GetMediaPlaylistIndex(); +u32 GetMediaSubImageIndex(); /// Returns the index of the specified path in the playlist, or UINT32_MAX if it does not exist. -u32 GetMediaPlaylistIndexForPath(const std::string& path); +u32 GetMediaSubImageIndexForTitle(const std::string_view& title); /// Returns the path to the specified playlist index. -const std::string& GetMediaPlaylistPath(u32 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); +std::string GetMediaSubImageTitle(u32 index); /// Switches to the specified media/disc playlist index. -bool SwitchMediaFromPlaylist(u32 index); +bool SwitchMediaSubImage(u32 index); /// Returns true if there is currently a cheat list. bool HasCheatList(); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 7ccc8f5e3..ba8f81b9a 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -914,8 +914,8 @@ void QtHostInterface::changeDiscFromPlaylist(quint32 index) if (System::IsShutdown()) return; - if (!System::SwitchMediaFromPlaylist(index)) - ReportFormattedError("Failed to switch to playlist index %u", index); + if (!System::SwitchMediaSubImage(index)) + ReportFormattedError("Failed to switch to subimage %u", index); } static QString FormatTimestampForSaveStateMenu(u64 timestamp) @@ -1063,15 +1063,15 @@ void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QW void QtHostInterface::populatePlaylistEntryMenu(QMenu* menu) { - if (!System::IsValid()) + if (!System::IsValid() || !System::HasMediaSubImages()) return; QActionGroup* ag = new QActionGroup(menu); - const u32 count = System::GetMediaPlaylistCount(); - const u32 current = System::GetMediaPlaylistIndex(); + const u32 count = System::GetMediaSubImageCount(); + const u32 current = System::GetMediaSubImageIndex(); for (u32 i = 0; i < count; i++) { - QAction* action = ag->addAction(QString::fromStdString(System::GetMediaPlaylistPath(i))); + QAction* action = ag->addAction(QString::fromStdString(System::GetMediaSubImageTitle(i))); action->setCheckable(true); action->setChecked(i == current); connect(action, &QAction::triggered, [this, i]() { changeDiscFromPlaylist(i); }); diff --git a/src/frontend-common/cheevos.cpp b/src/frontend-common/cheevos.cpp index 18e62e27a..c7c1ffa08 100644 --- a/src/frontend-common/cheevos.cpp +++ b/src/frontend-common/cheevos.cpp @@ -847,27 +847,19 @@ void GameChanged(const std::string& path, CDImage* image) s_http_downloader->WaitForAllRequests(); - const u32 playlist_count = System::GetMediaPlaylistCount(); - if (playlist_count > 1 && s_use_first_disc_from_playlist) + if (image && image->HasSubImages() && image->GetCurrentSubImage() != 0) { - // have to pass the path in, because the image isn't owned by the system yet - const u32 playlist_index = System::GetMediaPlaylistIndexForPath(path); - if (playlist_index > 0 && playlist_index < playlist_count) + std::unique_ptr image_copy(CDImage::Open(image->GetFileName().c_str(), nullptr)); + if (!image_copy) { - const std::string& first_disc_path(System::GetMediaPlaylistPath(0)); - std::unique_ptr first_disc_image(CDImage::Open(first_disc_path.c_str(), nullptr)); - if (first_disc_image) - { - Log_InfoPrintf("Using first disc '%s' from playlist (currently '%s')", first_disc_path.c_str(), path.c_str()); - GameChanged(first_disc_path, first_disc_image.get()); - return; - } - else - { - Log_ErrorPrintf("Failed to open first disc '%s' from playlist", first_disc_path.c_str()); - return; - } + Log_ErrorPrintf("Failed to reopen image '%s'", image->GetFileName().c_str()); + return; } + + // this will go to subimage zero automatically + Assert(image_copy->GetCurrentSubImage() == 0); + GameChanged(path, image_copy.get()); + return; } ClearGameInfo(); diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 4cf3ea58b..d54e53748 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -666,20 +666,20 @@ static void DoChangeDiscFromFile() static void DoChangeDisc() { - const u32 playlist_count = System::GetMediaPlaylistCount(); - if (playlist_count == 0) + if (!System::HasMediaSubImages()) { DoChangeDiscFromFile(); return; } - const u32 current_index = (playlist_count > 0) ? System::GetMediaPlaylistIndex() : 0; + const u32 current_index = System::GetMediaSubImageIndex(); + const u32 count = System::GetMediaSubImageCount(); ImGuiFullscreen::ChoiceDialogOptions options; - options.reserve(playlist_count + 1); + options.reserve(count + 1); options.emplace_back("From File...", false); - for (u32 i = 0; i < playlist_count; i++) - options.emplace_back(System::GetMediaPlaylistPath(i), i == current_index); + for (u32 i = 0; i < count; i++) + options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index); auto callback = [](s32 index, const std::string& title, bool checked) { if (index == 0) @@ -690,7 +690,7 @@ static void DoChangeDisc() } else if (index > 0) { - System::SwitchMediaFromPlaylist(static_cast(index - 1)); + System::SwitchMediaSubImage(static_cast(index - 1)); } ClearImGuiFocus(); @@ -1798,10 +1798,10 @@ void DrawSettingsWindow() MenuHeading("Shared Settings"); - settings_changed |= ToggleButton( - "Use Single Card For Playlist", - "When using a playlist (m3u) and per-game (title) memory cards, use a single memory card for all discs.", - &s_settings_copy.memory_card_use_playlist_title); + settings_changed |= ToggleButton("Use Single Card For Sub-Images", + "When using a multi-disc image (m3u/pbp) and per-game (title) memory cards, " + "use a single memory card for all discs.", + &s_settings_copy.memory_card_use_playlist_title); static std::string memory_card_directory; if (memory_card_directory.empty()) diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp index 820841825..e3f4df460 100644 --- a/src/frontend-common/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -147,61 +147,12 @@ bool GameList::GetPsfListEntry(const char* path, GameListEntry* entry) return true; } -bool GameList::GetM3UListEntry(const char* path, GameListEntry* entry) -{ - FILESYSTEM_STAT_DATA ffd; - if (!FileSystem::StatFile(path, &ffd)) - return false; - - std::vector entries = System::ParseM3UFile(path); - if (entries.empty()) - return false; - - entry->code.clear(); - entry->title = System::GetTitleForPath(path); - entry->path = path; - entry->region = DiscRegion::Other; - entry->total_size = 0; - entry->last_modified_time = ffd.ModificationTime.AsUnixTimestamp(); - entry->type = GameListEntryType::Playlist; - entry->compatibility_rating = GameListCompatibilityRating::Unknown; - - for (size_t i = 0; i < entries.size(); i++) - { - std::unique_ptr entry_image = CDImage::Open(entries[i].c_str(), nullptr); - if (!entry_image) - { - Log_ErrorPrintf("Failed to open entry %zu ('%s') in playlist %s", i, entries[i].c_str(), path); - return false; - } - - entry->total_size += static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(entry_image->GetLBACount()); - - if (entry->region == DiscRegion::Other) - entry->region = System::GetRegionForImage(entry_image.get()); - - if (entry->compatibility_rating == GameListCompatibilityRating::Unknown) - { - std::string code = System::GetGameCodeForImage(entry_image.get(), true); - const GameListCompatibilityEntry* compatibility_entry = GetCompatibilityEntryForCode(entry->code); - if (compatibility_entry) - entry->compatibility_rating = compatibility_entry->compatibility_rating; - else - Log_WarningPrintf("'%s' (%s) not found in compatibility list", entry->code.c_str(), entry->title.c_str()); - } - } - - return true; -} - bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) { if (System::IsExeFileName(path.c_str())) return GetExeListEntry(path.c_str(), entry); if (System::IsPsfFileName(path.c_str())) return GetPsfListEntry(path.c_str(), entry); - if (System::IsM3UFileName(path.c_str())) - return GetM3UListEntry(path.c_str(), entry); std::unique_ptr cdi = CDImage::Open(path.c_str(), nullptr); if (!cdi) @@ -218,7 +169,6 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) entry->total_size = static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); entry->type = GameListEntryType::Disc; entry->compatibility_rating = GameListCompatibilityRating::Unknown; - cdi.reset(); if (entry->code.empty()) { @@ -255,6 +205,28 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) entry->settings = *settings; } + if (cdi->HasSubImages()) + { + entry->type = GameListEntryType::Playlist; + + std::string image_title(cdi->GetMetadata("title")); + if (!image_title.empty()) + entry->title = std::move(image_title); + + // get the size of all the subimages + const u32 subimage_count = cdi->GetSubImageCount(); + for (u32 i = 1; i < subimage_count; i++) + { + if (!cdi->SwitchSubImage(i, nullptr)) + { + Log_ErrorPrintf("Failed to switch to subimage %u in '%s'", i, entry->path.c_str()); + continue; + } + + entry->total_size += static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); + } + } + FILESYSTEM_STAT_DATA ffd; if (!FileSystem::StatFile(path.c_str(), &ffd)) return false;