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_ecm.cpp (11560B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "cd_image.h"
      5 #include "cd_subchannel_replacement.h"
      6 
      7 #include "common/assert.h"
      8 #include "common/error.h"
      9 #include "common/file_system.h"
     10 #include "common/log.h"
     11 #include "common/path.h"
     12 
     13 #include "libchdr/cdrom.h"
     14 
     15 #include <array>
     16 #include <map>
     17 
     18 Log_SetChannel(CDImageEcm);
     19 
     20 namespace {
     21 
     22 class CDImageEcm : public CDImage
     23 {
     24 public:
     25   CDImageEcm();
     26   ~CDImageEcm() override;
     27 
     28   bool Open(const char* filename, Error* error);
     29 
     30   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
     31   bool HasNonStandardSubchannel() const override;
     32   s64 GetSizeOnDisk() const override;
     33 
     34 protected:
     35   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
     36 
     37 private:
     38   bool ReadChunks(u32 disc_offset, u32 size);
     39 
     40   std::FILE* m_fp = nullptr;
     41 
     42   enum class SectorType : u32
     43   {
     44     Raw = 0x00,
     45     Mode1 = 0x01,
     46     Mode2Form1 = 0x02,
     47     Mode2Form2 = 0x03,
     48     Count,
     49   };
     50 
     51   static constexpr std::array<u32, static_cast<u32>(SectorType::Count)> s_sector_sizes = {
     52     0x930, // raw
     53     0x803, // mode1
     54     0x804, // mode2form1
     55     0x918, // mode2form2
     56   };
     57 
     58   static constexpr std::array<u32, static_cast<u32>(SectorType::Count)> s_chunk_sizes = {
     59     0,    // raw
     60     2352, // mode1
     61     2336, // mode2form1
     62     2336, // mode2form2
     63   };
     64 
     65   struct SectorEntry
     66   {
     67     u32 file_offset;
     68     u32 chunk_size;
     69     SectorType type;
     70   };
     71 
     72   using DataMap = std::map<u32, SectorEntry>;
     73 
     74   DataMap m_data_map;
     75   std::vector<u8> m_chunk_buffer;
     76   u32 m_chunk_start = 0;
     77 
     78   CDSubChannelReplacement m_sbi;
     79 };
     80 
     81 } // namespace
     82 
     83 CDImageEcm::CDImageEcm() = default;
     84 
     85 CDImageEcm::~CDImageEcm()
     86 {
     87   if (m_fp)
     88     std::fclose(m_fp);
     89 }
     90 
     91 bool CDImageEcm::Open(const char* filename, Error* error)
     92 {
     93   m_filename = filename;
     94   m_fp = FileSystem::OpenSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite, error);
     95   if (!m_fp)
     96   {
     97     Error::AddPrefixFmt(error, "Failed to open binfile '{}': ", Path::GetFileName(filename));
     98     return false;
     99   }
    100 
    101   s64 file_size;
    102   if (FileSystem::FSeek64(m_fp, 0, SEEK_END) != 0 || (file_size = FileSystem::FTell64(m_fp)) <= 0 ||
    103       FileSystem::FSeek64(m_fp, 0, SEEK_SET) != 0)
    104   {
    105     ERROR_LOG("Get file size failed: errno {}", errno);
    106     if (error)
    107       error->SetErrno(errno);
    108 
    109     return false;
    110   }
    111 
    112   char header[4];
    113   if (std::fread(header, sizeof(header), 1, m_fp) != 1 || header[0] != 'E' || header[1] != 'C' || header[2] != 'M' ||
    114       header[3] != 0)
    115   {
    116     ERROR_LOG("Failed to read/invalid header");
    117     Error::SetStringView(error, "Failed to read/invalid header");
    118     return false;
    119   }
    120 
    121   // build sector map
    122   u32 file_offset = static_cast<u32>(std::ftell(m_fp));
    123   u32 disc_offset = 0;
    124 
    125   for (;;)
    126   {
    127     int bits = std::fgetc(m_fp);
    128     if (bits == EOF)
    129     {
    130       ERROR_LOG("Unexpected EOF after {} chunks", m_data_map.size());
    131       Error::SetStringFmt(error, "Unexpected EOF after {} chunks", m_data_map.size());
    132       return false;
    133     }
    134 
    135     file_offset++;
    136     const SectorType type = static_cast<SectorType>(static_cast<u32>(bits) & 0x03u);
    137     u32 count = (static_cast<u32>(bits) >> 2) & 0x1F;
    138     u32 shift = 5;
    139     while (bits & 0x80)
    140     {
    141       bits = std::fgetc(m_fp);
    142       if (bits == EOF)
    143       {
    144         ERROR_LOG("Unexpected EOF after {} chunks", m_data_map.size());
    145         Error::SetStringFmt(error, "Unexpected EOF after {} chunks", m_data_map.size());
    146         return false;
    147       }
    148 
    149       count |= (static_cast<u32>(bits) & 0x7F) << shift;
    150       shift += 7;
    151       file_offset++;
    152     }
    153 
    154     if (count == 0xFFFFFFFFu)
    155       break;
    156 
    157     // for this sector
    158     count++;
    159 
    160     if (count >= 0x80000000u)
    161     {
    162       ERROR_LOG("Corrupted header after {} chunks", m_data_map.size());
    163       Error::SetStringFmt(error, "Corrupted header after {} chunks", m_data_map.size());
    164       return false;
    165     }
    166 
    167     if (type == SectorType::Raw)
    168     {
    169       while (count > 0)
    170       {
    171         const u32 size = std::min<u32>(count, 2352);
    172         m_data_map.emplace(disc_offset, SectorEntry{file_offset, size, type});
    173         disc_offset += size;
    174         file_offset += size;
    175         count -= size;
    176 
    177         if (static_cast<s64>(file_offset) > file_size)
    178         {
    179           ERROR_LOG("Out of file bounds after {} chunks", m_data_map.size());
    180           Error::SetStringFmt(error, "Out of file bounds after {} chunks", m_data_map.size());
    181         }
    182       }
    183     }
    184     else
    185     {
    186       const u32 size = s_sector_sizes[static_cast<u32>(type)];
    187       const u32 chunk_size = s_chunk_sizes[static_cast<u32>(type)];
    188       for (u32 i = 0; i < count; i++)
    189       {
    190         m_data_map.emplace(disc_offset, SectorEntry{file_offset, chunk_size, type});
    191         disc_offset += chunk_size;
    192         file_offset += size;
    193 
    194         if (static_cast<s64>(file_offset) > file_size)
    195         {
    196           ERROR_LOG("Out of file bounds after {} chunks", m_data_map.size());
    197           Error::SetStringFmt(error, "Out of file bounds after {} chunks", m_data_map.size());
    198         }
    199       }
    200     }
    201 
    202     if (std::fseek(m_fp, file_offset, SEEK_SET) != 0)
    203     {
    204       ERROR_LOG("Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size());
    205       Error::SetStringFmt(error, "Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size());
    206       return false;
    207     }
    208   }
    209 
    210   if (m_data_map.empty())
    211   {
    212     ERROR_LOG("No data in image '{}'", filename);
    213     Error::SetStringFmt(error, "No data in image '{}'", filename);
    214     return false;
    215   }
    216 
    217   m_lba_count = disc_offset / RAW_SECTOR_SIZE;
    218   if ((disc_offset % RAW_SECTOR_SIZE) != 0)
    219     WARNING_LOG("ECM image is misaligned with offset {}", disc_offset);
    220   if (m_lba_count == 0)
    221     return false;
    222 
    223   SubChannelQ::Control control = {};
    224   TrackMode mode = TrackMode::Mode2Raw;
    225   control.data = mode != TrackMode::Audio;
    226 
    227   // Two seconds default pregap.
    228   const u32 pregap_frames = 2 * FRAMES_PER_SECOND;
    229   Index pregap_index = {};
    230   pregap_index.file_sector_size = RAW_SECTOR_SIZE;
    231   pregap_index.start_lba_on_disc = 0;
    232   pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
    233   pregap_index.length = pregap_frames;
    234   pregap_index.track_number = 1;
    235   pregap_index.index_number = 0;
    236   pregap_index.mode = mode;
    237   pregap_index.submode = CDImage::SubchannelMode::None;
    238   pregap_index.control.bits = control.bits;
    239   pregap_index.is_pregap = true;
    240   m_indices.push_back(pregap_index);
    241 
    242   // Data index.
    243   Index data_index = {};
    244   data_index.file_index = 0;
    245   data_index.file_offset = 0;
    246   data_index.file_sector_size = RAW_SECTOR_SIZE;
    247   data_index.start_lba_on_disc = pregap_index.length;
    248   data_index.track_number = 1;
    249   data_index.index_number = 1;
    250   data_index.start_lba_in_track = 0;
    251   data_index.length = m_lba_count;
    252   data_index.mode = mode;
    253   data_index.submode = CDImage::SubchannelMode::None;
    254   data_index.control.bits = control.bits;
    255   m_indices.push_back(data_index);
    256 
    257   // Assume a single track.
    258   m_tracks.push_back(Track{static_cast<u32>(1), data_index.start_lba_on_disc, static_cast<u32>(0), m_lba_count, mode,
    259                            SubchannelMode::None, control});
    260 
    261   AddLeadOutIndex();
    262 
    263   m_sbi.LoadFromImagePath(filename);
    264 
    265   m_chunk_buffer.reserve(RAW_SECTOR_SIZE * 2);
    266   return Seek(1, Position{0, 0, 0});
    267 }
    268 
    269 bool CDImageEcm::ReadChunks(u32 disc_offset, u32 size)
    270 {
    271   DataMap::iterator next =
    272     m_data_map.lower_bound((disc_offset > RAW_SECTOR_SIZE) ? (disc_offset - RAW_SECTOR_SIZE) : 0);
    273   DataMap::iterator current = m_data_map.begin();
    274   while (next != m_data_map.end() && next->first <= disc_offset)
    275     current = next++;
    276 
    277   // extra bytes if we need to buffer some at the start
    278   m_chunk_start = current->first;
    279   m_chunk_buffer.clear();
    280   if (m_chunk_start < disc_offset)
    281     size += (disc_offset - current->first);
    282 
    283   u32 total_bytes_read = 0;
    284   while (total_bytes_read < size)
    285   {
    286     if (current == m_data_map.end() || std::fseek(m_fp, current->second.file_offset, SEEK_SET) != 0)
    287       return false;
    288 
    289     const u32 chunk_size = current->second.chunk_size;
    290     const u32 chunk_start = static_cast<u32>(m_chunk_buffer.size());
    291     m_chunk_buffer.resize(chunk_start + chunk_size);
    292 
    293     if (current->second.type == SectorType::Raw)
    294     {
    295       if (std::fread(&m_chunk_buffer[chunk_start], chunk_size, 1, m_fp) != 1)
    296         return false;
    297 
    298       total_bytes_read += chunk_size;
    299     }
    300     else
    301     {
    302       // u8* sector = &m_chunk_buffer[chunk_start];
    303       u8 sector[RAW_SECTOR_SIZE];
    304 
    305       // TODO: needed?
    306       std::memset(sector, 0, RAW_SECTOR_SIZE);
    307       std::memset(sector + 1, 0xFF, 10);
    308 
    309       u32 skip;
    310       switch (current->second.type)
    311       {
    312         case SectorType::Mode1:
    313         {
    314           sector[0x0F] = 0x01;
    315           if (std::fread(sector + 0x00C, 0x003, 1, m_fp) != 1 || std::fread(sector + 0x010, 0x800, 1, m_fp) != 1)
    316             return false;
    317 
    318           edc_set(&sector[2064], edc_compute(sector, 2064));
    319           ecc_generate(sector);
    320           skip = 0;
    321         }
    322         break;
    323 
    324         case SectorType::Mode2Form1:
    325         {
    326           sector[0x0F] = 0x02;
    327           if (std::fread(sector + 0x014, 0x804, 1, m_fp) != 1)
    328             return false;
    329 
    330           sector[0x10] = sector[0x14];
    331           sector[0x11] = sector[0x15];
    332           sector[0x12] = sector[0x16];
    333           sector[0x13] = sector[0x17];
    334 
    335           edc_set(&sector[2072], edc_compute(&sector[16], 2056));
    336           ecc_generate(sector);
    337           skip = 0x10;
    338         }
    339         break;
    340 
    341         case SectorType::Mode2Form2:
    342         {
    343           sector[0x0F] = 0x02;
    344           if (std::fread(sector + 0x014, 0x918, 1, m_fp) != 1)
    345             return false;
    346 
    347           sector[0x10] = sector[0x14];
    348           sector[0x11] = sector[0x15];
    349           sector[0x12] = sector[0x16];
    350           sector[0x13] = sector[0x17];
    351 
    352           edc_set(&sector[2348], edc_compute(&sector[16], 2332));
    353           skip = 0x10;
    354         }
    355         break;
    356 
    357         default:
    358           UnreachableCode();
    359           return false;
    360       }
    361 
    362       std::memcpy(&m_chunk_buffer[chunk_start], sector + skip, chunk_size);
    363       total_bytes_read += chunk_size;
    364     }
    365 
    366     ++current;
    367   }
    368 
    369   return true;
    370 }
    371 
    372 bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
    373 {
    374   if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
    375     return true;
    376 
    377   return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
    378 }
    379 
    380 bool CDImageEcm::HasNonStandardSubchannel() const
    381 {
    382   return (m_sbi.GetReplacementSectorCount() > 0);
    383 }
    384 
    385 bool CDImageEcm::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
    386 {
    387   const u32 file_start = static_cast<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);
    388   const u32 file_end = file_start + RAW_SECTOR_SIZE;
    389 
    390   if (file_start < m_chunk_start || file_end > (m_chunk_start + m_chunk_buffer.size()))
    391   {
    392     if (!ReadChunks(file_start, RAW_SECTOR_SIZE))
    393       return false;
    394   }
    395 
    396   DebugAssert(file_start >= m_chunk_start && file_end <= (m_chunk_start + m_chunk_buffer.size()));
    397 
    398   const size_t chunk_offset = static_cast<size_t>(file_start - m_chunk_start);
    399   std::memcpy(buffer, &m_chunk_buffer[chunk_offset], RAW_SECTOR_SIZE);
    400   return true;
    401 }
    402 
    403 s64 CDImageEcm::GetSizeOnDisk() const
    404 {
    405   return FileSystem::FSize64(m_fp);
    406 }
    407 
    408 std::unique_ptr<CDImage> CDImage::OpenEcmImage(const char* filename, Error* error)
    409 {
    410   std::unique_ptr<CDImageEcm> image = std::make_unique<CDImageEcm>();
    411   if (!image->Open(filename, error))
    412     return {};
    413 
    414   return image;
    415 }