Compare commits

...

6 Commits

Author SHA1 Message Date
Jack Andersen
f147e12356 Windows build fixes 2019-11-24 15:17:57 -10:00
Jack Andersen
48a2981a93 Shared NFS data class not necessary 2019-11-24 13:47:48 -10:00
Jack Andersen
19604b2a3b Use correct slash finding function 2019-11-23 20:25:29 -10:00
Jack Andersen
c1a1d1abc8 Use logical block for iv 2019-11-23 20:00:48 -10:00
Jack Andersen
6bf4f07129 Consistent variable names for blocks 2019-11-23 17:29:57 -10:00
Jack Andersen
75fc574f81 Support for Wii U VC NFS images 2019-11-23 17:24:33 -10:00
9 changed files with 337 additions and 52 deletions

View File

@@ -28,6 +28,7 @@ public:
virtual ~IDiscIO() = default;
virtual std::unique_ptr<IReadStream> beginReadStream(uint64_t offset = 0) const = 0;
virtual std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset = 0) const = 0;
virtual bool hasWiiCrypto() const { return true; } /* NFS overrides this to false */
};
struct IPartReadStream : IReadStream {

View File

@@ -54,14 +54,21 @@
namespace nod {
/* define our own min/max to avoid MSVC BS */
template <typename T>
inline T min(T a, T b) {
constexpr T min(T a, T b) {
return a < b ? a : b;
}
template <typename T>
inline T max(T a, T b) {
constexpr T max(T a, T b) {
return a > b ? a : b;
}
/* template-based div for flexible typing and avoiding a library call */
template <typename T>
constexpr auto div(T a, T b) {
struct DivTp { T quot, rem; };
return DivTp{a / b, a % b};
}
/* Log Module */
extern logvisor::Module LogModule;

View File

@@ -6,6 +6,7 @@ add_library(nod
DiscBase.cpp
DiscGCN.cpp
DiscIOISO.cpp
DiscIONFS.cpp
DiscIOWBFS.cpp
DiscWii.cpp
nod.cpp

View File

@@ -28,9 +28,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IReadStream>(new ReadStream(m_fio->beginReadStream(offset), err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
@@ -51,9 +50,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IWriteStream>(new WriteStream(m_fio->beginWriteStream(offset), err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}

271
lib/DiscIONFS.cpp Normal file
View File

@@ -0,0 +1,271 @@
#include "nod/IDiscIO.hpp"
#include "nod/IFileIO.hpp"
#include "nod/Util.hpp"
#include "nod/aes.hpp"
#include <logvisor/logvisor.hpp>
namespace nod {
/*
* NFS is the image format used to distribute Wii VC games for the Wii U.
* It is an LBA format similar to WBFS but adds its own encryption layer.
* It logically stores a standard Wii disc image with partitions.
*/
class DiscIONFS : public IDiscIO {
std::vector<std::unique_ptr<IFileIO>> files;
struct NFSHead {
uint32_t magic; // EGGS
uint32_t version;
uint32_t unknown[2]; // Signature, UUID?
uint32_t lbaRangeCount;
struct {
uint32_t startBlock;
uint32_t numBlocks;
} lbaRanges[61];
uint32_t endMagic; // SGGE
} nfsHead;
uint8_t key[16];
uint32_t calculateNumFiles() const {
uint32_t totalBlockCount = 0;
for (uint32_t i = 0; i < nfsHead.lbaRangeCount; ++i)
totalBlockCount += nfsHead.lbaRanges[i].numBlocks;
return (uint64_t(totalBlockCount) * uint64_t(0x8000) +
(uint64_t(0x200) + uint64_t(0xF9FFFFF))) / uint64_t(0xFA00000);
}
struct FBO {
uint32_t file, block, lblock, offset;
};
FBO logicalToFBO(uint64_t offset) const {
auto blockAndRemBytes = nod::div(offset, uint64_t(0x8000)); /* 32768 bytes per block */
uint32_t block = UINT32_MAX;
for (uint32_t i = 0, physicalBlock = 0; i < nfsHead.lbaRangeCount; ++i) {
const auto& range = nfsHead.lbaRanges[i];
if (blockAndRemBytes.quot >= range.startBlock && blockAndRemBytes.quot - range.startBlock < range.numBlocks) {
block = physicalBlock + (blockAndRemBytes.quot - range.startBlock);
break;
}
physicalBlock += range.numBlocks;
}
/* This offset has no physical mapping, read zeroes */
if (block == UINT32_MAX)
return {UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX};
auto fileAndRemBlocks = nod::div(block, uint32_t(8000)); /* 8000 blocks per file */
return {uint32_t(fileAndRemBlocks.quot), uint32_t(fileAndRemBlocks.rem),
uint32_t(blockAndRemBytes.quot), uint32_t(blockAndRemBytes.rem)};
}
public:
DiscIONFS(SystemStringView fpin, bool& err) {
/* Validate file path format */
using SignedSize = std::make_signed<SystemString::size_type>::type;
const auto dotPos = SignedSize(fpin.rfind(_SYS_STR('.')));
const auto slashPos = SignedSize(fpin.find_last_of(_SYS_STR("/\\")));
if (fpin.size() <= 4 || dotPos == -1 || dotPos <= slashPos ||
fpin.compare(slashPos + 1, 4, _SYS_STR("hif_")) ||
fpin.compare(dotPos, fpin.size() - dotPos, _SYS_STR(".nfs"))) {
LogModule.report(logvisor::Error,
fmt(_SYS_STR("'{}' must begin with 'hif_' and end with '.nfs' to be accepted as an NFS image")), fpin);
err = true;
return;
}
/* Load key file */
const SystemString dir(fpin.begin(), fpin.begin() + slashPos + 1);
auto keyFile = NewFileIO(dir + _SYS_STR("../code/htk.bin"))->beginReadStream();
if (!keyFile)
keyFile = NewFileIO(dir + _SYS_STR("htk.bin"))->beginReadStream();
if (!keyFile) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("Unable to open '{}../code/htk.bin' or '{}htk.bin'")), dir, dir);
err = true;
return;
}
if (keyFile->read(key, 16) != 16) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("Unable to read from '{}../code/htk.bin' or '{}htk.bin'")), dir, dir);
err = true;
return;
}
/* Load header from first file */
const SystemString firstPath = fmt::format(fmt(_SYS_STR("{}hif_{:06}.nfs")), dir, 0);
files.push_back(NewFileIO(firstPath));
auto rs = files.back()->beginReadStream();
if (!rs) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("'{}' does not exist")), firstPath);
err = true;
return;
}
if (rs->read(&nfsHead, 0x200) != 0x200) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("Unable to read header from '{}'")), firstPath);
err = true;
return;
}
if (std::memcmp(&nfsHead.magic, "EGGS", 4)) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("Invalid magic in '{}'")), firstPath);
err = true;
return;
}
nfsHead.lbaRangeCount = SBig(nfsHead.lbaRangeCount);
for (uint32_t i = 0; i < nfsHead.lbaRangeCount; ++i) {
auto& range = nfsHead.lbaRanges[i];
range.startBlock = SBig(range.startBlock);
range.numBlocks = SBig(range.numBlocks);
}
/* Ensure remaining files exist */
const uint32_t numFiles = calculateNumFiles();
files.reserve(numFiles);
for (uint32_t i = 1; i < numFiles; ++i) {
SystemString path = fmt::format(fmt(_SYS_STR("{}hif_{:06}.nfs")), dir, i);
files.push_back(NewFileIO(path));
if (!files.back()->exists()) {
LogModule.report(logvisor::Error, fmt(_SYS_STR("'{}' does not exist")), path);
err = true;
return;
}
}
}
class ReadStream : public IReadStream {
friend class DiscIONFS;
const DiscIONFS& m_parent;
std::unique_ptr<IReadStream> m_rs;
std::unique_ptr<IAES> m_aes;
/* Physical address - all UINT32_MAX indicates logical zero block */
DiscIONFS::FBO m_physAddr;
/* Logical address */
uint64_t m_offset;
/* Active file stream and its offset as set in the system.
* Block is typically one ahead of the presently decrypted block. */
uint32_t m_curFile = UINT32_MAX;
uint32_t m_curBlock = UINT32_MAX;
ReadStream(const DiscIONFS& parent, uint64_t offset, bool& err)
: m_parent(parent), m_aes(NewAES()),
m_physAddr({UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX}), m_offset(offset) {
m_aes->setKey(m_parent.key);
setLogicalAddr(offset);
}
uint8_t m_encBuf[0x8000] = {};
uint8_t m_decBuf[0x8000] = {};
void setCurFile(uint32_t curFile) {
if (curFile >= m_parent.files.size()) {
LogModule.report(logvisor::Error, fmt("Out of bounds NFS file access"));
return;
}
m_curFile = curFile;
m_curBlock = UINT32_MAX;
m_rs = m_parent.files[m_curFile]->beginReadStream();
}
void setCurBlock(uint32_t curBlock) {
m_curBlock = curBlock;
m_rs->seek(m_curBlock * 0x8000 + 0x200);
}
void setPhysicalAddr(DiscIONFS::FBO physAddr) {
/* If we're just changing the offset, nothing else needs to be done */
if (m_physAddr.file == physAddr.file && m_physAddr.block == physAddr.block) {
m_physAddr.offset = physAddr.offset;
return;
}
m_physAddr = physAddr;
/* Set logical zero block */
if (m_physAddr.file == UINT32_MAX) {
memset(m_decBuf, 0, 0x8000);
return;
}
/* Make necessary file and block current with system */
if (m_physAddr.file != m_curFile)
setCurFile(m_physAddr.file);
if (m_physAddr.block != m_curBlock)
setCurBlock(m_physAddr.block);
/* Read block, handling 0x200 overlap case */
if (m_physAddr.block == 7999) {
m_rs->read(m_encBuf, 0x7E00);
setCurFile(m_curFile + 1);
m_rs->read(m_encBuf + 0x7E00, 0x200);
m_curBlock = 0;
} else {
m_rs->read(m_encBuf, 0x8000);
++m_curBlock;
}
/* Decrypt */
const uint32_t ivBuf[] = {0, 0, 0, SBig(m_physAddr.lblock)};
m_aes->decrypt((const uint8_t*)ivBuf, m_encBuf, m_decBuf, 0x8000);
}
void setLogicalAddr(uint64_t addr) { setPhysicalAddr(m_parent.logicalToFBO(m_offset)); }
public:
uint64_t read(void* buf, uint64_t length) override {
uint64_t rem = length;
uint8_t* dst = (uint8_t*)buf;
/* Perform reads on block boundaries */
while (rem) {
uint64_t readSize = rem;
uint32_t blockOffset = (m_physAddr.offset == UINT32_MAX) ? 0 : m_physAddr.offset;
if (readSize + blockOffset > 0x8000)
readSize = 0x8000 - blockOffset;
memmove(dst, m_decBuf + blockOffset, readSize);
dst += readSize;
rem -= readSize;
m_offset += readSize;
setLogicalAddr(m_offset);
}
return dst - (uint8_t*)buf;
}
uint64_t position() const override { return m_offset; }
void seek(int64_t offset, int whence) override {
if (whence == SEEK_SET)
m_offset = offset;
else if (whence == SEEK_CUR)
m_offset += offset;
else
return;
setLogicalAddr(m_offset);
}
};
std::unique_ptr<IReadStream> beginReadStream(uint64_t offset) const override {
bool err = false;
auto ret = std::unique_ptr<IReadStream>(new ReadStream(*this, offset, err));
if (err)
return {};
return ret;
}
std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset) const override { return {}; }
bool hasWiiCrypto() const override { return false; }
};
std::unique_ptr<IDiscIO> NewDiscIONFS(SystemStringView path) {
bool err = false;
auto ret = std::make_unique<DiscIONFS>(path, err);
if (err)
return {};
return ret;
}
}

