Reformat all the things. Have fun with merge conflicts.

This commit is contained in:
Pierre Bourdon
2016-06-24 10:43:46 +02:00
parent 2115e8a4a6
commit 3570c7f03a
1116 changed files with 187405 additions and 180344 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(), &not_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(), &not_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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};
}

View File

@ -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

View File

@ -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), &region);
// 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), &region);
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, &region_code, false))
{
return country_value;
}
u32 region_code;
if (!ReadSwapped(0x4E000, &region_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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};
}