mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-26 07:39:45 -06:00
Reformat all the things. Have fun with merge conflicts.
This commit is contained in:
@ -21,22 +21,21 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
m_chunk_blocks = std::max(block_cnt, 1);
|
||||
// Clear cache and resize the data arrays
|
||||
SetSectorSize(m_block_size);
|
||||
}
|
||||
|
||||
SectorReader::~SectorReader()
|
||||
@ -45,154 +44,149 @@ 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;
|
||||
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;
|
||||
itr->MarkUsed();
|
||||
return &*itr;
|
||||
}
|
||||
|
||||
SectorReader::Cache* SectorReader::GetEmptyCacheLine()
|
||||
{
|
||||
Cache* oldest = &m_cache[0];
|
||||
// Find the Least Recently Used cache line to replace.
|
||||
for (auto& cache_entry : m_cache)
|
||||
{
|
||||
if (cache_entry.IsLessRecentlyUsedThan(*oldest))
|
||||
oldest = &cache_entry;
|
||||
cache_entry.ShiftLRU();
|
||||
}
|
||||
oldest->Reset();
|
||||
return oldest;
|
||||
Cache* oldest = &m_cache[0];
|
||||
// Find the Least Recently Used cache line to replace.
|
||||
for (auto& cache_entry : m_cache)
|
||||
{
|
||||
if (cache_entry.IsLessRecentlyUsedThan(*oldest))
|
||||
oldest = &cache_entry;
|
||||
cache_entry.ShiftLRU();
|
||||
}
|
||||
oldest->Reset();
|
||||
return oldest;
|
||||
}
|
||||
|
||||
const SectorReader::Cache* SectorReader::GetCacheLine(u64 block_num)
|
||||
{
|
||||
if (auto entry = FindCacheLine(block_num))
|
||||
return entry;
|
||||
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);
|
||||
// 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;
|
||||
// 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)
|
||||
{
|
||||
u64 remain = size;
|
||||
u64 block = 0;
|
||||
u32 position_in_block = static_cast<u32>(offset % m_block_size);
|
||||
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;
|
||||
while (remain > 0)
|
||||
{
|
||||
block = offset / m_block_size;
|
||||
|
||||
const Cache* cache = GetCacheLine(block);
|
||||
if (!cache)
|
||||
return false;
|
||||
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));
|
||||
// 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);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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 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;
|
||||
}
|
||||
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;
|
||||
// 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<IBlobReader> CreateBlobReader(const std::string& filename)
|
||||
{
|
||||
if (cdio_is_cdrom(filename))
|
||||
return DriveReader::Create(filename);
|
||||
if (cdio_is_cdrom(filename))
|
||||
return DriveReader::Create(filename);
|
||||
|
||||
if (!File::Exists(filename))
|
||||
return nullptr;
|
||||
if (!File::Exists(filename))
|
||||
return nullptr;
|
||||
|
||||
if (IsWbfsBlob(filename))
|
||||
return WbfsFileReader::Create(filename);
|
||||
if (IsWbfsBlob(filename))
|
||||
return WbfsFileReader::Create(filename);
|
||||
|
||||
if (IsGCZBlob(filename))
|
||||
return CompressedBlobReader::Create(filename);
|
||||
if (IsGCZBlob(filename))
|
||||
return CompressedBlobReader::Create(filename);
|
||||
|
||||
if (IsCISOBlob(filename))
|
||||
return CISOFileReader::Create(filename);
|
||||
if (IsCISOBlob(filename))
|
||||
return CISOFileReader::Create(filename);
|
||||
|
||||
// Still here? Assume plain file - since we know it exists due to the File::Exists check above.
|
||||
return PlainFileReader::Create(filename);
|
||||
// Still here? Assume plain file - since we know it exists due to the File::Exists check above.
|
||||
return PlainFileReader::Create(filename);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -22,159 +22,135 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
// Increment CACHE_REVISION if the enum below is modified (ISOFile.cpp & GameFile.cpp)
|
||||
enum class BlobType
|
||||
{
|
||||
PLAIN,
|
||||
DRIVE,
|
||||
DIRECTORY,
|
||||
GCZ,
|
||||
CISO,
|
||||
WBFS
|
||||
PLAIN,
|
||||
DRIVE,
|
||||
DIRECTORY,
|
||||
GCZ,
|
||||
CISO,
|
||||
WBFS
|
||||
};
|
||||
|
||||
class IBlobReader
|
||||
{
|
||||
public:
|
||||
virtual ~IBlobReader() {}
|
||||
|
||||
virtual BlobType GetBlobType() const = 0;
|
||||
virtual u64 GetRawSize() const = 0;
|
||||
virtual u64 GetDataSize() const = 0;
|
||||
// NOT thread-safe - can't call this from multiple threads.
|
||||
virtual bool Read(u64 offset, u64 size, u8* out_ptr) = 0;
|
||||
virtual ~IBlobReader() {}
|
||||
virtual BlobType GetBlobType() const = 0;
|
||||
virtual u64 GetRawSize() const = 0;
|
||||
virtual u64 GetDataSize() const = 0;
|
||||
// NOT thread-safe - can't call this from multiple threads.
|
||||
virtual bool Read(u64 offset, u64 size, u8* out_ptr) = 0;
|
||||
|
||||
protected:
|
||||
IBlobReader() {}
|
||||
IBlobReader() {}
|
||||
};
|
||||
|
||||
|
||||
// Provides caching and byte-operation-to-block-operations facilities.
|
||||
// Used for compressed blob and direct drive reading.
|
||||
// NOTE: GetDataSize() is expected to be evenly divisible by the sector size.
|
||||
class SectorReader : public IBlobReader
|
||||
{
|
||||
public:
|
||||
virtual ~SectorReader() = 0;
|
||||
virtual ~SectorReader() = 0;
|
||||
|
||||
bool Read(u64 offset, u64 size, u8* out_ptr) override;
|
||||
bool Read(u64 offset, u64 size, u8* out_ptr) override;
|
||||
|
||||
protected:
|
||||
void SetSectorSize(int blocksize);
|
||||
int GetSectorSize() const
|
||||
{
|
||||
return m_block_size;
|
||||
}
|
||||
void SetSectorSize(int blocksize);
|
||||
int GetSectorSize() const { return m_block_size; }
|
||||
// Set the chunk size -> the number of blocks to read at a time.
|
||||
// Default value is 1 but that is too low for physical devices
|
||||
// like CDROMs. Setting this to a higher value helps reduce seeking
|
||||
// and IO overhead by batching reads. Do not set it too high either
|
||||
// as large reads are slow and will take too long to resolve.
|
||||
void SetChunkSize(int blocks);
|
||||
int GetChunkSize() const { return m_chunk_blocks; }
|
||||
// Read a single block/sector.
|
||||
virtual bool GetBlock(u64 block_num, u8* out) = 0;
|
||||
|
||||
// Set the chunk size -> the number of blocks to read at a time.
|
||||
// Default value is 1 but that is too low for physical devices
|
||||
// like CDROMs. Setting this to a higher value helps reduce seeking
|
||||
// and IO overhead by batching reads. Do not set it too high either
|
||||
// as large reads are slow and will take too long to resolve.
|
||||
void SetChunkSize(int blocks);
|
||||
int GetChunkSize() const
|
||||
{
|
||||
return m_chunk_blocks;
|
||||
}
|
||||
|
||||
// Read a single block/sector.
|
||||
virtual bool GetBlock(u64 block_num, u8* out) = 0;
|
||||
|
||||
// Read multiple contiguous blocks.
|
||||
// Default implementation just calls GetBlock in a loop, it should be
|
||||
// overridden in derived classes where possible.
|
||||
virtual bool ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr);
|
||||
// Read multiple contiguous blocks.
|
||||
// Default implementation just calls GetBlock in a loop, it should be
|
||||
// overridden in derived classes where possible.
|
||||
virtual bool ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr);
|
||||
|
||||
private:
|
||||
struct Cache
|
||||
{
|
||||
std::vector<u8> data;
|
||||
u64 block_idx = 0;
|
||||
u32 num_blocks = 0;
|
||||
struct Cache
|
||||
{
|
||||
std::vector<u8> data;
|
||||
u64 block_idx = 0;
|
||||
u32 num_blocks = 0;
|
||||
|
||||
// [Pseudo-] Least Recently Used Shift Register
|
||||
// When an empty cache line is needed, the line with the lowest value
|
||||
// is taken and reset; the LRU register is then shifted down 1 place
|
||||
// on all lines (low bit discarded). When a line is used, the high bit
|
||||
// is set marking it as most recently used.
|
||||
u32 lru_sreg = 0;
|
||||
// [Pseudo-] Least Recently Used Shift Register
|
||||
// When an empty cache line is needed, the line with the lowest value
|
||||
// is taken and reset; the LRU register is then shifted down 1 place
|
||||
// on all lines (low bit discarded). When a line is used, the high bit
|
||||
// is set marking it as most recently used.
|
||||
u32 lru_sreg = 0;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
block_idx = 0;
|
||||
num_blocks = 0;
|
||||
lru_sreg = 0;
|
||||
}
|
||||
void Fill(u64 block, u32 count)
|
||||
{
|
||||
block_idx = block;
|
||||
num_blocks = count;
|
||||
// NOTE: Setting only the high bit means the newest line will
|
||||
// be selected for eviction if every line in the cache was
|
||||
// touched. This gives MRU behavior which is probably
|
||||
// desirable in that case.
|
||||
MarkUsed();
|
||||
}
|
||||
bool Contains(u64 block) const
|
||||
{
|
||||
return block >= block_idx && block - block_idx < num_blocks;
|
||||
}
|
||||
void MarkUsed()
|
||||
{
|
||||
lru_sreg |= 0x80000000;
|
||||
}
|
||||
void ShiftLRU()
|
||||
{
|
||||
lru_sreg >>= 1;
|
||||
}
|
||||
bool IsLessRecentlyUsedThan(const Cache& other) const
|
||||
{
|
||||
return lru_sreg < other.lru_sreg;
|
||||
}
|
||||
};
|
||||
void Reset()
|
||||
{
|
||||
block_idx = 0;
|
||||
num_blocks = 0;
|
||||
lru_sreg = 0;
|
||||
}
|
||||
void Fill(u64 block, u32 count)
|
||||
{
|
||||
block_idx = block;
|
||||
num_blocks = count;
|
||||
// NOTE: Setting only the high bit means the newest line will
|
||||
// be selected for eviction if every line in the cache was
|
||||
// touched. This gives MRU behavior which is probably
|
||||
// desirable in that case.
|
||||
MarkUsed();
|
||||
}
|
||||
bool Contains(u64 block) const { return block >= block_idx && block - block_idx < num_blocks; }
|
||||
void MarkUsed() { lru_sreg |= 0x80000000; }
|
||||
void ShiftLRU() { lru_sreg >>= 1; }
|
||||
bool IsLessRecentlyUsedThan(const Cache& other) const { return lru_sreg < other.lru_sreg; }
|
||||
};
|
||||
|
||||
// Gets the cache line that contains the given block, or nullptr.
|
||||
// NOTE: The cache record only lasts until it expires (next GetEmptyCacheLine)
|
||||
const Cache* FindCacheLine(u64 block_num);
|
||||
// Gets the cache line that contains the given block, or nullptr.
|
||||
// NOTE: The cache record only lasts until it expires (next GetEmptyCacheLine)
|
||||
const Cache* FindCacheLine(u64 block_num);
|
||||
|
||||
// Finds the least recently used cache line, resets and returns it.
|
||||
Cache* GetEmptyCacheLine();
|
||||
// Finds the least recently used cache line, resets and returns it.
|
||||
Cache* GetEmptyCacheLine();
|
||||
|
||||
// Combines FindCacheLine with GetEmptyCacheLine and ReadChunk.
|
||||
// Always returns a valid cache line (loading the data if needed).
|
||||
// May return nullptr only if the cache missed and the read failed.
|
||||
const Cache* GetCacheLine(u64 block_num);
|
||||
// Combines FindCacheLine with GetEmptyCacheLine and ReadChunk.
|
||||
// Always returns a valid cache line (loading the data if needed).
|
||||
// May return nullptr only if the cache missed and the read failed.
|
||||
const Cache* GetCacheLine(u64 block_num);
|
||||
|
||||
// Read all bytes from a chunk of blocks into a buffer.
|
||||
// Returns the number of blocks read (may be less than m_chunk_blocks
|
||||
// if chunk_num is the last chunk on the disk and the disk size is not
|
||||
// evenly divisible into chunks). Returns zero if it fails.
|
||||
u32 ReadChunk(u8* buffer, u64 chunk_num);
|
||||
// Read all bytes from a chunk of blocks into a buffer.
|
||||
// Returns the number of blocks read (may be less than m_chunk_blocks
|
||||
// if chunk_num is the last chunk on the disk and the disk size is not
|
||||
// evenly divisible into chunks). Returns zero if it fails.
|
||||
u32 ReadChunk(u8* buffer, u64 chunk_num);
|
||||
|
||||
static constexpr int CACHE_LINES = 32;
|
||||
u32 m_block_size = 0; // Bytes in a sector/block
|
||||
u32 m_chunk_blocks = 1; // Number of sectors/blocks in a chunk
|
||||
std::array<Cache, CACHE_LINES> m_cache;
|
||||
static constexpr int CACHE_LINES = 32;
|
||||
u32 m_block_size = 0; // Bytes in a sector/block
|
||||
u32 m_chunk_blocks = 1; // Number of sectors/blocks in a chunk
|
||||
std::array<Cache, CACHE_LINES> m_cache;
|
||||
};
|
||||
|
||||
class CBlobBigEndianReader
|
||||
{
|
||||
public:
|
||||
CBlobBigEndianReader(IBlobReader& reader) : m_reader(reader) {}
|
||||
|
||||
template <typename T>
|
||||
bool ReadSwapped(u64 offset, T* buffer) const
|
||||
{
|
||||
T temp;
|
||||
if (!m_reader.Read(offset, sizeof(T), reinterpret_cast<u8*>(&temp)))
|
||||
return false;
|
||||
*buffer = Common::FromBigEndian(temp);
|
||||
return true;
|
||||
}
|
||||
CBlobBigEndianReader(IBlobReader& reader) : m_reader(reader) {}
|
||||
template <typename T>
|
||||
bool ReadSwapped(u64 offset, T* buffer) const
|
||||
{
|
||||
T temp;
|
||||
if (!m_reader.Read(offset, sizeof(T), reinterpret_cast<u8*>(&temp)))
|
||||
return false;
|
||||
*buffer = Common::FromBigEndian(temp);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
IBlobReader& m_reader;
|
||||
IBlobReader& m_reader;
|
||||
};
|
||||
|
||||
// Factory function - examines the path to choose the right type of IBlobReader, and returns one.
|
||||
@ -182,9 +158,10 @@ std::unique_ptr<IBlobReader> CreateBlobReader(const std::string& filename);
|
||||
|
||||
typedef bool (*CompressCB)(const std::string& text, float percent, void* arg);
|
||||
|
||||
bool CompressFileToBlob(const std::string& infile, const std::string& outfile, u32 sub_type = 0, int sector_size = 16384,
|
||||
CompressCB callback = nullptr, void *arg = nullptr);
|
||||
bool CompressFileToBlob(const std::string& infile, const std::string& outfile, u32 sub_type = 0,
|
||||
int sector_size = 16384, CompressCB callback = nullptr,
|
||||
void* arg = nullptr);
|
||||
bool DecompressBlobToFile(const std::string& infile, const std::string& outfile,
|
||||
CompressCB callback = nullptr, void *arg = nullptr);
|
||||
CompressCB callback = nullptr, void* arg = nullptr);
|
||||
|
||||
} // namespace
|
||||
|
@ -12,84 +12,82 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
static const char CISO_MAGIC[] = "CISO";
|
||||
|
||||
CISOFileReader::CISOFileReader(std::FILE* file)
|
||||
: m_file(file)
|
||||
CISOFileReader::CISOFileReader(std::FILE* file) : m_file(file)
|
||||
{
|
||||
m_size = m_file.GetSize();
|
||||
m_size = m_file.GetSize();
|
||||
|
||||
CISOHeader header;
|
||||
m_file.ReadArray(&header, 1);
|
||||
CISOHeader header;
|
||||
m_file.ReadArray(&header, 1);
|
||||
|
||||
m_block_size = header.block_size;
|
||||
m_block_size = header.block_size;
|
||||
|
||||
MapType count = 0;
|
||||
for (u32 idx = 0; idx < CISO_MAP_SIZE; ++idx)
|
||||
m_ciso_map[idx] = (1 == header.map[idx]) ? count++ : UNUSED_BLOCK_ID;
|
||||
MapType count = 0;
|
||||
for (u32 idx = 0; idx < CISO_MAP_SIZE; ++idx)
|
||||
m_ciso_map[idx] = (1 == header.map[idx]) ? count++ : UNUSED_BLOCK_ID;
|
||||
}
|
||||
|
||||
std::unique_ptr<CISOFileReader> CISOFileReader::Create(const std::string& filename)
|
||||
{
|
||||
if (IsCISOBlob(filename))
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
return std::unique_ptr<CISOFileReader>(new CISOFileReader(f.ReleaseHandle()));
|
||||
}
|
||||
if (IsCISOBlob(filename))
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
return std::unique_ptr<CISOFileReader>(new CISOFileReader(f.ReleaseHandle()));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u64 CISOFileReader::GetDataSize() const
|
||||
{
|
||||
return CISO_MAP_SIZE * m_block_size;
|
||||
return CISO_MAP_SIZE * m_block_size;
|
||||
}
|
||||
|
||||
u64 CISOFileReader::GetRawSize() const
|
||||
{
|
||||
return m_size;
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool CISOFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
|
||||
{
|
||||
while (nbytes != 0)
|
||||
{
|
||||
u64 const block = offset / m_block_size;
|
||||
u64 const data_offset = offset % m_block_size;
|
||||
u64 const bytes_to_read = std::min(m_block_size - data_offset, nbytes);
|
||||
while (nbytes != 0)
|
||||
{
|
||||
u64 const block = offset / m_block_size;
|
||||
u64 const data_offset = offset % m_block_size;
|
||||
u64 const bytes_to_read = std::min(m_block_size - data_offset, nbytes);
|
||||
|
||||
if (block < CISO_MAP_SIZE && UNUSED_BLOCK_ID != m_ciso_map[block])
|
||||
{
|
||||
// calculate the base address
|
||||
u64 const file_off = CISO_HEADER_SIZE + m_ciso_map[block] * (u64)m_block_size + data_offset;
|
||||
if (block < CISO_MAP_SIZE && UNUSED_BLOCK_ID != m_ciso_map[block])
|
||||
{
|
||||
// calculate the base address
|
||||
u64 const file_off = CISO_HEADER_SIZE + m_ciso_map[block] * (u64)m_block_size + data_offset;
|
||||
|
||||
if (!(m_file.Seek(file_off, SEEK_SET) && m_file.ReadArray(out_ptr, bytes_to_read)))
|
||||
{
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill_n(out_ptr, bytes_to_read, 0);
|
||||
}
|
||||
if (!(m_file.Seek(file_off, SEEK_SET) && m_file.ReadArray(out_ptr, bytes_to_read)))
|
||||
{
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill_n(out_ptr, bytes_to_read, 0);
|
||||
}
|
||||
|
||||
out_ptr += bytes_to_read;
|
||||
offset += bytes_to_read;
|
||||
nbytes -= bytes_to_read;
|
||||
}
|
||||
out_ptr += bytes_to_read;
|
||||
offset += bytes_to_read;
|
||||
nbytes -= bytes_to_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsCISOBlob(const std::string& filename)
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
File::IOFile f(filename, "rb");
|
||||
|
||||
CISOHeader header;
|
||||
return (f.ReadArray(&header, 1) &&
|
||||
std::equal(header.magic, header.magic + sizeof(header.magic), CISO_MAGIC));
|
||||
CISOHeader header;
|
||||
return (f.ReadArray(&header, 1) &&
|
||||
std::equal(header.magic, header.magic + sizeof(header.magic), CISO_MAGIC));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
bool IsCISOBlob(const std::string& filename);
|
||||
|
||||
static const u32 CISO_HEADER_SIZE = 0x8000;
|
||||
@ -22,40 +21,39 @@ static const u32 CISO_MAP_SIZE = CISO_HEADER_SIZE - sizeof(u32) - sizeof(char) *
|
||||
|
||||
struct CISOHeader
|
||||
{
|
||||
// "CISO"
|
||||
char magic[4];
|
||||
// "CISO"
|
||||
char magic[4];
|
||||
|
||||
// little endian
|
||||
u32 block_size;
|
||||
// little endian
|
||||
u32 block_size;
|
||||
|
||||
// 0=unused, 1=used, others=invalid
|
||||
u8 map[CISO_MAP_SIZE];
|
||||
// 0=unused, 1=used, others=invalid
|
||||
u8 map[CISO_MAP_SIZE];
|
||||
};
|
||||
|
||||
class CISOFileReader : public IBlobReader
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<CISOFileReader> Create(const std::string& filename);
|
||||
static std::unique_ptr<CISOFileReader> Create(const std::string& filename);
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::CISO; }
|
||||
BlobType GetBlobType() const override { return BlobType::CISO; }
|
||||
// The CISO format does not save the original file size.
|
||||
// This function returns an upper bound.
|
||||
u64 GetDataSize() const override;
|
||||
|
||||
// The CISO format does not save the original file size.
|
||||
// This function returns an upper bound.
|
||||
u64 GetDataSize() const override;
|
||||
|
||||
u64 GetRawSize() const override;
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
u64 GetRawSize() const override;
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
CISOFileReader(std::FILE* file);
|
||||
CISOFileReader(std::FILE* file);
|
||||
|
||||
typedef u16 MapType;
|
||||
static const MapType UNUSED_BLOCK_ID = -1;
|
||||
typedef u16 MapType;
|
||||
static const MapType UNUSED_BLOCK_ID = -1;
|
||||
|
||||
File::IOFile m_file;
|
||||
u64 m_size;
|
||||
u32 m_block_size;
|
||||
MapType m_ciso_map[CISO_MAP_SIZE];
|
||||
File::IOFile m_file;
|
||||
u64 m_size;
|
||||
u32 m_block_size;
|
||||
MapType m_ciso_map[CISO_MAP_SIZE];
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -19,47 +19,46 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Hash.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/CompressedBlob.h"
|
||||
#include "DiscIO/DiscScrubber.h"
|
||||
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
CompressedBlobReader::CompressedBlobReader(const std::string& filename) : m_file_name(filename)
|
||||
{
|
||||
m_file.Open(filename, "rb");
|
||||
m_file_size = File::GetSize(filename);
|
||||
m_file.ReadArray(&m_header, 1);
|
||||
m_file.Open(filename, "rb");
|
||||
m_file_size = File::GetSize(filename);
|
||||
m_file.ReadArray(&m_header, 1);
|
||||
|
||||
SetSectorSize(m_header.block_size);
|
||||
SetSectorSize(m_header.block_size);
|
||||
|
||||
// cache block pointers and hashes
|
||||
m_block_pointers.resize(m_header.num_blocks);
|
||||
m_file.ReadArray(m_block_pointers.data(), m_header.num_blocks);
|
||||
m_hashes.resize(m_header.num_blocks);
|
||||
m_file.ReadArray(m_hashes.data(), m_header.num_blocks);
|
||||
// cache block pointers and hashes
|
||||
m_block_pointers.resize(m_header.num_blocks);
|
||||
m_file.ReadArray(m_block_pointers.data(), m_header.num_blocks);
|
||||
m_hashes.resize(m_header.num_blocks);
|
||||
m_file.ReadArray(m_hashes.data(), m_header.num_blocks);
|
||||
|
||||
m_data_offset = (sizeof(CompressedBlobHeader))
|
||||
+ (sizeof(u64)) * m_header.num_blocks // skip block pointers
|
||||
+ (sizeof(u32)) * m_header.num_blocks; // skip hashes
|
||||
m_data_offset = (sizeof(CompressedBlobHeader)) +
|
||||
(sizeof(u64)) * m_header.num_blocks // skip block pointers
|
||||
+ (sizeof(u32)) * m_header.num_blocks; // skip hashes
|
||||
|
||||
// A compressed block is never ever longer than a decompressed block, so just header.block_size should be fine.
|
||||
// I still add some safety margin.
|
||||
const u32 zlib_buffer_size = m_header.block_size + 64;
|
||||
m_zlib_buffer.resize(zlib_buffer_size);
|
||||
// A compressed block is never ever longer than a decompressed block, so just header.block_size
|
||||
// should be fine.
|
||||
// I still add some safety margin.
|
||||
const u32 zlib_buffer_size = m_header.block_size + 64;
|
||||
m_zlib_buffer.resize(zlib_buffer_size);
|
||||
}
|
||||
|
||||
std::unique_ptr<CompressedBlobReader> CompressedBlobReader::Create(const std::string& filename)
|
||||
{
|
||||
if (IsGCZBlob(filename))
|
||||
return std::unique_ptr<CompressedBlobReader>(new CompressedBlobReader(filename));
|
||||
if (IsGCZBlob(filename))
|
||||
return std::unique_ptr<CompressedBlobReader>(new CompressedBlobReader(filename));
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CompressedBlobReader::~CompressedBlobReader()
|
||||
@ -69,343 +68,343 @@ CompressedBlobReader::~CompressedBlobReader()
|
||||
// IMPORTANT: Calling this function invalidates all earlier pointers gotten from this function.
|
||||
u64 CompressedBlobReader::GetBlockCompressedSize(u64 block_num) const
|
||||
{
|
||||
u64 start = m_block_pointers[block_num];
|
||||
if (block_num < m_header.num_blocks - 1)
|
||||
return m_block_pointers[block_num + 1] - start;
|
||||
else if (block_num == m_header.num_blocks - 1)
|
||||
return m_header.compressed_data_size - start;
|
||||
else
|
||||
PanicAlert("GetBlockCompressedSize - illegal block number %i", (int)block_num);
|
||||
return 0;
|
||||
u64 start = m_block_pointers[block_num];
|
||||
if (block_num < m_header.num_blocks - 1)
|
||||
return m_block_pointers[block_num + 1] - start;
|
||||
else if (block_num == m_header.num_blocks - 1)
|
||||
return m_header.compressed_data_size - start;
|
||||
else
|
||||
PanicAlert("GetBlockCompressedSize - illegal block number %i", (int)block_num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CompressedBlobReader::GetBlock(u64 block_num, u8 *out_ptr)
|
||||
bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
{
|
||||
bool uncompressed = false;
|
||||
u32 comp_block_size = (u32)GetBlockCompressedSize(block_num);
|
||||
u64 offset = m_block_pointers[block_num] + m_data_offset;
|
||||
bool uncompressed = false;
|
||||
u32 comp_block_size = (u32)GetBlockCompressedSize(block_num);
|
||||
u64 offset = m_block_pointers[block_num] + m_data_offset;
|
||||
|
||||
if (offset & (1ULL << 63))
|
||||
{
|
||||
if (comp_block_size != m_header.block_size)
|
||||
PanicAlert("Uncompressed block with wrong size");
|
||||
uncompressed = true;
|
||||
offset &= ~(1ULL << 63);
|
||||
}
|
||||
if (offset & (1ULL << 63))
|
||||
{
|
||||
if (comp_block_size != m_header.block_size)
|
||||
PanicAlert("Uncompressed block with wrong size");
|
||||
uncompressed = true;
|
||||
offset &= ~(1ULL << 63);
|
||||
}
|
||||
|
||||
// clear unused part of zlib buffer. maybe this can be deleted when it works fully.
|
||||
memset(&m_zlib_buffer[comp_block_size], 0, m_zlib_buffer.size() - comp_block_size);
|
||||
// clear unused part of zlib buffer. maybe this can be deleted when it works fully.
|
||||
memset(&m_zlib_buffer[comp_block_size], 0, m_zlib_buffer.size() - comp_block_size);
|
||||
|
||||
m_file.Seek(offset, SEEK_SET);
|
||||
if (!m_file.ReadBytes(m_zlib_buffer.data(), comp_block_size))
|
||||
{
|
||||
PanicAlertT("The disc image \"%s\" is truncated, some of the data is missing.",
|
||||
m_file_name.c_str());
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
m_file.Seek(offset, SEEK_SET);
|
||||
if (!m_file.ReadBytes(m_zlib_buffer.data(), comp_block_size))
|
||||
{
|
||||
PanicAlertT("The disc image \"%s\" is truncated, some of the data is missing.",
|
||||
m_file_name.c_str());
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// First, check hash.
|
||||
u32 block_hash = HashAdler32(m_zlib_buffer.data(), comp_block_size);
|
||||
if (block_hash != m_hashes[block_num])
|
||||
PanicAlertT("The disc image \"%s\" is corrupt.\n"
|
||||
"Hash of block %" PRIu64 " is %08x instead of %08x.",
|
||||
m_file_name.c_str(),
|
||||
block_num, block_hash, m_hashes[block_num]);
|
||||
// First, check hash.
|
||||
u32 block_hash = HashAdler32(m_zlib_buffer.data(), comp_block_size);
|
||||
if (block_hash != m_hashes[block_num])
|
||||
PanicAlertT("The disc image \"%s\" is corrupt.\n"
|
||||
"Hash of block %" PRIu64 " is %08x instead of %08x.",
|
||||
m_file_name.c_str(), block_num, block_hash, m_hashes[block_num]);
|
||||
|
||||
if (uncompressed)
|
||||
{
|
||||
std::copy(m_zlib_buffer.begin(), m_zlib_buffer.begin() + comp_block_size, out_ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
z_stream z = {};
|
||||
z.next_in = m_zlib_buffer.data();
|
||||
z.avail_in = comp_block_size;
|
||||
if (z.avail_in > m_header.block_size)
|
||||
{
|
||||
PanicAlert("We have a problem");
|
||||
}
|
||||
z.next_out = out_ptr;
|
||||
z.avail_out = m_header.block_size;
|
||||
inflateInit(&z);
|
||||
int status = inflate(&z, Z_FULL_FLUSH);
|
||||
u32 uncomp_size = m_header.block_size - z.avail_out;
|
||||
if (status != Z_STREAM_END)
|
||||
{
|
||||
// this seem to fire wrongly from time to time
|
||||
// to be sure, don't use compressed isos :P
|
||||
PanicAlert("Failure reading block %" PRIu64 " - out of data and not at end.", block_num);
|
||||
}
|
||||
inflateEnd(&z);
|
||||
if (uncomp_size != m_header.block_size)
|
||||
{
|
||||
PanicAlert("Wrong block size");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
if (uncompressed)
|
||||
{
|
||||
std::copy(m_zlib_buffer.begin(), m_zlib_buffer.begin() + comp_block_size, out_ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
z_stream z = {};
|
||||
z.next_in = m_zlib_buffer.data();
|
||||
z.avail_in = comp_block_size;
|
||||
if (z.avail_in > m_header.block_size)
|
||||
{
|
||||
PanicAlert("We have a problem");
|
||||
}
|
||||
z.next_out = out_ptr;
|
||||
z.avail_out = m_header.block_size;
|
||||
inflateInit(&z);
|
||||
int status = inflate(&z, Z_FULL_FLUSH);
|
||||
u32 uncomp_size = m_header.block_size - z.avail_out;
|
||||
if (status != Z_STREAM_END)
|
||||
{
|
||||
// this seem to fire wrongly from time to time
|
||||
// to be sure, don't use compressed isos :P
|
||||
PanicAlert("Failure reading block %" PRIu64 " - out of data and not at end.", block_num);
|
||||
}
|
||||
inflateEnd(&z);
|
||||
if (uncomp_size != m_header.block_size)
|
||||
{
|
||||
PanicAlert("Wrong block size");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompressFileToBlob(const std::string& infile, const std::string& outfile, u32 sub_type,
|
||||
int block_size, CompressCB callback, void* arg)
|
||||
int block_size, CompressCB callback, void* arg)
|
||||
{
|
||||
bool scrubbing = false;
|
||||
bool scrubbing = false;
|
||||
|
||||
if (IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("\"%s\" is already compressed! Cannot compress it further.", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
if (IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("\"%s\" is already compressed! Cannot compress it further.", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
File::IOFile inf(infile, "rb");
|
||||
if (!inf)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
File::IOFile inf(infile, "rb");
|
||||
if (!inf)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
File::IOFile f(outfile, "wb");
|
||||
if (!f)
|
||||
{
|
||||
PanicAlertT("Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can be written.",
|
||||
outfile.c_str());
|
||||
return false;
|
||||
}
|
||||
File::IOFile f(outfile, "wb");
|
||||
if (!f)
|
||||
{
|
||||
PanicAlertT("Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can "
|
||||
"be written.",
|
||||
outfile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sub_type == 1)
|
||||
{
|
||||
if (!DiscScrubber::SetupScrub(infile, block_size))
|
||||
{
|
||||
PanicAlertT("\"%s\" failed to be scrubbed. Probably the image is corrupt.", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
if (sub_type == 1)
|
||||
{
|
||||
if (!DiscScrubber::SetupScrub(infile, block_size))
|
||||
{
|
||||
PanicAlertT("\"%s\" failed to be scrubbed. Probably the image is corrupt.", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
scrubbing = true;
|
||||
}
|
||||
scrubbing = true;
|
||||
}
|
||||
|
||||
z_stream z = {};
|
||||
if (deflateInit(&z, 9) != Z_OK)
|
||||
{
|
||||
DiscScrubber::Cleanup();
|
||||
return false;
|
||||
}
|
||||
z_stream z = {};
|
||||
if (deflateInit(&z, 9) != Z_OK)
|
||||
{
|
||||
DiscScrubber::Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
callback(GetStringT("Files opened, ready to compress."), 0, arg);
|
||||
callback(GetStringT("Files opened, ready to compress."), 0, arg);
|
||||
|
||||
CompressedBlobHeader header;
|
||||
header.magic_cookie = kBlobCookie;
|
||||
header.sub_type = sub_type;
|
||||
header.block_size = block_size;
|
||||
header.data_size = File::GetSize(infile);
|
||||
CompressedBlobHeader header;
|
||||
header.magic_cookie = kBlobCookie;
|
||||
header.sub_type = sub_type;
|
||||
header.block_size = block_size;
|
||||
header.data_size = File::GetSize(infile);
|
||||
|
||||
// round upwards!
|
||||
header.num_blocks = (u32)((header.data_size + (block_size - 1)) / block_size);
|
||||
// round upwards!
|
||||
header.num_blocks = (u32)((header.data_size + (block_size - 1)) / block_size);
|
||||
|
||||
std::vector<u64> offsets(header.num_blocks);
|
||||
std::vector<u32> hashes(header.num_blocks);
|
||||
std::vector<u8> out_buf(block_size);
|
||||
std::vector<u8> in_buf(block_size);
|
||||
std::vector<u64> offsets(header.num_blocks);
|
||||
std::vector<u32> hashes(header.num_blocks);
|
||||
std::vector<u8> out_buf(block_size);
|
||||
std::vector<u8> in_buf(block_size);
|
||||
|
||||
// seek past the header (we will write it at the end)
|
||||
f.Seek(sizeof(CompressedBlobHeader), SEEK_CUR);
|
||||
// seek past the offset and hash tables (we will write them at the end)
|
||||
f.Seek((sizeof(u64) + sizeof(u32)) * header.num_blocks, SEEK_CUR);
|
||||
// seek past the header (we will write it at the end)
|
||||
f.Seek(sizeof(CompressedBlobHeader), SEEK_CUR);
|
||||
// seek past the offset and hash tables (we will write them at the end)
|
||||
f.Seek((sizeof(u64) + sizeof(u32)) * header.num_blocks, SEEK_CUR);
|
||||
|
||||
// Now we are ready to write compressed data!
|
||||
u64 position = 0;
|
||||
int num_compressed = 0;
|
||||
int num_stored = 0;
|
||||
int progress_monitor = std::max<int>(1, header.num_blocks / 1000);
|
||||
bool success = true;
|
||||
// Now we are ready to write compressed data!
|
||||
u64 position = 0;
|
||||
int num_compressed = 0;
|
||||
int num_stored = 0;
|
||||
int progress_monitor = std::max<int>(1, header.num_blocks / 1000);
|
||||
bool success = true;
|
||||
|
||||
for (u32 i = 0; i < header.num_blocks; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
const u64 inpos = inf.Tell();
|
||||
int ratio = 0;
|
||||
if (inpos != 0)
|
||||
ratio = (int)(100 * position / inpos);
|
||||
for (u32 i = 0; i < header.num_blocks; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
const u64 inpos = inf.Tell();
|
||||
int ratio = 0;
|
||||
if (inpos != 0)
|
||||
ratio = (int)(100 * position / inpos);
|
||||
|
||||
std::string temp = StringFromFormat(GetStringT("%i of %i blocks. Compression ratio %i%%").c_str(),
|
||||
i, header.num_blocks, ratio);
|
||||
bool was_cancelled = !callback(temp, (float)i / (float)header.num_blocks, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string temp =
|
||||
StringFromFormat(GetStringT("%i of %i blocks. Compression ratio %i%%").c_str(), i,
|
||||
header.num_blocks, ratio);
|
||||
bool was_cancelled = !callback(temp, (float)i / (float)header.num_blocks, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
offsets[i] = position;
|
||||
offsets[i] = position;
|
||||
|
||||
size_t read_bytes;
|
||||
if (scrubbing)
|
||||
read_bytes = DiscScrubber::GetNextBlock(inf, in_buf.data());
|
||||
else
|
||||
inf.ReadArray(in_buf.data(), header.block_size, &read_bytes);
|
||||
if (read_bytes < header.block_size)
|
||||
std::fill(in_buf.begin() + read_bytes, in_buf.begin() + header.block_size, 0);
|
||||
size_t read_bytes;
|
||||
if (scrubbing)
|
||||
read_bytes = DiscScrubber::GetNextBlock(inf, in_buf.data());
|
||||
else
|
||||
inf.ReadArray(in_buf.data(), header.block_size, &read_bytes);
|
||||
if (read_bytes < header.block_size)
|
||||
std::fill(in_buf.begin() + read_bytes, in_buf.begin() + header.block_size, 0);
|
||||
|
||||
int retval = deflateReset(&z);
|
||||
z.next_in = in_buf.data();
|
||||
z.avail_in = header.block_size;
|
||||
z.next_out = out_buf.data();
|
||||
z.avail_out = block_size;
|
||||
int retval = deflateReset(&z);
|
||||
z.next_in = in_buf.data();
|
||||
z.avail_in = header.block_size;
|
||||
z.next_out = out_buf.data();
|
||||
z.avail_out = block_size;
|
||||
|
||||
if (retval != Z_OK)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Deflate failed");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
if (retval != Z_OK)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Deflate failed");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int status = deflate(&z, Z_FINISH);
|
||||
int comp_size = block_size - z.avail_out;
|
||||
int status = deflate(&z, Z_FINISH);
|
||||
int comp_size = block_size - z.avail_out;
|
||||
|
||||
u8* write_buf;
|
||||
int write_size;
|
||||
if ((status != Z_STREAM_END) || (z.avail_out < 10))
|
||||
{
|
||||
//PanicAlert("%i %i Store %i", i*block_size, position, comp_size);
|
||||
// let's store uncompressed
|
||||
write_buf = in_buf.data();
|
||||
offsets[i] |= 0x8000000000000000ULL;
|
||||
write_size = block_size;
|
||||
num_stored++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// let's store compressed
|
||||
//PanicAlert("Comp %i to %i", block_size, comp_size);
|
||||
write_buf = out_buf.data();
|
||||
write_size = comp_size;
|
||||
num_compressed++;
|
||||
}
|
||||
u8* write_buf;
|
||||
int write_size;
|
||||
if ((status != Z_STREAM_END) || (z.avail_out < 10))
|
||||
{
|
||||
// PanicAlert("%i %i Store %i", i*block_size, position, comp_size);
|
||||
// let's store uncompressed
|
||||
write_buf = in_buf.data();
|
||||
offsets[i] |= 0x8000000000000000ULL;
|
||||
write_size = block_size;
|
||||
num_stored++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// let's store compressed
|
||||
// PanicAlert("Comp %i to %i", block_size, comp_size);
|
||||
write_buf = out_buf.data();
|
||||
write_size = comp_size;
|
||||
num_compressed++;
|
||||
}
|
||||
|
||||
if (!f.WriteBytes(write_buf, write_size))
|
||||
{
|
||||
PanicAlertT(
|
||||
"Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
if (!f.WriteBytes(write_buf, write_size))
|
||||
{
|
||||
PanicAlertT("Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
position += write_size;
|
||||
position += write_size;
|
||||
|
||||
hashes[i] = HashAdler32(write_buf, write_size);
|
||||
}
|
||||
hashes[i] = HashAdler32(write_buf, write_size);
|
||||
}
|
||||
|
||||
header.compressed_data_size = position;
|
||||
header.compressed_data_size = position;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
f.Close();
|
||||
File::Delete(outfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Okay, go back and fill in headers
|
||||
f.Seek(0, SEEK_SET);
|
||||
f.WriteArray(&header, 1);
|
||||
f.WriteArray(offsets.data(), header.num_blocks);
|
||||
f.WriteArray(hashes.data(), header.num_blocks);
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
f.Close();
|
||||
File::Delete(outfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Okay, go back and fill in headers
|
||||
f.Seek(0, SEEK_SET);
|
||||
f.WriteArray(&header, 1);
|
||||
f.WriteArray(offsets.data(), header.num_blocks);
|
||||
f.WriteArray(hashes.data(), header.num_blocks);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
deflateEnd(&z);
|
||||
DiscScrubber::Cleanup();
|
||||
// Cleanup
|
||||
deflateEnd(&z);
|
||||
DiscScrubber::Cleanup();
|
||||
|
||||
if (success)
|
||||
{
|
||||
callback(GetStringT("Done compressing disc image."), 1.0f, arg);
|
||||
}
|
||||
return success;
|
||||
if (success)
|
||||
{
|
||||
callback(GetStringT("Done compressing disc image."), 1.0f, arg);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DecompressBlobToFile(const std::string& infile, const std::string& outfile, CompressCB callback, void* arg)
|
||||
bool DecompressBlobToFile(const std::string& infile, const std::string& outfile,
|
||||
CompressCB callback, void* arg)
|
||||
{
|
||||
if (!IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("File not compressed");
|
||||
return false;
|
||||
}
|
||||
if (!IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("File not compressed");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<CompressedBlobReader> reader(CompressedBlobReader::Create(infile));
|
||||
if (!reader)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<CompressedBlobReader> reader(CompressedBlobReader::Create(infile));
|
||||
if (!reader)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
File::IOFile f(outfile, "wb");
|
||||
if (!f)
|
||||
{
|
||||
PanicAlertT(
|
||||
"Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can be written.",
|
||||
outfile.c_str());
|
||||
return false;
|
||||
}
|
||||
File::IOFile f(outfile, "wb");
|
||||
if (!f)
|
||||
{
|
||||
PanicAlertT("Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can "
|
||||
"be written.",
|
||||
outfile.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompressedBlobHeader &header = reader->GetHeader();
|
||||
static const size_t BUFFER_BLOCKS = 32;
|
||||
size_t buffer_size = header.block_size * BUFFER_BLOCKS;
|
||||
size_t last_buffer_size = header.block_size * (header.num_blocks % BUFFER_BLOCKS);
|
||||
std::vector<u8> buffer(buffer_size);
|
||||
u32 num_buffers = (header.num_blocks + BUFFER_BLOCKS - 1) / BUFFER_BLOCKS;
|
||||
int progress_monitor = std::max<int>(1, num_buffers / 100);
|
||||
bool success = true;
|
||||
const CompressedBlobHeader& header = reader->GetHeader();
|
||||
static const size_t BUFFER_BLOCKS = 32;
|
||||
size_t buffer_size = header.block_size * BUFFER_BLOCKS;
|
||||
size_t last_buffer_size = header.block_size * (header.num_blocks % BUFFER_BLOCKS);
|
||||
std::vector<u8> buffer(buffer_size);
|
||||
u32 num_buffers = (header.num_blocks + BUFFER_BLOCKS - 1) / BUFFER_BLOCKS;
|
||||
int progress_monitor = std::max<int>(1, num_buffers / 100);
|
||||
bool success = true;
|
||||
|
||||
for (u64 i = 0; i < num_buffers; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
bool was_cancelled = !callback(GetStringT("Unpacking"), (float)i / (float)num_buffers, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const size_t sz = i == num_buffers - 1 ? last_buffer_size : buffer_size;
|
||||
reader->Read(i * buffer_size, sz, buffer.data());
|
||||
if (!f.WriteBytes(buffer.data(), sz))
|
||||
{
|
||||
PanicAlertT(
|
||||
"Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (u64 i = 0; i < num_buffers; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
bool was_cancelled = !callback(GetStringT("Unpacking"), (float)i / (float)num_buffers, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const size_t sz = i == num_buffers - 1 ? last_buffer_size : buffer_size;
|
||||
reader->Read(i * buffer_size, sz, buffer.data());
|
||||
if (!f.WriteBytes(buffer.data(), sz))
|
||||
{
|
||||
PanicAlertT("Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
f.Close();
|
||||
File::Delete(outfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Resize(header.data_size);
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
f.Close();
|
||||
File::Delete(outfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Resize(header.data_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsGCZBlob(const std::string& filename)
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
File::IOFile f(filename, "rb");
|
||||
|
||||
CompressedBlobHeader header;
|
||||
return f.ReadArray(&header, 1) && (header.magic_cookie == kBlobCookie);
|
||||
CompressedBlobHeader header;
|
||||
return f.ReadArray(&header, 1) && (header.magic_cookie == kBlobCookie);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// WARNING Code not big-endian safe.
|
||||
|
||||
// To create new compressed BLOBs, use CompressFileToBlob.
|
||||
@ -24,7 +23,6 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
bool IsGCZBlob(const std::string& filename);
|
||||
|
||||
const u32 kBlobCookie = 0xB10BC001;
|
||||
@ -35,38 +33,39 @@ const u32 kBlobCookie = 0xB10BC001;
|
||||
// compressed data
|
||||
|
||||
// Blocks that won't compress to less than 97% of the original size are stored as-is.
|
||||
struct CompressedBlobHeader // 32 bytes
|
||||
struct CompressedBlobHeader // 32 bytes
|
||||
{
|
||||
u32 magic_cookie; //0xB10BB10B
|
||||
u32 sub_type; // GC image, whatever
|
||||
u64 compressed_data_size;
|
||||
u64 data_size;
|
||||
u32 block_size;
|
||||
u32 num_blocks;
|
||||
u32 magic_cookie; // 0xB10BB10B
|
||||
u32 sub_type; // GC image, whatever
|
||||
u64 compressed_data_size;
|
||||
u64 data_size;
|
||||
u32 block_size;
|
||||
u32 num_blocks;
|
||||
};
|
||||
|
||||
class CompressedBlobReader : public SectorReader
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<CompressedBlobReader> Create(const std::string& filename);
|
||||
~CompressedBlobReader();
|
||||
const CompressedBlobHeader &GetHeader() const { return m_header; }
|
||||
BlobType GetBlobType() const override { return BlobType::GCZ; }
|
||||
u64 GetDataSize() const override { return m_header.data_size; }
|
||||
u64 GetRawSize() const override { return m_file_size; }
|
||||
u64 GetBlockCompressedSize(u64 block_num) const;
|
||||
bool GetBlock(u64 block_num, u8* out_ptr) override;
|
||||
private:
|
||||
CompressedBlobReader(const std::string& filename);
|
||||
static std::unique_ptr<CompressedBlobReader> Create(const std::string& filename);
|
||||
~CompressedBlobReader();
|
||||
const CompressedBlobHeader& GetHeader() const { return m_header; }
|
||||
BlobType GetBlobType() const override { return BlobType::GCZ; }
|
||||
u64 GetDataSize() const override { return m_header.data_size; }
|
||||
u64 GetRawSize() const override { return m_file_size; }
|
||||
u64 GetBlockCompressedSize(u64 block_num) const;
|
||||
bool GetBlock(u64 block_num, u8* out_ptr) override;
|
||||
|
||||
CompressedBlobHeader m_header;
|
||||
std::vector<u64> m_block_pointers;
|
||||
std::vector<u32> m_hashes;
|
||||
int m_data_offset;
|
||||
File::IOFile m_file;
|
||||
u64 m_file_size;
|
||||
std::vector<u8> m_zlib_buffer;
|
||||
std::string m_file_name;
|
||||
private:
|
||||
CompressedBlobReader(const std::string& filename);
|
||||
|
||||
CompressedBlobHeader m_header;
|
||||
std::vector<u64> m_block_pointers;
|
||||
std::vector<u32> m_hashes;
|
||||
int m_data_offset;
|
||||
File::IOFile m_file;
|
||||
u64 m_file_size;
|
||||
std::vector<u8> m_zlib_buffer;
|
||||
std::string m_file_name;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -20,10 +20,8 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
namespace DiscScrubber
|
||||
{
|
||||
|
||||
#define CLUSTER_SIZE 0x8000
|
||||
|
||||
static u8* m_FreeTable = nullptr;
|
||||
@ -38,40 +36,39 @@ static std::unique_ptr<IVolume> s_disc;
|
||||
|
||||
struct SPartitionHeader
|
||||
{
|
||||
u8* Ticket[0x2a4];
|
||||
u32 TMDSize;
|
||||
u64 TMDOffset;
|
||||
u32 CertChainSize;
|
||||
u64 CertChainOffset;
|
||||
// H3Size is always 0x18000
|
||||
u64 H3Offset;
|
||||
u64 DataOffset;
|
||||
u64 DataSize;
|
||||
// TMD would be here
|
||||
u64 DOLOffset;
|
||||
u64 DOLSize;
|
||||
u64 FSTOffset;
|
||||
u64 FSTSize;
|
||||
u32 ApploaderSize;
|
||||
u32 ApploaderTrailerSize;
|
||||
u8* Ticket[0x2a4];
|
||||
u32 TMDSize;
|
||||
u64 TMDOffset;
|
||||
u32 CertChainSize;
|
||||
u64 CertChainOffset;
|
||||
// H3Size is always 0x18000
|
||||
u64 H3Offset;
|
||||
u64 DataOffset;
|
||||
u64 DataSize;
|
||||
// TMD would be here
|
||||
u64 DOLOffset;
|
||||
u64 DOLSize;
|
||||
u64 FSTOffset;
|
||||
u64 FSTSize;
|
||||
u32 ApploaderSize;
|
||||
u32 ApploaderTrailerSize;
|
||||
};
|
||||
struct SPartition
|
||||
{
|
||||
u32 GroupNumber;
|
||||
u32 Number;
|
||||
u64 Offset;
|
||||
u32 Type;
|
||||
SPartitionHeader Header;
|
||||
u32 GroupNumber;
|
||||
u32 Number;
|
||||
u64 Offset;
|
||||
u32 Type;
|
||||
SPartitionHeader Header;
|
||||
};
|
||||
struct SPartitionGroup
|
||||
{
|
||||
u32 numPartitions;
|
||||
u64 PartitionsOffset;
|
||||
std::vector<SPartition> PartitionsVec;
|
||||
u32 numPartitions;
|
||||
u64 PartitionsOffset;
|
||||
std::vector<SPartition> PartitionsVec;
|
||||
};
|
||||
static SPartitionGroup PartitionGroup[4];
|
||||
|
||||
|
||||
void MarkAsUsed(u64 _Offset, u64 _Size);
|
||||
void MarkAsUsedE(u64 _PartitionDataOffset, u64 _Offset, u64 _Size);
|
||||
bool ReadFromVolume(u64 _Offset, u32& _Buffer, bool _Decrypt);
|
||||
@ -79,257 +76,255 @@ bool ReadFromVolume(u64 _Offset, u64& _Buffer, bool _Decrypt);
|
||||
bool ParseDisc();
|
||||
bool ParsePartitionData(SPartition& _rPartition);
|
||||
|
||||
|
||||
bool SetupScrub(const std::string& filename, int block_size)
|
||||
{
|
||||
bool success = true;
|
||||
m_Filename = filename;
|
||||
m_BlockSize = block_size;
|
||||
bool success = true;
|
||||
m_Filename = filename;
|
||||
m_BlockSize = block_size;
|
||||
|
||||
if (CLUSTER_SIZE % m_BlockSize != 0)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Block size %i is not a factor of 0x8000, scrubbing not possible", m_BlockSize);
|
||||
return false;
|
||||
}
|
||||
if (CLUSTER_SIZE % m_BlockSize != 0)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Block size %i is not a factor of 0x8000, scrubbing not possible",
|
||||
m_BlockSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_BlocksPerCluster = CLUSTER_SIZE / m_BlockSize;
|
||||
m_BlocksPerCluster = CLUSTER_SIZE / m_BlockSize;
|
||||
|
||||
s_disc = CreateVolumeFromFilename(filename);
|
||||
if (!s_disc)
|
||||
return false;
|
||||
s_disc = CreateVolumeFromFilename(filename);
|
||||
if (!s_disc)
|
||||
return false;
|
||||
|
||||
m_FileSize = s_disc->GetSize();
|
||||
m_FileSize = s_disc->GetSize();
|
||||
|
||||
u32 numClusters = (u32)(m_FileSize / CLUSTER_SIZE);
|
||||
u32 numClusters = (u32)(m_FileSize / CLUSTER_SIZE);
|
||||
|
||||
// Warn if not DVD5 or DVD9 size
|
||||
if (numClusters != 0x23048 && numClusters != 0x46090)
|
||||
WARN_LOG(DISCIO, "%s is not a standard sized Wii disc! (%x blocks)", filename.c_str(), numClusters);
|
||||
// Warn if not DVD5 or DVD9 size
|
||||
if (numClusters != 0x23048 && numClusters != 0x46090)
|
||||
WARN_LOG(DISCIO, "%s is not a standard sized Wii disc! (%x blocks)", filename.c_str(),
|
||||
numClusters);
|
||||
|
||||
// Table of free blocks
|
||||
m_FreeTable = new u8[numClusters];
|
||||
std::fill(m_FreeTable, m_FreeTable + numClusters, 1);
|
||||
// Table of free blocks
|
||||
m_FreeTable = new u8[numClusters];
|
||||
std::fill(m_FreeTable, m_FreeTable + numClusters, 1);
|
||||
|
||||
// Fill out table of free blocks
|
||||
success = ParseDisc();
|
||||
// Fill out table of free blocks
|
||||
success = ParseDisc();
|
||||
|
||||
// Done with it; need it closed for the next part
|
||||
s_disc.reset();
|
||||
m_BlockCount = 0;
|
||||
// Done with it; need it closed for the next part
|
||||
s_disc.reset();
|
||||
m_BlockCount = 0;
|
||||
|
||||
// Let's not touch the file if we've failed up to here :p
|
||||
if (!success)
|
||||
Cleanup();
|
||||
// Let's not touch the file if we've failed up to here :p
|
||||
if (!success)
|
||||
Cleanup();
|
||||
|
||||
m_isScrubbing = success;
|
||||
return success;
|
||||
m_isScrubbing = success;
|
||||
return success;
|
||||
}
|
||||
|
||||
size_t GetNextBlock(File::IOFile& in, u8* buffer)
|
||||
{
|
||||
u64 CurrentOffset = m_BlockCount * m_BlockSize;
|
||||
u64 i = CurrentOffset / CLUSTER_SIZE;
|
||||
u64 CurrentOffset = m_BlockCount * m_BlockSize;
|
||||
u64 i = CurrentOffset / CLUSTER_SIZE;
|
||||
|
||||
size_t ReadBytes = 0;
|
||||
if (m_isScrubbing && m_FreeTable[i])
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Freeing 0x%016" PRIx64, CurrentOffset);
|
||||
std::fill(buffer, buffer + m_BlockSize, 0xFF);
|
||||
in.Seek(m_BlockSize, SEEK_CUR);
|
||||
ReadBytes = m_BlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Used 0x%016" PRIx64, CurrentOffset);
|
||||
in.ReadArray(buffer, m_BlockSize, &ReadBytes);
|
||||
}
|
||||
size_t ReadBytes = 0;
|
||||
if (m_isScrubbing && m_FreeTable[i])
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Freeing 0x%016" PRIx64, CurrentOffset);
|
||||
std::fill(buffer, buffer + m_BlockSize, 0xFF);
|
||||
in.Seek(m_BlockSize, SEEK_CUR);
|
||||
ReadBytes = m_BlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Used 0x%016" PRIx64, CurrentOffset);
|
||||
in.ReadArray(buffer, m_BlockSize, &ReadBytes);
|
||||
}
|
||||
|
||||
m_BlockCount++;
|
||||
return ReadBytes;
|
||||
m_BlockCount++;
|
||||
return ReadBytes;
|
||||
}
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
if (m_FreeTable) delete[] m_FreeTable;
|
||||
m_FreeTable = nullptr;
|
||||
m_FileSize = 0;
|
||||
m_BlockCount = 0;
|
||||
m_BlockSize = 0;
|
||||
m_BlocksPerCluster = 0;
|
||||
m_isScrubbing = false;
|
||||
if (m_FreeTable)
|
||||
delete[] m_FreeTable;
|
||||
m_FreeTable = nullptr;
|
||||
m_FileSize = 0;
|
||||
m_BlockCount = 0;
|
||||
m_BlockSize = 0;
|
||||
m_BlocksPerCluster = 0;
|
||||
m_isScrubbing = false;
|
||||
}
|
||||
|
||||
void MarkAsUsed(u64 _Offset, u64 _Size)
|
||||
{
|
||||
u64 CurrentOffset = _Offset;
|
||||
u64 EndOffset = CurrentOffset + _Size;
|
||||
u64 CurrentOffset = _Offset;
|
||||
u64 EndOffset = CurrentOffset + _Size;
|
||||
|
||||
DEBUG_LOG(DISCIO, "Marking 0x%016" PRIx64 " - 0x%016" PRIx64 " as used", _Offset, EndOffset);
|
||||
DEBUG_LOG(DISCIO, "Marking 0x%016" PRIx64 " - 0x%016" PRIx64 " as used", _Offset, EndOffset);
|
||||
|
||||
while ((CurrentOffset < EndOffset) && (CurrentOffset < m_FileSize))
|
||||
{
|
||||
m_FreeTable[CurrentOffset / CLUSTER_SIZE] = 0;
|
||||
CurrentOffset += CLUSTER_SIZE;
|
||||
}
|
||||
while ((CurrentOffset < EndOffset) && (CurrentOffset < m_FileSize))
|
||||
{
|
||||
m_FreeTable[CurrentOffset / CLUSTER_SIZE] = 0;
|
||||
CurrentOffset += CLUSTER_SIZE;
|
||||
}
|
||||
}
|
||||
// Compensate for 0x400(SHA-1) per 0x8000(cluster)
|
||||
void MarkAsUsedE(u64 _PartitionDataOffset, u64 _Offset, u64 _Size)
|
||||
{
|
||||
u64 Offset;
|
||||
u64 Size;
|
||||
u64 Offset;
|
||||
u64 Size;
|
||||
|
||||
Offset = _Offset / 0x7c00;
|
||||
Offset = Offset * CLUSTER_SIZE;
|
||||
Offset += _PartitionDataOffset;
|
||||
Offset = _Offset / 0x7c00;
|
||||
Offset = Offset * CLUSTER_SIZE;
|
||||
Offset += _PartitionDataOffset;
|
||||
|
||||
Size = _Size / 0x7c00;
|
||||
Size = (Size + 1) * CLUSTER_SIZE;
|
||||
Size = _Size / 0x7c00;
|
||||
Size = (Size + 1) * CLUSTER_SIZE;
|
||||
|
||||
// Add on the offset in the first block for the case where data straddles blocks
|
||||
Size += _Offset % 0x7c00;
|
||||
// Add on the offset in the first block for the case where data straddles blocks
|
||||
Size += _Offset % 0x7c00;
|
||||
|
||||
MarkAsUsed(Offset, Size);
|
||||
MarkAsUsed(Offset, Size);
|
||||
}
|
||||
|
||||
// Helper functions for reading the BE volume
|
||||
bool ReadFromVolume(u64 _Offset, u32& _Buffer, bool _Decrypt)
|
||||
{
|
||||
return s_disc->ReadSwapped(_Offset, &_Buffer, _Decrypt);
|
||||
return s_disc->ReadSwapped(_Offset, &_Buffer, _Decrypt);
|
||||
}
|
||||
|
||||
bool ReadFromVolume(u64 _Offset, u64& _Buffer, bool _Decrypt)
|
||||
{
|
||||
u32 temp_buffer;
|
||||
if (!s_disc->ReadSwapped(_Offset, &temp_buffer, _Decrypt))
|
||||
return false;
|
||||
_Buffer = static_cast<u64>(temp_buffer) << 2;
|
||||
return true;
|
||||
u32 temp_buffer;
|
||||
if (!s_disc->ReadSwapped(_Offset, &temp_buffer, _Decrypt))
|
||||
return false;
|
||||
_Buffer = static_cast<u64>(temp_buffer) << 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseDisc()
|
||||
{
|
||||
// Mark the header as used - it's mostly 0s anyways
|
||||
MarkAsUsed(0, 0x50000);
|
||||
// Mark the header as used - it's mostly 0s anyways
|
||||
MarkAsUsed(0, 0x50000);
|
||||
|
||||
for (int x = 0; x < 4; x++)
|
||||
{
|
||||
if (!ReadFromVolume(0x40000 + (x * 8) + 0, PartitionGroup[x].numPartitions, false) ||
|
||||
!ReadFromVolume(0x40000 + (x * 8) + 4, PartitionGroup[x].PartitionsOffset, false))
|
||||
return false;
|
||||
for (int x = 0; x < 4; x++)
|
||||
{
|
||||
if (!ReadFromVolume(0x40000 + (x * 8) + 0, PartitionGroup[x].numPartitions, false) ||
|
||||
!ReadFromVolume(0x40000 + (x * 8) + 4, PartitionGroup[x].PartitionsOffset, false))
|
||||
return false;
|
||||
|
||||
// Read all partitions
|
||||
for (u32 i = 0; i < PartitionGroup[x].numPartitions; i++)
|
||||
{
|
||||
SPartition Partition;
|
||||
// Read all partitions
|
||||
for (u32 i = 0; i < PartitionGroup[x].numPartitions; i++)
|
||||
{
|
||||
SPartition Partition;
|
||||
|
||||
Partition.GroupNumber = x;
|
||||
Partition.Number = i;
|
||||
Partition.GroupNumber = x;
|
||||
Partition.Number = i;
|
||||
|
||||
if (!ReadFromVolume(PartitionGroup[x].PartitionsOffset + (i * 8) + 0, Partition.Offset, false) ||
|
||||
!ReadFromVolume(PartitionGroup[x].PartitionsOffset + (i * 8) + 4, Partition.Type, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2a4, Partition.Header.TMDSize, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2a8, Partition.Header.TMDOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2ac, Partition.Header.CertChainSize, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b0, Partition.Header.CertChainOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b4, Partition.Header.H3Offset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b8, Partition.Header.DataOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2bc, Partition.Header.DataSize, false))
|
||||
return false;
|
||||
if (!ReadFromVolume(PartitionGroup[x].PartitionsOffset + (i * 8) + 0, Partition.Offset,
|
||||
false) ||
|
||||
!ReadFromVolume(PartitionGroup[x].PartitionsOffset + (i * 8) + 4, Partition.Type,
|
||||
false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2a4, Partition.Header.TMDSize, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2a8, Partition.Header.TMDOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2ac, Partition.Header.CertChainSize, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b0, Partition.Header.CertChainOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b4, Partition.Header.H3Offset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2b8, Partition.Header.DataOffset, false) ||
|
||||
!ReadFromVolume(Partition.Offset + 0x2bc, Partition.Header.DataSize, false))
|
||||
return false;
|
||||
|
||||
PartitionGroup[x].PartitionsVec.push_back(Partition);
|
||||
}
|
||||
PartitionGroup[x].PartitionsVec.push_back(Partition);
|
||||
}
|
||||
|
||||
for (auto& rPartition : PartitionGroup[x].PartitionsVec)
|
||||
{
|
||||
const SPartitionHeader& rHeader = rPartition.Header;
|
||||
for (auto& rPartition : PartitionGroup[x].PartitionsVec)
|
||||
{
|
||||
const SPartitionHeader& rHeader = rPartition.Header;
|
||||
|
||||
MarkAsUsed(rPartition.Offset, 0x2c0);
|
||||
MarkAsUsed(rPartition.Offset, 0x2c0);
|
||||
|
||||
MarkAsUsed(rPartition.Offset + rHeader.TMDOffset, rHeader.TMDSize);
|
||||
MarkAsUsed(rPartition.Offset + rHeader.CertChainOffset, rHeader.CertChainSize);
|
||||
MarkAsUsed(rPartition.Offset + rHeader.H3Offset, 0x18000);
|
||||
// This would mark the whole (encrypted) data area
|
||||
// we need to parse FST and other crap to find what's free within it!
|
||||
//MarkAsUsed(rPartition.Offset + rHeader.DataOffset, rHeader.DataSize);
|
||||
MarkAsUsed(rPartition.Offset + rHeader.TMDOffset, rHeader.TMDSize);
|
||||
MarkAsUsed(rPartition.Offset + rHeader.CertChainOffset, rHeader.CertChainSize);
|
||||
MarkAsUsed(rPartition.Offset + rHeader.H3Offset, 0x18000);
|
||||
// This would mark the whole (encrypted) data area
|
||||
// we need to parse FST and other crap to find what's free within it!
|
||||
// MarkAsUsed(rPartition.Offset + rHeader.DataOffset, rHeader.DataSize);
|
||||
|
||||
// Parse Data! This is where the big gain is
|
||||
if (!ParsePartitionData(rPartition))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Parse Data! This is where the big gain is
|
||||
if (!ParsePartitionData(rPartition))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Operations dealing with encrypted space are done here - the volume is swapped to allow this
|
||||
bool ParsePartitionData(SPartition& partition)
|
||||
{
|
||||
bool parsed_ok = true;
|
||||
bool parsed_ok = true;
|
||||
|
||||
// Switch out the main volume temporarily
|
||||
std::unique_ptr<IVolume> old_volume;
|
||||
s_disc.swap(old_volume);
|
||||
// Switch out the main volume temporarily
|
||||
std::unique_ptr<IVolume> old_volume;
|
||||
s_disc.swap(old_volume);
|
||||
|
||||
// Ready some stuff
|
||||
s_disc = CreateVolumeFromFilename(m_Filename, partition.GroupNumber, partition.Number);
|
||||
if (s_disc == nullptr)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Failed to create volume from file %s", m_Filename.c_str());
|
||||
s_disc.swap(old_volume);
|
||||
return false;
|
||||
}
|
||||
// Ready some stuff
|
||||
s_disc = CreateVolumeFromFilename(m_Filename, partition.GroupNumber, partition.Number);
|
||||
if (s_disc == nullptr)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Failed to create volume from file %s", m_Filename.c_str());
|
||||
s_disc.swap(old_volume);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<IFileSystem> filesystem(CreateFileSystem(s_disc.get()));
|
||||
if (!filesystem)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Failed to create filesystem for group %d partition %u", partition.GroupNumber, partition.Number);
|
||||
parsed_ok = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark things as used which are not in the filesystem
|
||||
// Header, Header Information, Apploader
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x2440 + 0x14, partition.Header.ApploaderSize, true);
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x2440 + 0x18, partition.Header.ApploaderTrailerSize, true);
|
||||
MarkAsUsedE(partition.Offset
|
||||
+ partition.Header.DataOffset
|
||||
, 0
|
||||
, 0x2440
|
||||
+ partition.Header.ApploaderSize
|
||||
+ partition.Header.ApploaderTrailerSize);
|
||||
std::unique_ptr<IFileSystem> filesystem(CreateFileSystem(s_disc.get()));
|
||||
if (!filesystem)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Failed to create filesystem for group %d partition %u",
|
||||
partition.GroupNumber, partition.Number);
|
||||
parsed_ok = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark things as used which are not in the filesystem
|
||||
// Header, Header Information, Apploader
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x2440 + 0x14, partition.Header.ApploaderSize, true);
|
||||
parsed_ok =
|
||||
parsed_ok && ReadFromVolume(0x2440 + 0x18, partition.Header.ApploaderTrailerSize, true);
|
||||
MarkAsUsedE(partition.Offset + partition.Header.DataOffset, 0,
|
||||
0x2440 + partition.Header.ApploaderSize + partition.Header.ApploaderTrailerSize);
|
||||
|
||||
// DOL
|
||||
partition.Header.DOLOffset = filesystem->GetBootDOLOffset();
|
||||
partition.Header.DOLSize = filesystem->GetBootDOLSize(partition.Header.DOLOffset);
|
||||
parsed_ok = parsed_ok && partition.Header.DOLOffset && partition.Header.DOLSize;
|
||||
MarkAsUsedE(partition.Offset
|
||||
+ partition.Header.DataOffset
|
||||
, partition.Header.DOLOffset
|
||||
, partition.Header.DOLSize);
|
||||
// DOL
|
||||
partition.Header.DOLOffset = filesystem->GetBootDOLOffset();
|
||||
partition.Header.DOLSize = filesystem->GetBootDOLSize(partition.Header.DOLOffset);
|
||||
parsed_ok = parsed_ok && partition.Header.DOLOffset && partition.Header.DOLSize;
|
||||
MarkAsUsedE(partition.Offset + partition.Header.DataOffset, partition.Header.DOLOffset,
|
||||
partition.Header.DOLSize);
|
||||
|
||||
// FST
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x424, partition.Header.FSTOffset, true);
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x428, partition.Header.FSTSize, true);
|
||||
MarkAsUsedE(partition.Offset
|
||||
+ partition.Header.DataOffset
|
||||
, partition.Header.FSTOffset
|
||||
, partition.Header.FSTSize);
|
||||
// FST
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x424, partition.Header.FSTOffset, true);
|
||||
parsed_ok = parsed_ok && ReadFromVolume(0x428, partition.Header.FSTSize, true);
|
||||
MarkAsUsedE(partition.Offset + partition.Header.DataOffset, partition.Header.FSTOffset,
|
||||
partition.Header.FSTSize);
|
||||
|
||||
// Go through the filesystem and mark entries as used
|
||||
for (SFileInfo file : filesystem->GetFileList())
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "%s", file.m_FullPath.empty() ? "/" : file.m_FullPath.c_str());
|
||||
if ((file.m_NameOffset & 0x1000000) == 0)
|
||||
MarkAsUsedE(partition.Offset + partition.Header.DataOffset, file.m_Offset, file.m_FileSize);
|
||||
}
|
||||
}
|
||||
// Go through the filesystem and mark entries as used
|
||||
for (SFileInfo file : filesystem->GetFileList())
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "%s", file.m_FullPath.empty() ? "/" : file.m_FullPath.c_str());
|
||||
if ((file.m_NameOffset & 0x1000000) == 0)
|
||||
MarkAsUsedE(partition.Offset + partition.Header.DataOffset, file.m_Offset, file.m_FileSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Swap back
|
||||
s_disc.swap(old_volume);
|
||||
// Swap back
|
||||
s_disc.swap(old_volume);
|
||||
|
||||
return parsed_ok;
|
||||
return parsed_ok;
|
||||
}
|
||||
|
||||
} // namespace DiscScrubber
|
||||
} // namespace DiscScrubber
|
||||
|
||||
} // namespace DiscIO
|
||||
} // namespace DiscIO
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// DiscScrubber removes the garbage data from discs (currently Wii only) which
|
||||
// is on the disc due to encryption
|
||||
|
||||
@ -16,18 +15,19 @@
|
||||
#include <string>
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace File { class IOFile; }
|
||||
namespace File
|
||||
{
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
namespace DiscScrubber
|
||||
{
|
||||
|
||||
bool SetupScrub(const std::string& filename, int block_size);
|
||||
size_t GetNextBlock(File::IOFile& in, u8* buffer);
|
||||
void Cleanup();
|
||||
|
||||
} // namespace DiscScrubber
|
||||
} // namespace DiscScrubber
|
||||
|
||||
} // namespace DiscIO
|
||||
} // namespace DiscIO
|
||||
|
@ -11,154 +11,148 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/DriveBlob.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Common/StringUtil.h"
|
||||
#else
|
||||
#include <stdio.h> // fileno
|
||||
#include <sys/ioctl.h>
|
||||
#include <stdio.h> // fileno
|
||||
#if defined __linux__
|
||||
#include <linux/fs.h> // BLKGETSIZE64
|
||||
#include <linux/fs.h> // BLKGETSIZE64
|
||||
#elif defined __FreeBSD__
|
||||
#include <sys/disk.h> // DIOCGMEDIASIZE
|
||||
#include <sys/disk.h> // DIOCGMEDIASIZE
|
||||
#elif defined __APPLE__
|
||||
#include <sys/disk.h> // DKIOCGETBLOCKCOUNT / DKIOCGETBLOCKSIZE
|
||||
#include <sys/disk.h> // DKIOCGETBLOCKCOUNT / DKIOCGETBLOCKSIZE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
DriveReader::DriveReader(const std::string& drive)
|
||||
{
|
||||
// 32 sectors is roughly the optimal amount a CD Drive can read in
|
||||
// a single IO cycle. Larger values yield no performance improvement
|
||||
// and just cause IO stalls from the read delay. Smaller values allow
|
||||
// the OS IO and seeking overhead to ourstrip the time actually spent
|
||||
// transferring bytes from the media.
|
||||
SetChunkSize(32); // 32*2048 = 64KiB
|
||||
SetSectorSize(2048);
|
||||
// 32 sectors is roughly the optimal amount a CD Drive can read in
|
||||
// a single IO cycle. Larger values yield no performance improvement
|
||||
// and just cause IO stalls from the read delay. Smaller values allow
|
||||
// the OS IO and seeking overhead to ourstrip the time actually spent
|
||||
// transferring bytes from the media.
|
||||
SetChunkSize(32); // 32*2048 = 64KiB
|
||||
SetSectorSize(2048);
|
||||
#ifdef _WIN32
|
||||
auto const path = UTF8ToTStr(std::string("\\\\.\\") + drive);
|
||||
m_disc_handle = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
if (IsOK())
|
||||
{
|
||||
// Do a test read to make sure everything is OK, since it seems you can get
|
||||
// handles to empty drives.
|
||||
DWORD not_used;
|
||||
std::vector<u8> buffer(GetSectorSize());
|
||||
if (!ReadFile(m_disc_handle, buffer.data(), GetSectorSize(), ¬_used, nullptr))
|
||||
{
|
||||
// OK, something is wrong.
|
||||
CloseHandle(m_disc_handle);
|
||||
m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
if (IsOK())
|
||||
{
|
||||
// Initialize m_size by querying the volume capacity.
|
||||
STORAGE_READ_CAPACITY storage_size;
|
||||
storage_size.Version = sizeof(storage_size);
|
||||
DWORD bytes = 0;
|
||||
DeviceIoControl(m_disc_handle, IOCTL_STORAGE_READ_CAPACITY, nullptr, 0,
|
||||
&storage_size, sizeof(storage_size), &bytes, nullptr);
|
||||
m_size = bytes ? storage_size.DiskLength.QuadPart : 0;
|
||||
auto const path = UTF8ToTStr(std::string("\\\\.\\") + drive);
|
||||
m_disc_handle = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
if (IsOK())
|
||||
{
|
||||
// Do a test read to make sure everything is OK, since it seems you can get
|
||||
// handles to empty drives.
|
||||
DWORD not_used;
|
||||
std::vector<u8> buffer(GetSectorSize());
|
||||
if (!ReadFile(m_disc_handle, buffer.data(), GetSectorSize(), ¬_used, nullptr))
|
||||
{
|
||||
// OK, something is wrong.
|
||||
CloseHandle(m_disc_handle);
|
||||
m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
if (IsOK())
|
||||
{
|
||||
// Initialize m_size by querying the volume capacity.
|
||||
STORAGE_READ_CAPACITY storage_size;
|
||||
storage_size.Version = sizeof(storage_size);
|
||||
DWORD bytes = 0;
|
||||
DeviceIoControl(m_disc_handle, IOCTL_STORAGE_READ_CAPACITY, nullptr, 0, &storage_size,
|
||||
sizeof(storage_size), &bytes, nullptr);
|
||||
m_size = bytes ? storage_size.DiskLength.QuadPart : 0;
|
||||
|
||||
#ifdef _LOCKDRIVE // Do we want to lock the drive?
|
||||
// Lock the compact disc in the CD-ROM drive to prevent accidental
|
||||
// removal while reading from it.
|
||||
m_lock_cdrom.PreventMediaRemoval = TRUE;
|
||||
DeviceIoControl(m_disc_handle, IOCTL_CDROM_MEDIA_REMOVAL,
|
||||
&m_lock_cdrom, sizeof(m_lock_cdrom), nullptr,
|
||||
0, &dwNotUsed, nullptr);
|
||||
#endif
|
||||
#ifdef _LOCKDRIVE // Do we want to lock the drive?
|
||||
// Lock the compact disc in the CD-ROM drive to prevent accidental
|
||||
// removal while reading from it.
|
||||
m_lock_cdrom.PreventMediaRemoval = TRUE;
|
||||
DeviceIoControl(m_disc_handle, IOCTL_CDROM_MEDIA_REMOVAL, &m_lock_cdrom, sizeof(m_lock_cdrom),
|
||||
nullptr, 0, &dwNotUsed, nullptr);
|
||||
#endif
|
||||
#else
|
||||
m_file.Open(drive, "rb");
|
||||
if (m_file)
|
||||
{
|
||||
int fd = fileno(m_file.GetHandle());
|
||||
m_file.Open(drive, "rb");
|
||||
if (m_file)
|
||||
{
|
||||
int fd = fileno(m_file.GetHandle());
|
||||
#if defined __linux__
|
||||
// NOTE: Doesn't matter if it fails, m_size was initialized to zero
|
||||
ioctl(fd, BLKGETSIZE64, &m_size); // u64*
|
||||
// NOTE: Doesn't matter if it fails, m_size was initialized to zero
|
||||
ioctl(fd, BLKGETSIZE64, &m_size); // u64*
|
||||
#elif defined __FreeBSD__
|
||||
off_t size = 0;
|
||||
ioctl(fd, DIOCGMEDIASIZE, &size); // off_t*
|
||||
m_size = size;
|
||||
off_t size = 0;
|
||||
ioctl(fd, DIOCGMEDIASIZE, &size); // off_t*
|
||||
m_size = size;
|
||||
#elif defined __APPLE__
|
||||
u64 count = 0;
|
||||
u32 block_size = 0;
|
||||
ioctl(fd, DKIOCGETBLOCKCOUNT, &count); // u64*
|
||||
ioctl(fd, DKIOCGETBLOCKSIZE, &block_size); // u32*
|
||||
m_size = count * block_size;
|
||||
u64 count = 0;
|
||||
u32 block_size = 0;
|
||||
ioctl(fd, DKIOCGETBLOCKCOUNT, &count); // u64*
|
||||
ioctl(fd, DKIOCGETBLOCKSIZE, &block_size); // u32*
|
||||
m_size = count * block_size;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Load from DVD backup failed or no disc in drive %s", drive.c_str());
|
||||
}
|
||||
}
|
||||
else { NOTICE_LOG(DISCIO, "Load from DVD backup failed or no disc in drive %s", drive.c_str()); }
|
||||
}
|
||||
|
||||
DriveReader::~DriveReader()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#ifdef _LOCKDRIVE // Do we want to lock the drive?
|
||||
// Unlock the disc in the CD-ROM drive.
|
||||
m_lock_cdrom.PreventMediaRemoval = FALSE;
|
||||
DeviceIoControl (m_disc_handle, IOCTL_CDROM_MEDIA_REMOVAL,
|
||||
&m_lock_cdrom, sizeof(m_lock_cdrom), nullptr,
|
||||
0, &dwNotUsed, nullptr);
|
||||
#ifdef _LOCKDRIVE // Do we want to lock the drive?
|
||||
// Unlock the disc in the CD-ROM drive.
|
||||
m_lock_cdrom.PreventMediaRemoval = FALSE;
|
||||
DeviceIoControl(m_disc_handle, IOCTL_CDROM_MEDIA_REMOVAL, &m_lock_cdrom, sizeof(m_lock_cdrom),
|
||||
nullptr, 0, &dwNotUsed, nullptr);
|
||||
#endif
|
||||
if (m_disc_handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(m_disc_handle);
|
||||
m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
if (m_disc_handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(m_disc_handle);
|
||||
m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#else
|
||||
m_file.Close();
|
||||
m_file.Close();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<DriveReader> DriveReader::Create(const std::string& drive)
|
||||
{
|
||||
auto reader = std::unique_ptr<DriveReader>(new DriveReader(drive));
|
||||
auto reader = std::unique_ptr<DriveReader>(new DriveReader(drive));
|
||||
|
||||
if (!reader->IsOK())
|
||||
reader.reset();
|
||||
if (!reader->IsOK())
|
||||
reader.reset();
|
||||
|
||||
return reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
bool DriveReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
{
|
||||
return DriveReader::ReadMultipleAlignedBlocks(block_num, 1, out_ptr);
|
||||
return DriveReader::ReadMultipleAlignedBlocks(block_num, 1, out_ptr);
|
||||
}
|
||||
|
||||
bool DriveReader::ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER offset;
|
||||
offset.QuadPart = GetSectorSize() * block_num;
|
||||
SetFilePointerEx(m_disc_handle, offset, nullptr, FILE_BEGIN);
|
||||
DWORD bytes_read;
|
||||
if (!ReadFile(m_disc_handle, out_ptr, static_cast<DWORD>(GetSectorSize() * num_blocks),
|
||||
&bytes_read, nullptr))
|
||||
{
|
||||
PanicAlertT("Disc Read Error");
|
||||
return false;
|
||||
}
|
||||
return bytes_read == GetSectorSize() * num_blocks;
|
||||
LARGE_INTEGER offset;
|
||||
offset.QuadPart = GetSectorSize() * block_num;
|
||||
SetFilePointerEx(m_disc_handle, offset, nullptr, FILE_BEGIN);
|
||||
DWORD bytes_read;
|
||||
if (!ReadFile(m_disc_handle, out_ptr, static_cast<DWORD>(GetSectorSize() * num_blocks),
|
||||
&bytes_read, nullptr))
|
||||
{
|
||||
PanicAlertT("Disc Read Error");
|
||||
return false;
|
||||
}
|
||||
return bytes_read == GetSectorSize() * num_blocks;
|
||||
#else
|
||||
m_file.Seek(GetSectorSize() * block_num, SEEK_SET);
|
||||
if (m_file.ReadBytes(out_ptr, num_blocks * GetSectorSize()))
|
||||
return true;
|
||||
m_file.Clear();
|
||||
return false;
|
||||
m_file.Seek(GetSectorSize() * block_num, SEEK_SET);
|
||||
if (m_file.ReadBytes(out_ptr, num_blocks * GetSectorSize()))
|
||||
return true;
|
||||
m_file.Clear();
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -18,30 +18,28 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class DriveReader : public SectorReader
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<DriveReader> Create(const std::string& drive);
|
||||
~DriveReader();
|
||||
BlobType GetBlobType() const override { return BlobType::DRIVE; }
|
||||
u64 GetDataSize() const override { return m_size; }
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
|
||||
static std::unique_ptr<DriveReader> Create(const std::string& drive);
|
||||
~DriveReader();
|
||||
BlobType GetBlobType() const override { return BlobType::DRIVE; }
|
||||
u64 GetDataSize() const override { return m_size; }
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
private:
|
||||
DriveReader(const std::string& drive);
|
||||
bool GetBlock(u64 block_num, u8 *out_ptr) override;
|
||||
bool ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr) override;
|
||||
DriveReader(const std::string& drive);
|
||||
bool GetBlock(u64 block_num, u8* out_ptr) override;
|
||||
bool ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr) override;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
PREVENT_MEDIA_REMOVAL m_lock_cdrom;
|
||||
bool IsOK() const { return m_disc_handle != INVALID_HANDLE_VALUE; }
|
||||
HANDLE m_disc_handle = INVALID_HANDLE_VALUE;
|
||||
PREVENT_MEDIA_REMOVAL m_lock_cdrom;
|
||||
bool IsOK() const { return m_disc_handle != INVALID_HANDLE_VALUE; }
|
||||
#else
|
||||
File::IOFile m_file;
|
||||
bool IsOK() const { return m_file.IsOpen() && m_file.IsGood(); }
|
||||
File::IOFile m_file;
|
||||
bool IsOK() const { return m_file.IsOpen() && m_file.IsGood(); }
|
||||
#endif
|
||||
u64 m_size = 0;
|
||||
u64 m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -2,39 +2,37 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DiscIO/FileBlob.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "DiscIO/FileBlob.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
PlainFileReader::PlainFileReader(std::FILE* file)
|
||||
: m_file(file)
|
||||
PlainFileReader::PlainFileReader(std::FILE* file) : m_file(file)
|
||||
{
|
||||
m_size = m_file.GetSize();
|
||||
m_size = m_file.GetSize();
|
||||
}
|
||||
|
||||
std::unique_ptr<PlainFileReader> PlainFileReader::Create(const std::string& filename)
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
if (f)
|
||||
return std::unique_ptr<PlainFileReader>(new PlainFileReader(f.ReleaseHandle()));
|
||||
File::IOFile f(filename, "rb");
|
||||
if (f)
|
||||
return std::unique_ptr<PlainFileReader>(new PlainFileReader(f.ReleaseHandle()));
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlainFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
|
||||
{
|
||||
if (m_file.Seek(offset, SEEK_SET) && m_file.ReadBytes(out_ptr, nbytes))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
if (m_file.Seek(offset, SEEK_SET) && m_file.ReadBytes(out_ptr, nbytes))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_file.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -14,22 +14,21 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class PlainFileReader : public IBlobReader
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<PlainFileReader> Create(const std::string& filename);
|
||||
static std::unique_ptr<PlainFileReader> Create(const std::string& filename);
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::PLAIN; }
|
||||
u64 GetDataSize() const override { return m_size; }
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
BlobType GetBlobType() const override { return BlobType::PLAIN; }
|
||||
u64 GetDataSize() const override { return m_size; }
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
PlainFileReader(std::FILE* file);
|
||||
PlainFileReader(std::FILE* file);
|
||||
|
||||
File::IOFile m_file;
|
||||
s64 m_size;
|
||||
File::IOFile m_file;
|
||||
s64 m_size;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -11,12 +11,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/LogManager.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Boot/Boot.h"
|
||||
|
||||
#include "DiscIO/FileMonitor.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
namespace FileMon
|
||||
{
|
||||
|
||||
static std::unique_ptr<DiscIO::IVolume> s_open_iso;
|
||||
static std::unique_ptr<DiscIO::IFileSystem> s_filesystem;
|
||||
static std::string ISOFile = "", CurrentFile = "";
|
||||
@ -34,121 +33,118 @@ static bool FileAccess = true;
|
||||
// Filtered files
|
||||
bool IsSoundFile(const std::string& filename)
|
||||
{
|
||||
std::string extension;
|
||||
SplitPath(filename, nullptr, nullptr, &extension);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
std::string extension;
|
||||
SplitPath(filename, nullptr, nullptr, &extension);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
static std::unordered_set<std::string> extensions = {
|
||||
".adp", // 1080 Avalanche, Crash Bandicoot, etc.
|
||||
".adx", // Sonic Adventure 2 Battle, etc.
|
||||
".afc", // Zelda WW
|
||||
".ast", // Zelda TP, Mario Kart
|
||||
".brstm", // Wii Sports, Wario Land, etc.
|
||||
".dsp", // Metroid Prime
|
||||
".hps", // SSB Melee
|
||||
".ogg", // Tony Hawk's Underground 2
|
||||
".sad", // Disaster
|
||||
".snd", // Tales of Symphonia
|
||||
".song", // Tales of Symphonia
|
||||
".ssm", // Custom Robo, Kirby Air Ride, etc.
|
||||
".str", // Harry Potter & the Sorcerer's Stone
|
||||
};
|
||||
static std::unordered_set<std::string> extensions = {
|
||||
".adp", // 1080 Avalanche, Crash Bandicoot, etc.
|
||||
".adx", // Sonic Adventure 2 Battle, etc.
|
||||
".afc", // Zelda WW
|
||||
".ast", // Zelda TP, Mario Kart
|
||||
".brstm", // Wii Sports, Wario Land, etc.
|
||||
".dsp", // Metroid Prime
|
||||
".hps", // SSB Melee
|
||||
".ogg", // Tony Hawk's Underground 2
|
||||
".sad", // Disaster
|
||||
".snd", // Tales of Symphonia
|
||||
".song", // Tales of Symphonia
|
||||
".ssm", // Custom Robo, Kirby Air Ride, etc.
|
||||
".str", // Harry Potter & the Sorcerer's Stone
|
||||
};
|
||||
|
||||
return extensions.find(extension) != extensions.end();
|
||||
return extensions.find(extension) != extensions.end();
|
||||
}
|
||||
|
||||
|
||||
// Read the file system
|
||||
void ReadFileSystem(const std::string& filename)
|
||||
{
|
||||
// Should have an actual Shutdown procedure or something
|
||||
s_open_iso.reset();
|
||||
s_filesystem.reset();
|
||||
// Should have an actual Shutdown procedure or something
|
||||
s_open_iso.reset();
|
||||
s_filesystem.reset();
|
||||
|
||||
s_open_iso = DiscIO::CreateVolumeFromFilename(filename);
|
||||
if (!s_open_iso)
|
||||
return;
|
||||
s_open_iso = DiscIO::CreateVolumeFromFilename(filename);
|
||||
if (!s_open_iso)
|
||||
return;
|
||||
|
||||
if (s_open_iso->GetVolumeType() != DiscIO::IVolume::WII_WAD)
|
||||
{
|
||||
s_filesystem = DiscIO::CreateFileSystem(s_open_iso.get());
|
||||
if (s_open_iso->GetVolumeType() != DiscIO::IVolume::WII_WAD)
|
||||
{
|
||||
s_filesystem = DiscIO::CreateFileSystem(s_open_iso.get());
|
||||
|
||||
if (!s_filesystem)
|
||||
return;
|
||||
}
|
||||
if (!s_filesystem)
|
||||
return;
|
||||
}
|
||||
|
||||
FileAccess = true;
|
||||
FileAccess = true;
|
||||
}
|
||||
|
||||
// Logs a file if it passes a few checks
|
||||
void CheckFile(const std::string& file, u64 size)
|
||||
{
|
||||
// Don't do anything if the log is unselected
|
||||
if (!LogManager::GetInstance()->IsEnabled(LogTypes::FILEMON, LogTypes::LWARNING))
|
||||
return;
|
||||
// Do nothing if we found the same file again
|
||||
if (CurrentFile == file)
|
||||
return;
|
||||
// Don't do anything if the log is unselected
|
||||
if (!LogManager::GetInstance()->IsEnabled(LogTypes::FILEMON, LogTypes::LWARNING))
|
||||
return;
|
||||
// Do nothing if we found the same file again
|
||||
if (CurrentFile == file)
|
||||
return;
|
||||
|
||||
if (size > 0)
|
||||
size = (size / 1000);
|
||||
if (size > 0)
|
||||
size = (size / 1000);
|
||||
|
||||
std::string str = StringFromFormat("%s kB %s", ThousandSeparate(size, 7).c_str(), file.c_str());
|
||||
if (IsSoundFile(file))
|
||||
{
|
||||
INFO_LOG(FILEMON, "%s", str.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(FILEMON, "%s", str.c_str());
|
||||
}
|
||||
std::string str = StringFromFormat("%s kB %s", ThousandSeparate(size, 7).c_str(), file.c_str());
|
||||
if (IsSoundFile(file))
|
||||
{
|
||||
INFO_LOG(FILEMON, "%s", str.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(FILEMON, "%s", str.c_str());
|
||||
}
|
||||
|
||||
// Update the current file
|
||||
CurrentFile = file;
|
||||
// Update the current file
|
||||
CurrentFile = file;
|
||||
}
|
||||
|
||||
|
||||
// Find the filename
|
||||
void FindFilename(u64 offset)
|
||||
{
|
||||
// Don't do anything if a game is not running
|
||||
if (Core::GetState() != Core::CORE_RUN)
|
||||
return;
|
||||
// Don't do anything if a game is not running
|
||||
if (Core::GetState() != Core::CORE_RUN)
|
||||
return;
|
||||
|
||||
// Or if the log is unselected
|
||||
if (!LogManager::GetInstance()->IsEnabled(LogTypes::FILEMON, LogTypes::LWARNING))
|
||||
return;
|
||||
// Or if the log is unselected
|
||||
if (!LogManager::GetInstance()->IsEnabled(LogTypes::FILEMON, LogTypes::LWARNING))
|
||||
return;
|
||||
|
||||
// Or if we don't have file access
|
||||
if (!FileAccess)
|
||||
return;
|
||||
// Or if we don't have file access
|
||||
if (!FileAccess)
|
||||
return;
|
||||
|
||||
if (!s_filesystem || ISOFile != SConfig::GetInstance().m_LastFilename)
|
||||
{
|
||||
FileAccess = false;
|
||||
ReadFileSystem(SConfig::GetInstance().m_LastFilename);
|
||||
ISOFile = SConfig::GetInstance().m_LastFilename;
|
||||
INFO_LOG(FILEMON, "Opening '%s'", ISOFile.c_str());
|
||||
return;
|
||||
}
|
||||
if (!s_filesystem || ISOFile != SConfig::GetInstance().m_LastFilename)
|
||||
{
|
||||
FileAccess = false;
|
||||
ReadFileSystem(SConfig::GetInstance().m_LastFilename);
|
||||
ISOFile = SConfig::GetInstance().m_LastFilename;
|
||||
INFO_LOG(FILEMON, "Opening '%s'", ISOFile.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string filename = s_filesystem->GetFileName(offset);
|
||||
const std::string filename = s_filesystem->GetFileName(offset);
|
||||
|
||||
if (filename.empty())
|
||||
return;
|
||||
if (filename.empty())
|
||||
return;
|
||||
|
||||
CheckFile(filename, s_filesystem->GetFileSize(filename));
|
||||
CheckFile(filename, s_filesystem->GetFileSize(filename));
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
s_open_iso.reset();
|
||||
s_filesystem.reset();
|
||||
s_open_iso.reset();
|
||||
s_filesystem.reset();
|
||||
|
||||
ISOFile = "";
|
||||
CurrentFile = "";
|
||||
FileAccess = true;
|
||||
ISOFile = "";
|
||||
CurrentFile = "";
|
||||
FileAccess = true;
|
||||
}
|
||||
|
||||
|
||||
} // FileMon
|
||||
} // FileMon
|
||||
|
@ -10,11 +10,9 @@
|
||||
|
||||
namespace FileMon
|
||||
{
|
||||
|
||||
bool IsSoundFile(const std::string& filename);
|
||||
void ReadFileSystem(const std::string& file);
|
||||
void CheckFile(const std::string& file, u64 size);
|
||||
void FindFilename(u64 offset);
|
||||
void Close();
|
||||
|
||||
}
|
||||
|
@ -11,347 +11,349 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/FileSystemGCWii.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
CFileSystemGCWii::CFileSystemGCWii(const IVolume *_rVolume)
|
||||
: IFileSystem(_rVolume)
|
||||
, m_Initialized(false)
|
||||
, m_Valid(false)
|
||||
, m_Wii(false)
|
||||
CFileSystemGCWii::CFileSystemGCWii(const IVolume* _rVolume)
|
||||
: IFileSystem(_rVolume), m_Initialized(false), m_Valid(false), m_Wii(false)
|
||||
{
|
||||
m_Valid = DetectFileSystem();
|
||||
m_Valid = DetectFileSystem();
|
||||
}
|
||||
|
||||
CFileSystemGCWii::~CFileSystemGCWii()
|
||||
{
|
||||
m_FileInfoVector.clear();
|
||||
m_FileInfoVector.clear();
|
||||
}
|
||||
|
||||
u64 CFileSystemGCWii::GetFileSize(const std::string& _rFullPath)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
|
||||
if (pFileInfo != nullptr && !pFileInfo->IsDirectory())
|
||||
return pFileInfo->m_FileSize;
|
||||
if (pFileInfo != nullptr && !pFileInfo->IsDirectory())
|
||||
return pFileInfo->m_FileSize;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string CFileSystemGCWii::GetFileName(u64 _Address)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
for (auto& fileInfo : m_FileInfoVector)
|
||||
{
|
||||
if ((fileInfo.m_Offset <= _Address) &&
|
||||
((fileInfo.m_Offset + fileInfo.m_FileSize) > _Address))
|
||||
{
|
||||
return fileInfo.m_FullPath;
|
||||
}
|
||||
}
|
||||
for (auto& fileInfo : m_FileInfoVector)
|
||||
{
|
||||
if ((fileInfo.m_Offset <= _Address) && ((fileInfo.m_Offset + fileInfo.m_FileSize) > _Address))
|
||||
{
|
||||
return fileInfo.m_FullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
return "";
|
||||
}
|
||||
|
||||
u64 CFileSystemGCWii::ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize, u64 _OffsetInFile)
|
||||
u64 CFileSystemGCWii::ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize,
|
||||
u64 _OffsetInFile)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
if (pFileInfo == nullptr)
|
||||
return 0;
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
if (pFileInfo == nullptr)
|
||||
return 0;
|
||||
|
||||
if (_OffsetInFile >= pFileInfo->m_FileSize)
|
||||
return 0;
|
||||
if (_OffsetInFile >= pFileInfo->m_FileSize)
|
||||
return 0;
|
||||
|
||||
u64 read_length = std::min(_MaxBufferSize, pFileInfo->m_FileSize - _OffsetInFile);
|
||||
u64 read_length = std::min(_MaxBufferSize, pFileInfo->m_FileSize - _OffsetInFile);
|
||||
|
||||
DEBUG_LOG(DISCIO, "Reading %" PRIx64 " bytes at %" PRIx64 " from file %s. Offset: %" PRIx64 " Size: %" PRIx64,
|
||||
read_length, _OffsetInFile, _rFullPath.c_str(), pFileInfo->m_Offset, pFileInfo->m_FileSize);
|
||||
DEBUG_LOG(DISCIO, "Reading %" PRIx64 " bytes at %" PRIx64 " from file %s. Offset: %" PRIx64
|
||||
" Size: %" PRIx64,
|
||||
read_length, _OffsetInFile, _rFullPath.c_str(), pFileInfo->m_Offset,
|
||||
pFileInfo->m_FileSize);
|
||||
|
||||
m_rVolume->Read(pFileInfo->m_Offset + _OffsetInFile, read_length, _pBuffer, m_Wii);
|
||||
return read_length;
|
||||
m_rVolume->Read(pFileInfo->m_Offset + _OffsetInFile, read_length, _pBuffer, m_Wii);
|
||||
return read_length;
|
||||
}
|
||||
|
||||
bool CFileSystemGCWii::ExportFile(const std::string& _rFullPath, const std::string& _rExportFilename)
|
||||
bool CFileSystemGCWii::ExportFile(const std::string& _rFullPath,
|
||||
const std::string& _rExportFilename)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
const SFileInfo* pFileInfo = FindFileInfo(_rFullPath);
|
||||
|
||||
if (!pFileInfo)
|
||||
return false;
|
||||
if (!pFileInfo)
|
||||
return false;
|
||||
|
||||
u64 remainingSize = pFileInfo->m_FileSize;
|
||||
u64 fileOffset = pFileInfo->m_Offset;
|
||||
u64 remainingSize = pFileInfo->m_FileSize;
|
||||
u64 fileOffset = pFileInfo->m_Offset;
|
||||
|
||||
File::IOFile f(_rExportFilename, "wb");
|
||||
if (!f)
|
||||
return false;
|
||||
File::IOFile f(_rExportFilename, "wb");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
bool result = true;
|
||||
|
||||
while (remainingSize)
|
||||
{
|
||||
// Limit read size to 128 MB
|
||||
size_t readSize = (size_t)std::min(remainingSize, (u64)0x08000000);
|
||||
while (remainingSize)
|
||||
{
|
||||
// Limit read size to 128 MB
|
||||
size_t readSize = (size_t)std::min(remainingSize, (u64)0x08000000);
|
||||
|
||||
std::vector<u8> buffer(readSize);
|
||||
std::vector<u8> buffer(readSize);
|
||||
|
||||
result = m_rVolume->Read(fileOffset, readSize, &buffer[0], m_Wii);
|
||||
result = m_rVolume->Read(fileOffset, readSize, &buffer[0], m_Wii);
|
||||
|
||||
if (!result)
|
||||
break;
|
||||
if (!result)
|
||||
break;
|
||||
|
||||
f.WriteBytes(&buffer[0], readSize);
|
||||
f.WriteBytes(&buffer[0], readSize);
|
||||
|
||||
remainingSize -= readSize;
|
||||
fileOffset += readSize;
|
||||
}
|
||||
remainingSize -= readSize;
|
||||
fileOffset += readSize;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CFileSystemGCWii::ExportApploader(const std::string& _rExportFolder) const
|
||||
{
|
||||
u32 apploader_size;
|
||||
u32 trailer_size;
|
||||
const u32 header_size = 0x20;
|
||||
if (!m_rVolume->ReadSwapped(0x2440 + 0x14, &apploader_size, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(0x2440 + 0x18, &trailer_size, m_Wii))
|
||||
return false;
|
||||
apploader_size += trailer_size + header_size;
|
||||
DEBUG_LOG(DISCIO, "Apploader size -> %x", apploader_size);
|
||||
u32 apploader_size;
|
||||
u32 trailer_size;
|
||||
const u32 header_size = 0x20;
|
||||
if (!m_rVolume->ReadSwapped(0x2440 + 0x14, &apploader_size, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(0x2440 + 0x18, &trailer_size, m_Wii))
|
||||
return false;
|
||||
apploader_size += trailer_size + header_size;
|
||||
DEBUG_LOG(DISCIO, "Apploader size -> %x", apploader_size);
|
||||
|
||||
std::vector<u8> buffer(apploader_size);
|
||||
if (m_rVolume->Read(0x2440, apploader_size, buffer.data(), m_Wii))
|
||||
{
|
||||
std::string exportName(_rExportFolder + "/apploader.img");
|
||||
std::vector<u8> buffer(apploader_size);
|
||||
if (m_rVolume->Read(0x2440, apploader_size, buffer.data(), m_Wii))
|
||||
{
|
||||
std::string exportName(_rExportFolder + "/apploader.img");
|
||||
|
||||
File::IOFile AppFile(exportName, "wb");
|
||||
if (AppFile)
|
||||
{
|
||||
AppFile.WriteBytes(buffer.data(), apploader_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File::IOFile AppFile(exportName, "wb");
|
||||
if (AppFile)
|
||||
{
|
||||
AppFile.WriteBytes(buffer.data(), apploader_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 CFileSystemGCWii::GetBootDOLOffset() const
|
||||
{
|
||||
u32 offset = 0;
|
||||
m_rVolume->ReadSwapped(0x420, &offset, m_Wii);
|
||||
return static_cast<u64>(offset) << GetOffsetShift();
|
||||
u32 offset = 0;
|
||||
m_rVolume->ReadSwapped(0x420, &offset, m_Wii);
|
||||
return static_cast<u64>(offset) << GetOffsetShift();
|
||||
}
|
||||
|
||||
u32 CFileSystemGCWii::GetBootDOLSize(u64 dol_offset) const
|
||||
{
|
||||
// The dol_offset value is usually obtained by calling GetBootDOLOffset.
|
||||
// If GetBootDOLOffset fails by returning 0, GetBootDOLSize should also fail.
|
||||
if (dol_offset == 0)
|
||||
return 0;
|
||||
// The dol_offset value is usually obtained by calling GetBootDOLOffset.
|
||||
// If GetBootDOLOffset fails by returning 0, GetBootDOLSize should also fail.
|
||||
if (dol_offset == 0)
|
||||
return 0;
|
||||
|
||||
u32 dol_size = 0;
|
||||
u32 offset = 0;
|
||||
u32 size = 0;
|
||||
u32 dol_size = 0;
|
||||
u32 offset = 0;
|
||||
u32 size = 0;
|
||||
|
||||
// Iterate through the 7 code segments
|
||||
for (u8 i = 0; i < 7; i++)
|
||||
{
|
||||
if (!m_rVolume->ReadSwapped(dol_offset + 0x00 + i * 4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(dol_offset + 0x90 + i * 4, &size, m_Wii))
|
||||
return 0;
|
||||
dol_size = std::max(offset + size, dol_size);
|
||||
}
|
||||
// Iterate through the 7 code segments
|
||||
for (u8 i = 0; i < 7; i++)
|
||||
{
|
||||
if (!m_rVolume->ReadSwapped(dol_offset + 0x00 + i * 4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(dol_offset + 0x90 + i * 4, &size, m_Wii))
|
||||
return 0;
|
||||
dol_size = std::max(offset + size, dol_size);
|
||||
}
|
||||
|
||||
// Iterate through the 11 data segments
|
||||
for (u8 i = 0; i < 11; i++)
|
||||
{
|
||||
if (!m_rVolume->ReadSwapped(dol_offset + 0x1c + i * 4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(dol_offset + 0xac + i * 4, &size, m_Wii))
|
||||
return 0;
|
||||
dol_size = std::max(offset + size, dol_size);
|
||||
}
|
||||
// Iterate through the 11 data segments
|
||||
for (u8 i = 0; i < 11; i++)
|
||||
{
|
||||
if (!m_rVolume->ReadSwapped(dol_offset + 0x1c + i * 4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(dol_offset + 0xac + i * 4, &size, m_Wii))
|
||||
return 0;
|
||||
dol_size = std::max(offset + size, dol_size);
|
||||
}
|
||||
|
||||
return dol_size;
|
||||
return dol_size;
|
||||
}
|
||||
|
||||
bool CFileSystemGCWii::ExportDOL(const std::string& _rExportFolder) const
|
||||
{
|
||||
u64 DolOffset = GetBootDOLOffset();
|
||||
u32 DolSize = GetBootDOLSize(DolOffset);
|
||||
u64 DolOffset = GetBootDOLOffset();
|
||||
u32 DolSize = GetBootDOLSize(DolOffset);
|
||||
|
||||
if (DolOffset == 0 || DolSize == 0)
|
||||
return false;
|
||||
if (DolOffset == 0 || DolSize == 0)
|
||||
return false;
|
||||
|
||||
std::vector<u8> buffer(DolSize);
|
||||
if (m_rVolume->Read(DolOffset, DolSize, &buffer[0], m_Wii))
|
||||
{
|
||||
std::string exportName(_rExportFolder + "/boot.dol");
|
||||
std::vector<u8> buffer(DolSize);
|
||||
if (m_rVolume->Read(DolOffset, DolSize, &buffer[0], m_Wii))
|
||||
{
|
||||
std::string exportName(_rExportFolder + "/boot.dol");
|
||||
|
||||
File::IOFile DolFile(exportName, "wb");
|
||||
if (DolFile)
|
||||
{
|
||||
DolFile.WriteBytes(&buffer[0], DolSize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File::IOFile DolFile(exportName, "wb");
|
||||
if (DolFile)
|
||||
{
|
||||
DolFile.WriteBytes(&buffer[0], DolSize);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string CFileSystemGCWii::GetStringFromOffset(u64 _Offset) const
|
||||
{
|
||||
std::string data(255, 0x00);
|
||||
m_rVolume->Read(_Offset, data.size(), (u8*)&data[0], m_Wii);
|
||||
data.erase(std::find(data.begin(), data.end(), 0x00), data.end());
|
||||
std::string data(255, 0x00);
|
||||
m_rVolume->Read(_Offset, data.size(), (u8*)&data[0], m_Wii);
|
||||
data.erase(std::find(data.begin(), data.end(), 0x00), data.end());
|
||||
|
||||
// TODO: Should we really always use SHIFT-JIS?
|
||||
// It makes some filenames in Pikmin (NTSC-U) sane, but is it correct?
|
||||
return SHIFTJISToUTF8(data);
|
||||
// TODO: Should we really always use SHIFT-JIS?
|
||||
// It makes some filenames in Pikmin (NTSC-U) sane, but is it correct?
|
||||
return SHIFTJISToUTF8(data);
|
||||
}
|
||||
|
||||
const std::vector<SFileInfo>& CFileSystemGCWii::GetFileList()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
return m_FileInfoVector;
|
||||
return m_FileInfoVector;
|
||||
}
|
||||
|
||||
const SFileInfo* CFileSystemGCWii::FindFileInfo(const std::string& _rFullPath)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
if (!m_Initialized)
|
||||
InitFileSystem();
|
||||
|
||||
for (auto& fileInfo : m_FileInfoVector)
|
||||
{
|
||||
if (!strcasecmp(fileInfo.m_FullPath.c_str(), _rFullPath.c_str()))
|
||||
return &fileInfo;
|
||||
}
|
||||
for (auto& fileInfo : m_FileInfoVector)
|
||||
{
|
||||
if (!strcasecmp(fileInfo.m_FullPath.c_str(), _rFullPath.c_str()))
|
||||
return &fileInfo;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CFileSystemGCWii::DetectFileSystem()
|
||||
{
|
||||
u32 magic_bytes;
|
||||
if (m_rVolume->ReadSwapped(0x18, &magic_bytes, false) && magic_bytes == 0x5D1C9EA3)
|
||||
{
|
||||
m_Wii = true;
|
||||
return true;
|
||||
}
|
||||
else if (m_rVolume->ReadSwapped(0x1c, &magic_bytes, false) && magic_bytes == 0xC2339F3D)
|
||||
{
|
||||
m_Wii = false;
|
||||
return true;
|
||||
}
|
||||
u32 magic_bytes;
|
||||
if (m_rVolume->ReadSwapped(0x18, &magic_bytes, false) && magic_bytes == 0x5D1C9EA3)
|
||||
{
|
||||
m_Wii = true;
|
||||
return true;
|
||||
}
|
||||
else if (m_rVolume->ReadSwapped(0x1c, &magic_bytes, false) && magic_bytes == 0xC2339F3D)
|
||||
{
|
||||
m_Wii = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void CFileSystemGCWii::InitFileSystem()
|
||||
{
|
||||
m_Initialized = true;
|
||||
u32 const shift = GetOffsetShift();
|
||||
m_Initialized = true;
|
||||
u32 const shift = GetOffsetShift();
|
||||
|
||||
// read the whole FST
|
||||
u32 fst_offset_unshifted;
|
||||
if (!m_rVolume->ReadSwapped(0x424, &fst_offset_unshifted, m_Wii))
|
||||
return;
|
||||
u64 FSTOffset = static_cast<u64>(fst_offset_unshifted) << shift;
|
||||
// read the whole FST
|
||||
u32 fst_offset_unshifted;
|
||||
if (!m_rVolume->ReadSwapped(0x424, &fst_offset_unshifted, m_Wii))
|
||||
return;
|
||||
u64 FSTOffset = static_cast<u64>(fst_offset_unshifted) << shift;
|
||||
|
||||
// read all fileinfos
|
||||
u32 name_offset, offset, size;
|
||||
if (!m_rVolume->ReadSwapped(FSTOffset + 0x0, &name_offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(FSTOffset + 0x4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(FSTOffset + 0x8, &size, m_Wii))
|
||||
return;
|
||||
SFileInfo root = { name_offset, static_cast<u64>(offset) << shift, size };
|
||||
// read all fileinfos
|
||||
u32 name_offset, offset, size;
|
||||
if (!m_rVolume->ReadSwapped(FSTOffset + 0x0, &name_offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(FSTOffset + 0x4, &offset, m_Wii) ||
|
||||
!m_rVolume->ReadSwapped(FSTOffset + 0x8, &size, m_Wii))
|
||||
return;
|
||||
SFileInfo root = {name_offset, static_cast<u64>(offset) << shift, size};
|
||||
|
||||
if (!root.IsDirectory())
|
||||
return;
|
||||
if (!root.IsDirectory())
|
||||
return;
|
||||
|
||||
// 12 bytes (the size of a file entry) times 10 * 1024 * 1024 is 120 MiB,
|
||||
// more than total RAM in a Wii. No file system should use anywhere near that much.
|
||||
static const u32 ARBITRARY_FILE_SYSTEM_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
if (root.m_FileSize > ARBITRARY_FILE_SYSTEM_SIZE_LIMIT)
|
||||
{
|
||||
// Without this check, Dolphin can crash by trying to allocate too much
|
||||
// memory when loading the file systems of certain malformed disc images.
|
||||
// 12 bytes (the size of a file entry) times 10 * 1024 * 1024 is 120 MiB,
|
||||
// more than total RAM in a Wii. No file system should use anywhere near that much.
|
||||
static const u32 ARBITRARY_FILE_SYSTEM_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
if (root.m_FileSize > ARBITRARY_FILE_SYSTEM_SIZE_LIMIT)
|
||||
{
|
||||
// Without this check, Dolphin can crash by trying to allocate too much
|
||||
// memory when loading the file systems of certain malformed disc images.
|
||||
|
||||
ERROR_LOG(DISCIO, "File system is abnormally large! Aborting loading");
|
||||
return;
|
||||
}
|
||||
ERROR_LOG(DISCIO, "File system is abnormally large! Aborting loading");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_FileInfoVector.size())
|
||||
PanicAlert("Wtf?");
|
||||
u64 NameTableOffset = FSTOffset;
|
||||
if (m_FileInfoVector.size())
|
||||
PanicAlert("Wtf?");
|
||||
u64 NameTableOffset = FSTOffset;
|
||||
|
||||
m_FileInfoVector.reserve((size_t)root.m_FileSize);
|
||||
for (u32 i = 0; i < root.m_FileSize; i++)
|
||||
{
|
||||
const u64 read_offset = FSTOffset + (i * 0xC);
|
||||
name_offset = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x0, &name_offset, m_Wii);
|
||||
offset = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x4, &offset, m_Wii);
|
||||
size = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x8, &size, m_Wii);
|
||||
m_FileInfoVector.emplace_back(name_offset, static_cast<u64>(offset) << shift, size);
|
||||
NameTableOffset += 0xC;
|
||||
}
|
||||
m_FileInfoVector.reserve((size_t)root.m_FileSize);
|
||||
for (u32 i = 0; i < root.m_FileSize; i++)
|
||||
{
|
||||
const u64 read_offset = FSTOffset + (i * 0xC);
|
||||
name_offset = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x0, &name_offset, m_Wii);
|
||||
offset = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x4, &offset, m_Wii);
|
||||
size = 0;
|
||||
m_rVolume->ReadSwapped(read_offset + 0x8, &size, m_Wii);
|
||||
m_FileInfoVector.emplace_back(name_offset, static_cast<u64>(offset) << shift, size);
|
||||
NameTableOffset += 0xC;
|
||||
}
|
||||
|
||||
BuildFilenames(1, m_FileInfoVector.size(), "", NameTableOffset);
|
||||
BuildFilenames(1, m_FileInfoVector.size(), "", NameTableOffset);
|
||||
}
|
||||
|
||||
size_t CFileSystemGCWii::BuildFilenames(const size_t _FirstIndex, const size_t _LastIndex, const std::string& _szDirectory, u64 _NameTableOffset)
|
||||
size_t CFileSystemGCWii::BuildFilenames(const size_t _FirstIndex, const size_t _LastIndex,
|
||||
const std::string& _szDirectory, u64 _NameTableOffset)
|
||||
{
|
||||
size_t CurrentIndex = _FirstIndex;
|
||||
size_t CurrentIndex = _FirstIndex;
|
||||
|
||||
while (CurrentIndex < _LastIndex)
|
||||
{
|
||||
SFileInfo& rFileInfo = m_FileInfoVector[CurrentIndex];
|
||||
u64 const uOffset = _NameTableOffset + (rFileInfo.m_NameOffset & 0xFFFFFF);
|
||||
std::string const offset_str { GetStringFromOffset(uOffset) };
|
||||
bool const is_dir = rFileInfo.IsDirectory();
|
||||
rFileInfo.m_FullPath.reserve(_szDirectory.size() + offset_str.size());
|
||||
while (CurrentIndex < _LastIndex)
|
||||
{
|
||||
SFileInfo& rFileInfo = m_FileInfoVector[CurrentIndex];
|
||||
u64 const uOffset = _NameTableOffset + (rFileInfo.m_NameOffset & 0xFFFFFF);
|
||||
std::string const offset_str{GetStringFromOffset(uOffset)};
|
||||
bool const is_dir = rFileInfo.IsDirectory();
|
||||
rFileInfo.m_FullPath.reserve(_szDirectory.size() + offset_str.size());
|
||||
|
||||
rFileInfo.m_FullPath.append(_szDirectory.data(), _szDirectory.size())
|
||||
.append(offset_str.data(), offset_str.size())
|
||||
.append("/", size_t(is_dir));
|
||||
rFileInfo.m_FullPath.append(_szDirectory.data(), _szDirectory.size())
|
||||
.append(offset_str.data(), offset_str.size())
|
||||
.append("/", size_t(is_dir));
|
||||
|
||||
if (!is_dir)
|
||||
{
|
||||
++CurrentIndex;
|
||||
continue;
|
||||
}
|
||||
if (!is_dir)
|
||||
{
|
||||
++CurrentIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check next index
|
||||
CurrentIndex = BuildFilenames(CurrentIndex + 1, (size_t) rFileInfo.m_FileSize, rFileInfo.m_FullPath, _NameTableOffset);
|
||||
}
|
||||
// check next index
|
||||
CurrentIndex = BuildFilenames(CurrentIndex + 1, (size_t)rFileInfo.m_FileSize,
|
||||
rFileInfo.m_FullPath, _NameTableOffset);
|
||||
}
|
||||
|
||||
return CurrentIndex;
|
||||
return CurrentIndex;
|
||||
}
|
||||
|
||||
u32 CFileSystemGCWii::GetOffsetShift() const
|
||||
{
|
||||
return m_Wii ? 2 : 0;
|
||||
return m_Wii ? 2 : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -13,38 +13,39 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class IVolume;
|
||||
|
||||
class CFileSystemGCWii : public IFileSystem
|
||||
{
|
||||
public:
|
||||
CFileSystemGCWii(const IVolume* _rVolume);
|
||||
virtual ~CFileSystemGCWii();
|
||||
CFileSystemGCWii(const IVolume* _rVolume);
|
||||
virtual ~CFileSystemGCWii();
|
||||
|
||||
bool IsValid() const override { return m_Valid; }
|
||||
u64 GetFileSize(const std::string& _rFullPath) override;
|
||||
const std::vector<SFileInfo>& GetFileList() override;
|
||||
const std::string GetFileName(u64 _Address) override;
|
||||
u64 ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize, u64 _OffsetInFile) override;
|
||||
bool ExportFile(const std::string& _rFullPath, const std::string&_rExportFilename) override;
|
||||
bool ExportApploader(const std::string& _rExportFolder) const override;
|
||||
bool ExportDOL(const std::string& _rExportFolder) const override;
|
||||
u64 GetBootDOLOffset() const override;
|
||||
u32 GetBootDOLSize(u64 dol_offset) const override;
|
||||
bool IsValid() const override { return m_Valid; }
|
||||
u64 GetFileSize(const std::string& _rFullPath) override;
|
||||
const std::vector<SFileInfo>& GetFileList() override;
|
||||
const std::string GetFileName(u64 _Address) override;
|
||||
u64 ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize,
|
||||
u64 _OffsetInFile) override;
|
||||
bool ExportFile(const std::string& _rFullPath, const std::string& _rExportFilename) override;
|
||||
bool ExportApploader(const std::string& _rExportFolder) const override;
|
||||
bool ExportDOL(const std::string& _rExportFolder) const override;
|
||||
u64 GetBootDOLOffset() const override;
|
||||
u32 GetBootDOLSize(u64 dol_offset) const override;
|
||||
|
||||
private:
|
||||
bool m_Initialized;
|
||||
bool m_Valid;
|
||||
bool m_Wii;
|
||||
std::vector<SFileInfo> m_FileInfoVector;
|
||||
bool m_Initialized;
|
||||
bool m_Valid;
|
||||
bool m_Wii;
|
||||
std::vector<SFileInfo> m_FileInfoVector;
|
||||
|
||||
std::string GetStringFromOffset(u64 _Offset) const;
|
||||
const SFileInfo* FindFileInfo(const std::string& _rFullPath);
|
||||
bool DetectFileSystem();
|
||||
void InitFileSystem();
|
||||
size_t BuildFilenames(const size_t _FirstIndex, const size_t _LastIndex, const std::string& _szDirectory, u64 _NameTableOffset);
|
||||
u32 GetOffsetShift() const;
|
||||
std::string GetStringFromOffset(u64 _Offset) const;
|
||||
const SFileInfo* FindFileInfo(const std::string& _rFullPath);
|
||||
bool DetectFileSystem();
|
||||
void InitFileSystem();
|
||||
size_t BuildFilenames(const size_t _FirstIndex, const size_t _LastIndex,
|
||||
const std::string& _szDirectory, u64 _NameTableOffset);
|
||||
u32 GetOffsetShift() const;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -2,33 +2,31 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include <memory>
|
||||
#include "DiscIO/FileSystemGCWii.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
IFileSystem::IFileSystem(const IVolume *_rVolume)
|
||||
: m_rVolume(_rVolume)
|
||||
{}
|
||||
|
||||
IFileSystem::IFileSystem(const IVolume* _rVolume) : m_rVolume(_rVolume)
|
||||
{
|
||||
}
|
||||
|
||||
IFileSystem::~IFileSystem()
|
||||
{}
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<IFileSystem> CreateFileSystem(const IVolume* volume)
|
||||
{
|
||||
std::unique_ptr<IFileSystem> filesystem = std::make_unique<CFileSystemGCWii>(volume);
|
||||
std::unique_ptr<IFileSystem> filesystem = std::make_unique<CFileSystemGCWii>(volume);
|
||||
|
||||
if (!filesystem)
|
||||
return nullptr;
|
||||
if (!filesystem)
|
||||
return nullptr;
|
||||
|
||||
if (!filesystem->IsValid())
|
||||
filesystem.reset();
|
||||
if (!filesystem->IsValid())
|
||||
filesystem.reset();
|
||||
|
||||
return filesystem;
|
||||
return filesystem;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -12,50 +12,48 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class IVolume;
|
||||
|
||||
// file info of an FST entry
|
||||
struct SFileInfo
|
||||
{
|
||||
u64 m_NameOffset = 0u;
|
||||
u64 m_Offset = 0u;
|
||||
u64 m_FileSize = 0u;
|
||||
std::string m_FullPath;
|
||||
u64 m_NameOffset = 0u;
|
||||
u64 m_Offset = 0u;
|
||||
u64 m_FileSize = 0u;
|
||||
std::string m_FullPath;
|
||||
|
||||
bool IsDirectory() const { return (m_NameOffset & 0xFF000000) != 0; }
|
||||
bool IsDirectory() const { return (m_NameOffset & 0xFF000000) != 0; }
|
||||
SFileInfo(u64 name_offset, u64 offset, u64 filesize)
|
||||
: m_NameOffset(name_offset), m_Offset(offset), m_FileSize(filesize)
|
||||
{
|
||||
}
|
||||
|
||||
SFileInfo(u64 name_offset, u64 offset, u64 filesize) :
|
||||
m_NameOffset(name_offset),
|
||||
m_Offset(offset),
|
||||
m_FileSize(filesize)
|
||||
{ }
|
||||
|
||||
SFileInfo (SFileInfo const&) = default;
|
||||
SFileInfo () = default;
|
||||
SFileInfo(SFileInfo const&) = default;
|
||||
SFileInfo() = default;
|
||||
};
|
||||
|
||||
class IFileSystem
|
||||
{
|
||||
public:
|
||||
IFileSystem(const IVolume *_rVolume);
|
||||
IFileSystem(const IVolume* _rVolume);
|
||||
|
||||
virtual ~IFileSystem();
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual const std::vector<SFileInfo>& GetFileList() = 0;
|
||||
virtual u64 GetFileSize(const std::string& _rFullPath) = 0;
|
||||
virtual u64 ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize, u64 _OffsetInFile = 0) = 0;
|
||||
virtual bool ExportFile(const std::string& _rFullPath, const std::string& _rExportFilename) = 0;
|
||||
virtual bool ExportApploader(const std::string& _rExportFolder) const = 0;
|
||||
virtual bool ExportDOL(const std::string& _rExportFolder) const = 0;
|
||||
virtual const std::string GetFileName(u64 _Address) = 0;
|
||||
virtual u64 GetBootDOLOffset() const = 0;
|
||||
virtual u32 GetBootDOLSize(u64 dol_offset) const = 0;
|
||||
virtual ~IFileSystem();
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual const std::vector<SFileInfo>& GetFileList() = 0;
|
||||
virtual u64 GetFileSize(const std::string& _rFullPath) = 0;
|
||||
virtual u64 ReadFile(const std::string& _rFullPath, u8* _pBuffer, u64 _MaxBufferSize,
|
||||
u64 _OffsetInFile = 0) = 0;
|
||||
virtual bool ExportFile(const std::string& _rFullPath, const std::string& _rExportFilename) = 0;
|
||||
virtual bool ExportApploader(const std::string& _rExportFolder) const = 0;
|
||||
virtual bool ExportDOL(const std::string& _rExportFolder) const = 0;
|
||||
virtual const std::string GetFileName(u64 _Address) = 0;
|
||||
virtual u64 GetBootDOLOffset() const = 0;
|
||||
virtual u32 GetBootDOLSize(u64 dol_offset) const = 0;
|
||||
|
||||
protected:
|
||||
const IVolume *m_rVolume;
|
||||
const IVolume* m_rVolume;
|
||||
};
|
||||
|
||||
std::unique_ptr<IFileSystem> CreateFileSystem(const IVolume* volume);
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -9,19 +9,19 @@
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
@ -29,132 +29,129 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
CSharedContent::CSharedContent()
|
||||
{
|
||||
UpdateLocation();
|
||||
UpdateLocation();
|
||||
}
|
||||
|
||||
void CSharedContent::UpdateLocation()
|
||||
{
|
||||
m_Elements.clear();
|
||||
m_LastID = 0;
|
||||
m_ContentMap = StringFromFormat("%s/shared1/content.map", File::GetUserPath(D_WIIROOT_IDX).c_str());
|
||||
m_Elements.clear();
|
||||
m_LastID = 0;
|
||||
m_ContentMap =
|
||||
StringFromFormat("%s/shared1/content.map", File::GetUserPath(D_WIIROOT_IDX).c_str());
|
||||
|
||||
File::IOFile pFile(m_ContentMap, "rb");
|
||||
SElement Element;
|
||||
while (pFile.ReadArray(&Element, 1))
|
||||
{
|
||||
m_Elements.push_back(Element);
|
||||
m_LastID++;
|
||||
}
|
||||
File::IOFile pFile(m_ContentMap, "rb");
|
||||
SElement Element;
|
||||
while (pFile.ReadArray(&Element, 1))
|
||||
{
|
||||
m_Elements.push_back(Element);
|
||||
m_LastID++;
|
||||
}
|
||||
}
|
||||
|
||||
CSharedContent::~CSharedContent()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
std::string CSharedContent::GetFilenameFromSHA1(const u8* hash)
|
||||
{
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if (memcmp(hash, Element.SHA1Hash, 20) == 0)
|
||||
{
|
||||
return StringFromFormat("%s/shared1/%c%c%c%c%c%c%c%c.app", File::GetUserPath(D_WIIROOT_IDX).c_str(),
|
||||
Element.FileName[0], Element.FileName[1], Element.FileName[2], Element.FileName[3],
|
||||
Element.FileName[4], Element.FileName[5], Element.FileName[6], Element.FileName[7]);
|
||||
}
|
||||
}
|
||||
return "unk";
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if (memcmp(hash, Element.SHA1Hash, 20) == 0)
|
||||
{
|
||||
return StringFromFormat(
|
||||
"%s/shared1/%c%c%c%c%c%c%c%c.app", File::GetUserPath(D_WIIROOT_IDX).c_str(),
|
||||
Element.FileName[0], Element.FileName[1], Element.FileName[2], Element.FileName[3],
|
||||
Element.FileName[4], Element.FileName[5], Element.FileName[6], Element.FileName[7]);
|
||||
}
|
||||
}
|
||||
return "unk";
|
||||
}
|
||||
|
||||
std::string CSharedContent::AddSharedContent(const u8* hash)
|
||||
{
|
||||
std::string filename = GetFilenameFromSHA1(hash);
|
||||
std::string filename = GetFilenameFromSHA1(hash);
|
||||
|
||||
if (strcasecmp(filename.c_str(), "unk") == 0)
|
||||
{
|
||||
std::string id = StringFromFormat("%08x", m_LastID);
|
||||
SElement Element;
|
||||
memcpy(Element.FileName, id.c_str(), 8);
|
||||
memcpy(Element.SHA1Hash, hash, 20);
|
||||
m_Elements.push_back(Element);
|
||||
if (strcasecmp(filename.c_str(), "unk") == 0)
|
||||
{
|
||||
std::string id = StringFromFormat("%08x", m_LastID);
|
||||
SElement Element;
|
||||
memcpy(Element.FileName, id.c_str(), 8);
|
||||
memcpy(Element.SHA1Hash, hash, 20);
|
||||
m_Elements.push_back(Element);
|
||||
|
||||
File::CreateFullPath(m_ContentMap);
|
||||
File::CreateFullPath(m_ContentMap);
|
||||
|
||||
File::IOFile pFile(m_ContentMap, "ab");
|
||||
pFile.WriteArray(&Element, 1);
|
||||
File::IOFile pFile(m_ContentMap, "ab");
|
||||
pFile.WriteArray(&Element, 1);
|
||||
|
||||
filename = StringFromFormat("%s/shared1/%s.app", File::GetUserPath(D_WIIROOT_IDX).c_str(), id.c_str());
|
||||
m_LastID++;
|
||||
}
|
||||
filename =
|
||||
StringFromFormat("%s/shared1/%s.app", File::GetUserPath(D_WIIROOT_IDX).c_str(), id.c_str());
|
||||
m_LastID++;
|
||||
}
|
||||
|
||||
return filename;
|
||||
return filename;
|
||||
}
|
||||
|
||||
void CNANDContentDataFile::EnsureOpen()
|
||||
{
|
||||
if (!m_file)
|
||||
m_file = std::make_unique<File::IOFile>(m_filename, "rb");
|
||||
else if (!m_file->IsOpen())
|
||||
m_file->Open(m_filename, "rb");
|
||||
if (!m_file)
|
||||
m_file = std::make_unique<File::IOFile>(m_filename, "rb");
|
||||
else if (!m_file->IsOpen())
|
||||
m_file->Open(m_filename, "rb");
|
||||
}
|
||||
void CNANDContentDataFile::Open()
|
||||
{
|
||||
EnsureOpen();
|
||||
EnsureOpen();
|
||||
}
|
||||
const std::vector<u8> CNANDContentDataFile::Get()
|
||||
{
|
||||
std::vector<u8> result;
|
||||
EnsureOpen();
|
||||
if (!m_file->IsGood())
|
||||
return result;
|
||||
std::vector<u8> result;
|
||||
EnsureOpen();
|
||||
if (!m_file->IsGood())
|
||||
return result;
|
||||
|
||||
u64 size = m_file->GetSize();
|
||||
if (size == 0)
|
||||
return result;
|
||||
u64 size = m_file->GetSize();
|
||||
if (size == 0)
|
||||
return result;
|
||||
|
||||
result.resize(size);
|
||||
m_file->ReadBytes(result.data(), result.size());
|
||||
result.resize(size);
|
||||
m_file->ReadBytes(result.data(), result.size());
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CNANDContentDataFile::GetRange(u32 start, u32 size, u8* buffer)
|
||||
{
|
||||
EnsureOpen();
|
||||
if (!m_file->IsGood())
|
||||
return false;
|
||||
EnsureOpen();
|
||||
if (!m_file->IsGood())
|
||||
return false;
|
||||
|
||||
if (!m_file->Seek(start, SEEK_SET))
|
||||
return false;
|
||||
if (!m_file->Seek(start, SEEK_SET))
|
||||
return false;
|
||||
|
||||
return m_file->ReadBytes(buffer, static_cast<size_t>(size));
|
||||
return m_file->ReadBytes(buffer, static_cast<size_t>(size));
|
||||
}
|
||||
void CNANDContentDataFile::Close()
|
||||
{
|
||||
if (m_file && m_file->IsOpen())
|
||||
m_file->Close();
|
||||
if (m_file && m_file->IsOpen())
|
||||
m_file->Close();
|
||||
}
|
||||
|
||||
|
||||
bool CNANDContentDataBuffer::GetRange(u32 start, u32 size, u8* buffer)
|
||||
{
|
||||
if (start + size > m_buffer.size())
|
||||
return false;
|
||||
if (start + size > m_buffer.size())
|
||||
return false;
|
||||
|
||||
std::copy(&m_buffer[start], &m_buffer[start + size], buffer);
|
||||
return true;
|
||||
std::copy(&m_buffer[start], &m_buffer[start + size], buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
CNANDContentLoader::CNANDContentLoader(const std::string& content_name)
|
||||
: m_Valid(false)
|
||||
, m_IsWAD(false)
|
||||
, m_TitleID(-1)
|
||||
, m_IosVersion(0x09)
|
||||
, m_BootIndex(-1)
|
||||
: m_Valid(false), m_IsWAD(false), m_TitleID(-1), m_IosVersion(0x09), m_BootIndex(-1)
|
||||
{
|
||||
m_Valid = Initialize(content_name);
|
||||
m_Valid = Initialize(content_name);
|
||||
}
|
||||
|
||||
CNANDContentLoader::~CNANDContentLoader()
|
||||
@ -163,363 +160,373 @@ CNANDContentLoader::~CNANDContentLoader()
|
||||
|
||||
const SNANDContent* CNANDContentLoader::GetContentByIndex(int index) const
|
||||
{
|
||||
for (auto& Content : m_Content)
|
||||
{
|
||||
if (Content.m_Index == index)
|
||||
{
|
||||
return &Content;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
for (auto& Content : m_Content)
|
||||
{
|
||||
if (Content.m_Index == index)
|
||||
{
|
||||
return &Content;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CNANDContentLoader::Initialize(const std::string& name)
|
||||
{
|
||||
if (name.empty())
|
||||
return false;
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
m_Path = name;
|
||||
m_Path = name;
|
||||
|
||||
WiiWAD wad(name);
|
||||
std::vector<u8> data_app;
|
||||
std::vector<u8> tmd;
|
||||
std::vector<u8> decrypted_title_key;
|
||||
WiiWAD wad(name);
|
||||
std::vector<u8> data_app;
|
||||
std::vector<u8> tmd;
|
||||
std::vector<u8> decrypted_title_key;
|
||||
|
||||
if (wad.IsValid())
|
||||
{
|
||||
m_IsWAD = true;
|
||||
m_Ticket = wad.GetTicket();
|
||||
decrypted_title_key = GetKeyFromTicket(m_Ticket);
|
||||
tmd = wad.GetTMD();
|
||||
data_app = wad.GetDataApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string tmd_filename(m_Path);
|
||||
if (wad.IsValid())
|
||||
{
|
||||
m_IsWAD = true;
|
||||
m_Ticket = wad.GetTicket();
|
||||
decrypted_title_key = GetKeyFromTicket(m_Ticket);
|
||||
tmd = wad.GetTMD();
|
||||
data_app = wad.GetDataApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string tmd_filename(m_Path);
|
||||
|
||||
if (tmd_filename.back() == '/')
|
||||
tmd_filename += "title.tmd";
|
||||
else
|
||||
m_Path = tmd_filename.substr(0, tmd_filename.find("title.tmd"));
|
||||
if (tmd_filename.back() == '/')
|
||||
tmd_filename += "title.tmd";
|
||||
else
|
||||
m_Path = tmd_filename.substr(0, tmd_filename.find("title.tmd"));
|
||||
|
||||
File::IOFile tmd_file(tmd_filename, "rb");
|
||||
if (!tmd_file)
|
||||
{
|
||||
WARN_LOG(DISCIO, "CreateFromDirectory: error opening %s", tmd_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
File::IOFile tmd_file(tmd_filename, "rb");
|
||||
if (!tmd_file)
|
||||
{
|
||||
WARN_LOG(DISCIO, "CreateFromDirectory: error opening %s", tmd_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
tmd.resize(static_cast<size_t>(File::GetSize(tmd_filename)));
|
||||
tmd_file.ReadBytes(tmd.data(), tmd.size());
|
||||
}
|
||||
tmd.resize(static_cast<size_t>(File::GetSize(tmd_filename)));
|
||||
tmd_file.ReadBytes(tmd.data(), tmd.size());
|
||||
}
|
||||
|
||||
std::copy(&tmd[0], &tmd[TMD_HEADER_SIZE], m_TMDHeader);
|
||||
std::copy(&tmd[0x180], &tmd[0x180 + TMD_VIEW_SIZE], m_TMDView);
|
||||
std::copy(&tmd[0], &tmd[TMD_HEADER_SIZE], m_TMDHeader);
|
||||
std::copy(&tmd[0x180], &tmd[0x180 + TMD_VIEW_SIZE], m_TMDView);
|
||||
|
||||
m_TitleVersion = Common::swap16(&tmd[0x01DC]);
|
||||
m_NumEntries = Common::swap16(&tmd[0x01DE]);
|
||||
m_BootIndex = Common::swap16(&tmd[0x01E0]);
|
||||
m_TitleID = Common::swap64(&tmd[0x018C]);
|
||||
m_IosVersion = Common::swap16(&tmd[0x018A]);
|
||||
m_Country = static_cast<u8>(m_TitleID & 0xFF);
|
||||
m_TitleVersion = Common::swap16(&tmd[0x01DC]);
|
||||
m_NumEntries = Common::swap16(&tmd[0x01DE]);
|
||||
m_BootIndex = Common::swap16(&tmd[0x01E0]);
|
||||
m_TitleID = Common::swap64(&tmd[0x018C]);
|
||||
m_IosVersion = Common::swap16(&tmd[0x018A]);
|
||||
m_Country = static_cast<u8>(m_TitleID & 0xFF);
|
||||
|
||||
if (m_Country == 2) // SYSMENU
|
||||
m_Country = GetSysMenuRegion(m_TitleVersion);
|
||||
if (m_Country == 2) // SYSMENU
|
||||
m_Country = GetSysMenuRegion(m_TitleVersion);
|
||||
|
||||
InitializeContentEntries(tmd, decrypted_title_key, data_app);
|
||||
return true;
|
||||
InitializeContentEntries(tmd, decrypted_title_key, data_app);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& tmd, const std::vector<u8>& decrypted_title_key, const std::vector<u8>& data_app)
|
||||
void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& tmd,
|
||||
const std::vector<u8>& decrypted_title_key,
|
||||
const std::vector<u8>& data_app)
|
||||
{
|
||||
m_Content.resize(m_NumEntries);
|
||||
m_Content.resize(m_NumEntries);
|
||||
|
||||
std::array<u8, 16> iv;
|
||||
u32 data_app_offset = 0;
|
||||
std::array<u8, 16> iv;
|
||||
u32 data_app_offset = 0;
|
||||
|
||||
for (u32 i = 0; i < m_NumEntries; i++)
|
||||
{
|
||||
const u32 entry_offset = 0x24 * i;
|
||||
for (u32 i = 0; i < m_NumEntries; i++)
|
||||
{
|
||||
const u32 entry_offset = 0x24 * i;
|
||||
|
||||
SNANDContent& content = m_Content[i];
|
||||
content.m_ContentID = Common::swap32(&tmd[entry_offset + 0x01E4]);
|
||||
content.m_Index = Common::swap16(&tmd[entry_offset + 0x01E8]);
|
||||
content.m_Type = Common::swap16(&tmd[entry_offset + 0x01EA]);
|
||||
content.m_Size = static_cast<u32>(Common::swap64(&tmd[entry_offset + 0x01EC]));
|
||||
SNANDContent& content = m_Content[i];
|
||||
content.m_ContentID = Common::swap32(&tmd[entry_offset + 0x01E4]);
|
||||
content.m_Index = Common::swap16(&tmd[entry_offset + 0x01E8]);
|
||||
content.m_Type = Common::swap16(&tmd[entry_offset + 0x01EA]);
|
||||
content.m_Size = static_cast<u32>(Common::swap64(&tmd[entry_offset + 0x01EC]));
|
||||
|
||||
const auto header_begin = std::next(tmd.begin(), entry_offset + 0x01E4);
|
||||
const auto header_end = std::next(header_begin, ArraySize(content.m_Header));
|
||||
std::copy(header_begin, header_end, content.m_Header);
|
||||
const auto header_begin = std::next(tmd.begin(), entry_offset + 0x01E4);
|
||||
const auto header_end = std::next(header_begin, ArraySize(content.m_Header));
|
||||
std::copy(header_begin, header_end, content.m_Header);
|
||||
|
||||
const auto hash_begin = std::next(tmd.begin(), entry_offset + 0x01F4);
|
||||
const auto hash_end = std::next(hash_begin, ArraySize(content.m_SHA1Hash));
|
||||
std::copy(hash_begin, hash_end, content.m_SHA1Hash);
|
||||
const auto hash_begin = std::next(tmd.begin(), entry_offset + 0x01F4);
|
||||
const auto hash_end = std::next(hash_begin, ArraySize(content.m_SHA1Hash));
|
||||
std::copy(hash_begin, hash_end, content.m_SHA1Hash);
|
||||
|
||||
if (m_IsWAD)
|
||||
{
|
||||
u32 rounded_size = ROUND_UP(content.m_Size, 0x40);
|
||||
if (m_IsWAD)
|
||||
{
|
||||
u32 rounded_size = ROUND_UP(content.m_Size, 0x40);
|
||||
|
||||
iv.fill(0);
|
||||
std::copy(&tmd[entry_offset + 0x01E8], &tmd[entry_offset + 0x01E8 + 2], iv.begin());
|
||||
iv.fill(0);
|
||||
std::copy(&tmd[entry_offset + 0x01E8], &tmd[entry_offset + 0x01E8 + 2], iv.begin());
|
||||
|
||||
content.m_Data = std::make_unique<CNANDContentDataBuffer>(AESDecode(decrypted_title_key.data(), iv.data(), &data_app[data_app_offset], rounded_size));
|
||||
content.m_Data = std::make_unique<CNANDContentDataBuffer>(AESDecode(
|
||||
decrypted_title_key.data(), iv.data(), &data_app[data_app_offset], rounded_size));
|
||||
|
||||
data_app_offset += rounded_size;
|
||||
continue;
|
||||
}
|
||||
data_app_offset += rounded_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string filename;
|
||||
if (content.m_Type & 0x8000) // shared app
|
||||
filename = CSharedContent::AccessInstance().GetFilenameFromSHA1(content.m_SHA1Hash);
|
||||
else
|
||||
filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_ContentID);
|
||||
std::string filename;
|
||||
if (content.m_Type & 0x8000) // shared app
|
||||
filename = CSharedContent::AccessInstance().GetFilenameFromSHA1(content.m_SHA1Hash);
|
||||
else
|
||||
filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_ContentID);
|
||||
|
||||
content.m_Data = std::make_unique<CNANDContentDataFile>(filename);
|
||||
content.m_Data = std::make_unique<CNANDContentDataFile>(filename);
|
||||
|
||||
// Be graceful about incorrect TMDs.
|
||||
if (File::Exists(filename))
|
||||
content.m_Size = static_cast<u32>(File::GetSize(filename));
|
||||
}
|
||||
// Be graceful about incorrect TMDs.
|
||||
if (File::Exists(filename))
|
||||
content.m_Size = static_cast<u32>(File::GetSize(filename));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> CNANDContentLoader::AESDecode(const u8* key, u8* iv, const u8* src, u32 size)
|
||||
{
|
||||
mbedtls_aes_context aes_ctx;
|
||||
std::vector<u8> buffer(size);
|
||||
mbedtls_aes_context aes_ctx;
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
|
||||
|
||||
return buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::vector<u8> CNANDContentLoader::GetKeyFromTicket(const std::vector<u8>& ticket)
|
||||
{
|
||||
const u8 common_key[16] = {0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4,0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7};
|
||||
u8 iv[16] = {};
|
||||
const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4,
|
||||
0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7};
|
||||
u8 iv[16] = {};
|
||||
|
||||
std::copy(&ticket[0x01DC], &ticket[0x01DC + 8], iv);
|
||||
return AESDecode(common_key, iv, &ticket[0x01BF], 16);
|
||||
std::copy(&ticket[0x01DC], &ticket[0x01DC + 8], iv);
|
||||
return AESDecode(common_key, iv, &ticket[0x01BF], 16);
|
||||
}
|
||||
|
||||
|
||||
DiscIO::IVolume::ECountry CNANDContentLoader::GetCountry() const
|
||||
{
|
||||
if (!IsValid())
|
||||
return DiscIO::IVolume::COUNTRY_UNKNOWN;
|
||||
if (!IsValid())
|
||||
return DiscIO::IVolume::COUNTRY_UNKNOWN;
|
||||
|
||||
return CountrySwitch(m_Country);
|
||||
return CountrySwitch(m_Country);
|
||||
}
|
||||
|
||||
|
||||
CNANDContentManager::~CNANDContentManager()
|
||||
{
|
||||
}
|
||||
|
||||
const CNANDContentLoader& CNANDContentManager::GetNANDLoader(const std::string& content_path)
|
||||
{
|
||||
auto it = m_map.find(content_path);
|
||||
if (it != m_map.end())
|
||||
return *it->second;
|
||||
return *m_map.emplace_hint(it, std::make_pair(content_path, std::make_unique<CNANDContentLoader>(content_path)))->second;
|
||||
auto it = m_map.find(content_path);
|
||||
if (it != m_map.end())
|
||||
return *it->second;
|
||||
return *m_map
|
||||
.emplace_hint(it, std::make_pair(content_path,
|
||||
std::make_unique<CNANDContentLoader>(content_path)))
|
||||
->second;
|
||||
}
|
||||
|
||||
const CNANDContentLoader& CNANDContentManager::GetNANDLoader(u64 title_id, Common::FromWhichRoot from)
|
||||
const CNANDContentLoader& CNANDContentManager::GetNANDLoader(u64 title_id,
|
||||
Common::FromWhichRoot from)
|
||||
{
|
||||
std::string path = Common::GetTitleContentPath(title_id, from);
|
||||
return GetNANDLoader(path);
|
||||
std::string path = Common::GetTitleContentPath(title_id, from);
|
||||
return GetNANDLoader(path);
|
||||
}
|
||||
|
||||
bool CNANDContentManager::RemoveTitle(u64 title_id, Common::FromWhichRoot from)
|
||||
{
|
||||
auto& loader = GetNANDLoader(title_id, from);
|
||||
if (!loader.IsValid())
|
||||
return false;
|
||||
loader.RemoveTitle();
|
||||
return GetNANDLoader(title_id, from).IsValid();
|
||||
auto& loader = GetNANDLoader(title_id, from);
|
||||
if (!loader.IsValid())
|
||||
return false;
|
||||
loader.RemoveTitle();
|
||||
return GetNANDLoader(title_id, from).IsValid();
|
||||
}
|
||||
|
||||
void CNANDContentManager::ClearCache()
|
||||
{
|
||||
m_map.clear();
|
||||
m_map.clear();
|
||||
}
|
||||
|
||||
void CNANDContentLoader::RemoveTitle() const
|
||||
{
|
||||
INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
|
||||
if (IsValid())
|
||||
{
|
||||
// remove TMD?
|
||||
for (u32 i = 0; i < m_NumEntries; i++)
|
||||
{
|
||||
if (!(m_Content[i].m_Type & 0x8000)) // skip shared apps
|
||||
{
|
||||
std::string filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), m_Content[i].m_ContentID);
|
||||
INFO_LOG(DISCIO, "Delete %s", filename.c_str());
|
||||
File::Delete(filename);
|
||||
}
|
||||
}
|
||||
CNANDContentManager::Access().ClearCache(); // deletes 'this'
|
||||
}
|
||||
INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
|
||||
if (IsValid())
|
||||
{
|
||||
// remove TMD?
|
||||
for (u32 i = 0; i < m_NumEntries; i++)
|
||||
{
|
||||
if (!(m_Content[i].m_Type & 0x8000)) // skip shared apps
|
||||
{
|
||||
std::string filename =
|
||||
StringFromFormat("%s/%08x.app", m_Path.c_str(), m_Content[i].m_ContentID);
|
||||
INFO_LOG(DISCIO, "Delete %s", filename.c_str());
|
||||
File::Delete(filename);
|
||||
}
|
||||
}
|
||||
CNANDContentManager::Access().ClearCache(); // deletes 'this'
|
||||
}
|
||||
}
|
||||
|
||||
cUIDsys::cUIDsys()
|
||||
{
|
||||
UpdateLocation();
|
||||
UpdateLocation();
|
||||
}
|
||||
|
||||
void cUIDsys::UpdateLocation()
|
||||
{
|
||||
m_Elements.clear();
|
||||
m_LastUID = 0x00001000;
|
||||
m_UidSys = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/sys/uid.sys";
|
||||
m_Elements.clear();
|
||||
m_LastUID = 0x00001000;
|
||||
m_UidSys = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/sys/uid.sys";
|
||||
|
||||
File::IOFile pFile(m_UidSys, "rb");
|
||||
SElement Element;
|
||||
while (pFile.ReadArray(&Element, 1))
|
||||
{
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
m_Elements.push_back(Element);
|
||||
}
|
||||
pFile.Close();
|
||||
File::IOFile pFile(m_UidSys, "rb");
|
||||
SElement Element;
|
||||
while (pFile.ReadArray(&Element, 1))
|
||||
{
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
m_Elements.push_back(Element);
|
||||
}
|
||||
pFile.Close();
|
||||
|
||||
if (m_Elements.empty())
|
||||
{
|
||||
*(u64*)&(Element.titleID) = Common::swap64(TITLEID_SYSMENU);
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
if (m_Elements.empty())
|
||||
{
|
||||
*(u64*)&(Element.titleID) = Common::swap64(TITLEID_SYSMENU);
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
|
||||
File::CreateFullPath(m_UidSys);
|
||||
pFile.Open(m_UidSys, "wb");
|
||||
if (!pFile.WriteArray(&Element, 1))
|
||||
ERROR_LOG(DISCIO, "Failed to write to %s", m_UidSys.c_str());
|
||||
}
|
||||
File::CreateFullPath(m_UidSys);
|
||||
pFile.Open(m_UidSys, "wb");
|
||||
if (!pFile.WriteArray(&Element, 1))
|
||||
ERROR_LOG(DISCIO, "Failed to write to %s", m_UidSys.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
cUIDsys::~cUIDsys()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
u32 cUIDsys::GetUIDFromTitle(u64 title_id)
|
||||
{
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if (Common::swap64(title_id) == *(u64*)&(Element.titleID))
|
||||
{
|
||||
return Common::swap32(Element.UID);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if (Common::swap64(title_id) == *(u64*)&(Element.titleID))
|
||||
{
|
||||
return Common::swap32(Element.UID);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cUIDsys::AddTitle(u64 title_id)
|
||||
{
|
||||
if (GetUIDFromTitle(title_id))
|
||||
{
|
||||
INFO_LOG(DISCIO, "Title %08x%08x, already exists in uid.sys", (u32)(title_id >> 32), (u32)title_id);
|
||||
return;
|
||||
}
|
||||
if (GetUIDFromTitle(title_id))
|
||||
{
|
||||
INFO_LOG(DISCIO, "Title %08x%08x, already exists in uid.sys", (u32)(title_id >> 32),
|
||||
(u32)title_id);
|
||||
return;
|
||||
}
|
||||
|
||||
SElement Element;
|
||||
*(u64*)&(Element.titleID) = Common::swap64(title_id);
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
m_Elements.push_back(Element);
|
||||
SElement Element;
|
||||
*(u64*)&(Element.titleID) = Common::swap64(title_id);
|
||||
*(u32*)&(Element.UID) = Common::swap32(m_LastUID++);
|
||||
m_Elements.push_back(Element);
|
||||
|
||||
File::CreateFullPath(m_UidSys);
|
||||
File::IOFile pFile(m_UidSys, "ab");
|
||||
File::CreateFullPath(m_UidSys);
|
||||
File::IOFile pFile(m_UidSys, "ab");
|
||||
|
||||
if (!pFile.WriteArray(&Element, 1))
|
||||
ERROR_LOG(DISCIO, "fwrite failed");
|
||||
if (!pFile.WriteArray(&Element, 1))
|
||||
ERROR_LOG(DISCIO, "fwrite failed");
|
||||
}
|
||||
|
||||
void cUIDsys::GetTitleIDs(std::vector<u64>& title_ids, bool owned)
|
||||
{
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if ((owned && Common::CheckTitleTIK(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)) ||
|
||||
(!owned && Common::CheckTitleTMD(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)))
|
||||
title_ids.push_back(Common::swap64(Element.titleID));
|
||||
}
|
||||
for (auto& Element : m_Elements)
|
||||
{
|
||||
if ((owned &&
|
||||
Common::CheckTitleTIK(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)) ||
|
||||
(!owned &&
|
||||
Common::CheckTitleTMD(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)))
|
||||
title_ids.push_back(Common::swap64(Element.titleID));
|
||||
}
|
||||
}
|
||||
|
||||
u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
|
||||
{
|
||||
if (filename.find(".wad") == std::string::npos)
|
||||
return 0;
|
||||
const CNANDContentLoader& content_loader = GetNANDLoader(filename);
|
||||
if (content_loader.IsValid() == false)
|
||||
return 0;
|
||||
if (filename.find(".wad") == std::string::npos)
|
||||
return 0;
|
||||
const CNANDContentLoader& content_loader = GetNANDLoader(filename);
|
||||
if (content_loader.IsValid() == false)
|
||||
return 0;
|
||||
|
||||
u64 title_id = content_loader.GetTitleID();
|
||||
u64 title_id = content_loader.GetTitleID();
|
||||
|
||||
//copy WAD's TMD header and contents to content directory
|
||||
// copy WAD's TMD header and contents to content directory
|
||||
|
||||
std::string content_path(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
std::string tmd_filename(Common::GetTMDFileName(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
File::CreateFullPath(tmd_filename);
|
||||
std::string content_path(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
std::string tmd_filename(Common::GetTMDFileName(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
File::CreateFullPath(tmd_filename);
|
||||
|
||||
File::IOFile tmd_file(tmd_filename, "wb");
|
||||
if (!tmd_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", tmd_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
File::IOFile tmd_file(tmd_filename, "wb");
|
||||
if (!tmd_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", tmd_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmd_file.WriteBytes(content_loader.GetTMDHeader(), CNANDContentLoader::TMD_HEADER_SIZE);
|
||||
tmd_file.WriteBytes(content_loader.GetTMDHeader(), CNANDContentLoader::TMD_HEADER_SIZE);
|
||||
|
||||
for (u32 i = 0; i < content_loader.GetContentSize(); i++)
|
||||
{
|
||||
const SNANDContent& content = content_loader.GetContent()[i];
|
||||
for (u32 i = 0; i < content_loader.GetContentSize(); i++)
|
||||
{
|
||||
const SNANDContent& content = content_loader.GetContent()[i];
|
||||
|
||||
tmd_file.WriteBytes(content.m_Header, CNANDContentLoader::CONTENT_HEADER_SIZE);
|
||||
tmd_file.WriteBytes(content.m_Header, CNANDContentLoader::CONTENT_HEADER_SIZE);
|
||||
|
||||
std::string app_filename;
|
||||
if (content.m_Type & 0x8000) //shared
|
||||
app_filename = CSharedContent::AccessInstance().AddSharedContent(content.m_SHA1Hash);
|
||||
else
|
||||
app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_ContentID);
|
||||
std::string app_filename;
|
||||
if (content.m_Type & 0x8000) // shared
|
||||
app_filename = CSharedContent::AccessInstance().AddSharedContent(content.m_SHA1Hash);
|
||||
else
|
||||
app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_ContentID);
|
||||
|
||||
if (!File::Exists(app_filename))
|
||||
{
|
||||
File::CreateFullPath(app_filename);
|
||||
File::IOFile app_file(app_filename, "wb");
|
||||
if (!app_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", app_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
if (!File::Exists(app_filename))
|
||||
{
|
||||
File::CreateFullPath(app_filename);
|
||||
File::IOFile app_file(app_filename, "wb");
|
||||
if (!app_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", app_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
app_file.WriteBytes(content.m_Data->Get().data(), content.m_Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(DISCIO, "Content %s already exists.", app_filename.c_str());
|
||||
}
|
||||
}
|
||||
app_file.WriteBytes(content.m_Data->Get().data(), content.m_Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(DISCIO, "Content %s already exists.", app_filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//Extract and copy WAD's ticket to ticket directory
|
||||
if (!AddTicket(title_id, content_loader.GetTicket()))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating ticket");
|
||||
return 0;
|
||||
}
|
||||
// Extract and copy WAD's ticket to ticket directory
|
||||
if (!AddTicket(title_id, content_loader.GetTicket()))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating ticket");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cUIDsys::AccessInstance().AddTitle(title_id);
|
||||
cUIDsys::AccessInstance().AddTitle(title_id);
|
||||
|
||||
ClearCache();
|
||||
ClearCache();
|
||||
|
||||
return title_id;
|
||||
return title_id;
|
||||
}
|
||||
|
||||
bool AddTicket(u64 title_id, const std::vector<u8>& ticket)
|
||||
{
|
||||
std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
File::CreateFullPath(ticket_filename);
|
||||
std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
File::CreateFullPath(ticket_filename);
|
||||
|
||||
File::IOFile ticket_file(ticket_filename, "wb");
|
||||
if (!ticket_file)
|
||||
return false;
|
||||
File::IOFile ticket_file(ticket_filename, "wb");
|
||||
if (!ticket_file)
|
||||
return false;
|
||||
|
||||
return ticket_file.WriteBytes(ticket.data(), ticket.size());
|
||||
return ticket_file.WriteBytes(ticket.data(), ticket.size());
|
||||
}
|
||||
|
||||
} // namespace end
|
||||
|
||||
} // namespace end
|
||||
|
@ -20,186 +20,197 @@ bool AddTicket(u64 title_id, const std::vector<u8>& ticket);
|
||||
class CNANDContentData
|
||||
{
|
||||
public:
|
||||
virtual void Open() { };
|
||||
virtual const std::vector<u8> Get() = 0;
|
||||
virtual bool GetRange(u32 start, u32 size, u8* buffer) = 0;
|
||||
virtual void Close() { };
|
||||
virtual void Open(){};
|
||||
virtual const std::vector<u8> Get() = 0;
|
||||
virtual bool GetRange(u32 start, u32 size, u8* buffer) = 0;
|
||||
virtual void Close(){};
|
||||
};
|
||||
|
||||
class CNANDContentDataFile final : public CNANDContentData
|
||||
{
|
||||
public:
|
||||
CNANDContentDataFile(const std::string& filename) : m_filename(filename) { };
|
||||
CNANDContentDataFile(const std::string& filename) : m_filename(filename){};
|
||||
|
||||
void Open() override;
|
||||
const std::vector<u8> Get() override;
|
||||
bool GetRange(u32 start, u32 size, u8* buffer) override;
|
||||
void Close() override;
|
||||
|
||||
void Open() override;
|
||||
const std::vector<u8> Get() override;
|
||||
bool GetRange(u32 start, u32 size, u8* buffer) override;
|
||||
void Close() override;
|
||||
private:
|
||||
void EnsureOpen();
|
||||
void EnsureOpen();
|
||||
|
||||
const std::string m_filename;
|
||||
std::unique_ptr<File::IOFile> m_file;
|
||||
const std::string m_filename;
|
||||
std::unique_ptr<File::IOFile> m_file;
|
||||
};
|
||||
class CNANDContentDataBuffer final : public CNANDContentData
|
||||
{
|
||||
public:
|
||||
CNANDContentDataBuffer(const std::vector<u8>& buffer) : m_buffer(buffer) { };
|
||||
CNANDContentDataBuffer(const std::vector<u8>& buffer) : m_buffer(buffer){};
|
||||
|
||||
const std::vector<u8> Get() override { return m_buffer; };
|
||||
bool GetRange(u32 start, u32 size, u8* buffer) override;
|
||||
|
||||
const std::vector<u8> Get() override { return m_buffer; };
|
||||
bool GetRange(u32 start, u32 size, u8* buffer) override;
|
||||
private:
|
||||
const std::vector<u8> m_buffer;
|
||||
const std::vector<u8> m_buffer;
|
||||
};
|
||||
|
||||
struct SNANDContent
|
||||
{
|
||||
u32 m_ContentID;
|
||||
u16 m_Index;
|
||||
u16 m_Type;
|
||||
u32 m_Size;
|
||||
u8 m_SHA1Hash[20];
|
||||
u8 m_Header[36]; //all of the above
|
||||
u32 m_ContentID;
|
||||
u16 m_Index;
|
||||
u16 m_Type;
|
||||
u32 m_Size;
|
||||
u8 m_SHA1Hash[20];
|
||||
u8 m_Header[36]; // all of the above
|
||||
|
||||
std::unique_ptr<CNANDContentData> m_Data;
|
||||
std::unique_ptr<CNANDContentData> m_Data;
|
||||
};
|
||||
|
||||
// Instances of this class must be created by CNANDContentManager
|
||||
class CNANDContentLoader final
|
||||
{
|
||||
public:
|
||||
CNANDContentLoader(const std::string& content_name);
|
||||
virtual ~CNANDContentLoader();
|
||||
CNANDContentLoader(const std::string& content_name);
|
||||
virtual ~CNANDContentLoader();
|
||||
|
||||
bool IsValid() const { return m_Valid; }
|
||||
void RemoveTitle() const;
|
||||
u64 GetTitleID() const { return m_TitleID; }
|
||||
u16 GetIosVersion() const { return m_IosVersion; }
|
||||
u32 GetBootIndex() const { return m_BootIndex; }
|
||||
size_t GetContentSize() const { return m_Content.size(); }
|
||||
const SNANDContent* GetContentByIndex(int index) const;
|
||||
const u8* GetTMDView() const { return m_TMDView; }
|
||||
const u8* GetTMDHeader() const { return m_TMDHeader; }
|
||||
const std::vector<u8>& GetTicket() const { return m_Ticket; }
|
||||
|
||||
const std::vector<SNANDContent>& GetContent() const { return m_Content; }
|
||||
|
||||
u16 GetTitleVersion() const { return m_TitleVersion; }
|
||||
u16 GetNumEntries() const { return m_NumEntries; }
|
||||
DiscIO::IVolume::ECountry GetCountry() const;
|
||||
u8 GetCountryChar() const { return m_Country; }
|
||||
|
||||
enum
|
||||
{
|
||||
TMD_VIEW_SIZE = 0x58,
|
||||
TMD_HEADER_SIZE = 0x1E4,
|
||||
CONTENT_HEADER_SIZE = 0x24,
|
||||
TICKET_SIZE = 0x2A4
|
||||
};
|
||||
bool IsValid() const { return m_Valid; }
|
||||
void RemoveTitle() const;
|
||||
u64 GetTitleID() const { return m_TitleID; }
|
||||
u16 GetIosVersion() const { return m_IosVersion; }
|
||||
u32 GetBootIndex() const { return m_BootIndex; }
|
||||
size_t GetContentSize() const { return m_Content.size(); }
|
||||
const SNANDContent* GetContentByIndex(int index) const;
|
||||
const u8* GetTMDView() const { return m_TMDView; }
|
||||
const u8* GetTMDHeader() const { return m_TMDHeader; }
|
||||
const std::vector<u8>& GetTicket() const { return m_Ticket; }
|
||||
const std::vector<SNANDContent>& GetContent() const { return m_Content; }
|
||||
u16 GetTitleVersion() const { return m_TitleVersion; }
|
||||
u16 GetNumEntries() const { return m_NumEntries; }
|
||||
DiscIO::IVolume::ECountry GetCountry() const;
|
||||
u8 GetCountryChar() const { return m_Country; }
|
||||
enum
|
||||
{
|
||||
TMD_VIEW_SIZE = 0x58,
|
||||
TMD_HEADER_SIZE = 0x1E4,
|
||||
CONTENT_HEADER_SIZE = 0x24,
|
||||
TICKET_SIZE = 0x2A4
|
||||
};
|
||||
|
||||
private:
|
||||
bool Initialize(const std::string& name);
|
||||
void InitializeContentEntries(const std::vector<u8>& tmd, const std::vector<u8>& decrypted_title_key, const std::vector<u8>& data_app);
|
||||
bool Initialize(const std::string& name);
|
||||
void InitializeContentEntries(const std::vector<u8>& tmd,
|
||||
const std::vector<u8>& decrypted_title_key,
|
||||
const std::vector<u8>& data_app);
|
||||
|
||||
static std::vector<u8> AESDecode(const u8* key, u8* iv, const u8* src, u32 size);
|
||||
static std::vector<u8> GetKeyFromTicket(const std::vector<u8>& ticket);
|
||||
static std::vector<u8> AESDecode(const u8* key, u8* iv, const u8* src, u32 size);
|
||||
static std::vector<u8> GetKeyFromTicket(const std::vector<u8>& ticket);
|
||||
|
||||
bool m_Valid;
|
||||
bool m_IsWAD;
|
||||
std::string m_Path;
|
||||
u64 m_TitleID;
|
||||
u16 m_IosVersion;
|
||||
u32 m_BootIndex;
|
||||
u16 m_NumEntries;
|
||||
u16 m_TitleVersion;
|
||||
u8 m_TMDView[TMD_VIEW_SIZE];
|
||||
u8 m_TMDHeader[TMD_HEADER_SIZE];
|
||||
std::vector<u8> m_Ticket;
|
||||
u8 m_Country;
|
||||
bool m_Valid;
|
||||
bool m_IsWAD;
|
||||
std::string m_Path;
|
||||
u64 m_TitleID;
|
||||
u16 m_IosVersion;
|
||||
u32 m_BootIndex;
|
||||
u16 m_NumEntries;
|
||||
u16 m_TitleVersion;
|
||||
u8 m_TMDView[TMD_VIEW_SIZE];
|
||||
u8 m_TMDHeader[TMD_HEADER_SIZE];
|
||||
std::vector<u8> m_Ticket;
|
||||
u8 m_Country;
|
||||
|
||||
std::vector<SNANDContent> m_Content;
|
||||
std::vector<SNANDContent> m_Content;
|
||||
};
|
||||
|
||||
|
||||
// we open the NAND Content files too often... let's cache them
|
||||
class CNANDContentManager
|
||||
{
|
||||
public:
|
||||
static CNANDContentManager& Access() { static CNANDContentManager instance; return instance; }
|
||||
u64 Install_WiiWAD(const std::string& fileName);
|
||||
static CNANDContentManager& Access()
|
||||
{
|
||||
static CNANDContentManager instance;
|
||||
return instance;
|
||||
}
|
||||
u64 Install_WiiWAD(const std::string& fileName);
|
||||
|
||||
const CNANDContentLoader& GetNANDLoader(const std::string& content_path);
|
||||
const CNANDContentLoader& GetNANDLoader(u64 title_id, Common::FromWhichRoot from);
|
||||
bool RemoveTitle(u64 title_id, Common::FromWhichRoot from);
|
||||
void ClearCache();
|
||||
const CNANDContentLoader& GetNANDLoader(const std::string& content_path);
|
||||
const CNANDContentLoader& GetNANDLoader(u64 title_id, Common::FromWhichRoot from);
|
||||
bool RemoveTitle(u64 title_id, Common::FromWhichRoot from);
|
||||
void ClearCache();
|
||||
|
||||
private:
|
||||
CNANDContentManager() {}
|
||||
~CNANDContentManager();
|
||||
CNANDContentManager() {}
|
||||
~CNANDContentManager();
|
||||
|
||||
CNANDContentManager(CNANDContentManager const&) = delete;
|
||||
void operator=(CNANDContentManager const&) = delete;
|
||||
CNANDContentManager(CNANDContentManager const&) = delete;
|
||||
void operator=(CNANDContentManager const&) = delete;
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<CNANDContentLoader>> m_map;
|
||||
std::unordered_map<std::string, std::unique_ptr<CNANDContentLoader>> m_map;
|
||||
};
|
||||
|
||||
class CSharedContent
|
||||
{
|
||||
public:
|
||||
static CSharedContent& AccessInstance() { static CSharedContent instance; return instance; }
|
||||
static CSharedContent& AccessInstance()
|
||||
{
|
||||
static CSharedContent instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::string GetFilenameFromSHA1(const u8* hash);
|
||||
std::string AddSharedContent(const u8* hash);
|
||||
void UpdateLocation();
|
||||
std::string GetFilenameFromSHA1(const u8* hash);
|
||||
std::string AddSharedContent(const u8* hash);
|
||||
void UpdateLocation();
|
||||
|
||||
private:
|
||||
CSharedContent();
|
||||
virtual ~CSharedContent();
|
||||
CSharedContent();
|
||||
virtual ~CSharedContent();
|
||||
|
||||
CSharedContent(CSharedContent const&) = delete;
|
||||
void operator=(CSharedContent const&) = delete;
|
||||
CSharedContent(CSharedContent const&) = delete;
|
||||
void operator=(CSharedContent const&) = delete;
|
||||
|
||||
#pragma pack(push,1)
|
||||
struct SElement
|
||||
{
|
||||
u8 FileName[8];
|
||||
u8 SHA1Hash[20];
|
||||
};
|
||||
#pragma pack(push, 1)
|
||||
struct SElement
|
||||
{
|
||||
u8 FileName[8];
|
||||
u8 SHA1Hash[20];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
u32 m_LastID;
|
||||
std::string m_ContentMap;
|
||||
std::vector<SElement> m_Elements;
|
||||
u32 m_LastID;
|
||||
std::string m_ContentMap;
|
||||
std::vector<SElement> m_Elements;
|
||||
};
|
||||
|
||||
class cUIDsys
|
||||
{
|
||||
public:
|
||||
static cUIDsys& AccessInstance() { static cUIDsys instance; return instance; }
|
||||
static cUIDsys& AccessInstance()
|
||||
{
|
||||
static cUIDsys instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
u32 GetUIDFromTitle(u64 title_id);
|
||||
void AddTitle(u64 title_id);
|
||||
void GetTitleIDs(std::vector<u64>& title_ids, bool owned = false);
|
||||
void UpdateLocation();
|
||||
u32 GetUIDFromTitle(u64 title_id);
|
||||
void AddTitle(u64 title_id);
|
||||
void GetTitleIDs(std::vector<u64>& title_ids, bool owned = false);
|
||||
void UpdateLocation();
|
||||
|
||||
private:
|
||||
cUIDsys();
|
||||
virtual ~cUIDsys();
|
||||
cUIDsys();
|
||||
virtual ~cUIDsys();
|
||||
|
||||
cUIDsys(cUIDsys const&) = delete;
|
||||
void operator=(cUIDsys const&) = delete;
|
||||
cUIDsys(cUIDsys const&) = delete;
|
||||
void operator=(cUIDsys const&) = delete;
|
||||
|
||||
#pragma pack(push,1)
|
||||
struct SElement
|
||||
{
|
||||
u8 titleID[8];
|
||||
u8 UID[4];
|
||||
};
|
||||
#pragma pack(push, 1)
|
||||
struct SElement
|
||||
{
|
||||
u8 titleID[8];
|
||||
u8 UID[4];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
u32 m_LastUID;
|
||||
std::string m_UidSys;
|
||||
std::vector<SElement> m_Elements;
|
||||
u32 m_LastUID;
|
||||
std::string m_UidSys;
|
||||
std::vector<SElement> m_Elements;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -19,119 +19,119 @@ namespace DiscIO
|
||||
class IVolume
|
||||
{
|
||||
public:
|
||||
// Increment CACHE_REVISION if the enums below are modified (ISOFile.cpp & GameFile.cpp)
|
||||
enum EPlatform
|
||||
{
|
||||
GAMECUBE_DISC = 0,
|
||||
WII_DISC,
|
||||
WII_WAD,
|
||||
ELF_DOL,
|
||||
NUMBER_OF_PLATFORMS
|
||||
};
|
||||
// Increment CACHE_REVISION if the enums below are modified (ISOFile.cpp & GameFile.cpp)
|
||||
enum EPlatform
|
||||
{
|
||||
GAMECUBE_DISC = 0,
|
||||
WII_DISC,
|
||||
WII_WAD,
|
||||
ELF_DOL,
|
||||
NUMBER_OF_PLATFORMS
|
||||
};
|
||||
|
||||
enum ECountry
|
||||
{
|
||||
COUNTRY_EUROPE = 0,
|
||||
COUNTRY_JAPAN,
|
||||
COUNTRY_USA,
|
||||
COUNTRY_AUSTRALIA,
|
||||
COUNTRY_FRANCE,
|
||||
COUNTRY_GERMANY,
|
||||
COUNTRY_ITALY,
|
||||
COUNTRY_KOREA,
|
||||
COUNTRY_NETHERLANDS,
|
||||
COUNTRY_RUSSIA,
|
||||
COUNTRY_SPAIN,
|
||||
COUNTRY_TAIWAN,
|
||||
COUNTRY_WORLD,
|
||||
COUNTRY_UNKNOWN,
|
||||
NUMBER_OF_COUNTRIES
|
||||
};
|
||||
enum ECountry
|
||||
{
|
||||
COUNTRY_EUROPE = 0,
|
||||
COUNTRY_JAPAN,
|
||||
COUNTRY_USA,
|
||||
COUNTRY_AUSTRALIA,
|
||||
COUNTRY_FRANCE,
|
||||
COUNTRY_GERMANY,
|
||||
COUNTRY_ITALY,
|
||||
COUNTRY_KOREA,
|
||||
COUNTRY_NETHERLANDS,
|
||||
COUNTRY_RUSSIA,
|
||||
COUNTRY_SPAIN,
|
||||
COUNTRY_TAIWAN,
|
||||
COUNTRY_WORLD,
|
||||
COUNTRY_UNKNOWN,
|
||||
NUMBER_OF_COUNTRIES
|
||||
};
|
||||
|
||||
// Languages 0 - 9 match the official Wii language numbering.
|
||||
// Languages 1 - 6 match the official GC PAL languages 0 - 5.
|
||||
enum ELanguage
|
||||
{
|
||||
LANGUAGE_JAPANESE = 0,
|
||||
LANGUAGE_ENGLISH = 1,
|
||||
LANGUAGE_GERMAN = 2,
|
||||
LANGUAGE_FRENCH = 3,
|
||||
LANGUAGE_SPANISH = 4,
|
||||
LANGUAGE_ITALIAN = 5,
|
||||
LANGUAGE_DUTCH = 6,
|
||||
LANGUAGE_SIMPLIFIED_CHINESE = 7,
|
||||
LANGUAGE_TRADITIONAL_CHINESE = 8,
|
||||
LANGUAGE_KOREAN = 9,
|
||||
LANGUAGE_UNKNOWN
|
||||
};
|
||||
// Languages 0 - 9 match the official Wii language numbering.
|
||||
// Languages 1 - 6 match the official GC PAL languages 0 - 5.
|
||||
enum ELanguage
|
||||
{
|
||||
LANGUAGE_JAPANESE = 0,
|
||||
LANGUAGE_ENGLISH = 1,
|
||||
LANGUAGE_GERMAN = 2,
|
||||
LANGUAGE_FRENCH = 3,
|
||||
LANGUAGE_SPANISH = 4,
|
||||
LANGUAGE_ITALIAN = 5,
|
||||
LANGUAGE_DUTCH = 6,
|
||||
LANGUAGE_SIMPLIFIED_CHINESE = 7,
|
||||
LANGUAGE_TRADITIONAL_CHINESE = 8,
|
||||
LANGUAGE_KOREAN = 9,
|
||||
LANGUAGE_UNKNOWN
|
||||
};
|
||||
|
||||
IVolume() {}
|
||||
virtual ~IVolume() {}
|
||||
IVolume() {}
|
||||
virtual ~IVolume() {}
|
||||
// decrypt parameter must be false if not reading a Wii disc
|
||||
virtual bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const = 0;
|
||||
template <typename T>
|
||||
bool ReadSwapped(u64 offset, T* buffer, bool decrypt) const
|
||||
{
|
||||
T temp;
|
||||
if (!Read(offset, sizeof(T), reinterpret_cast<u8*>(&temp), decrypt))
|
||||
return false;
|
||||
*buffer = Common::FromBigEndian(temp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// decrypt parameter must be false if not reading a Wii disc
|
||||
virtual bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const = 0;
|
||||
template <typename T>
|
||||
bool ReadSwapped(u64 offset, T* buffer, bool decrypt) const
|
||||
{
|
||||
T temp;
|
||||
if (!Read(offset, sizeof(T), reinterpret_cast<u8*>(&temp), decrypt))
|
||||
return false;
|
||||
*buffer = Common::FromBigEndian(temp);
|
||||
return true;
|
||||
}
|
||||
virtual bool GetTitleID(u64*) const { return false; }
|
||||
virtual std::vector<u8> GetTMD() const { return {}; }
|
||||
virtual std::string GetUniqueID() const = 0;
|
||||
virtual std::string GetMakerID() const = 0;
|
||||
virtual u16 GetRevision() const = 0;
|
||||
virtual std::string GetInternalName() const = 0;
|
||||
virtual std::map<ELanguage, std::string> GetNames(bool prefer_long) const = 0;
|
||||
virtual std::map<ELanguage, std::string> GetDescriptions() const
|
||||
{
|
||||
return std::map<ELanguage, std::string>();
|
||||
}
|
||||
virtual std::string GetCompany() const { return std::string(); }
|
||||
virtual std::vector<u32> GetBanner(int* width, int* height) const = 0;
|
||||
virtual u64 GetFSTSize() const = 0;
|
||||
virtual std::string GetApploaderDate() const = 0;
|
||||
// 0 is the first disc, 1 is the second disc
|
||||
virtual u8 GetDiscNumber() const { return 0; }
|
||||
virtual EPlatform GetVolumeType() const = 0;
|
||||
virtual bool SupportsIntegrityCheck() const { return false; }
|
||||
virtual bool CheckIntegrity() const { return false; }
|
||||
virtual bool ChangePartition(u64 offset) { return false; }
|
||||
virtual ECountry GetCountry() const = 0;
|
||||
virtual BlobType GetBlobType() const = 0;
|
||||
// Size of virtual disc (not always accurate)
|
||||
virtual u64 GetSize() const = 0;
|
||||
// Size on disc (compressed size)
|
||||
virtual u64 GetRawSize() const = 0;
|
||||
|
||||
virtual bool GetTitleID(u64*) const { return false; }
|
||||
virtual std::vector<u8> GetTMD() const { return {}; }
|
||||
virtual std::string GetUniqueID() const = 0;
|
||||
virtual std::string GetMakerID() const = 0;
|
||||
virtual u16 GetRevision() const = 0;
|
||||
virtual std::string GetInternalName() const = 0;
|
||||
virtual std::map<ELanguage, std::string> GetNames(bool prefer_long) const = 0;
|
||||
virtual std::map<ELanguage, std::string> GetDescriptions() const { return std::map<ELanguage, std::string>(); }
|
||||
virtual std::string GetCompany() const { return std::string(); }
|
||||
virtual std::vector<u32> GetBanner(int* width, int* height) const = 0;
|
||||
virtual u64 GetFSTSize() const = 0;
|
||||
virtual std::string GetApploaderDate() const = 0;
|
||||
// 0 is the first disc, 1 is the second disc
|
||||
virtual u8 GetDiscNumber() const { return 0; }
|
||||
|
||||
virtual EPlatform GetVolumeType() const = 0;
|
||||
virtual bool SupportsIntegrityCheck() const { return false; }
|
||||
virtual bool CheckIntegrity() const { return false; }
|
||||
virtual bool ChangePartition(u64 offset) { return false; }
|
||||
|
||||
virtual ECountry GetCountry() const = 0;
|
||||
virtual BlobType GetBlobType() const = 0;
|
||||
// Size of virtual disc (not always accurate)
|
||||
virtual u64 GetSize() const = 0;
|
||||
// Size on disc (compressed size)
|
||||
virtual u64 GetRawSize() const = 0;
|
||||
|
||||
static std::vector<u32> GetWiiBanner(int* width, int* height, u64 title_id);
|
||||
static std::vector<u32> GetWiiBanner(int* width, int* height, u64 title_id);
|
||||
|
||||
protected:
|
||||
template <u32 N>
|
||||
std::string DecodeString(const char(&data)[N]) const
|
||||
{
|
||||
// strnlen to trim NULLs
|
||||
std::string string(data, strnlen(data, sizeof(data)));
|
||||
template <u32 N>
|
||||
std::string DecodeString(const char (&data)[N]) const
|
||||
{
|
||||
// strnlen to trim NULLs
|
||||
std::string string(data, strnlen(data, sizeof(data)));
|
||||
|
||||
// There don't seem to be any GC discs with the country set to Taiwan...
|
||||
// But maybe they would use Shift_JIS if they existed? Not sure
|
||||
bool use_shift_jis = (COUNTRY_JAPAN == GetCountry() || COUNTRY_TAIWAN == GetCountry());
|
||||
// There don't seem to be any GC discs with the country set to Taiwan...
|
||||
// But maybe they would use Shift_JIS if they existed? Not sure
|
||||
bool use_shift_jis = (COUNTRY_JAPAN == GetCountry() || COUNTRY_TAIWAN == GetCountry());
|
||||
|
||||
if (use_shift_jis)
|
||||
return SHIFTJISToUTF8(string);
|
||||
else
|
||||
return CP1252ToUTF8(string);
|
||||
}
|
||||
if (use_shift_jis)
|
||||
return SHIFTJISToUTF8(string);
|
||||
else
|
||||
return CP1252ToUTF8(string);
|
||||
}
|
||||
|
||||
static std::map<IVolume::ELanguage, std::string> ReadWiiNames(const std::vector<u8>& data);
|
||||
static std::map<IVolume::ELanguage, std::string> ReadWiiNames(const std::vector<u8>& data);
|
||||
|
||||
static const size_t NUMBER_OF_LANGUAGES = 10;
|
||||
static const size_t NAME_STRING_LENGTH = 42;
|
||||
static const size_t NAME_BYTES_LENGTH = NAME_STRING_LENGTH * sizeof(u16);
|
||||
static const size_t NAMES_TOTAL_BYTES = NAME_BYTES_LENGTH * NUMBER_OF_LANGUAGES;
|
||||
static const size_t NUMBER_OF_LANGUAGES = 10;
|
||||
static const size_t NAME_STRING_LENGTH = 42;
|
||||
static const size_t NAME_BYTES_LENGTH = NAME_STRING_LENGTH * sizeof(u16);
|
||||
static const size_t NAMES_TOTAL_BYTES = NAME_BYTES_LENGTH * NUMBER_OF_LANGUAGES;
|
||||
};
|
||||
|
||||
// Generic Switch function for all volumes
|
||||
@ -139,4 +139,4 @@ IVolume::ECountry CountrySwitch(u8 country_code);
|
||||
u8 GetSysMenuRegion(u16 _TitleVersion);
|
||||
std::string GetCompanyFromID(const std::string& company_id);
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,8 +12,8 @@
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeCreator.h"
|
||||
@ -22,192 +22,197 @@
|
||||
#include "DiscIO/VolumeWad.h"
|
||||
#include "DiscIO/VolumeWiiCrypted.h"
|
||||
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
enum EDiscType
|
||||
{
|
||||
DISC_TYPE_UNK,
|
||||
DISC_TYPE_WII,
|
||||
DISC_TYPE_WII_CONTAINER,
|
||||
DISC_TYPE_GC,
|
||||
DISC_TYPE_WAD
|
||||
DISC_TYPE_UNK,
|
||||
DISC_TYPE_WII,
|
||||
DISC_TYPE_WII_CONTAINER,
|
||||
DISC_TYPE_GC,
|
||||
DISC_TYPE_WAD
|
||||
};
|
||||
|
||||
static const unsigned char s_master_key[16] = {
|
||||
0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4,
|
||||
0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7
|
||||
};
|
||||
static const unsigned char s_master_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4,
|
||||
0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7};
|
||||
|
||||
static const unsigned char s_master_key_korean[16] = {
|
||||
0x63,0xb8,0x2b,0xb4,0xf4,0x61,0x4e,0x2e,
|
||||
0x13,0xf2,0xfe,0xfb,0xba,0x4c,0x9b,0x7e
|
||||
};
|
||||
0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 0x13, 0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e};
|
||||
|
||||
static std::unique_ptr<IVolume> CreateVolumeFromCryptedWiiImage(std::unique_ptr<IBlobReader> reader, u32 partition_group, u32 volume_type, u32 volume_number);
|
||||
static std::unique_ptr<IVolume> CreateVolumeFromCryptedWiiImage(std::unique_ptr<IBlobReader> reader,
|
||||
u32 partition_group,
|
||||
u32 volume_type, u32 volume_number);
|
||||
EDiscType GetDiscType(IBlobReader& _rReader);
|
||||
|
||||
std::unique_ptr<IVolume> CreateVolumeFromFilename(const std::string& filename, u32 partition_group, u32 volume_number)
|
||||
std::unique_ptr<IVolume> CreateVolumeFromFilename(const std::string& filename, u32 partition_group,
|
||||
u32 volume_number)
|
||||
{
|
||||
std::unique_ptr<IBlobReader> reader(CreateBlobReader(filename));
|
||||
if (reader == nullptr)
|
||||
return nullptr;
|
||||
std::unique_ptr<IBlobReader> reader(CreateBlobReader(filename));
|
||||
if (reader == nullptr)
|
||||
return nullptr;
|
||||
|
||||
switch (GetDiscType(*reader))
|
||||
{
|
||||
case DISC_TYPE_WII:
|
||||
case DISC_TYPE_GC:
|
||||
return std::make_unique<CVolumeGC>(std::move(reader));
|
||||
switch (GetDiscType(*reader))
|
||||
{
|
||||
case DISC_TYPE_WII:
|
||||
case DISC_TYPE_GC:
|
||||
return std::make_unique<CVolumeGC>(std::move(reader));
|
||||
|
||||
case DISC_TYPE_WAD:
|
||||
return std::make_unique<CVolumeWAD>(std::move(reader));
|
||||
case DISC_TYPE_WAD:
|
||||
return std::make_unique<CVolumeWAD>(std::move(reader));
|
||||
|
||||
case DISC_TYPE_WII_CONTAINER:
|
||||
return CreateVolumeFromCryptedWiiImage(std::move(reader), partition_group, 0, volume_number);
|
||||
case DISC_TYPE_WII_CONTAINER:
|
||||
return CreateVolumeFromCryptedWiiImage(std::move(reader), partition_group, 0, volume_number);
|
||||
|
||||
case DISC_TYPE_UNK:
|
||||
default:
|
||||
std::string name, extension;
|
||||
SplitPath(filename, nullptr, &name, &extension);
|
||||
name += extension;
|
||||
NOTICE_LOG(DISCIO, "%s does not have the Magic word for a gcm, wiidisc or wad file\n"
|
||||
"Set Log Verbosity to Warning and attempt to load the game again to view the values", name.c_str());
|
||||
}
|
||||
case DISC_TYPE_UNK:
|
||||
default:
|
||||
std::string name, extension;
|
||||
SplitPath(filename, nullptr, &name, &extension);
|
||||
name += extension;
|
||||
NOTICE_LOG(DISCIO,
|
||||
"%s does not have the Magic word for a gcm, wiidisc or wad file\n"
|
||||
"Set Log Verbosity to Warning and attempt to load the game again to view the values",
|
||||
name.c_str());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<IVolume> CreateVolumeFromDirectory(const std::string& directory, bool is_wii, const std::string& apploader, const std::string& dol)
|
||||
std::unique_ptr<IVolume> CreateVolumeFromDirectory(const std::string& directory, bool is_wii,
|
||||
const std::string& apploader,
|
||||
const std::string& dol)
|
||||
{
|
||||
if (CVolumeDirectory::IsValidDirectory(directory))
|
||||
return std::make_unique<CVolumeDirectory>(directory, is_wii, apploader, dol);
|
||||
if (CVolumeDirectory::IsValidDirectory(directory))
|
||||
return std::make_unique<CVolumeDirectory>(directory, is_wii, apploader, dol);
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VolumeKeyForPartition(IBlobReader& _rReader, u64 offset, u8* VolumeKey)
|
||||
{
|
||||
u8 SubKey[16];
|
||||
_rReader.Read(offset + 0x1bf, 16, SubKey);
|
||||
u8 SubKey[16];
|
||||
_rReader.Read(offset + 0x1bf, 16, SubKey);
|
||||
|
||||
u8 IV[16];
|
||||
memset(IV, 0, 16);
|
||||
_rReader.Read(offset + 0x44c, 8, IV);
|
||||
u8 IV[16];
|
||||
memset(IV, 0, 16);
|
||||
_rReader.Read(offset + 0x44c, 8, IV);
|
||||
|
||||
// Issue: 6813
|
||||
// Magic value is at partition's offset + 0x1f1 (1byte)
|
||||
// If encrypted with the Korean key, the magic value would be 1
|
||||
// Otherwise it is zero
|
||||
u8 using_korean_key = 0;
|
||||
_rReader.Read(offset + 0x1f1, sizeof(u8), &using_korean_key);
|
||||
u8 region = 0;
|
||||
_rReader.Read(0x3, sizeof(u8), ®ion);
|
||||
// Issue: 6813
|
||||
// Magic value is at partition's offset + 0x1f1 (1byte)
|
||||
// If encrypted with the Korean key, the magic value would be 1
|
||||
// Otherwise it is zero
|
||||
u8 using_korean_key = 0;
|
||||
_rReader.Read(offset + 0x1f1, sizeof(u8), &using_korean_key);
|
||||
u8 region = 0;
|
||||
_rReader.Read(0x3, sizeof(u8), ®ion);
|
||||
|
||||
mbedtls_aes_context AES_ctx;
|
||||
mbedtls_aes_setkey_dec(&AES_ctx, (using_korean_key == 1 && region == 'K' ? s_master_key_korean : s_master_key), 128);
|
||||
mbedtls_aes_context AES_ctx;
|
||||
mbedtls_aes_setkey_dec(
|
||||
&AES_ctx, (using_korean_key == 1 && region == 'K' ? s_master_key_korean : s_master_key), 128);
|
||||
|
||||
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, 16, IV, SubKey, VolumeKey);
|
||||
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, 16, IV, SubKey, VolumeKey);
|
||||
}
|
||||
|
||||
static std::unique_ptr<IVolume> CreateVolumeFromCryptedWiiImage(std::unique_ptr<IBlobReader> reader, u32 partition_group, u32 volume_type, u32 volume_number)
|
||||
static std::unique_ptr<IVolume> CreateVolumeFromCryptedWiiImage(std::unique_ptr<IBlobReader> reader,
|
||||
u32 partition_group,
|
||||
u32 volume_type, u32 volume_number)
|
||||
{
|
||||
CBlobBigEndianReader big_endian_reader(*reader);
|
||||
CBlobBigEndianReader big_endian_reader(*reader);
|
||||
|
||||
u32 num_partitions;
|
||||
if (!big_endian_reader.ReadSwapped(0x40000 + (partition_group * 8), &num_partitions))
|
||||
return nullptr;
|
||||
u32 num_partitions;
|
||||
if (!big_endian_reader.ReadSwapped(0x40000 + (partition_group * 8), &num_partitions))
|
||||
return nullptr;
|
||||
|
||||
// Check if we're looking for a valid partition
|
||||
if ((int)volume_number != -1 && volume_number > num_partitions)
|
||||
return nullptr;
|
||||
// Check if we're looking for a valid partition
|
||||
if ((int)volume_number != -1 && volume_number > num_partitions)
|
||||
return nullptr;
|
||||
|
||||
u32 partitions_offset_unshifted;
|
||||
if (!big_endian_reader.ReadSwapped(0x40000 + (partition_group * 8) + 4, &partitions_offset_unshifted))
|
||||
return nullptr;
|
||||
u64 partitions_offset = (u64)partitions_offset_unshifted << 2;
|
||||
u32 partitions_offset_unshifted;
|
||||
if (!big_endian_reader.ReadSwapped(0x40000 + (partition_group * 8) + 4,
|
||||
&partitions_offset_unshifted))
|
||||
return nullptr;
|
||||
u64 partitions_offset = (u64)partitions_offset_unshifted << 2;
|
||||
|
||||
struct SPartition
|
||||
{
|
||||
SPartition(u64 offset_, u32 type_) : offset(offset_), type(type_) {}
|
||||
struct SPartition
|
||||
{
|
||||
SPartition(u64 offset_, u32 type_) : offset(offset_), type(type_) {}
|
||||
u64 offset;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
u64 offset;
|
||||
u32 type;
|
||||
};
|
||||
struct SPartitionGroup
|
||||
{
|
||||
u32 num_partitions;
|
||||
u64 partitions_offset;
|
||||
std::vector<SPartition> partitions;
|
||||
};
|
||||
SPartitionGroup partition_groups[4];
|
||||
|
||||
struct SPartitionGroup
|
||||
{
|
||||
u32 num_partitions;
|
||||
u64 partitions_offset;
|
||||
std::vector<SPartition> partitions;
|
||||
};
|
||||
SPartitionGroup partition_groups[4];
|
||||
// Read all partitions
|
||||
for (SPartitionGroup& group : partition_groups)
|
||||
{
|
||||
for (u32 i = 0; i < num_partitions; i++)
|
||||
{
|
||||
u32 partition_offset, partition_type;
|
||||
if (big_endian_reader.ReadSwapped(partitions_offset + (i * 8) + 0, &partition_offset) &&
|
||||
big_endian_reader.ReadSwapped(partitions_offset + (i * 8) + 4, &partition_type))
|
||||
{
|
||||
group.partitions.emplace_back((u64)partition_offset << 2, partition_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read all partitions
|
||||
for (SPartitionGroup& group : partition_groups)
|
||||
{
|
||||
for (u32 i = 0; i < num_partitions; i++)
|
||||
{
|
||||
u32 partition_offset, partition_type;
|
||||
if (big_endian_reader.ReadSwapped(partitions_offset + (i * 8) + 0, &partition_offset) &&
|
||||
big_endian_reader.ReadSwapped(partitions_offset + (i * 8) + 4, &partition_type))
|
||||
{
|
||||
group.partitions.emplace_back((u64)partition_offset << 2, partition_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return the partition type specified or number
|
||||
// types: 0 = game, 1 = firmware update, 2 = channel installer
|
||||
// some partitions on SSBB use the ASCII title id of the demo VC game they hold...
|
||||
for (size_t i = 0; i < partition_groups[partition_group].partitions.size(); i++)
|
||||
{
|
||||
const SPartition& partition = partition_groups[partition_group].partitions.at(i);
|
||||
|
||||
// Return the partition type specified or number
|
||||
// types: 0 = game, 1 = firmware update, 2 = channel installer
|
||||
// some partitions on SSBB use the ASCII title id of the demo VC game they hold...
|
||||
for (size_t i = 0; i < partition_groups[partition_group].partitions.size(); i++)
|
||||
{
|
||||
const SPartition& partition = partition_groups[partition_group].partitions.at(i);
|
||||
if ((partition.type == volume_type && (int)volume_number == -1) || i == volume_number)
|
||||
{
|
||||
u8 volume_key[16];
|
||||
VolumeKeyForPartition(*reader, partition.offset, volume_key);
|
||||
return std::make_unique<CVolumeWiiCrypted>(std::move(reader), partition.offset, volume_key);
|
||||
}
|
||||
}
|
||||
|
||||
if ((partition.type == volume_type && (int)volume_number == -1) || i == volume_number)
|
||||
{
|
||||
u8 volume_key[16];
|
||||
VolumeKeyForPartition(*reader, partition.offset, volume_key);
|
||||
return std::make_unique<CVolumeWiiCrypted>(std::move(reader), partition.offset, volume_key);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EDiscType GetDiscType(IBlobReader& _rReader)
|
||||
{
|
||||
CBlobBigEndianReader Reader(_rReader);
|
||||
CBlobBigEndianReader Reader(_rReader);
|
||||
|
||||
// Check for Wii
|
||||
u32 WiiMagic = 0;
|
||||
Reader.ReadSwapped(0x18, &WiiMagic);
|
||||
u32 WiiContainerMagic = 0;
|
||||
Reader.ReadSwapped(0x60, &WiiContainerMagic);
|
||||
if (WiiMagic == 0x5D1C9EA3 && WiiContainerMagic != 0)
|
||||
return DISC_TYPE_WII;
|
||||
if (WiiMagic == 0x5D1C9EA3 && WiiContainerMagic == 0)
|
||||
return DISC_TYPE_WII_CONTAINER;
|
||||
// Check for Wii
|
||||
u32 WiiMagic = 0;
|
||||
Reader.ReadSwapped(0x18, &WiiMagic);
|
||||
u32 WiiContainerMagic = 0;
|
||||
Reader.ReadSwapped(0x60, &WiiContainerMagic);
|
||||
if (WiiMagic == 0x5D1C9EA3 && WiiContainerMagic != 0)
|
||||
return DISC_TYPE_WII;
|
||||
if (WiiMagic == 0x5D1C9EA3 && WiiContainerMagic == 0)
|
||||
return DISC_TYPE_WII_CONTAINER;
|
||||
|
||||
// Check for WAD
|
||||
// 0x206962 for boot2 wads
|
||||
u32 WADMagic = 0;
|
||||
Reader.ReadSwapped(0x02, &WADMagic);
|
||||
if (WADMagic == 0x00204973 || WADMagic == 0x00206962)
|
||||
return DISC_TYPE_WAD;
|
||||
// Check for WAD
|
||||
// 0x206962 for boot2 wads
|
||||
u32 WADMagic = 0;
|
||||
Reader.ReadSwapped(0x02, &WADMagic);
|
||||
if (WADMagic == 0x00204973 || WADMagic == 0x00206962)
|
||||
return DISC_TYPE_WAD;
|
||||
|
||||
// Check for GC
|
||||
u32 GCMagic = 0;
|
||||
Reader.ReadSwapped(0x1C, &GCMagic);
|
||||
if (GCMagic == 0xC2339F3D)
|
||||
return DISC_TYPE_GC;
|
||||
// Check for GC
|
||||
u32 GCMagic = 0;
|
||||
Reader.ReadSwapped(0x1C, &GCMagic);
|
||||
if (GCMagic == 0xC2339F3D)
|
||||
return DISC_TYPE_GC;
|
||||
|
||||
WARN_LOG(DISCIO, "No known magic words found");
|
||||
WARN_LOG(DISCIO, "Wii offset: 0x18 value: 0x%08x", WiiMagic);
|
||||
WARN_LOG(DISCIO, "WiiC offset: 0x60 value: 0x%08x", WiiContainerMagic);
|
||||
WARN_LOG(DISCIO, "WAD offset: 0x02 value: 0x%08x", WADMagic);
|
||||
WARN_LOG(DISCIO, "GC offset: 0x1C value: 0x%08x", GCMagic);
|
||||
WARN_LOG(DISCIO, "No known magic words found");
|
||||
WARN_LOG(DISCIO, "Wii offset: 0x18 value: 0x%08x", WiiMagic);
|
||||
WARN_LOG(DISCIO, "WiiC offset: 0x60 value: 0x%08x", WiiContainerMagic);
|
||||
WARN_LOG(DISCIO, "WAD offset: 0x02 value: 0x%08x", WADMagic);
|
||||
WARN_LOG(DISCIO, "GC offset: 0x1C value: 0x%08x", GCMagic);
|
||||
|
||||
return DISC_TYPE_UNK;
|
||||
return DISC_TYPE_UNK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class IVolume;
|
||||
class IBlobReader;
|
||||
|
||||
std::unique_ptr<IVolume> CreateVolumeFromFilename(const std::string& filename, u32 partition_group = 0, u32 volume_number = -1);
|
||||
std::unique_ptr<IVolume> CreateVolumeFromDirectory(const std::string& directory, bool is_wii, const std::string& apploader = "", const std::string& dol = "");
|
||||
std::unique_ptr<IVolume> CreateVolumeFromFilename(const std::string& filename,
|
||||
u32 partition_group = 0, u32 volume_number = -1);
|
||||
std::unique_ptr<IVolume> CreateVolumeFromDirectory(const std::string& directory, bool is_wii,
|
||||
const std::string& apploader = "",
|
||||
const std::string& dol = "");
|
||||
void VolumeKeyForPartition(IBlobReader& _rReader, u64 offset, u8* VolumeKey);
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -14,8 +14,8 @@
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/FileMonitor.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
@ -23,35 +23,30 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
const size_t CVolumeDirectory::MAX_NAME_LENGTH;
|
||||
const size_t CVolumeDirectory::MAX_ID_LENGTH;
|
||||
|
||||
CVolumeDirectory::CVolumeDirectory(const std::string& _rDirectory, bool _bIsWii,
|
||||
const std::string& _rApploader, const std::string& _rDOL)
|
||||
: m_totalNameSize(0)
|
||||
, m_dataStartAddress(-1)
|
||||
, m_diskHeader(DISKHEADERINFO_ADDRESS)
|
||||
, m_diskHeaderInfo(std::make_unique<SDiskHeaderInfo>())
|
||||
, m_fst_address(0)
|
||||
, m_dol_address(0)
|
||||
const std::string& _rApploader, const std::string& _rDOL)
|
||||
: m_totalNameSize(0), m_dataStartAddress(-1), m_diskHeader(DISKHEADERINFO_ADDRESS),
|
||||
m_diskHeaderInfo(std::make_unique<SDiskHeaderInfo>()), m_fst_address(0), m_dol_address(0)
|
||||
{
|
||||
m_rootDirectory = ExtractDirectoryName(_rDirectory);
|
||||
m_rootDirectory = ExtractDirectoryName(_rDirectory);
|
||||
|
||||
// create the default disk header
|
||||
SetUniqueID("AGBJ01");
|
||||
SetName("Default name");
|
||||
// create the default disk header
|
||||
SetUniqueID("AGBJ01");
|
||||
SetName("Default name");
|
||||
|
||||
if (_bIsWii)
|
||||
SetDiskTypeWii();
|
||||
else
|
||||
SetDiskTypeGC();
|
||||
if (_bIsWii)
|
||||
SetDiskTypeWii();
|
||||
else
|
||||
SetDiskTypeGC();
|
||||
|
||||
// Don't load the DOL if we don't have an apploader
|
||||
if (SetApploader(_rApploader))
|
||||
SetDOL(_rDOL);
|
||||
// Don't load the DOL if we don't have an apploader
|
||||
if (SetApploader(_rApploader))
|
||||
SetDOL(_rDOL);
|
||||
|
||||
BuildFST();
|
||||
BuildFST();
|
||||
}
|
||||
|
||||
CVolumeDirectory::~CVolumeDirectory()
|
||||
@ -60,437 +55,446 @@ CVolumeDirectory::~CVolumeDirectory()
|
||||
|
||||
bool CVolumeDirectory::IsValidDirectory(const std::string& _rDirectory)
|
||||
{
|
||||
return File::IsDirectory(ExtractDirectoryName(_rDirectory));
|
||||
return File::IsDirectory(ExtractDirectoryName(_rDirectory));
|
||||
}
|
||||
|
||||
bool CVolumeDirectory::Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const
|
||||
{
|
||||
if (!decrypt && (_Offset + _Length >= 0x400) && m_is_wii)
|
||||
{
|
||||
// Fully supporting this would require re-encrypting every file that's read.
|
||||
// Only supporting the areas that IOS allows software to read could be more feasible.
|
||||
// Currently, only the header (up to 0x400) is supported, though we're cheating a bit
|
||||
// with it by reading the header inside the current partition instead. Supporting the
|
||||
// header is enough for booting games, but not for running things like the Disc Channel.
|
||||
return false;
|
||||
}
|
||||
if (!decrypt && (_Offset + _Length >= 0x400) && m_is_wii)
|
||||
{
|
||||
// Fully supporting this would require re-encrypting every file that's read.
|
||||
// Only supporting the areas that IOS allows software to read could be more feasible.
|
||||
// Currently, only the header (up to 0x400) is supported, though we're cheating a bit
|
||||
// with it by reading the header inside the current partition instead. Supporting the
|
||||
// header is enough for booting games, but not for running things like the Disc Channel.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decrypt && !m_is_wii)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
if (decrypt && !m_is_wii)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
|
||||
// header
|
||||
if (_Offset < DISKHEADERINFO_ADDRESS)
|
||||
{
|
||||
WriteToBuffer(DISKHEADER_ADDRESS, DISKHEADERINFO_ADDRESS, m_diskHeader.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// header info
|
||||
if (_Offset >= DISKHEADERINFO_ADDRESS && _Offset < APPLOADER_ADDRESS)
|
||||
{
|
||||
WriteToBuffer(DISKHEADERINFO_ADDRESS, sizeof(m_diskHeaderInfo), (u8*)m_diskHeaderInfo.get(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// apploader
|
||||
if (_Offset >= APPLOADER_ADDRESS && _Offset < APPLOADER_ADDRESS + m_apploader.size())
|
||||
{
|
||||
WriteToBuffer(APPLOADER_ADDRESS, m_apploader.size(), m_apploader.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// dol
|
||||
if (_Offset >= m_dol_address && _Offset < m_dol_address + m_DOL.size())
|
||||
{
|
||||
WriteToBuffer(m_dol_address, m_DOL.size(), m_DOL.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// fst
|
||||
if (_Offset >= m_fst_address && _Offset < m_dataStartAddress)
|
||||
{
|
||||
WriteToBuffer(m_fst_address, m_FSTData.size(), m_FSTData.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// header
|
||||
if (_Offset < DISKHEADERINFO_ADDRESS)
|
||||
{
|
||||
WriteToBuffer(DISKHEADER_ADDRESS, DISKHEADERINFO_ADDRESS, m_diskHeader.data(), _Offset, _Length,
|
||||
_pBuffer);
|
||||
}
|
||||
// header info
|
||||
if (_Offset >= DISKHEADERINFO_ADDRESS && _Offset < APPLOADER_ADDRESS)
|
||||
{
|
||||
WriteToBuffer(DISKHEADERINFO_ADDRESS, sizeof(m_diskHeaderInfo), (u8*)m_diskHeaderInfo.get(),
|
||||
_Offset, _Length, _pBuffer);
|
||||
}
|
||||
// apploader
|
||||
if (_Offset >= APPLOADER_ADDRESS && _Offset < APPLOADER_ADDRESS + m_apploader.size())
|
||||
{
|
||||
WriteToBuffer(APPLOADER_ADDRESS, m_apploader.size(), m_apploader.data(), _Offset, _Length,
|
||||
_pBuffer);
|
||||
}
|
||||
// dol
|
||||
if (_Offset >= m_dol_address && _Offset < m_dol_address + m_DOL.size())
|
||||
{
|
||||
WriteToBuffer(m_dol_address, m_DOL.size(), m_DOL.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
// fst
|
||||
if (_Offset >= m_fst_address && _Offset < m_dataStartAddress)
|
||||
{
|
||||
WriteToBuffer(m_fst_address, m_FSTData.size(), m_FSTData.data(), _Offset, _Length, _pBuffer);
|
||||
}
|
||||
|
||||
if (m_virtualDisk.empty())
|
||||
return true;
|
||||
if (m_virtualDisk.empty())
|
||||
return true;
|
||||
|
||||
// Determine which file the offset refers to
|
||||
std::map<u64, std::string>::const_iterator fileIter = m_virtualDisk.lower_bound(_Offset);
|
||||
if (fileIter->first > _Offset && fileIter != m_virtualDisk.begin())
|
||||
--fileIter;
|
||||
// Determine which file the offset refers to
|
||||
std::map<u64, std::string>::const_iterator fileIter = m_virtualDisk.lower_bound(_Offset);
|
||||
if (fileIter->first > _Offset && fileIter != m_virtualDisk.begin())
|
||||
--fileIter;
|
||||
|
||||
// zero fill to start of file data
|
||||
PadToAddress(fileIter->first, _Offset, _Length, _pBuffer);
|
||||
// zero fill to start of file data
|
||||
PadToAddress(fileIter->first, _Offset, _Length, _pBuffer);
|
||||
|
||||
while (fileIter != m_virtualDisk.end() && _Length > 0)
|
||||
{
|
||||
_dbg_assert_(DVDINTERFACE, fileIter->first <= _Offset);
|
||||
u64 fileOffset = _Offset - fileIter->first;
|
||||
const std::string fileName = fileIter->second;
|
||||
while (fileIter != m_virtualDisk.end() && _Length > 0)
|
||||
{
|
||||
_dbg_assert_(DVDINTERFACE, fileIter->first <= _Offset);
|
||||
u64 fileOffset = _Offset - fileIter->first;
|
||||
const std::string fileName = fileIter->second;
|
||||
|
||||
File::IOFile file(fileName, "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
File::IOFile file(fileName, "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
u64 fileSize = file.GetSize();
|
||||
u64 fileSize = file.GetSize();
|
||||
|
||||
FileMon::CheckFile(fileName, fileSize);
|
||||
FileMon::CheckFile(fileName, fileSize);
|
||||
|
||||
if (fileOffset < fileSize)
|
||||
{
|
||||
u64 fileBytes = std::min(fileSize - fileOffset, _Length);
|
||||
if (fileOffset < fileSize)
|
||||
{
|
||||
u64 fileBytes = std::min(fileSize - fileOffset, _Length);
|
||||
|
||||
if (!file.Seek(fileOffset, SEEK_SET))
|
||||
return false;
|
||||
if (!file.ReadBytes(_pBuffer, fileBytes))
|
||||
return false;
|
||||
if (!file.Seek(fileOffset, SEEK_SET))
|
||||
return false;
|
||||
if (!file.ReadBytes(_pBuffer, fileBytes))
|
||||
return false;
|
||||
|
||||
_Length -= fileBytes;
|
||||
_pBuffer += fileBytes;
|
||||
_Offset += fileBytes;
|
||||
}
|
||||
_Length -= fileBytes;
|
||||
_pBuffer += fileBytes;
|
||||
_Offset += fileBytes;
|
||||
}
|
||||
|
||||
++fileIter;
|
||||
++fileIter;
|
||||
|
||||
if (fileIter != m_virtualDisk.end())
|
||||
{
|
||||
_dbg_assert_(DVDINTERFACE, fileIter->first >= _Offset);
|
||||
PadToAddress(fileIter->first, _Offset, _Length, _pBuffer);
|
||||
}
|
||||
}
|
||||
if (fileIter != m_virtualDisk.end())
|
||||
{
|
||||
_dbg_assert_(DVDINTERFACE, fileIter->first >= _Offset);
|
||||
PadToAddress(fileIter->first, _Offset, _Length, _pBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CVolumeDirectory::GetUniqueID() const
|
||||
{
|
||||
return std::string(m_diskHeader.begin(), m_diskHeader.begin() + MAX_ID_LENGTH);
|
||||
return std::string(m_diskHeader.begin(), m_diskHeader.begin() + MAX_ID_LENGTH);
|
||||
}
|
||||
|
||||
void CVolumeDirectory::SetUniqueID(const std::string& id)
|
||||
{
|
||||
memcpy(m_diskHeader.data(), id.c_str(), std::min(id.length(), MAX_ID_LENGTH));
|
||||
memcpy(m_diskHeader.data(), id.c_str(), std::min(id.length(), MAX_ID_LENGTH));
|
||||
}
|
||||
|
||||
IVolume::ECountry CVolumeDirectory::GetCountry() const
|
||||
{
|
||||
return CountrySwitch(m_diskHeader[3]);
|
||||
return CountrySwitch(m_diskHeader[3]);
|
||||
}
|
||||
|
||||
std::string CVolumeDirectory::GetMakerID() const
|
||||
{
|
||||
// Not implemented
|
||||
return "00";
|
||||
// Not implemented
|
||||
return "00";
|
||||
}
|
||||
|
||||
std::string CVolumeDirectory::GetInternalName() const
|
||||
{
|
||||
char name[0x60];
|
||||
if (Read(0x20, 0x60, (u8*)name, false))
|
||||
return DecodeString(name);
|
||||
else
|
||||
return "";
|
||||
char name[0x60];
|
||||
if (Read(0x20, 0x60, (u8*)name, false))
|
||||
return DecodeString(name);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeDirectory::GetNames(bool prefer_long) const
|
||||
{
|
||||
std::string name = GetInternalName();
|
||||
if (name.empty())
|
||||
return { { } };
|
||||
return { { IVolume::LANGUAGE_UNKNOWN, name } };
|
||||
std::string name = GetInternalName();
|
||||
if (name.empty())
|
||||
return {{}};
|
||||
return {{IVolume::LANGUAGE_UNKNOWN, name}};
|
||||
}
|
||||
|
||||
std::vector<u32> CVolumeDirectory::GetBanner(int* width, int* height) const
|
||||
{
|
||||
// Not implemented
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
return std::vector<u32>();
|
||||
// Not implemented
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
return std::vector<u32>();
|
||||
}
|
||||
|
||||
void CVolumeDirectory::SetName(const std::string& name)
|
||||
{
|
||||
size_t length = std::min(name.length(), MAX_NAME_LENGTH);
|
||||
memcpy(&m_diskHeader[0x20], name.c_str(), length);
|
||||
m_diskHeader[length + 0x20] = 0;
|
||||
size_t length = std::min(name.length(), MAX_NAME_LENGTH);
|
||||
memcpy(&m_diskHeader[0x20], name.c_str(), length);
|
||||
m_diskHeader[length + 0x20] = 0;
|
||||
}
|
||||
|
||||
u64 CVolumeDirectory::GetFSTSize() const
|
||||
{
|
||||
// Not implemented
|
||||
return 0;
|
||||
// Not implemented
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string CVolumeDirectory::GetApploaderDate() const
|
||||
{
|
||||
// Not implemented
|
||||
return "VOID";
|
||||
// Not implemented
|
||||
return "VOID";
|
||||
}
|
||||
|
||||
IVolume::EPlatform CVolumeDirectory::GetVolumeType() const
|
||||
{
|
||||
return m_is_wii ? WII_DISC : GAMECUBE_DISC;
|
||||
return m_is_wii ? WII_DISC : GAMECUBE_DISC;
|
||||
}
|
||||
|
||||
BlobType CVolumeDirectory::GetBlobType() const
|
||||
{
|
||||
// VolumeDirectory isn't actually a blob, but it sort of acts
|
||||
// like one, so it makes sense that it has its own blob type.
|
||||
// It should be made into a proper blob in the future.
|
||||
return BlobType::DIRECTORY;
|
||||
// VolumeDirectory isn't actually a blob, but it sort of acts
|
||||
// like one, so it makes sense that it has its own blob type.
|
||||
// It should be made into a proper blob in the future.
|
||||
return BlobType::DIRECTORY;
|
||||
}
|
||||
|
||||
u64 CVolumeDirectory::GetSize() const
|
||||
{
|
||||
// Not implemented
|
||||
return 0;
|
||||
// Not implemented
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CVolumeDirectory::GetRawSize() const
|
||||
{
|
||||
// Not implemented
|
||||
return 0;
|
||||
// Not implemented
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string CVolumeDirectory::ExtractDirectoryName(const std::string& _rDirectory)
|
||||
{
|
||||
std::string directoryName = _rDirectory;
|
||||
std::string directoryName = _rDirectory;
|
||||
|
||||
size_t lastSep = directoryName.find_last_of(DIR_SEP_CHR);
|
||||
size_t lastSep = directoryName.find_last_of(DIR_SEP_CHR);
|
||||
|
||||
if (lastSep != directoryName.size() - 1)
|
||||
{
|
||||
// TODO: This assumes that file names will always have a dot in them
|
||||
// and directory names never will; both assumptions are often
|
||||
// right but in general wrong.
|
||||
size_t extensionStart = directoryName.find_last_of('.');
|
||||
if (extensionStart != std::string::npos && extensionStart > lastSep)
|
||||
{
|
||||
directoryName.resize(lastSep);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directoryName.resize(lastSep);
|
||||
}
|
||||
if (lastSep != directoryName.size() - 1)
|
||||
{
|
||||
// TODO: This assumes that file names will always have a dot in them
|
||||
// and directory names never will; both assumptions are often
|
||||
// right but in general wrong.
|
||||
size_t extensionStart = directoryName.find_last_of('.');
|
||||
if (extensionStart != std::string::npos && extensionStart > lastSep)
|
||||
{
|
||||
directoryName.resize(lastSep);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directoryName.resize(lastSep);
|
||||
}
|
||||
|
||||
return directoryName;
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
void CVolumeDirectory::SetDiskTypeWii()
|
||||
{
|
||||
Write32(0x5d1c9ea3, 0x18, &m_diskHeader);
|
||||
memset(&m_diskHeader[0x1c], 0, 4);
|
||||
Write32(0x5d1c9ea3, 0x18, &m_diskHeader);
|
||||
memset(&m_diskHeader[0x1c], 0, 4);
|
||||
|
||||
m_is_wii = true;
|
||||
m_addressShift = 2;
|
||||
m_is_wii = true;
|
||||
m_addressShift = 2;
|
||||
}
|
||||
|
||||
void CVolumeDirectory::SetDiskTypeGC()
|
||||
{
|
||||
memset(&m_diskHeader[0x18], 0, 4);
|
||||
Write32(0xc2339f3d, 0x1c, &m_diskHeader);
|
||||
memset(&m_diskHeader[0x18], 0, 4);
|
||||
Write32(0xc2339f3d, 0x1c, &m_diskHeader);
|
||||
|
||||
m_is_wii = false;
|
||||
m_addressShift = 0;
|
||||
m_is_wii = false;
|
||||
m_addressShift = 0;
|
||||
}
|
||||
|
||||
bool CVolumeDirectory::SetApploader(const std::string& _rApploader)
|
||||
{
|
||||
if (!_rApploader.empty())
|
||||
{
|
||||
std::string data;
|
||||
if (!File::ReadFileToString(_rApploader, data))
|
||||
{
|
||||
PanicAlertT("Apploader unable to load from file");
|
||||
return false;
|
||||
}
|
||||
size_t apploaderSize = 0x20 + Common::swap32(*(u32*)&data.data()[0x14]) + Common::swap32(*(u32*)&data.data()[0x18]);
|
||||
if (apploaderSize != data.size())
|
||||
{
|
||||
PanicAlertT("Apploader is the wrong size...is it really an apploader?");
|
||||
return false;
|
||||
}
|
||||
m_apploader.resize(apploaderSize);
|
||||
std::copy(data.begin(), data.end(), m_apploader.begin());
|
||||
if (!_rApploader.empty())
|
||||
{
|
||||
std::string data;
|
||||
if (!File::ReadFileToString(_rApploader, data))
|
||||
{
|
||||
PanicAlertT("Apploader unable to load from file");
|
||||
return false;
|
||||
}
|
||||
size_t apploaderSize = 0x20 + Common::swap32(*(u32*)&data.data()[0x14]) +
|
||||
Common::swap32(*(u32*)&data.data()[0x18]);
|
||||
if (apploaderSize != data.size())
|
||||
{
|
||||
PanicAlertT("Apploader is the wrong size...is it really an apploader?");
|
||||
return false;
|
||||
}
|
||||
m_apploader.resize(apploaderSize);
|
||||
std::copy(data.begin(), data.end(), m_apploader.begin());
|
||||
|
||||
// 32byte aligned (plus 0x20 padding)
|
||||
m_dol_address = ROUND_UP(APPLOADER_ADDRESS + m_apploader.size() + 0x20, 0x20ull);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_apploader.resize(0x20);
|
||||
// Make sure BS2 HLE doesn't try to run the apploader
|
||||
*(u32*)&m_apploader[0x10] = (u32)-1;
|
||||
return false;
|
||||
}
|
||||
// 32byte aligned (plus 0x20 padding)
|
||||
m_dol_address = ROUND_UP(APPLOADER_ADDRESS + m_apploader.size() + 0x20, 0x20ull);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_apploader.resize(0x20);
|
||||
// Make sure BS2 HLE doesn't try to run the apploader
|
||||
*(u32*)&m_apploader[0x10] = (u32)-1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CVolumeDirectory::SetDOL(const std::string& rDOL)
|
||||
{
|
||||
if (!rDOL.empty())
|
||||
{
|
||||
std::string data;
|
||||
File::ReadFileToString(rDOL, data);
|
||||
m_DOL.resize(data.size());
|
||||
std::copy(data.begin(), data.end(), m_DOL.begin());
|
||||
if (!rDOL.empty())
|
||||
{
|
||||
std::string data;
|
||||
File::ReadFileToString(rDOL, data);
|
||||
m_DOL.resize(data.size());
|
||||
std::copy(data.begin(), data.end(), m_DOL.begin());
|
||||
|
||||
Write32((u32)(m_dol_address >> m_addressShift), 0x0420, &m_diskHeader);
|
||||
Write32((u32)(m_dol_address >> m_addressShift), 0x0420, &m_diskHeader);
|
||||
|
||||
// 32byte aligned (plus 0x20 padding)
|
||||
m_fst_address = ROUND_UP(m_dol_address + m_DOL.size() + 0x20, 0x20ull);
|
||||
}
|
||||
// 32byte aligned (plus 0x20 padding)
|
||||
m_fst_address = ROUND_UP(m_dol_address + m_DOL.size() + 0x20, 0x20ull);
|
||||
}
|
||||
}
|
||||
|
||||
void CVolumeDirectory::BuildFST()
|
||||
{
|
||||
m_FSTData.clear();
|
||||
m_FSTData.clear();
|
||||
|
||||
File::FSTEntry rootEntry;
|
||||
File::FSTEntry rootEntry;
|
||||
|
||||
// read data from physical disk to rootEntry
|
||||
u64 totalEntries = AddDirectoryEntries(m_rootDirectory, rootEntry) + 1;
|
||||
// read data from physical disk to rootEntry
|
||||
u64 totalEntries = AddDirectoryEntries(m_rootDirectory, rootEntry) + 1;
|
||||
|
||||
m_fstNameOffset = totalEntries * ENTRY_SIZE; // offset in FST nameTable
|
||||
m_FSTData.resize(m_fstNameOffset + m_totalNameSize);
|
||||
m_fstNameOffset = totalEntries * ENTRY_SIZE; // offset in FST nameTable
|
||||
m_FSTData.resize(m_fstNameOffset + m_totalNameSize);
|
||||
|
||||
// if FST hasn't been assigned (ie no apploader/dol setup), set to default
|
||||
if (m_fst_address == 0)
|
||||
m_fst_address = APPLOADER_ADDRESS + 0x2000;
|
||||
// if FST hasn't been assigned (ie no apploader/dol setup), set to default
|
||||
if (m_fst_address == 0)
|
||||
m_fst_address = APPLOADER_ADDRESS + 0x2000;
|
||||
|
||||
// 4 byte aligned start of data on disk
|
||||
m_dataStartAddress = ROUND_UP(m_fst_address + m_FSTData.size(), 0x8000ull);
|
||||
u64 curDataAddress = m_dataStartAddress;
|
||||
// 4 byte aligned start of data on disk
|
||||
m_dataStartAddress = ROUND_UP(m_fst_address + m_FSTData.size(), 0x8000ull);
|
||||
u64 curDataAddress = m_dataStartAddress;
|
||||
|
||||
u32 fstOffset = 0; // Offset within FST data
|
||||
u32 nameOffset = 0; // Offset within name table
|
||||
u32 rootOffset = 0; // Offset of root of FST
|
||||
u32 fstOffset = 0; // Offset within FST data
|
||||
u32 nameOffset = 0; // Offset within name table
|
||||
u32 rootOffset = 0; // Offset of root of FST
|
||||
|
||||
// write root entry
|
||||
WriteEntryData(fstOffset, DIRECTORY_ENTRY, 0, 0, totalEntries);
|
||||
// write root entry
|
||||
WriteEntryData(fstOffset, DIRECTORY_ENTRY, 0, 0, totalEntries);
|
||||
|
||||
for (auto& entry : rootEntry.children)
|
||||
{
|
||||
WriteEntry(entry, fstOffset, nameOffset, curDataAddress, rootOffset);
|
||||
}
|
||||
for (auto& entry : rootEntry.children)
|
||||
{
|
||||
WriteEntry(entry, fstOffset, nameOffset, curDataAddress, rootOffset);
|
||||
}
|
||||
|
||||
// overflow check
|
||||
_dbg_assert_(DVDINTERFACE, nameOffset == m_totalNameSize);
|
||||
// overflow check
|
||||
_dbg_assert_(DVDINTERFACE, nameOffset == m_totalNameSize);
|
||||
|
||||
// write FST size and location
|
||||
Write32((u32)(m_fst_address >> m_addressShift), 0x0424, &m_diskHeader);
|
||||
Write32((u32)(m_FSTData.size() >> m_addressShift), 0x0428, &m_diskHeader);
|
||||
Write32((u32)(m_FSTData.size() >> m_addressShift), 0x042c, &m_diskHeader);
|
||||
// write FST size and location
|
||||
Write32((u32)(m_fst_address >> m_addressShift), 0x0424, &m_diskHeader);
|
||||
Write32((u32)(m_FSTData.size() >> m_addressShift), 0x0428, &m_diskHeader);
|
||||
Write32((u32)(m_FSTData.size() >> m_addressShift), 0x042c, &m_diskHeader);
|
||||
}
|
||||
|
||||
void CVolumeDirectory::WriteToBuffer(u64 _SrcStartAddress, u64 _SrcLength, const u8* _Src,
|
||||
u64& _Address, u64& _Length, u8*& _pBuffer) const
|
||||
u64& _Address, u64& _Length, u8*& _pBuffer) const
|
||||
{
|
||||
if (_Length == 0)
|
||||
return;
|
||||
if (_Length == 0)
|
||||
return;
|
||||
|
||||
_dbg_assert_(DVDINTERFACE, _Address >= _SrcStartAddress);
|
||||
_dbg_assert_(DVDINTERFACE, _Address >= _SrcStartAddress);
|
||||
|
||||
u64 srcOffset = _Address - _SrcStartAddress;
|
||||
u64 srcOffset = _Address - _SrcStartAddress;
|
||||
|
||||
if (srcOffset < _SrcLength)
|
||||
{
|
||||
u64 srcBytes = std::min(_SrcLength - srcOffset, _Length);
|
||||
if (srcOffset < _SrcLength)
|
||||
{
|
||||
u64 srcBytes = std::min(_SrcLength - srcOffset, _Length);
|
||||
|
||||
memcpy(_pBuffer, _Src + srcOffset, (size_t)srcBytes);
|
||||
memcpy(_pBuffer, _Src + srcOffset, (size_t)srcBytes);
|
||||
|
||||
_Length -= srcBytes;
|
||||
_pBuffer += srcBytes;
|
||||
_Address += srcBytes;
|
||||
}
|
||||
_Length -= srcBytes;
|
||||
_pBuffer += srcBytes;
|
||||
_Address += srcBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void CVolumeDirectory::PadToAddress(u64 _StartAddress, u64& _Address, u64& _Length, u8*& _pBuffer) const
|
||||
void CVolumeDirectory::PadToAddress(u64 _StartAddress, u64& _Address, u64& _Length,
|
||||
u8*& _pBuffer) const
|
||||
{
|
||||
if (_StartAddress > _Address && _Length > 0)
|
||||
{
|
||||
u64 padBytes = std::min(_StartAddress - _Address, _Length);
|
||||
memset(_pBuffer, 0, (size_t)padBytes);
|
||||
_Length -= padBytes;
|
||||
_pBuffer += padBytes;
|
||||
_Address += padBytes;
|
||||
}
|
||||
if (_StartAddress > _Address && _Length > 0)
|
||||
{
|
||||
u64 padBytes = std::min(_StartAddress - _Address, _Length);
|
||||
memset(_pBuffer, 0, (size_t)padBytes);
|
||||
_Length -= padBytes;
|
||||
_pBuffer += padBytes;
|
||||
_Address += padBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void CVolumeDirectory::Write32(u32 data, u32 offset, std::vector<u8>* const buffer)
|
||||
{
|
||||
(*buffer)[offset++] = (data >> 24);
|
||||
(*buffer)[offset++] = (data >> 16) & 0xff;
|
||||
(*buffer)[offset++] = (data >> 8) & 0xff;
|
||||
(*buffer)[offset] = (data) & 0xff;
|
||||
(*buffer)[offset++] = (data >> 24);
|
||||
(*buffer)[offset++] = (data >> 16) & 0xff;
|
||||
(*buffer)[offset++] = (data >> 8) & 0xff;
|
||||
(*buffer)[offset] = (data)&0xff;
|
||||
}
|
||||
|
||||
void CVolumeDirectory::WriteEntryData(u32& entryOffset, u8 type, u32 nameOffset, u64 dataOffset, u64 length)
|
||||
void CVolumeDirectory::WriteEntryData(u32& entryOffset, u8 type, u32 nameOffset, u64 dataOffset,
|
||||
u64 length)
|
||||
{
|
||||
m_FSTData[entryOffset++] = type;
|
||||
m_FSTData[entryOffset++] = type;
|
||||
|
||||
m_FSTData[entryOffset++] = (nameOffset >> 16) & 0xff;
|
||||
m_FSTData[entryOffset++] = (nameOffset >> 8) & 0xff;
|
||||
m_FSTData[entryOffset++] = (nameOffset) & 0xff;
|
||||
m_FSTData[entryOffset++] = (nameOffset >> 16) & 0xff;
|
||||
m_FSTData[entryOffset++] = (nameOffset >> 8) & 0xff;
|
||||
m_FSTData[entryOffset++] = (nameOffset)&0xff;
|
||||
|
||||
Write32((u32)(dataOffset >> m_addressShift), entryOffset, &m_FSTData);
|
||||
entryOffset += 4;
|
||||
Write32((u32)(dataOffset >> m_addressShift), entryOffset, &m_FSTData);
|
||||
entryOffset += 4;
|
||||
|
||||
Write32((u32)length, entryOffset, &m_FSTData);
|
||||
entryOffset += 4;
|
||||
Write32((u32)length, entryOffset, &m_FSTData);
|
||||
entryOffset += 4;
|
||||
}
|
||||
|
||||
void CVolumeDirectory::WriteEntryName(u32& nameOffset, const std::string& name)
|
||||
{
|
||||
strncpy((char*)&m_FSTData[nameOffset + m_fstNameOffset], name.c_str(), name.length() + 1);
|
||||
strncpy((char*)&m_FSTData[nameOffset + m_fstNameOffset], name.c_str(), name.length() + 1);
|
||||
|
||||
nameOffset += (u32)(name.length() + 1);
|
||||
nameOffset += (u32)(name.length() + 1);
|
||||
}
|
||||
|
||||
void CVolumeDirectory::WriteEntry(const File::FSTEntry& entry, u32& fstOffset, u32& nameOffset, u64& dataOffset, u32 parentEntryNum)
|
||||
void CVolumeDirectory::WriteEntry(const File::FSTEntry& entry, u32& fstOffset, u32& nameOffset,
|
||||
u64& dataOffset, u32 parentEntryNum)
|
||||
{
|
||||
if (entry.isDirectory)
|
||||
{
|
||||
u32 myOffset = fstOffset;
|
||||
u32 myEntryNum = myOffset / ENTRY_SIZE;
|
||||
WriteEntryData(fstOffset, DIRECTORY_ENTRY, nameOffset, parentEntryNum, myEntryNum + entry.size + 1);
|
||||
WriteEntryName(nameOffset, entry.virtualName);
|
||||
if (entry.isDirectory)
|
||||
{
|
||||
u32 myOffset = fstOffset;
|
||||
u32 myEntryNum = myOffset / ENTRY_SIZE;
|
||||
WriteEntryData(fstOffset, DIRECTORY_ENTRY, nameOffset, parentEntryNum,
|
||||
myEntryNum + entry.size + 1);
|
||||
WriteEntryName(nameOffset, entry.virtualName);
|
||||
|
||||
for (const auto& child : entry.children)
|
||||
{
|
||||
WriteEntry(child, fstOffset, nameOffset, dataOffset, myEntryNum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// put entry in FST
|
||||
WriteEntryData(fstOffset, FILE_ENTRY, nameOffset, dataOffset, entry.size);
|
||||
WriteEntryName(nameOffset, entry.virtualName);
|
||||
for (const auto& child : entry.children)
|
||||
{
|
||||
WriteEntry(child, fstOffset, nameOffset, dataOffset, myEntryNum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// put entry in FST
|
||||
WriteEntryData(fstOffset, FILE_ENTRY, nameOffset, dataOffset, entry.size);
|
||||
WriteEntryName(nameOffset, entry.virtualName);
|
||||
|
||||
// write entry to virtual disk
|
||||
_dbg_assert_(DVDINTERFACE, m_virtualDisk.find(dataOffset) == m_virtualDisk.end());
|
||||
m_virtualDisk.emplace(dataOffset, entry.physicalName);
|
||||
// write entry to virtual disk
|
||||
_dbg_assert_(DVDINTERFACE, m_virtualDisk.find(dataOffset) == m_virtualDisk.end());
|
||||
m_virtualDisk.emplace(dataOffset, entry.physicalName);
|
||||
|
||||
// 4 byte aligned
|
||||
dataOffset = ROUND_UP(dataOffset + std::max<u64>(entry.size, 1ull), 0x8000ull);
|
||||
}
|
||||
// 4 byte aligned
|
||||
dataOffset = ROUND_UP(dataOffset + std::max<u64>(entry.size, 1ull), 0x8000ull);
|
||||
}
|
||||
}
|
||||
|
||||
static u32 ComputeNameSize(const File::FSTEntry& parentEntry)
|
||||
{
|
||||
u32 nameSize = 0;
|
||||
const std::vector<File::FSTEntry>& children = parentEntry.children;
|
||||
for (auto it = children.cbegin(); it != children.cend(); ++it)
|
||||
{
|
||||
const File::FSTEntry& entry = *it;
|
||||
if (entry.isDirectory)
|
||||
{
|
||||
nameSize += ComputeNameSize(entry);
|
||||
}
|
||||
nameSize += (u32)entry.virtualName.length() + 1;
|
||||
}
|
||||
return nameSize;
|
||||
u32 nameSize = 0;
|
||||
const std::vector<File::FSTEntry>& children = parentEntry.children;
|
||||
for (auto it = children.cbegin(); it != children.cend(); ++it)
|
||||
{
|
||||
const File::FSTEntry& entry = *it;
|
||||
if (entry.isDirectory)
|
||||
{
|
||||
nameSize += ComputeNameSize(entry);
|
||||
}
|
||||
nameSize += (u32)entry.virtualName.length() + 1;
|
||||
}
|
||||
return nameSize;
|
||||
}
|
||||
|
||||
u64 CVolumeDirectory::AddDirectoryEntries(const std::string& _Directory, File::FSTEntry& parentEntry)
|
||||
u64 CVolumeDirectory::AddDirectoryEntries(const std::string& _Directory,
|
||||
File::FSTEntry& parentEntry)
|
||||
{
|
||||
parentEntry = File::ScanDirectoryTree(_Directory, true);
|
||||
m_totalNameSize += ComputeNameSize(parentEntry);
|
||||
return parentEntry.size;
|
||||
parentEntry = File::ScanDirectoryTree(_Directory, true);
|
||||
m_totalNameSize += ComputeNameSize(parentEntry);
|
||||
return parentEntry.size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -13,7 +13,10 @@
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
namespace File { struct FSTEntry; }
|
||||
namespace File
|
||||
{
|
||||
struct FSTEntry;
|
||||
}
|
||||
|
||||
//
|
||||
// --- this volume type is used for reading files directly from the hard drive ---
|
||||
@ -21,133 +24,132 @@ namespace File { struct FSTEntry; }
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class CVolumeDirectory : public IVolume
|
||||
{
|
||||
public:
|
||||
CVolumeDirectory(const std::string& _rDirectory, bool _bIsWii,
|
||||
const std::string& _rApploader = "", const std::string& _rDOL = "");
|
||||
|
||||
CVolumeDirectory(const std::string& _rDirectory, bool _bIsWii,
|
||||
const std::string& _rApploader = "", const std::string& _rDOL = "");
|
||||
~CVolumeDirectory();
|
||||
|
||||
~CVolumeDirectory();
|
||||
static bool IsValidDirectory(const std::string& _rDirectory);
|
||||
|
||||
static bool IsValidDirectory(const std::string& _rDirectory);
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
|
||||
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
|
||||
std::string GetUniqueID() const override;
|
||||
void SetUniqueID(const std::string& _ID);
|
||||
|
||||
std::string GetUniqueID() const override;
|
||||
void SetUniqueID(const std::string& _ID);
|
||||
std::string GetMakerID() const override;
|
||||
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override { return 0; }
|
||||
std::string GetInternalName() const override;
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
void SetName(const std::string&);
|
||||
|
||||
u16 GetRevision() const override { return 0; }
|
||||
std::string GetInternalName() const override;
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
void SetName(const std::string&);
|
||||
u64 GetFSTSize() const override;
|
||||
|
||||
u64 GetFSTSize() const override;
|
||||
std::string GetApploaderDate() const override;
|
||||
EPlatform GetVolumeType() const override;
|
||||
|
||||
std::string GetApploaderDate() const override;
|
||||
EPlatform GetVolumeType() const override;
|
||||
ECountry GetCountry() const override;
|
||||
|
||||
ECountry GetCountry() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
void BuildFST();
|
||||
void BuildFST();
|
||||
|
||||
private:
|
||||
static std::string ExtractDirectoryName(const std::string& _rDirectory);
|
||||
static std::string ExtractDirectoryName(const std::string& _rDirectory);
|
||||
|
||||
void SetDiskTypeWii();
|
||||
void SetDiskTypeGC();
|
||||
void SetDiskTypeWii();
|
||||
void SetDiskTypeGC();
|
||||
|
||||
bool SetApploader(const std::string& _rApploader);
|
||||
bool SetApploader(const std::string& _rApploader);
|
||||
|
||||
void SetDOL(const std::string& _rDOL);
|
||||
void SetDOL(const std::string& _rDOL);
|
||||
|
||||
// writing to read buffer
|
||||
void WriteToBuffer(u64 _SrcStartAddress, u64 _SrcLength, const u8* _Src,
|
||||
u64& _Address, u64& _Length, u8*& _pBuffer) const;
|
||||
// writing to read buffer
|
||||
void WriteToBuffer(u64 _SrcStartAddress, u64 _SrcLength, const u8* _Src, u64& _Address,
|
||||
u64& _Length, u8*& _pBuffer) const;
|
||||
|
||||
void PadToAddress(u64 _StartAddress, u64& _Address, u64& _Length, u8*& _pBuffer) const;
|
||||
void PadToAddress(u64 _StartAddress, u64& _Address, u64& _Length, u8*& _pBuffer) const;
|
||||
|
||||
void Write32(u32 data, u32 offset, std::vector<u8>* const buffer);
|
||||
void Write32(u32 data, u32 offset, std::vector<u8>* const buffer);
|
||||
|
||||
// FST creation
|
||||
void WriteEntryData(u32& entryOffset, u8 type, u32 nameOffset, u64 dataOffset, u64 length);
|
||||
void WriteEntryName(u32& nameOffset, const std::string& name);
|
||||
void WriteEntry(const File::FSTEntry& entry, u32& fstOffset, u32& nameOffset, u64& dataOffset, u32 parentEntryNum);
|
||||
// FST creation
|
||||
void WriteEntryData(u32& entryOffset, u8 type, u32 nameOffset, u64 dataOffset, u64 length);
|
||||
void WriteEntryName(u32& nameOffset, const std::string& name);
|
||||
void WriteEntry(const File::FSTEntry& entry, u32& fstOffset, u32& nameOffset, u64& dataOffset,
|
||||
u32 parentEntryNum);
|
||||
|
||||
// returns number of entries found in _Directory
|
||||
u64 AddDirectoryEntries(const std::string& _Directory, File::FSTEntry& parentEntry);
|
||||
// returns number of entries found in _Directory
|
||||
u64 AddDirectoryEntries(const std::string& _Directory, File::FSTEntry& parentEntry);
|
||||
|
||||
std::string m_rootDirectory;
|
||||
std::string m_rootDirectory;
|
||||
|
||||
std::map<u64, std::string> m_virtualDisk;
|
||||
std::map<u64, std::string> m_virtualDisk;
|
||||
|
||||
u32 m_totalNameSize;
|
||||
u32 m_totalNameSize;
|
||||
|
||||
bool m_is_wii;
|
||||
bool m_is_wii;
|
||||
|
||||
// GameCube has no shift, Wii has 2 bit shift
|
||||
u32 m_addressShift;
|
||||
// GameCube has no shift, Wii has 2 bit shift
|
||||
u32 m_addressShift;
|
||||
|
||||
// first address on disk containing file data
|
||||
u64 m_dataStartAddress;
|
||||
// first address on disk containing file data
|
||||
u64 m_dataStartAddress;
|
||||
|
||||
u64 m_fstNameOffset;
|
||||
std::vector<u8> m_FSTData;
|
||||
u64 m_fstNameOffset;
|
||||
std::vector<u8> m_FSTData;
|
||||
|
||||
std::vector<u8> m_diskHeader;
|
||||
std::vector<u8> m_diskHeader;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct SDiskHeaderInfo
|
||||
{
|
||||
u32 debug_mntr_size;
|
||||
u32 simulated_mem_size;
|
||||
u32 arg_offset;
|
||||
u32 debug_flag;
|
||||
u32 track_location;
|
||||
u32 track_size;
|
||||
u32 country_code;
|
||||
u32 unknown;
|
||||
u32 unknown2;
|
||||
#pragma pack(push, 1)
|
||||
struct SDiskHeaderInfo
|
||||
{
|
||||
u32 debug_mntr_size;
|
||||
u32 simulated_mem_size;
|
||||
u32 arg_offset;
|
||||
u32 debug_flag;
|
||||
u32 track_location;
|
||||
u32 track_size;
|
||||
u32 country_code;
|
||||
u32 unknown;
|
||||
u32 unknown2;
|
||||
|
||||
// All the data is byteswapped
|
||||
SDiskHeaderInfo()
|
||||
{
|
||||
debug_mntr_size = 0;
|
||||
simulated_mem_size = 0;
|
||||
arg_offset = 0;
|
||||
debug_flag = 0;
|
||||
track_location = 0;
|
||||
track_size = 0;
|
||||
country_code = 0;
|
||||
unknown = 0;
|
||||
unknown2 = 0;
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
std::unique_ptr<SDiskHeaderInfo> m_diskHeaderInfo;
|
||||
// All the data is byteswapped
|
||||
SDiskHeaderInfo()
|
||||
{
|
||||
debug_mntr_size = 0;
|
||||
simulated_mem_size = 0;
|
||||
arg_offset = 0;
|
||||
debug_flag = 0;
|
||||
track_location = 0;
|
||||
track_size = 0;
|
||||
country_code = 0;
|
||||
unknown = 0;
|
||||
unknown2 = 0;
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
std::unique_ptr<SDiskHeaderInfo> m_diskHeaderInfo;
|
||||
|
||||
std::vector<u8> m_apploader;
|
||||
std::vector<u8> m_DOL;
|
||||
std::vector<u8> m_apploader;
|
||||
std::vector<u8> m_DOL;
|
||||
|
||||
u64 m_fst_address;
|
||||
u64 m_dol_address;
|
||||
u64 m_fst_address;
|
||||
u64 m_dol_address;
|
||||
|
||||
static constexpr u8 ENTRY_SIZE = 0x0c;
|
||||
static constexpr u8 FILE_ENTRY = 0;
|
||||
static constexpr u8 DIRECTORY_ENTRY = 1;
|
||||
static constexpr u64 DISKHEADER_ADDRESS = 0;
|
||||
static constexpr u64 DISKHEADERINFO_ADDRESS = 0x440;
|
||||
static constexpr u64 APPLOADER_ADDRESS = 0x2440;
|
||||
static const size_t MAX_NAME_LENGTH = 0x3df;
|
||||
static const size_t MAX_ID_LENGTH = 6;
|
||||
static constexpr u8 ENTRY_SIZE = 0x0c;
|
||||
static constexpr u8 FILE_ENTRY = 0;
|
||||
static constexpr u8 DIRECTORY_ENTRY = 1;
|
||||
static constexpr u64 DISKHEADER_ADDRESS = 0;
|
||||
static constexpr u64 DISKHEADERINFO_ADDRESS = 0x440;
|
||||
static constexpr u64 APPLOADER_ADDRESS = 0x2440;
|
||||
static const size_t MAX_NAME_LENGTH = 0x3df;
|
||||
static const size_t MAX_ID_LENGTH = 6;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -11,9 +11,9 @@
|
||||
|
||||
#include "Common/ColorUtil.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/FileMonitor.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
@ -22,9 +22,9 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
CVolumeGC::CVolumeGC(std::unique_ptr<IBlobReader> reader)
|
||||
: m_pReader(std::move(reader))
|
||||
{}
|
||||
CVolumeGC::CVolumeGC(std::unique_ptr<IBlobReader> reader) : m_pReader(std::move(reader))
|
||||
{
|
||||
}
|
||||
|
||||
CVolumeGC::~CVolumeGC()
|
||||
{
|
||||
@ -32,269 +32,271 @@ CVolumeGC::~CVolumeGC()
|
||||
|
||||
bool CVolumeGC::Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const
|
||||
{
|
||||
if (decrypt)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
if (decrypt)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
|
||||
FileMon::FindFilename(_Offset);
|
||||
FileMon::FindFilename(_Offset);
|
||||
|
||||
return m_pReader->Read(_Offset, _Length, _pBuffer);
|
||||
return m_pReader->Read(_Offset, _Length, _pBuffer);
|
||||
}
|
||||
|
||||
std::string CVolumeGC::GetUniqueID() const
|
||||
{
|
||||
static const std::string NO_UID("NO_UID");
|
||||
if (m_pReader == nullptr)
|
||||
return NO_UID;
|
||||
static const std::string NO_UID("NO_UID");
|
||||
if (m_pReader == nullptr)
|
||||
return NO_UID;
|
||||
|
||||
char ID[6];
|
||||
char ID[6];
|
||||
|
||||
if (!Read(0, sizeof(ID), reinterpret_cast<u8*>(ID)))
|
||||
{
|
||||
PanicAlertT("Failed to read unique ID from disc image");
|
||||
return NO_UID;
|
||||
}
|
||||
if (!Read(0, sizeof(ID), reinterpret_cast<u8*>(ID)))
|
||||
{
|
||||
PanicAlertT("Failed to read unique ID from disc image");
|
||||
return NO_UID;
|
||||
}
|
||||
|
||||
return DecodeString(ID);
|
||||
return DecodeString(ID);
|
||||
}
|
||||
|
||||
IVolume::ECountry CVolumeGC::GetCountry() const
|
||||
{
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
|
||||
u8 country_code;
|
||||
m_pReader->Read(3, 1, &country_code);
|
||||
u8 country_code;
|
||||
m_pReader->Read(3, 1, &country_code);
|
||||
|
||||
return CountrySwitch(country_code);
|
||||
return CountrySwitch(country_code);
|
||||
}
|
||||
|
||||
std::string CVolumeGC::GetMakerID() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
|
||||
char makerID[2];
|
||||
if (!Read(0x4, 0x2, (u8*)&makerID))
|
||||
return std::string();
|
||||
char makerID[2];
|
||||
if (!Read(0x4, 0x2, (u8*)&makerID))
|
||||
return std::string();
|
||||
|
||||
return DecodeString(makerID);
|
||||
return DecodeString(makerID);
|
||||
}
|
||||
|
||||
u16 CVolumeGC::GetRevision() const
|
||||
{
|
||||
if (!m_pReader)
|
||||
return 0;
|
||||
if (!m_pReader)
|
||||
return 0;
|
||||
|
||||
u8 revision;
|
||||
if (!Read(7, 1, &revision))
|
||||
return 0;
|
||||
u8 revision;
|
||||
if (!Read(7, 1, &revision))
|
||||
return 0;
|
||||
|
||||
return revision;
|
||||
return revision;
|
||||
}
|
||||
|
||||
std::string CVolumeGC::GetInternalName() const
|
||||
{
|
||||
char name[0x60];
|
||||
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)name))
|
||||
return DecodeString(name);
|
||||
else
|
||||
return "";
|
||||
char name[0x60];
|
||||
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)name))
|
||||
return DecodeString(name);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeGC::GetNames(bool prefer_long) const
|
||||
{
|
||||
return ReadMultiLanguageStrings(false, prefer_long);
|
||||
return ReadMultiLanguageStrings(false, prefer_long);
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeGC::GetDescriptions() const
|
||||
{
|
||||
return ReadMultiLanguageStrings(true);
|
||||
return ReadMultiLanguageStrings(true);
|
||||
}
|
||||
|
||||
std::string CVolumeGC::GetCompany() const
|
||||
{
|
||||
if (!LoadBannerFile())
|
||||
return "";
|
||||
if (!LoadBannerFile())
|
||||
return "";
|
||||
|
||||
std::string company = DecodeString(m_banner_file.comment[0].longMaker);
|
||||
std::string company = DecodeString(m_banner_file.comment[0].longMaker);
|
||||
|
||||
if (company.empty())
|
||||
company = DecodeString(m_banner_file.comment[0].shortMaker);
|
||||
if (company.empty())
|
||||
company = DecodeString(m_banner_file.comment[0].shortMaker);
|
||||
|
||||
return company;
|
||||
return company;
|
||||
}
|
||||
|
||||
std::vector<u32> CVolumeGC::GetBanner(int* width, int* height) const
|
||||
{
|
||||
if (!LoadBannerFile())
|
||||
{
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
return std::vector<u32>();
|
||||
}
|
||||
if (!LoadBannerFile())
|
||||
{
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
return std::vector<u32>();
|
||||
}
|
||||
|
||||
std::vector<u32> image_buffer(GC_BANNER_WIDTH * GC_BANNER_HEIGHT);
|
||||
ColorUtil::decode5A3image(image_buffer.data(), m_banner_file.image, GC_BANNER_WIDTH, GC_BANNER_HEIGHT);
|
||||
*width = GC_BANNER_WIDTH;
|
||||
*height = GC_BANNER_HEIGHT;
|
||||
return image_buffer;
|
||||
std::vector<u32> image_buffer(GC_BANNER_WIDTH * GC_BANNER_HEIGHT);
|
||||
ColorUtil::decode5A3image(image_buffer.data(), m_banner_file.image, GC_BANNER_WIDTH,
|
||||
GC_BANNER_HEIGHT);
|
||||
*width = GC_BANNER_WIDTH;
|
||||
*height = GC_BANNER_HEIGHT;
|
||||
return image_buffer;
|
||||
}
|
||||
|
||||
u64 CVolumeGC::GetFSTSize() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return 0;
|
||||
if (m_pReader == nullptr)
|
||||
return 0;
|
||||
|
||||
u32 size;
|
||||
if (!Read(0x428, 0x4, (u8*)&size))
|
||||
return 0;
|
||||
u32 size;
|
||||
if (!Read(0x428, 0x4, (u8*)&size))
|
||||
return 0;
|
||||
|
||||
return Common::swap32(size);
|
||||
return Common::swap32(size);
|
||||
}
|
||||
|
||||
std::string CVolumeGC::GetApploaderDate() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
|
||||
char date[16];
|
||||
if (!Read(0x2440, 0x10, (u8*)&date))
|
||||
return std::string();
|
||||
char date[16];
|
||||
if (!Read(0x2440, 0x10, (u8*)&date))
|
||||
return std::string();
|
||||
|
||||
return DecodeString(date);
|
||||
return DecodeString(date);
|
||||
}
|
||||
|
||||
BlobType CVolumeGC::GetBlobType() const
|
||||
{
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
}
|
||||
|
||||
u64 CVolumeGC::GetSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CVolumeGC::GetRawSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
u8 CVolumeGC::GetDiscNumber() const
|
||||
{
|
||||
u8 disc_number;
|
||||
Read(6, 1, &disc_number);
|
||||
return disc_number;
|
||||
u8 disc_number;
|
||||
Read(6, 1, &disc_number);
|
||||
return disc_number;
|
||||
}
|
||||
|
||||
IVolume::EPlatform CVolumeGC::GetVolumeType() const
|
||||
{
|
||||
return GAMECUBE_DISC;
|
||||
return GAMECUBE_DISC;
|
||||
}
|
||||
|
||||
// Returns true if the loaded banner file is valid,
|
||||
// regardless of whether it was loaded by the current call
|
||||
bool CVolumeGC::LoadBannerFile() const
|
||||
{
|
||||
// The methods ReadMultiLanguageStrings, GetCompany and GetBanner
|
||||
// need to access the opening.bnr file. These methods are
|
||||
// usually called one after another. The file is cached in
|
||||
// RAM to avoid reading it from the disc several times, but
|
||||
// if none of these methods are called, the file is never loaded.
|
||||
// The methods ReadMultiLanguageStrings, GetCompany and GetBanner
|
||||
// need to access the opening.bnr file. These methods are
|
||||
// usually called one after another. The file is cached in
|
||||
// RAM to avoid reading it from the disc several times, but
|
||||
// if none of these methods are called, the file is never loaded.
|
||||
|
||||
// If opening.bnr has been loaded already, return immediately
|
||||
if (m_banner_file_type != BANNER_NOT_LOADED)
|
||||
return m_banner_file_type != BANNER_INVALID;
|
||||
// If opening.bnr has been loaded already, return immediately
|
||||
if (m_banner_file_type != BANNER_NOT_LOADED)
|
||||
return m_banner_file_type != BANNER_INVALID;
|
||||
|
||||
std::unique_ptr<IFileSystem> file_system(CreateFileSystem(this));
|
||||
size_t file_size = (size_t)file_system->GetFileSize("opening.bnr");
|
||||
if (file_size == BNR1_SIZE || file_size == BNR2_SIZE)
|
||||
{
|
||||
file_system->ReadFile("opening.bnr", reinterpret_cast<u8*>(&m_banner_file), file_size);
|
||||
std::unique_ptr<IFileSystem> file_system(CreateFileSystem(this));
|
||||
size_t file_size = (size_t)file_system->GetFileSize("opening.bnr");
|
||||
if (file_size == BNR1_SIZE || file_size == BNR2_SIZE)
|
||||
{
|
||||
file_system->ReadFile("opening.bnr", reinterpret_cast<u8*>(&m_banner_file), file_size);
|
||||
|
||||
if (file_size == BNR1_SIZE && m_banner_file.id == 0x31524e42) // "BNR1"
|
||||
{
|
||||
m_banner_file_type = BANNER_BNR1;
|
||||
}
|
||||
else if (file_size == BNR2_SIZE && m_banner_file.id == 0x32524e42) // "BNR2"
|
||||
{
|
||||
m_banner_file_type = BANNER_BNR2;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_banner_file_type = BANNER_INVALID;
|
||||
WARN_LOG(DISCIO, "Invalid opening.bnr. Type: %0x Size: %0zx", m_banner_file.id, file_size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_banner_file_type = BANNER_INVALID;
|
||||
WARN_LOG(DISCIO, "Invalid opening.bnr. Size: %0zx", file_size);
|
||||
}
|
||||
if (file_size == BNR1_SIZE && m_banner_file.id == 0x31524e42) // "BNR1"
|
||||
{
|
||||
m_banner_file_type = BANNER_BNR1;
|
||||
}
|
||||
else if (file_size == BNR2_SIZE && m_banner_file.id == 0x32524e42) // "BNR2"
|
||||
{
|
||||
m_banner_file_type = BANNER_BNR2;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_banner_file_type = BANNER_INVALID;
|
||||
WARN_LOG(DISCIO, "Invalid opening.bnr. Type: %0x Size: %0zx", m_banner_file.id, file_size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_banner_file_type = BANNER_INVALID;
|
||||
WARN_LOG(DISCIO, "Invalid opening.bnr. Size: %0zx", file_size);
|
||||
}
|
||||
|
||||
return m_banner_file_type != BANNER_INVALID;
|
||||
return m_banner_file_type != BANNER_INVALID;
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeGC::ReadMultiLanguageStrings(bool description, bool prefer_long) const
|
||||
std::map<IVolume::ELanguage, std::string>
|
||||
CVolumeGC::ReadMultiLanguageStrings(bool description, bool prefer_long) const
|
||||
{
|
||||
std::map<ELanguage, std::string> strings;
|
||||
std::map<ELanguage, std::string> strings;
|
||||
|
||||
if (!LoadBannerFile())
|
||||
return strings;
|
||||
if (!LoadBannerFile())
|
||||
return strings;
|
||||
|
||||
u32 number_of_languages = 0;
|
||||
ELanguage start_language = LANGUAGE_UNKNOWN;
|
||||
bool is_japanese = GetCountry() == ECountry::COUNTRY_JAPAN;
|
||||
u32 number_of_languages = 0;
|
||||
ELanguage start_language = LANGUAGE_UNKNOWN;
|
||||
bool is_japanese = GetCountry() == ECountry::COUNTRY_JAPAN;
|
||||
|
||||
switch (m_banner_file_type)
|
||||
{
|
||||
case BANNER_BNR1: // NTSC
|
||||
number_of_languages = 1;
|
||||
start_language = is_japanese ? ELanguage::LANGUAGE_JAPANESE : ELanguage::LANGUAGE_ENGLISH;
|
||||
break;
|
||||
switch (m_banner_file_type)
|
||||
{
|
||||
case BANNER_BNR1: // NTSC
|
||||
number_of_languages = 1;
|
||||
start_language = is_japanese ? ELanguage::LANGUAGE_JAPANESE : ELanguage::LANGUAGE_ENGLISH;
|
||||
break;
|
||||
|
||||
case BANNER_BNR2: // PAL
|
||||
number_of_languages = 6;
|
||||
start_language = ELanguage::LANGUAGE_ENGLISH;
|
||||
break;
|
||||
case BANNER_BNR2: // PAL
|
||||
number_of_languages = 6;
|
||||
start_language = ELanguage::LANGUAGE_ENGLISH;
|
||||
break;
|
||||
|
||||
// Shouldn't happen
|
||||
case BANNER_INVALID:
|
||||
case BANNER_NOT_LOADED:
|
||||
break;
|
||||
}
|
||||
// Shouldn't happen
|
||||
case BANNER_INVALID:
|
||||
case BANNER_NOT_LOADED:
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < number_of_languages; ++i)
|
||||
{
|
||||
const GCBannerComment& comment = m_banner_file.comment[i];
|
||||
std::string string;
|
||||
for (u32 i = 0; i < number_of_languages; ++i)
|
||||
{
|
||||
const GCBannerComment& comment = m_banner_file.comment[i];
|
||||
std::string string;
|
||||
|
||||
if (description)
|
||||
{
|
||||
string = DecodeString(comment.comment);
|
||||
}
|
||||
else // Title
|
||||
{
|
||||
if (prefer_long)
|
||||
string = DecodeString(comment.longTitle);
|
||||
if (description)
|
||||
{
|
||||
string = DecodeString(comment.comment);
|
||||
}
|
||||
else // Title
|
||||
{
|
||||
if (prefer_long)
|
||||
string = DecodeString(comment.longTitle);
|
||||
|
||||
if (string.empty())
|
||||
string = DecodeString(comment.shortTitle);
|
||||
}
|
||||
if (string.empty())
|
||||
string = DecodeString(comment.shortTitle);
|
||||
}
|
||||
|
||||
if (!string.empty())
|
||||
strings[(ELanguage)(start_language + i)] = string;
|
||||
}
|
||||
if (!string.empty())
|
||||
strings[(ELanguage)(start_language + i)] = string;
|
||||
}
|
||||
|
||||
return strings;
|
||||
return strings;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -17,71 +17,71 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class CVolumeGC : public IVolume
|
||||
{
|
||||
public:
|
||||
CVolumeGC(std::unique_ptr<IBlobReader> reader);
|
||||
~CVolumeGC();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt = false) const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override;
|
||||
std::map<ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::map<ELanguage, std::string> GetDescriptions() const override;
|
||||
std::string GetCompany() const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override;
|
||||
std::string GetApploaderDate() const override;
|
||||
u8 GetDiscNumber() const override;
|
||||
CVolumeGC(std::unique_ptr<IBlobReader> reader);
|
||||
~CVolumeGC();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt = false) const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override;
|
||||
std::map<ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::map<ELanguage, std::string> GetDescriptions() const override;
|
||||
std::string GetCompany() const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override;
|
||||
std::string GetApploaderDate() const override;
|
||||
u8 GetDiscNumber() const override;
|
||||
|
||||
EPlatform GetVolumeType() const override;
|
||||
ECountry GetCountry() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
EPlatform GetVolumeType() const override;
|
||||
ECountry GetCountry() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
private:
|
||||
bool LoadBannerFile() const;
|
||||
std::map<ELanguage, std::string> ReadMultiLanguageStrings(bool description, bool prefer_long = true) const;
|
||||
bool LoadBannerFile() const;
|
||||
std::map<ELanguage, std::string> ReadMultiLanguageStrings(bool description,
|
||||
bool prefer_long = true) const;
|
||||
|
||||
static const int GC_BANNER_WIDTH = 96;
|
||||
static const int GC_BANNER_HEIGHT = 32;
|
||||
static const int GC_BANNER_WIDTH = 96;
|
||||
static const int GC_BANNER_HEIGHT = 32;
|
||||
|
||||
// Banner Comment
|
||||
struct GCBannerComment
|
||||
{
|
||||
char shortTitle[32]; // Short game title shown in IPL menu
|
||||
char shortMaker[32]; // Short developer, publisher names shown in IPL menu
|
||||
char longTitle[64]; // Long game title shown in IPL game start screen
|
||||
char longMaker[64]; // Long developer, publisher names shown in IPL game start screen
|
||||
char comment[128]; // Game description shown in IPL game start screen in two lines.
|
||||
};
|
||||
// Banner Comment
|
||||
struct GCBannerComment
|
||||
{
|
||||
char shortTitle[32]; // Short game title shown in IPL menu
|
||||
char shortMaker[32]; // Short developer, publisher names shown in IPL menu
|
||||
char longTitle[64]; // Long game title shown in IPL game start screen
|
||||
char longMaker[64]; // Long developer, publisher names shown in IPL game start screen
|
||||
char comment[128]; // Game description shown in IPL game start screen in two lines.
|
||||
};
|
||||
|
||||
struct GCBanner
|
||||
{
|
||||
u32 id; // "BNR1" for NTSC, "BNR2" for PAL
|
||||
u32 padding[7];
|
||||
u16 image[GC_BANNER_WIDTH * GC_BANNER_HEIGHT]; // RGB5A3 96x32 image
|
||||
GCBannerComment comment[6]; // Comments in six languages (only one for BNR1 type)
|
||||
};
|
||||
struct GCBanner
|
||||
{
|
||||
u32 id; // "BNR1" for NTSC, "BNR2" for PAL
|
||||
u32 padding[7];
|
||||
u16 image[GC_BANNER_WIDTH * GC_BANNER_HEIGHT]; // RGB5A3 96x32 image
|
||||
GCBannerComment comment[6]; // Comments in six languages (only one for BNR1 type)
|
||||
};
|
||||
|
||||
static const size_t BNR1_SIZE = sizeof(GCBanner) - sizeof(GCBannerComment) * 5;
|
||||
static const size_t BNR2_SIZE = sizeof(GCBanner);
|
||||
static const size_t BNR1_SIZE = sizeof(GCBanner) - sizeof(GCBannerComment) * 5;
|
||||
static const size_t BNR2_SIZE = sizeof(GCBanner);
|
||||
|
||||
enum BannerFileType
|
||||
{
|
||||
BANNER_NOT_LOADED,
|
||||
BANNER_INVALID,
|
||||
BANNER_BNR1,
|
||||
BANNER_BNR2
|
||||
};
|
||||
enum BannerFileType
|
||||
{
|
||||
BANNER_NOT_LOADED,
|
||||
BANNER_INVALID,
|
||||
BANNER_BNR1,
|
||||
BANNER_BNR2
|
||||
};
|
||||
|
||||
mutable BannerFileType m_banner_file_type = BANNER_NOT_LOADED;
|
||||
mutable GCBanner m_banner_file;
|
||||
mutable BannerFileType m_banner_file_type = BANNER_NOT_LOADED;
|
||||
mutable GCBanner m_banner_file;
|
||||
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -11,10 +11,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeWad.h"
|
||||
@ -24,19 +24,19 @@
|
||||
namespace DiscIO
|
||||
{
|
||||
CVolumeWAD::CVolumeWAD(std::unique_ptr<IBlobReader> reader)
|
||||
: m_pReader(std::move(reader)), m_offset(0), m_tmd_offset(0), m_opening_bnr_offset(0),
|
||||
m_hdr_size(0), m_cert_size(0), m_tick_size(0), m_tmd_size(0), m_data_size(0)
|
||||
: m_pReader(std::move(reader)), m_offset(0), m_tmd_offset(0), m_opening_bnr_offset(0),
|
||||
m_hdr_size(0), m_cert_size(0), m_tick_size(0), m_tmd_size(0), m_data_size(0)
|
||||
{
|
||||
// Source: http://wiibrew.org/wiki/WAD_files
|
||||
Read(0x00, 4, (u8*)&m_hdr_size);
|
||||
Read(0x08, 4, (u8*)&m_cert_size);
|
||||
Read(0x10, 4, (u8*)&m_tick_size);
|
||||
Read(0x14, 4, (u8*)&m_tmd_size);
|
||||
Read(0x18, 4, (u8*)&m_data_size);
|
||||
// Source: http://wiibrew.org/wiki/WAD_files
|
||||
Read(0x00, 4, (u8*)&m_hdr_size);
|
||||
Read(0x08, 4, (u8*)&m_cert_size);
|
||||
Read(0x10, 4, (u8*)&m_tick_size);
|
||||
Read(0x14, 4, (u8*)&m_tmd_size);
|
||||
Read(0x18, 4, (u8*)&m_data_size);
|
||||
|
||||
m_offset = ALIGN_40(m_hdr_size) + ALIGN_40(m_cert_size);
|
||||
m_tmd_offset = ALIGN_40(m_hdr_size) + ALIGN_40(m_cert_size) + ALIGN_40(m_tick_size);
|
||||
m_opening_bnr_offset = m_tmd_offset + ALIGN_40(m_tmd_size) + ALIGN_40(m_data_size);
|
||||
m_offset = ALIGN_40(m_hdr_size) + ALIGN_40(m_cert_size);
|
||||
m_tmd_offset = ALIGN_40(m_hdr_size) + ALIGN_40(m_cert_size) + ALIGN_40(m_tick_size);
|
||||
m_opening_bnr_offset = m_tmd_offset + ALIGN_40(m_tmd_size) + ALIGN_40(m_data_size);
|
||||
}
|
||||
|
||||
CVolumeWAD::~CVolumeWAD()
|
||||
@ -45,119 +45,119 @@ CVolumeWAD::~CVolumeWAD()
|
||||
|
||||
bool CVolumeWAD::Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const
|
||||
{
|
||||
if (decrypt)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
if (decrypt)
|
||||
PanicAlertT("Tried to decrypt data from a non-Wii volume");
|
||||
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
|
||||
return m_pReader->Read(_Offset, _Length, _pBuffer);
|
||||
return m_pReader->Read(_Offset, _Length, _pBuffer);
|
||||
}
|
||||
|
||||
IVolume::ECountry CVolumeWAD::GetCountry() const
|
||||
{
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
|
||||
// read the last digit of the titleID in the ticket
|
||||
u8 country_code;
|
||||
Read(m_tmd_offset + 0x0193, 1, &country_code);
|
||||
// read the last digit of the titleID in the ticket
|
||||
u8 country_code;
|
||||
Read(m_tmd_offset + 0x0193, 1, &country_code);
|
||||
|
||||
if (country_code == 2) // SYSMENU
|
||||
{
|
||||
u16 title_version = 0;
|
||||
Read(m_tmd_offset + 0x01dc, 2, (u8*)&title_version);
|
||||
country_code = GetSysMenuRegion(Common::swap16(title_version));
|
||||
}
|
||||
if (country_code == 2) // SYSMENU
|
||||
{
|
||||
u16 title_version = 0;
|
||||
Read(m_tmd_offset + 0x01dc, 2, (u8*)&title_version);
|
||||
country_code = GetSysMenuRegion(Common::swap16(title_version));
|
||||
}
|
||||
|
||||
return CountrySwitch(country_code);
|
||||
return CountrySwitch(country_code);
|
||||
}
|
||||
|
||||
std::string CVolumeWAD::GetUniqueID() const
|
||||
{
|
||||
char GameCode[6];
|
||||
if (!Read(m_offset + 0x01E0, 4, (u8*)GameCode))
|
||||
return "0";
|
||||
char GameCode[6];
|
||||
if (!Read(m_offset + 0x01E0, 4, (u8*)GameCode))
|
||||
return "0";
|
||||
|
||||
std::string temp = GetMakerID();
|
||||
GameCode[4] = temp.at(0);
|
||||
GameCode[5] = temp.at(1);
|
||||
std::string temp = GetMakerID();
|
||||
GameCode[4] = temp.at(0);
|
||||
GameCode[5] = temp.at(1);
|
||||
|
||||
return DecodeString(GameCode);
|
||||
return DecodeString(GameCode);
|
||||
}
|
||||
|
||||
std::string CVolumeWAD::GetMakerID() const
|
||||
{
|
||||
char temp[2] = {1};
|
||||
// Some weird channels use 0x0000 in place of the MakerID, so we need a check there
|
||||
if (!Read(0x198 + m_tmd_offset, 2, (u8*)temp) || temp[0] == 0 || temp[1] == 0)
|
||||
return "00";
|
||||
char temp[2] = {1};
|
||||
// Some weird channels use 0x0000 in place of the MakerID, so we need a check there
|
||||
if (!Read(0x198 + m_tmd_offset, 2, (u8*)temp) || temp[0] == 0 || temp[1] == 0)
|
||||
return "00";
|
||||
|
||||
return DecodeString(temp);
|
||||
return DecodeString(temp);
|
||||
}
|
||||
|
||||
bool CVolumeWAD::GetTitleID(u64* buffer) const
|
||||
{
|
||||
if (!Read(m_offset + 0x01DC, sizeof(u64), reinterpret_cast<u8*>(buffer)))
|
||||
return false;
|
||||
if (!Read(m_offset + 0x01DC, sizeof(u64), reinterpret_cast<u8*>(buffer)))
|
||||
return false;
|
||||
|
||||
*buffer = Common::swap64(*buffer);
|
||||
return true;
|
||||
*buffer = Common::swap64(*buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
u16 CVolumeWAD::GetRevision() const
|
||||
{
|
||||
u16 revision;
|
||||
if (!m_pReader->Read(m_tmd_offset + 0x1dc, 2, (u8*)&revision))
|
||||
return 0;
|
||||
u16 revision;
|
||||
if (!m_pReader->Read(m_tmd_offset + 0x1dc, 2, (u8*)&revision))
|
||||
return 0;
|
||||
|
||||
return Common::swap16(revision);
|
||||
return Common::swap16(revision);
|
||||
}
|
||||
|
||||
IVolume::EPlatform CVolumeWAD::GetVolumeType() const
|
||||
{
|
||||
return WII_WAD;
|
||||
return WII_WAD;
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeWAD::GetNames(bool prefer_long) const
|
||||
{
|
||||
std::vector<u8> name_data(NAMES_TOTAL_BYTES);
|
||||
if (!Read(m_opening_bnr_offset + 0x9C, NAMES_TOTAL_BYTES, name_data.data()))
|
||||
return std::map<IVolume::ELanguage, std::string>();
|
||||
return ReadWiiNames(name_data);
|
||||
std::vector<u8> name_data(NAMES_TOTAL_BYTES);
|
||||
if (!Read(m_opening_bnr_offset + 0x9C, NAMES_TOTAL_BYTES, name_data.data()))
|
||||
return std::map<IVolume::ELanguage, std::string>();
|
||||
return ReadWiiNames(name_data);
|
||||
}
|
||||
|
||||
std::vector<u32> CVolumeWAD::GetBanner(int* width, int* height) const
|
||||
{
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
|
||||
u64 title_id;
|
||||
if (!GetTitleID(&title_id))
|
||||
return std::vector<u32>();
|
||||
u64 title_id;
|
||||
if (!GetTitleID(&title_id))
|
||||
return std::vector<u32>();
|
||||
|
||||
return GetWiiBanner(width, height, title_id);
|
||||
return GetWiiBanner(width, height, title_id);
|
||||
}
|
||||
|
||||
BlobType CVolumeWAD::GetBlobType() const
|
||||
{
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
}
|
||||
|
||||
u64 CVolumeWAD::GetSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CVolumeWAD::GetRawSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -19,40 +19,38 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class CVolumeWAD : public IVolume
|
||||
{
|
||||
public:
|
||||
CVolumeWAD(std::unique_ptr<IBlobReader> reader);
|
||||
~CVolumeWAD();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt = false) const override;
|
||||
bool GetTitleID(u64* buffer) const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override { return ""; }
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override { return 0; }
|
||||
std::string GetApploaderDate() const override { return ""; }
|
||||
CVolumeWAD(std::unique_ptr<IBlobReader> reader);
|
||||
~CVolumeWAD();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt = false) const override;
|
||||
bool GetTitleID(u64* buffer) const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override { return ""; }
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override { return 0; }
|
||||
std::string GetApploaderDate() const override { return ""; }
|
||||
EPlatform GetVolumeType() const override;
|
||||
ECountry GetCountry() const override;
|
||||
|
||||
EPlatform GetVolumeType() const override;
|
||||
ECountry GetCountry() const override;
|
||||
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
u32 m_offset;
|
||||
u32 m_tmd_offset;
|
||||
u32 m_opening_bnr_offset;
|
||||
u32 m_hdr_size;
|
||||
u32 m_cert_size;
|
||||
u32 m_tick_size;
|
||||
u32 m_tmd_size;
|
||||
u32 m_data_size;
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
u32 m_offset;
|
||||
u32 m_tmd_offset;
|
||||
u32 m_opening_bnr_offset;
|
||||
u32 m_hdr_size;
|
||||
u32 m_cert_size;
|
||||
u32 m_tick_size;
|
||||
u32 m_tmd_size;
|
||||
u32 m_data_size;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -5,17 +5,17 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/FileMonitor.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
@ -26,27 +26,23 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr<IBlobReader> reader, u64 _VolumeOffset,
|
||||
const unsigned char* _pVolumeKey)
|
||||
: m_pReader(std::move(reader)),
|
||||
m_AES_ctx(std::make_unique<mbedtls_aes_context>()),
|
||||
m_VolumeOffset(_VolumeOffset),
|
||||
m_dataOffset(0x20000),
|
||||
m_LastDecryptedBlockOffset(-1)
|
||||
const unsigned char* _pVolumeKey)
|
||||
: m_pReader(std::move(reader)), m_AES_ctx(std::make_unique<mbedtls_aes_context>()),
|
||||
m_VolumeOffset(_VolumeOffset), m_dataOffset(0x20000), m_LastDecryptedBlockOffset(-1)
|
||||
{
|
||||
mbedtls_aes_setkey_dec(m_AES_ctx.get(), _pVolumeKey, 128);
|
||||
mbedtls_aes_setkey_dec(m_AES_ctx.get(), _pVolumeKey, 128);
|
||||
}
|
||||
|
||||
bool CVolumeWiiCrypted::ChangePartition(u64 offset)
|
||||
{
|
||||
m_VolumeOffset = offset;
|
||||
m_LastDecryptedBlockOffset = -1;
|
||||
m_VolumeOffset = offset;
|
||||
m_LastDecryptedBlockOffset = -1;
|
||||
|
||||
u8 volume_key[16];
|
||||
DiscIO::VolumeKeyForPartition(*m_pReader, offset, volume_key);
|
||||
mbedtls_aes_setkey_dec(m_AES_ctx.get(), volume_key, 128);
|
||||
return true;
|
||||
u8 volume_key[16];
|
||||
DiscIO::VolumeKeyForPartition(*m_pReader, offset, volume_key);
|
||||
mbedtls_aes_setkey_dec(m_AES_ctx.get(), volume_key, 128);
|
||||
return true;
|
||||
}
|
||||
|
||||
CVolumeWiiCrypted::~CVolumeWiiCrypted()
|
||||
@ -55,334 +51,337 @@ CVolumeWiiCrypted::~CVolumeWiiCrypted()
|
||||
|
||||
bool CVolumeWiiCrypted::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, bool decrypt) const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
if (m_pReader == nullptr)
|
||||
return false;
|
||||
|
||||
if (!decrypt)
|
||||
return m_pReader->Read(_ReadOffset, _Length, _pBuffer);
|
||||
if (!decrypt)
|
||||
return m_pReader->Read(_ReadOffset, _Length, _pBuffer);
|
||||
|
||||
FileMon::FindFilename(_ReadOffset);
|
||||
FileMon::FindFilename(_ReadOffset);
|
||||
|
||||
std::vector<u8> read_buffer(s_block_total_size);
|
||||
while (_Length > 0)
|
||||
{
|
||||
// Calculate block offset
|
||||
u64 Block = _ReadOffset / s_block_data_size;
|
||||
u64 Offset = _ReadOffset % s_block_data_size;
|
||||
std::vector<u8> read_buffer(s_block_total_size);
|
||||
while (_Length > 0)
|
||||
{
|
||||
// Calculate block offset
|
||||
u64 Block = _ReadOffset / s_block_data_size;
|
||||
u64 Offset = _ReadOffset % s_block_data_size;
|
||||
|
||||
if (m_LastDecryptedBlockOffset != Block)
|
||||
{
|
||||
// Read the current block
|
||||
if (!m_pReader->Read(m_VolumeOffset + m_dataOffset + Block * s_block_total_size, s_block_total_size, read_buffer.data()))
|
||||
return false;
|
||||
if (m_LastDecryptedBlockOffset != Block)
|
||||
{
|
||||
// Read the current block
|
||||
if (!m_pReader->Read(m_VolumeOffset + m_dataOffset + Block * s_block_total_size,
|
||||
s_block_total_size, read_buffer.data()))
|
||||
return false;
|
||||
|
||||
// Decrypt the block's data.
|
||||
// 0x3D0 - 0x3DF in m_pBuffer will be overwritten,
|
||||
// but that won't affect anything, because we won't
|
||||
// use the content of m_pBuffer anymore after this
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, s_block_data_size, &read_buffer[0x3D0],
|
||||
&read_buffer[s_block_header_size], m_LastDecryptedBlock);
|
||||
m_LastDecryptedBlockOffset = Block;
|
||||
// Decrypt the block's data.
|
||||
// 0x3D0 - 0x3DF in m_pBuffer will be overwritten,
|
||||
// but that won't affect anything, because we won't
|
||||
// use the content of m_pBuffer anymore after this
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, s_block_data_size,
|
||||
&read_buffer[0x3D0], &read_buffer[s_block_header_size],
|
||||
m_LastDecryptedBlock);
|
||||
m_LastDecryptedBlockOffset = Block;
|
||||
|
||||
// The only thing we currently use from the 0x000 - 0x3FF part
|
||||
// of the block is the IV (at 0x3D0), but it also contains SHA-1
|
||||
// hashes that IOS uses to check that discs aren't tampered with.
|
||||
// http://wiibrew.org/wiki/Wii_Disc#Encrypted
|
||||
}
|
||||
// The only thing we currently use from the 0x000 - 0x3FF part
|
||||
// of the block is the IV (at 0x3D0), but it also contains SHA-1
|
||||
// hashes that IOS uses to check that discs aren't tampered with.
|
||||
// http://wiibrew.org/wiki/Wii_Disc#Encrypted
|
||||
}
|
||||
|
||||
// Copy the decrypted data
|
||||
u64 MaxSizeToCopy = s_block_data_size - Offset;
|
||||
u64 CopySize = (_Length > MaxSizeToCopy) ? MaxSizeToCopy : _Length;
|
||||
memcpy(_pBuffer, &m_LastDecryptedBlock[Offset], (size_t)CopySize);
|
||||
// Copy the decrypted data
|
||||
u64 MaxSizeToCopy = s_block_data_size - Offset;
|
||||
u64 CopySize = (_Length > MaxSizeToCopy) ? MaxSizeToCopy : _Length;
|
||||
memcpy(_pBuffer, &m_LastDecryptedBlock[Offset], (size_t)CopySize);
|
||||
|
||||
// Update offsets
|
||||
_Length -= CopySize;
|
||||
_pBuffer += CopySize;
|
||||
_ReadOffset += CopySize;
|
||||
}
|
||||
// Update offsets
|
||||
_Length -= CopySize;
|
||||
_pBuffer += CopySize;
|
||||
_ReadOffset += CopySize;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVolumeWiiCrypted::GetTitleID(u64* buffer) const
|
||||
{
|
||||
// Tik is at m_VolumeOffset size 0x2A4
|
||||
// TitleID offset in tik is 0x1DC
|
||||
if (!Read(m_VolumeOffset + 0x1DC, sizeof(u64), reinterpret_cast<u8*>(buffer), false))
|
||||
return false;
|
||||
// Tik is at m_VolumeOffset size 0x2A4
|
||||
// TitleID offset in tik is 0x1DC
|
||||
if (!Read(m_VolumeOffset + 0x1DC, sizeof(u64), reinterpret_cast<u8*>(buffer), false))
|
||||
return false;
|
||||
|
||||
*buffer = Common::swap64(*buffer);
|
||||
return true;
|
||||
*buffer = Common::swap64(*buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> CVolumeWiiCrypted::GetTMD() const
|
||||
{
|
||||
u32 tmd_size;
|
||||
u32 tmd_address;
|
||||
u32 tmd_size;
|
||||
u32 tmd_address;
|
||||
|
||||
Read(m_VolumeOffset + 0x2a4, sizeof(u32), (u8*)&tmd_size, false);
|
||||
Read(m_VolumeOffset + 0x2a8, sizeof(u32), (u8*)&tmd_address, false);
|
||||
tmd_size = Common::swap32(tmd_size);
|
||||
tmd_address = Common::swap32(tmd_address) << 2;
|
||||
Read(m_VolumeOffset + 0x2a4, sizeof(u32), (u8*)&tmd_size, false);
|
||||
Read(m_VolumeOffset + 0x2a8, sizeof(u32), (u8*)&tmd_address, false);
|
||||
tmd_size = Common::swap32(tmd_size);
|
||||
tmd_address = Common::swap32(tmd_address) << 2;
|
||||
|
||||
if (tmd_size > 1024 * 1024 * 4)
|
||||
{
|
||||
// The size is checked so that a malicious or corrupt ISO
|
||||
// can't force Dolphin to allocate up to 4 GiB of memory.
|
||||
// 4 MiB should be much bigger than the size of TMDs and much smaller
|
||||
// than the amount of RAM in a computer that can run Dolphin.
|
||||
PanicAlert("TMD > 4 MiB");
|
||||
tmd_size = 1024 * 1024 * 4;
|
||||
}
|
||||
if (tmd_size > 1024 * 1024 * 4)
|
||||
{
|
||||
// The size is checked so that a malicious or corrupt ISO
|
||||
// can't force Dolphin to allocate up to 4 GiB of memory.
|
||||
// 4 MiB should be much bigger than the size of TMDs and much smaller
|
||||
// than the amount of RAM in a computer that can run Dolphin.
|
||||
PanicAlert("TMD > 4 MiB");
|
||||
tmd_size = 1024 * 1024 * 4;
|
||||
}
|
||||
|
||||
std::vector<u8> buffer(tmd_size);
|
||||
Read(m_VolumeOffset + tmd_address, tmd_size, buffer.data(), false);
|
||||
std::vector<u8> buffer(tmd_size);
|
||||
Read(m_VolumeOffset + tmd_address, tmd_size, buffer.data(), false);
|
||||
|
||||
return buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string CVolumeWiiCrypted::GetUniqueID() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
|
||||
char ID[6];
|
||||
char ID[6];
|
||||
|
||||
if (!Read(0, 6, (u8*)ID, false))
|
||||
return std::string();
|
||||
if (!Read(0, 6, (u8*)ID, false))
|
||||
return std::string();
|
||||
|
||||
return DecodeString(ID);
|
||||
return DecodeString(ID);
|
||||
}
|
||||
|
||||
|
||||
IVolume::ECountry CVolumeWiiCrypted::GetCountry() const
|
||||
{
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
if (!m_pReader)
|
||||
return COUNTRY_UNKNOWN;
|
||||
|
||||
u8 country_byte;
|
||||
if (!m_pReader->Read(3, 1, &country_byte))
|
||||
{
|
||||
return COUNTRY_UNKNOWN;
|
||||
}
|
||||
u8 country_byte;
|
||||
if (!m_pReader->Read(3, 1, &country_byte))
|
||||
{
|
||||
return COUNTRY_UNKNOWN;
|
||||
}
|
||||
|
||||
IVolume::ECountry country_value = CountrySwitch(country_byte);
|
||||
IVolume::ECountry country_value = CountrySwitch(country_byte);
|
||||
|
||||
u32 region_code;
|
||||
if (!ReadSwapped(0x4E000, ®ion_code, false))
|
||||
{
|
||||
return country_value;
|
||||
}
|
||||
u32 region_code;
|
||||
if (!ReadSwapped(0x4E000, ®ion_code, false))
|
||||
{
|
||||
return country_value;
|
||||
}
|
||||
|
||||
switch (region_code)
|
||||
{
|
||||
case 0:
|
||||
switch (country_value)
|
||||
{
|
||||
case IVolume::COUNTRY_TAIWAN:
|
||||
return IVolume::COUNTRY_TAIWAN;
|
||||
default:
|
||||
return IVolume::COUNTRY_JAPAN;
|
||||
}
|
||||
case 1:
|
||||
return IVolume::COUNTRY_USA;
|
||||
case 2:
|
||||
switch (country_value)
|
||||
{
|
||||
case IVolume::COUNTRY_FRANCE:
|
||||
case IVolume::COUNTRY_GERMANY:
|
||||
case IVolume::COUNTRY_ITALY:
|
||||
case IVolume::COUNTRY_NETHERLANDS:
|
||||
case IVolume::COUNTRY_RUSSIA:
|
||||
case IVolume::COUNTRY_SPAIN:
|
||||
case IVolume::COUNTRY_AUSTRALIA:
|
||||
return country_value;
|
||||
default:
|
||||
return IVolume::COUNTRY_EUROPE;
|
||||
}
|
||||
case 4:
|
||||
return IVolume::COUNTRY_KOREA;
|
||||
default:
|
||||
return country_value;
|
||||
}
|
||||
switch (region_code)
|
||||
{
|
||||
case 0:
|
||||
switch (country_value)
|
||||
{
|
||||
case IVolume::COUNTRY_TAIWAN:
|
||||
return IVolume::COUNTRY_TAIWAN;
|
||||
default:
|
||||
return IVolume::COUNTRY_JAPAN;
|
||||
}
|
||||
case 1:
|
||||
return IVolume::COUNTRY_USA;
|
||||
case 2:
|
||||
switch (country_value)
|
||||
{
|
||||
case IVolume::COUNTRY_FRANCE:
|
||||
case IVolume::COUNTRY_GERMANY:
|
||||
case IVolume::COUNTRY_ITALY:
|
||||
case IVolume::COUNTRY_NETHERLANDS:
|
||||
case IVolume::COUNTRY_RUSSIA:
|
||||
case IVolume::COUNTRY_SPAIN:
|
||||
case IVolume::COUNTRY_AUSTRALIA:
|
||||
return country_value;
|
||||
default:
|
||||
return IVolume::COUNTRY_EUROPE;
|
||||
}
|
||||
case 4:
|
||||
return IVolume::COUNTRY_KOREA;
|
||||
default:
|
||||
return country_value;
|
||||
}
|
||||
}
|
||||
|
||||
std::string CVolumeWiiCrypted::GetMakerID() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
|
||||
char makerID[2];
|
||||
char makerID[2];
|
||||
|
||||
if (!Read(0x4, 0x2, (u8*)&makerID, false))
|
||||
return std::string();
|
||||
if (!Read(0x4, 0x2, (u8*)&makerID, false))
|
||||
return std::string();
|
||||
|
||||
return DecodeString(makerID);
|
||||
return DecodeString(makerID);
|
||||
}
|
||||
|
||||
u16 CVolumeWiiCrypted::GetRevision() const
|
||||
{
|
||||
if (!m_pReader)
|
||||
return 0;
|
||||
if (!m_pReader)
|
||||
return 0;
|
||||
|
||||
u8 revision;
|
||||
if (!m_pReader->Read(7, 1, &revision))
|
||||
return 0;
|
||||
u8 revision;
|
||||
if (!m_pReader->Read(7, 1, &revision))
|
||||
return 0;
|
||||
|
||||
return revision;
|
||||
return revision;
|
||||
}
|
||||
|
||||
std::string CVolumeWiiCrypted::GetInternalName() const
|
||||
{
|
||||
char name_buffer[0x60];
|
||||
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)&name_buffer, false))
|
||||
return DecodeString(name_buffer);
|
||||
char name_buffer[0x60];
|
||||
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)&name_buffer, false))
|
||||
return DecodeString(name_buffer);
|
||||
|
||||
return "";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::map<IVolume::ELanguage, std::string> CVolumeWiiCrypted::GetNames(bool prefer_long) const
|
||||
{
|
||||
std::unique_ptr<IFileSystem> file_system(CreateFileSystem(this));
|
||||
std::vector<u8> opening_bnr(NAMES_TOTAL_BYTES);
|
||||
opening_bnr.resize(file_system->ReadFile("opening.bnr", opening_bnr.data(), opening_bnr.size(), 0x5C));
|
||||
return ReadWiiNames(opening_bnr);
|
||||
std::unique_ptr<IFileSystem> file_system(CreateFileSystem(this));
|
||||
std::vector<u8> opening_bnr(NAMES_TOTAL_BYTES);
|
||||
opening_bnr.resize(
|
||||
file_system->ReadFile("opening.bnr", opening_bnr.data(), opening_bnr.size(), 0x5C));
|
||||
return ReadWiiNames(opening_bnr);
|
||||
}
|
||||
|
||||
std::vector<u32> CVolumeWiiCrypted::GetBanner(int* width, int* height) const
|
||||
{
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
|
||||
u64 title_id;
|
||||
if (!GetTitleID(&title_id))
|
||||
return std::vector<u32>();
|
||||
u64 title_id;
|
||||
if (!GetTitleID(&title_id))
|
||||
return std::vector<u32>();
|
||||
|
||||
return GetWiiBanner(width, height, title_id);
|
||||
return GetWiiBanner(width, height, title_id);
|
||||
}
|
||||
|
||||
u64 CVolumeWiiCrypted::GetFSTSize() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return 0;
|
||||
if (m_pReader == nullptr)
|
||||
return 0;
|
||||
|
||||
u32 size;
|
||||
u32 size;
|
||||
|
||||
if (!Read(0x428, 0x4, (u8*)&size, true))
|
||||
return 0;
|
||||
if (!Read(0x428, 0x4, (u8*)&size, true))
|
||||
return 0;
|
||||
|
||||
return (u64)Common::swap32(size) << 2;
|
||||
return (u64)Common::swap32(size) << 2;
|
||||
}
|
||||
|
||||
std::string CVolumeWiiCrypted::GetApploaderDate() const
|
||||
{
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
if (m_pReader == nullptr)
|
||||
return std::string();
|
||||
|
||||
char date[16];
|
||||
char date[16];
|
||||
|
||||
if (!Read(0x2440, 0x10, (u8*)&date, true))
|
||||
return std::string();
|
||||
if (!Read(0x2440, 0x10, (u8*)&date, true))
|
||||
return std::string();
|
||||
|
||||
return DecodeString(date);
|
||||
return DecodeString(date);
|
||||
}
|
||||
|
||||
IVolume::EPlatform CVolumeWiiCrypted::GetVolumeType() const
|
||||
{
|
||||
return WII_DISC;
|
||||
return WII_DISC;
|
||||
}
|
||||
|
||||
u8 CVolumeWiiCrypted::GetDiscNumber() const
|
||||
{
|
||||
u8 disc_number;
|
||||
m_pReader->Read(6, 1, &disc_number);
|
||||
return disc_number;
|
||||
u8 disc_number;
|
||||
m_pReader->Read(6, 1, &disc_number);
|
||||
return disc_number;
|
||||
}
|
||||
|
||||
BlobType CVolumeWiiCrypted::GetBlobType() const
|
||||
{
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
return m_pReader ? m_pReader->GetBlobType() : BlobType::PLAIN;
|
||||
}
|
||||
|
||||
u64 CVolumeWiiCrypted::GetSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetDataSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CVolumeWiiCrypted::GetRawSize() const
|
||||
{
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
if (m_pReader)
|
||||
return m_pReader->GetRawSize();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CVolumeWiiCrypted::CheckIntegrity() const
|
||||
{
|
||||
// Get partition data size
|
||||
u32 partSizeDiv4;
|
||||
Read(m_VolumeOffset + 0x2BC, 4, (u8*)&partSizeDiv4, false);
|
||||
u64 partDataSize = (u64)Common::swap32(partSizeDiv4) * 4;
|
||||
// Get partition data size
|
||||
u32 partSizeDiv4;
|
||||
Read(m_VolumeOffset + 0x2BC, 4, (u8*)&partSizeDiv4, false);
|
||||
u64 partDataSize = (u64)Common::swap32(partSizeDiv4) * 4;
|
||||
|
||||
u32 nClusters = (u32)(partDataSize / 0x8000);
|
||||
for (u32 clusterID = 0; clusterID < nClusters; ++clusterID)
|
||||
{
|
||||
u64 clusterOff = m_VolumeOffset + m_dataOffset + (u64)clusterID * 0x8000;
|
||||
u32 nClusters = (u32)(partDataSize / 0x8000);
|
||||
for (u32 clusterID = 0; clusterID < nClusters; ++clusterID)
|
||||
{
|
||||
u64 clusterOff = m_VolumeOffset + m_dataOffset + (u64)clusterID * 0x8000;
|
||||
|
||||
// Read and decrypt the cluster metadata
|
||||
u8 clusterMDCrypted[0x400];
|
||||
u8 clusterMD[0x400];
|
||||
u8 IV[16] = { 0 };
|
||||
if (!m_pReader->Read(clusterOff, 0x400, clusterMDCrypted))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read metadata", clusterID);
|
||||
return false;
|
||||
}
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, 0x400, IV, clusterMDCrypted, clusterMD);
|
||||
// Read and decrypt the cluster metadata
|
||||
u8 clusterMDCrypted[0x400];
|
||||
u8 clusterMD[0x400];
|
||||
u8 IV[16] = {0};
|
||||
if (!m_pReader->Read(clusterOff, 0x400, clusterMDCrypted))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read metadata", clusterID);
|
||||
return false;
|
||||
}
|
||||
mbedtls_aes_crypt_cbc(m_AES_ctx.get(), MBEDTLS_AES_DECRYPT, 0x400, IV, clusterMDCrypted,
|
||||
clusterMD);
|
||||
|
||||
// Some clusters have invalid data and metadata because they aren't
|
||||
// meant to be read by the game (for example, holes between files). To
|
||||
// try to avoid reporting errors because of these clusters, we check
|
||||
// the 0x00 paddings in the metadata.
|
||||
//
|
||||
// This may cause some false negatives though: some bad clusters may be
|
||||
// skipped because they are *too* bad and are not even recognized as
|
||||
// valid clusters. To be improved.
|
||||
bool meaningless = false;
|
||||
for (u32 idx = 0x26C; idx < 0x280; ++idx)
|
||||
if (clusterMD[idx] != 0)
|
||||
meaningless = true;
|
||||
|
||||
// Some clusters have invalid data and metadata because they aren't
|
||||
// meant to be read by the game (for example, holes between files). To
|
||||
// try to avoid reporting errors because of these clusters, we check
|
||||
// the 0x00 paddings in the metadata.
|
||||
//
|
||||
// This may cause some false negatives though: some bad clusters may be
|
||||
// skipped because they are *too* bad and are not even recognized as
|
||||
// valid clusters. To be improved.
|
||||
bool meaningless = false;
|
||||
for (u32 idx = 0x26C; idx < 0x280; ++idx)
|
||||
if (clusterMD[idx] != 0)
|
||||
meaningless = true;
|
||||
if (meaningless)
|
||||
continue;
|
||||
|
||||
if (meaningless)
|
||||
continue;
|
||||
u8 clusterData[0x7C00];
|
||||
if (!Read((u64)clusterID * 0x7C00, 0x7C00, clusterData, true))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read data", clusterID);
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 clusterData[0x7C00];
|
||||
if (!Read((u64)clusterID * 0x7C00, 0x7C00, clusterData, true))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read data", clusterID);
|
||||
return false;
|
||||
}
|
||||
for (u32 hashID = 0; hashID < 31; ++hashID)
|
||||
{
|
||||
u8 hash[20];
|
||||
|
||||
for (u32 hashID = 0; hashID < 31; ++hashID)
|
||||
{
|
||||
u8 hash[20];
|
||||
mbedtls_sha1(clusterData + hashID * 0x400, 0x400, hash);
|
||||
|
||||
mbedtls_sha1(clusterData + hashID * 0x400, 0x400, hash);
|
||||
// Note that we do not use strncmp here
|
||||
if (memcmp(hash, clusterMD + hashID * 20, 20))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: hash %d is invalid", clusterID,
|
||||
hashID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we do not use strncmp here
|
||||
if (memcmp(hash, clusterMD + hashID * 20, 20))
|
||||
{
|
||||
NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: hash %d is invalid", clusterID, hashID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -5,10 +5,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
@ -18,48 +18,48 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class CVolumeWiiCrypted : public IVolume
|
||||
{
|
||||
public:
|
||||
CVolumeWiiCrypted(std::unique_ptr<IBlobReader> reader, u64 _VolumeOffset, const unsigned char* _pVolumeKey);
|
||||
~CVolumeWiiCrypted();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
|
||||
bool GetTitleID(u64* buffer) const override;
|
||||
std::vector<u8> GetTMD() const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override;
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override;
|
||||
std::string GetApploaderDate() const override;
|
||||
u8 GetDiscNumber() const override;
|
||||
CVolumeWiiCrypted(std::unique_ptr<IBlobReader> reader, u64 _VolumeOffset,
|
||||
const unsigned char* _pVolumeKey);
|
||||
~CVolumeWiiCrypted();
|
||||
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
|
||||
bool GetTitleID(u64* buffer) const override;
|
||||
std::vector<u8> GetTMD() const override;
|
||||
std::string GetUniqueID() const override;
|
||||
std::string GetMakerID() const override;
|
||||
u16 GetRevision() const override;
|
||||
std::string GetInternalName() const override;
|
||||
std::map<IVolume::ELanguage, std::string> GetNames(bool prefer_long) const override;
|
||||
std::vector<u32> GetBanner(int* width, int* height) const override;
|
||||
u64 GetFSTSize() const override;
|
||||
std::string GetApploaderDate() const override;
|
||||
u8 GetDiscNumber() const override;
|
||||
|
||||
EPlatform GetVolumeType() const override;
|
||||
bool SupportsIntegrityCheck() const override { return true; }
|
||||
bool CheckIntegrity() const override;
|
||||
bool ChangePartition(u64 offset) override;
|
||||
EPlatform GetVolumeType() const override;
|
||||
bool SupportsIntegrityCheck() const override { return true; }
|
||||
bool CheckIntegrity() const override;
|
||||
bool ChangePartition(u64 offset) override;
|
||||
|
||||
ECountry GetCountry() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
ECountry GetCountry() const override;
|
||||
BlobType GetBlobType() const override;
|
||||
u64 GetSize() const override;
|
||||
u64 GetRawSize() const override;
|
||||
|
||||
private:
|
||||
static const unsigned int s_block_header_size = 0x0400;
|
||||
static const unsigned int s_block_data_size = 0x7C00;
|
||||
static const unsigned int s_block_total_size = s_block_header_size + s_block_data_size;
|
||||
static const unsigned int s_block_header_size = 0x0400;
|
||||
static const unsigned int s_block_data_size = 0x7C00;
|
||||
static const unsigned int s_block_total_size = s_block_header_size + s_block_data_size;
|
||||
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
std::unique_ptr<mbedtls_aes_context> m_AES_ctx;
|
||||
std::unique_ptr<IBlobReader> m_pReader;
|
||||
std::unique_ptr<mbedtls_aes_context> m_AES_ctx;
|
||||
|
||||
u64 m_VolumeOffset;
|
||||
u64 m_dataOffset;
|
||||
u64 m_VolumeOffset;
|
||||
u64 m_dataOffset;
|
||||
|
||||
mutable u64 m_LastDecryptedBlockOffset;
|
||||
mutable unsigned char m_LastDecryptedBlock[s_block_data_size];
|
||||
mutable u64 m_LastDecryptedBlockOffset;
|
||||
mutable unsigned char m_LastDecryptedBlock[s_block_data_size];
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
@ -12,8 +12,8 @@
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "DiscIO/WbfsBlob.h"
|
||||
|
||||
namespace DiscIO
|
||||
@ -24,24 +24,25 @@ static const u64 WII_DISC_HEADER_SIZE = 256;
|
||||
|
||||
static inline u64 align(u64 value, u64 bounds)
|
||||
{
|
||||
return (value + (bounds - 1)) & (~(bounds - 1));
|
||||
return (value + (bounds - 1)) & (~(bounds - 1));
|
||||
}
|
||||
|
||||
WbfsFileReader::WbfsFileReader(const std::string& filename)
|
||||
: m_total_files(0), m_size(0), m_good(true)
|
||||
: m_total_files(0), m_size(0), m_good(true)
|
||||
{
|
||||
if (filename.length() < 4 || !OpenFiles(filename) || !ReadHeader())
|
||||
{
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
if (filename.length() < 4 || !OpenFiles(filename) || !ReadHeader())
|
||||
{
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab disc info (assume slot 0, checked in ReadHeader())
|
||||
m_wlba_table.resize(m_blocks_per_disc);
|
||||
m_files[0]->file.Seek(m_hd_sector_size + WII_DISC_HEADER_SIZE /*+ i * m_disc_info_size*/, SEEK_SET);
|
||||
m_files[0]->file.ReadBytes(m_wlba_table.data(), m_blocks_per_disc * sizeof(u16));
|
||||
for (size_t i = 0; i < m_blocks_per_disc; i++)
|
||||
m_wlba_table[i] = Common::swap16(m_wlba_table[i]);
|
||||
// Grab disc info (assume slot 0, checked in ReadHeader())
|
||||
m_wlba_table.resize(m_blocks_per_disc);
|
||||
m_files[0]->file.Seek(m_hd_sector_size + WII_DISC_HEADER_SIZE /*+ i * m_disc_info_size*/,
|
||||
SEEK_SET);
|
||||
m_files[0]->file.ReadBytes(m_wlba_table.data(), m_blocks_per_disc * sizeof(u16));
|
||||
for (size_t i = 0; i < m_blocks_per_disc; i++)
|
||||
m_wlba_table[i] = Common::swap16(m_wlba_table[i]);
|
||||
}
|
||||
|
||||
WbfsFileReader::~WbfsFileReader()
|
||||
@ -50,140 +51,139 @@ WbfsFileReader::~WbfsFileReader()
|
||||
|
||||
u64 WbfsFileReader::GetDataSize() const
|
||||
{
|
||||
return WII_SECTOR_COUNT * WII_SECTOR_SIZE;
|
||||
return WII_SECTOR_COUNT * WII_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
bool WbfsFileReader::OpenFiles(const std::string& filename)
|
||||
{
|
||||
m_total_files = 0;
|
||||
m_total_files = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto new_entry = std::make_unique<file_entry>();
|
||||
while (true)
|
||||
{
|
||||
auto new_entry = std::make_unique<file_entry>();
|
||||
|
||||
// Replace last character with index (e.g. wbfs = wbf1)
|
||||
std::string path = filename;
|
||||
if (m_total_files != 0)
|
||||
{
|
||||
path[path.length() - 1] = '0' + m_total_files;
|
||||
}
|
||||
// Replace last character with index (e.g. wbfs = wbf1)
|
||||
std::string path = filename;
|
||||
if (m_total_files != 0)
|
||||
{
|
||||
path[path.length() - 1] = '0' + m_total_files;
|
||||
}
|
||||
|
||||
if (!new_entry->file.Open(path, "rb"))
|
||||
{
|
||||
return m_total_files != 0;
|
||||
}
|
||||
if (!new_entry->file.Open(path, "rb"))
|
||||
{
|
||||
return m_total_files != 0;
|
||||
}
|
||||
|
||||
new_entry->base_address = m_size;
|
||||
new_entry->size = new_entry->file.GetSize();
|
||||
m_size += new_entry->size;
|
||||
new_entry->base_address = m_size;
|
||||
new_entry->size = new_entry->file.GetSize();
|
||||
m_size += new_entry->size;
|
||||
|
||||
m_total_files++;
|
||||
m_files.emplace_back(std::move(new_entry));
|
||||
}
|
||||
m_total_files++;
|
||||
m_files.emplace_back(std::move(new_entry));
|
||||
}
|
||||
}
|
||||
|
||||
bool WbfsFileReader::ReadHeader()
|
||||
{
|
||||
// Read hd size info
|
||||
m_files[0]->file.ReadBytes(&m_header, sizeof(WbfsHeader));
|
||||
m_header.hd_sector_count = Common::swap32(m_header.hd_sector_count);
|
||||
// Read hd size info
|
||||
m_files[0]->file.ReadBytes(&m_header, sizeof(WbfsHeader));
|
||||
m_header.hd_sector_count = Common::swap32(m_header.hd_sector_count);
|
||||
|
||||
m_hd_sector_size = 1ull << m_header.hd_sector_shift;
|
||||
m_hd_sector_size = 1ull << m_header.hd_sector_shift;
|
||||
|
||||
if (m_size != (m_header.hd_sector_count * m_hd_sector_size))
|
||||
return false;
|
||||
if (m_size != (m_header.hd_sector_count * m_hd_sector_size))
|
||||
return false;
|
||||
|
||||
// Read wbfs cluster info
|
||||
m_wbfs_sector_size = 1ull << m_header.wbfs_sector_shift;
|
||||
m_wbfs_sector_count = m_size / m_wbfs_sector_size;
|
||||
// Read wbfs cluster info
|
||||
m_wbfs_sector_size = 1ull << m_header.wbfs_sector_shift;
|
||||
m_wbfs_sector_count = m_size / m_wbfs_sector_size;
|
||||
|
||||
if (m_wbfs_sector_size < WII_SECTOR_SIZE)
|
||||
return false;
|
||||
if (m_wbfs_sector_size < WII_SECTOR_SIZE)
|
||||
return false;
|
||||
|
||||
m_blocks_per_disc = (WII_SECTOR_COUNT * WII_SECTOR_SIZE + m_wbfs_sector_size - 1) / m_wbfs_sector_size;
|
||||
m_disc_info_size = align(WII_DISC_HEADER_SIZE + m_blocks_per_disc * sizeof(u16), m_hd_sector_size);
|
||||
m_blocks_per_disc =
|
||||
(WII_SECTOR_COUNT * WII_SECTOR_SIZE + m_wbfs_sector_size - 1) / m_wbfs_sector_size;
|
||||
m_disc_info_size =
|
||||
align(WII_DISC_HEADER_SIZE + m_blocks_per_disc * sizeof(u16), m_hd_sector_size);
|
||||
|
||||
return m_header.disc_table[0] != 0;
|
||||
return m_header.disc_table[0] != 0;
|
||||
}
|
||||
|
||||
bool WbfsFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
|
||||
{
|
||||
while (nbytes)
|
||||
{
|
||||
u64 read_size;
|
||||
File::IOFile& data_file = SeekToCluster(offset, &read_size);
|
||||
if (read_size == 0)
|
||||
return false;
|
||||
read_size = std::min(read_size, nbytes);
|
||||
while (nbytes)
|
||||
{
|
||||
u64 read_size;
|
||||
File::IOFile& data_file = SeekToCluster(offset, &read_size);
|
||||
if (read_size == 0)
|
||||
return false;
|
||||
read_size = std::min(read_size, nbytes);
|
||||
|
||||
if (!data_file.ReadBytes(out_ptr, read_size))
|
||||
{
|
||||
data_file.Clear();
|
||||
return false;
|
||||
}
|
||||
if (!data_file.ReadBytes(out_ptr, read_size))
|
||||
{
|
||||
data_file.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
out_ptr += read_size;
|
||||
nbytes -= read_size;
|
||||
offset += read_size;
|
||||
}
|
||||
out_ptr += read_size;
|
||||
nbytes -= read_size;
|
||||
offset += read_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
File::IOFile& WbfsFileReader::SeekToCluster(u64 offset, u64* available)
|
||||
{
|
||||
u64 base_cluster = (offset >> m_header.wbfs_sector_shift);
|
||||
if (base_cluster < m_blocks_per_disc)
|
||||
{
|
||||
u64 cluster_address = m_wbfs_sector_size * m_wlba_table[base_cluster];
|
||||
u64 cluster_offset = offset & (m_wbfs_sector_size - 1);
|
||||
u64 final_address = cluster_address + cluster_offset;
|
||||
u64 base_cluster = (offset >> m_header.wbfs_sector_shift);
|
||||
if (base_cluster < m_blocks_per_disc)
|
||||
{
|
||||
u64 cluster_address = m_wbfs_sector_size * m_wlba_table[base_cluster];
|
||||
u64 cluster_offset = offset & (m_wbfs_sector_size - 1);
|
||||
u64 final_address = cluster_address + cluster_offset;
|
||||
|
||||
for (u32 i = 0; i != m_total_files; i++)
|
||||
{
|
||||
if (final_address < (m_files[i]->base_address + m_files[i]->size))
|
||||
{
|
||||
m_files[i]->file.Seek(final_address - m_files[i]->base_address, SEEK_SET);
|
||||
if (available)
|
||||
{
|
||||
u64 till_end_of_file = m_files[i]->size - (final_address - m_files[i]->base_address);
|
||||
u64 till_end_of_sector = m_wbfs_sector_size - cluster_offset;
|
||||
*available = std::min(till_end_of_file, till_end_of_sector);
|
||||
}
|
||||
for (u32 i = 0; i != m_total_files; i++)
|
||||
{
|
||||
if (final_address < (m_files[i]->base_address + m_files[i]->size))
|
||||
{
|
||||
m_files[i]->file.Seek(final_address - m_files[i]->base_address, SEEK_SET);
|
||||
if (available)
|
||||
{
|
||||
u64 till_end_of_file = m_files[i]->size - (final_address - m_files[i]->base_address);
|
||||
u64 till_end_of_sector = m_wbfs_sector_size - cluster_offset;
|
||||
*available = std::min(till_end_of_file, till_end_of_sector);
|
||||
}
|
||||
|
||||
return m_files[i]->file;
|
||||
}
|
||||
}
|
||||
}
|
||||
return m_files[i]->file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanicAlert("Read beyond end of disc");
|
||||
if (available)
|
||||
*available = 0;
|
||||
m_files[0]->file.Seek(0, SEEK_SET);
|
||||
return m_files[0]->file;
|
||||
PanicAlert("Read beyond end of disc");
|
||||
if (available)
|
||||
*available = 0;
|
||||
m_files[0]->file.Seek(0, SEEK_SET);
|
||||
return m_files[0]->file;
|
||||
}
|
||||
|
||||
std::unique_ptr<WbfsFileReader> WbfsFileReader::Create(const std::string& filename)
|
||||
{
|
||||
auto reader = std::unique_ptr<WbfsFileReader>(new WbfsFileReader(filename));
|
||||
auto reader = std::unique_ptr<WbfsFileReader>(new WbfsFileReader(filename));
|
||||
|
||||
if (!reader->IsGood())
|
||||
reader.reset();
|
||||
if (!reader->IsGood())
|
||||
reader.reset();
|
||||
|
||||
return reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
bool IsWbfsBlob(const std::string& filename)
|
||||
{
|
||||
File::IOFile f(filename, "rb");
|
||||
File::IOFile f(filename, "rb");
|
||||
|
||||
u8 magic[4] = {0, 0, 0, 0};
|
||||
f.ReadBytes(&magic, 4);
|
||||
u8 magic[4] = {0, 0, 0, 0};
|
||||
f.ReadBytes(&magic, 4);
|
||||
|
||||
return (magic[0] == 'W') &&
|
||||
(magic[1] == 'B') &&
|
||||
(magic[2] == 'F') &&
|
||||
(magic[3] == 'S');
|
||||
return (magic[0] == 'W') && (magic[1] == 'B') && (magic[2] == 'F') && (magic[3] == 'S');
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -14,70 +14,65 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class WbfsFileReader : public IBlobReader
|
||||
{
|
||||
public:
|
||||
~WbfsFileReader();
|
||||
~WbfsFileReader();
|
||||
|
||||
static std::unique_ptr<WbfsFileReader> Create(const std::string& filename);
|
||||
static std::unique_ptr<WbfsFileReader> Create(const std::string& filename);
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::WBFS; }
|
||||
BlobType GetBlobType() const override { return BlobType::WBFS; }
|
||||
// The WBFS format does not save the original file size.
|
||||
// This function returns a constant upper bound
|
||||
// (the size of a double-layer Wii disc).
|
||||
u64 GetDataSize() const override;
|
||||
|
||||
// The WBFS format does not save the original file size.
|
||||
// This function returns a constant upper bound
|
||||
// (the size of a double-layer Wii disc).
|
||||
u64 GetDataSize() const override;
|
||||
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
WbfsFileReader(const std::string& filename);
|
||||
WbfsFileReader(const std::string& filename);
|
||||
|
||||
bool OpenFiles(const std::string& filename);
|
||||
bool ReadHeader();
|
||||
bool OpenFiles(const std::string& filename);
|
||||
bool ReadHeader();
|
||||
|
||||
File::IOFile& SeekToCluster(u64 offset, u64* available);
|
||||
bool IsGood() {return m_good;}
|
||||
File::IOFile& SeekToCluster(u64 offset, u64* available);
|
||||
bool IsGood() { return m_good; }
|
||||
struct file_entry
|
||||
{
|
||||
File::IOFile file;
|
||||
u64 base_address;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<file_entry>> m_files;
|
||||
|
||||
struct file_entry
|
||||
{
|
||||
File::IOFile file;
|
||||
u64 base_address;
|
||||
u64 size;
|
||||
};
|
||||
u32 m_total_files;
|
||||
u64 m_size;
|
||||
|
||||
std::vector<std::unique_ptr<file_entry>> m_files;
|
||||
|
||||
u32 m_total_files;
|
||||
u64 m_size;
|
||||
|
||||
u64 m_hd_sector_size;
|
||||
u64 m_wbfs_sector_size;
|
||||
u64 m_wbfs_sector_count;
|
||||
u64 m_disc_info_size;
|
||||
u64 m_hd_sector_size;
|
||||
u64 m_wbfs_sector_size;
|
||||
u64 m_wbfs_sector_count;
|
||||
u64 m_disc_info_size;
|
||||
|
||||
#pragma pack(1)
|
||||
struct WbfsHeader
|
||||
{
|
||||
char magic[4];
|
||||
u32 hd_sector_count;
|
||||
u8 hd_sector_shift;
|
||||
u8 wbfs_sector_shift;
|
||||
u8 padding[2];
|
||||
u8 disc_table[500];
|
||||
} m_header;
|
||||
struct WbfsHeader
|
||||
{
|
||||
char magic[4];
|
||||
u32 hd_sector_count;
|
||||
u8 hd_sector_shift;
|
||||
u8 wbfs_sector_shift;
|
||||
u8 padding[2];
|
||||
u8 disc_table[500];
|
||||
} m_header;
|
||||
#pragma pack()
|
||||
|
||||
std::vector<u16> m_wlba_table;
|
||||
u64 m_blocks_per_disc;
|
||||
std::vector<u16> m_wlba_table;
|
||||
u64 m_blocks_per_disc;
|
||||
|
||||
bool m_good;
|
||||
bool m_good;
|
||||
};
|
||||
|
||||
bool IsWbfsBlob(const std::string& filename);
|
||||
|
||||
|
||||
} // namespace
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -10,24 +9,23 @@
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/WiiWad.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
WiiWAD::WiiWAD(const std::string& name)
|
||||
{
|
||||
std::unique_ptr<IBlobReader> reader(CreateBlobReader(name));
|
||||
if (reader == nullptr || File::IsDirectory(name))
|
||||
{
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<IBlobReader> reader(CreateBlobReader(name));
|
||||
if (reader == nullptr || File::IsDirectory(name))
|
||||
{
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_valid = ParseWAD(*reader);
|
||||
m_valid = ParseWAD(*reader);
|
||||
}
|
||||
|
||||
WiiWAD::~WiiWAD()
|
||||
@ -36,63 +34,67 @@ WiiWAD::~WiiWAD()
|
||||
|
||||
std::vector<u8> WiiWAD::CreateWADEntry(IBlobReader& reader, u32 size, u64 offset)
|
||||
{
|
||||
if (size == 0)
|
||||
return {};
|
||||
if (size == 0)
|
||||
return {};
|
||||
|
||||
std::vector<u8> buffer(size);
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
if (!reader.Read(offset, size, buffer.data()))
|
||||
{
|
||||
ERROR_LOG(DISCIO, "WiiWAD: Could not read from file");
|
||||
PanicAlertT("WiiWAD: Could not read from file");
|
||||
}
|
||||
if (!reader.Read(offset, size, buffer.data()))
|
||||
{
|
||||
ERROR_LOG(DISCIO, "WiiWAD: Could not read from file");
|
||||
PanicAlertT("WiiWAD: Could not read from file");
|
||||
}
|
||||
|
||||
return buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
bool WiiWAD::ParseWAD(IBlobReader& reader)
|
||||
{
|
||||
CBlobBigEndianReader big_endian_reader(reader);
|
||||
CBlobBigEndianReader big_endian_reader(reader);
|
||||
|
||||
if (!IsWiiWAD(big_endian_reader))
|
||||
return false;
|
||||
if (!IsWiiWAD(big_endian_reader))
|
||||
return false;
|
||||
|
||||
u32 certificate_chain_size;
|
||||
u32 reserved;
|
||||
u32 ticket_size;
|
||||
u32 tmd_size;
|
||||
u32 data_app_size;
|
||||
u32 footer_size;
|
||||
u32 certificate_chain_size;
|
||||
u32 reserved;
|
||||
u32 ticket_size;
|
||||
u32 tmd_size;
|
||||
u32 data_app_size;
|
||||
u32 footer_size;
|
||||
|
||||
if (!big_endian_reader.ReadSwapped(0x08, &certificate_chain_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x0C, &reserved) ||
|
||||
!big_endian_reader.ReadSwapped(0x10, &ticket_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x14, &tmd_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x18, &data_app_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x1C, &footer_size))
|
||||
return false;
|
||||
if (!big_endian_reader.ReadSwapped(0x08, &certificate_chain_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x0C, &reserved) ||
|
||||
!big_endian_reader.ReadSwapped(0x10, &ticket_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x14, &tmd_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x18, &data_app_size) ||
|
||||
!big_endian_reader.ReadSwapped(0x1C, &footer_size))
|
||||
return false;
|
||||
|
||||
if (MAX_LOGLEVEL >= LogTypes::LOG_LEVELS::LDEBUG)
|
||||
_dbg_assert_msg_(BOOT, reserved == 0x00, "WiiWAD: Reserved must be 0x00");
|
||||
if (MAX_LOGLEVEL >= LogTypes::LOG_LEVELS::LDEBUG)
|
||||
_dbg_assert_msg_(BOOT, reserved == 0x00, "WiiWAD: Reserved must be 0x00");
|
||||
|
||||
u32 offset = 0x40;
|
||||
m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset); offset += ROUND_UP(certificate_chain_size, 0x40);
|
||||
m_ticket = CreateWADEntry(reader, ticket_size, offset); offset += ROUND_UP(ticket_size, 0x40);
|
||||
m_tmd = CreateWADEntry(reader, tmd_size, offset); offset += ROUND_UP(tmd_size, 0x40);
|
||||
m_data_app = CreateWADEntry(reader, data_app_size, offset); offset += ROUND_UP(data_app_size, 0x40);
|
||||
m_footer = CreateWADEntry(reader, footer_size, offset); offset += ROUND_UP(footer_size, 0x40);
|
||||
u32 offset = 0x40;
|
||||
m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset);
|
||||
offset += ROUND_UP(certificate_chain_size, 0x40);
|
||||
m_ticket = CreateWADEntry(reader, ticket_size, offset);
|
||||
offset += ROUND_UP(ticket_size, 0x40);
|
||||
m_tmd = CreateWADEntry(reader, tmd_size, offset);
|
||||
offset += ROUND_UP(tmd_size, 0x40);
|
||||
m_data_app = CreateWADEntry(reader, data_app_size, offset);
|
||||
offset += ROUND_UP(data_app_size, 0x40);
|
||||
m_footer = CreateWADEntry(reader, footer_size, offset);
|
||||
offset += ROUND_UP(footer_size, 0x40);
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiiWAD::IsWiiWAD(const CBlobBigEndianReader& reader)
|
||||
{
|
||||
u32 header_size = 0;
|
||||
u32 header_type = 0;
|
||||
reader.ReadSwapped(0x0, &header_size);
|
||||
reader.ReadSwapped(0x4, &header_type);
|
||||
return header_size == 0x20 && (header_type == 0x49730000 || header_type == 0x69620000);
|
||||
u32 header_size = 0;
|
||||
u32 header_type = 0;
|
||||
reader.ReadSwapped(0x0, &header_size);
|
||||
reader.ReadSwapped(0x4, &header_type);
|
||||
return header_size == 0x20 && (header_type == 0x49730000 || header_type == 0x69620000);
|
||||
}
|
||||
|
||||
} // namespace end
|
||||
} // namespace end
|
||||
|
@ -11,36 +11,32 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
class IBlobReader;
|
||||
class CBlobBigEndianReader;
|
||||
|
||||
class WiiWAD
|
||||
{
|
||||
public:
|
||||
WiiWAD(const std::string& name);
|
||||
~WiiWAD();
|
||||
|
||||
bool IsValid() const { return m_valid; }
|
||||
|
||||
const std::vector<u8>& GetCertificateChain() const { return m_certificate_chain; }
|
||||
const std::vector<u8>& GetTicket() const { return m_ticket; }
|
||||
const std::vector<u8>& GetTMD() const { return m_tmd; }
|
||||
const std::vector<u8>& GetDataApp() const { return m_data_app; }
|
||||
const std::vector<u8>& GetFooter() const { return m_footer; }
|
||||
WiiWAD(const std::string& name);
|
||||
~WiiWAD();
|
||||
|
||||
bool IsValid() const { return m_valid; }
|
||||
const std::vector<u8>& GetCertificateChain() const { return m_certificate_chain; }
|
||||
const std::vector<u8>& GetTicket() const { return m_ticket; }
|
||||
const std::vector<u8>& GetTMD() const { return m_tmd; }
|
||||
const std::vector<u8>& GetDataApp() const { return m_data_app; }
|
||||
const std::vector<u8>& GetFooter() const { return m_footer; }
|
||||
private:
|
||||
bool ParseWAD(IBlobReader& reader);
|
||||
static std::vector<u8> CreateWADEntry(IBlobReader& reader, u32 size, u64 offset);
|
||||
static bool IsWiiWAD(const CBlobBigEndianReader& reader);
|
||||
bool ParseWAD(IBlobReader& reader);
|
||||
static std::vector<u8> CreateWADEntry(IBlobReader& reader, u32 size, u64 offset);
|
||||
static bool IsWiiWAD(const CBlobBigEndianReader& reader);
|
||||
|
||||
bool m_valid;
|
||||
bool m_valid;
|
||||
|
||||
std::vector<u8> m_certificate_chain;
|
||||
std::vector<u8> m_ticket;
|
||||
std::vector<u8> m_tmd;
|
||||
std::vector<u8> m_data_app;
|
||||
std::vector<u8> m_footer;
|
||||
std::vector<u8> m_certificate_chain;
|
||||
std::vector<u8> m_ticket;
|
||||
std::vector<u8> m_tmd;
|
||||
std::vector<u8> m_data_app;
|
||||
std::vector<u8> m_footer;
|
||||
};
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user