Refactor of extracted directory structure and API simplification

This commit is contained in:
Jack Andersen 2017-07-01 13:36:16 -10:00
parent e49568ac83
commit 41148a1368
14 changed files with 814 additions and 577 deletions

View File

@ -51,12 +51,12 @@ auto progFunc = [&](size_t idx, const nod::SystemString& name, size_t bytes)
};
/* Making a GCN image */
nod::DiscBuilderGCN b(isoOutPath, gameID, gameTitle, dolLoadAddress, progFunc);
ret = b.buildFromDirectory(fsRootDirPath, bootDolPath, apploaderPath);
nod::DiscBuilderGCN b(isoOutPath, progFunc);
ret = b.buildFromDirectory(fsRootDirPath);
/* Making a Wii image */
nod::DiscBuilderWii b(isoOutPath, gameID, gameTitle, dualLayer, progFunc);
ret = b.buildFromDirectory(fsRootDirPath, bootDolPath, apploaderPath, partitionHeadPath);
nod::DiscBuilderWii b(isoOutPath, dualLayer, progFunc);
ret = b.buildFromDirectory(fsRootDirPath);
```
Wii images are fakesigned using a commonly-applied [signing bug](http://wiibrew.org/wiki/Signing_bug).
@ -75,7 +75,6 @@ An extract/repack works like so:
>$ cd <dir-out>
# Then one of:
>$ nodtool makegcn <gameid> <game-title> fsroot boot.dol apploader.bin [<image-out>]
>$ nodtool makewiisl <gameid> <game-title> fsroot boot.dol apploader.bin partition_head.bin [<image-out>]
>$ nodtool makewiidl <gameid> <game-title> fsroot boot.dol apploader.bin partition_head.bin [<image-out>]
>$ nodtool makegcn fsroot [<image-out>]
>$ nodtool makewii fsroot [<image-out>]
```

View File

@ -7,8 +7,8 @@ static void printHelp()
{
fprintf(stderr, "Usage:\n"
" nodtool extract [-f] <image-in> [<dir-out>]\n"
" nodtool makegcn <gameid> <game-title> <fsroot-in> <dol-in> <apploader-in> [<image-out>]\n"
" nodtool makewii <gameid> <game-title> <fsroot-in> <dol-in> <apploader-in> <parthead-in> [<image-out>]\n"
" nodtool makegcn <fsroot-in> [<image-out>]\n"
" nodtool makewii <fsroot-in> [<image-out>]\n"
" nodtool mergegcn <fsroot-in> <image-in> [<image-out>]\n"
" nodtool mergewii <fsroot-in> <image-in> [<image-out>]\n");
}
@ -26,8 +26,8 @@ int main(int argc, char* argv[])
#endif
{
if (argc < 3 ||
(!strcasecmp(argv[1], _S("makegcn")) && argc < 7) ||
(!strcasecmp(argv[1], _S("makewii")) && argc < 8) ||
(!strcasecmp(argv[1], _S("makegcn")) && argc < 3) ||
(!strcasecmp(argv[1], _S("makewii")) && argc < 3) ||
(!strcasecmp(argv[1], _S("mergegcn")) && argc < 4) ||
(!strcasecmp(argv[1], _S("mergewii")) && argc < 4))
{
@ -39,9 +39,12 @@ int main(int argc, char* argv[])
logvisor::RegisterStandardExceptions();
logvisor::RegisterConsoleLogger();
nod::ExtractionContext ctx = { true, true, [&](const std::string& str, float c){
fprintf(stderr, "Current node: %s, Extraction %g%% Complete\n", str.c_str(), c * 100.f);
}};
bool verbose = false;
nod::ExtractionContext ctx = {true,
[&](const std::string& str, float c) {
if (verbose)
fprintf(stderr, "Current node: %s, Extraction %g%% Complete\n", str.c_str(), c * 100.f);
}};
const nod::SystemChar* inDir = nullptr;
const nod::SystemChar* outDir = _S(".");
@ -50,7 +53,7 @@ int main(int argc, char* argv[])
if (argv[a][0] == '-' && argv[a][1] == 'f')
ctx.force = true;
else if (argv[a][0] == '-' && argv[a][1] == 'v')
ctx.verbose = true;
verbose = true;
else if (!inDir)
inDir = argv[a];
@ -77,10 +80,6 @@ int main(int argc, char* argv[])
nod::Mkdir(outDir, 0755);
if (isWii)
static_cast<nod::DiscWii&>(*disc).writeOutDataPartitionHeader(
(nod::SystemString(outDir) + _S("/partition_head.bin")).c_str());
nod::Partition* dataPart = disc->getDataPartition();
if (!dataPart)
return 1;
@ -90,59 +89,30 @@ int main(int argc, char* argv[])
}
else if (!strcasecmp(argv[1], _S("makegcn")))
{
#if NOD_UCS2
if (wcslen(argv[2]) < 6)
{
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::Error, _S("game-id is not at least 6 characters"));
return 1;
}
#endif
/* Pre-validate paths */
/* Pre-validate path */
nod::Sstat theStat;
if (nod::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode))
if (nod::Stat(argv[2], &theStat) || !S_ISDIR(theStat.st_mode))
{
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::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::Error, "unable to stat %s as file", argv[6]);
nod::LogModule.report(logvisor::Error, _S("unable to stat %s as directory"), argv[2]);
return 1;
}
nod::SystemString gameIdSys(argv[2]);
nod::SystemUTF8View gameId(gameIdSys);
nod::SystemString gameTitleSys(argv[3]);
nod::SystemUTF8View gameTitle(gameTitleSys);
if (nod::DiscBuilderGCN::CalculateTotalSizeRequired(argv[4], argv[5]) == -1)
if (nod::DiscBuilderGCN::CalculateTotalSizeRequired(argv[2]) == -1)
return 1;
nod::EBuildResult ret;
if (argc < 8)
if (argc < 4)
{
nod::SystemString outPath(argv[4]);
nod::SystemString outPath(argv[2]);
outPath.append(_S(".iso"));
nod::DiscBuilderGCN b(outPath.c_str(), gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), 0x0003EB60, progFunc);
ret = b.buildFromDirectory(argv[4], argv[5], argv[6]);
nod::DiscBuilderGCN b(outPath.c_str(), progFunc);
ret = b.buildFromDirectory(argv[2]);
}
else
{
nod::DiscBuilderGCN b(argv[7], gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), 0x0003EB60, progFunc);
ret = b.buildFromDirectory(argv[4], argv[5], argv[6]);
nod::DiscBuilderGCN b(argv[3], progFunc);
ret = b.buildFromDirectory(argv[2]);
}
printf("\n");
@ -151,65 +121,31 @@ int main(int argc, char* argv[])
}
else if (!strcasecmp(argv[1], _S("makewii")))
{
#if NOD_UCS2
if (wcslen(argv[2]) < 6)
{
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::Error, _S("game-id is not at least 6 characters"));
return 1;
}
#endif
/* Pre-validate paths */
/* Pre-validate path */
nod::Sstat theStat;
if (nod::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode))
if (nod::Stat(argv[2], &theStat) || !S_ISDIR(theStat.st_mode))
{
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::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::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::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);
bool dual = false;
if (nod::DiscBuilderWii::CalculateTotalSizeRequired(argv[4], argv[5], dual) == -1)
if (nod::DiscBuilderWii::CalculateTotalSizeRequired(argv[2], dual) == -1)
return 1;
nod::EBuildResult ret;
if (argc < 9)
if (argc < 4)
{
nod::SystemString outPath(argv[4]);
nod::SystemString outPath(argv[2]);
outPath.append(_S(".iso"));
nod::DiscBuilderWii b(outPath.c_str(), gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), dual, progFunc);
ret = b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]);
nod::DiscBuilderWii b(outPath.c_str(), dual, progFunc);
ret = b.buildFromDirectory(argv[2]);
}
else
{
nod::DiscBuilderWii b(argv[8], gameId.utf8_str().c_str(), gameTitle.utf8_str().c_str(), dual, progFunc);
ret = b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]);
nod::DiscBuilderWii b(argv[3], dual, progFunc);
ret = b.buildFromDirectory(argv[2]);
}
printf("\n");

View File

