duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

cd_image_mds.cpp (9708B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "assert.h"
      5 #include "cd_image.h"
      6 #include "cd_subchannel_replacement.h"
      7 
      8 #include "common/error.h"
      9 #include "common/file_system.h"
     10 #include "common/log.h"
     11 #include "common/path.h"
     12 
     13 #include <algorithm>
     14 #include <cerrno>
     15 #include <map>
     16 
     17 Log_SetChannel(CDImageMds);
     18 
     19 namespace {
     20 
     21 #pragma pack(push, 1)
     22 struct TrackEntry
     23 {
     24   u8 track_type;
     25   u8 has_subchannel_data;
     26   u8 unk1;
     27   u8 unk2;
     28   u8 track_number;
     29   u8 unk3[4];
     30   u8 start_m;
     31   u8 start_s;
     32   u8 start_f;
     33   u32 extra_offset;
     34   u8 unk4[24];
     35   u32 track_offset_in_mdf;
     36   u8 unk5[36];
     37 };
     38 static_assert(sizeof(TrackEntry) == 0x50, "TrackEntry is 0x50 bytes");
     39 #pragma pack(pop)
     40 
     41 class CDImageMds : public CDImage
     42 {
     43 public:
     44   CDImageMds();
     45   ~CDImageMds() override;
     46 
     47   bool OpenAndParse(const char* filename, Error* error);
     48 
     49   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
     50   bool HasNonStandardSubchannel() const override;
     51   s64 GetSizeOnDisk() const override;
     52 
     53 protected:
     54   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
     55 
     56 private:
     57   std::FILE* m_mdf_file = nullptr;
     58   u64 m_mdf_file_position = 0;
     59   CDSubChannelReplacement m_sbi;
     60 };
     61 
     62 } // namespace
     63 
     64 CDImageMds::CDImageMds() = default;
     65 
     66 CDImageMds::~CDImageMds()
     67 {
     68   if (m_mdf_file)
     69     std::fclose(m_mdf_file);
     70 }
     71 
     72 bool CDImageMds::OpenAndParse(const char* filename, Error* error)
     73 {
     74   std::FILE* mds_fp = FileSystem::OpenSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite, error);
     75   if (!mds_fp)
     76   {
     77     Error::AddPrefixFmt(error, "Failed to open mds '{}': ", Path::GetFileName(filename));
     78     return false;
     79   }
     80 
     81   std::optional<DynamicHeapArray<u8>> mds_data_opt(FileSystem::ReadBinaryFile(mds_fp));
     82   std::fclose(mds_fp);
     83   if (!mds_data_opt.has_value() || mds_data_opt->size() < 0x54)
     84   {
     85     ERROR_LOG("Failed to read mds file '{}'", Path::GetFileName(filename));
     86     Error::SetStringFmt(error, "Failed to read mds file '{}'", filename);
     87     return false;
     88   }
     89 
     90   std::string mdf_filename(Path::ReplaceExtension(filename, "mdf"));
     91   m_mdf_file = FileSystem::OpenSharedCFile(mdf_filename.c_str(), "rb", FileSystem::FileShareMode::DenyWrite, error);
     92   if (!m_mdf_file)
     93   {
     94     Error::AddPrefixFmt(error, "Failed to open mdf file '{}': ", Path::GetFileName(mdf_filename));
     95     return false;
     96   }
     97 
     98   const DynamicHeapArray<u8>& mds = mds_data_opt.value();
     99   static constexpr char expected_signature[] = "MEDIA DESCRIPTOR";
    100   if (std::memcmp(&mds[0], expected_signature, sizeof(expected_signature) - 1) != 0)
    101   {
    102     ERROR_LOG("Incorrect signature in '{}'", Path::GetFileName(filename));
    103     Error::SetStringFmt(error, "Incorrect signature in '{}'", Path::GetFileName(filename));
    104     return false;
    105   }
    106 
    107   u32 session_offset;
    108   std::memcpy(&session_offset, &mds[0x50], sizeof(session_offset));
    109   if ((session_offset + 24) > mds.size())
    110   {
    111     ERROR_LOG("Invalid session offset in '{}'", Path::GetFileName(filename));
    112     Error::SetStringFmt(error, "Invalid session offset in '{}'", Path::GetFileName(filename));
    113     return false;
    114   }
    115 
    116   u16 track_count;
    117   u32 track_offset;
    118   std::memcpy(&track_count, &mds[session_offset + 14], sizeof(track_count));
    119   std::memcpy(&track_offset, &mds[session_offset + 20], sizeof(track_offset));
    120   if (track_count > 99 || track_offset >= mds.size())
    121   {
    122     ERROR_LOG("Invalid track count/block offset {}/{} in '{}'", track_count, track_offset, Path::GetFileName(filename));
    123     Error::SetStringFmt(error, "Invalid track count/block offset {}/{} in '{}'", track_count, track_offset,
    124                         Path::GetFileName(filename));
    125     return false;
    126   }
    127 
    128   while ((track_offset + sizeof(TrackEntry)) <= mds.size())
    129   {
    130     TrackEntry track;
    131     std::memcpy(&track, &mds[track_offset], sizeof(track));
    132     if (track.track_number < 0xA0)
    133       break;
    134 
    135     track_offset += sizeof(TrackEntry);
    136   }
    137 
    138   for (u32 track_number = 1; track_number <= track_count; track_number++)
    139   {
    140     if ((track_offset + sizeof(TrackEntry)) > mds.size())
    141     {
    142       ERROR_LOG("End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
    143       Error::SetStringFmt(error, "End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
    144       return false;
    145     }
    146 
    147     TrackEntry track;
    148     std::memcpy(&track, &mds[track_offset], sizeof(track));
    149     track_offset += sizeof(TrackEntry);
    150 
    151     if (PackedBCDToBinary(track.track_number) != track_number)
    152     {
    153       ERROR_LOG("Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
    154       Error::SetStringFmt(error, "Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
    155       return false;
    156     }
    157 
    158     const bool contains_subchannel = (track.has_subchannel_data != 0);
    159     const u32 track_sector_size = (contains_subchannel ? 2448 : RAW_SECTOR_SIZE);
    160     const TrackMode mode = (track.track_type == 0xA9) ? TrackMode::Audio : TrackMode::Mode2Raw;
    161 
    162     if ((track.extra_offset + sizeof(u32) + sizeof(u32)) > mds.size())
    163     {
    164       ERROR_LOG("Invalid extra offset {} in track {}", track.extra_offset, track_number);
    165       Error::SetStringFmt(error, "Invalid extra offset {} in track {}", track.extra_offset, track_number);
    166       return false;
    167     }
    168 
    169     u32 track_start_lba = Position::FromBCD(track.start_m, track.start_s, track.start_f).ToLBA();
    170     u32 track_file_offset = track.track_offset_in_mdf;
    171 
    172     u32 track_pregap;
    173     u32 track_length;
    174     std::memcpy(&track_pregap, &mds[track.extra_offset], sizeof(track_pregap));
    175     std::memcpy(&track_length, &mds[track.extra_offset + sizeof(u32)], sizeof(track_length));
    176 
    177     // precompute subchannel q flags for the whole track
    178     // todo: pull from mds?
    179     SubChannelQ::Control control{};
    180     control.data = mode != TrackMode::Audio;
    181 
    182     // create the index for the pregap
    183     if (track_pregap > 0)
    184     {
    185       if (track_pregap > track_start_lba)
    186       {
    187         ERROR_LOG("Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
    188         Error::SetStringFmt(error, "Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
    189         return false;
    190       }
    191 
    192       Index pregap_index = {};
    193       pregap_index.start_lba_on_disc = track_start_lba - track_pregap;
    194       pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(track_pregap));
    195       pregap_index.length = track_pregap;
    196       pregap_index.track_number = track_number;
    197       pregap_index.index_number = 0;
    198       pregap_index.mode = mode;
    199       pregap_index.submode = CDImage::SubchannelMode::None;
    200       pregap_index.control.bits = control.bits;
    201       pregap_index.is_pregap = true;
    202 
    203       const bool pregap_in_file = (track_number > 1);
    204       if (pregap_in_file)
    205       {
    206         pregap_index.file_index = 0;
    207         pregap_index.file_offset = track_file_offset;
    208         pregap_index.file_sector_size = track_sector_size;
    209         track_file_offset += track_pregap * track_sector_size;
    210       }
    211 
    212       m_indices.push_back(pregap_index);
    213     }
    214 
    215     // add the track itself
    216     m_tracks.push_back(Track{static_cast<u32>(track_number), track_start_lba, static_cast<u32>(m_indices.size()),
    217                              static_cast<u32>(track_length), mode, SubchannelMode::None, control});
    218 
    219     // how many indices in this track?
    220     Index last_index;
    221     last_index.start_lba_on_disc = track_start_lba;
    222     last_index.start_lba_in_track = 0;
    223     last_index.track_number = track_number;
    224     last_index.index_number = 1;
    225     last_index.file_index = 0;
    226     last_index.file_sector_size = track_sector_size;
    227     last_index.file_offset = track_file_offset;
    228     last_index.mode = mode;
    229     last_index.submode = CDImage::SubchannelMode::None;
    230     last_index.control.bits = control.bits;
    231     last_index.is_pregap = false;
    232     last_index.length = track_length;
    233     m_indices.push_back(last_index);
    234   }
    235 
    236   if (m_tracks.empty())
    237   {
    238     ERROR_LOG("File '{}' contains no tracks", Path::GetFileName(filename));
    239     Error::SetStringFmt(error, "File '{}' contains no tracks", Path::GetFileName(filename));
    240     return false;
    241   }
    242 
    243   m_lba_count = m_tracks.back().start_lba + m_tracks.back().length;
    244   AddLeadOutIndex();
    245 
    246   m_sbi.LoadFromImagePath(filename);
    247 
    248   return Seek(1, Position{0, 0, 0});
    249 }
    250 
    251 bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
    252 {
    253   if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
    254     return true;
    255 
    256   return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
    257 }
    258 
    259 bool CDImageMds::HasNonStandardSubchannel() const
    260 {
    261   return (m_sbi.GetReplacementSectorCount() > 0);
    262 }
    263 
    264 bool CDImageMds::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
    265 {
    266   const u64 file_position = index.file_offset + (static_cast<u64>(lba_in_index) * index.file_sector_size);
    267   if (m_mdf_file_position != file_position)
    268   {
    269     if (std::fseek(m_mdf_file, static_cast<long>(file_position), SEEK_SET) != 0)
    270       return false;
    271 
    272     m_mdf_file_position = file_position;
    273   }
    274 
    275   // we don't want the subchannel data
    276   const u32 read_size = RAW_SECTOR_SIZE;
    277   if (std::fread(buffer, read_size, 1, m_mdf_file) != 1)
    278   {
    279     std::fseek(m_mdf_file, static_cast<long>(m_mdf_file_position), SEEK_SET);
    280     return false;
    281   }
    282 
    283   m_mdf_file_position += read_size;
    284   return true;
    285 }
    286 
    287 s64 CDImageMds::GetSizeOnDisk() const
    288 {
    289   return FileSystem::FSize64(m_mdf_file);
    290 }
    291 
    292 std::unique_ptr<CDImage> CDImage::OpenMdsImage(const char* filename, Error* error)
    293 {
    294   std::unique_ptr<CDImageMds> image = std::make_unique<CDImageMds>();
    295   if (!image->OpenAndParse(filename, error))
    296     return {};
    297 
    298   return image;
    299 }