From 3de662940bcf7d6a6cf69d311b25e5750ddee021 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Mon, 25 Jan 2016 10:21:10 -1000 Subject: [PATCH] Codebase cleanups and README --- README.md | 81 ++++++++++++++++++++ driver/main.cpp | 25 +++--- lib/CMakeLists.txt | 5 +- lib/DiscBase.cpp | 16 ++-- lib/FileIOFILE.cpp | 111 +-------------------------- lib/FileIOWin32.cpp | 183 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 299 insertions(+), 122 deletions(-) create mode 100644 README.md create mode 100644 lib/FileIOWin32.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..c53da91 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +### NODLib + +**NODLib** is a library and utility (`nodtool`) for traversing, dumping, and authoring +*GameCube* and *Wii* optical disc images. + +#### Library + +The primary motivation of NODLib is to supply a *uniform C++11 API* for accessing data +from image files directly. `NOD::DiscBase` provides a common interface for traversing partitions +and individual files. Files may be individually streamed, or the whole partition may be extracted +to the user's filesystem. Raw *ISO* and *WBFS* images are supported read sources. + +```cpp +bool isWii; /* Set by reference next line */ +std::unique_ptr disc = NOD::OpenDiscFromImage(path, isWii); +if (!disc) + return FAILURE; + +/* Access first data-partition on Wii, or full GameCube disc */ +NOD::Partition* dataPart = disc->getDataPartition(); +if (!dataPart) + return FAILURE; + +/* One-shot extraction to filesystem */ +if (!dataPart->extractToDirectory(outDir, ctx)) + return FAILURE; + +return SUCCESS; +``` + +Authoring images is always done from the user's filesystem and may be integrated into +a content pipeline using the `NOD::DiscBuilderBase` interface. + +```cpp +/* Sample logging lambda for progress feedback */ +size_t lastIdx = -1; +auto progFunc = [&](size_t idx, const NOD::SystemString& name, size_t bytes) +{ + if (idx != lastIdx) + { + lastIdx = idx; + /* NOD provides I/O wrappers using wchar_t on Windows; + * _S() conditionally makes string-literals wide */ + NOD::Printf(_S("\n")); + } + if (bytes != -1) + NOD::Printf(_S("\r%s %" PRISize " B"), name.c_str(), bytes); + else + NOD::Printf(_S("\r%s"), name.c_str()); + fflush(stdout); +}; + +/* Making a GCN image */ +NOD::DiscBuilderGCN b(isoOutPath, gameID, gameTitle, dolLoadAddress, progFunc); +ret = b.buildFromDirectory(fsRootDirPath, boolDolPath, apploaderPath); + +/* Making a Wii image */ +NOD::DiscBuilderWii b(isoOutPath, gameID, gameTitle, dualLayer, progFunc); +ret = b.buildFromDirectory(fsRootDirPath, boolDolPath, apploaderPath, partitionHeadPath); +``` + +Wii images are fakesigned using a commonly-applied [signing bug](http://wiibrew.org/wiki/Signing_bug). + +Additionally, any `*.dol` files added to the disc are patched to bypass the #001 error caused by invalid signature checks. +This allows games with multiple .dols to inter-boot without extensive loader-patching. + +#### Tool + +The library usage mentioned above is provided by a command-line tool called `nodtool`. + +An extract/repack works like so: + +```sh +>$ nodtool extract [] +>$ cd + +# Then one of: +>$ nodtool makegcn fsroot boot.dol apploader.bin [] +>$ nodtool makewiisl fsroot boot.dol apploader.bin partition_head.bin [] +>$ nodtool makewiidl fsroot boot.dol apploader.bin partition_head.bin [] +``` diff --git a/driver/main.cpp b/driver/main.cpp index c658245..b444a94 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -29,7 +29,7 @@ int main(int argc, char* argv[]) (!strcasecmp(argv[1], _S("makewiidl")) && argc < 8)) { printHelp(); - return -1; + return 1; } /* Enable logging to console */ @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) bool isWii; std::unique_ptr disc = NOD::OpenDiscFromImage(inDir, isWii); if (!disc) - return -1; + return 1; NOD::Mkdir(outDir, 0755); @@ -69,10 +69,10 @@ int main(int argc, char* argv[]) NOD::Partition* dataPart = disc->getDataPartition(); if (!dataPart) - return -1; + return 1; if (!dataPart->extractToDirectory(outDir, ctx)) - return -1; + return 1; } else if (!strcasecmp(argv[1], _S("makegcn"))) { @@ -110,20 +110,24 @@ int main(int argc, char* argv[]) fflush(stdout); }; + bool ret; + if (argc < 8) { NOD::SystemString outPath(argv[4]); outPath.append(_S(".iso")); NOD::DiscBuilderGCN b(outPath.c_str(), gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), 0x0003EB60, progFunc); - b.buildFromDirectory(argv[4], argv[5], argv[6]); + ret = b.buildFromDirectory(argv[4], argv[5], argv[6]); } else { NOD::DiscBuilderGCN b(argv[7], gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), 0x0003EB60, progFunc); - b.buildFromDirectory(argv[4], argv[5], argv[6]); + ret = b.buildFromDirectory(argv[4], argv[5], argv[6]); } printf("\n"); + if (!ret) + return 1; } else if (!strcasecmp(argv[1], _S("makewiisl")) || !strcasecmp(argv[1], _S("makewiidl"))) { @@ -164,26 +168,29 @@ int main(int argc, char* argv[]) }; bool dual = (argv[1][7] == _S('d') || argv[1][7] == _S('D')); + bool ret; if (argc < 9) { NOD::SystemString outPath(argv[4]); outPath.append(_S(".iso")); NOD::DiscBuilderWii b(outPath.c_str(), gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), dual, progFunc); - b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); + ret = b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); } else { NOD::DiscBuilderWii b(argv[8], gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), dual, progFunc); - b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); + ret = b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); } printf("\n"); + if (!ret) + return 1; } else { printHelp(); - return -1; + return 1; } return 0; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a717f82..5f21860 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,5 +1,8 @@ if(NOT WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar") +set(PLAT_SRCS FileIOFILE.cpp) +else() +set(PLAT_SRCS FileIOWin32.cpp) endif() add_library(NOD aes.cpp @@ -9,9 +12,9 @@ add_library(NOD DiscIOISO.cpp DiscIOWBFS.cpp DiscWii.cpp - FileIOFILE.cpp DirectoryEnumerator.cpp NOD.cpp + ${PLAT_SRCS} ${NOD_HEADERS}) if(NOT WIN32) set_source_files_properties(aes.cpp PROPERTIES COMPILE_FLAGS -maes) diff --git a/lib/DiscBase.cpp b/lib/DiscBase.cpp index fddbbbb..eb31c8d 100644 --- a/lib/DiscBase.cpp +++ b/lib/DiscBase.cpp @@ -222,15 +222,19 @@ static bool IsSystemFile(const SystemString& name, bool& isDol) /** Patches out pesky #001 integrity check performed by game's OSInit. * This is required for multi-DOL games, but doesn't harm functionality otherwise */ -static size_t PatchDOL(IFileIO::IReadStream& in, IPartWriteStream& out, size_t sz) +static size_t PatchDOL(IFileIO::IReadStream& in, IPartWriteStream& out, size_t sz, bool& patched) { + patched = false; std::unique_ptr buf(new uint8_t[sz]); sz = in.read(buf.get(), sz); uint8_t* found = static_cast(memmem(buf.get(), sz, "\x3C\x03\xF8\x00\x28\x00\x00\x00\x40\x82\x00\x0C" "\x38\x60\x00\x01\x48\x00\x02\x44\x38\x61\x00\x18\x48", 25)); if (found) + { found[11] = '\x04'; + patched = true; + } return out.write(buf.get(), sz); } @@ -263,8 +267,9 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream size_t xferSz = 0; if (isDol) { - xferSz = PatchDOL(*rs, ws, e.m_fileSz); - m_parent.m_progressCB(++m_parent.m_progressIdx, e.m_name + _S(" [PATCHED]"), xferSz); + bool patched; + xferSz = PatchDOL(*rs, ws, e.m_fileSz, patched); + m_parent.m_progressCB(++m_parent.m_progressIdx, e.m_name + (patched ? _S(" [PATCHED]") : _S("")), xferSz); } else { @@ -344,8 +349,9 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream& m_dolOffset = fileOff; m_dolSize = fileSz; std::unique_ptr rs = NewFileIO(dolIn)->beginReadStream(); - size_t xferSz = PatchDOL(*rs, ws, dolStat.st_size); - m_parent.m_progressCB(++m_parent.m_progressIdx, SystemString(dolIn) + _S(" [PATCHED]"), xferSz); + bool patched; + size_t xferSz = PatchDOL(*rs, ws, dolStat.st_size, patched); + m_parent.m_progressCB(++m_parent.m_progressIdx, SystemString(dolIn) + (patched ? _S(" [PATCHED]") : _S("")), xferSz); for (size_t i=0 ; i(b))?(a):(b)) - namespace NOD { @@ -26,38 +17,15 @@ public: bool exists() { -#if _WIN32 - HANDLE fp = CreateFileW(m_path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (fp == INVALID_HANDLE_VALUE) - return false; - CloseHandle(fp); - return true; -#else FILE* fp = fopen(m_path.c_str(), "rb"); if (!fp) return false; fclose(fp); return true; -#endif } uint64_t size() { -#if _WIN32 - HANDLE fp = CreateFileW(m_path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (fp == INVALID_HANDLE_VALUE) - return 0; - LARGE_INTEGER sz; - if (!GetFileSizeEx(fp, &sz)) - { - CloseHandle(fp); - return 0; - } - CloseHandle(fp); - return sz.QuadPart; -#else FILE* fp = fopen(m_path.c_str(), "rb"); if (!fp) return 0; @@ -65,44 +33,20 @@ public: uint64_t result = ftello(fp); fclose(fp); return result; -#endif } struct WriteStream : public IFileIO::IWriteStream { uint8_t buf[0x7c00]; -#if _WIN32 - HANDLE fp; -#else FILE* fp; -#endif WriteStream(const SystemString& path) { -#if _WIN32 - fp = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, - nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (fp == INVALID_HANDLE_VALUE) - goto FailLoc; -#else fp = fopen(path.c_str(), "wb"); if (!fp) - goto FailLoc; -#endif - return; - FailLoc: - LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for writing"), path.c_str()); + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for writing"), path.c_str()); } WriteStream(const SystemString& path, uint64_t offset) { -#if _WIN32 - fp = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, - nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (fp == INVALID_HANDLE_VALUE) - goto FailLoc; - LARGE_INTEGER lioffset; - lioffset.QuadPart = offset; - SetFilePointerEx(fp, lioffset, nullptr, FILE_BEGIN); -#else fp = fopen(path.c_str(), "ab"); if (!fp) goto FailLoc; @@ -111,35 +55,24 @@ public: if (!fp) goto FailLoc; FSeek(fp, offset, SEEK_SET); -#endif return; FailLoc: LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for writing"), path.c_str()); } ~WriteStream() { -#if _WIN32 - CloseHandle(fp); -#else fclose(fp); -#endif } uint64_t write(const void* buf, uint64_t length) { -#if _WIN32 - DWORD ret = 0; - WriteFile(fp, buf, length, &ret, nullptr); - return ret; -#else return fwrite(buf, 1, length, fp); -#endif } uint64_t copyFromDisc(IPartReadStream& discio, uint64_t length) { uint64_t read = 0; while (length) { - uint64_t thisSz = MIN(0x7c00, length); + uint64_t thisSz = NOD::min(uint64_t(0x7c00), length); uint64_t readSz = discio.read(buf, thisSz); if (thisSz != readSz) { @@ -168,73 +101,37 @@ public: struct ReadStream : public IFileIO::IReadStream { -#if _WIN32 - HANDLE fp; -#else FILE* fp; -#endif uint8_t buf[0x7c00]; ReadStream(const SystemString& path) { -#if _WIN32 - fp = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (fp == INVALID_HANDLE_VALUE) - goto FailLoc; -#else fp = fopen(path.c_str(), "rb"); if (!fp) - goto FailLoc; -#endif - return; - FailLoc: - LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for reading"), path.c_str()); + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for reading"), path.c_str()); } ReadStream(const SystemString& path, uint64_t offset) : ReadStream(path) { -#if _WIN32 - LARGE_INTEGER lioffset; - lioffset.QuadPart = offset; - SetFilePointerEx(fp, lioffset, nullptr, FILE_BEGIN); -#else FSeek(fp, offset, SEEK_SET); -#endif } ~ReadStream() { -#if _WIN32 - CloseHandle(fp); -#else fclose(fp); -#endif } void seek(int64_t offset, int whence) { -#if _WIN32 - LARGE_INTEGER li; - li.QuadPart = offset; - SetFilePointerEx(fp, li, nullptr, whence); -#else FSeek(fp, offset, whence); -#endif } uint64_t read(void* buf, uint64_t length) { -#if _WIN32 - DWORD ret = 0; - ReadFile(fp, buf, length, &ret, nullptr); - return ret; -#else return fread(buf, 1, length, fp); -#endif } uint64_t copyToDisc(IPartWriteStream& discio, uint64_t length) { uint64_t written = 0; while (length) { - uint64_t thisSz = MIN(0x7c00, length); + uint64_t thisSz = NOD::min(uint64_t(0x7c00), length); if (read(buf, thisSz) != thisSz) { LogModule.report(LogVisor::FatalError, "unable to read enough from file"); diff --git a/lib/FileIOWin32.cpp b/lib/FileIOWin32.cpp new file mode 100644 index 0000000..6b17b26 --- /dev/null +++ b/lib/FileIOWin32.cpp @@ -0,0 +1,183 @@ +#include +#include +#include "NOD/Util.hpp" +#include "NOD/IFileIO.hpp" + +namespace NOD +{ + +class FileIOWin32 : public IFileIO +{ + SystemString m_path; +public: + FileIOWin32(const SystemString& path) + : m_path(path) {} + FileIOWin32(const SystemChar* path) + : m_path(path) {} + + bool exists() + { + HANDLE fp = CreateFileW(m_path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fp == INVALID_HANDLE_VALUE) + return false; + CloseHandle(fp); + return true; + } + + uint64_t size() + { + HANDLE fp = CreateFileW(m_path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fp == INVALID_HANDLE_VALUE) + return 0; + LARGE_INTEGER sz; + if (!GetFileSizeEx(fp, &sz)) + { + CloseHandle(fp); + return 0; + } + CloseHandle(fp); + return sz.QuadPart; + } + + struct WriteStream : public IFileIO::IWriteStream + { + uint8_t buf[0x7c00]; + HANDLE fp; + WriteStream(const SystemString& path) + { + fp = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, + nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fp == INVALID_HANDLE_VALUE) + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for writing"), path.c_str()); + } + WriteStream(const SystemString& path, uint64_t offset) + { + fp = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, + nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fp == INVALID_HANDLE_VALUE) + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for writing"), path.c_str()); + LARGE_INTEGER lioffset; + lioffset.QuadPart = offset; + SetFilePointerEx(fp, lioffset, nullptr, FILE_BEGIN); + } + ~WriteStream() + { + CloseHandle(fp); + } + uint64_t write(const void* buf, uint64_t length) + { + DWORD ret = 0; + WriteFile(fp, buf, length, &ret, nullptr); + return ret; + } + uint64_t copyFromDisc(IPartReadStream& discio, uint64_t length) + { + uint64_t read = 0; + while (length) + { + uint64_t thisSz = NOD::min(uint64_t(0x7c00), length); + uint64_t readSz = discio.read(buf, thisSz); + if (thisSz != readSz) + { + LogModule.report(LogVisor::FatalError, "unable to read enough from disc"); + return read; + } + if (write(buf, readSz) != readSz) + { + LogModule.report(LogVisor::FatalError, "unable to write in file"); + return read; + } + length -= thisSz; + read += thisSz; + } + return read; + } + }; + std::unique_ptr beginWriteStream() const + { + return std::unique_ptr(new WriteStream(m_path)); + } + std::unique_ptr beginWriteStream(uint64_t offset) const + { + return std::unique_ptr(new WriteStream(m_path, offset)); + } + + struct ReadStream : public IFileIO::IReadStream + { + HANDLE fp; + uint8_t buf[0x7c00]; + ReadStream(const SystemString& path) + { + fp = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fp == INVALID_HANDLE_VALUE) + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for reading"), path.c_str()); + } + ReadStream(const SystemString& path, uint64_t offset) + : ReadStream(path) + { + LARGE_INTEGER lioffset; + lioffset.QuadPart = offset; + SetFilePointerEx(fp, lioffset, nullptr, FILE_BEGIN); + } + ~ReadStream() + { + CloseHandle(fp); + } + void seek(int64_t offset, int whence) + { + LARGE_INTEGER li; + li.QuadPart = offset; + SetFilePointerEx(fp, li, nullptr, whence); + } + uint64_t read(void* buf, uint64_t length) + { + DWORD ret = 0; + ReadFile(fp, buf, length, &ret, nullptr); + return ret; + } + uint64_t copyToDisc(IPartWriteStream& discio, uint64_t length) + { + uint64_t written = 0; + while (length) + { + uint64_t thisSz = NOD::min(uint64_t(0x7c00), length); + if (read(buf, thisSz) != thisSz) + { + LogModule.report(LogVisor::FatalError, "unable to read enough from file"); + return written; + } + if (discio.write(buf, thisSz) != thisSz) + { + LogModule.report(LogVisor::FatalError, "unable to write enough to disc"); + return written; + } + length -= thisSz; + written += thisSz; + } + return written; + } + }; + std::unique_ptr beginReadStream() const + { + return std::unique_ptr(new ReadStream(m_path)); + } + std::unique_ptr beginReadStream(uint64_t offset) const + { + return std::unique_ptr(new ReadStream(m_path, offset)); + } +}; + +std::unique_ptr NewFileIO(const SystemString& path) +{ + return std::unique_ptr(new FileIOWin32(path)); +} + +std::unique_ptr NewFileIO(const SystemChar* path) +{ + return std::unique_ptr(new FileIOWin32(path)); +} + +}