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(§or[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(§or[2072], edc_compute(§or[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(§or[2348], edc_compute(§or[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 }