cd_image_ppf.cpp (12865B)
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/file_system.h" 9 #include "common/log.h" 10 #include "common/path.h" 11 12 #include <algorithm> 13 #include <cerrno> 14 #include <map> 15 #include <unordered_map> 16 17 Log_SetChannel(CDImagePPF); 18 19 namespace { 20 21 enum : u32 22 { 23 DESC_SIZE = 50, 24 BLOCKCHECK_SIZE = 1024 25 }; 26 27 class CDImagePPF : public CDImage 28 { 29 public: 30 CDImagePPF(); 31 ~CDImagePPF() override; 32 33 bool Open(const char* filename, std::unique_ptr<CDImage> parent_image); 34 35 bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; 36 bool HasNonStandardSubchannel() const override; 37 s64 GetSizeOnDisk() const override; 38 39 std::string GetMetadata(std::string_view type) const override; 40 std::string GetSubImageMetadata(u32 index, std::string_view type) const override; 41 42 PrecacheResult Precache(ProgressCallback* progress = ProgressCallback::NullProgressCallback) override; 43 44 protected: 45 bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; 46 47 private: 48 bool ReadV1Patch(std::FILE* fp); 49 bool ReadV2Patch(std::FILE* fp); 50 bool ReadV3Patch(std::FILE* fp); 51 u32 ReadFileIDDiz(std::FILE* fp, u32 version); 52 53 bool AddPatch(u64 offset, const u8* patch, u32 patch_size); 54 55 std::unique_ptr<CDImage> m_parent_image; 56 std::vector<u8> m_replacement_data; 57 std::unordered_map<u32, u32> m_replacement_map; 58 s64 m_patch_size = 0; 59 u32 m_replacement_offset = 0; 60 }; 61 62 } // namespace 63 64 CDImagePPF::CDImagePPF() = default; 65 66 CDImagePPF::~CDImagePPF() = default; 67 68 bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_image) 69 { 70 auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite); 71 if (!fp) 72 { 73 ERROR_LOG("Failed to open '{}'", Path::GetFileName(filename)); 74 return false; 75 } 76 77 m_patch_size = FileSystem::FSize64(fp.get()); 78 79 u32 magic; 80 if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1) 81 { 82 ERROR_LOG("Failed to read magic from '{}'", Path::GetFileName(filename)); 83 return false; 84 } 85 86 // work out the offset from the start of the parent image which we need to patch 87 // i.e. the two second implicit pregap on data sectors 88 if (parent_image->GetTrack(1).mode != TrackMode::Audio) 89 m_replacement_offset = parent_image->GetIndex(1).start_lba_on_disc; 90 91 // copy all the stuff from the parent image 92 m_filename = parent_image->GetFileName(); 93 m_tracks = parent_image->GetTracks(); 94 m_indices = parent_image->GetIndices(); 95 m_parent_image = std::move(parent_image); 96 97 if (magic == 0x33465050) // PPF3 98 return ReadV3Patch(fp.get()); 99 else if (magic == 0x32465050) // PPF2 100 return ReadV2Patch(fp.get()); 101 else if (magic == 0x31465050) // PPF1 102 return ReadV1Patch(fp.get()); 103 104 ERROR_LOG("Unknown PPF magic {:08X}", magic); 105 return false; 106 } 107 108 u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version) 109 { 110 const int lenidx = (version == 2) ? 4 : 2; 111 112 u32 magic; 113 if (std::fseek(fp, -(lenidx + 4), SEEK_END) != 0 || std::fread(&magic, sizeof(magic), 1, fp) != 1) [[unlikely]] 114 { 115 WARNING_LOG("Failed to read diz magic"); 116 return 0; 117 } 118 119 if (magic != 0x5A49442E) // .DIZ 120 return 0; 121 122 u32 dlen = 0; 123 if (std::fseek(fp, -lenidx, SEEK_END) != 0 || std::fread(&dlen, lenidx, 1, fp) != 1) [[unlikely]] 124 { 125 WARNING_LOG("Failed to read diz length"); 126 return 0; 127 } 128 129 if (dlen > static_cast<u32>(std::ftell(fp))) [[unlikely]] 130 { 131 WARNING_LOG("diz length out of range"); 132 return 0; 133 } 134 135 std::string fdiz; 136 fdiz.resize(dlen); 137 if (std::fseek(fp, -(lenidx + 16 + static_cast<int>(dlen)), SEEK_END) != 0 || 138 std::fread(fdiz.data(), 1, dlen, fp) != dlen) [[unlikely]] 139 { 140 WARNING_LOG("Failed to read fdiz"); 141 return 0; 142 } 143 144 INFO_LOG("File_Id.diz: {}", fdiz); 145 return dlen; 146 } 147 148 bool CDImagePPF::ReadV1Patch(std::FILE* fp) 149 { 150 char desc[DESC_SIZE + 1] = {}; 151 if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]] 152 { 153 ERROR_LOG("Failed to read description"); 154 return false; 155 } 156 157 u32 filelen; 158 if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 56) 159 [[unlikely]] 160 { 161 ERROR_LOG("Invalid ppf file"); 162 return false; 163 } 164 165 u32 count = filelen - 56; 166 if (count <= 0) 167 return false; 168 169 if (std::fseek(fp, 56, SEEK_SET) != 0) 170 return false; 171 172 std::vector<u8> temp; 173 while (count > 0) 174 { 175 u32 offset; 176 u8 chunk_size; 177 if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1) 178 [[unlikely]] 179 { 180 ERROR_LOG("Incomplete ppf"); 181 return false; 182 } 183 184 temp.resize(chunk_size); 185 if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]] 186 { 187 ERROR_LOG("Failed to read patch data"); 188 return false; 189 } 190 191 if (!AddPatch(offset, temp.data(), chunk_size)) [[unlikely]] 192 return false; 193 194 count -= sizeof(offset) + sizeof(chunk_size) + chunk_size; 195 } 196 197 INFO_LOG("Loaded {} replacement sectors from version 1 PPF", m_replacement_map.size()); 198 return true; 199 } 200 201 bool CDImagePPF::ReadV2Patch(std::FILE* fp) 202 { 203 char desc[DESC_SIZE + 1] = {}; 204 if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]] 205 { 206 ERROR_LOG("Failed to read description"); 207 return false; 208 } 209 210 INFO_LOG("Patch description: {}", desc); 211 212 const u32 idlen = ReadFileIDDiz(fp, 2); 213 214 u32 origlen; 215 if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&origlen, sizeof(origlen), 1, fp) != 1) [[unlikely]] 216 { 217 ERROR_LOG("Failed to read size"); 218 return false; 219 } 220 221 std::vector<u8> temp; 222 temp.resize(BLOCKCHECK_SIZE); 223 if (std::fread(temp.data(), 1, BLOCKCHECK_SIZE, fp) != BLOCKCHECK_SIZE) [[unlikely]] 224 { 225 ERROR_LOG("Failed to read blockcheck data"); 226 return false; 227 } 228 229 // do blockcheck 230 { 231 u32 blockcheck_src_sector = 16 + m_replacement_offset; 232 u32 blockcheck_src_offset = 32; 233 234 std::vector<u8> src_sector(RAW_SECTOR_SIZE); 235 if (m_parent_image->Seek(blockcheck_src_sector) && m_parent_image->ReadRawSector(src_sector.data(), nullptr)) 236 { 237 if (std::memcmp(&src_sector[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0) 238 WARNING_LOG("Blockcheck failed. The patch may not apply correctly."); 239 } 240 else 241 { 242 WARNING_LOG("Failed to read blockcheck sector {}", blockcheck_src_sector); 243 } 244 } 245 246 u32 filelen; 247 if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 1084) 248 [[unlikely]] 249 { 250 ERROR_LOG("Invalid ppf file"); 251 return false; 252 } 253 254 u32 count = filelen - 1084; 255 if (idlen > 0) 256 count -= (idlen + 38); 257 258 if (count <= 0) 259 return false; 260 261 if (std::fseek(fp, 1084, SEEK_SET) != 0) 262 return false; 263 264 while (count > 0) 265 { 266 u32 offset; 267 u8 chunk_size; 268 if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1) 269 [[unlikely]] 270 { 271 ERROR_LOG("Incomplete ppf"); 272 return false; 273 } 274 275 temp.resize(chunk_size); 276 if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]] 277 { 278 ERROR_LOG("Failed to read patch data"); 279 return false; 280 } 281 282 if (!AddPatch(offset, temp.data(), chunk_size)) 283 return false; 284 285 count -= sizeof(offset) + sizeof(chunk_size) + chunk_size; 286 } 287 288 INFO_LOG("Loaded {} replacement sectors from version 2 PPF", m_replacement_map.size()); 289 return true; 290 } 291 292 bool CDImagePPF::ReadV3Patch(std::FILE* fp) 293 { 294 char desc[DESC_SIZE + 1] = {}; 295 if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) 296 { 297 ERROR_LOG("Failed to read description"); 298 return false; 299 } 300 301 INFO_LOG("Patch description: {}", desc); 302 303 u32 idlen = ReadFileIDDiz(fp, 3); 304 305 u8 image_type; 306 u8 block_check; 307 u8 undo; 308 if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&image_type, sizeof(image_type), 1, fp) != 1 || 309 std::fread(&block_check, sizeof(block_check), 1, fp) != 1 || std::fread(&undo, sizeof(undo), 1, fp) != 1) 310 { 311 ERROR_LOG("Failed to read headers"); 312 return false; 313 } 314 315 // TODO: Blockcheck 316 317 std::fseek(fp, 0, SEEK_END); 318 u32 count = static_cast<u32>(std::ftell(fp)); 319 320 u32 seekpos = (block_check) ? 1084 : 60; 321 if (seekpos >= count) 322 { 323 ERROR_LOG("File is too short"); 324 return false; 325 } 326 327 count -= seekpos; 328 if (idlen > 0) 329 { 330 const u32 extralen = idlen + 18 + 16 + 2; 331 if (count < extralen) 332 { 333 ERROR_LOG("File is too short (diz)"); 334 return false; 335 } 336 337 count -= extralen; 338 } 339 340 if (std::fseek(fp, seekpos, SEEK_SET) != 0) 341 return false; 342 343 std::vector<u8> temp; 344 345 while (count > 0) 346 { 347 u64 offset; 348 u8 chunk_size; 349 if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1) 350 { 351 ERROR_LOG("Incomplete ppf"); 352 return false; 353 } 354 355 temp.resize(chunk_size); 356 if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) 357 { 358 ERROR_LOG("Failed to read patch data"); 359 return false; 360 } 361 362 if (!AddPatch(offset, temp.data(), chunk_size)) 363 return false; 364 365 count -= sizeof(offset) + sizeof(chunk_size) + chunk_size; 366 } 367 368 INFO_LOG("Loaded {} replacement sectors from version 3 PPF", m_replacement_map.size()); 369 return true; 370 } 371 372 bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size) 373 { 374 DEBUG_LOG("Starting applying patch of {} bytes at at offset {}", patch_size, offset); 375 376 while (patch_size > 0) 377 { 378 const u32 sector_index = Truncate32(offset / RAW_SECTOR_SIZE) + m_replacement_offset; 379 const u32 sector_offset = Truncate32(offset % RAW_SECTOR_SIZE); 380 if (sector_index >= m_parent_image->GetLBACount()) 381 { 382 ERROR_LOG("Sector {} in patch is out of range", sector_index); 383 return false; 384 } 385 386 const u32 bytes_to_patch = std::min(patch_size, RAW_SECTOR_SIZE - sector_offset); 387 388 auto iter = m_replacement_map.find(sector_index); 389 if (iter == m_replacement_map.end()) 390 { 391 const u32 replacement_buffer_start = static_cast<u32>(m_replacement_data.size()); 392 m_replacement_data.resize(m_replacement_data.size() + RAW_SECTOR_SIZE); 393 if (!m_parent_image->Seek(sector_index) || 394 !m_parent_image->ReadRawSector(&m_replacement_data[replacement_buffer_start], nullptr)) 395 { 396 ERROR_LOG("Failed to read sector {} from parent image", sector_index); 397 return false; 398 } 399 400 iter = m_replacement_map.emplace(sector_index, replacement_buffer_start).first; 401 } 402 403 // patch it! 404 DEBUG_LOG(" Patching {} bytes at sector {} offset {}", bytes_to_patch, sector_index, sector_offset); 405 std::memcpy(&m_replacement_data[iter->second + sector_offset], patch, bytes_to_patch); 406 offset += bytes_to_patch; 407 patch += bytes_to_patch; 408 patch_size -= bytes_to_patch; 409 } 410 411 return true; 412 } 413 414 bool CDImagePPF::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) 415 { 416 return m_parent_image->ReadSubChannelQ(subq, index, lba_in_index); 417 } 418 419 bool CDImagePPF::HasNonStandardSubchannel() const 420 { 421 return m_parent_image->HasNonStandardSubchannel(); 422 } 423 424 std::string CDImagePPF::GetMetadata(std::string_view type) const 425 { 426 return m_parent_image->GetMetadata(type); 427 } 428 429 std::string CDImagePPF::GetSubImageMetadata(u32 index, std::string_view type) const 430 { 431 // We only support a single sub-image for patched games. 432 std::string ret; 433 if (index == 0) 434 ret = m_parent_image->GetSubImageMetadata(index, type); 435 436 return ret; 437 } 438 439 CDImage::PrecacheResult CDImagePPF::Precache(ProgressCallback* progress /*= ProgressCallback::NullProgressCallback*/) 440 { 441 return m_parent_image->Precache(progress); 442 } 443 444 bool CDImagePPF::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) 445 { 446 DebugAssert(index.file_index == 0); 447 448 const u32 sector_number = index.start_lba_on_disc + lba_in_index; 449 const auto it = m_replacement_map.find(sector_number); 450 if (it == m_replacement_map.end()) 451 return m_parent_image->ReadSectorFromIndex(buffer, index, lba_in_index); 452 453 std::memcpy(buffer, &m_replacement_data[it->second], RAW_SECTOR_SIZE); 454 return true; 455 } 456 457 s64 CDImagePPF::GetSizeOnDisk() const 458 { 459 return m_patch_size + m_parent_image->GetSizeOnDisk(); 460 } 461 462 std::unique_ptr<CDImage> 463 CDImage::OverlayPPFPatch(const char* filename, std::unique_ptr<CDImage> parent_image, 464 ProgressCallback* progress /* = ProgressCallback::NullProgressCallback */) 465 { 466 std::unique_ptr<CDImagePPF> ppf_image = std::make_unique<CDImagePPF>(); 467 if (!ppf_image->Open(filename, std::move(parent_image))) 468 return {}; 469 470 return ppf_image; 471 }