2015-06-30 06:46:19 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2015-07-04 06:02:12 +00:00
|
|
|
#include <LogVisor/LogVisor.hpp>
|
2015-07-02 18:33:55 +00:00
|
|
|
#include "NOD/NOD.hpp"
|
2015-06-30 06:46:19 +00:00
|
|
|
|
2015-06-30 19:38:51 +00:00
|
|
|
static void printHelp()
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Usage:\n"
|
2016-01-21 06:30:37 +00:00
|
|
|
" nodtool extract [-f] <image-in> [<dir-out>]\n"
|
|
|
|
" nodtool makegcn <gameid> <game-title> <fsroot-in> <dol-in> <apploader-in> [<image-out>]\n"
|
2016-01-22 23:45:58 +00:00
|
|
|
" nodtool makewii(sl|dl) <gameid> <game-title> <fsroot-in> <dol-in> <apploader-in> <parthead-in> [<image-out>]\n");
|
2015-06-30 19:38:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-02 20:37:07 +00:00
|
|
|
#if NOD_UCS2
|
|
|
|
#ifdef strcasecmp
|
|
|
|
#undef strcasecmp
|
|
|
|
#endif
|
|
|
|
#define strcasecmp _wcsicmp
|
2016-01-21 06:30:37 +00:00
|
|
|
#define PRISize "Iu"
|
2015-07-02 20:37:07 +00:00
|
|
|
int wmain(int argc, wchar_t* argv[])
|
|
|
|
#else
|
2016-01-21 06:30:37 +00:00
|
|
|
#define PRISize "zu"
|
2015-06-30 06:46:19 +00:00
|
|
|
int main(int argc, char* argv[])
|
2015-07-02 20:37:07 +00:00
|
|
|
#endif
|
2015-06-30 06:46:19 +00:00
|
|
|
{
|
2016-01-21 06:30:37 +00:00
|
|
|
if (argc < 3 ||
|
|
|
|
(!strcasecmp(argv[1], _S("makegcn")) && argc < 7) ||
|
2016-01-22 23:45:58 +00:00
|
|
|
(!strcasecmp(argv[1], _S("makewiisl")) && argc < 8) ||
|
|
|
|
(!strcasecmp(argv[1], _S("makewiidl")) && argc < 8))
|
2015-06-30 06:46:19 +00:00
|
|
|
{
|
2015-06-30 19:38:51 +00:00
|
|
|
printHelp();
|
2015-06-30 06:46:19 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-07-04 06:02:12 +00:00
|
|
|
/* Enable logging to console */
|
|
|
|
LogVisor::RegisterConsoleLogger();
|
|
|
|
|
2015-09-28 01:55:59 +00:00
|
|
|
NOD::ExtractionContext ctx = { true, true, [&](const std::string& str){
|
|
|
|
fprintf(stderr, "%s\n", str.c_str());
|
|
|
|
}};
|
2015-07-02 20:37:07 +00:00
|
|
|
const NOD::SystemChar* inDir = nullptr;
|
|
|
|
const NOD::SystemChar* outDir = _S(".");
|
2015-09-28 01:55:59 +00:00
|
|
|
|
2015-06-30 19:38:51 +00:00
|
|
|
for (int a=2 ; a<argc ; ++a)
|
|
|
|
{
|
|
|
|
if (argv[a][0] == '-' && argv[a][1] == 'f')
|
2015-09-28 01:55:59 +00:00
|
|
|
ctx.force = true;
|
|
|
|
else if (argv[a][0] == '-' && argv[a][1] == 'v')
|
|
|
|
ctx.verbose = true;
|
|
|
|
|
2015-06-30 19:38:51 +00:00
|
|
|
else if (!inDir)
|
|
|
|
inDir = argv[a];
|
|
|
|
else
|
|
|
|
outDir = argv[a];
|
|
|
|
}
|
2015-06-30 06:46:19 +00:00
|
|
|
|
2015-07-02 20:37:07 +00:00
|
|
|
if (!strcasecmp(argv[1], _S("extract")))
|
2015-06-30 19:38:51 +00:00
|
|
|
{
|
2016-01-22 23:45:58 +00:00
|
|
|
bool isWii;
|
|
|
|
std::unique_ptr<NOD::DiscBase> disc = NOD::OpenDiscFromImage(inDir, isWii);
|
2015-06-30 19:38:51 +00:00
|
|
|
if (!disc)
|
|
|
|
return -1;
|
|
|
|
|
2016-01-22 23:45:58 +00:00
|
|
|
NOD::Mkdir(outDir, 0755);
|
|
|
|
|
|
|
|
if (isWii)
|
|
|
|
static_cast<NOD::DiscWii&>(*disc).writeOutDataPartitionHeader(
|
|
|
|
(NOD::SystemString(outDir) + _S("/partition_head.bin")).c_str());
|
|
|
|
|
2015-10-01 21:47:27 +00:00
|
|
|
NOD::Partition* dataPart = disc->getDataPartition();
|
2015-06-30 19:38:51 +00:00
|
|
|
if (!dataPart)
|
|
|
|
return -1;
|
2015-06-30 06:46:19 +00:00
|
|
|
|
2015-09-28 01:55:59 +00:00
|
|
|
if (!dataPart->extractToDirectory(outDir, ctx))
|
2015-07-26 02:44:44 +00:00
|
|
|
return -1;
|
2015-06-30 19:38:51 +00:00
|
|
|
}
|
2016-01-21 06:30:37 +00:00
|
|
|
else if (!strcasecmp(argv[1], _S("makegcn")))
|
2015-06-30 06:46:19 +00:00
|
|
|
{
|
2016-01-21 06:30:37 +00:00
|
|
|
#if NOD_UCS2
|
|
|
|
if (_wcslen(argv[2]) < 6)
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters");
|
|
|
|
#else
|
|
|
|
if (strlen(argv[2]) < 6)
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Pre-validate paths */
|
|
|
|
NOD::Sstat theStat;
|
|
|
|
if (NOD::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as directory", argv[4]);
|
|
|
|
if (NOD::Stat(argv[5], &theStat) || !S_ISREG(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[5]);
|
|
|
|
if (NOD::Stat(argv[6], &theStat) || !S_ISREG(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[6]);
|
|
|
|
|
|
|
|
size_t lastIdx = -1;
|
|
|
|
auto progFunc = [&](size_t idx, const NOD::SystemString& name, size_t bytes)
|
|
|
|
{
|
|
|
|
if (idx != lastIdx)
|
|
|
|
{
|
|
|
|
lastIdx = idx;
|
|
|
|
printf("\n");
|
|
|
|
}
|
2016-01-21 20:46:07 +00:00
|
|
|
if (bytes != -1)
|
|
|
|
printf("\r%s %" PRISize " B", name.c_str(), bytes);
|
|
|
|
else
|
|
|
|
printf("\r%s", name.c_str());
|
2016-01-21 06:30:37 +00:00
|
|
|
fflush(stdout);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (argc < 8)
|
|
|
|
{
|
|
|
|
NOD::SystemString outPath(argv[4]);
|
|
|
|
outPath.append(_S(".iso"));
|
|
|
|
NOD::DiscBuilderGCN b(outPath.c_str(), argv[2], argv[3], 0x0003EB60, progFunc);
|
|
|
|
b.buildFromDirectory(argv[4], argv[5], argv[6]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NOD::DiscBuilderGCN b(argv[7], argv[2], argv[3], 0x0003EB60, progFunc);
|
|
|
|
b.buildFromDirectory(argv[4], argv[5], argv[6]);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
2015-06-30 19:38:51 +00:00
|
|
|
}
|
2016-01-22 23:45:58 +00:00
|
|
|
else if (!strcasecmp(argv[1], _S("makewiisl")) || !strcasecmp(argv[1], _S("makewiidl")))
|
2016-01-22 02:30:17 +00:00
|
|
|
{
|
|
|
|
#if NOD_UCS2
|
|
|
|
if (_wcslen(argv[2]) < 6)
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters");
|
|
|
|
#else
|
|
|
|
if (strlen(argv[2]) < 6)
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "game-id is not at least 6 characters");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Pre-validate paths */
|
|
|
|
NOD::Sstat theStat;
|
|
|
|
if (NOD::Stat(argv[4], &theStat) || !S_ISDIR(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as directory", argv[4]);
|
|
|
|
if (NOD::Stat(argv[5], &theStat) || !S_ISREG(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[5]);
|
|
|
|
if (NOD::Stat(argv[6], &theStat) || !S_ISREG(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[6]);
|
|
|
|
if (NOD::Stat(argv[7], &theStat) || !S_ISREG(theStat.st_mode))
|
|
|
|
NOD::LogModule.report(LogVisor::FatalError, "unable to stat %s as file", argv[7]);
|
|
|
|
|
|
|
|
size_t lastIdx = -1;
|
|
|
|
auto progFunc = [&](size_t idx, const NOD::SystemString& name, size_t bytes)
|
|
|
|
{
|
|
|
|
if (idx != lastIdx)
|
|
|
|
{
|
|
|
|
lastIdx = idx;
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
if (bytes != -1)
|
|
|
|
printf("\r%s %" PRISize " B", name.c_str(), bytes);
|
|
|
|
else
|
|
|
|
printf("\r%s", name.c_str());
|
|
|
|
fflush(stdout);
|
|
|
|
};
|
|
|
|
|
2016-01-22 23:45:58 +00:00
|
|
|
bool dual = (argv[1][7] == _S('d') || argv[1][7] == _S('D'));
|
|
|
|
|
2016-01-22 02:30:17 +00:00
|
|
|
if (argc < 9)
|
|
|
|
{
|
|
|
|
NOD::SystemString outPath(argv[4]);
|
|
|
|
outPath.append(_S(".iso"));
|
2016-01-22 23:45:58 +00:00
|
|
|
NOD::DiscBuilderWii b(outPath.c_str(), argv[2], argv[3], dual, progFunc);
|
2016-01-22 02:30:17 +00:00
|
|
|
b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-22 23:45:58 +00:00
|
|
|
NOD::DiscBuilderWii b(argv[8], argv[2], argv[3], dual, progFunc);
|
2016-01-22 02:30:17 +00:00
|
|
|
b.buildFromDirectory(argv[4], argv[5], argv[6], argv[7]);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
}
|
2015-06-30 19:38:51 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
printHelp();
|
|
|
|
return -1;
|
2015-06-30 06:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|