View File

@@ -23,7 +23,7 @@ static uint8_t size_to_shift(uint32_t size) {
}
class DiscIOWBFS : public IDiscIO {
SystemString filepath;
std::unique_ptr<IFileIO> m_fio;
struct WBFSHead {
uint32_t magic;
@@ -83,10 +83,9 @@ class DiscIOWBFS : public IDiscIO {
}
public:
DiscIOWBFS(SystemStringView fpin) : filepath(fpin) {
DiscIOWBFS(SystemStringView fpin) : m_fio(NewFileIO(fpin)) {
/* Temporary file handle to read LBA table */
std::unique_ptr<IFileIO> fio = NewFileIO(filepath);
std::unique_ptr<IFileIO::IReadStream> rs = fio->beginReadStream();
std::unique_ptr<IFileIO::IReadStream> rs = m_fio->beginReadStream();
if (!rs)
return;
@@ -265,16 +264,15 @@ public:
std::unique_ptr<IReadStream> beginReadStream(uint64_t offset) const override {
bool err = false;
auto ret = std::unique_ptr<IReadStream>(new ReadStream(*this, NewFileIO(filepath)->beginReadStream(), offset, err));
auto ret = std::unique_ptr<IReadStream>(new ReadStream(*this, m_fio->beginReadStream(), offset, err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset) const override { return nullptr; }
std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset) const override { return {}; }
};
std::unique_ptr<IDiscIO> NewDiscIOWBFS(SystemStringView path) { return std::make_unique<DiscIOWBFS>(path); }

View File

@@ -332,14 +332,22 @@ public:
uint8_t m_decBuf[0x7c00];
void decryptBlock() {
m_dio->read(m_encBuf, 0x8000);
m_aes->decrypt(&m_encBuf[0x3d0], &m_encBuf[0x400], m_decBuf, 0x7c00);
if (m_aes) {
m_dio->read(m_encBuf, 0x8000);
m_aes->decrypt(&m_encBuf[0x3d0], &m_encBuf[0x400], m_decBuf, 0x7c00);
} else {
m_dio->seek(0x400, SEEK_CUR);
m_dio->read(m_decBuf, 0x7c00);
}
}
public:
PartReadStream(const PartitionWii& parent, uint64_t baseOffset, uint64_t offset, bool& err)
: m_aes(NewAES()), m_parent(parent), m_baseOffset(baseOffset), m_offset(offset) {
m_aes->setKey(parent.m_decKey);
: m_parent(parent), m_baseOffset(baseOffset), m_offset(offset) {
if (m_parent.m_parent.getDiscIO().hasWiiCrypto()) {
m_aes = NewAES();
m_aes->setKey(parent.m_decKey);
}
size_t block = m_offset / 0x7c00;
m_dio = m_parent.m_parent.getDiscIO().beginReadStream(m_baseOffset + block * 0x8000);
if (!m_dio) {
@@ -365,27 +373,25 @@ public:
}
uint64_t position() const override { return m_offset; }
uint64_t read(void* buf, uint64_t length) override {
size_t block = m_offset / 0x7c00;
size_t cacheOffset = m_offset % 0x7c00;
uint64_t cacheSize;
auto blockAndRemOff = nod::div(m_offset, uint64_t(0x7c00));
uint64_t rem = length;
uint8_t* dst = (uint8_t*)buf;
while (rem) {
if (block != m_curBlock) {
if (blockAndRemOff.quot != m_curBlock) {
decryptBlock();
m_curBlock = block;
m_curBlock = blockAndRemOff.quot;
}
cacheSize = rem;
if (cacheSize + cacheOffset > 0x7c00)
cacheSize = 0x7c00 - cacheOffset;
uint64_t cacheSize = rem;
if (cacheSize + blockAndRemOff.rem > 0x7c00)
cacheSize = 0x7c00 - blockAndRemOff.rem;
memmove(dst, m_decBuf + cacheOffset, cacheSize);
memmove(dst, m_decBuf + blockAndRemOff.rem, cacheSize);
dst += cacheSize;
rem -= cacheSize;
cacheOffset = 0;
++block;
blockAndRemOff.rem = 0;
++blockAndRemOff.quot;
}
m_offset += length;
@@ -1233,7 +1239,7 @@ std::optional<uint64_t> DiscBuilderWii::CalculateTotalSizeRequired(SystemStringV
std::optional<uint64_t> sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dirIn, PartitionKind::Data, true);
if (!sz)
return sz;
auto szDiv = std::lldiv(*sz, 0x1F0000);
auto szDiv = nod::div(*sz, uint64_t(0x1F0000));
if (szDiv.rem)
++szDiv.quot;
sz = szDiv.quot * 0x200000;
@@ -1340,7 +1346,7 @@ std::optional<uint64_t> DiscMergerWii::CalculateTotalSizeRequired(DiscWii& sourc
std::optional<uint64_t> sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeMerge(sourceDisc.getDataPartition(), dirIn);
if (!sz)
return std::nullopt;
auto szDiv = std::lldiv(*sz, 0x1F0000);
auto szDiv = nod::div(*sz, uint64_t(0x1F0000));
if (szDiv.rem)
++szDiv.quot;
sz = szDiv.quot * 0x200000;

View File

@@ -77,9 +77,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IWriteStream>(new WriteStream(m_path, m_maxWriteSize, err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
@@ -88,9 +87,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IWriteStream>(new WriteStream(m_path, offset, m_maxWriteSize, err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
@@ -137,9 +135,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IReadStream>(new ReadStream(m_path, err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
@@ -148,9 +145,8 @@ public:
bool err = false;
auto ret = std::unique_ptr<IReadStream>(new ReadStream(m_path, offset, err));
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}

View File

@@ -12,6 +12,7 @@ logvisor::Module LogModule("nod");
std::unique_ptr<IDiscIO> NewDiscIOISO(SystemStringView path);
std::unique_ptr<IDiscIO> NewDiscIOWBFS(SystemStringView path);
std::unique_ptr<IDiscIO> NewDiscIONFS(SystemStringView path);
std::unique_ptr<DiscBase> OpenDiscFromImage(SystemStringView path, bool& isWii) {
/* Temporary file handle to determine image type */
@@ -32,9 +33,17 @@ std::unique_ptr<DiscBase> OpenDiscFromImage(SystemStringView path, bool& isWii)
return {};
}
using SignedSize = std::make_signed<SystemString::size_type>::type;
const auto dotPos = SignedSize(path.rfind(_SYS_STR('.')));
const auto slashPos = SignedSize(path.find_last_of(_SYS_STR("/\\")));
if (magic == nod::SBig((uint32_t)'WBFS')) {
discIO = NewDiscIOWBFS(path);
isWii = true;
} else if (path.size() > 4 && dotPos != -1 && dotPos > slashPos &&
!path.compare(slashPos + 1, 4, _SYS_STR("hif_")) &&
!path.compare(dotPos, path.size() - dotPos, _SYS_STR(".nfs"))) {
discIO = NewDiscIONFS(path);
isWii = true;
} else {
rs->seek(0x18, SEEK_SET);
rs->read(&magic, 4);
@@ -59,16 +68,14 @@ std::unique_ptr<DiscBase> OpenDiscFromImage(SystemStringView path, bool& isWii)
std::unique_ptr<DiscBase> ret;
if (isWii) {
ret = std::make_unique<DiscWii>(std::move(discIO), err);
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}
ret = std::make_unique<DiscGCN>(std::move(discIO), err);
if (err) {
return nullptr;
}
if (err)
return {};
return ret;
}