| 
									
										
										
										
											2015-05-24 06:55:12 +02:00
										 |  |  | // Copyright 2008 Dolphin Emulator Project
 | 
					
						
							| 
									
										
										
										
											2015-05-18 01:08:10 +02:00
										 |  |  | // Licensed under GPLv2+
 | 
					
						
							| 
									
										
										
										
											2013-04-17 23:09:55 -04:00
										 |  |  | // Refer to the license.txt file included.
 | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | #include <algorithm>
 | 
					
						
							| 
									
										
										
										
											2014-02-21 01:47:53 +01:00
										 |  |  | #include <cstddef>
 | 
					
						
							| 
									
										
										
										
											2015-12-07 19:53:42 -05:00
										 |  |  | #include <limits>
 | 
					
						
							| 
									
										
										
										
											2015-12-06 23:15:51 -05:00
										 |  |  | #include <memory>
 | 
					
						
							| 
									
										
										
										
											2014-02-21 01:47:53 +01:00
										 |  |  | #include <string>
 | 
					
						
							| 
									
										
										
										
											2016-12-21 12:50:15 +01:00
										 |  |  | #include <utility>
 | 
					
						
							| 
									
										
										
										
											2014-02-21 01:47:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-02-17 05:18:15 -05:00
										 |  |  | #include "Common/CDUtils.h"
 | 
					
						
							| 
									
										
										
										
											2014-02-21 01:47:53 +01:00
										 |  |  | #include "Common/CommonTypes.h"
 | 
					
						
							| 
									
										
										
										
											2014-02-17 05:18:15 -05:00
										 |  |  | #include "Common/FileUtil.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "DiscIO/Blob.h"
 | 
					
						
							|  |  |  | #include "DiscIO/CISOBlob.h"
 | 
					
						
							|  |  |  | #include "DiscIO/CompressedBlob.h"
 | 
					
						
							|  |  |  | #include "DiscIO/DriveBlob.h"
 | 
					
						
							|  |  |  | #include "DiscIO/FileBlob.h"
 | 
					
						
							| 
									
										
										
										
											2016-12-17 14:48:49 +01:00
										 |  |  | #include "DiscIO/TGCBlob.h"
 | 
					
						
							| 
									
										
										
										
											2014-02-17 05:18:15 -05:00
										 |  |  | #include "DiscIO/WbfsBlob.h"
 | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace DiscIO | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | void SectorReader::SetSectorSize(int blocksize) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   m_block_size = std::max(blocksize, 0); | 
					
						
							|  |  |  |   for (auto& cache_entry : m_cache) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     cache_entry.Reset(); | 
					
						
							|  |  |  |     cache_entry.data.resize(m_chunk_blocks * m_block_size); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2015-12-07 19:53:42 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | void SectorReader::SetChunkSize(int block_cnt) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   m_chunk_blocks = std::max(block_cnt, 1); | 
					
						
							|  |  |  |   // Clear cache and resize the data arrays
 | 
					
						
							|  |  |  |   SetSectorSize(m_block_size); | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-12 15:33:41 -04:00
										 |  |  | SectorReader::~SectorReader() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | const SectorReader::Cache* SectorReader::FindCacheLine(u64 block_num) | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   auto itr = std::find_if(m_cache.begin(), m_cache.end(), | 
					
						
							|  |  |  |                           [&](const Cache& entry) { return entry.Contains(block_num); }); | 
					
						
							|  |  |  |   if (itr == m_cache.end()) | 
					
						
							|  |  |  |     return nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   itr->MarkUsed(); | 
					
						
							|  |  |  |   return &*itr; | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SectorReader::Cache* SectorReader::GetEmptyCacheLine() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   Cache* oldest = &m_cache[0]; | 
					
						
							|  |  |  |   // Find the Least Recently Used cache line to replace.
 | 
					
						
							| 
									
										
										
										
											2016-07-06 10:37:59 +00:00
										 |  |  |   std::for_each(m_cache.begin() + 1, m_cache.end(), [&](Cache& line) { | 
					
						
							|  |  |  |     if (line.IsLessRecentlyUsedThan(*oldest)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       oldest->ShiftLRU(); | 
					
						
							|  |  |  |       oldest = &line; | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     line.ShiftLRU(); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   oldest->Reset(); | 
					
						
							|  |  |  |   return oldest; | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const SectorReader::Cache* SectorReader::GetCacheLine(u64 block_num) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   if (auto entry = FindCacheLine(block_num)) | 
					
						
							|  |  |  |     return entry; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Cache miss. Fault in the missing entry.
 | 
					
						
							|  |  |  |   Cache* cache = GetEmptyCacheLine(); | 
					
						
							|  |  |  |   // We only read aligned chunks, this avoids duplicate overlapping entries.
 | 
					
						
							|  |  |  |   u64 chunk_idx = block_num / m_chunk_blocks; | 
					
						
							|  |  |  |   u32 blocks_read = ReadChunk(cache->data.data(), chunk_idx); | 
					
						
							|  |  |  |   if (!blocks_read) | 
					
						
							|  |  |  |     return nullptr; | 
					
						
							|  |  |  |   cache->Fill(chunk_idx * m_chunk_blocks, blocks_read); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Secondary check for out-of-bounds read.
 | 
					
						
							|  |  |  |   // If we got less than m_chunk_blocks, we may still have missed.
 | 
					
						
							|  |  |  |   // We do this after the cache fill since the cache line itself is
 | 
					
						
							|  |  |  |   // fine, the problem is being asked to read past the end of the disk.
 | 
					
						
							|  |  |  |   return cache->Contains(block_num) ? cache : nullptr; | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool SectorReader::Read(u64 offset, u64 size, u8* out_ptr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   u64 remain = size; | 
					
						
							|  |  |  |   u64 block = 0; | 
					
						
							|  |  |  |   u32 position_in_block = static_cast<u32>(offset % m_block_size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (remain > 0) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     block = offset / m_block_size; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const Cache* cache = GetCacheLine(block); | 
					
						
							|  |  |  |     if (!cache) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Cache entries are aligned chunks, we may not want to read from the start
 | 
					
						
							|  |  |  |     u32 read_offset = static_cast<u32>(block - cache->block_idx) * m_block_size + position_in_block; | 
					
						
							|  |  |  |     u32 can_read = m_block_size * cache->num_blocks - read_offset; | 
					
						
							|  |  |  |     u32 was_read = static_cast<u32>(std::min<u64>(can_read, remain)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     std::copy(cache->data.begin() + read_offset, cache->data.begin() + read_offset + was_read, | 
					
						
							|  |  |  |               out_ptr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     offset += was_read; | 
					
						
							|  |  |  |     out_ptr += was_read; | 
					
						
							|  |  |  |     remain -= was_read; | 
					
						
							|  |  |  |     position_in_block = 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | // Crap default implementation if not overridden.
 | 
					
						
							|  |  |  | bool SectorReader::ReadMultipleAlignedBlocks(u64 block_num, u64 cnt_blocks, u8* out_ptr) | 
					
						
							| 
									
										
										
										
											2009-02-22 16:48:54 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   for (u64 i = 0; i < cnt_blocks; ++i) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (!GetBlock(block_num + i, out_ptr)) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     out_ptr += m_block_size; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2015-12-07 19:53:42 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-26 11:24:08 +00:00
										 |  |  | u32 SectorReader::ReadChunk(u8* buffer, u64 chunk_num) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   u64 block_num = chunk_num * m_chunk_blocks; | 
					
						
							|  |  |  |   u32 cnt_blocks = m_chunk_blocks; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // If we are reading the end of a disk, there may not be enough blocks to
 | 
					
						
							|  |  |  |   // read a whole chunk. We need to clamp down in that case.
 | 
					
						
							|  |  |  |   u64 end_block = (GetDataSize() + m_block_size - 1) / m_block_size; | 
					
						
							|  |  |  |   if (end_block) | 
					
						
							|  |  |  |     cnt_blocks = static_cast<u32>(std::min<u64>(m_chunk_blocks, end_block - block_num)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ReadMultipleAlignedBlocks(block_num, cnt_blocks, buffer)) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (cnt_blocks < m_chunk_blocks) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       std::fill(buffer + cnt_blocks * m_block_size, buffer + m_chunk_blocks * m_block_size, 0u); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return cnt_blocks; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // end_block may be zero on real disks if we fail to get the media size.
 | 
					
						
							|  |  |  |   // We have to fallback to probing the disk instead.
 | 
					
						
							|  |  |  |   if (!end_block) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     for (u32 i = 0; i < cnt_blocks; ++i) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       if (!GetBlock(block_num + i, buffer)) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         std::fill(buffer, buffer + (cnt_blocks - i) * m_block_size, 0u); | 
					
						
							|  |  |  |         return i; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       buffer += m_block_size; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return cnt_blocks; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return 0; | 
					
						
							| 
									
										
										
										
											2009-02-22 16:48:54 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-06 23:15:51 -05:00
										 |  |  | std::unique_ptr<IBlobReader> CreateBlobReader(const std::string& filename) | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |   if (cdio_is_cdrom(filename)) | 
					
						
							|  |  |  |     return DriveReader::Create(filename); | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   File::IOFile file(filename, "rb"); | 
					
						
							|  |  |  |   u32 magic; | 
					
						
							|  |  |  |   if (!file.ReadArray(&magic, 1)) | 
					
						
							| 
									
										
										
										
											2016-06-24 10:43:46 +02:00
										 |  |  |     return nullptr; | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   // Conveniently, every supported file format (except for plain disc images) starts
 | 
					
						
							|  |  |  |   // with a 4-byte magic number that identifies the format, so we just need a simple
 | 
					
						
							|  |  |  |   // switch statement to create the right blob type. If the magic number doesn't
 | 
					
						
							|  |  |  |   // match any known magic number, we assume it's a plain disc image. If that
 | 
					
						
							|  |  |  |   // assumption is wrong, the volume code that runs later will notice the error
 | 
					
						
							|  |  |  |   // because the blob won't provide valid data when reading the GC/Wii disc header.
 | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   switch (magic) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |   case CISO_MAGIC: | 
					
						
							| 
									
										
										
										
											2016-12-21 12:50:15 +01:00
										 |  |  |     return CISOFileReader::Create(std::move(file)); | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   case GCZ_MAGIC: | 
					
						
							| 
									
										
										
										
											2016-12-21 12:50:15 +01:00
										 |  |  |     return CompressedBlobReader::Create(std::move(file), filename); | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   case TGC_MAGIC: | 
					
						
							| 
									
										
										
										
											2016-12-21 12:50:15 +01:00
										 |  |  |     return TGCFileReader::Create(std::move(file)); | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   case WBFS_MAGIC: | 
					
						
							| 
									
										
										
										
											2016-12-21 14:01:00 +01:00
										 |  |  |     return WbfsFileReader::Create(std::move(file), filename); | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   default: | 
					
						
							| 
									
										
										
										
											2016-12-21 12:50:15 +01:00
										 |  |  |     return PlainFileReader::Create(std::move(file)); | 
					
						
							| 
									
										
										
										
											2016-12-21 11:30:12 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2008-12-08 05:30:24 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }  // namespace
 |