@ -67,60 +67,126 @@ struct Header
uint32_t m_debugMonOff;
uint32_t m_debugLoadAddr;
char m_unk3[0x18];
uint32_t m_dolOff;
uint32_t m_fstOff;
uint32_t m_fstSz;
uint32_t m_fstMaxSz;
uint32_t m_fstMemoryAddress;
uint32_t m_userPosition;
uint32_t m_userSz;
uint8_t padding1[4];
Header() = default;
Header(IDiscIO& dio, bool& err)
{
std::unique_ptr<IDiscIO::IReadStream> s = dio.beginReadStream(0);
if (!s)
auto rs = dio.beginReadStream();
if (!rs)
{
err = true;
return;
}
s->read(this, sizeof(*this));
read(*rs);
}
void read(IReadStream& s)
{
memset(this, 0, sizeof(*this));
s.read(this, sizeof(*this));
m_wiiMagic = SBig(m_wiiMagic);
m_gcnMagic = SBig(m_gcnMagic);
m_debugMonOff = SBig(m_debugMonOff);
m_debugLoadAddr = SBig(m_debugLoadAddr);
m_dolOff = SBig(m_dolOff);
m_fstOff = SBig(m_fstOff);
m_fstSz = SBig(m_fstSz);
m_fstMaxSz = SBig(m_fstMaxSz);
m_fstMemoryAddress = SBig(m_fstMemoryAddress);
m_userPosition = SBig(m_userPosition);
m_userSz = SBig(m_userSz);
}
Header(const char gameID[6], const char* gameTitle, bool wii, char discNum=0, char discVersion=0,
char audioStreaming=1, char streamBufSz=0)
{
memset(this, 0, sizeof(*this));
memcpy(m_gameID, gameID, 6);
strncpy(m_gameTitle, gameTitle, 64);
m_discNum = discNum;
m_discVersion = discVersion;
m_audioStreaming = audioStreaming;
m_streamBufSz = streamBufSz;
if (wii)
m_wiiMagic = 0x5D1C9EA3;
else
m_gcnMagic = 0xC2339F3D;
}
template <class WriteStream>
void write(WriteStream& ws) const
void write(IWriteStream& ws) const
{
Header hs(*this);
hs.m_wiiMagic = SBig(hs.m_wiiMagic);
hs.m_gcnMagic = SBig(hs.m_gcnMagic);
hs.m_debugMonOff = SBig(hs.m_debugMonOff);
hs.m_debugLoadAddr = SBig(hs.m_debugLoadAddr);
hs.m_dolOff = SBig(hs.m_dolOff);
hs.m_fstOff = SBig(hs.m_fstOff);
hs.m_fstSz = SBig(hs.m_fstSz);
hs.m_fstMaxSz = SBig(hs.m_fstMaxSz);
hs.m_fstMemoryAddress = SBig(hs.m_fstMemoryAddress);
hs.m_userPosition = SBig(hs.m_userPosition);
hs.m_userSz = SBig(hs.m_userSz);
ws.write(&hs, sizeof(hs));
}
};
/* Currently only kept for dolphin compatibility */
struct BI2Header
{
int32_t m_debugMonitorSize;
int32_t m_simMemSize;
uint32_t m_argOffset;
uint32_t m_debugFlag;
uint32_t m_trkAddress;
uint32_t m_trkSz;
uint32_t m_countryCode;
uint32_t m_unk1;
uint32_t m_unk2;
uint32_t m_unk3;
uint32_t m_dolLimit;
uint32_t m_unk4;
uint8_t padding2[0x1FD0];
void read(IReadStream& rs)
{
memset(this, 0, sizeof(*this));
rs.read(this, sizeof(*this));
m_debugMonitorSize = SBig(m_debugMonitorSize);
m_simMemSize = SBig(m_simMemSize);
m_argOffset = SBig(m_argOffset);
m_debugFlag = SBig(m_debugFlag);
m_trkAddress = SBig(m_trkAddress);
m_trkSz = SBig(m_trkSz);
m_countryCode = SBig(m_countryCode);
m_unk1 = SBig(m_unk1);
m_unk2 = SBig(m_unk2);
m_unk3 = SBig(m_unk3);
m_dolLimit = SBig(m_dolLimit);
m_unk4 = SBig(m_unk4);
}
void write(IWriteStream& ws) const
{
BI2Header h = *this;
h.m_debugMonitorSize = SBig(h.m_debugMonitorSize);
h.m_simMemSize = SBig(h.m_simMemSize);
h.m_argOffset = SBig(h.m_argOffset);
h.m_debugFlag = SBig(h.m_debugFlag);
h.m_trkAddress = SBig(h.m_trkAddress);
h.m_trkSz = SBig(h.m_trkSz);
h.m_countryCode = SBig(h.m_countryCode);
h.m_unk1 = SBig(h.m_unk1);
h.m_unk2 = SBig(h.m_unk2);
h.m_unk3 = SBig(h.m_unk3);
h.m_dolLimit = SBig(h.m_dolLimit);
h.m_unk4 = SBig(h.m_unk4);
ws.write(&h, sizeof(h));
}
};
struct ExtractionContext;
class DiscBase
{
public:
virtual ~DiscBase() {}
virtual ~DiscBase() = default;
class IPartition
{
public:
virtual ~IPartition() {}
virtual ~IPartition() = default;
enum class Kind : uint32_t
{
Data,
@ -140,32 +206,6 @@ public:
uint32_t entryPoint;
};
/* Currently only kept for dolphin compatibility*/
struct BI2Header
{
uint32_t dolOff;
uint32_t fstOff;
uint32_t fstSz;
uint32_t fstMaxSz;
uint32_t fstMemoryAddress;
uint32_t userPosition;
uint32_t userSz;
uint8_t padding1[4];
int32_t debugMonitorSize;
int32_t simMemSize;
uint32_t argOffset;
uint32_t debugFlag;
uint32_t trkAddress;
uint32_t trkSz;
uint32_t countryCode;
uint32_t unk1;
uint32_t unk2;
uint32_t unk3;
uint32_t dolLimit;
uint32_t unk4;
uint8_t padding2[0x1fd0];
};
class Node
{
public:
@ -259,11 +299,11 @@ public:
bool extractToDirectory(const SystemString& basePath, const ExtractionContext& ctx) const;
};
protected:
Header m_header;
BI2Header m_bi2Header;
uint64_t m_dolOff;
uint64_t m_fstOff;
uint64_t m_fstSz;
uint64_t m_fstMemoryAddr;
uint64_t m_apploaderSz;
std::vector<Node> m_nodes;
void parseFST(IPartReadStream& s);
@ -325,8 +365,6 @@ public:
return buf;
}
inline uint64_t getFSTMemoryAddr() const {return m_fstMemoryAddr;}
inline uint64_t getApploaderSize() const {return m_apploaderSz;}
inline std::unique_ptr<uint8_t[]> getApploaderBuf() const
{
@ -336,8 +374,9 @@ public:
}
inline size_t getNodeCount() const { return m_nodes.size(); }
inline const Header& getHeader() const { return m_parent.getHeader(); }
inline const uint8_t* getBI2Buf() const { return reinterpret_cast<const uint8_t*>(&m_bi2Header); }
inline const Header& getHeader() const { return m_header; }
inline const BI2Header& getBI2() const { return m_bi2Header; }
virtual bool extractCryptoFiles(const SystemString& path, const ExtractionContext& ctx) const { return true; }
};
protected:
@ -364,6 +403,7 @@ public:
return part.get();
return nullptr;
}
inline IPartition* getUpdatePartition()
{
for (const std::unique_ptr<IPartition>& part : m_partitions)
@ -371,12 +411,14 @@ public:
return part.get();
return nullptr;
}
inline void extractToDirectory(const SystemString& path, const ExtractionContext& ctx)
{
for (std::unique_ptr<IPartition>& part : m_partitions)
part->extractToDirectory(path, ctx);
}
virtual bool extractDiscHeaderFiles(const SystemString& path, const ExtractionContext& ctx) const=0;
};
class DiscBuilderBase
@ -386,7 +428,7 @@ public:
class PartitionBuilderBase
{
public:
virtual ~PartitionBuilderBase() {}
virtual ~PartitionBuilderBase() = default;
enum class Kind : uint32_t
{
Data,
@ -401,10 +443,10 @@ public:
virtual uint64_t userAllocate(uint64_t reqSz, IPartWriteStream& ws)=0;
virtual uint32_t packOffset(uint64_t offset) const=0;
void recursiveBuildNodesPre(const SystemChar* dirIn, uint64_t dolInode);
bool recursiveBuildNodes(IPartWriteStream& ws, bool system, const SystemChar* dirIn, uint64_t dolInode);
void recursiveBuildNodesPre(const SystemChar* dirIn);
bool recursiveBuildNodes(IPartWriteStream& ws, bool system, const SystemChar* dirIn);
bool recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode,
bool recursiveBuildFST(const SystemChar* dirIn,
std::function<void(void)> incParents);
void recursiveMergeNodesPre(const DiscBase::IPartition::Node* nodeIn, const SystemChar* dirIn);
@ -428,32 +470,21 @@ public:
DiscBuilderBase& m_parent;
Kind m_kind;
char m_gameID[6];
std::string m_gameTitle;
uint64_t m_dolOffset = 0;
uint64_t m_dolSize = 0;
public:
PartitionBuilderBase(DiscBuilderBase& parent, Kind kind,
const char gameID[6], const char* gameTitle)
: m_parent(parent), m_kind(kind), m_gameTitle(gameTitle)
{
memcpy(m_gameID, gameID, 6);
}
PartitionBuilderBase(DiscBuilderBase& parent, Kind kind)
: m_parent(parent), m_kind(kind)
{}
virtual std::unique_ptr<IPartWriteStream> beginWriteStream(uint64_t offset)=0;
bool buildFromDirectory(IPartWriteStream& ws,
const SystemChar* dirIn, const SystemChar* dolIn,
const SystemChar* apploaderIn);
static uint64_t CalculateTotalSizeBuild(const SystemChar* dolIn,
const SystemChar* dirIn);
const SystemChar* dirIn);
static uint64_t CalculateTotalSizeBuild(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:
SystemString m_outPath;
@ -480,13 +511,14 @@ public:
}
virtual ~DiscBuilderBase() = default;
DiscBuilderBase(const SystemChar* outPath, int64_t discCapacity, FProgress progressCB)
DiscBuilderBase(const SystemChar* outPath,
int64_t discCapacity, FProgress 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;}
IFileIO& getFileIO() { return *m_fileIO; }
};
using Partition = DiscBase::IPartition;

View File

@ -13,17 +13,16 @@ class DiscGCN : public DiscBase
DiscBuilderGCN makeMergeBuilder(const SystemChar* outPath, FProgress progressCB);
public:
DiscGCN(std::unique_ptr<IDiscIO>&& dio, bool& err);
bool extractDiscHeaderFiles(const SystemString& path, const ExtractionContext& ctx) const;
};
class DiscBuilderGCN : public DiscBuilderBase
{
friend class DiscMergerGCN;
public:
DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], const char* gameTitle,
uint32_t fstMemoryAddr, FProgress progressCB);
EBuildResult buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn,
const SystemChar* apploaderIn);
static uint64_t CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn);
DiscBuilderGCN(const SystemChar* outPath, FProgress progressCB);
EBuildResult buildFromDirectory(const SystemChar* dirIn);
static uint64_t CalculateTotalSizeRequired(const SystemChar* dirIn);
};
class DiscMergerGCN

View File

@ -12,21 +12,16 @@ class DiscWii : public DiscBase
public:
DiscWii(std::unique_ptr<IDiscIO>&& dio, bool& err);
DiscBuilderWii makeMergeBuilder(const SystemChar* outPath, bool dualLayer, FProgress progressCB);
bool writeOutDataPartitionHeader(const SystemChar* pathOut) const;
bool extractDiscHeaderFiles(const SystemString& path, const ExtractionContext& ctx) const;
};
class DiscBuilderWii : public DiscBuilderBase
{
bool m_dualLayer;
public:
DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle,
bool dualLayer, FProgress progressCB);
EBuildResult 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);
DiscBuilderWii(const SystemChar* outPath, bool dualLayer, FProgress progressCB);
EBuildResult buildFromDirectory(const SystemChar* dirIn);
static uint64_t CalculateTotalSizeRequired(const SystemChar* dirIn, bool& dualLayer);
};
class DiscMergerWii

