dolphin/Source/Core/DiscIO/NFSBlob.h
JosJuice 3a6df63e9b DiscIO: Add support for the NFS format
For a few years now, I've been thinking it would be nice to make Dolphin
support reading Wii games in the format they come in when you download
them from the Wii U eShop. The Wii U eShop has some good deals on Wii
games (Metroid Prime Trilogy especially is rather expensive if you try
to buy it physically!), and it's the only place right now where you can
buy Wii games digitally.

Of course, Nintendo being Nintendo, next year they're going to shut down
this only place where you can buy Wii games digitally. I kind of wish I
had implemented this feature earlier so that people would've had ample
time to buy the games they want, but... better late than never, right?

I used MIT-licensed code from the NOD library as a reference when
implementing this. None of the code has been directly copied, but
you may notice that the names of the struct members are very similar.
c1635245b8/lib/DiscIONFS.cpp
2022-08-04 22:00:58 +02:00

91 lines
2.9 KiB
C++

// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/IOFile.h"
#include "DiscIO/Blob.h"
// This is the file format used for Wii games released on the Wii U eShop.
namespace DiscIO
{
static constexpr u32 NFS_MAGIC = 0x53474745; // "EGGS" (byteswapped to little endian)
struct NFSLBARange
{
u32 start_block;
u32 num_blocks;
};
struct NFSHeader
{
u32 magic; // EGGS
u32 version;
u32 unknown_1;
u32 unknown_2;
u32 lba_range_count;
std::array<NFSLBARange, 61> lba_ranges;
u32 end_magic; // SGGE
};
static_assert(sizeof(NFSHeader) == 0x200);
class NFSFileReader : public BlobReader
{
public:
static std::unique_ptr<NFSFileReader> Create(File::IOFile first_file,
const std::string& directory_path);
BlobType GetBlobType() const override { return BlobType::NFS; }
u64 GetRawSize() const override;
u64 GetDataSize() const override;
bool IsDataSizeAccurate() const override { return false; }
u64 GetBlockSize() const override { return BLOCK_SIZE; }
bool HasFastRandomAccessInBlock() const override { return false; }
std::string GetCompressionMethod() const override { return {}; }
std::optional<int> GetCompressionLevel() const override { return std::nullopt; }
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
private:
using Key = std::array<u8, Common::AES::Context::KEY_SIZE>;
static constexpr u32 BLOCK_SIZE = 0x8000;
static constexpr u32 MAX_FILE_SIZE = 0xFA00000;
static bool ReadKey(const std::string& path, const std::string& directory, Key* key_out);
static std::vector<NFSLBARange> GetLBARanges(const NFSHeader& header);
static std::vector<File::IOFile> OpenFiles(const std::string& directory, File::IOFile first_file,
u64 expected_raw_size, u64* raw_size_out);
static u64 CalculateExpectedRawSize(const std::vector<NFSLBARange>& lba_ranges);
static u64 CalculateExpectedDataSize(const std::vector<NFSLBARange>& lba_ranges);
NFSFileReader(std::vector<NFSLBARange> lba_ranges, std::vector<File::IOFile> files, Key key,
u64 raw_size);
u64 ToPhysicalBlockIndex(u64 logical_block_index);
bool ReadEncryptedBlock(u64 physical_block_index);
void DecryptBlock(u64 logical_block_index);
bool ReadAndDecryptBlock(u64 logical_block_index);
std::array<u8, BLOCK_SIZE> m_current_block_encrypted;
std::array<u8, BLOCK_SIZE> m_current_block_decrypted;
u64 m_current_logical_block_index = std::numeric_limits<u64>::max();
std::vector<NFSLBARange> m_lba_ranges;
std::vector<File::IOFile> m_files;
std::unique_ptr<Common::AES::Context> m_aes_context;
u64 m_raw_size;
u64 m_data_size;
};
} // namespace DiscIO