mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 05:47:56 -07:00
dcc10cff11
This should be a fairly easy merge, assuming I didn’t mess anything up. TL:DR no one uses it and it’s not great. Boot from DVD Backup is an ancient feature with origins in the Megacommit. Back then, GameCube and Wii games were quite large relative to drives of the time. For example, in 2008, the most common hard drive sizes were 320GB and 512GB. On the 320GB drive I personally had at the time, as little as 42 Wii ISOs could have filled it entirely! And that’s ignoring any other files one might want to put onto a drive. Backup DVDs allowed users to burn relatively cheap DVD media and store their GameCube and Wii dumps in a Dolphin accessible way that didn’t eat into their precious HDD space. It had compromises, even then, but in 2008… I mean honestly users probably wouldn’t even notice those compromises with how Dolphin barely even worked at all back then. Obviously, today the storage space concerns are not as big of an issue. According to seagate the average hard drive it sells today is 8TB. For typical laptops purchased now, the -minimum- selection for storage is usually 1TB. You can even buy a name brand 4TB external hard drive for $100. GC and Wii ISOs are not as big as they once were, relatively anyway. Plus flash drives and SD cards are super cheap and way faster than disc drives ever were. For anyone that has limited drive space, removable flash media can fulfill this offloading role far better than backup DVD media ever could. Also no one has DVD drives anymore. That’s kind of an important detail. But to see if Booting from DVD Backup even still worked, I decided to give it a try. I have an ASUS BW-16D1HT, a badass Bluray XL reading and burning drive, connected to my Windows 11 Threadripper 5975WX machine. A super fast drive on a super fast machine is as good as it possibly can get for this feature. So I bought a spindle of DVD-Rs, burned a couple of discs and gave it a try. Surprisingly, it does still work. However, as expected, it introduces a lot of stuttering. Testing Prime 1 and Prime 3, in both games stuttering was introduced whenever the DVD Drive had to suddenly seek. Spikes of 50ms occurred constantly, but I observed 150ms and even over 1000ms stutters! The worst was a three second stutter, when loading Elysia in Prime 3. I could even hear the stutters - any time the drive suddenly made a harsh seeking noise, the game would have a hard stutter. It worked but, it has some serious compromises. Boot from DVD Backup isn’t great, using removable flash media or external hard drives is a FAR better option for anyone with limited storage space today, and no one can even use this feature anymore because their computers don’t even have disc drives. It’s time for Boot from DVD Backup to go! So I did my best on the cleanup but I’m bound to have left some bits. Especially in translation - I didn’t get any warnings or anything there that could help point me to where to clean that up. Please review!
255 lines
7.0 KiB
C++
255 lines
7.0 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DiscIO/Blob.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "Common/CDUtils.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/IOFile.h"
|
|
#include "Common/MsgHandler.h"
|
|
|
|
#include "DiscIO/CISOBlob.h"
|
|
#include "DiscIO/CompressedBlob.h"
|
|
#include "DiscIO/DirectoryBlob.h"
|
|
#include "DiscIO/FileBlob.h"
|
|
#include "DiscIO/NFSBlob.h"
|
|
#include "DiscIO/TGCBlob.h"
|
|
#include "DiscIO/WIABlob.h"
|
|
#include "DiscIO/WbfsBlob.h"
|
|
|
|
namespace DiscIO
|
|
{
|
|
std::string GetName(BlobType blob_type, bool translate)
|
|
{
|
|
const auto translate_str = [translate](const std::string& str) {
|
|
return translate ? Common::GetStringT(str.c_str()) : str;
|
|
};
|
|
|
|
switch (blob_type)
|
|
{
|
|
case BlobType::PLAIN:
|
|
return "ISO";
|
|
case BlobType::DIRECTORY:
|
|
return translate_str("Directory");
|
|
case BlobType::GCZ:
|
|
return "GCZ";
|
|
case BlobType::CISO:
|
|
return "CISO";
|
|
case BlobType::WBFS:
|
|
return "WBFS";
|
|
case BlobType::TGC:
|
|
return "TGC";
|
|
case BlobType::WIA:
|
|
return "WIA";
|
|
case BlobType::RVZ:
|
|
return "RVZ";
|
|
case BlobType::MOD_DESCRIPTOR:
|
|
return translate_str("Mod");
|
|
case BlobType::NFS:
|
|
return "NFS";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void SectorReader::SetSectorSize(int blocksize)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
void SectorReader::SetChunkSize(int block_cnt)
|
|
{
|
|
m_chunk_blocks = std::max(block_cnt, 1);
|
|
// Clear cache and resize the data arrays
|
|
SetSectorSize(m_block_size);
|
|
}
|
|
|
|
SectorReader::~SectorReader()
|
|
{
|
|
}
|
|
|
|
const SectorReader::Cache* SectorReader::FindCacheLine(u64 block_num)
|
|
{
|
|
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;
|
|
}
|
|
|
|
SectorReader::Cache* SectorReader::GetEmptyCacheLine()
|
|
{
|
|
Cache* oldest = &m_cache[0];
|
|
// Find the Least Recently Used cache line to replace.
|
|
std::for_each(m_cache.begin() + 1, m_cache.end(), [&](Cache& line) {
|
|
if (line.IsLessRecentlyUsedThan(*oldest))
|
|
{
|
|
oldest->ShiftLRU();
|
|
oldest = &line;
|
|
return;
|
|
}
|
|
line.ShiftLRU();
|
|
});
|
|
oldest->Reset();
|
|
return oldest;
|
|
}
|
|
|
|
const SectorReader::Cache* SectorReader::GetCacheLine(u64 block_num)
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool SectorReader::Read(u64 offset, u64 size, u8* out_ptr)
|
|
{
|
|
if (offset + size > GetDataSize())
|
|
return false;
|
|
|
|
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;
|
|
}
|
|
|
|
// Crap default implementation if not overridden.
|
|
bool SectorReader::ReadMultipleAlignedBlocks(u64 block_num, u64 cnt_blocks, u8* out_ptr)
|
|
{
|
|
for (u64 i = 0; i < cnt_blocks; ++i)
|
|
{
|
|
if (!GetBlock(block_num + i, out_ptr))
|
|
return false;
|
|
out_ptr += m_block_size;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
u32 SectorReader::ReadChunk(u8* buffer, u64 chunk_num)
|
|
{
|
|
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;
|
|
}
|
|
|
|
std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename)
|
|
{
|
|
File::IOFile file(filename, "rb");
|
|
u32 magic;
|
|
if (!file.ReadArray(&magic, 1))
|
|
return nullptr;
|
|
|
|
// Conveniently, every supported file format (except for plain disc images and
|
|
// extracted discs) 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 and the directory structure
|
|
// doesn't match the directory blob format, 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 the right data when reading the GC/Wii disc header.
|
|
|
|
switch (magic)
|
|
{
|
|
case CISO_MAGIC:
|
|
return CISOFileReader::Create(std::move(file));
|
|
case GCZ_MAGIC:
|
|
return CompressedBlobReader::Create(std::move(file), filename);
|
|
case TGC_MAGIC:
|
|
return TGCFileReader::Create(std::move(file));
|
|
case WBFS_MAGIC:
|
|
return WbfsFileReader::Create(std::move(file), filename);
|
|
case WIA_MAGIC:
|
|
return WIAFileReader::Create(std::move(file), filename);
|
|
case RVZ_MAGIC:
|
|
return RVZFileReader::Create(std::move(file), filename);
|
|
case NFS_MAGIC:
|
|
return NFSFileReader::Create(std::move(file), filename);
|
|
default:
|
|
if (auto directory_blob = DirectoryBlobReader::Create(filename))
|
|
return std::move(directory_blob);
|
|
|
|
return PlainFileReader::Create(std::move(file));
|
|
}
|
|
}
|
|
|
|
} // namespace DiscIO
|