View File

@ -14,42 +14,38 @@
namespace nod
{
struct IReadStream
{
virtual ~IReadStream() = default;
virtual uint64_t read(void* buf, uint64_t length)=0;
virtual void seek(int64_t offset, int whence=SEEK_SET)=0;
virtual uint64_t position() const=0;
};
struct IWriteStream
{
virtual ~IWriteStream() = default;
virtual uint64_t write(const void* buf, uint64_t length)=0;
};
class IDiscIO
{
public:
virtual ~IDiscIO() {}
struct IReadStream
{
virtual ~IReadStream() {}
virtual uint64_t read(void* buf, uint64_t length)=0;
virtual void seek(int64_t offset, int whence=SEEK_SET)=0;
virtual uint64_t position() const=0;
};
virtual ~IDiscIO() = default;
virtual std::unique_ptr<IReadStream> beginReadStream(uint64_t offset=0) const=0;
struct IWriteStream
{
virtual ~IWriteStream() {}
virtual uint64_t write(const void* buf, uint64_t length)=0;
};
virtual std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset=0) const=0;
};
struct IPartReadStream
struct IPartReadStream : IReadStream
{
virtual ~IPartReadStream() {}
virtual void seek(int64_t offset, int whence=SEEK_SET)=0;
virtual uint64_t position() const=0;
virtual uint64_t read(void* buf, uint64_t length)=0;
virtual ~IPartReadStream() = default;
};
struct IPartWriteStream
struct IPartWriteStream : IWriteStream
{
virtual ~IPartWriteStream() {}
virtual ~IPartWriteStream() = default;
virtual void close()=0;
virtual uint64_t position() const=0;
virtual uint64_t write(const void* buf, uint64_t length)=0;
};
#if NOD_ATHENA

View File

@ -13,15 +13,12 @@ namespace nod
class IFileIO
{
public:
virtual ~IFileIO() {}
virtual ~IFileIO() = default;
virtual bool exists()=0;
virtual uint64_t size()=0;
struct IWriteStream
struct IWriteStream : nod::IWriteStream
{
virtual ~IWriteStream() {}
virtual uint64_t write(const void* buf, uint64_t length)=0;
uint64_t copyFromDisc(IPartReadStream& discio, uint64_t length)
{
uint64_t read = 0;
@ -74,12 +71,8 @@ public:
virtual std::unique_ptr<IWriteStream> beginWriteStream() const=0;
virtual std::unique_ptr<IWriteStream> beginWriteStream(uint64_t offset) const=0;
struct IReadStream
struct IReadStream : nod::IReadStream
{
virtual ~IReadStream() {}
virtual void seek(int64_t offset, int whence)=0;
virtual int64_t position()=0;
virtual uint64_t read(void* buf, uint64_t length)=0;
virtual uint64_t copyToDisc(struct IPartWriteStream& discio, uint64_t length)=0;
};
virtual std::unique_ptr<IReadStream> beginReadStream() const=0;

View File

@ -11,7 +11,7 @@ namespace nod
class IAES
{
public:
virtual ~IAES() {}
virtual ~IAES() = default;
virtual void encrypt(const uint8_t* iv, const uint8_t* inbuf, uint8_t* outbuf, size_t len)=0;
virtual void decrypt(const uint8_t* iv, const uint8_t* inbuf, uint8_t* outbuf, size_t len)=0;
virtual void setKey(const uint8_t* key)=0;

View File

@ -13,7 +13,6 @@ class DiscBase;
struct ExtractionContext final
{
bool verbose : 1;
bool force : 1;
std::function<void(const std::string&, float)> progressCB;
};

View File

@ -98,7 +98,7 @@ bool DiscBase::IPartition::Node::extractToDirectory(const SystemString& basePath
if (m_kind == Kind::Directory)
{
++m_parent.m_curNodeIdx;
if (ctx.verbose && ctx.progressCB && !getName().empty())
if (ctx.progressCB && !getName().empty())
ctx.progressCB(getName(), m_parent.m_curNodeIdx / float(m_parent.getNodeCount()));
if (Mkdir(path.c_str(), 0755) && errno != EEXIST)
{
@ -112,7 +112,7 @@ bool DiscBase::IPartition::Node::extractToDirectory(const SystemString& basePath
else if (m_kind == Kind::File)
{
Sstat theStat;
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB(getName(), m_parent.m_curNodeIdx / float(m_parent.getNodeCount()));
if (ctx.force || Stat(path.c_str(), &theStat))
@ -124,7 +124,7 @@ bool DiscBase::IPartition::Node::extractToDirectory(const SystemString& basePath
ws->copyFromDisc(*rs, m_discLength,
[&](float prog)
{
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB(getName(), (m_parent.m_curNodeIdx + prog) / float(m_parent.getNodeCount()));
});
}
@ -144,17 +144,31 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path,
return false;
}
if (Mkdir((path + _S("/sys")).c_str(), 0755) && errno != EEXIST)
if (Mkdir((path + _S("/DATA")).c_str(), 0755) && errno != EEXIST)
{
LogModule.report(logvisor::Error, _S("unable to mkdir '%s/sys'"), path.c_str());
LogModule.report(logvisor::Error, _S("unable to mkdir '%s/DATA'"), path.c_str());
return false;
}
if (Mkdir((path + _S("/DATA/sys")).c_str(), 0755) && errno != EEXIST)
{
LogModule.report(logvisor::Error, _S("unable to mkdir '%s/DATA/sys'"), path.c_str());
return false;
}
/* Extract Disc Files */
if (!m_parent.extractDiscHeaderFiles(path, ctx))
return false;
/* Extract Crypto Files */
if (!extractCryptoFiles(path, ctx))
return false;
/* Extract Apploader */
SystemString apploaderPath = path + _S("/sys/apploader.img");
SystemString apploaderPath = path + _S("/DATA/sys/apploader.img");
if (ctx.force || Stat(apploaderPath.c_str(), &theStat))
{
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB("apploader.bin", 0.f);
std::unique_ptr<uint8_t[]> buf = getApploaderBuf();
auto ws = NewFileIO(apploaderPath)->beginWriteStream();
@ -164,10 +178,10 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path,
}
/* Extract Dol */
SystemString dolPath = path + _S("/sys/main.dol");
SystemString dolPath = path + _S("/DATA/sys/main.dol");
if (ctx.force || Stat(dolPath.c_str(), &theStat))
{
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB("main.dol", 0.f);
std::unique_ptr<uint8_t[]> buf = getDOLBuf();
auto ws = NewFileIO(dolPath)->beginWriteStream();
@ -177,32 +191,32 @@ bool DiscBase::IPartition::extractToDirectory(const SystemString& path,
}
/* Extract Boot info */
SystemString bootPath = path + _S("/sys/boot.bin");
SystemString bootPath = path + _S("/DATA/sys/boot.bin");
if (ctx.force || Stat(bootPath.c_str(), &theStat))
{
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB("boot.bin", 0.f);
auto ws = NewFileIO(bootPath)->beginWriteStream();
if (!ws)
return false;
getHeader().write(*ws.get());
m_header.write(*ws.get());
}
/* Extract BI2 info */
SystemString bi2Path = path + _S("/sys/bi2.bin");
SystemString bi2Path = path + _S("/DATA/sys/bi2.bin");
if (ctx.force || Stat(bi2Path.c_str(), &theStat))
{
if (ctx.verbose && ctx.progressCB)
if (ctx.progressCB)
ctx.progressCB("bi2.bin", 0.f);
const uint8_t* buf = getBI2Buf();
auto ws = NewFileIO(bi2Path)->beginWriteStream();
if (!ws)
return false;
ws->write(buf, sizeof(BI2Header));
m_bi2Header.write(*ws);
}
/* Extract Filesystem */
SystemString fsPath = path + _S("/files");
SystemString fsPath = path + _S("/DATA/files");
if (Mkdir(fsPath.c_str(), 0755) && errno != EEXIST)
{
LogModule.report(logvisor::Error, _S("unable to mkdir '%s'"), fsPath.c_str());
@ -299,37 +313,28 @@ static size_t PatchDOL(IFileIO::IReadStream& in, IPartWriteStream& out, size_t s
return out.write(buf.get(), sz);
}
void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodesPre(const SystemChar* dirIn,
uint64_t dolInode)
void DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodesPre(const SystemChar* filesIn)
{
DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
DirectoryEnumerator dEnum(filesIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const DirectoryEnumerator::Entry& e : dEnum)
{
if (e.m_isDir)
{
recursiveBuildNodesPre(e.m_path.c_str(), dolInode);
}
recursiveBuildNodesPre(e.m_path.c_str());
else
{
if (dolInode == GetInode(e.m_path.c_str()))
continue;
++m_parent.m_progressTotal;
}
}
}
bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream& ws,
bool system,
const SystemChar* dirIn,
uint64_t dolInode)
const SystemChar* filesIn)
{
DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
DirectoryEnumerator dEnum(filesIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const DirectoryEnumerator::Entry& e : dEnum)
{
if (e.m_isDir)
{
if (!recursiveBuildNodes(ws, system, e.m_path.c_str(), dolInode))
if (!recursiveBuildNodes(ws, system, e.m_path.c_str()))
return false;
}
else
@ -339,9 +344,6 @@ bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream
if (system ^ isSys)
continue;
if (dolInode == GetInode(e.m_path.c_str()))
continue;
size_t fileSz = ROUND_UP_32(e.m_fileSz);
uint64_t fileOff = userAllocate(fileSz, ws);
if (fileOff == -1)
@ -380,10 +382,10 @@ bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildNodes(IPartWriteStream
return true;
}
bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* dirIn, uint64_t dolInode,
bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar* filesIn,
std::function<void(void)> incParents)
{
DirectoryEnumerator dEnum(dirIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
DirectoryEnumerator dEnum(filesIn, DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const DirectoryEnumerator::Entry& e : dEnum)
{
if (e.m_isDir)
@ -392,19 +394,11 @@ bool DiscBuilderBase::PartitionBuilderBase::recursiveBuildFST(const SystemChar*
m_buildNodes.emplace_back(true, m_buildNameOff, 0, dirNodeIdx+1);
addBuildName(e.m_name);
incParents();
if (!recursiveBuildFST(e.m_path.c_str(), dolInode, [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}))
if (!recursiveBuildFST(e.m_path.c_str(), [&](){m_buildNodes[dirNodeIdx].incrementLength(); incParents();}))
return false;
}
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;
}
std::pair<uint64_t,uint64_t> fileOffSz = m_fileOffsetsSizes.at(e.m_path);
m_buildNodes.emplace_back(false, m_buildNameOff, packOffset(fileOffSz.first), fileOffSz.second);
addBuildName(e.m_name);
@ -796,20 +790,21 @@ bool DiscBuilderBase::PartitionBuilderBase::RecursiveCalculateTotalSize(
}
bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream& ws,
const SystemChar* dirIn,
const SystemChar* dolIn,
const SystemChar* apploaderIn)
const SystemChar* dirIn)
{
if (!dirIn || !dolIn || !apploaderIn)
if (!dirIn)
{
LogModule.report(logvisor::Error, _S("all arguments must be supplied to buildFromDirectory()"));
return false;
}
SystemString dirStr(dirIn);
SystemString dolIn = dirStr + _S("/DATA/sys/main.dol");
SystemString filesIn = dirStr + _S("/DATA/files");
/* 1st pass - Tally up total progress steps */
uint64_t dolInode = GetInode(dolIn);
m_parent.m_progressTotal += 2; /* Prep and DOL */
recursiveBuildNodesPre(dirIn, dolInode);
recursiveBuildNodesPre(filesIn.c_str());
/* Clear file */
m_parent.m_progressCB(m_parent.getProgressFactor(), _S("Preparing output image"), -1);
@ -822,9 +817,9 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream&
/* Write Boot DOL first (first thing seeked to after Apploader) */
{
Sstat dolStat;
if (Stat(dolIn, &dolStat))
if (Stat(dolIn.c_str(), &dolStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), dolIn);
LogModule.report(logvisor::Error, _S("unable to stat %s"), dolIn.c_str());
return false;
}
size_t fileSz = ROUND_UP_32(dolStat.st_size);
@ -833,12 +828,12 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream&
return false;
m_dolOffset = fileOff;
m_dolSize = fileSz;
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(dolIn)->beginReadStream();
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(dolIn.c_str())->beginReadStream();
if (!rs)
return false;
bool patched;
size_t xferSz = PatchDOL(*rs, ws, dolStat.st_size, patched);
m_parent.m_progressCB(m_parent.getProgressFactor(), SystemString(dolIn) +
m_parent.m_progressCB(m_parent.getProgressFactor(), dolIn +
(patched ? _S(" [PATCHED]") : _S("")), xferSz);
++m_parent.m_progressIdx;
for (size_t i=0 ; i<fileSz-xferSz ; ++i)
@ -846,27 +841,30 @@ bool DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(IPartWriteStream&
}
/* Gather files in root directory */
if (!recursiveBuildNodes(ws, true, dirIn, dolInode))
if (!recursiveBuildNodes(ws, true, filesIn.c_str()))
return false;
if (!recursiveBuildNodes(ws, false, dirIn, dolInode))
if (!recursiveBuildNodes(ws, false, filesIn.c_str()))
return false;
if (!recursiveBuildFST(dirIn, dolInode, [&](){m_buildNodes[0].incrementLength();}))
if (!recursiveBuildFST(filesIn.c_str(), [&](){m_buildNodes[0].incrementLength();}))
return false;
return true;
}
uint64_t DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(const SystemChar* dolIn,
const SystemChar* dirIn)
uint64_t DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(const SystemChar* dirIn)
{
SystemString dirStr(dirIn);
SystemString dolIn = dirStr + _S("/DATA/sys/main.dol");
SystemString filesIn = dirStr + _S("/DATA/files");
Sstat dolStat;
if (Stat(dolIn, &dolStat))
if (Stat(dolIn.c_str(), &dolStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), dolIn);
LogModule.report(logvisor::Error, _S("unable to stat %s"), dolIn.c_str());
return -1;
}
uint64_t totalSz = ROUND_UP_32(dolStat.st_size);
if (!RecursiveCalculateTotalSize(totalSz, nullptr, dirIn))
if (!RecursiveCalculateTotalSize(totalSz, nullptr, filesIn.c_str()))
return -1;
return totalSz;
}
@ -881,9 +879,12 @@ bool DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(IPartWriteStream&
return false;
}
SystemString dirStr(dirIn);
SystemString filesIn = dirStr + _S("/DATA/files");
/* 1st pass - Tally up total progress steps */
m_parent.m_progressTotal += 2; /* Prep and DOL */
recursiveMergeNodesPre(&partIn->getFSTRoot(), dirIn);
recursiveMergeNodesPre(&partIn->getFSTRoot(), filesIn.c_str());
/* Clear file */
m_parent.m_progressCB(m_parent.getProgressFactor(), _S("Preparing output image"), -1);
@ -915,11 +916,11 @@ bool DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(IPartWriteStream&
/* Gather files in root directory */
SystemString keyPath;
if (!recursiveMergeNodes(ws, true, &partIn->getFSTRoot(), dirIn, keyPath))
if (!recursiveMergeNodes(ws, true, &partIn->getFSTRoot(), filesIn.c_str(), keyPath))
return false;
if (!recursiveMergeNodes(ws, false, &partIn->getFSTRoot(), dirIn, keyPath))
if (!recursiveMergeNodes(ws, false, &partIn->getFSTRoot(), filesIn.c_str(), keyPath))
return false;
if (!recursiveMergeFST(&partIn->getFSTRoot(), dirIn, [&](){m_buildNodes[0].incrementLength();}, keyPath))
if (!recursiveMergeFST(&partIn->getFSTRoot(), filesIn.c_str(), [&](){m_buildNodes[0].incrementLength();}, keyPath))
return false;
return true;
@ -928,8 +929,11 @@ bool DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(IPartWriteStream&
uint64_t DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeMerge(const DiscBase::IPartition* partIn,
const SystemChar* dirIn)
{
SystemString dirStr(dirIn);
SystemString filesIn = dirStr + _S("/DATA/files");
uint64_t totalSz = ROUND_UP_32(partIn->getDOLSize());
if (!RecursiveCalculateTotalSize(totalSz, &partIn->getFSTRoot(), dirIn))
if (!RecursiveCalculateTotalSize(totalSz, &partIn->getFSTRoot(), filesIn.c_str()))
return -1;
return totalSz;
}

View File

@ -1,4 +1,5 @@
#include "nod/DiscGCN.hpp"
#include "nod/nod.hpp"
#include <inttypes.h>
#define BUFFER_SZ 0x8000
@ -12,18 +13,17 @@ public:
: IPartition(parent, kind, offset)
{
/* GCN-specific header reads */
std::unique_ptr<IPartReadStream> s = beginReadStream(0x420);
std::unique_ptr<IPartReadStream> s = beginReadStream(0x0);
if (!s)
{
err = true;
return;
}
s->read(&m_bi2Header, sizeof(BI2Header));
m_dolOff = SBig(m_bi2Header.dolOff);
m_fstOff = SBig(m_bi2Header.fstOff);
m_fstSz = SBig(m_bi2Header.fstSz);
m_fstMemoryAddr = SBig(m_bi2Header.fstMemoryAddress);
m_header.read(*s);
m_bi2Header.read(*s);
m_dolOff = m_header.m_dolOff;
m_fstOff = m_header.m_fstOff;
m_fstSz = m_header.m_fstSz;
uint32_t vals[2];
s->seek(0x2440 + 0x14);
s->read(vals, 8);
@ -40,7 +40,7 @@ public:
class PartReadStream : public IPartReadStream
{
const PartitionGCN& m_parent;
std::unique_ptr<IDiscIO::IReadStream> m_dio;
std::unique_ptr<IReadStream> m_dio;
uint64_t m_offset;
size_t m_curBlock = SIZE_MAX;
@ -97,7 +97,7 @@ public:
if (cacheSize + cacheOffset > BUFFER_SZ)
cacheSize = BUFFER_SZ - cacheOffset;
memcpy(dst, m_buf + cacheOffset, cacheSize);
memmove(dst, m_buf + cacheOffset, cacheSize);
dst += cacheSize;
rem -= cacheSize;
cacheOffset = 0;
@ -131,15 +131,42 @@ DiscGCN::DiscGCN(std::unique_ptr<IDiscIO>&& dio, bool& 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);
return DiscBuilderGCN(outPath, progressCB);
}
bool DiscGCN::extractDiscHeaderFiles(const SystemString& path, const ExtractionContext& ctx) const
{
if (Mkdir((path + _S("/DATA/disc")).c_str(), 0755) && errno != EEXIST)
{
LogModule.report(logvisor::Error, _S("unable to mkdir '%s/DATA/disc'"), path.c_str());
return false;
}
Sstat theStat;
/* Extract Header */
SystemString headerPath = path + _S("/DATA/disc/header.bin");
if (ctx.force || Stat(headerPath.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("header.bin", 0.f);
std::unique_ptr<IReadStream> rs = getDiscIO().beginReadStream(0x0);
if (!rs)
return false;
Header header;
header.read(*rs);
auto ws = NewFileIO(headerPath)->beginWriteStream();
if (!ws)
return false;
header.write(*ws);
}
return true;
}
class PartitionBuilderGCN : public DiscBuilderBase::PartitionBuilderBase
{
uint64_t m_curUser = 0x57058000;
uint32_t m_fstMemoryAddr;
public:
class PartWriteStream : public IPartWriteStream
@ -171,9 +198,8 @@ public:
}
};
PartitionBuilderGCN(DiscBuilderBase& parent, Kind kind,
const char gameID[6], const char* gameTitle, uint32_t fstMemoryAddr)
: DiscBuilderBase::PartitionBuilderBase(parent, kind, gameID, gameTitle), m_fstMemoryAddr(fstMemoryAddr) {}
PartitionBuilderGCN(DiscBuilderBase& parent)
: DiscBuilderBase::PartitionBuilderBase(parent, Kind::Data) {}
uint64_t userAllocate(uint64_t reqSz, IPartWriteStream& ws)
{
@ -202,17 +228,16 @@ public:
return ret;
}
bool _build(const std::function<bool(IPartWriteStream&, size_t&)>& func)
bool _build(const std::function<bool(IPartWriteStream&, uint32_t, uint32_t,
uint32_t, uint32_t, uint32_t)>& headerFunc,
const std::function<bool(IPartWriteStream&)>& bi2Func,
const std::function<bool(IPartWriteStream&, size_t&)>& apploaderFunc)
{
std::unique_ptr<IPartWriteStream> ws = beginWriteStream(0);
std::unique_ptr<IPartWriteStream> ws = beginWriteStream(0x2440);
if (!ws)
return false;
Header header(m_gameID, m_gameTitle.c_str(), false);
header.write(*ws);
ws = beginWriteStream(0x2440);
size_t xferSz = 0;
if (!func(*ws, xferSz))
if (!apploaderFunc(*ws, xferSz))
return false;
size_t fstOff = ROUND_UP_32(xferSz);
@ -233,38 +258,89 @@ public:
return false;
}
ws = beginWriteStream(0x420);
ws = beginWriteStream(0);
if (!ws)
return false;
uint32_t vals[7];
vals[0] = SBig(uint32_t(m_dolOffset));
vals[1] = SBig(uint32_t(fstOff));
vals[2] = SBig(uint32_t(fstSz));
vals[3] = SBig(uint32_t(fstSz));
vals[4] = SBig(uint32_t(m_fstMemoryAddr));
vals[5] = SBig(uint32_t(m_curUser));
vals[6] = SBig(uint32_t(0x57058000 - m_curUser));
ws->write(vals, sizeof(vals));
if (!headerFunc(*ws, m_dolOffset, fstOff, fstSz, m_curUser, 0x57058000 - m_curUser))
return false;
if (!bi2Func(*ws))
return false;
return true;
}
bool buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn, const SystemChar* apploaderIn)
bool buildFromDirectory(const SystemChar* dirIn)
{
std::unique_ptr<IPartWriteStream> ws = beginWriteStream(0);
if (!ws)
return false;
bool result = DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(*ws, dirIn, dolIn, apploaderIn);
bool result = DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(*ws, dirIn);
if (!result)
return false;
return _build([this, apploaderIn](IPartWriteStream& ws, size_t& xferSz) -> bool
SystemString dirStr(dirIn);
/* Check Apploader */
SystemString apploaderIn = dirStr + _S("/DATA/sys/apploader.img");
Sstat apploaderStat;
if (Stat(apploaderIn.c_str(), &apploaderStat))
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(apploaderIn)->beginReadStream();
LogModule.report(logvisor::Error, _S("unable to stat %s"), apploaderIn.c_str());
return -1;
}
/* Check Boot */
SystemString bootIn = dirStr + _S("/DATA/sys/boot.bin");
Sstat bootStat;
if (Stat(bootIn.c_str(), &bootStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), bootIn.c_str());
return -1;
}
/* Check BI2 */
SystemString bi2In = dirStr + _S("/DATA/sys/bi2.bin");
Sstat bi2Stat;
if (Stat(bi2In.c_str(), &bi2Stat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), bi2In.c_str());
return -1;
}
return _build(
[this, &bootIn](IPartWriteStream& ws, uint32_t dolOff, uint32_t fstOff, uint32_t fstSz,
uint32_t userOff, uint32_t userSz) -> bool
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(bootIn.c_str())->beginReadStream();
if (!rs)
return false;
Header header;
header.read(*rs);
header.m_dolOff = dolOff;
header.m_fstOff = fstOff;
header.m_fstSz = fstSz;
header.m_fstMaxSz = fstSz;
header.m_userPosition = userOff;
header.m_userSz = userSz;
header.write(ws);
return true;
},
[this, &bi2In](IPartWriteStream& ws) -> bool
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(bi2In.c_str())->beginReadStream();
if (!rs)
return false;
BI2Header bi2;
bi2.read(*rs);
bi2.write(ws);
return true;
},
[this, &apploaderIn](IPartWriteStream& ws, size_t& xferSz) -> bool
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(apploaderIn.c_str())->beginReadStream();
if (!rs)
return false;
char buf[8192];
SystemString apploaderName(apploaderIn);
while (true)
{
size_t rdSz = rs->read(buf, 8192);
@ -278,7 +354,7 @@ public:
"apploader flows into user area (one or the other is too big)");
return false;
}
m_parent.m_progressCB(m_parent.getProgressFactor(), apploaderName, xferSz);
m_parent.m_progressCB(m_parent.getProgressFactor(), apploaderIn, xferSz);
}
++m_parent.m_progressIdx;
return true;
@ -294,7 +370,26 @@ public:
if (!result)
return false;
return _build([this, partIn](IPartWriteStream& ws, size_t& xferSz) -> bool
return _build(
[this, partIn](IPartWriteStream& ws, uint32_t dolOff, uint32_t fstOff, uint32_t fstSz,
uint32_t userOff, uint32_t userSz) -> bool
{
Header header = partIn->getHeader();
header.m_dolOff = dolOff;
header.m_fstOff = fstOff;
header.m_fstSz = fstSz;
header.m_fstMaxSz = fstSz;
header.m_userPosition = userOff;
header.m_userSz = userSz;
header.write(ws);
return true;
},
[this, partIn](IPartWriteStream& ws) -> bool
{
partIn->getBI2().write(ws);
return true;
},
[this, partIn](IPartWriteStream& ws, size_t& xferSz) -> bool
{
std::unique_ptr<uint8_t[]> apploaderBuf = partIn->getApploaderBuf();
size_t apploaderSz = partIn->getApploaderSize();
@ -314,8 +409,7 @@ public:
}
};
EBuildResult DiscBuilderGCN::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn,
const SystemChar* apploaderIn)
EBuildResult DiscBuilderGCN::buildFromDirectory(const SystemChar* dirIn)
{
if (!m_fileIO->beginWriteStream())
return EBuildResult::Failed;
@ -332,12 +426,12 @@ EBuildResult DiscBuilderGCN::buildFromDirectory(const SystemChar* dirIn, const S
ws->write("", 1);
PartitionBuilderGCN& pb = static_cast<PartitionBuilderGCN&>(*m_partitions[0]);
return pb.buildFromDirectory(dirIn, dolIn, apploaderIn) ? EBuildResult::Success : EBuildResult::Failed;
return pb.buildFromDirectory(dirIn) ? EBuildResult::Success : EBuildResult::Failed;
}
uint64_t DiscBuilderGCN::CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn)
uint64_t DiscBuilderGCN::CalculateTotalSizeRequired(const SystemChar* dirIn)
{
uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dolIn, dirIn);
uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dirIn);
if (sz == -1)
return -1;
sz += 0x30000;
@ -349,12 +443,10 @@ uint64_t DiscBuilderGCN::CalculateTotalSizeRequired(const SystemChar* dirIn, con
return sz;
}
DiscBuilderGCN::DiscBuilderGCN(const SystemChar* outPath, const char gameID[6], const char* gameTitle,
uint32_t fstMemoryAddr, FProgress progressCB)
DiscBuilderGCN::DiscBuilderGCN(const SystemChar* outPath, FProgress progressCB)
: DiscBuilderBase(outPath, 0x57058000, progressCB)
{
PartitionBuilderGCN* partBuilder = new PartitionBuilderGCN(*this, PartitionBuilderBase::Kind::Data,
gameID, gameTitle, fstMemoryAddr);
PartitionBuilderGCN* partBuilder = new PartitionBuilderGCN(*this);
m_partitions.emplace_back(partBuilder);
}

View File

@ -5,6 +5,7 @@
#include "nod/DiscWii.hpp"
#include "nod/aes.hpp"
#include "nod/sha1.h"
#include "nod/nod.hpp"
namespace nod
{
@ -66,7 +67,7 @@ class PartitionWii : public DiscBase::IPartition
uint32_t timeLimit;
} timeLimits[8];
void read(IDiscIO::IReadStream& s)
void read(IReadStream& s)
{
s.read(this, 676);
sigType = SBig(sigType);
@ -79,6 +80,21 @@ class PartitionWii : public DiscBase::IPartition
timeLimits[t].timeLimit = SBig(timeLimits[t].timeLimit);
}
}
void write(IWriteStream& s) const
{
Ticket tik = *this;
tik.sigType = SBig(tik.sigType);
tik.ticketVersion = SBig(tik.ticketVersion);
tik.permittedTitlesMask = SBig(tik.permittedTitlesMask);
tik.permitMask = SBig(tik.permitMask);
for (size_t t=0 ; t<8 ; ++t)
{
tik.timeLimits[t].enableTimeLimit = SBig(tik.timeLimits[t].enableTimeLimit);
tik.timeLimits[t].timeLimit = SBig(tik.timeLimits[t].timeLimit);
}
s.write(&tik, 676);
}
} m_ticket;
struct TMD
@ -112,7 +128,7 @@ class PartitionWii : public DiscBase::IPartition
uint64_t size;
char hash[20];
void read(IDiscIO::IReadStream& s)
void read(IReadStream& s)
{
s.read(this, 36);
id = SBig(id);
@ -120,10 +136,20 @@ class PartitionWii : public DiscBase::IPartition
type = SBig(type);
size = SBig(size);
}
void write(IWriteStream& s) const
{
Content c = *this;
c.id = SBig(c.id);
c.index = SBig(c.index);
c.type = SBig(c.type);
c.size = SBig(c.size);
s.write(&c, 36);
}
};
std::vector<Content> contents;
void read(IDiscIO::IReadStream& s)
void read(IReadStream& s)
{
s.read(this, 484);
sigType = SigType(SBig(uint32_t(sigType)));
@ -144,6 +170,24 @@ class PartitionWii : public DiscBase::IPartition
contents.back().read(s);
}
}
void write(IWriteStream& s) const
{
TMD tmd = *this;
tmd.sigType = SigType(SBig(uint32_t(tmd.sigType)));
tmd.iosIdMajor = SBig(tmd.iosIdMajor);
tmd.iosIdMinor = SBig(tmd.iosIdMinor);
tmd.titleIdMajor = SBig(tmd.titleIdMajor);
tmd.titleType = SBig(tmd.titleType);
tmd.groupId = SBig(tmd.groupId);
tmd.accessFlags = SBig(tmd.accessFlags);
tmd.titleVersion = SBig(tmd.titleVersion);
tmd.numContents = SBig(tmd.numContents);
tmd.bootIdx = SBig(tmd.bootIdx);
s.write(&tmd, 484);
for (uint16_t c=0 ; c<numContents ; ++c)
tmd.contents.back().write(s);
}
} m_tmd;
struct Certificate
@ -157,7 +201,7 @@ class PartitionWii : public DiscBase::IPartition
uint32_t modulus;
uint32_t pubExp;
void read(IDiscIO::IReadStream& s)
void read(IReadStream& s)
{
s.read(&sigType, 4);
sigType = SigType(SBig(uint32_t(sigType)));
@ -184,11 +228,46 @@ class PartitionWii : public DiscBase::IPartition
s.seek(52, SEEK_CUR);
}
void write(IWriteStream& s) const
{
Certificate c = *this;
c.sigType = SigType(SBig(uint32_t(c.sigType)));
s.write(&c.sigType, 4);
if (sigType == SigType::RSA_4096)
s.write(sig, 512);
else if (sigType == SigType::RSA_2048)
s.write(sig, 256);
else if (sigType == SigType::ELIPTICAL_CURVE)
s.write(sig, 64);
uint32_t zero = 0;
for (int i=0 ; i<15 ; ++i)
s.write(&zero, 4);
s.write(issuer, 64);
c.keyType = KeyType(SBig(uint32_t(c.keyType)));
s.write(&c.keyType, 4);
s.write(subject, 64);
if (keyType == KeyType::RSA_4096)
s.write(key, 512);
else if (keyType == KeyType::RSA_2048)
s.write(key, 256);
c.modulus = SBig(c.modulus);
c.pubExp = SBig(c.pubExp);
s.write(&c.modulus, 8);
for (int i=0 ; i<13 ; ++i)
s.write(&zero, 4);
}
};
Certificate m_caCert;
Certificate m_tmdCert;
Certificate m_ticketCert;
std::unique_ptr<uint8_t[]> m_h3Data;
uint64_t m_dataOff;
uint8_t m_decKey[16];
@ -196,7 +275,7 @@ public:
PartitionWii(const DiscWii& parent, Kind kind, uint64_t offset, bool& err)
: IPartition(parent, kind, offset)
{
std::unique_ptr<IDiscIO::IReadStream> s = parent.getDiscIO().beginReadStream(offset);
std::unique_ptr<IReadStream> s = parent.getDiscIO().beginReadStream(offset);
if (!s)
{
err = true;
@ -239,25 +318,29 @@ public:
m_tmdCert.read(*s);
m_ticketCert.read(*s);
s->seek(globalHashTableOff);
m_h3Data.reset(new uint8_t[0x18000]);
s->read(m_h3Data.get(), 0x18000);
/* Decrypt title key */
std::unique_ptr<IAES> aes = NewAES();
uint8_t iv[16] = {};
memcpy(iv, m_ticket.titleId, 8);
memmove(iv, m_ticket.titleId, 8);
aes->setKey(COMMON_KEYS[(int)m_ticket.commonKeyIdx]);
aes->decrypt(iv, m_ticket.encKey, m_decKey, 16);
/* Wii-specific header reads (now using title key to decrypt) */
std::unique_ptr<IPartReadStream> ds = beginReadStream(0x420);
std::unique_ptr<IPartReadStream> ds = beginReadStream(0x0);
if (!ds)
{
err = true;
return;
}
s->read(&m_bi2Header, sizeof(BI2Header));
m_dolOff = SBig(m_bi2Header.dolOff) << 2;
m_fstOff = SBig(m_bi2Header.fstOff) << 2;
m_fstSz = SBig(m_bi2Header.fstSz) << 2;
m_header.read(*ds);
m_bi2Header.read(*ds);
m_dolOff = m_header.m_dolOff << 2;
m_fstOff = m_header.m_fstOff << 2;
m_fstSz = m_header.m_fstSz << 2;
ds->seek(0x2440 + 0x14);
uint32_t vals[2];
ds->read(vals, 8);
@ -277,7 +360,7 @@ public:
const PartitionWii& m_parent;
uint64_t m_baseOffset;
uint64_t m_offset;
std::unique_ptr<IDiscIO::IReadStream> m_dio;
std::unique_ptr<IReadStream> m_dio;
size_t m_curBlock = SIZE_MAX;
uint8_t m_encBuf[0x8000];
@ -340,7 +423,7 @@ public:
if (cacheSize + cacheOffset > 0x7c00)
cacheSize = 0x7c00 - cacheOffset;
memcpy(dst, m_decBuf + cacheOffset, cacheSize);
memmove(dst, m_decBuf + cacheOffset, cacheSize);
dst += cacheSize;
rem -= cacheSize;
cacheOffset = 0;
@ -366,7 +449,7 @@ public:
std::unique_ptr<uint8_t[]> readPartitionHeaderBuf(size_t& szOut) const
{
{
std::unique_ptr<IDiscIO::IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4);
std::unique_ptr<IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4);
if (!rs)
return {};
@ -380,7 +463,7 @@ public:
szOut = uint64_t(h3) << 2;
}
std::unique_ptr<IDiscIO::IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset);
std::unique_ptr<IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset);
if (!rs)
return {};
@ -390,39 +473,58 @@ public:
return buf;
}
bool writeOutPartitionHeader(const SystemChar* pathOut) const
bool extractCryptoFiles(const SystemString& path, const ExtractionContext& ctx) const
{
std::unique_ptr<IFileIO::IWriteStream> ws = NewFileIO(pathOut)->beginWriteStream();
if (!ws)
return false;
uint64_t h3Off;
{
std::unique_ptr<IDiscIO::IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset + 0x2B4);
if (!rs)
return false;
Sstat theStat;
uint32_t h3;
if (rs->read(&h3, 4) != 4)
{
LogModule.report(logvisor::Error, _S("unable to read H3 offset to %s"), pathOut);
/* Extract Ticket */
SystemString ticketPath = path + _S("/ticket.bin");
if (ctx.force || Stat(ticketPath.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("ticket.bin", 0.f);
auto ws = NewFileIO(ticketPath)->beginWriteStream();
if (!ws)
return false;
}
h3 = SBig(h3);
h3Off = uint64_t(h3) << 2;
m_ticket.write(*ws);
}
char buf[8192];
size_t rem = h3Off;
std::unique_ptr<IDiscIO::IReadStream> rs = m_parent.getDiscIO().beginReadStream(m_offset);
if (!rs)
return false;
while (rem)
/* Extract TMD */
SystemString tmdPath = path + _S("/tmd.bin");
if (ctx.force || Stat(tmdPath.c_str(), &theStat))
{
size_t rdSz = nod::min(rem, size_t(8192ul));
rs->read(buf, rdSz);
ws->write(buf, rdSz);
rem -= rdSz;
if (ctx.progressCB)
ctx.progressCB("tmd.bin", 0.f);
auto ws = NewFileIO(tmdPath)->beginWriteStream();
if (!ws)
return false;
m_tmd.write(*ws);
}
/* Extract Certs */
SystemString certPath = path + _S("/cert.bin");
if (ctx.force || Stat(certPath.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("cert.bin", 0.f);
auto ws = NewFileIO(certPath)->beginWriteStream();
if (!ws)
return false;
m_caCert.write(*ws);
m_tmdCert.write(*ws);
m_ticketCert.write(*ws);
}
/* Extract H3 */
SystemString h3Path = path + _S("/h3.bin");
if (ctx.force || Stat(h3Path.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("h3.bin", 0.f);
auto ws = NewFileIO(h3Path)->beginWriteStream();
if (!ws)
return false;
ws->write(m_h3Data.get(), 0x18000);
}
return true;
@ -447,7 +549,7 @@ DiscWii::DiscWii(std::unique_ptr<IDiscIO>&& dio, bool& err)
} parts[4];
PartInfo(IDiscIO& dio, bool& err)
{
std::unique_ptr<IDiscIO::IReadStream> s = dio.beginReadStream(0x40000);
std::unique_ptr<IReadStream> s = dio.beginReadStream(0x40000);
if (!s)
{
err = true;
@ -496,20 +598,54 @@ DiscWii::DiscWii(std::unique_ptr<IDiscIO>&& dio, bool& err)
DiscBuilderWii DiscWii::makeMergeBuilder(const SystemChar* outPath, bool dualLayer, FProgress progressCB)
{
return DiscBuilderWii(outPath, m_header.m_gameID, m_header.m_gameTitle,
dualLayer, progressCB);
return DiscBuilderWii(outPath, dualLayer, progressCB);
}
bool DiscWii::writeOutDataPartitionHeader(const SystemChar* pathOut) const
bool DiscWii::extractDiscHeaderFiles(const SystemString& path, const ExtractionContext& ctx) const
{
for (const std::unique_ptr<IPartition>& part : m_partitions)
if (Mkdir((path + _S("/DATA/disc")).c_str(), 0755) && errno != EEXIST)
{
if (part->getKind() == IPartition::Kind::Data)
{
return static_cast<PartitionWii&>(*part).writeOutPartitionHeader(pathOut);
}
LogModule.report(logvisor::Error, _S("unable to mkdir '%s/DATA/disc'"), path.c_str());
return false;
}
return false;
Sstat theStat;
/* Extract Header */
SystemString headerPath = path + _S("/DATA/disc/header.bin");
if (ctx.force || Stat(headerPath.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("header.bin", 0.f);
std::unique_ptr<IReadStream> rs = getDiscIO().beginReadStream(0x0);
if (!rs)
return false;
Header header;
header.read(*rs);
auto ws = NewFileIO(headerPath)->beginWriteStream();
if (!ws)
return false;
header.write(*ws);
}
/* Extract Region info */
SystemString regionPath = path + _S("/DATA/disc/region.bin");
if (ctx.force || Stat(regionPath.c_str(), &theStat))
{
if (ctx.progressCB)
ctx.progressCB("header.bin", 0.f);
std::unique_ptr<IReadStream> rs = getDiscIO().beginReadStream(0x4E000);
if (!rs)
return false;
std::unique_ptr<uint8_t[]> buf(new uint8_t[0x20]);
rs->read(buf.get(), 0x20);
auto ws = NewFileIO(regionPath)->beginWriteStream();
if (!ws)
return false;
ws->write(buf.get(), 0x20);
}
return true;
}
static const uint8_t ZEROIV[16] = {0};
@ -557,32 +693,32 @@ public:
{
sha1_init(&sha);
sha1_write(&sha, ptr0 + (j+1)*0x400, 0x400);
memcpy(h0[j], sha1_result(&sha), 20);
memmove(h0[j], sha1_result(&sha), 20);
}
sha1_init(&sha);
sha1_write(&sha, (char*)h0, 0x26C);
memcpy(h1[c], sha1_result(&sha), 20);
memmove(h1[c], sha1_result(&sha), 20);
memcpy(ptr0, h0, 0x26C);
memmove(ptr0, h0, 0x26C);
memset(ptr0+0x26C, 0, 0x014);
}
sha1_init(&sha);
sha1_write(&sha, (char*)h1, 0x0A0);
memcpy(h2[s], sha1_result(&sha), 20);
memmove(h2[s], sha1_result(&sha), 20);
for (int c=0 ; c<8 ; ++c)
{
char* ptr0 = ptr1 + c*0x8000;
memcpy(ptr0+0x280, h1, 0x0A0);
memmove(ptr0+0x280, h1, 0x0A0);
memset(ptr0+0x320, 0, 0x020);
}
}
sha1_init(&sha);
sha1_write(&sha, (char*)h2, 0x0A0);
memcpy(h3Out, sha1_result(&sha), 20);
memmove(h3Out, sha1_result(&sha), 20);
for (int s=0 ; s<8 ; ++s)
{
@ -590,7 +726,7 @@ public:
for (int c=0 ; c<8 ; ++c)
{
char* ptr0 = ptr1 + c*0x8000;
memcpy(ptr0+0x340, h2, 0x0A0);
memmove(ptr0+0x340, h2, 0x0A0);
memset(ptr0+0x3E0, 0, 0x020);
m_parent.m_aes->encrypt(ZEROIV, (uint8_t*)ptr0, (uint8_t*)ptr0, 0x400);
m_parent.m_aes->encrypt((uint8_t*)(ptr0+0x3D0), (uint8_t*)(ptr0+0x400), (uint8_t*)(ptr0+0x400), 0x7c00);
@ -659,7 +795,7 @@ public:
if (src)
{
memcpy(m_buf + block * 0x8000 + 0x400 + cacheOffset, src, cacheSize);
memmove(m_buf + block * 0x8000 + 0x400 + cacheOffset, src, cacheSize);
src += cacheSize;
}
else
@ -680,9 +816,8 @@ public:
}
};
PartitionBuilderWii(DiscBuilderBase& parent, Kind kind,
const char gameID[6], const char* gameTitle, uint64_t baseOffset)
: DiscBuilderBase::PartitionBuilderBase(parent, kind, gameID, gameTitle),
PartitionBuilderWii(DiscBuilderBase& parent, Kind kind, uint64_t baseOffset)
: DiscBuilderBase::PartitionBuilderBase(parent, kind),
m_baseOffset(baseOffset), m_aes(NewAES()) {}
uint64_t getCurUserEnd() const {return m_curUser;}
@ -723,101 +858,28 @@ public:
return ret;
}
uint64_t _build(const std::function<bool(IPartWriteStream&)>& contentFunc,
uint64_t _build(const std::function<bool(IFileIO::IWriteStream&, uint32_t& h3Off, uint32_t& dataOff,
uint8_t& ccIdx, uint8_t tkey[16], uint8_t tkeyiv[16],
std::unique_ptr<uint8_t[]>& tmdData, size_t& tmdSz)>& cryptoFunc,
const std::function<bool(IPartWriteStream&, uint32_t, uint32_t, uint32_t)>& headerFunc,
const std::function<bool(IPartWriteStream&)>& bi2Func,
const std::function<bool(IPartWriteStream&, size_t&)>& apploaderFunc,
const uint8_t* phBuf, size_t phSz, size_t apploaderSz)
const std::function<bool(IPartWriteStream&)>& contentFunc,
size_t apploaderSz)
{
/* Read head and validate key members */
uint8_t tkey[16];
{
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 (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 (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::Error, _S("common key index may only be 0 or 1"));
return -1;
}
}
uint32_t tmdSz;
{
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 (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 (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<uint8_t[]> tmdData(new uint8_t[tmdSz]);
{
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 */
/* Write partition head up to H3 table */
std::unique_ptr<IFileIO::IWriteStream> ws = m_parent.getFileIO().beginWriteStream(m_baseOffset);
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 ; i<remCopy ; ++i)
ws->write("", 1);
uint32_t h3Off, dataOff;
uint8_t tkey[16], tkeyiv[16];
uint8_t ccIdx;
std::unique_ptr<uint8_t[]> tmdData;
size_t tmdSz;
if (!cryptoFunc(*ws, h3Off, dataOff, ccIdx, tkey, tkeyiv, tmdData, tmdSz))
return -1;
m_userOffset = dataOff;
/* Prepare crypto pass */
m_aes->setKey(COMMON_KEYS[ccIdx]);
@ -847,8 +909,6 @@ public:
cws = beginWriteStream(0);
if (!cws)
return -1;
Header header(m_gameID, m_gameTitle.c_str(), true, 0, 0, 0);
header.write(*cws);
/* Compute boot table members and write */
size_t fstOff = 0x2440 + ROUND_UP_32(apploaderSz);
@ -863,13 +923,11 @@ public:
return -1;
}
cws->write(nullptr, 0x420 - sizeof(Header));
uint32_t vals[4];
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));
cws->write(vals, 16);
if (!headerFunc(*cws, m_dolOffset, fstOff, fstSz))
return -1;
if (!bi2Func(*cws))
return -1;
size_t xferSz = 0;
if (!apploaderFunc(*cws, xferSz))
@ -958,42 +1016,151 @@ public:
return m_baseOffset + dataOff + groupCount * 0x200000;
}
uint64_t buildFromDirectory(const SystemChar* dirIn,
const SystemChar* dolIn,
const SystemChar* apploaderIn,
const SystemChar* partHeadIn)
uint64_t buildFromDirectory(const SystemChar* dirIn)
{
std::unique_ptr<IFileIO> ph = NewFileIO(partHeadIn);
size_t phSz = ph->size();
std::unique_ptr<uint8_t[]> phBuf(new uint8_t[phSz]);
SystemString dirStr(dirIn);
/* Check Ticket */
SystemString ticketIn = dirStr + _S("/ticket.bin");
Sstat theStat;
if (Stat(ticketIn.c_str(), &theStat))
{
auto rs = ph->beginReadStream();
if (!rs)
return -1;
rs->read(phBuf.get(), phSz);
LogModule.report(logvisor::Error, _S("unable to stat %s"), ticketIn.c_str());
return -1;
}
/* Get Apploader Size */
Sstat theStat;
if (Stat(apploaderIn, &theStat))
/* Check TMD */
SystemString tmdIn = dirStr + _S("/tmd.bin");
Sstat tmdStat;
if (Stat(tmdIn.c_str(), &tmdStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), apploaderIn);
LogModule.report(logvisor::Error, _S("unable to stat %s"), tmdIn.c_str());
return -1;
}
/* Check Cert */
SystemString certIn = dirStr + _S("/cert.bin");
Sstat certStat;
if (Stat(certIn.c_str(), &certStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), certIn.c_str());
return -1;
}
/* Check Apploader */
SystemString apploaderIn = dirStr + _S("/DATA/sys/apploader.img");
Sstat apploaderStat;
if (Stat(apploaderIn.c_str(), &apploaderStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), apploaderIn.c_str());
return -1;
}
/* Check Boot */
SystemString bootIn = dirStr + _S("/DATA/sys/boot.bin");
Sstat bootStat;
if (Stat(bootIn.c_str(), &bootStat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), bootIn.c_str());
return -1;
}
/* Check BI2 */
SystemString bi2In = dirStr + _S("/DATA/sys/bi2.bin");
Sstat bi2Stat;
if (Stat(bi2In.c_str(), &bi2Stat))
{
LogModule.report(logvisor::Error, _S("unable to stat %s"), bi2In.c_str());
return -1;
}
return _build(
[this, dirIn, dolIn, apploaderIn](IPartWriteStream& cws) -> bool
[&](IFileIO::IWriteStream& ws, uint32_t& h3OffOut, uint32_t& dataOffOut,
uint8_t& ccIdx, uint8_t tkey[16], uint8_t tkeyiv[16],
std::unique_ptr<uint8_t[]>& tmdData, size_t& tmdSzOut) -> bool
{
return DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(cws, dirIn, dolIn, apploaderIn);
h3OffOut = 0x8000;
dataOffOut = 0x20000;
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(ticketIn.c_str())->beginReadStream();
if (!rs)
return false;
uint8_t buf[0x2A4];
memset(buf, 0, 0x2A4);
rs->read(buf, 0x2A4);
ws.write(buf, 0x2A4);
ccIdx = buf[0x1F1];
memmove(tkey, buf + 0x1BF, 16);
memmove(tkeyiv, buf + 0x1DC, 8);
memset(tkeyiv + 8, 0, 8);
uint32_t curOff = 0x2C0;
uint32_t tmdSz = SBig(uint32_t(tmdStat.st_size));
ws.write(&tmdSz, 4);
uint32_t tmdOff = SBig(curOff >> 2);
ws.write(&tmdOff, 4);
curOff += ROUND_UP_32(tmdStat.st_size);
uint32_t certSz = SBig(uint32_t(certStat.st_size));
ws.write(&certSz, 4);
uint32_t certOff = SBig(curOff >> 2);
ws.write(&certOff, 4);
curOff += ROUND_UP_32(certStat.st_size);
uint32_t h3Off = SBig(0x8000 >> 2);
ws.write(&h3Off, 4);
uint32_t dataOff = SBig(0x20000 >> 2);
ws.write(&dataOff, 4);
uint32_t dataSz = 0;
ws.write(&dataSz, 4);
rs = NewFileIO(tmdIn.c_str())->beginReadStream();
tmdData.reset(new uint8_t[tmdStat.st_size]);
tmdSzOut = tmdStat.st_size;
rs->read(tmdData.get(), tmdStat.st_size);
ws.write(tmdData.get(), tmdStat.st_size);
uint32_t tmdPadding = ROUND_UP_32(tmdStat.st_size) - tmdStat.st_size;
for (int i=0 ; i<tmdPadding ; ++i)
ws.write("", 1);
rs = NewFileIO(certIn.c_str())->beginReadStream();
std::unique_ptr<uint8_t[]> certBuf(new uint8_t[certStat.st_size]);
rs->read(certBuf.get(), certStat.st_size);
ws.write(certBuf.get(), certStat.st_size);
return true;
},
[this, apploaderIn](IPartWriteStream& cws, size_t& xferSz) -> bool
[this, &bootIn](IPartWriteStream& cws, uint32_t dolOff, uint32_t fstOff, uint32_t fstSz) -> bool
{
cws.write(nullptr, 0x2440 - 0x430);
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(apploaderIn)->beginReadStream();
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(bootIn.c_str())->beginReadStream();
if (!rs)
return false;
Header header;
header.read(*rs);
header.m_dolOff = uint32_t(dolOff >> 2);
header.m_fstOff = uint32_t(fstOff >> 2);
header.m_fstSz = fstSz;
header.m_fstMaxSz = fstSz;
header.write(cws);
return true;
},
[this, &bi2In](IPartWriteStream& cws) -> bool
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(bi2In.c_str())->beginReadStream();
if (!rs)
return false;
BI2Header bi2;
bi2.read(*rs);
bi2.write(cws);
return true;
},
[this, &apploaderIn](IPartWriteStream& cws, size_t& xferSz) -> bool
{
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(apploaderIn.c_str())->beginReadStream();
if (!rs)
return false;
char buf[8192];
SystemString apploaderName(apploaderIn);
while (true)
{
size_t rdSz = rs->read(buf, 8192);
@ -1007,11 +1174,15 @@ public:
"apploader flows into user area (one or the other is too big)");
return false;
}
m_parent.m_progressCB(m_parent.getProgressFactor(), apploaderName, xferSz);
m_parent.m_progressCB(m_parent.getProgressFactor(), apploaderIn, xferSz);
}
++m_parent.m_progressIdx;
return true;
}, phBuf.get(), phSz, theStat.st_size);
},
[this, dirIn](IPartWriteStream& cws) -> bool
{
return DiscBuilderBase::PartitionBuilderBase::buildFromDirectory(cws, dirIn);
}, apploaderStat.st_size);
}
bool mergeFromDirectory(const PartitionWii* partIn, const SystemChar* dirIn)
@ -1020,13 +1191,44 @@ public:
std::unique_ptr<uint8_t[]> phBuf = partIn->readPartitionHeaderBuf(phSz);
return _build(
[this, partIn, dirIn](IPartWriteStream& cws) -> bool
[&](IFileIO::IWriteStream& ws, uint32_t& h3OffOut, uint32_t& dataOffOut,
uint8_t& ccIdx, uint8_t tkey[16], uint8_t tkeyiv[16],
std::unique_ptr<uint8_t[]>& tmdData, size_t& tmdSz) -> bool
{
return DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(cws, partIn, dirIn);
h3OffOut = SBig(*reinterpret_cast<uint32_t*>(&phBuf[0x2B4])) << 2;
dataOffOut = SBig(*reinterpret_cast<uint32_t*>(&phBuf[0x2B8])) << 2;
ccIdx = phBuf[0x1F1];
memmove(tkey, phBuf.get() + 0x1BF, 16);
memmove(tkeyiv, phBuf.get() + 0x1DC, 8);
memset(tkeyiv + 8, 0, 8);
tmdSz = SBig(*reinterpret_cast<uint32_t*>(&phBuf[0x2A4]));
tmdData.reset(new uint8_t[tmdSz]);
memmove(tmdData.get(), phBuf.get() + 0x2C0, tmdSz);
size_t copySz = std::min(phSz, size_t(h3OffOut));
ws.write(phBuf.get(), copySz);
return true;
},
[this, partIn](IPartWriteStream& cws, uint32_t dolOff, uint32_t fstOff, uint32_t fstSz) -> bool
{
Header header = partIn->getHeader();
header.m_dolOff = uint32_t(dolOff >> uint64_t(2));
header.m_fstOff = uint32_t(fstOff >> uint64_t(2));
header.m_fstSz = fstSz;
header.m_fstMaxSz = fstSz;
header.write(cws);
return true;
},
[this, partIn](IPartWriteStream& cws) -> bool
{
partIn->getBI2().write(cws);
return true;
},
[this, partIn](IPartWriteStream& cws, size_t& xferSz) -> bool
{
cws.write(nullptr, 0x2440 - 0x430);
std::unique_ptr<uint8_t[]> apploaderBuf = partIn->getApploaderBuf();
size_t apploaderSz = partIn->getApploaderSize();
SystemString apploaderName(_S("<apploader>"));
@ -1041,13 +1243,18 @@ public:
m_parent.m_progressCB(m_parent.getProgressFactor(), apploaderName, xferSz);
++m_parent.m_progressIdx;
return true;
}, phBuf.get(), phSz, partIn->getApploaderSize());
},
[this, partIn, dirIn](IPartWriteStream& cws) -> bool
{
return DiscBuilderBase::PartitionBuilderBase::mergeFromDirectory(cws, partIn, dirIn);
}, partIn->getApploaderSize());
}
};
EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const SystemChar* dolIn,
const SystemChar* apploaderIn, const SystemChar* partHeadIn)
EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn)
{
SystemString dirStr(dirIn);
PartitionBuilderWii& pb = static_cast<PartitionBuilderWii&>(*m_partitions[0]);
uint64_t filledSz = pb.m_baseOffset;
if (!m_fileIO->beginWriteStream())
@ -1066,7 +1273,7 @@ EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const S
ws->write("", 1);
/* Assemble image */
filledSz = pb.buildFromDirectory(dirIn, dolIn, apploaderIn, partHeadIn);
filledSz = pb.buildFromDirectory(dirIn);
if (filledSz == -1)
return EBuildResult::Failed;
else if (filledSz >= uint64_t(m_discCapacity))
@ -1082,7 +1289,12 @@ EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const S
ws = m_fileIO->beginWriteStream(0);
if (!ws)
return EBuildResult::Failed;
Header header(pb.getGameID(), pb.getGameTitle().c_str(), true, 0, 0, 0);
SystemString headerPath = dirStr + _S("/DATA/disc/header.bin");
std::unique_ptr<IFileIO::IReadStream> rs = NewFileIO(headerPath.c_str())->beginReadStream();
if (!rs)
return EBuildResult::Failed;
Header header;
header.read(*rs);
header.write(*ws);
/* Populate partition info */
@ -1099,24 +1311,16 @@ EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const S
ws->write(vals, 4);
/* Populate region info */
SystemString regionPath = dirStr + _S("/DATA/disc/region.bin");
rs = NewFileIO(regionPath.c_str())->beginReadStream();
if (!rs)
return EBuildResult::Failed;
uint8_t regionBuf[0x20];
rs->read(regionBuf, 0x20);
ws = m_fileIO->beginWriteStream(0x4E000);
if (!ws)
return EBuildResult::Failed;
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_fileIO->beginWriteStream(0x4E010);
if (!ws)
return EBuildResult::Failed;
for (int i=0 ; i<16 ; ++i)
ws->write("\x80", 1);
ws->write(regionBuf, 0x20);
/* Fill image to end */
ws = m_fileIO->beginWriteStream(filledSz);
@ -1139,10 +1343,9 @@ EBuildResult DiscBuilderWii::buildFromDirectory(const SystemChar* dirIn, const S
return EBuildResult::Success;
}
uint64_t DiscBuilderWii::CalculateTotalSizeRequired(const SystemChar* dirIn, const SystemChar* dolIn,
bool& dualLayer)
uint64_t DiscBuilderWii::CalculateTotalSizeRequired(const SystemChar* dirIn, bool& dualLayer)
{
uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dolIn, dirIn);
uint64_t sz = DiscBuilderBase::PartitionBuilderBase::CalculateTotalSizeBuild(dirIn);
if (sz == -1)
return -1;
auto szDiv = std::lldiv(sz, 0x1F0000);
@ -1158,12 +1361,10 @@ uint64_t DiscBuilderWii::CalculateTotalSizeRequired(const SystemChar* dirIn, con
return sz;
}
DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, const char gameID[6], const char* gameTitle,
bool dualLayer, FProgress progressCB)
DiscBuilderWii::DiscBuilderWii(const SystemChar* outPath, bool dualLayer, FProgress progressCB)
: DiscBuilderBase(outPath, dualLayer ? 0x1FB4E0000 : 0x118240000, progressCB), m_dualLayer(dualLayer)
{
PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, PartitionBuilderBase::Kind::Data,
gameID, gameTitle, 0x200000);
PartitionBuilderWii* partBuilder = new PartitionBuilderWii(*this, PartitionBuilderBase::Kind::Data, 0x200000);
m_partitions.emplace_back(partBuilder);
}
@ -1224,24 +1425,15 @@ EBuildResult DiscMergerWii::mergeFromDirectory(const SystemChar* dirIn)
ws->write(vals, 4);
/* Populate region info */
std::unique_ptr<IReadStream> rs = m_sourceDisc.getDiscIO().beginReadStream(0x4E000);
if (!rs)
return EBuildResult::Failed;
uint8_t regionBuf[0x20];
rs->read(regionBuf, 0x20);
ws = m_builder.m_fileIO->beginWriteStream(0x4E000);
if (!ws)
return EBuildResult::Failed;
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 EBuildResult::Failed;
for (int i=0 ; i<16 ; ++i)
ws->write("\x80", 1);
ws->write(regionBuf, 0x20);
/* Fill image to end */
ws = m_builder.m_fileIO->beginWriteStream(filledSz);

View File

@ -128,7 +128,7 @@ public:
{
FSeek(fp, offset, whence);
}
int64_t position()
uint64_t position() const
{
return FTell(fp);
}

View File

@ -145,7 +145,7 @@ public:
li.QuadPart = offset;
SetFilePointerEx(fp, li, nullptr, whence);
}
int64_t position()
uint64_t position() const
{
LARGE_INTEGER li = {};
LARGE_INTEGER res;