From 7403996ed3daa7317c40d8b4be3a55d278586446 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Thu, 21 Jan 2016 16:30:17 -1000 Subject: [PATCH] Initial Wii image generation (needs disc header) --- CMakeLists.txt | 2 +- driver/main.cpp | 55 +++++- include/NOD/DiscBase.hpp | 11 +- include/NOD/DiscWii.hpp | 13 +- include/NOD/IFileIO.hpp | 4 +- include/NOD/sha1.h | 45 +++++ lib/CMakeLists.txt | 1 + lib/DiscBase.cpp | 12 +- lib/DiscGCN.cpp | 13 +- lib/DiscWii.cpp | 365 +++++++++++++++++++++++++++++++++++++++ lib/FileIOFILE.cpp | 12 +- lib/sha1.c | 288 ++++++++++++++++++++++++++++++ 12 files changed, 789 insertions(+), 32 deletions(-) create mode 100644 include/NOD/sha1.h create mode 100644 lib/sha1.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 45f4abf..b8e59b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ if (NOT TARGET LogVisor) endif() include_directories(include ${LOG_VISOR_INCLUDE_DIR}) -file(GLOB NOD_HEADERS include/NOD/*.hpp) +file(GLOB NOD_HEADERS include/NOD/*.h*) add_subdirectory(lib) add_subdirectory(driver) diff --git a/driver/main.cpp b/driver/main.cpp index 9477ac0..305749d 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -8,7 +8,7 @@ static void printHelp() fprintf(stderr, "Usage:\n" " nodtool extract [-f] []\n" " nodtool makegcn []\n" - " nodtool makewii [] [-u ]\n"); + " nodtool makewii []\n"); } #if NOD_UCS2 @@ -25,7 +25,7 @@ int main(int argc, char* argv[]) { if (argc < 3 || (!strcasecmp(argv[1], _S("makegcn")) && argc < 7) || - (!strcasecmp(argv[1], _S("makewii")) && argc < 7)) + (!strcasecmp(argv[1], _S("makewii")) && argc < 8)) { printHelp(); return -1; @@ -115,6 +115,57 @@ int main(int argc, char* argv[]) printf("\n"); } + else if (!strcasecmp(argv[1], _S("makewii"))) + { +#if NOD_UCS2 + if (_wcslen(argv[2]) < 6) + NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters"); +#else + if (strlen(argv[2]) < 6) + NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters"); +#endif + + /* Pre-validate paths */ + NOD::Sstat theStat; + if (NOD::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode)) + NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as directory", argv[4]); + if (NOD::Stat(argv[5], &theStat) || !S_ISREG(theStat.st_mode)) + NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[5]); + if (NOD::Stat(argv[6], &theStat) || !S_ISREG(theStat.st_mode)) + NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[6]); + if (NOD::Stat(argv[7], &theStat) || !S_ISREG(theStat.st_mode)) + NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[7]); + + size_t lastIdx = -1; + auto progFunc = [&](size_t idx, const NOD::SystemString& name, size_t bytes) + { + if (idx != lastIdx) + { + lastIdx = idx; + printf("\n"); + } + if (bytes != -1) + printf("\r%s %" PRISize " B", name.c_str(), bytes); + else + printf("\r%s", name.c_str()); + fflush(stdout); + }; + + if (argc < 9) + { + NOD::SystemString outPath(argv[4]); + outPath.append(_S(".iso")); + NOD::DiscBuilderWii b(outPath.c_str(), argv[2], argv[3], progFunc); + b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); + } + else + { + NOD::DiscBuilderWii b(argv[8], argv[2], argv[3], progFunc); + b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); + } + + printf("\n"); + } else { printHelp(); diff --git a/include/NOD/DiscBase.hpp b/include/NOD/DiscBase.hpp index 465cadc..dbf615c 100644 --- a/include/NOD/DiscBase.hpp +++ b/include/NOD/DiscBase.hpp @@ -325,16 +325,14 @@ public: DiscBuilderBase& m_parent; Kind m_kind; - uint64_t m_offset; char m_gameID[6]; std::string m_gameTitle; - uint32_t m_fstMemoryAddr; uint64_t m_dolOffset = 0; public: - IPartitionBuilder(DiscBuilderBase& parent, Kind kind, uint64_t offset, - const char gameID[6], const char* gameTitle, uint32_t fstMemoryAddr) - : m_parent(parent), m_kind(kind), m_offset(offset), m_gameTitle(gameTitle), m_fstMemoryAddr(fstMemoryAddr) + IPartitionBuilder(DiscBuilderBase& parent, Kind kind, + const char gameID[6], const char* gameTitle) + : m_parent(parent), m_kind(kind), m_gameTitle(gameTitle) { memcpy(m_gameID, gameID, 6); } @@ -353,9 +351,6 @@ public: : m_fileIO(std::move(fio)), m_progressCB(progressCB) {} IFileIO& getFileIO() {return *m_fileIO;} - - virtual bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, - const SystemChar* apploaderIn)=0; }; using Partition = DiscBase::IPartition; diff --git a/include/NOD/DiscWii.hpp b/include/NOD/DiscWii.hpp index c4cb95e..ea30ab1 100644 --- a/include/NOD/DiscWii.hpp +++ b/include/NOD/DiscWii.hpp @@ -10,9 +10,16 @@ class DiscWii : public DiscBase { public: DiscWii(std::unique_ptr&& dio); - DiscWii(const SystemChar* dataPath, const SystemChar* updatePath, - const SystemChar* outPath, const char gameID[6], const char* gameTitle, - bool korean=false); +}; + +class DiscBuilderWii : public DiscBuilderBase +{ + const SystemChar* m_outPath; +public: + DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, + std::function progressCB); + bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, + const SystemChar* apploaderIn, const SystemChar* partHeadIn); }; } diff --git a/include/NOD/IFileIO.hpp b/include/NOD/IFileIO.hpp index 8bf736d..b57bfdd 100644 --- a/include/NOD/IFileIO.hpp +++ b/include/NOD/IFileIO.hpp @@ -22,7 +22,7 @@ public: virtual uint64_t copyFromDisc(struct IPartReadStream& discio, uint64_t length)=0; }; virtual std::unique_ptr beginWriteStream() const=0; - virtual std::unique_ptr beginWriteStream(size_t offset) const=0; + virtual std::unique_ptr beginWriteStream(uint64_t offset) const=0; struct IReadStream { @@ -31,7 +31,7 @@ public: virtual uint64_t copyToDisc(struct IPartWriteStream& discio, uint64_t length)=0; }; virtual std::unique_ptr beginReadStream() const=0; - virtual std::unique_ptr beginReadStream(size_t offset) const=0; + virtual std::unique_ptr beginReadStream(uint64_t offset) const=0; }; std::unique_ptr NewFileIO(const SystemString& path); diff --git a/include/NOD/sha1.h b/include/NOD/sha1.h new file mode 100644 index 0000000..df6445a --- /dev/null +++ b/include/NOD/sha1.h @@ -0,0 +1,45 @@ +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA1_HASH_LENGTH 20 +#define SHA1_BLOCK_LENGTH 64 + +typedef struct sha1nfo { + uint32_t buffer[SHA1_BLOCK_LENGTH/4]; + uint32_t state[SHA1_HASH_LENGTH/4]; + uint32_t byteCount; + uint8_t bufferOffset; + uint8_t keyBuffer[SHA1_BLOCK_LENGTH]; + uint8_t innerHash[SHA1_HASH_LENGTH]; +} sha1nfo; + +/** + */ +void sha1_init(sha1nfo *s); +/** + */ +void sha1_writebyte(sha1nfo *s, uint8_t data); +/** + */ +void sha1_write(sha1nfo *s, const char *data, size_t len); +/** + */ +uint8_t* sha1_result(sha1nfo *s); +/** + */ +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); +/** + */ +uint8_t* sha1_resultHmac(sha1nfo *s); + +#ifdef __cplusplus +} +#endif + +#endif // __SHA1_H__ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7fbb994..a717f82 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -3,6 +3,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar") endif() add_library(NOD aes.cpp + sha1.c DiscBase.cpp DiscGCN.cpp DiscIOISO.cpp diff --git a/lib/DiscBase.cpp b/lib/DiscBase.cpp index a376789..b7d560b 100644 --- a/lib/DiscBase.cpp +++ b/lib/DiscBase.cpp @@ -141,17 +141,17 @@ static uint64_t GetInode(const SystemChar* path) OFSTRUCT ofs; HFILE fp = OpenFile(path, &ofs, OF_READ); if (fp == HFILE_ERROR) - LogModule.report(LogVisor::FatalError, "unable to open %s", path); + LogModule.report(LogVisor::FatalError, _S("unable to open %s"), path); BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(fp, &info)) - LogModule.report(LogVisor::FatalError, "unable to GetFileInformationByHandle %s", path); + LogModule.report(LogVisor::FatalError, _S("unable to GetFileInformationByHandle %s"), path); inode = info.nFileIndexHigh << 32; inode |= info.nFileIndexLow; CloseHandle(fp); #else struct stat st; if (stat(path, &st)) - LogModule.report(LogVisor::FatalError, "unable to stat %s", path); + LogModule.report(LogVisor::FatalError, _S("unable to stat %s"), path); inode = uint64_t(st.st_ino); #endif return inode; @@ -181,7 +181,7 @@ void DiscBuilderBase::IPartitionBuilder::recursiveBuildNodes(const SystemChar* d std::unique_ptr ws = m_parent.getFileIO().beginWriteStream(fileOff); FILE* fp = Fopen(e.m_path.c_str(), _S("rb"), FileLockType::Read); if (!fp) - LogModule.report(LogVisor::FatalError, "unable to open '%s' for reading", e.m_path.c_str()); + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for reading"), e.m_path.c_str()); char buf[8192]; size_t xferSz = 0; ++m_parent.m_progressIdx; @@ -224,14 +224,14 @@ bool DiscBuilderBase::IPartitionBuilder::buildFromDirectory(const SystemChar* di { Sstat dolStat; if (Stat(dolIn, &dolStat)) - LogModule.report(LogVisor::FatalError, "unable to stat %s", dolIn); + LogModule.report(LogVisor::FatalError, _S("unable to stat %s"), dolIn); size_t fileSz = ROUND_UP_32(dolStat.st_size); uint64_t fileOff = userAllocate(fileSz); m_dolOffset = fileOff; std::unique_ptr ws = m_parent.getFileIO().beginWriteStream(fileOff); FILE* fp = Fopen(dolIn, _S("rb"), FileLockType::Read); if (!fp) - LogModule.report(LogVisor::FatalError, "unable to open '%s' for reading", dolIn); + LogModule.report(LogVisor::FatalError, _S("unable to open '%s' for reading"), dolIn); char buf[8192]; size_t xferSz = 0; SystemString dolName(dolIn); diff --git a/lib/DiscGCN.cpp b/lib/DiscGCN.cpp index f0c94cc..de6b037 100644 --- a/lib/DiscGCN.cpp +++ b/lib/DiscGCN.cpp @@ -112,10 +112,11 @@ DiscGCN::DiscGCN(std::unique_ptr&& dio) class PartitionBuilderGCN : public DiscBuilderBase::IPartitionBuilder { uint64_t m_curUser = 0x57058000; + uint32_t m_fstMemoryAddr; public: - PartitionBuilderGCN(DiscBuilderBase& parent, Kind kind, uint64_t offset, + PartitionBuilderGCN(DiscBuilderBase& parent, Kind kind, const char gameID[6], const char* gameTitle, uint32_t fstMemoryAddr) - : DiscBuilderBase::IPartitionBuilder(parent, kind, offset, gameID, gameTitle, fstMemoryAddr) {} + : DiscBuilderBase::IPartitionBuilder(parent, kind, gameID, gameTitle), m_fstMemoryAddr(fstMemoryAddr) {} uint64_t userAllocate(uint64_t reqSz) { @@ -142,7 +143,7 @@ public: ws = m_parent.getFileIO().beginWriteStream(0x2440); FILE* fp = Fopen(apploaderIn, _S("rb"), FileLockType::Read); if (!fp) - LogModule.report(LogVisor::FatalError, "unable to open %s for reading", apploaderIn); + LogModule.report(LogVisor::FatalError, _S("unable to open %s for reading"), apploaderIn); char buf[8192]; size_t xferSz = 0; SystemString apploaderName(apploaderIn); @@ -172,6 +173,10 @@ public: fstSz += m_buildNameOff; fstSz = ROUND_UP_32(fstSz); + if (fstOff + fstSz >= m_curUser) + LogModule.report(LogVisor::FatalError, + "FST flows into user area (one or the other is too big)"); + ws = m_parent.getFileIO().beginWriteStream(0x420); uint32_t vals[7]; vals[0] = SBig(uint32_t(m_dolOffset)); @@ -198,7 +203,7 @@ DiscBuilderGCN::DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], uint32_t fstMemoryAddr, std::function progressCB) : DiscBuilderBase(std::move(NewFileIO(outPath)), progressCB) { - PartitionBuilderGCN* partBuilder = new PartitionBuilderGCN(*this, IPartitionBuilder::Kind::Data, 0, + PartitionBuilderGCN* partBuilder = new PartitionBuilderGCN(*this, IPartitionBuilder::Kind::Data, gameID, gameTitle, fstMemoryAddr); m_partitions.emplace_back(partBuilder); } diff --git a/lib/DiscWii.cpp b/lib/DiscWii.cpp index 7f009d8..d9ab743 100644 --- a/lib/DiscWii.cpp +++ b/lib/DiscWii.cpp @@ -2,6 +2,7 @@ #include #include "NOD/DiscWii.hpp" #include "NOD/aes.hpp" +#include "NOD/sha1.h" namespace NOD { @@ -390,4 +391,368 @@ DiscWii::DiscWii(std::unique_ptr&& dio) } } +class PartitionBuilderWii : public DiscBuilderBase::IPartitionBuilder +{ + uint64_t m_curUser = 0x40000; +public: + PartitionBuilderWii(DiscBuilderBase& parent, Kind kind, + const char gameID[6], const char* gameTitle) + : DiscBuilderBase::IPartitionBuilder(parent, kind, gameID, gameTitle) {} + + uint64_t getCurUserEnd() const {return m_curUser;} + + uint64_t userAllocate(uint64_t reqSz) + { + reqSz = ROUND_UP_32(reqSz); + if (m_curUser + reqSz >= 0x1FB450000) + { + LogModule.report(LogVisor::FatalError, "partition exceeds maximum single-partition capacity"); + return -1; + } + uint64_t ret = m_curUser; + m_curUser += reqSz; + return ret; + } + + bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) + { + bool result = DiscBuilderBase::IPartitionBuilder::buildFromDirectory(dirIn, dolIn, apploaderIn); + if (!result) + return false; + + std::unique_ptr ws; + + /* Pad out user area to nearest cleartext sector */ + uint64_t curUserRem = m_curUser % 0x1F0000; + if (curUserRem) + { + ws = m_parent.getFileIO().beginWriteStream(m_curUser); + curUserRem = 0x1F0000 - curUserRem; + for (size_t i=0 ; iwrite("\xff", 1); + m_curUser += curUserRem; + } + + ws = m_parent.getFileIO().beginWriteStream(0); + Header header(m_gameID, m_gameTitle.c_str(), true); + header.write(*ws); + + ws = m_parent.getFileIO().beginWriteStream(0x2440); + FILE* fp = Fopen(apploaderIn, _S("rb"), FileLockType::Read); + if (!fp) + LogModule.report(LogVisor::FatalError, _S("unable to open %s for reading"), apploaderIn); + char buf[8192]; + size_t xferSz = 0; + SystemString apploaderName(apploaderIn); + ++m_parent.m_progressIdx; + while (true) + { + size_t rdSz = fread(buf, 1, 8192, fp); + if (!rdSz) + break; + ws->write(buf, rdSz); + xferSz += rdSz; + if (0x2440 + xferSz >= 0x40000) + LogModule.report(LogVisor::FatalError, + "apploader flows into user area (one or the other is too big)"); + m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); + } + fclose(fp); + + size_t fstOff = ROUND_UP_32(xferSz); + size_t fstSz = sizeof(FSTNode) * m_buildNodes.size(); + for (size_t i=0 ; iwrite("\xff", 1); + fstOff += 0x2440; + ws->write(m_buildNodes.data(), fstSz); + for (const std::string& str : m_buildNames) + ws->write(str.data(), str.size()+1); + fstSz += m_buildNameOff; + fstSz = ROUND_UP_32(fstSz); + + if (fstOff + fstSz >= 0x40000) + LogModule.report(LogVisor::FatalError, + "FST flows into user area (one or the other is too big)"); + + ws = m_parent.getFileIO().beginWriteStream(0x420); + uint32_t vals[4]; + vals[0] = SBig(uint32_t(m_dolOffset >> 2)); + vals[1] = SBig(uint32_t(fstOff >> 2)); + vals[2] = SBig(uint32_t(fstSz)); + vals[3] = SBig(uint32_t(fstSz)); + ws->write(vals, sizeof(vals)); + + return true; + } + + uint64_t cryptAndFakesign(IFileIO& out, uint64_t offset, const SystemChar* partHeadIn) const + { + /* Read head and validate key members */ + FILE* fp = Fopen(partHeadIn, _S("rb"), FileLockType::Read); + if (!fp) + LogModule.report(LogVisor::FatalError, _S("unable to open %s for reading"), partHeadIn); + + uint8_t tkey[16]; + { + fseeko64(fp, 0x1BF, SEEK_SET); + if (fread(tkey, 1, 16, fp) != 16) + LogModule.report(LogVisor::FatalError, _S("unable to read title key from %s"), partHeadIn); + } + + uint8_t tkeyiv[16] = {}; + { + fseeko64(fp, 0x1DC, SEEK_SET); + if (fread(tkeyiv, 1, 8, fp) != 8) + LogModule.report(LogVisor::FatalError, _S("unable to read title key IV from %s"), partHeadIn); + } + + uint8_t ccIdx; + { + fseeko64(fp, 0x1F1, SEEK_SET); + if (fread(&ccIdx, 1, 1, fp) != 1) + LogModule.report(LogVisor::FatalError, _S("unable to read common key index from %s"), partHeadIn); + if (ccIdx > 1) + LogModule.report(LogVisor::FatalError, _S("common key index may only be 0 or 1")); + } + + uint32_t tmdSz; + { + fseeko64(fp, 0x2A4, SEEK_SET); + if (fread(&tmdSz, 1, 4, fp) != 4) + LogModule.report(LogVisor::FatalError, _S("unable to read TMD size from %s"), partHeadIn); + tmdSz = SBig(tmdSz); + } + + uint64_t h3Off; + { + uint32_t h3Ptr; + fseeko64(fp, 0x2B4, SEEK_SET); + if (fread(&h3Ptr, 1, 4, fp) != 4) + LogModule.report(LogVisor::FatalError, _S("unable to read H3 pointer from %s"), partHeadIn); + h3Off = uint64_t(SBig(h3Ptr)) << 2; + } + + uint64_t dataOff; + { + uint32_t dataPtr; + if (fread(&dataPtr, 1, 4, fp) != 4) + LogModule.report(LogVisor::FatalError, _S("unable to read data pointer from %s"), partHeadIn); + dataOff = uint64_t(SBig(dataPtr)) << 2; + } + + std::unique_ptr tmdData(new uint8_t[tmdSz]); + fseeko64(fp, 0x2C0, SEEK_SET); + if (fread(tmdData.get(), 1, tmdSz, fp) != tmdSz) + LogModule.report(LogVisor::FatalError, _S("unable to read TMD from %s"), partHeadIn); + + /* Copy partition head up to H3 table */ + std::unique_ptr ws = out.beginWriteStream(offset); + { + uint64_t remCopy = h3Off; + uint8_t copyBuf[8192]; + fseeko64(fp, 0, SEEK_SET); + while (remCopy) + { + size_t rdBytes = fread(copyBuf, 1, std::min(8192ul, remCopy), fp); + if (rdBytes) + { + ws->write(copyBuf, rdBytes); + remCopy -= rdBytes; + continue; + } + for (size_t i=0 ; iwrite("", 1); + break; + } + } + + fclose(fp); + + /* Prepare crypto pass */ + std::unique_ptr rs = m_parent.getFileIO().beginReadStream(0); + sha1nfo sha; + std::unique_ptr aes = NewAES(); + aes->setKey(COMMON_KEYS[ccIdx]); + aes->decrypt(tkeyiv, tkey, tkey, 16); + aes->setKey(tkey); + static const uint8_t ZEROIV[16] = {0}; + + std::unique_ptr cleartext(new char[0x1F0000]); + std::unique_ptr ciphertext(new char[0x200000]); + uint8_t h3[4916][20] = {}; + + uint64_t groupCount = m_curUser / 0x1F0000; + ws = out.beginWriteStream(offset + dataOff); + SystemString cryptoName(_S("Hashing and encrypting")); + ++m_parent.m_progressIdx; + for (uint64_t g=0 ; gread(cleartext2, 0x1F0000) != 0x1F0000) + LogModule.report(LogVisor::FatalError, "cleartext file too short"); + + uint8_t h2[8][20]; + + for (int s=0 ; s<8 ; ++s) + { + char* cleartext1 = cleartext2 + s*0x3E000; + char* ciphertext1 = ciphertext2 + s*0x40000; + uint8_t h1[8][20]; + + for (int c=0 ; c<8 ; ++c) + { + char* cleartext0 = cleartext1 + c*0x7c00; + char* ciphertext0 = ciphertext1 + c*0x8000; + uint8_t h0[31][20]; + + for (int j=0 ; j<31 ; ++j) + { + sha1_init(&sha); + sha1_write(&sha, cleartext0 + j*0x400, 0x400); + memcpy(h0[j], sha1_result(&sha), 20); + } + + sha1_init(&sha); + sha1_write(&sha, (char*)h0, 0x26C); + memcpy(h1[c], sha1_result(&sha), 20); + + memcpy(ciphertext0, h0, 0x26C); + memset(ciphertext0+0x26C, 0, 0x014); + } + + sha1_init(&sha); + sha1_write(&sha, (char*)h1, 0x0A0); + memcpy(h2[s], sha1_result(&sha), 20); + + for (int c=0 ; c<8 ; ++c) + { + char* ciphertext0 = ciphertext1 + c*0x8000; + memcpy(ciphertext0+0x280, h1, 0x0A0); + memset(ciphertext0+0x320, 0, 0x020); + } + } + + sha1_init(&sha); + sha1_write(&sha, (char*)h2, 0x0A0); + memcpy(h3[g], sha1_result(&sha), 20); + + for (int s=0 ; s<8 ; ++s) + { + char* cleartext1 = cleartext2 + s*0x3E000; + char* ciphertext1 = ciphertext2 + s*0x40000; + for (int c=0 ; c<8 ; ++c) + { + char* cleartext0 = cleartext1 + c*0x7c00; + char* ciphertext0 = ciphertext1 + c*0x8000; + memcpy(ciphertext0+0x340, h2, 0x0A0); + memset(ciphertext0+0x3E0, 0, 0x020); + aes->encrypt(ZEROIV, (uint8_t*)ciphertext0, (uint8_t*)ciphertext0, 0x400); + aes->encrypt((uint8_t*)(ciphertext0+0x3D0), (uint8_t*)cleartext0, (uint8_t*)(ciphertext0+0x400), 0x7c00); + } + } + + if (ws->write(ciphertext2, 0x200000) != 0x200000) + LogModule.report(LogVisor::FatalError, "unable to write full disc sector"); + m_parent.m_progressCB(m_parent.m_progressIdx, cryptoName, (g+1)*0x200000); + } + + /* Write new crypto content size */ + uint64_t cryptContentSize = (groupCount * 0x200000) >> 2; + uint32_t cryptContentSizeBig = SBig(uint32_t(cryptContentSize)); + ws = out.beginWriteStream(offset + 0x2BC); + ws->write(&cryptContentSizeBig, 0x4); + + /* Write new H3 */ + ws = out.beginWriteStream(offset + h3Off); + ws->write(h3, 0x18000); + + /* Compute content hash and replace in TMD */ + sha1_init(&sha); + sha1_write(&sha, (char*)h3, 0x18000); + memcpy(tmdData.get() + 0x1F4, sha1_result(&sha), 20); + + /* Same for content size */ + uint64_t contentSize = groupCount * 0x1F0000; + uint64_t contentSizeBig = SBig(contentSize); + memcpy(tmdData.get() + 0x1EC, &contentSizeBig, 8); + + /* Zero-out TMD signature to simplify brute-force */ + memset(tmdData.get() + 0x4, 0, 0x100); + + /* Brute-force zero-starting hash */ + size_t tmdCheckSz = tmdSz - 0x140; + struct BFWindow + { + uint64_t word[7]; + }* bfWindow = (BFWindow*)(tmdData.get() + 0x19A); + bool good = false; + uint64_t attempts = 0; + SystemString bfName(_S("Brute force attempts")); + ++m_parent.m_progressIdx; + for (int w=0 ; w<7 ; ++w) + { + for (uint64_t i=0 ; iword[w] = i; + sha1_init(&sha); + sha1_write(&sha, (char*)(tmdData.get() + 0x140), tmdCheckSz); + uint8_t* hash = sha1_result(&sha); + if (hash[0] == 0) + { + good = true; + break; + } + ++attempts; + if ((attempts % 1024) == 0) + m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts); + } + if (good) + break; + } + m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts); + + ws = out.beginWriteStream(offset + 0x2C0); + ws->write(tmdData.get(), tmdSz); + + return offset + dataOff + groupCount * 0x200000; + } +}; + +bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, + const SystemChar* apploaderIn, const SystemChar* partHeadIn) +{ + PartitionBuilderWii& pb = static_cast(*m_partitions[0]); + uint64_t filledSz = 0x200000; + std::unique_ptr imgOut = NewFileIO(m_outPath); + + m_fileIO = std::move(NewFileIO(SystemString(m_outPath) + _S(".cleardata"))); + if (!pb.buildFromDirectory(dirIn, dolIn, apploaderIn)) + return false; + + filledSz = pb.cryptAndFakesign(*imgOut, filledSz, partHeadIn); + if (filledSz >= 0x1FB4E0000) + { + LogModule.report(LogVisor::FatalError, "data partition exceeds disc capacity"); + return false; + } + + /* Fill image to end */ + std::unique_ptr ws = imgOut->beginWriteStream(filledSz); + for (size_t i=0 ; i<0x1FB4E0000-filledSz ; ++i) + ws->write("\xff", 1); + + return true; +} + +DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, + std::function progressCB) +: DiscBuilderBase(std::move(std::unique_ptr()), progressCB), m_outPath(outPath) +{ + PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, IPartitionBuilder::Kind::Data, + gameID, gameTitle); + m_partitions.emplace_back(partBuilder); +} + } diff --git a/lib/FileIOFILE.cpp b/lib/FileIOFILE.cpp index ffbc732..d163f93 100644 --- a/lib/FileIOFILE.cpp +++ b/lib/FileIOFILE.cpp @@ -53,7 +53,7 @@ public: if (!fp) LogModule.report(LogVisor::Error, _S("unable to open '%s' for writing"), path.c_str()); } - WriteStream(const SystemString& path, size_t offset) + WriteStream(const SystemString& path, uint64_t offset) { #if NOD_UCS2 fp = _wfopen(path.c_str(), L"r+b"); @@ -62,7 +62,7 @@ public: #endif if (!fp) LogModule.report(LogVisor::Error, _S("unable to open '%s' for writing"), path.c_str()); - fseek(fp, offset, SEEK_SET); + fseeko64(fp, offset, SEEK_SET); } ~WriteStream() { @@ -99,7 +99,7 @@ public: { return std::unique_ptr(new WriteStream(m_path)); } - std::unique_ptr beginWriteStream(size_t offset) const + std::unique_ptr beginWriteStream(uint64_t offset) const { return std::unique_ptr(new WriteStream(m_path, offset)); } @@ -118,10 +118,10 @@ public: if (!fp) LogModule.report(LogVisor::Error, _S("unable to open '%s' for reading"), path.c_str()); } - ReadStream(const SystemString& path, size_t offset) + ReadStream(const SystemString& path, uint64_t offset) : ReadStream(path) { - fseek(fp, offset, SEEK_SET); + fseeko64(fp, offset, SEEK_SET); } ~ReadStream() {fclose(fp);} uint64_t read(void* buf, uint64_t length) @@ -152,7 +152,7 @@ public: { return std::unique_ptr(new ReadStream(m_path)); } - std::unique_ptr beginReadStream(size_t offset) const + std::unique_ptr beginReadStream(uint64_t offset) const { return std::unique_ptr(new ReadStream(m_path, offset)); } diff --git a/lib/sha1.c b/lib/sha1.c new file mode 100644 index 0000000..126ac4c --- /dev/null +++ b/lib/sha1.c @@ -0,0 +1,288 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ +// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test + +#include +#include +#include "NOD/sha1.h" + +#ifdef __BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +#elif defined __LITTLE_ENDIAN__ +/* override */ +#elif defined __BYTE_ORDER +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +# endif +#else // ! defined __LITTLE_ENDIAN__ +# include // machine/endian.h +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +# endif +#endif + +/* code */ +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +void sha1_init(sha1nfo *s) { + s->state[0] = 0x67452301; + s->state[1] = 0xefcdab89; + s->state[2] = 0x98badcfe; + s->state[3] = 0x10325476; + s->state[4] = 0xc3d2e1f0; + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state[0]; + b=s->state[1]; + c=s->state[2]; + d=s->state[3]; + e=s->state[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15]; + s->buffer[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state[0] += a; + s->state[1] += b; + s->state[2] += c; + s->state[3] += d; + s->state[4] += e; +} + +void sha1_addUncounted(sha1nfo *s, uint8_t data) { + uint8_t * const b = (uint8_t*) s->buffer; +#ifdef SHA_BIG_ENDIAN + b[s->bufferOffset] = data; +#else + b[s->bufferOffset ^ 3] = data; +#endif + s->bufferOffset++; + if (s->bufferOffset == SHA1_BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + // Pad to complete the last block + sha1_pad(s); + +#ifndef SHA_BIG_ENDIAN + // Swap byte order back + int i; + for (i=0; i<5; i++) { + s->state[i]= + (((s->state[i])<<24)& 0xff000000) + | (((s->state[i])<<8) & 0x00ff0000) + | (((s->state[i])>>8) & 0x0000ff00) + | (((s->state[i])>>24)& 0x000000ff); + } +#endif + + // Return pointer to hash (20 characters) + return (uint8_t*) s->state; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { + uint8_t i; + memset(s->keyBuffer, 0, SHA1_BLOCK_LENGTH); + if (keyLength > SHA1_BLOCK_LENGTH) { + // Hash long keys + sha1_init(s); + for (;keyLength--;) sha1_writebyte(s, *key++); + memcpy(s->keyBuffer, sha1_result(s), SHA1_HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(s->keyBuffer, key, keyLength); + } + // Start inner hash + sha1_init(s); + for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); + } +} + +uint8_t* sha1_resultHmac(sha1nfo *s) { + uint8_t i; + // Complete inner hash + memcpy(s->innerHash,sha1_result(s),SHA1_HASH_LENGTH); + // Calculate outer hash + sha1_init(s); + for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); + for (i=0; iinnerHash[i]); + return sha1_result(s); +} + +/* self-test */ + +#if SHA1TEST +#include + +uint8_t hmacKey1[]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f +}; +uint8_t hmacKey2[]={ + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42,0x43 +}; +uint8_t hmacKey3[]={ + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, + 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, + 0xb0,0xb1,0xb2,0xb3 +}; +uint8_t hmacKey4[]={ + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, + 0xa0 +}; + +void printHash(uint8_t* hash) { + int i; + for (i=0; i<20; i++) { + printf("%02x", hash[i]); + } + printf("\n"); +} + + +int main (int argc, char **argv) { + uint32_t a; + sha1nfo s; + + // SHA tests + printf("Test: FIPS 180-2 C.1 and RFC3174 7.3 TEST1\n"); + printf("Expect:a9993e364706816aba3e25717850c26c9cd0d89d\n"); + printf("Result:"); + sha1_init(&s); + sha1_write(&s, "abc", 3); + printHash(sha1_result(&s)); + printf("\n\n"); + + printf("Test: FIPS 180-2 C.2 and RFC3174 7.3 TEST2\n"); + printf("Expect:84983e441c3bd26ebaae4aa1f95129e5e54670f1\n"); + printf("Result:"); + sha1_init(&s); + sha1_write(&s, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56); + printHash(sha1_result(&s)); + printf("\n\n"); + + printf("Test: RFC3174 7.3 TEST4\n"); + printf("Expect:dea356a2cddd90c7a7ecedc5ebb563934f460452\n"); + printf("Result:"); + sha1_init(&s); + for (a=0; a<80; a++) sha1_write(&s, "01234567", 8); + printHash(sha1_result(&s)); + printf("\n\n"); + + // HMAC tests + printf("Test: FIPS 198a A.1\n"); + printf("Expect:4f4ca3d5d68ba7cc0a1208c9c61e9c5da0403c0a\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey1, 64); + sha1_write(&s, "Sample #1",9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.2\n"); + printf("Expect:0922d3405faa3d194f82a45830737d5cc6c75d24\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey2, 20); + sha1_write(&s, "Sample #2", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.3\n"); + printf("Expect:bcf41eab8bb2d802f3d05caf7cb092ecf8d1a3aa\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey3,100); + sha1_write(&s, "Sample #3", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.4\n"); + printf("Expect:9ea886efe268dbecce420c7524df32e0751a2a26\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey4,49); + sha1_write(&s, "Sample #4", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + // Long tests + printf("Test: FIPS 180-2 C.3 and RFC3174 7.3 TEST3\n"); + printf("Expect:34aa973cd4c4daa4f61eeb2bdbad27316534016f\n"); + printf("Result:"); + sha1_init(&s); + for (a=0; a<1000000; a++) sha1_writebyte(&s, 'a'); + printHash(sha1_result(&s)); + + return 0; +} +#endif /* self-test */