CDImage: Support sub-images, use subimages for m3u
This commit is contained in:
parent
573aa6d9cc
commit
50d712c3fe
|
@ -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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "cd_image.h"
|
||||
#include "assert.h"
|
||||
#include "file_system.h"
|
||||
#include "log.h"
|
||||
#include <array>
|
||||
Log_SetChannel(CDImage);
|
||||
|
@ -54,6 +55,10 @@ std::unique_ptr<CDImage> 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<s32>(index->start_lba_in_track + index_offset)));
|
||||
Position::FromLBA(std::abs(static_cast<s32>(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);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,8 @@ public:
|
|||
BitField<u8, bool, 5, 1> digital_copy_permitted;
|
||||
BitField<u8, bool, 6, 1> data;
|
||||
BitField<u8, bool, 7, 1> four_channel_audio;
|
||||
|
||||
Control& operator=(const Control& c) { bits = c.bits; return *this; }
|
||||
};
|
||||
|
||||
struct
|
||||
|
@ -202,6 +204,7 @@ public:
|
|||
static std::unique_ptr<CDImage> OpenEcmImage(const char* filename, Common::Error* error);
|
||||
static std::unique_ptr<CDImage> OpenMdsImage(const char* filename, Common::Error* error);
|
||||
static std::unique_ptr<CDImage> OpenPBPImage(const char* filename, Common::Error* error);
|
||||
static std::unique_ptr<CDImage> OpenM3uImage(const char* filename, Common::Error* error);
|
||||
static std::unique_ptr<CDImage>
|
||||
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<Track> m_tracks;
|
||||
std::vector<Index> m_indices;
|
||||
|
||||
private:
|
||||
// Position on disc.
|
||||
LBA m_position_on_disc = 0;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
#include "assert.h"
|
||||
#include "cd_image.h"
|
||||
#include "cd_subchannel_replacement.h"
|
||||
#include "file_system.h"
|
||||
#include "log.h"
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
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<Entry> m_entries;
|
||||
std::unique_ptr<CDImage> 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<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;
|
||||
|
||||
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<u32>(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<CDImage> 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> CDImage::OpenM3uImage(const char* filename, Common::Error* error)
|
||||
{
|
||||
std::unique_ptr<CDImageM3u> image = std::make_unique<CDImageM3u>();
|
||||
if (!image->Open(filename, error))
|
||||
return {};
|
||||
|
||||
return image;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
<ClCompile Include="cd_image_cue.cpp" />
|
||||
<ClCompile Include="cd_image_ecm.cpp" />
|
||||
<ClCompile Include="cd_image_hasher.cpp" />
|
||||
<ClCompile Include="cd_image_m3u.cpp" />
|
||||
<ClCompile Include="cd_image_mds.cpp" />
|
||||
<ClCompile Include="cd_image_memory.cpp" />
|
||||
<ClCompile Include="cd_image_pbp.cpp" />
|
||||
|
|
|
@ -216,6 +216,7 @@
|
|||
<ClCompile Include="cd_image_mds.cpp" />
|
||||
<ClCompile Include="cd_image_pbp.cpp" />
|
||||
<ClCompile Include="error.cpp" />
|
||||
<ClCompile Include="cd_image_m3u.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
|
|
|
@ -2389,7 +2389,16 @@ void CDROM::DrawDebugWindow()
|
|||
const CDImage* media = m_reader.GetMedia();
|
||||
const CDImage::Position disc_position = CDImage::Position::FromLBA(m_current_lba);
|
||||
|
||||
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());
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "types.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<std::string> s_media_playlist;
|
||||
static std::string s_media_playlist_filename;
|
||||
|
||||
static std::unique_ptr<CheatList> 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<CDImage> 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<CDImage> 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<CDImage> 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<std::string> playlist_entries;
|
||||
if (header.playlist_filename_length > 0)
|
||||
{
|
||||
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))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
playlist_entries = ParseM3UFile(playlist_filename.c_str());
|
||||
if (playlist_entries.empty())
|
||||
{
|
||||
g_host_interface->ReportFormattedError("Failed to load save state playlist entries from '%s'",
|
||||
playlist_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateRunningGame(media_filename.c_str(), media.get());
|
||||
|
||||
if (media && header.version >= 51)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_InfoPrintf("Switched to subimage %u in '%s'", header.media_subimage_index, media_filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
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<u32>(state->GetPosition());
|
||||
header.media_filename_length = static_cast<u32>(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<u32>(state->GetPosition());
|
||||
header.playlist_filename_length = static_cast<u32>(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<u32>(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<u32>::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<u32>(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<u32>::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<u32>(s_media_playlist.size()); i++)
|
||||
{
|
||||
if (path == s_media_playlist[i])
|
||||
return RemoveMediaPathFromPlaylist(i);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RemoveMediaPathFromPlaylist(u32 index)
|
||||
{
|
||||
if (index >= static_cast<u32>(s_media_playlist.size()))
|
||||
if (!g_cdrom.HasMedia())
|
||||
return false;
|
||||
|
||||
if (GetMediaPlaylistIndex() == index)
|
||||
std::unique_ptr<CDImage> 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();
|
||||
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;
|
||||
}
|
||||
|
||||
s_media_playlist.erase(s_media_playlist.begin() + index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path)
|
||||
{
|
||||
if (index >= static_cast<u32>(s_media_playlist.size()))
|
||||
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();
|
||||
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));
|
||||
|
||||
s_media_playlist[index] = path;
|
||||
InsertMedia(s_media_playlist[index].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
s_media_playlist[index] = path;
|
||||
}
|
||||
// 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<bool>(s_cheat_list);
|
||||
|
|
|
@ -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<std::string> 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();
|
||||
|
|
|
@ -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); });
|
||||
|
|
|
@ -847,28 +847,20 @@ 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<CDImage> image_copy(CDImage::Open(image->GetFileName().c_str(), nullptr));
|
||||
if (!image_copy)
|
||||
{
|
||||
const std::string& first_disc_path(System::GetMediaPlaylistPath(0));
|
||||
std::unique_ptr<CDImage> 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());
|
||||
Log_ErrorPrintf("Failed to reopen image '%s'", image->GetFileName().c_str());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open first disc '%s' from playlist", first_disc_path.c_str());
|
||||
|
||||
// this will go to subimage zero automatically
|
||||
Assert(image_copy->GetCurrentSubImage() == 0);
|
||||
GameChanged(path, image_copy.get());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClearGameInfo();
|
||||
ClearGamePath();
|
||||
|
|
|
@ -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<u32>(index - 1));
|
||||
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
|
||||
}
|
||||
|
||||
ClearImGuiFocus();
|
||||
|
@ -1798,9 +1798,9 @@ 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.",
|
||||
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;
|
||||
|
|
|
@ -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<std::string> 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<CDImage> 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<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(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<CDImage> 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<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(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<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(cdi->GetLBACount());
|
||||
}
|
||||
}
|
||||
|
||||
FILESYSTEM_STAT_DATA ffd;
|
||||
if (!FileSystem::StatFile(path.c_str(), &ffd))
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue