mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 09:35:40 +00:00
CDImage: Add support for loading mds/mdf images
This commit is contained in:
parent
320394fbd1
commit
753dd0480f
|
@ -17,6 +17,7 @@ add_library(common
|
||||||
cd_image_hasher.cpp
|
cd_image_hasher.cpp
|
||||||
cd_image_hasher.h
|
cd_image_hasher.h
|
||||||
cd_image_memory.cpp
|
cd_image_memory.cpp
|
||||||
|
cd_image_mds.cpp
|
||||||
cd_subchannel_replacement.cpp
|
cd_subchannel_replacement.cpp
|
||||||
cd_subchannel_replacement.h
|
cd_subchannel_replacement.h
|
||||||
cd_xa.cpp
|
cd_xa.cpp
|
||||||
|
|
|
@ -46,6 +46,10 @@ std::unique_ptr<CDImage> CDImage::Open(const char* filename)
|
||||||
{
|
{
|
||||||
return OpenEcmImage(filename);
|
return OpenEcmImage(filename);
|
||||||
}
|
}
|
||||||
|
else if (CASE_COMPARE(extension, ".mds") == 0)
|
||||||
|
{
|
||||||
|
return OpenMdsImage(filename);
|
||||||
|
}
|
||||||
|
|
||||||
#undef CASE_COMPARE
|
#undef CASE_COMPARE
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,7 @@ public:
|
||||||
static std::unique_ptr<CDImage> OpenCueSheetImage(const char* filename);
|
static std::unique_ptr<CDImage> OpenCueSheetImage(const char* filename);
|
||||||
static std::unique_ptr<CDImage> OpenCHDImage(const char* filename);
|
static std::unique_ptr<CDImage> OpenCHDImage(const char* filename);
|
||||||
static std::unique_ptr<CDImage> OpenEcmImage(const char* filename);
|
static std::unique_ptr<CDImage> OpenEcmImage(const char* filename);
|
||||||
|
static std::unique_ptr<CDImage> OpenMdsImage(const char* filename);
|
||||||
static std::unique_ptr<CDImage>
|
static std::unique_ptr<CDImage>
|
||||||
CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback);
|
CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback);
|
||||||
|
|
||||||
|
|
267
src/common/cd_image_mds.cpp
Normal file
267
src/common/cd_image_mds.cpp
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
#include "assert.h"
|
||||||
|
#include "cd_image.h"
|
||||||
|
#include "cd_subchannel_replacement.h"
|
||||||
|
#include "file_system.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <map>
|
||||||
|
Log_SetChannel(CDImageMds);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct TrackEntry
|
||||||
|
{
|
||||||
|
u8 track_type;
|
||||||
|
u8 has_subchannel_data;
|
||||||
|
u8 unk1;
|
||||||
|
u8 unk2;
|
||||||
|
u8 track_number;
|
||||||
|
u8 unk3[4];
|
||||||
|
u8 start_m;
|
||||||
|
u8 start_s;
|
||||||
|
u8 start_f;
|
||||||
|
u32 extra_offset;
|
||||||
|
u8 unk4[24];
|
||||||
|
u32 track_offset_in_mdf;
|
||||||
|
u8 unk5[36];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TrackEntry) == 0x50, "TrackEntry is 0x50 bytes");
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
class CDImageMds : public CDImage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CDImageMds();
|
||||||
|
~CDImageMds() override;
|
||||||
|
|
||||||
|
bool OpenAndParse(const char* filename);
|
||||||
|
|
||||||
|
bool ReadSubChannelQ(SubChannelQ* subq) override;
|
||||||
|
bool HasNonStandardSubchannel() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::FILE* m_mdf_file = nullptr;
|
||||||
|
u64 m_mdf_file_position = 0;
|
||||||
|
CDSubChannelReplacement m_sbi;
|
||||||
|
};
|
||||||
|
|
||||||
|
CDImageMds::CDImageMds() = default;
|
||||||
|
|
||||||
|
CDImageMds::~CDImageMds()
|
||||||
|
{
|
||||||
|
if (m_mdf_file)
|
||||||
|
std::fclose(m_mdf_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDImageMds::OpenAndParse(const char* filename)
|
||||||
|
{
|
||||||
|
std::FILE* mds_fp = FileSystem::OpenCFile(filename, "rb");
|
||||||
|
if (!mds_fp)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open mds '%s': errno %d", filename, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<u8>> mds_data_opt(FileSystem::ReadBinaryFile(mds_fp));
|
||||||
|
std::fclose(mds_fp);
|
||||||
|
if (!mds_data_opt.has_value() || mds_data_opt->size() < 0x54)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to read mds file '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mdf_filename(FileSystem::ReplaceExtension(filename, "mdf"));
|
||||||
|
m_mdf_file = FileSystem::OpenCFile(mdf_filename.c_str(), "rb");
|
||||||
|
if (!m_mdf_file)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open mdf file '%s': errno %d", mdf_filename.c_str(), errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<u8>& mds = mds_data_opt.value();
|
||||||
|
static constexpr char expected_signature[] = "MEDIA DESCRIPTOR";
|
||||||
|
if (std::memcmp(&mds[0], expected_signature, sizeof(expected_signature) - 1) != 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Incorrect signature in '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 session_offset;
|
||||||
|
std::memcpy(&session_offset, &mds[0x50], sizeof(session_offset));
|
||||||
|
if ((session_offset + 24) > mds.size())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Invalid session offset in '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 track_count;
|
||||||
|
u32 track_offset;
|
||||||
|
std::memcpy(&track_count, &mds[session_offset + 14], sizeof(track_count));
|
||||||
|
std::memcpy(&track_offset, &mds[session_offset + 20], sizeof(track_offset));
|
||||||
|
if (track_count > 99 || track_offset >= mds.size())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Invalid track count/block offset %u/%u in '%s'", track_count, track_offset, filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((track_offset + sizeof(TrackEntry)) <= mds.size())
|
||||||
|
{
|
||||||
|
TrackEntry track;
|
||||||
|
std::memcpy(&track, &mds[track_offset], sizeof(track));
|
||||||
|
if (track.track_number < 0xA0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
track_offset += sizeof(TrackEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 track_number = 1; track_number <= track_count; track_number++)
|
||||||
|
{
|
||||||
|
if ((track_offset + sizeof(TrackEntry)) > mds.size())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("End of file in '%s' at track %u", filename, track_number);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackEntry track;
|
||||||
|
std::memcpy(&track, &mds[track_offset], sizeof(track));
|
||||||
|
if (PackedBCDToBinary(track.track_number) != track_number)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Unexpected track number 0x%02X in track %u", track.track_number, track_number);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool contains_subchannel = (track.has_subchannel_data != 0);
|
||||||
|
const u32 track_sector_size = (contains_subchannel ? 2448 : RAW_SECTOR_SIZE);
|
||||||
|
const TrackMode mode = (track.track_type == 0xA9) ? TrackMode::Audio : TrackMode::Mode2Raw;
|
||||||
|
|
||||||
|
if ((track.extra_offset + sizeof(u32) + sizeof(u32)) > mds.size())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Invalid extra offset %u in track %u", track.extra_offset, track_number);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 track_start_lba = Position::FromBCD(track.start_m, track.start_s, track.start_f).ToLBA();
|
||||||
|
u32 track_file_offset = track.track_offset_in_mdf;
|
||||||
|
|
||||||
|
u32 track_pregap;
|
||||||
|
u32 track_length;
|
||||||
|
std::memcpy(&track_pregap, &mds[track.extra_offset], sizeof(track_pregap));
|
||||||
|
std::memcpy(&track_length, &mds[track.extra_offset + sizeof(u32)], sizeof(track_length));
|
||||||
|
|
||||||
|
// precompute subchannel q flags for the whole track
|
||||||
|
// todo: pull from mds?
|
||||||
|
SubChannelQ::Control control{};
|
||||||
|
control.data = mode != TrackMode::Audio;
|
||||||
|
|
||||||
|
// create the index for the pregap
|
||||||
|
if (track_pregap > 0)
|
||||||
|
{
|
||||||
|
if (track_pregap > track_start_lba)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Track pregap %u is too large for start lba %u", track_pregap, track_start_lba);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Index pregap_index = {};
|
||||||
|
pregap_index.start_lba_on_disc = track_start_lba - track_pregap;
|
||||||
|
pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(track_pregap));
|
||||||
|
pregap_index.length = track_pregap;
|
||||||
|
pregap_index.track_number = track_number;
|
||||||
|
pregap_index.index_number = 0;
|
||||||
|
pregap_index.mode = mode;
|
||||||
|
pregap_index.control.bits = control.bits;
|
||||||
|
pregap_index.is_pregap = true;
|
||||||
|
|
||||||
|
const bool pregap_in_file = (track_number > 1);
|
||||||
|
if (pregap_in_file)
|
||||||
|
{
|
||||||
|
pregap_index.file_index = 0;
|
||||||
|
pregap_index.file_offset = track_file_offset;
|
||||||
|
pregap_index.file_sector_size = track_sector_size;
|
||||||
|
track_file_offset += track_pregap * track_sector_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_indices.push_back(pregap_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the track itself
|
||||||
|
m_tracks.push_back(Track{static_cast<u32>(track_number), track_start_lba, static_cast<u32>(m_indices.size()),
|
||||||
|
static_cast<u32>(track_length), mode, control});
|
||||||
|
|
||||||
|
// how many indices in this track?
|
||||||
|
Index last_index;
|
||||||
|
last_index.start_lba_on_disc = track_start_lba;
|
||||||
|
last_index.start_lba_in_track = 0;
|
||||||
|
last_index.track_number = track_number;
|
||||||
|
last_index.index_number = 1;
|
||||||
|
last_index.file_index = 0;
|
||||||
|
last_index.file_sector_size = track_sector_size;
|
||||||
|
last_index.file_offset = track_file_offset;
|
||||||
|
last_index.mode = mode;
|
||||||
|
last_index.control.bits = control.bits;
|
||||||
|
last_index.is_pregap = false;
|
||||||
|
last_index.length = track_length;
|
||||||
|
m_indices.push_back(last_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_tracks.empty())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("File '%s' contains no tracks", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lba_count = m_tracks.back().start_lba + m_tracks.back().length;
|
||||||
|
AddLeadOutIndex();
|
||||||
|
|
||||||
|
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
|
||||||
|
|
||||||
|
return Seek(1, Position{0, 0, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq)
|
||||||
|
{
|
||||||
|
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return CDImage::ReadSubChannelQ(subq);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDImageMds::HasNonStandardSubchannel() const
|
||||||
|
{
|
||||||
|
return (m_sbi.GetReplacementSectorCount() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDImageMds::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
|
||||||
|
{
|
||||||
|
const u64 file_position = index.file_offset + (static_cast<u64>(lba_in_index) * index.file_sector_size);
|
||||||
|
if (m_mdf_file_position != file_position)
|
||||||
|
{
|
||||||
|
if (std::fseek(m_mdf_file, static_cast<long>(file_position), SEEK_SET) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_mdf_file_position = file_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want the subchannel data
|
||||||
|
const u32 read_size = RAW_SECTOR_SIZE;
|
||||||
|
if (std::fread(buffer, read_size, 1, m_mdf_file) != 1)
|
||||||
|
{
|
||||||
|
std::fseek(m_mdf_file, static_cast<long>(m_mdf_file_position), SEEK_SET);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mdf_file_position += read_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CDImage> CDImage::OpenMdsImage(const char* filename)
|
||||||
|
{
|
||||||
|
std::unique_ptr<CDImageMds> image = std::make_unique<CDImageMds>();
|
||||||
|
if (!image->OpenAndParse(filename))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
|
@ -132,6 +132,7 @@
|
||||||
<ClCompile Include="cd_image_cue.cpp" />
|
<ClCompile Include="cd_image_cue.cpp" />
|
||||||
<ClCompile Include="cd_image_ecm.cpp" />
|
<ClCompile Include="cd_image_ecm.cpp" />
|
||||||
<ClCompile Include="cd_image_hasher.cpp" />
|
<ClCompile Include="cd_image_hasher.cpp" />
|
||||||
|
<ClCompile Include="cd_image_mds.cpp" />
|
||||||
<ClCompile Include="cd_image_memory.cpp" />
|
<ClCompile Include="cd_image_memory.cpp" />
|
||||||
<ClCompile Include="crash_handler.cpp" />
|
<ClCompile Include="crash_handler.cpp" />
|
||||||
<ClCompile Include="d3d11\shader_cache.cpp" />
|
<ClCompile Include="d3d11\shader_cache.cpp" />
|
||||||
|
|
|
@ -211,6 +211,7 @@
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="crash_handler.cpp" />
|
<ClCompile Include="crash_handler.cpp" />
|
||||||
<ClCompile Include="cd_image_ecm.cpp" />
|
<ClCompile Include="cd_image_ecm.cpp" />
|
||||||
|
<ClCompile Include="cd_image_mds.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
|
@ -296,10 +296,10 @@ bool IsM3UFileName(const char* path)
|
||||||
|
|
||||||
bool IsLoadableFilename(const char* path)
|
bool IsLoadableFilename(const char* path)
|
||||||
{
|
{
|
||||||
static constexpr auto extensions = make_array(".bin", ".cue", ".img", ".iso", ".chd", ".ecm", // discs
|
static constexpr auto extensions = make_array(".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs
|
||||||
".exe", ".psexe", // exes
|
".exe", ".psexe", // exes
|
||||||
".psf", ".minipsf", // psf
|
".psf", ".minipsf", // psf
|
||||||
".m3u" // playlists
|
".m3u" // playlists
|
||||||
);
|
);
|
||||||
const char* extension = std::strrchr(path, '.');
|
const char* extension = std::strrchr(path, '.');
|
||||||
if (!extension)
|
if (!extension)
|
||||||
|
|
|
@ -33,9 +33,10 @@
|
||||||
|
|
||||||
static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
|
static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
|
||||||
"MainWindow",
|
"MainWindow",
|
||||||
"All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.exe *.psexe *.psf *.minipsf *.m3u);;Single-Track Raw Images "
|
"All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.exe *.psexe *.psf *.minipsf *.m3u);;Single-Track Raw "
|
||||||
"(*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;PlayStation "
|
"Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media "
|
||||||
"Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u)");
|
"Descriptor Sidecar Images (*.mds);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf "
|
||||||
|
"*.minipsf);;Playlists (*.m3u)");
|
||||||
|
|
||||||
ALWAYS_INLINE static QString getWindowTitle()
|
ALWAYS_INLINE static QString getWindowTitle()
|
||||||
{
|
{
|
||||||
|
|
|
@ -513,7 +513,8 @@ bool InvalidateCachedTexture(const std::string& path)
|
||||||
|
|
||||||
static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters()
|
static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters()
|
||||||
{
|
{
|
||||||
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.psexe", "*.exe", "*.psf", "*.minipsf", "*.m3u"};
|
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm",
|
||||||
|
"*.mds", "*.psexe", "*.exe", "*.psf", "*.minipsf", "*.m3u"};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DoStartPath(const std::string& path, bool allow_resume)
|
static void DoStartPath(const std::string& path, bool allow_resume)
|
||||||
|
|
Loading…
Reference in a new issue