diff --git a/driver/main.cpp b/driver/main.cpp index 2a3c03c..8436d44 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -8,7 +8,9 @@ static void printHelp() fprintf(stderr, "Usage:\n" " nodtool extract [-f] []\n" " nodtool makegcn []\n" - " nodtool makewii(sl|dl) []\n"); + " nodtool makewii []\n" + " nodtool mergegcn []\n" + " nodtool mergewii []\n"); } #if NOD_UCS2 @@ -25,8 +27,9 @@ int main(int argc, char* argv[]) { if (argc < 3 || (!strcasecmp(argv[1], _S("makegcn")) && argc < 7) || - (!strcasecmp(argv[1], _S("makewiisl")) && argc < 8) || - (!strcasecmp(argv[1], _S("makewiidl")) && argc < 8)) + (!strcasecmp(argv[1], _S("makewii")) && argc < 8) || + (!strcasecmp(argv[1], _S("mergegcn")) && argc < 4) || + (!strcasecmp(argv[1], _S("mergewii")) && argc < 4)) { printHelp(); return 1; @@ -55,6 +58,21 @@ int main(int argc, char* argv[]) outDir = argv[a]; } + 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) + nod::Printf(_S("\r%s %" PRISize " B"), name.c_str(), bytes); + else + nod::Printf(_S("\r%s"), name.c_str()); + fflush(stdout); + }; + if (!strcasecmp(argv[1], _S("extract"))) { bool isWii; @@ -79,39 +97,43 @@ int main(int argc, char* argv[]) { #if NOD_UCS2 if (wcslen(argv[2]) < 6) - nod::LogModule.report(logvisor::Fatal, _S("game-id is not at least 6 characters")); + { + nod::LogModule.report(logvisor::Error, _S("game-id is not at least 6 characters")); + return 1; + } #else if (strlen(argv[2]) < 6) - nod::LogModule.report(logvisor::Fatal, _S("game-id is not at least 6 characters")); + { + nod::LogModule.report(logvisor::Error, _S("game-id is not at least 6 characters")); + return 1; + } #endif /* Pre-validate paths */ nod::Sstat theStat; if (nod::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as directory"), argv[4]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as directory"), argv[4]); + return 1; + } if (nod::Stat(argv[5], &theStat) || !S_ISREG(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as file"), argv[5]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[5]); + return 1; + } if (nod::Stat(argv[6], &theStat) || !S_ISREG(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, "unable to stat %s as file", argv[6]); + { + nod::LogModule.report(logvisor::Error, "unable to stat %s as file", argv[6]); + return 1; + } nod::SystemString gameIdSys(argv[2]); nod::SystemUTF8View gameId(gameIdSys); nod::SystemString gameTitleSys(argv[3]); nod::SystemUTF8View gameTitle(gameTitleSys); - 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) - nod::Printf(_S("\r%s %" PRISize " B"), name.c_str(), bytes); - else - nod::Printf(_S("\r%s"), name.c_str()); - fflush(stdout); - }; + + if (nod::DiscBuilderGCN::CalculateTotalSizeRequired(argv[4], argv[5]) == -1) + return 1; bool ret; @@ -132,47 +154,54 @@ int main(int argc, char* argv[]) if (!ret) return 1; } - else if (!strcasecmp(argv[1], _S("makewiisl")) || !strcasecmp(argv[1], _S("makewiidl"))) + else if (!strcasecmp(argv[1], _S("makewii"))) { #if NOD_UCS2 if (wcslen(argv[2]) < 6) - nod::LogModule.report(logvisor::Fatal, _S("game-id is not at least 6 characters")); + { + nod::LogModule.report(logvisor::Error, _S("game-id is not at least 6 characters")); + return 1; + } #else if (strlen(argv[2]) < 6) - nod::LogModule.report(logvisor::Fatal, _S("game-id is not at least 6 characters")); + { + nod::LogModule.report(logvisor::Error, _S("game-id is not at least 6 characters")); + return 1; + } #endif /* Pre-validate paths */ nod::Sstat theStat; if (nod::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as directory"), argv[4]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as directory"), argv[4]); + return 1; + } if (nod::Stat(argv[5], &theStat) || !S_ISREG(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as file"), argv[5]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[5]); + return 1; + } if (nod::Stat(argv[6], &theStat) || !S_ISREG(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as file"), argv[6]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[6]); + return 1; + } if (nod::Stat(argv[7], &theStat) || !S_ISREG(theStat.st_mode)) - nod::LogModule.report(logvisor::Fatal, _S("unable to stat %s as file"), argv[7]); + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[7]); + return 1; + } nod::SystemString gameIdSys(argv[2]); nod::SystemUTF8View gameId(gameIdSys); nod::SystemString gameTitleSys(argv[3]); nod::SystemUTF8View gameTitle(gameTitleSys); - 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) - nod::Printf(_S("\r%s %" PRISize " B"), name.c_str(), bytes); - else - nod::Printf(_S("\r%s"), name.c_str()); - fflush(stdout); - }; - bool dual = (argv[1][7] == _S('d') || argv[1][7] == _S('D')); + bool dual = false; + if (nod::DiscBuilderWii::CalculateTotalSizeRequired(argv[4], argv[5], dual) == -1) + return 1; + bool ret; if (argc < 9) @@ -192,12 +221,114 @@ int main(int argc, char* argv[]) if (!ret) return 1; } + else if (!strcasecmp(argv[1], _S("mergegcn"))) + { + /* Pre-validate paths */ + nod::Sstat theStat; + if (nod::Stat(argv[2], &theStat) || !S_ISDIR(theStat.st_mode)) + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as directory"), argv[2]); + return 1; + } + if (nod::Stat(argv[3], &theStat) || !S_ISREG(theStat.st_mode)) + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[3]); + return 1; + } + + bool isWii; + std::unique_ptr disc = nod::OpenDiscFromImage(argv[3], isWii); + if (!disc) + { + nod::LogModule.report(logvisor::Error, _S("unable to open image %s"), argv[3]); + return 1; + } + if (isWii) + { + nod::LogModule.report(logvisor::Error, _S("Wii images should be merged with 'mergewii'")); + return 1; + } + + if (nod::DiscMergerGCN::CalculateTotalSizeRequired(static_cast(*disc), argv[2]) == -1) + return 1; + + bool ret; + + if (argc < 5) + { + nod::SystemString outPath(argv[2]); + outPath.append(_S(".iso")); + nod::DiscMergerGCN b(outPath.c_str(), static_cast(*disc), progFunc); + ret = b.mergeFromDirectory(argv[2]); + } + else + { + nod::DiscMergerGCN b(argv[4], static_cast(*disc), progFunc); + ret = b.mergeFromDirectory(argv[2]); + } + + printf("\n"); + if (!ret) + return 1; + } + else if (!strcasecmp(argv[1], _S("mergewii"))) + { + /* Pre-validate paths */ + nod::Sstat theStat; + if (nod::Stat(argv[2], &theStat) || !S_ISDIR(theStat.st_mode)) + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as directory"), argv[2]); + return 1; + } + if (nod::Stat(argv[3], &theStat) || !S_ISREG(theStat.st_mode)) + { + nod::LogModule.report(logvisor::Error, _S("unable to stat %s as file"), argv[3]); + return 1; + } + + bool isWii; + std::unique_ptr disc = nod::OpenDiscFromImage(argv[3], isWii); + if (!disc) + { + nod::LogModule.report(logvisor::Error, _S("unable to open image %s"), argv[3]); + return 1; + } + if (!isWii) + { + nod::LogModule.report(logvisor::Error, _S("GameCube images should be merged with 'mergegcn'")); + return 1; + } + + bool dual = false; + if (nod::DiscMergerWii::CalculateTotalSizeRequired(static_cast(*disc), argv[2], dual) == -1) + return 1; + + bool ret; + + if (argc < 5) + { + nod::SystemString outPath(argv[2]); + outPath.append(_S(".iso")); + nod::DiscMergerWii b(outPath.c_str(), static_cast(*disc), dual, progFunc); + ret = b.mergeFromDirectory(argv[2]); + } + else + { + nod::DiscMergerWii b(argv[4], static_cast(*disc), dual, progFunc); + ret = b.mergeFromDirectory(argv[2]); + } + + printf("\n"); + if (!ret) + return 1; + } else { printHelp(); return 1; } + nod::LogModule.report(logvisor::Info, _S("Success!")); return 0; } diff --git a/include/nod/DiscBase.hpp b/include/nod/DiscBase.hpp index ebda310..1087afc 100644 --- a/include/nod/DiscBase.hpp +++ b/include/nod/DiscBase.hpp @@ -15,6 +15,8 @@ namespace nod { +using FProgress = std::function; + class FSTNode { uint32_t typeAndNameOffset; @@ -53,9 +55,14 @@ struct Header uint32_t m_gcnMagic; char m_gameTitle[64]; - Header(IDiscIO& dio) + Header(IDiscIO& dio, bool& err) { std::unique_ptr s = dio.beginReadStream(0); + if (!s) + { + err = true; + return; + } s->read(this, sizeof(*this)); m_wiiMagic = SBig(m_wiiMagic); m_gcnMagic = SBig(m_gcnMagic); @@ -212,6 +219,7 @@ public: uint64_t m_dolOff; uint64_t m_fstOff; uint64_t m_fstSz; + uint64_t m_fstMemoryAddr; uint64_t m_apploaderSz; std::vector m_nodes; void parseFST(IPartReadStream& s); @@ -260,6 +268,8 @@ public: return buf; } + inline uint64_t getFSTMemoryAddr() const {return m_fstMemoryAddr;} + inline uint64_t getApploaderSize() const {return m_apploaderSz;} inline std::unique_ptr getApploaderBuf() const { @@ -274,8 +284,8 @@ protected: Header m_header; std::vector> m_partitions; public: - DiscBase(std::unique_ptr&& dio) - : m_discIO(std::move(dio)), m_header(*m_discIO) {} + DiscBase(std::unique_ptr&& dio, bool& err) + : m_discIO(std::move(dio)), m_header(*m_discIO, err) {} inline const Header& getHeader() const {return m_header;} inline const IDiscIO& getDiscIO() const {return *m_discIO;} @@ -302,6 +312,7 @@ public: class DiscBuilderBase { + friend class DiscMergerWii; public: class PartitionBuilderBase { @@ -320,9 +331,18 @@ public: size_t m_buildNameOff = 0; virtual uint64_t userAllocate(uint64_t reqSz, IPartWriteStream& ws)=0; virtual uint32_t packOffset(uint64_t offset) const=0; - void recursiveBuildNodes(IPartWriteStream& ws, bool system, const SystemChar* dirIn, uint64_t dolInode); - void recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode, + bool recursiveBuildNodes(IPartWriteStream& ws, bool system, const SystemChar* dirIn, uint64_t dolInode); + bool recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode, std::function incParents); + bool recursiveMergeNodes(IPartWriteStream& ws, bool system, + const DiscBase::IPartition::Node* nodeIn, const SystemChar* dirIn, + const SystemString& keyPath); + bool recursiveMergeFST(const DiscBase::IPartition::Node* nodeIn, + const SystemChar* dirIn, std::function incParents, + const SystemString& keyPath); + static bool RecursiveCalculateTotalSize(uint64_t& totalSz, + const DiscBase::IPartition::Node* nodeIn, + const SystemChar* dirIn); void addBuildName(const SystemString& str) { SystemUTF8View utf8View(str); @@ -348,23 +368,32 @@ public: bool buildFromDirectory(IPartWriteStream& ws, const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn); + static uint64_t CalculateTotalSizeBuild(const SystemChar* dolIn, + const SystemChar* dirIn); + bool mergeFromDirectory(IPartWriteStream& ws, + const DiscBase::IPartition* partIn, + const SystemChar* dirIn); + static uint64_t CalculateTotalSizeMerge(const DiscBase::IPartition* partIn, + const SystemChar* dirIn); const char* getGameID() const {return m_gameID;} const std::string& getGameTitle() const {return m_gameTitle;} }; protected: - const SystemChar* m_outPath; + SystemString m_outPath; std::unique_ptr m_fileIO; std::vector> m_partitions; int64_t m_discCapacity; public: std::function m_progressCB; size_t m_progressIdx = 0; - virtual ~DiscBuilderBase() {} + virtual ~DiscBuilderBase() = default; DiscBuilderBase(const SystemChar* outPath, int64_t discCapacity, std::function progressCB) : m_outPath(outPath), m_fileIO(NewFileIO(outPath, discCapacity)), m_discCapacity(discCapacity), m_progressCB(progressCB) {} + DiscBuilderBase(DiscBuilderBase&&) = default; + DiscBuilderBase& operator=(DiscBuilderBase&&) = default; IFileIO& getFileIO() {return *m_fileIO;} }; diff --git a/include/nod/DiscGCN.hpp b/include/nod/DiscGCN.hpp index 6d21fbd..5390a3e 100644 --- a/include/nod/DiscGCN.hpp +++ b/include/nod/DiscGCN.hpp @@ -5,20 +5,35 @@ namespace nod { +class DiscBuilderGCN; class DiscGCN : public DiscBase { + friend class DiscMergerGCN; + DiscBuilderGCN makeMergeBuilder(const SystemChar* outPath, FProgress progressCB); public: - DiscGCN(std::unique_ptr&& dio); + DiscGCN(std::unique_ptr&& dio, bool& err); }; class DiscBuilderGCN : public DiscBuilderBase { + friend class DiscMergerGCN; public: DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], const char* gameTitle, - uint32_t fstMemoryAddr, std::function progressCB); + uint32_t fstMemoryAddr, FProgress progressCB); bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn); + static uint64_t CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn); +}; + +class DiscMergerGCN +{ + DiscGCN& m_sourceDisc; + DiscBuilderGCN m_builder; +public: + DiscMergerGCN(const SystemChar* outPath, DiscGCN& sourceDisc, FProgress progressCB); + bool mergeFromDirectory(const SystemChar* dirIn); + static uint64_t CalculateTotalSizeRequired(DiscGCN& sourceDisc, const SystemChar* dirIn); }; } diff --git a/include/nod/DiscWii.hpp b/include/nod/DiscWii.hpp index 720084e..5cbcc2e 100644 --- a/include/nod/DiscWii.hpp +++ b/include/nod/DiscWii.hpp @@ -5,24 +5,40 @@ namespace nod { +class DiscBuilderWii; class DiscWii : public DiscBase { public: - DiscWii(std::unique_ptr&& dio); - void writeOutDataPartitionHeader(const SystemChar* pathOut) const; + DiscWii(std::unique_ptr&& dio, bool& err); + DiscBuilderWii makeMergeBuilder(const SystemChar* outPath, bool dualLayer, FProgress progressCB); + bool writeOutDataPartitionHeader(const SystemChar* pathOut) const; }; class DiscBuilderWii : public DiscBuilderBase { bool m_dualLayer; public: - DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, bool dualLayer, - std::function progressCB); + DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, + bool dualLayer, FProgress progressCB); bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn, const SystemChar* partHeadIn); + static uint64_t CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn, + bool& dualLayer); +}; + +class DiscMergerWii +{ + DiscWii& m_sourceDisc; + DiscBuilderWii m_builder; +public: + DiscMergerWii(const SystemChar* outPath, DiscWii& sourceDisc, + bool dualLayer, FProgress progressCB); + bool mergeFromDirectory(const SystemChar* dirIn); + static uint64_t CalculateTotalSizeRequired(DiscWii& sourceDisc, const SystemChar* dirIn, + bool& dualLayer); }; } diff --git a/include/nod/Util.hpp b/include/nod/Util.hpp index cad10f0..af2a4dd 100644 --- a/include/nod/Util.hpp +++ b/include/nod/Util.hpp @@ -309,16 +309,25 @@ static inline bool CheckFreeSpace(const SystemChar* path, size_t reqSz) wchar_t* end; DWORD ret = GetFullPathNameW(path, 1024, buf, &end); if (!ret || ret > 1024) - LogModule.report(logvisor::Fatal, _S("GetFullPathNameW %s"), path); + { + LogModule.report(logvisor::Error, _S("GetFullPathNameW %s"), path); + return false; + } if (end) end[0] = L'\0'; if (!GetDiskFreeSpaceExW(buf, &freeBytes, nullptr, nullptr)) - LogModule.report(logvisor::Fatal, _S("GetDiskFreeSpaceExW %s: %d"), path, GetLastError()); + { + LogModule.report(logvisor::Error, _S("GetDiskFreeSpaceExW %s: %d"), path, GetLastError()); + return false; + } return reqSz < freeBytes.QuadPart; #else struct statvfs svfs; if (statvfs(path, &svfs)) - LogModule.report(logvisor::Fatal, "statvfs %s: %s", path, strerror(errno)); + { + LogModule.report(logvisor::Error, "statvfs %s: %s", path, strerror(errno)); + return false; + } return reqSz < svfs.f_frsize * svfs.f_bavail; #endif } diff --git a/lib/DiscBase.cpp b/lib/DiscBase.cpp index 0844f3b..241eeec 100644 --- a/lib/DiscBase.cpp +++ b/lib/DiscBase.cpp @@ -118,6 +118,8 @@ bool DiscBase::IPartition::Node::extractToDirectory(const SystemString& basePath { std::unique_ptr rs = beginReadStream(); std::unique_ptr ws = NewFileIO(path)->beginWriteStream(); + if (!rs || !ws) + return false; ws->copyFromDisc(*rs, m_discLength); } } @@ -141,7 +143,10 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path, if (ctx.verbose && ctx.progressCB) ctx.progressCB("apploader.bin"); std::unique_ptr buf = getApploaderBuf(); - NewFileIO(apploaderPath)->beginWriteStream()->write(buf.get(), m_apploaderSz); + auto ws = NewFileIO(apploaderPath)->beginWriteStream(); + if (!ws) + return false; + ws->write(buf.get(), m_apploaderSz); } /* Extract Dol */ @@ -151,7 +156,10 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path, if (ctx.verbose && ctx.progressCB) ctx.progressCB("boot.dol"); std::unique_ptr buf = getDOLBuf(); - NewFileIO(dolPath)->beginWriteStream()->write(buf.get(), m_dolSz); + auto ws = NewFileIO(dolPath)->beginWriteStream(); + if (!ws) + return false; + ws->write(buf.get(), m_dolSz); } /* Extract Filesystem */ @@ -177,17 +185,26 @@ static uint64_t GetInode(const SystemChar* path) FILE_ATTRIBUTE_NORMAL, nullptr); if (!fp) - LogModule.report(logvisor::Fatal, _S("unable to open %s"), path); + { + LogModule.report(logvisor::Error, _S("unable to open %s"), path); + return 0; + } BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(fp, &info)) - LogModule.report(logvisor::Fatal, _S("unable to GetFileInformationByHandle %s"), path); + { + LogModule.report(logvisor::Error, _S("unable to GetFileInformationByHandle %s"), path); + return 0; + } inode = uint64_t(info.nFileIndexHigh) << 32; inode |= uint64_t(info.nFileIndexLow); CloseHandle(fp); #else struct stat st; if (stat(path, &st)) - LogModule.report(logvisor::Fatal, _S("unable to stat %s"), path); + { + LogModule.report(logvisor::Error, _S("unable to stat %s"), path); + return 0; + } inode = uint64_t(st.st_ino); #endif return inode; @@ -222,11 +239,9 @@ 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, bool& patched) +static void PatchDOL(std::unique_ptr& buf, 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)); @@ -235,10 +250,17 @@ static size_t PatchDOL(IFileIO::IReadStream& in, IPartWriteStream& out, size_t s found[11] = '\x04'; patched = true; } +} + +static size_t PatchDOL(IFileIO::IReadStream& in, IPartWriteStream& out, size_t sz, bool& patched) +{ + std::unique_ptr buf(new uint8_t[sz]); + sz = in.read(buf.get(), sz); + PatchDOL(buf, sz, patched); return out.write(buf.get(), sz); } -void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream& ws, +bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream& ws, bool system, const SystemChar* dirIn, uint64_t dolInode) @@ -248,7 +270,8 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream { if (e.m_isDir) { - recursiveBuildNodes(ws, system, e.m_path.c_str(), dolInode); + if (!recursiveBuildNodes(ws, system, e.m_path.c_str(), dolInode)) + return false; } else { @@ -262,8 +285,12 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream size_t fileSz = ROUND_UP_32(e.m_fileSz); uint64_t fileOff = userAllocate(fileSz, ws); + if (fileOff == -1) + return false; m_fileOffsetsSizes[e.m_path] = std::make_pair(fileOff, fileSz); std::unique_ptr rs = NewFileIO(e.m_path)->beginReadStream(); + if (!rs) + return false; size_t xferSz = 0; if (isDol) { @@ -289,9 +316,11 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream ws.write("\xff", 1); } } + + return true; } -void DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode, +bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode, std::function incParents) { DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true); @@ -303,7 +332,8 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* m_buildNodes.emplace_back(true, m_buildNameOff, 0, dirNodeIdx+1); addBuildName(e.m_name); incParents(); - recursiveBuildFST(e.m_path.c_str(), dolInode, [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}); + if (!recursiveBuildFST(e.m_path.c_str(), dolInode, [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();})) + return false; } else { @@ -321,6 +351,328 @@ void DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* incParents(); } } + + return true; +} + +bool DiscBuilderBase::PartitionBuilderBase::recursiveMergeNodes(IPartWriteStream& ws, + bool system, + const DiscBase::IPartition::Node* nodeIn, + const SystemChar* dirIn, + const SystemString& keyPath) +{ + /* Build map of existing nodes to write-through later */ + std::unordered_map fileNodes; + std::unordered_map dirNodes; + if (nodeIn) + { + fileNodes.reserve(nodeIn->size()); + dirNodes.reserve(nodeIn->size()); + for (const Partition::Node& ch : *nodeIn) + { + if (ch.getKind() == Partition::Node::Kind::File) + fileNodes.insert(std::make_pair(ch.getName(), &ch)); + else if (ch.getKind() == Partition::Node::Kind::Directory) + dirNodes.insert(std::make_pair(ch.getName(), &ch)); + } + } + + /* Merge this directory's files */ + if (dirIn) + { + DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true); + for (const DirectoryEnumerator::Entry& e : dEnum) + { + SystemUTF8View nameView(e.m_name); + SystemString chKeyPath = keyPath + _S('/') + e.m_name; + + if (e.m_isDir) + { + auto search = dirNodes.find(nameView.utf8_str()); + if (search != dirNodes.cend()) + { + if (!recursiveMergeNodes(ws, system, search->second, e.m_path.c_str(), chKeyPath)) + return false; + dirNodes.erase(search); + } + else + { + if (!recursiveMergeNodes(ws, system, nullptr, e.m_path.c_str(), chKeyPath)) + return false; + } + } + else + { + bool isDol; + bool isSys = IsSystemFile(e.m_name, isDol); + if (system ^ isSys) + continue; + + fileNodes.erase(nameView.utf8_str()); + + size_t fileSz = ROUND_UP_32(e.m_fileSz); + uint64_t fileOff = userAllocate(fileSz, ws); + if (fileOff == -1) + return false; + m_fileOffsetsSizes[chKeyPath] = std::make_pair(fileOff, fileSz); + std::unique_ptr rs = NewFileIO(e.m_path)->beginReadStream(); + if (!rs) + return false; + size_t xferSz = 0; + if (isDol) + { + 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 + { + char buf[0x8000]; + ++m_parent.m_progressIdx; + while (xferSz < e.m_fileSz) + { + size_t rdSz = rs->read(buf, nod::min(size_t(0x8000ul), e.m_fileSz - xferSz)); + if (!rdSz) + break; + ws.write(buf, rdSz); + xferSz += rdSz; + m_parent.m_progressCB(m_parent.m_progressIdx, e.m_name, xferSz); + } + } + for (size_t i=0 ; igetName()); + SystemString chKeyPath = keyPath + _S('/') + sysName.sys_str(); + if (!recursiveMergeNodes(ws, system, p.second, nullptr, chKeyPath)) + return false; + } + + /* Write-through remaining file nodes */ + for (const auto& p : fileNodes) + { + const Partition::Node& ch = *p.second; + SystemStringView sysName(ch.getName()); + SystemString chKeyPath = keyPath + _S('/') + sysName.sys_str(); + + bool isDol; + bool isSys = IsSystemFile(sysName.sys_str(), isDol); + if (system ^ isSys) + continue; + + size_t fileSz = ROUND_UP_32(ch.size()); + uint64_t fileOff = userAllocate(fileSz, ws); + if (fileOff == -1) + return false; + m_fileOffsetsSizes[chKeyPath] = std::make_pair(fileOff, fileSz); + std::unique_ptr rs = ch.beginReadStream(); + if (!rs) + return false; + size_t xferSz = 0; + if (isDol) + { + xferSz = ch.size(); + std::unique_ptr dolBuf = ch.getBuf(); + bool patched; + PatchDOL(dolBuf, xferSz, patched); + ws.write(dolBuf.get(), xferSz); + m_parent.m_progressCB(++m_parent.m_progressIdx, sysName.sys_str() + + (patched ? _S(" [PATCHED]") : _S("")), xferSz); + } + else + { + char buf[0x8000]; + ++m_parent.m_progressIdx; + while (xferSz < ch.size()) + { + size_t rdSz = rs->read(buf, nod::min(size_t(0x8000), size_t(ch.size() - xferSz))); + if (!rdSz) + break; + ws.write(buf, rdSz); + xferSz += rdSz; + m_parent.m_progressCB(m_parent.m_progressIdx, sysName.sys_str(), xferSz); + } + } + for (size_t i=0 ; i incParents, + const SystemString& keyPath) +{ + /* Build map of existing nodes to write-through later */ + std::unordered_map fileNodes; + std::unordered_map dirNodes; + if (nodeIn) + { + fileNodes.reserve(nodeIn->size()); + dirNodes.reserve(nodeIn->size()); + for (const Partition::Node& ch : *nodeIn) + { + if (ch.getKind() == Partition::Node::Kind::File) + fileNodes.insert(std::make_pair(ch.getName(), &ch)); + else if (ch.getKind() == Partition::Node::Kind::Directory) + dirNodes.insert(std::make_pair(ch.getName(), &ch)); + } + } + + /* Merge this directory's files */ + if (dirIn) + { + DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true); + for (const DirectoryEnumerator::Entry& e : dEnum) + { + SystemUTF8View nameView(e.m_name); + SystemString chKeyPath = keyPath + _S('/') + e.m_name; + + if (e.m_isDir) + { + size_t dirNodeIdx = m_buildNodes.size(); + m_buildNodes.emplace_back(true, m_buildNameOff, 0, dirNodeIdx+1); + addBuildName(e.m_name); + incParents(); + + auto search = dirNodes.find(nameView.utf8_str()); + if (search != dirNodes.cend()) + { + if (!recursiveMergeFST(search->second, e.m_path.c_str(), + [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}, + chKeyPath)) + return false; + dirNodes.erase(search); + } + else + { + if (!recursiveMergeFST(nullptr, e.m_path.c_str(), + [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}, + chKeyPath)) + return false; + } + } + else + { + fileNodes.erase(nameView.utf8_str()); + std::pair fileOffSz = m_fileOffsetsSizes.at(chKeyPath); + m_buildNodes.emplace_back(false, m_buildNameOff, packOffset(fileOffSz.first), fileOffSz.second); + addBuildName(e.m_name); + incParents(); + } + } + } + + + /* Write-through remaining dir nodes */ + for (const auto& p : dirNodes) + { + SystemStringView sysName(p.second->getName()); + SystemString chKeyPath = keyPath + _S('/') + sysName.sys_str(); + + size_t dirNodeIdx = m_buildNodes.size(); + m_buildNodes.emplace_back(true, m_buildNameOff, 0, dirNodeIdx+1); + addBuildName(sysName.sys_str()); + incParents(); + + if (!recursiveMergeFST(p.second, nullptr, + [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}, + chKeyPath)) + return false; + } + + /* Write-through remaining file nodes */ + for (const auto& p : fileNodes) + { + const Partition::Node& ch = *p.second; + SystemStringView sysName(ch.getName()); + SystemString chKeyPath = keyPath + _S('/') + sysName.sys_str(); + + std::pair fileOffSz = m_fileOffsetsSizes.at(chKeyPath); + m_buildNodes.emplace_back(false, m_buildNameOff, packOffset(fileOffSz.first), fileOffSz.second); + addBuildName(sysName.sys_str()); + incParents(); + } + + return true; +} + +bool DiscBuilderBase::PartitionBuilderBase::RecursiveCalculateTotalSize( + uint64_t& totalSz, + const DiscBase::IPartition::Node* nodeIn, + const SystemChar* dirIn) +{ + /* Build map of existing nodes to write-through later */ + std::unordered_map fileNodes; + std::unordered_map dirNodes; + if (nodeIn) + { + fileNodes.reserve(nodeIn->size()); + dirNodes.reserve(nodeIn->size()); + for (const Partition::Node& ch : *nodeIn) + { + if (ch.getKind() == Partition::Node::Kind::File) + fileNodes.insert(std::make_pair(ch.getName(), &ch)); + else if (ch.getKind() == Partition::Node::Kind::Directory) + dirNodes.insert(std::make_pair(ch.getName(), &ch)); + } + } + + /* Merge this directory's files */ + if (dirIn) + { + DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true); + for (const DirectoryEnumerator::Entry& e : dEnum) + { + SystemUTF8View nameView(e.m_name); + + if (e.m_isDir) + { + auto search = dirNodes.find(nameView.utf8_str()); + if (search != dirNodes.cend()) + { + if (!RecursiveCalculateTotalSize(totalSz, search->second, e.m_path.c_str())) + return false; + dirNodes.erase(search); + } + else + { + if (!RecursiveCalculateTotalSize(totalSz, nullptr, e.m_path.c_str())) + return false; + } + } + else + { + fileNodes.erase(nameView.utf8_str()); + totalSz += ROUND_UP_32(e.m_fileSz); + } + } + } + + /* Write-through remaining dir nodes */ + for (const auto& p : dirNodes) + { + if (!RecursiveCalculateTotalSize(totalSz, p.second, nullptr)) + return false; + } + + /* Write-through remaining file nodes */ + for (const auto& p : fileNodes) + { + const Partition::Node& ch = *p.second; + totalSz += ROUND_UP_32(ch.size()); + } + + return true; } bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream& ws, @@ -329,7 +681,10 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream& const SystemChar* apploaderIn) { if (!dirIn || !dolIn || !apploaderIn) - LogModule.report(logvisor::Fatal, _S("all arguments must be supplied to buildFromDirectory()")); + { + LogModule.report(logvisor::Error, _S("all arguments must be supplied to buildFromDirectory()")); + return false; + } /* Clear file */ ++m_parent.m_progressIdx; @@ -343,26 +698,110 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream& { Sstat dolStat; if (Stat(dolIn, &dolStat)) - LogModule.report(logvisor::Fatal, _S("unable to stat %s"), dolIn); + { + LogModule.report(logvisor::Error, _S("unable to stat %s"), dolIn); + return false; + } size_t fileSz = ROUND_UP_32(dolStat.st_size); uint64_t fileOff = userAllocate(fileSz, ws); + if (fileOff == -1) + return false; m_dolOffset = fileOff; m_dolSize = fileSz; std::unique_ptr rs = NewFileIO(dolIn)->beginReadStream(); + if (!rs) + return false; 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); + m_parent.m_progressCB(++m_parent.m_progressIdx, SystemString(dolIn) + + (patched ? _S(" [PATCHED]") : _S("")), xferSz); for (size_t i=0 ; i")); + + /* Write Boot DOL first (first thing seeked to after Apploader) */ + { + size_t xferSz = partIn->getDOLSize(); + size_t fileSz = ROUND_UP_32(xferSz); + uint64_t fileOff = userAllocate(fileSz, ws); + if (fileOff == -1) + return false; + m_dolOffset = fileOff; + m_dolSize = fileSz; + std::unique_ptr dolBuf = partIn->getDOLBuf(); + bool patched; + PatchDOL(dolBuf, xferSz, patched); + ws.write(dolBuf.get(), xferSz); + m_parent.m_progressCB(++m_parent.m_progressIdx, SystemString(_S("")) + + (patched ? _S(" [PATCHED]") : _S("")), xferSz); + for (size_t i=0 ; igetFSTRoot(), dirIn, keyPath)) + return false; + if (!recursiveMergeNodes(ws, false, &partIn->getFSTRoot(), dirIn, keyPath)) + return false; + if (!recursiveMergeFST(&partIn->getFSTRoot(), dirIn, [&](){m_buildNodes[0].incrementLength();}, keyPath)) + return false; + + return true; +} + +uint64_t DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeMerge(const DiscBase::IPartition* partIn, + const SystemChar* dirIn) +{ + uint64_t totalSz = ROUND_UP_32(partIn->getDOLSize()); + if (!RecursiveCalculateTotalSize(totalSz, &partIn->getFSTRoot(), dirIn)) + return -1; + return totalSz; +} + } diff --git a/lib/DiscGCN.cpp b/lib/DiscGCN.cpp index 671becc..890a32e 100644 --- a/lib/DiscGCN.cpp +++ b/lib/DiscGCN.cpp @@ -1,4 +1,5 @@ #include "nod/DiscGCN.hpp" +#include #define BUFFER_SZ 0x8000 namespace nod @@ -7,16 +8,22 @@ namespace nod class PartitionGCN : public DiscBase::IPartition { public: - PartitionGCN(const DiscGCN& parent, Kind kind, uint64_t offset) + PartitionGCN(const DiscGCN& parent, Kind kind, uint64_t offset, bool& err) : IPartition(parent, kind, offset) { /* GCN-specific header reads */ std::unique_ptr s = beginReadStream(0x420); - uint32_t vals[3]; - s->read(vals, 12); + if (!s) + { + err = true; + return; + } + uint32_t vals[5]; + s->read(vals, 5 * 4); m_dolOff = SBig(vals[0]); m_fstOff = SBig(vals[1]); m_fstSz = SBig(vals[2]); + m_fstMemoryAddr = SBig(vals[4]); s->seek(0x2440 + 0x14); s->read(vals, 8); m_apploaderSz = 32 + SBig(vals[0]) + SBig(vals[1]); @@ -39,11 +46,16 @@ public: uint8_t m_buf[BUFFER_SZ]; public: - PartReadStream(const PartitionGCN& parent, uint64_t offset) + PartReadStream(const PartitionGCN& parent, uint64_t offset, bool& err) : m_parent(parent), m_offset(offset) { size_t block = m_offset / BUFFER_SZ; m_dio = m_parent.m_parent.getDiscIO().beginReadStream(block * BUFFER_SZ); + if (!m_dio) + { + err = true; + return; + } m_dio->read(m_buf, BUFFER_SZ); m_curBlock = block; } @@ -98,15 +110,29 @@ public: std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new PartReadStream(*this, offset)); + bool Err = false; + auto ret = std::unique_ptr(new PartReadStream(*this, offset, Err)); + if (Err) + return {}; + return ret; } }; -DiscGCN::DiscGCN(std::unique_ptr&& dio) -: DiscBase(std::move(dio)) +DiscGCN::DiscGCN(std::unique_ptr&& dio, bool& err) +: DiscBase(std::move(dio), err) { + if (err) + return; + /* One lone partition for GCN */ - m_partitions.emplace_back(new PartitionGCN(*this, IPartition::Kind::Data, 0)); + m_partitions.emplace_back(new PartitionGCN(*this, IPartition::Kind::Data, 0, err)); +} + +DiscBuilderGCN DiscGCN::makeMergeBuilder(const SystemChar* outPath, FProgress progressCB) +{ + IPartition* dataPart = getDataPartition(); + return DiscBuilderGCN(outPath, m_header.m_gameID, m_header.m_gameTitle, + dataPart->getFSTMemoryAddr(), progressCB); } class PartitionBuilderGCN : public DiscBuilderBase::PartitionBuilderBase @@ -122,10 +148,12 @@ public: std::unique_ptr m_fio; public: - PartWriteStream(const PartitionBuilderGCN& parent, uint64_t offset) + PartWriteStream(const PartitionBuilderGCN& parent, uint64_t offset, bool& err) : m_parent(parent), m_offset(offset) { m_fio = m_parent.m_parent.getFileIO().beginWriteStream(offset); + if (!m_fio) + err = true; } void close() {m_fio.reset();} uint64_t position() const {return m_offset;} @@ -152,7 +180,7 @@ public: m_curUser &= 0xfffffffffffffff0; if (m_curUser < 0x30000) { - LogModule.report(logvisor::Fatal, "user area low mark reached"); + LogModule.report(logvisor::Error, "user area low mark reached"); return -1; } static_cast(ws).seek(m_curUser); @@ -166,38 +194,25 @@ public: std::unique_ptr beginWriteStream(uint64_t offset) { - return std::make_unique(*this, offset); + bool Err = false; + std::unique_ptr ret = std::make_unique(*this, offset, Err); + if (Err) + return {}; + return ret; } - bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) + bool _build(const std::function& func) { std::unique_ptr ws = beginWriteStream(0); - bool result = DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(*ws, dirIn, dolIn, apploaderIn); - if (!result) + if (!ws) return false; - - ws = beginWriteStream(0); Header header(m_gameID, m_gameTitle.c_str(), false); header.write(*ws); ws = beginWriteStream(0x2440); - std::unique_ptr rs = NewFileIO(apploaderIn)->beginReadStream(); - char buf[8192]; size_t xferSz = 0; - SystemString apploaderName(apploaderIn); - ++m_parent.m_progressIdx; - while (true) - { - size_t rdSz = rs->read(buf, 8192); - if (!rdSz) - break; - ws->write(buf, rdSz); - xferSz += rdSz; - if (0x2440 + xferSz >= m_curUser) - LogModule.report(logvisor::Fatal, - "apploader flows into user area (one or the other is too big)"); - m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); - } + if (!func(*ws, xferSz)) + return false; size_t fstOff = ROUND_UP_32(xferSz); size_t fstSz = sizeof(FSTNode) * m_buildNodes.size(); @@ -211,10 +226,15 @@ public: fstSz = ROUND_UP_32(fstSz); if (fstOff + fstSz >= m_curUser) - LogModule.report(logvisor::Fatal, + { + LogModule.report(logvisor::Error, "FST flows into user area (one or the other is too big)"); + return false; + } ws = beginWriteStream(0x420); + if (!ws) + return false; uint32_t vals[7]; vals[0] = SBig(uint32_t(m_dolOffset)); vals[1] = SBig(uint32_t(fstOff)); @@ -227,28 +247,109 @@ public: return true; } + + bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) + { + std::unique_ptr ws = beginWriteStream(0); + if (!ws) + return false; + bool result = DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(*ws, dirIn, dolIn, apploaderIn); + if (!result) + return false; + + return _build([this, apploaderIn](IPartWriteStream& ws, size_t& xferSz) -> bool + { + std::unique_ptr rs = NewFileIO(apploaderIn)->beginReadStream(); + if (!rs) + return false; + char buf[8192]; + SystemString apploaderName(apploaderIn); + ++m_parent.m_progressIdx; + while (true) + { + size_t rdSz = rs->read(buf, 8192); + if (!rdSz) + break; + ws.write(buf, rdSz); + xferSz += rdSz; + if (0x2440 + xferSz >= m_curUser) + { + LogModule.report(logvisor::Error, + "apploader flows into user area (one or the other is too big)"); + return false; + } + m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); + } + return true; + }); + } + + bool mergeFromDirectory(const PartitionGCN* partIn, const SystemChar* dirIn) + { + std::unique_ptr ws = beginWriteStream(0); + if (!ws) + return false; + bool result = DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(*ws, partIn, dirIn); + if (!result) + return false; + + return _build([this, partIn](IPartWriteStream& ws, size_t& xferSz) -> bool + { + std::unique_ptr apploaderBuf = partIn->getApploaderBuf(); + size_t apploaderSz = partIn->getApploaderSize(); + SystemString apploaderName(_S("")); + ++m_parent.m_progressIdx; + ws.write(apploaderBuf.get(), apploaderSz); + xferSz += apploaderSz; + if (0x2440 + xferSz >= m_curUser) + { + LogModule.report(logvisor::Error, + "apploader flows into user area (one or the other is too big)"); + return false; + } + m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); + return true; + }); + } }; bool DiscBuilderGCN::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn) { - m_fileIO->beginWriteStream(); - - if (!CheckFreeSpace(m_outPath, 0x57058000)) + if (!m_fileIO->beginWriteStream()) + return false; + if (!CheckFreeSpace(m_outPath.c_str(), 0x57058000)) { - LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_outPath); + LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_outPath.c_str()); return false; } ++m_progressIdx; m_progressCB(m_progressIdx, _S("Preallocating image"), -1); - m_fileIO->beginWriteStream(0x57058000 - 1)->write("", 1); + auto ws = m_fileIO->beginWriteStream(0x57058000 - 1); + if (!ws) + return false; + ws->write("", 1); PartitionBuilderGCN& pb = static_cast(*m_partitions[0]); return pb.buildFromDirectory(dirIn, dolIn, apploaderIn); } +uint64_t DiscBuilderGCN::CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn) +{ + uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dolIn, dirIn); + if (sz == -1) + return -1; + sz += 0x30000; + if (sz > 0x57058000) + { + LogModule.report(logvisor::Error, _S("disc capacity exceeded [%" PRIu64 " / %" PRIu64 "]"), sz, 0x57058000); + return -1; + } + return sz; +} + DiscBuilderGCN::DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], const char* gameTitle, - uint32_t fstMemoryAddr, std::function progressCB) + uint32_t fstMemoryAddr, FProgress progressCB) : DiscBuilderBase(outPath, 0x57058000, progressCB) { PartitionBuilderGCN* partBuilder = new PartitionBuilderGCN(*this, PartitionBuilderBase::Kind::Data, @@ -256,4 +357,43 @@ DiscBuilderGCN::DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], m_partitions.emplace_back(partBuilder); } +DiscMergerGCN::DiscMergerGCN(const SystemChar* outPath, DiscGCN& sourceDisc, FProgress progressCB) +: m_sourceDisc(sourceDisc), m_builder(sourceDisc.makeMergeBuilder(outPath, progressCB)) +{} + +bool DiscMergerGCN::mergeFromDirectory(const SystemChar* dirIn) +{ + if (!m_builder.getFileIO().beginWriteStream()) + return false; + if (!CheckFreeSpace(m_builder.m_outPath.c_str(), 0x57058000)) + { + LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_builder.m_outPath.c_str()); + return false; + } + ++m_builder.m_progressIdx; + m_builder.m_progressCB(m_builder.m_progressIdx, _S("Preallocating image"), -1); + auto ws = m_builder.m_fileIO->beginWriteStream(0x57058000 - 1); + if (!ws) + return false; + ws->write("", 1); + + PartitionBuilderGCN& pb = static_cast(*m_builder.m_partitions[0]); + return pb.mergeFromDirectory(static_cast(m_sourceDisc.getDataPartition()), dirIn); +} + +uint64_t DiscMergerGCN::CalculateTotalSizeRequired(DiscGCN& sourceDisc, const SystemChar* dirIn) +{ + uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeMerge( + sourceDisc.getDataPartition(), dirIn); + if (sz == -1) + return -1; + sz += 0x30000; + if (sz > 0x57058000) + { + LogModule.report(logvisor::Error, _S("disc capacity exceeded [%" PRIu64 " / %" PRIu64 "]"), sz, 0x57058000); + return -1; + } + return sz; +} + } diff --git a/lib/DiscIOISO.cpp b/lib/DiscIOISO.cpp index e5cc8b1..4ae8385 100644 --- a/lib/DiscIOISO.cpp +++ b/lib/DiscIOISO.cpp @@ -17,8 +17,8 @@ public: { friend class DiscIOISO; std::unique_ptr fp; - ReadStream(std::unique_ptr&& fpin) - : fp(std::move(fpin)) {} + ReadStream(std::unique_ptr&& fpin, bool& err) + : fp(std::move(fpin)) { if (!fp) err = true; } public: uint64_t read(void* buf, uint64_t length) {return fp->read(buf, length);} @@ -30,15 +30,19 @@ public: std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new ReadStream(m_fio->beginReadStream(offset))); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(m_fio->beginReadStream(offset), Err)); + if (Err) + return {}; + return ret; } class WriteStream : public IWriteStream { friend class DiscIOISO; std::unique_ptr fp; - WriteStream(std::unique_ptr&& fpin) - : fp(std::move(fpin)) {} + WriteStream(std::unique_ptr&& fpin, bool& err) + : fp(std::move(fpin)) { if (!fp) err = true; } public: uint64_t write(const void* buf, uint64_t length) { @@ -48,7 +52,11 @@ public: std::unique_ptr beginWriteStream(uint64_t offset) const { - return std::unique_ptr(new WriteStream(m_fio->beginWriteStream(offset))); + bool Err = false; + auto ret = std::unique_ptr(new WriteStream(m_fio->beginWriteStream(offset), Err)); + if (Err) + return {}; + return ret; } }; diff --git a/lib/DiscIOWBFS.cpp b/lib/DiscIOWBFS.cpp index 6aefa4a..8e78493 100644 --- a/lib/DiscIOWBFS.cpp +++ b/lib/DiscIOWBFS.cpp @@ -80,7 +80,7 @@ class DiscIOWBFS : public IDiscIO rs.seek(off, SEEK_SET); if (rs.read(buf, count*512ULL) != count*512ULL) { - LogModule.report(logvisor::Fatal, "error reading disc"); + LogModule.report(logvisor::Error, "error reading disc"); return 1; } return 0; @@ -93,19 +93,25 @@ public: /* Temporary file handle to read LBA table */ std::unique_ptr fio = NewFileIO(filepath); std::unique_ptr rs = fio->beginReadStream(); + if (!rs) + return; WBFS* p = &wbfs; WBFSHead tmpHead; - if (rs->read(&tmpHead, sizeof(tmpHead)) != sizeof(tmpHead)) - LogModule.report(logvisor::Fatal, "unable to read WBFS head"); + if (rs->read(&tmpHead, sizeof(tmpHead)) != sizeof(tmpHead)) { + LogModule.report(logvisor::Error, "unable to read WBFS head"); + return; + } unsigned hd_sector_size = 1 << tmpHead.hd_sec_sz_s; unsigned num_hd_sector = SBig(tmpHead.n_hd_sec); wbfsHead.reset(new uint8_t[hd_sector_size]); WBFSHead* head = (WBFSHead*)wbfsHead.get(); rs->seek(0, SEEK_SET); - if (rs->read(head, hd_sector_size) != hd_sector_size) - LogModule.report(logvisor::Fatal, "unable to read WBFS head"); + if (rs->read(head, hd_sector_size) != hd_sector_size) { + LogModule.report(logvisor::Error, "unable to read WBFS head"); + return; + } //constants, but put here for consistancy p->wii_sec_sz = 0x8000; @@ -113,12 +119,15 @@ public: p->n_wii_sec = (num_hd_sector/0x8000)*hd_sector_size; p->n_wii_sec_per_disc = 143432*2;//support for double layers discs.. p->part_lba = 0; - _wbfsReadSector(*rs, p->part_lba, 1, head); + if (_wbfsReadSector(*rs, p->part_lba, 1, head)) + return; if (hd_sector_size && head->hd_sec_sz_s != size_to_shift(hd_sector_size)) { - LogModule.report(logvisor::Fatal, "hd sector size doesn't match"); + LogModule.report(logvisor::Error, "hd sector size doesn't match"); + return; } if (num_hd_sector && head->n_hd_sec != SBig(num_hd_sector)) { - LogModule.report(logvisor::Fatal, "hd num sector doesn't match"); + LogModule.report(logvisor::Error, "hd num sector doesn't match"); + return; } p->hd_sec_sz = 1<hd_sec_sz_s; p->hd_sec_sz_s = head->hd_sec_sz_s; @@ -145,9 +154,12 @@ public: if (head->disc_table[0]) { wbfsDiscInfo.reset(new uint8_t[p->disc_info_sz]); - if (!wbfsDiscInfo) - LogModule.report(logvisor::Fatal, "allocating memory"); - _wbfsReadSector(*rs, p->part_lba+1, disc_info_sz_lba, wbfsDiscInfo.get()); + if (!wbfsDiscInfo) { + LogModule.report(logvisor::Error, "allocating memory"); + return; + } + if (_wbfsReadSector(*rs, p->part_lba+1, disc_info_sz_lba, wbfsDiscInfo.get())) + return; p->n_disc_open++; //for(i=0;in_wbfs_sec_per_disc;i++) // printf("%d,",wbfs_ntohs(d->header->wlba_table[i])); @@ -162,11 +174,11 @@ public: uint64_t m_offset; std::unique_ptr m_tmpBuffer; - ReadStream(const DiscIOWBFS& parent, std::unique_ptr&& fpin, uint64_t offset) + ReadStream(const DiscIOWBFS& parent, std::unique_ptr&& fpin, uint64_t offset, bool& err) : m_parent(parent), fp(std::move(fpin)), m_offset(offset), - m_tmpBuffer(new uint8_t[parent.wbfs.hd_sec_sz]) {} + m_tmpBuffer(new uint8_t[parent.wbfs.hd_sec_sz]) { if (!fp) err = true; } int wbfsReadSector(uint32_t lba, uint32_t count, void* buf) {return DiscIOWBFS::_wbfsReadSector(*fp, lba, count, buf);} @@ -271,7 +283,11 @@ public: std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new ReadStream(*this, NewFileIO(filepath)->beginReadStream(), offset)); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(*this, NewFileIO(filepath)->beginReadStream(), offset, Err)); + if (Err) + return {}; + return ret; } std::unique_ptr beginWriteStream(uint64_t offset) const diff --git a/lib/DiscWii.cpp b/lib/DiscWii.cpp index 1216e4d..8a4b522 100644 --- a/lib/DiscWii.cpp +++ b/lib/DiscWii.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "nod/DiscWii.hpp" #include "nod/aes.hpp" #include "nod/sha1.h" @@ -191,10 +193,16 @@ class PartitionWii : public DiscBase::IPartition uint8_t m_decKey[16]; public: - PartitionWii(const DiscWii& parent, Kind kind, uint64_t offset) + PartitionWii(const DiscWii& parent, Kind kind, uint64_t offset, bool& err) : IPartition(parent, kind, offset) { std::unique_ptr s = parent.getDiscIO().beginReadStream(offset); + if (!s) + { + err = true; + return; + } + m_ticket.read(*s); uint32_t tmdSize; @@ -240,6 +248,12 @@ public: /* Wii-specific header reads (now using title key to decrypt) */ std::unique_ptr ds = beginReadStream(0x420); + if (!ds) + { + err = true; + return; + } + uint32_t vals[3]; ds->read(vals, 12); m_dolOff = SBig(vals[0]) << 2; @@ -275,12 +289,17 @@ public: m_aes->decrypt(&m_encBuf[0x3d0], &m_encBuf[0x400], m_decBuf, 0x7c00); } public: - PartReadStream(const PartitionWii& parent, uint64_t baseOffset, uint64_t offset) + 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); size_t block = m_offset / 0x7c00; m_dio = m_parent.m_parent.getDiscIO().beginReadStream(m_baseOffset + block * 0x8000); + if (!m_dio) + { + err = true; + return; + } decryptBlock(); m_curBlock = block; } @@ -335,20 +354,59 @@ public: std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new PartReadStream(*this, m_dataOff, offset)); + bool Err = false; + auto ret = std::unique_ptr(new PartReadStream(*this, m_dataOff, offset, Err)); + if (Err) + return {}; + return ret; } uint64_t normalizeOffset(uint64_t anOffset) const {return anOffset << 2;} - void writeOutPartitionHeader(const SystemChar* pathOut) const + std::unique_ptr readPartitionHeaderBuf(size_t& szOut) const + { + { + std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4); + if (!rs) + return {}; + + uint32_t h3; + if (rs->read(&h3, 4) != 4) + { + LogModule.report(logvisor::Error, _S("unable to read H3 offset apploader")); + return {}; + } + h3 = SBig(h3); + szOut = uint64_t(h3) << 2; + } + + std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset); + if (!rs) + return {}; + + std::unique_ptr buf(new uint8_t[szOut]); + rs->read(buf.get(), szOut); + + return buf; + } + + bool writeOutPartitionHeader(const SystemChar* pathOut) const { std::unique_ptr ws = NewFileIO(pathOut)->beginWriteStream(); + if (!ws) + return false; uint64_t h3Off; { std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4); + if (!rs) + return false; + uint32_t h3; if (rs->read(&h3, 4) != 4) - LogModule.report(logvisor::Fatal, _S("unable to read H3 offset from %s"), pathOut); + { + LogModule.report(logvisor::Error, _S("unable to read H3 offset to %s"), pathOut); + return false; + } h3 = SBig(h3); h3Off = uint64_t(h3) << 2; } @@ -356,6 +414,9 @@ public: char buf[8192]; size_t rem = h3Off; std::unique_ptr rs = m_parent.getDiscIO().beginReadStream(m_offset); + if (!rs) + return false; + while (rem) { size_t rdSz = nod::min(rem, size_t(8192ul)); @@ -363,12 +424,17 @@ public: ws->write(buf, rdSz); rem -= rdSz; } + + return true; } }; -DiscWii::DiscWii(std::unique_ptr&& dio) -: DiscBase(std::move(dio)) +DiscWii::DiscWii(std::unique_ptr&& dio, bool& err) +: DiscBase(std::move(dio), err) { + if (err) + return; + /* Read partition info */ struct PartInfo { @@ -379,9 +445,15 @@ DiscWii::DiscWii(std::unique_ptr&& dio) uint32_t partDataOff; IPartition::Kind partType; } parts[4]; - PartInfo(IDiscIO& dio) + PartInfo(IDiscIO& dio, bool& err) { std::unique_ptr s = dio.beginReadStream(0x40000); + if (!s) + { + err = true; + return; + } + s->read(this, 32); partCount = SBig(partCount); partInfoOff = SBig(partInfoOff); @@ -394,7 +466,9 @@ DiscWii::DiscWii(std::unique_ptr&& dio) parts[p].partType = IPartition::Kind(SBig(uint32_t(parts[p].partType))); } } - } partInfo(*m_discIO); + } partInfo(*m_discIO, err); + if (err) + return; /* Iterate for data partition */ m_partitions.reserve(partInfo.partCount); @@ -410,22 +484,32 @@ DiscWii::DiscWii(std::unique_ptr&& dio) kind = part.partType; break; default: - LogModule.report(logvisor::Fatal, "invalid partition type %d", part.partType); + LogModule.report(logvisor::Error, "invalid partition type %d", part.partType); + err = true; + return; } - m_partitions.emplace_back(new PartitionWii(*this, kind, part.partDataOff << 2)); + m_partitions.emplace_back(new PartitionWii(*this, kind, part.partDataOff << 2, err)); + if (err) + return; } } -void DiscWii::writeOutDataPartitionHeader(const SystemChar* pathOut) const +DiscBuilderWii DiscWii::makeMergeBuilder(const SystemChar* outPath, bool dualLayer, FProgress progressCB) +{ + return DiscBuilderWii(outPath, m_header.m_gameID, m_header.m_gameTitle, + dualLayer, progressCB); +} + +bool 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; + return static_cast(*part).writeOutPartitionHeader(pathOut); } } + return false; } static const uint8_t ZEROIV[16] = {0}; @@ -433,6 +517,8 @@ static const uint8_t ZEROIV[16] = {0}; class PartitionBuilderWii : public DiscBuilderBase::PartitionBuilderBase { friend class DiscBuilderWii; + friend class DiscMergerWii; + uint64_t m_baseOffset; uint64_t m_userOffset = 0; uint64_t m_curUser = 0x1F0000; @@ -512,17 +598,26 @@ public: } if (m_fio->write(m_buf, 0x200000) != 0x200000) - LogModule.report(logvisor::Fatal, "unable to write full disc group"); + { + LogModule.report(logvisor::Error, "unable to write full disc group"); + return; + } } public: - PartWriteStream(PartitionBuilderWii& parent, uint64_t baseOffset, uint64_t offset) + PartWriteStream(PartitionBuilderWii& parent, uint64_t baseOffset, uint64_t offset, bool& err) : m_parent(parent), m_baseOffset(baseOffset), m_offset(offset) { if (offset % 0x1F0000) - LogModule.report(logvisor::Fatal, "partition write stream MUST begin on 0x1F0000-aligned boundary"); + { + LogModule.report(logvisor::Error, "partition write stream MUST begin on 0x1F0000-aligned boundary"); + err = true; + return; + } size_t group = m_offset / 0x1F0000; m_fio = m_parent.m_parent.getFileIO().beginWriteStream(m_baseOffset + group * 0x200000); + if (!m_fio) + err = true; m_curGroup = group; } ~PartWriteStream() {close();} @@ -597,14 +692,14 @@ public: reqSz = ROUND_UP_32(reqSz); if (m_curUser + reqSz >= 0x1FB450000) { - LogModule.report(logvisor::Fatal, "partition exceeds maximum single-partition capacity"); + LogModule.report(logvisor::Error, "partition exceeds maximum single-partition capacity"); return -1; } uint64_t ret = m_curUser; PartWriteStream& cws = static_cast(ws); if (cws.m_offset > ret) { - LogModule.report(logvisor::Fatal, "partition overwrite error"); + LogModule.report(logvisor::Error, "partition overwrite error"); return -1; } while (cws.m_offset < ret) @@ -620,86 +715,109 @@ public: std::unique_ptr beginWriteStream(uint64_t offset) { - return std::make_unique(*this, m_baseOffset + m_userOffset, offset); + bool Err = false; + std::unique_ptr ret = + std::make_unique(*this, m_baseOffset + m_userOffset, offset, Err); + if (Err) + return {}; + return ret; } - uint64_t buildFromDirectory(const SystemChar* dirIn, - const SystemChar* dolIn, - const SystemChar* apploaderIn, - const SystemChar* partHeadIn) + uint64_t _build(const std::function& contentFunc, + const std::function& apploaderFunc, + const uint8_t* phBuf, size_t phSz, size_t apploaderSz) { /* Read head and validate key members */ - std::unique_ptr ph = NewFileIO(partHeadIn); - uint8_t tkey[16]; { - if (ph->beginReadStream(0x1BF)->read(tkey, 16) != 16) - LogModule.report(logvisor::Fatal, _S("unable to read title key from %s"), partHeadIn); + if (0x1BF + 16 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read title key")); + return -1; + } + memmove(tkey, phBuf + 0x1BF, 16); } uint8_t tkeyiv[16] = {}; { - if (ph->beginReadStream(0x1DC)->read(tkeyiv, 8) != 8) - LogModule.report(logvisor::Fatal, _S("unable to read title key IV from %s"), partHeadIn); + if (0x1DC + 8 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read title key IV")); + return -1; + } + memmove(tkeyiv, phBuf + 0x1DC, 8); } uint8_t ccIdx; { - if (ph->beginReadStream(0x1F1)->read(&ccIdx, 1) != 1) - LogModule.report(logvisor::Fatal, _S("unable to read common key index from %s"), partHeadIn); + if (0x1F1 + 1 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read common key index")); + return -1; + } + memmove(&ccIdx, phBuf + 0x1F1, 1); if (ccIdx > 1) - LogModule.report(logvisor::Fatal, _S("common key index may only be 0 or 1")); + { + LogModule.report(logvisor::Error, _S("common key index may only be 0 or 1")); + return -1; + } } uint32_t tmdSz; { - if (ph->beginReadStream(0x2A4)->read(&tmdSz, 4) != 4) - LogModule.report(logvisor::Fatal, _S("unable to read TMD size from %s"), partHeadIn); + if (0x2A4 + 4 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read TMD size")); + return -1; + } + memmove(&tmdSz, phBuf + 0x2A4, 4); tmdSz = SBig(tmdSz); } uint64_t h3Off; { uint32_t h3Ptr; - if (ph->beginReadStream(0x2B4)->read(&h3Ptr, 4) != 4) - LogModule.report(logvisor::Fatal, _S("unable to read H3 pointer from %s"), partHeadIn); + if (0x2B4 + 4 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read H3 pointer")); + return -1; + } + memmove(&h3Ptr, phBuf + 0x2B4, 4); h3Off = uint64_t(SBig(h3Ptr)) << 2; } uint64_t dataOff; { uint32_t dataPtr; - if (ph->beginReadStream(0x2B8)->read(&dataPtr, 4) != 4) - LogModule.report(logvisor::Fatal, _S("unable to read data pointer from %s"), partHeadIn); + if (0x2B8 + 4 > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read data pointer")); + return -1; + } + memmove(&dataPtr, phBuf + 0x2B8, 4); dataOff = uint64_t(SBig(dataPtr)) << 2; } m_userOffset = dataOff; std::unique_ptr tmdData(new uint8_t[tmdSz]); - if (ph->beginReadStream(0x2C0)->read(tmdData.get(), tmdSz) != tmdSz) - LogModule.report(logvisor::Fatal, _S("unable to read TMD from %s"), partHeadIn); + { + if (0x2C0 + tmdSz > phSz) + { + LogModule.report(logvisor::Error, _S("unable to read TMD")); + return -1; + } + memmove(tmdData.get(), phBuf + 0x2C0, tmdSz); + } /* Copy partition head up to H3 table */ std::unique_ptr ws = m_parent.getFileIO().beginWriteStream(m_baseOffset); - { - uint64_t remCopy = h3Off; - - uint8_t copyBuf[8192]; - std::unique_ptr rs = ph->beginReadStream(); - while (remCopy) - { - size_t rdBytes = rs->read(copyBuf, std::min(size_t(8192), size_t(remCopy))); - if (rdBytes) - { - ws->write(copyBuf, rdBytes); - remCopy -= rdBytes; - continue; - } - for (size_t i=0 ; iwrite("", 1); - break; - } - } + if (!ws) + return -1; + size_t copySz = std::min(phSz, size_t(h3Off)); + ws->write(phBuf, copySz); + size_t remCopy = (h3Off > phSz) ? (h3Off - copySz) : 0; + for (size_t i=0 ; iwrite("", 1); /* Prepare crypto pass */ m_aes->setKey(COMMON_KEYS[ccIdx]); @@ -709,9 +827,10 @@ public: { /* Assemble partition data */ std::unique_ptr cws = beginWriteStream(0x1F0000); - bool result = DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(*cws, dirIn, dolIn, apploaderIn); - if (!result) - return 0; + if (!cws) + return -1; + if (!contentFunc(*cws)) + return -1; /* Pad out user area to nearest cleartext sector */ m_curUser = cws->position(); @@ -726,23 +845,23 @@ public: /* Begin crypto write and add content header */ cws = beginWriteStream(0); + if (!cws) + return -1; Header header(m_gameID, m_gameTitle.c_str(), true, 0, 0, 0); header.write(*cws); - /* Get Apploader Size */ - Sstat theStat; - if (Stat(apploaderIn, &theStat)) - LogModule.report(logvisor::Fatal, _S("unable to stat %s"), apploaderIn); - /* Compute boot table members and write */ - size_t fstOff = 0x2440 + ROUND_UP_32(theStat.st_size); + size_t fstOff = 0x2440 + ROUND_UP_32(apploaderSz); size_t fstSz = sizeof(FSTNode) * m_buildNodes.size(); fstSz += m_buildNameOff; fstSz = ROUND_UP_32(fstSz); if (fstOff + fstSz >= 0x1F0000) - LogModule.report(logvisor::Fatal, + { + LogModule.report(logvisor::Error, "FST flows into user area (one or the other is too big)"); + return -1; + } cws->write(nullptr, 0x420 - sizeof(Header)); uint32_t vals[4]; @@ -752,29 +871,16 @@ public: vals[3] = SBig(uint32_t(fstSz)); cws->write(vals, 16); - /* Write Apploader */ - cws->write(nullptr, 0x2440 - 0x430); - std::unique_ptr rs = NewFileIO(apploaderIn)->beginReadStream(); - char buf[8192]; size_t xferSz = 0; - SystemString apploaderName(apploaderIn); - ++m_parent.m_progressIdx; - while (true) - { - size_t rdSz = rs->read(buf, 8192); - if (!rdSz) - break; - cws->write(buf, rdSz); - xferSz += rdSz; - if (0x2440 + xferSz >= 0x1F0000) - LogModule.report(logvisor::Fatal, - "apploader flows into user area (one or the other is too big)"); - m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); - } + if (!apploaderFunc(*cws, xferSz)) + return -1; size_t fstOffRel = fstOff - 0x2440; if (xferSz > fstOffRel) - LogModule.report(logvisor::Fatal, "apploader unexpectedly flows into FST"); + { + LogModule.report(logvisor::Error, "apploader unexpectedly flows into FST"); + return -1; + } for (size_t i=0 ; iwrite("\xff", 1); @@ -789,22 +895,26 @@ public: uint64_t cryptContentSize = (groupCount * 0x200000) >> uint64_t(2); uint32_t cryptContentSizeBig = SBig(uint32_t(cryptContentSize)); ws = m_parent.getFileIO().beginWriteStream(m_baseOffset + 0x2BC); + if (!ws) + return -1; ws->write(&cryptContentSizeBig, 0x4); /* Write new H3 */ ws = m_parent.getFileIO().beginWriteStream(m_baseOffset + h3Off); + if (!ws) + return -1; ws->write(m_h3, 0x18000); /* Compute content hash and replace in TMD */ sha1nfo sha; sha1_init(&sha); sha1_write(&sha, (char*)m_h3, 0x18000); - memcpy(tmdData.get() + 0x1F4, sha1_result(&sha), 20); + memmove(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); + memmove(tmdData.get() + 0x1EC, &contentSizeBig, 8); /* Zero-out TMD signature to simplify brute-force */ memset(tmdData.get() + 0x4, 0, 0x100); @@ -841,10 +951,98 @@ public: m_parent.m_progressCB(m_parent.m_progressIdx, bfName, attempts); ws = m_parent.getFileIO().beginWriteStream(m_baseOffset + 0x2C0); + if (!ws) + return -1; ws->write(tmdData.get(), tmdSz); return m_baseOffset + dataOff + groupCount * 0x200000; } + + uint64_t buildFromDirectory(const SystemChar* dirIn, + const SystemChar* dolIn, + const SystemChar* apploaderIn, + const SystemChar* partHeadIn) + { + std::unique_ptr ph = NewFileIO(partHeadIn); + size_t phSz = ph->size(); + std::unique_ptr phBuf(new uint8_t[phSz]); + { + auto rs = ph->beginReadStream(); + if (!rs) + return -1; + rs->read(phBuf.get(), phSz); + } + + /* Get Apploader Size */ + Sstat theStat; + if (Stat(apploaderIn, &theStat)) + { + LogModule.report(logvisor::Error, _S("unable to stat %s"), apploaderIn); + return -1; + } + + return _build( + [this, dirIn, dolIn, apploaderIn](IPartWriteStream& cws) -> bool + { + return DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(cws, dirIn, dolIn, apploaderIn); + }, + [this, apploaderIn](IPartWriteStream& cws, size_t& xferSz) -> bool + { + cws.write(nullptr, 0x2440 - 0x430); + std::unique_ptr rs = NewFileIO(apploaderIn)->beginReadStream(); + if (!rs) + return false; + char buf[8192]; + SystemString apploaderName(apploaderIn); + ++m_parent.m_progressIdx; + while (true) + { + size_t rdSz = rs->read(buf, 8192); + if (!rdSz) + break; + cws.write(buf, rdSz); + xferSz += rdSz; + if (0x2440 + xferSz >= 0x1F0000) + { + LogModule.report(logvisor::Error, + "apploader flows into user area (one or the other is too big)"); + return false; + } + m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); + } + return true; + }, phBuf.get(), phSz, theStat.st_size); + } + + bool mergeFromDirectory(const PartitionWii* partIn, const SystemChar* dirIn) + { + size_t phSz; + std::unique_ptr phBuf = partIn->readPartitionHeaderBuf(phSz); + + return _build( + [this, partIn, dirIn](IPartWriteStream& cws) -> bool + { + return DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(cws, partIn, dirIn); + }, + [this, partIn](IPartWriteStream& cws, size_t& xferSz) -> bool + { + cws.write(nullptr, 0x2440 - 0x430); + std::unique_ptr apploaderBuf = partIn->getApploaderBuf(); + size_t apploaderSz = partIn->getApploaderSize(); + SystemString apploaderName(_S("")); + ++m_parent.m_progressIdx; + cws.write(apploaderBuf.get(), apploaderSz); + xferSz += apploaderSz; + if (0x2440 + xferSz >= 0x1F0000) + { + LogModule.report(logvisor::Error, + "apploader flows into user area (one or the other is too big)"); + return false; + } + m_parent.m_progressCB(m_parent.m_progressIdx, apploaderName, xferSz); + return true; + }, phBuf.get(), phSz, partIn->getApploaderSize()); + } }; bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, @@ -852,22 +1050,28 @@ bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemCha { PartitionBuilderWii& pb = static_cast(*m_partitions[0]); uint64_t filledSz = pb.m_baseOffset; - m_fileIO->beginWriteStream(); + if (!m_fileIO->beginWriteStream()) + return false; - if (!CheckFreeSpace(m_outPath, m_discCapacity)) + if (!CheckFreeSpace(m_outPath.c_str(), m_discCapacity)) { - LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_outPath); + LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_outPath.c_str()); return false; } ++m_progressIdx; m_progressCB(m_progressIdx, _S("Preallocating image"), -1); - m_fileIO->beginWriteStream(m_discCapacity - 1)->write("", 1); + std::unique_ptr ws = m_fileIO->beginWriteStream(m_discCapacity - 1); + if (!ws) + return false; + ws->write("", 1); /* Assemble image */ filledSz = pb.buildFromDirectory(dirIn, dolIn, apploaderIn, partHeadIn); - if (filledSz >= uint64_t(m_discCapacity)) + if (filledSz == -1) + return false; + else if (filledSz >= uint64_t(m_discCapacity)) { - LogModule.report(logvisor::Fatal, "data partition exceeds disc capacity"); + LogModule.report(logvisor::Error, "data partition exceeds disc capacity"); return false; } @@ -875,21 +1079,29 @@ bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemCha m_progressCB(m_progressIdx, _S("Finishing Disc"), -1); /* Populate disc header */ - std::unique_ptr ws = m_fileIO->beginWriteStream(0); + ws = m_fileIO->beginWriteStream(0); + if (!ws) + return false; Header header(pb.getGameID(), pb.getGameTitle().c_str(), true, 0, 0, 0); header.write(*ws); /* Populate partition info */ ws = m_fileIO->beginWriteStream(0x40000); + if (!ws) + return false; uint32_t vals[2] = {SBig(uint32_t(1)), SBig(uint32_t(0x40020 >> uint64_t(2)))}; ws->write(vals, 8); ws = m_fileIO->beginWriteStream(0x40020); + if (!ws) + return false; vals[0] = SBig(uint32_t(pb.m_baseOffset >> uint64_t(2))); ws->write(vals, 4); /* Populate region info */ ws = m_fileIO->beginWriteStream(0x4E000); + if (!ws) + return false; const char* gameID = pb.getGameID(); if (gameID[3] == 'P') vals[0] = SBig(uint32_t(2)); @@ -901,11 +1113,15 @@ bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemCha /* Make disc unrated */ ws = m_fileIO->beginWriteStream(0x4E010); + if (!ws) + return false; for (int i=0 ; i<16 ; ++i) ws->write("\x80", 1); /* Fill image to end */ ws = m_fileIO->beginWriteStream(filledSz); + if (!ws) + return false; uint8_t fillBuf[512]; memset(fillBuf, 0xff, 512); for (size_t i=m_discCapacity-filledSz ; i>0 ;) @@ -923,8 +1139,27 @@ bool DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemCha return true; } -DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, bool dualLayer, - std::function progressCB) +uint64_t DiscBuilderWii::CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn, + bool& dualLayer) +{ + uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dolIn, dirIn); + if (sz == -1) + return -1; + auto szDiv = std::lldiv(sz, 0x1F0000); + if (szDiv.rem) ++szDiv.quot; + sz = szDiv.quot * 0x200000; + sz += 0x200000; + dualLayer = (sz > 0x118240000); + if (sz > 0x1FB4E0000) + { + LogModule.report(logvisor::Error, _S("disc capacity exceeded [%" PRIu64 " / %" PRIu64 "]"), sz, 0x1FB4E0000); + return -1; + } + return sz; +} + +DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle, + bool dualLayer, FProgress progressCB) : DiscBuilderBase(outPath, dualLayer ? 0x1FB4E0000 : 0x118240000, progressCB), m_dualLayer(dualLayer) { PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, PartitionBuilderBase::Kind::Data, @@ -932,4 +1167,121 @@ DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], m_partitions.emplace_back(partBuilder); } +DiscMergerWii::DiscMergerWii(const SystemChar* outPath, DiscWii& sourceDisc, + bool dualLayer, FProgress progressCB) +: m_sourceDisc(sourceDisc), m_builder(sourceDisc.makeMergeBuilder(outPath, dualLayer, progressCB)) +{} + +bool DiscMergerWii::mergeFromDirectory(const SystemChar* dirIn) +{ + PartitionBuilderWii& pb = static_cast(*m_builder.m_partitions[0]); + uint64_t filledSz = pb.m_baseOffset; + if (!m_builder.m_fileIO->beginWriteStream()) + return false; + + if (!CheckFreeSpace(m_builder.m_outPath.c_str(), m_builder.m_discCapacity)) + { + LogModule.report(logvisor::Error, _S("not enough free disk space for %s"), m_builder.m_outPath.c_str()); + return false; + } + ++m_builder.m_progressIdx; + m_builder.m_progressCB(m_builder.m_progressIdx, _S("Preallocating image"), -1); + std::unique_ptr ws = m_builder.m_fileIO->beginWriteStream(m_builder.m_discCapacity - 1); + if (!ws) + return false; + ws->write("", 1); + + /* Assemble image */ + filledSz = pb.mergeFromDirectory(static_cast(m_sourceDisc.getDataPartition()), dirIn); + if (filledSz == -1) + return false; + else if (filledSz >= uint64_t(m_builder.m_discCapacity)) + { + LogModule.report(logvisor::Error, "data partition exceeds disc capacity"); + return false; + } + + ++m_builder.m_progressIdx; + m_builder.m_progressCB(m_builder.m_progressIdx, _S("Finishing Disc"), -1); + + /* Populate disc header */ + ws = m_builder.m_fileIO->beginWriteStream(0); + if (!ws) + return false; + m_sourceDisc.getHeader().write(*ws); + + /* Populate partition info */ + ws = m_builder.m_fileIO->beginWriteStream(0x40000); + if (!ws) + return false; + uint32_t vals[2] = {SBig(uint32_t(1)), SBig(uint32_t(0x40020 >> uint64_t(2)))}; + ws->write(vals, 8); + + ws = m_builder.m_fileIO->beginWriteStream(0x40020); + if (!ws) + return false; + vals[0] = SBig(uint32_t(pb.m_baseOffset >> uint64_t(2))); + ws->write(vals, 4); + + /* Populate region info */ + ws = m_builder.m_fileIO->beginWriteStream(0x4E000); + if (!ws) + return false; + 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 = m_builder.m_fileIO->beginWriteStream(0x4E010); + if (!ws) + return false; + for (int i=0 ; i<16 ; ++i) + ws->write("\x80", 1); + + /* Fill image to end */ + ws = m_builder.m_fileIO->beginWriteStream(filledSz); + if (!ws) + return false; + uint8_t fillBuf[512]; + memset(fillBuf, 0xff, 512); + for (size_t i=m_builder.m_discCapacity-filledSz ; i>0 ;) + { + if (i >= 512) + { + ws->write(fillBuf, 512); + i -= 512; + continue; + } + ws->write(fillBuf, i); + break; + } + + return true; +} + +uint64_t DiscMergerWii::CalculateTotalSizeRequired(DiscWii& sourceDisc, + const SystemChar* dirIn, bool& dualLayer) +{ + uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeMerge( + sourceDisc.getDataPartition(), dirIn); + if (sz == -1) + return -1; + auto szDiv = std::lldiv(sz, 0x1F0000); + if (szDiv.rem) ++szDiv.quot; + sz = szDiv.quot * 0x200000; + sz += 0x200000; + dualLayer = (sz > 0x118240000); + if (sz > 0x1FB4E0000) + { + LogModule.report(logvisor::Error, _S("disc capacity exceeded [%" PRIu64 " / %" PRIu64 "]"), sz, 0x1FB4E0000); + return -1; + } + return sz; +} + } diff --git a/lib/FileIOFILE.cpp b/lib/FileIOFILE.cpp index 60d5bbd..7e7813b 100644 --- a/lib/FileIOFILE.cpp +++ b/lib/FileIOFILE.cpp @@ -41,14 +41,17 @@ public: { FILE* fp; int64_t m_maxWriteSize; - WriteStream(const SystemString& path, int64_t maxWriteSize) + WriteStream(const SystemString& path, int64_t maxWriteSize, bool& err) : m_maxWriteSize(maxWriteSize) { fp = fopen(path.c_str(), "wb"); if (!fp) - LogModule.report(logvisor::Fatal, _S("unable to open '%s' for writing"), path.c_str()); + { + LogModule.report(logvisor::Error, _S("unable to open '%s' for writing"), path.c_str()); + err = true; + } } - WriteStream(const SystemString& path, uint64_t offset, int64_t maxWriteSize) + WriteStream(const SystemString& path, uint64_t offset, int64_t maxWriteSize, bool& err) : m_maxWriteSize(maxWriteSize) { fp = fopen(path.c_str(), "ab"); @@ -61,7 +64,8 @@ public: FSeek(fp, offset, SEEK_SET); return; FailLoc: - LogModule.report(logvisor::Fatal, _S("unable to open '%s' for writing"), path.c_str()); + LogModule.report(logvisor::Error, _S("unable to open '%s' for writing"), path.c_str()); + err = true; } ~WriteStream() { @@ -72,7 +76,10 @@ public: if (m_maxWriteSize >= 0) { if (FTell(fp) + length > m_maxWriteSize) - LogModule.report(logvisor::Fatal, _S("write operation exceeds file's %" PRIi64 "-byte limit"), m_maxWriteSize); + { + LogModule.report(logvisor::Error, _S("write operation exceeds file's %" PRIi64 "-byte limit"), m_maxWriteSize); + return 0; + } } return fwrite(buf, 1, length, fp); } @@ -86,12 +93,12 @@ public: uint64_t readSz = discio.read(buf, thisSz); if (thisSz != readSz) { - LogModule.report(logvisor::Fatal, "unable to read enough from disc"); + LogModule.report(logvisor::Error, "unable to read enough from disc"); return read; } if (write(buf, readSz) != readSz) { - LogModule.report(logvisor::Fatal, "unable to write in file"); + LogModule.report(logvisor::Error, "unable to write in file"); return read; } length -= thisSz; @@ -102,25 +109,38 @@ public: }; std::unique_ptr beginWriteStream() const { - return std::unique_ptr(new WriteStream(m_path, m_maxWriteSize)); + bool Err = false; + auto ret = std::unique_ptr(new WriteStream(m_path, m_maxWriteSize, Err)); + if (Err) + return {}; + return ret; } std::unique_ptr beginWriteStream(uint64_t offset) const { - return std::unique_ptr(new WriteStream(m_path, offset, m_maxWriteSize)); + bool Err = false; + auto ret = std::unique_ptr(new WriteStream(m_path, offset, m_maxWriteSize, Err)); + if (Err) + return {}; + return ret; } struct ReadStream : public IFileIO::IReadStream { FILE* fp; - ReadStream(const SystemString& path) + ReadStream(const SystemString& path, bool& err) { fp = fopen(path.c_str(), "rb"); if (!fp) - LogModule.report(logvisor::Fatal, _S("unable to open '%s' for reading"), path.c_str()); + { + err = true; + LogModule.report(logvisor::Error, _S("unable to open '%s' for reading"), path.c_str()); + } } - ReadStream(const SystemString& path, uint64_t offset) - : ReadStream(path) + ReadStream(const SystemString& path, uint64_t offset, bool& err) + : ReadStream(path, err) { + if (err) + return; FSeek(fp, offset, SEEK_SET); } ~ReadStream() @@ -148,12 +168,12 @@ public: uint64_t thisSz = nod::min(uint64_t(0x7c00), length); if (read(buf, thisSz) != thisSz) { - LogModule.report(logvisor::Fatal, "unable to read enough from file"); + LogModule.report(logvisor::Error, "unable to read enough from file"); return written; } if (discio.write(buf, thisSz) != thisSz) { - LogModule.report(logvisor::Fatal, "unable to write enough to disc"); + LogModule.report(logvisor::Error, "unable to write enough to disc"); return written; } length -= thisSz; @@ -164,11 +184,19 @@ public: }; std::unique_ptr beginReadStream() const { - return std::unique_ptr(new ReadStream(m_path)); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(m_path, Err)); + if (Err) + return {}; + return ret; } std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new ReadStream(m_path, offset)); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(m_path, offset, Err)); + if (Err) + return {}; + return ret; } }; diff --git a/lib/FileIOWin32.cpp b/lib/FileIOWin32.cpp index 941f948..4759aca 100644 --- a/lib/FileIOWin32.cpp +++ b/lib/FileIOWin32.cpp @@ -47,21 +47,28 @@ public: { HANDLE fp; int64_t m_maxWriteSize; - WriteStream(const SystemString& path, int64_t maxWriteSize) + WriteStream(const SystemString& path, int64_t maxWriteSize, bool& err) : m_maxWriteSize(maxWriteSize) { 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::Fatal, _S("unable to open '%s' for writing"), path.c_str()); + { + LogModule.report(logvisor::Error, _S("unable to open '%s' for writing"), path.c_str()); + err = true; + } } - WriteStream(const SystemString& path, uint64_t offset, int64_t maxWriteSize) + WriteStream(const SystemString& path, uint64_t offset, int64_t maxWriteSize, bool& err) : m_maxWriteSize(maxWriteSize) { 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::Fatal, _S("unable to open '%s' for writing"), path.c_str()); + { + LogModule.report(logvisor::Error, _S("unable to open '%s' for writing"), path.c_str()); + err = true; + return; + } LARGE_INTEGER lioffset; lioffset.QuadPart = offset; SetFilePointerEx(fp, lioffset, nullptr, FILE_BEGIN); @@ -78,7 +85,10 @@ public: LARGE_INTEGER res; SetFilePointerEx(fp, li, &res, FILE_CURRENT); if (res.QuadPart + int64_t(length) > m_maxWriteSize) - LogModule.report(logvisor::Fatal, _S("write operation exceeds file's %" PRIi64 "-byte limit"), m_maxWriteSize); + { + LogModule.report(logvisor::Error, _S("write operation exceeds file's %" PRIi64 "-byte limit"), m_maxWriteSize); + return 0; + } } DWORD ret = 0; @@ -95,12 +105,12 @@ public: uint64_t readSz = discio.read(buf, thisSz); if (thisSz != readSz) { - LogModule.report(logvisor::Fatal, "unable to read enough from disc"); + LogModule.report(logvisor::Error, "unable to read enough from disc"); return read; } if (write(buf, readSz) != readSz) { - LogModule.report(logvisor::Fatal, "unable to write in file"); + LogModule.report(logvisor::Error, "unable to write in file"); return read; } length -= thisSz; @@ -111,24 +121,35 @@ public: }; std::unique_ptr beginWriteStream() const { - return std::unique_ptr(new WriteStream(m_path, m_maxWriteSize)); + bool Err = false; + auto ret = std::unique_ptr(new WriteStream(m_path, m_maxWriteSize, Err)); + if (Err) + return {}; + return ret; } std::unique_ptr beginWriteStream(uint64_t offset) const { - return std::unique_ptr(new WriteStream(m_path, offset, m_maxWriteSize)); + bool Err = false; + auto ret = std::unique_ptr(new WriteStream(m_path, offset, m_maxWriteSize, Err)); + if (Err) + return {}; + return ret; } struct ReadStream : public IFileIO::IReadStream { HANDLE fp; - ReadStream(const SystemString& path) + ReadStream(const SystemString& path, bool& err) { 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::Fatal, _S("unable to open '%s' for reading"), path.c_str()); + { + err = true; + LogModule.report(logvisor::Error, _S("unable to open '%s' for reading"), path.c_str()); + } } - ReadStream(const SystemString& path, uint64_t offset) + ReadStream(const SystemString& path, uint64_t offset, bool& err) : ReadStream(path) { LARGE_INTEGER lioffset; @@ -167,12 +188,12 @@ public: uint64_t thisSz = nod::min(uint64_t(0x7c00), length); if (read(buf, thisSz) != thisSz) { - LogModule.report(logvisor::Fatal, "unable to read enough from file"); + LogModule.report(logvisor::Error, "unable to read enough from file"); return written; } if (discio.write(buf, thisSz) != thisSz) { - LogModule.report(logvisor::Fatal, "unable to write enough to disc"); + LogModule.report(logvisor::Error, "unable to write enough to disc"); return written; } length -= thisSz; @@ -183,11 +204,19 @@ public: }; std::unique_ptr beginReadStream() const { - return std::unique_ptr(new ReadStream(m_path)); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(m_path, Err)); + if (Err) + return {}; + return ret; } std::unique_ptr beginReadStream(uint64_t offset) const { - return std::unique_ptr(new ReadStream(m_path, offset)); + bool Err = false; + auto ret = std::unique_ptr(new ReadStream(m_path, offset, Err)); + if (Err) + return {}; + return ret; } }; diff --git a/lib/nod.cpp b/lib/nod.cpp index e8b1d6e..64e8d02 100644 --- a/lib/nod.cpp +++ b/lib/nod.cpp @@ -17,15 +17,20 @@ std::unique_ptr OpenDiscFromImage(const SystemChar* path, bool& isWii) if (!fio->exists()) { LogModule.report(logvisor::Error, _S("Unable to open '%s'"), path); - return std::unique_ptr(); + return {}; } std::unique_ptr rs = fio->beginReadStream(); + if (!rs) + return {}; isWii = false; std::unique_ptr discIO; uint32_t magic = 0; if (rs->read(&magic, 4) != 4) - LogModule.report(logvisor::Fatal, _S("Unable to read magic from '%s'"), path); + { + LogModule.report(logvisor::Error, _S("Unable to read magic from '%s'"), path); + return {}; + } if (magic == nod::SBig((uint32_t)'WBFS')) { @@ -54,14 +59,23 @@ std::unique_ptr OpenDiscFromImage(const SystemChar* path, bool& isWii) if (!discIO) { LogModule.report(logvisor::Error, _S("'%s' is not a valid image"), path); - return std::unique_ptr(); + return {}; } + bool Err = false; + std::unique_ptr ret; if (isWii) - return std::unique_ptr(new DiscWii(std::move(discIO))); - - return std::unique_ptr(new DiscGCN(std::move(discIO))); + { + ret = std::unique_ptr(new DiscWii(std::move(discIO), Err)); + if (Err) + return {}; + return ret; + } + ret = std::unique_ptr(new DiscGCN(std::move(discIO), Err)); + if (Err) + return {}; + return ret; } std::unique_ptr OpenDiscFromImage(const SystemChar* path)