diff --git a/driver/main.cpp b/driver/main.cpp index 305749d..6829f8c 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 []\n"); + " nodtool makewii(sl|dl) []\n"); } #if NOD_UCS2 @@ -25,7 +25,8 @@ int main(int argc, char* argv[]) { if (argc < 3 || (!strcasecmp(argv[1], _S("makegcn")) && argc < 7) || - (!strcasecmp(argv[1], _S("makewii")) && argc < 8)) + (!strcasecmp(argv[1], _S("makewiisl")) && argc < 8) || + (!strcasecmp(argv[1], _S("makewiidl")) && argc < 8)) { printHelp(); return -1; @@ -55,10 +56,17 @@ int main(int argc, char* argv[]) if (!strcasecmp(argv[1], _S("extract"))) { - std::unique_ptr disc = NOD::OpenDiscFromImage(inDir); + bool isWii; + std::unique_ptr disc = NOD::OpenDiscFromImage(inDir, isWii); if (!disc) return -1; + NOD::Mkdir(outDir, 0755); + + if (isWii) + static_cast(*disc).writeOutDataPartitionHeader( + (NOD::SystemString(outDir) + _S("/partition_head.bin")).c_str()); + NOD::Partition* dataPart = disc->getDataPartition(); if (!dataPart) return -1; @@ -115,7 +123,7 @@ int main(int argc, char* argv[]) printf("\n"); } - else if (!strcasecmp(argv[1], _S("makewii"))) + else if (!strcasecmp(argv[1], _S("makewiisl")) || !strcasecmp(argv[1], _S("makewiidl"))) { #if NOD_UCS2 if (_wcslen(argv[2]) < 6) @@ -151,16 +159,18 @@ int main(int argc, char* argv[]) fflush(stdout); }; + bool dual = (argv[1][7] == _S('d') || argv[1][7] == _S('D')); + if (argc < 9) { NOD::SystemString outPath(argv[4]); outPath.append(_S(".iso")); - NOD::DiscBuilderWii b(outPath.c_str(), argv[2], argv[3], progFunc); + NOD::DiscBuilderWii b(outPath.c_str(), argv[2], argv[3], dual, progFunc); b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); } else { - NOD::DiscBuilderWii b(argv[8], argv[2], argv[3], progFunc); + NOD::DiscBuilderWii b(argv[8], argv[2], argv[3], dual, progFunc); b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]); } diff --git a/include/NOD/DiscBase.hpp b/include/NOD/DiscBase.hpp index dbf615c..04f5166 100644 --- a/include/NOD/DiscBase.hpp +++ b/include/NOD/DiscBase.hpp @@ -229,6 +229,7 @@ public: : m_parent(parent), m_kind(kind), m_offset(offset) {} virtual uint64_t normalizeOffset(uint64_t anOffset) const {return anOffset;} inline Kind getKind() const {return m_kind;} + inline uint64_t getDiscOffset() const {return m_offset;} virtual std::unique_ptr beginReadStream(uint64_t offset=0) const=0; inline std::unique_ptr beginDOLReadStream(uint64_t offset=0) const {return beginReadStream(m_dolOff + offset);} @@ -314,6 +315,7 @@ public: std::vector m_buildNames; size_t m_buildNameOff = 0; virtual uint64_t userAllocate(uint64_t reqSz)=0; + virtual uint32_t packOffset(uint64_t offset) const=0; void recursiveBuildNodes(const SystemChar* dirIn, uint64_t dolInode, std::function incParents); void addBuildName(const SystemString& str) @@ -329,6 +331,7 @@ public: char m_gameID[6]; std::string m_gameTitle; uint64_t m_dolOffset = 0; + uint64_t m_dolSize = 0; public: IPartitionBuilder(DiscBuilderBase& parent, Kind kind, const char gameID[6], const char* gameTitle) @@ -338,6 +341,9 @@ public: } bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn); + + const char* getGameID() const {return m_gameID;} + const std::string& getGameTitle() const {return m_gameTitle;} }; protected: std::unique_ptr m_fileIO; diff --git a/include/NOD/DiscWii.hpp b/include/NOD/DiscWii.hpp index ea30ab1..ab56272 100644 --- a/include/NOD/DiscWii.hpp +++ b/include/NOD/DiscWii.hpp @@ -10,13 +10,15 @@ class DiscWii : public DiscBase { public: DiscWii(std::unique_ptr&& dio); + void writeOutDataPartitionHeader(const SystemChar* pathOut) const; }; class DiscBuilderWii : public DiscBuilderBase { const SystemChar* m_outPath; + bool m_dualLayer; public: - DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, + DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, bool dualLayer, std::function progressCB); bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn, const SystemChar* partHeadIn); diff --git a/include/NOD/Util.hpp b/include/NOD/Util.hpp index 9f3917c..c86e11e 100644 --- a/include/NOD/Util.hpp +++ b/include/NOD/Util.hpp @@ -14,6 +14,7 @@ #else #include #include +#include #endif #include @@ -102,6 +103,15 @@ public: #endif #endif +static inline void Unlink(const SystemChar* file) +{ +#if _WIN32 + _wunlink(file); +#else + unlink(file); +#endif +} + #undef bswap16 #undef bswap32 #undef bswap64 diff --git a/lib/DiscBase.cpp b/lib/DiscBase.cpp index b7d560b..39d6e86 100644 --- a/lib/DiscBase.cpp +++ b/lib/DiscBase.cpp @@ -121,11 +121,11 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path, } /* Extract Dol */ - SystemString dolPath = path + _S("/main.dol"); + SystemString dolPath = path + _S("/boot.dol"); if (ctx.force || Stat(dolPath.c_str(), &theStat)) { if (ctx.verbose && ctx.progressCB) - ctx.progressCB("main.dol"); + ctx.progressCB("boot.dol"); std::unique_ptr buf = getDOLBuf(); NewFileIO(dolPath)->beginWriteStream()->write(buf.get(), m_dolSz); } @@ -174,20 +174,26 @@ void DiscBuilderBase::IPartitionBuilder::recursiveBuildNodes(const SystemChar* d } else { + if (dolInode == GetInode(e.m_path.c_str())) + { + m_buildNodes.emplace_back(false, m_buildNameOff, packOffset(m_dolOffset), m_dolSize); + addBuildName(e.m_name); + incParents(); + continue; + } + size_t fileSz = ROUND_UP_32(e.m_fileSz); uint64_t fileOff = userAllocate(fileSz); - if (dolInode == GetInode(e.m_path.c_str())) - m_dolOffset = fileOff; 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, _S("unable to open '%s' for reading"), e.m_path.c_str()); - char buf[8192]; + char buf[0x8000]; size_t xferSz = 0; ++m_parent.m_progressIdx; while (xferSz < e.m_fileSz) { - size_t rdSz = fread(buf, 1, std::min(8192ul, e.m_fileSz - xferSz), fp); + size_t rdSz = fread(buf, 1, std::min(0x8000ul, e.m_fileSz - xferSz), fp); if (!rdSz) break; ws->write(buf, rdSz); @@ -197,7 +203,7 @@ void DiscBuilderBase::IPartitionBuilder::recursiveBuildNodes(const SystemChar* d fclose(fp); for (size_t i=0 ; iwrite("\xff", 1); - m_buildNodes.emplace_back(false, m_buildNameOff, fileOff, fileSz); + m_buildNodes.emplace_back(false, m_buildNameOff, packOffset(fileOff), fileSz); addBuildName(e.m_name); incParents(); } @@ -216,11 +222,11 @@ bool DiscBuilderBase::IPartitionBuilder::buildFromDirectory(const SystemChar* di ++m_parent.m_progressIdx; m_parent.m_progressCB(m_parent.m_progressIdx, "Preparing output image", -1); + /* Add root node */ m_buildNodes.emplace_back(true, m_buildNameOff, 0, 1); addBuildName(_S("")); - recursiveBuildNodes(dirIn, GetInode(dolIn), [&](){m_buildNodes[0].incrementLength();}); - if (!m_dolOffset) + /* Write DOL first (ensures that it's within a 32-bit offset for Wii apploaders) */ { Sstat dolStat; if (Stat(dolIn, &dolStat)) @@ -228,6 +234,7 @@ bool DiscBuilderBase::IPartitionBuilder::buildFromDirectory(const SystemChar* di size_t fileSz = ROUND_UP_32(dolStat.st_size); uint64_t fileOff = userAllocate(fileSz); m_dolOffset = fileOff; + m_dolSize = fileSz; std::unique_ptr ws = m_parent.getFileIO().beginWriteStream(fileOff); FILE* fp = Fopen(dolIn, _S("rb"), FileLockType::Read); if (!fp) @@ -250,6 +257,9 @@ bool DiscBuilderBase::IPartitionBuilder::buildFromDirectory(const SystemChar* di ws->write("\xff", 1); } + /* Gather files in root directory */ + recursiveBuildNodes(dirIn, GetInode(dolIn), [&](){m_buildNodes[0].incrementLength();}); + return true; } diff --git a/lib/DiscGCN.cpp b/lib/DiscGCN.cpp index de6b037..7cc86ab 100644 --- a/lib/DiscGCN.cpp +++ b/lib/DiscGCN.cpp @@ -130,6 +130,11 @@ public: return m_curUser; } + uint32_t packOffset(uint64_t offset) const + { + return offset; + } + bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) { bool result = DiscBuilderBase::IPartitionBuilder::buildFromDirectory(dirIn, dolIn, apploaderIn); diff --git a/lib/DiscWii.cpp b/lib/DiscWii.cpp index d9ab743..fe5a9a9 100644 --- a/lib/DiscWii.cpp +++ b/lib/DiscWii.cpp @@ -339,6 +339,36 @@ public: } uint64_t normalizeOffset(uint64_t anOffset) const {return anOffset << 2;} + + void writeOutPartitionHeader(const SystemChar* pathOut) const + { + FILE* fp = Fopen(pathOut, _S("wb"), FileLockType::Write); + if (!fp) + LogModule.report(LogVisor::FatalError, _S("unable to open %s for writing"), pathOut); + + uint64_t h3Off; + { + std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4); + uint32_t h3; + if (rs->read(&h3, 4) != 4) + LogModule.report(LogVisor::FatalError, _S("unable to read H3 offset from %s"), pathOut); + h3 = SBig(h3); + h3Off = uint64_t(h3) << 2; + } + + char buf[8192]; + size_t rem = h3Off; + std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset); + while (rem) + { + size_t rdSz = std::min(rem, 8192ul); + rs->read(buf, rdSz); + fwrite(buf, 1, rdSz, fp); + rem -= rdSz; + } + + fclose(fp); + } }; DiscWii::DiscWii(std::unique_ptr&& dio) @@ -391,9 +421,21 @@ DiscWii::DiscWii(std::unique_ptr&& dio) } } +void DiscWii::writeOutDataPartitionHeader(const SystemChar* pathOut) const +{ + for (const std::unique_ptr& part : m_partitions) + { + if (part->getKind() == IPartition::Kind::Data) + { + static_cast(*part).writeOutPartitionHeader(pathOut); + break; + } + } +} + class PartitionBuilderWii : public DiscBuilderBase::IPartitionBuilder { - uint64_t m_curUser = 0x40000; + uint64_t m_curUser = 0x1F0000; public: PartitionBuilderWii(DiscBuilderBase& parent, Kind kind, const char gameID[6], const char* gameTitle) @@ -414,6 +456,11 @@ public: return ret; } + uint32_t packOffset(uint64_t offset) const + { + return uint32_t(offset >> uint64_t(2)); + } + bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) { bool result = DiscBuilderBase::IPartitionBuilder::buildFromDirectory(dirIn, dolIn, apploaderIn); @@ -434,7 +481,7 @@ public: } ws = m_parent.getFileIO().beginWriteStream(0); - Header header(m_gameID, m_gameTitle.c_str(), true); + Header header(m_gameID, m_gameTitle.c_str(), true, 0, 0, 0); header.write(*ws); ws = m_parent.getFileIO().beginWriteStream(0x2440); @@ -452,7 +499,7 @@ public: break; ws->write(buf, rdSz); xferSz += rdSz; - if (0x2440 + xferSz >= 0x40000) + if (0x2440 + xferSz >= 0x1F0000) 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); @@ -470,14 +517,14 @@ public: fstSz += m_buildNameOff; fstSz = ROUND_UP_32(fstSz); - if (fstOff + fstSz >= 0x40000) + if (fstOff + fstSz >= 0x1F0000) 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[0] = SBig(uint32_t(m_dolOffset >> uint64_t(2))); + vals[1] = SBig(uint32_t(fstOff >> uint64_t(2))); vals[2] = SBig(uint32_t(fstSz)); vals[3] = SBig(uint32_t(fstSz)); ws->write(vals, sizeof(vals)); @@ -659,7 +706,7 @@ public: } /* Write new crypto content size */ - uint64_t cryptContentSize = (groupCount * 0x200000) >> 2; + uint64_t cryptContentSize = (groupCount * 0x200000) >> uint64_t(2); uint32_t cryptContentSizeBig = SBig(uint32_t(cryptContentSize)); ws = out.beginWriteStream(offset + 0x2BC); ws->write(&cryptContentSizeBig, 0x4); @@ -699,14 +746,13 @@ public: sha1_init(&sha); sha1_write(&sha, (char*)(tmdData.get() + 0x140), tmdCheckSz); uint8_t* hash = sha1_result(&sha); + ++attempts; if (hash[0] == 0) { good = true; break; } - ++attempts; - if ((attempts % 1024) == 0) - m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts); + m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts); } if (good) break; @@ -723,32 +769,75 @@ public: bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn, const SystemChar* partHeadIn) { + size_t DISC_CAPACITY = m_dualLayer ? 0x1FB4E0000 : 0x118240000; + PartitionBuilderWii& pb = static_cast(*m_partitions[0]); uint64_t filledSz = 0x200000; std::unique_ptr imgOut = NewFileIO(m_outPath); + imgOut->beginWriteStream(); - m_fileIO = std::move(NewFileIO(SystemString(m_outPath) + _S(".cleardata"))); + /* Assemble cleartext data partition into temporary file */ + SystemString clearPath(m_outPath); + clearPath += _S(".cleardata"); + m_fileIO = std::move(NewFileIO(clearPath)); if (!pb.buildFromDirectory(dirIn, dolIn, apploaderIn)) return false; + /* Fakesign cleartext into output file */ filledSz = pb.cryptAndFakesign(*imgOut, filledSz, partHeadIn); - if (filledSz >= 0x1FB4E0000) + if (filledSz >= DISC_CAPACITY) { LogModule.report(LogVisor::FatalError, "data partition exceeds disc capacity"); return false; } + ++m_progressIdx; + m_progressCB(m_progressIdx, "Finishing Disc", -1); + + /* Populate disc header */ + std::unique_ptr ws = imgOut->beginWriteStream(0); + Header header(pb.getGameID(), pb.getGameTitle().c_str(), true, 0, 0, 0); + header.write(*ws); + + /* Populate partition info */ + ws = imgOut->beginWriteStream(0x40000); + uint32_t vals[2] = {SBig(uint32_t(1)), SBig(uint32_t(0x40020 >> uint64_t(2)))}; + ws->write(vals, 8); + + ws = imgOut->beginWriteStream(0x40020); + vals[0] = SBig(uint32_t(0x200000 >> uint64_t(2))); + ws->write(vals, 4); + + /* Populate region info */ + ws = imgOut->beginWriteStream(0x4E000); + const char* gameID = pb.getGameID(); + if (gameID[3] == 'P') + vals[0] = SBig(uint32_t(2)); + else if (gameID[3] == 'J') + vals[0] = SBig(uint32_t(0)); + else + vals[0] = SBig(uint32_t(1)); + ws->write(vals, 4); + + /* Make disc unrated */ + ws = imgOut->beginWriteStream(0x4E010); + for (int i=0 ; i<16 ; ++i) + ws->write("\x80", 1); + /* Fill image to end */ - std::unique_ptr ws = imgOut->beginWriteStream(filledSz); - for (size_t i=0 ; i<0x1FB4E0000-filledSz ; ++i) + ws = imgOut->beginWriteStream(filledSz); + for (size_t i=0 ; iwrite("\xff", 1); + /* Delete cleartext file */ + Unlink(clearPath.c_str()); + return true; } -DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, +DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, bool dualLayer, std::function progressCB) -: DiscBuilderBase(std::move(std::unique_ptr()), progressCB), m_outPath(outPath) +: DiscBuilderBase(std::move(std::unique_ptr()), progressCB), m_outPath(outPath), m_dualLayer(dualLayer) { PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, IPartitionBuilder::Kind::Data, gameID, gameTitle); diff --git a/lib/FileIOFILE.cpp b/lib/FileIOFILE.cpp index d163f93..ff10584 100644 --- a/lib/FileIOFILE.cpp +++ b/lib/FileIOFILE.cpp @@ -56,8 +56,12 @@ public: WriteStream(const SystemString& path, uint64_t offset) { #if NOD_UCS2 + fp = _wfopen(path.c_str(), L"ab"); + fclose(fp); fp = _wfopen(path.c_str(), L"r+b"); #else + fp = fopen(path.c_str(), "ab"); + fclose(fp); fp = fopen(path.c_str(), "r+b"); #endif if (!fp)