#include "Settings.h" #include "types.h" #include "version.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class UnkOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: UnkOptionHandler(const std::string& module_label) : mModuleLabel(module_label) {} const std::vector& getOptionStrings() const { throw tc::InvalidOperationException("getOptionStrings() not defined for UnkOptionHandler."); } void processOption(const std::string& option, const std::vector& params) { throw tc::Exception(mModuleLabel, "Unrecognized option: \"" + option + "\""); } private: std::string mModuleLabel; }; class DeprecatedOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: DeprecatedOptionHandler(const std::string& warn_message, const std::vector& opts) : mWarnMessage(warn_message), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { fmt::print("[WARNING] Option \"{}\" is deprecated.{}{}\n", option, (mWarnMessage.empty() ? "" : " "), mWarnMessage); } private: std::string mWarnMessage; std::vector mOptStrings; }; class FlagOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: FlagOptionHandler(bool& flag, const std::vector& opts) : mFlag(flag), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 0) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" is a flag, that takes no parameters.", option)); } mFlag = true; } private: bool& mFlag; std::vector mOptStrings; }; class SingleParamStringOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: SingleParamStringOptionHandler(tc::Optional& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } mParam = params[0]; } private: tc::Optional& mParam; std::vector mOptStrings; }; class SingleParamPathOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: SingleParamPathOptionHandler(tc::Optional& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } mParam = params[0]; } private: tc::Optional& mParam; std::vector mOptStrings; }; class SingleParamSizetOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: SingleParamSizetOptionHandler(size_t& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } mParam = strtoul(params[0].c_str(), nullptr, 0); } private: size_t& mParam; std::vector mOptStrings; }; class SingleParamAesKeyOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: SingleParamAesKeyOptionHandler(tc::Optional& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } tc::ByteData key_raw = tc::cli::FormatUtil::hexStringToBytes(params[0]); if (key_raw.size() != sizeof(nstool::KeyBag::aes128_key_t)) { throw tc::ArgumentOutOfRangeException(fmt::format("Option: \"{:s}\", requires an AES128 key as the parameter (must be 32 hex chars).", option)); } nstool::KeyBag::aes128_key_t key_tmp; memcpy(key_tmp.data(), key_raw.data(), key_tmp.size()); mParam = key_tmp; } private: tc::Optional& mParam; std::vector mOptStrings; }; class FileTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: FileTypeOptionHandler(nstool::Settings::FileType& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } if (params[0] == "gc" \ || params[0] == "gamecard" \ || params[0] == "xci" \ || params[0] == "xcie" \ || params[0] == "xcir") { mParam = nstool::Settings::FILE_TYPE_GAMECARD; } else if (params[0] == "nsp") { mParam = nstool::Settings::FILE_TYPE_NSP; } else if (params[0] == "partitionfs" || params[0] == "hashedpartitionfs" \ || params[0] == "pfs" || params[0] == "pfs0" \ || params[0] == "hfs" || params[0] == "hfs0") { mParam = nstool::Settings::FILE_TYPE_PARTITIONFS; } else if (params[0] == "romfs") { mParam = nstool::Settings::FILE_TYPE_ROMFS; } else if (params[0] == "nca" || params[0] == "contentarchive") { mParam = nstool::Settings::FILE_TYPE_NCA; } else if (params[0] == "meta" || params[0] == "npdm") { mParam = nstool::Settings::FILE_TYPE_META; } else if (params[0] == "cnmt") { mParam = nstool::Settings::FILE_TYPE_CNMT; } else if (params[0] == "nso") { mParam = nstool::Settings::FILE_TYPE_NSO; } else if (params[0] == "nro") { mParam = nstool::Settings::FILE_TYPE_NRO; } else if (params[0] == "ini") { mParam = nstool::Settings::FILE_TYPE_INI; } else if (params[0] == "kip") { mParam = nstool::Settings::FILE_TYPE_KIP; } else if (params[0] == "nacp") { mParam = nstool::Settings::FILE_TYPE_NACP; } else if (params[0] == "cert") { mParam = nstool::Settings::FILE_TYPE_ES_CERT; } else if (params[0] == "tik") { mParam = nstool::Settings::FILE_TYPE_ES_TIK; } else if (params[0] == "aset" || params[0] == "asset") { mParam = nstool::Settings::FILE_TYPE_HB_ASSET; } else { throw tc::ArgumentException(fmt::format("File type \"{}\" unrecognised.", params[0])); } } private: nstool::Settings::FileType& mParam; std::vector mOptStrings; }; class InstructionTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: InstructionTypeOptionHandler(bool& param, const std::vector& opts) : mParam(param), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } if (params[0] == "32bit") { mParam = false; } else if (params[0] == "64bit") { mParam = true; } else { throw tc::ArgumentException(fmt::format("Instruction type \"{}\" unrecognised. Try \"32bit\" or \"64bit\"", params[0])); } } private: bool& mParam; std::vector mOptStrings; }; class ExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: ExtractDataPathOptionHandler(std::vector& jobs, const std::vector& opts) : mJobs(jobs), mOptStrings(opts) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() == 1) { mJobs.push_back({tc::io::Path("/"), tc::io::Path(params[0])}); } else if (params.size() == 2) { mJobs.push_back({tc::io::Path(params[0]), tc::io::Path(params[1])}); } else { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires parameters in the format \"[] \".", option)); } } private: std::vector& mJobs; std::vector mOptStrings; }; class CustomExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler { public: CustomExtractDataPathOptionHandler(std::vector& jobs, const std::vector& opts, const tc::io::Path& custom_path) : mJobs(jobs), mOptStrings(opts), mCustomPath(custom_path) {} const std::vector& getOptionStrings() const { return mOptStrings; } void processOption(const std::string& option, const std::vector& params) { if (params.size() != 1) { throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); } std::string custom_path_str; tc::io::PathUtil::pathToUnixUTF8(mCustomPath, custom_path_str); fmt::print("[WARNING] \"{:s} {:s}\" is deprecated. ", option, params[0]); // if custom path is root path, use the shortened version of -x if (mCustomPath == tc::io::Path("/")) { fmt::print("Consider using \"-x {:s}\" instead.\n", params[0]); } else { fmt::print("Consider using \"-x {:s} {:s}\" instead.\n", custom_path_str, params[0]); } mJobs.push_back({mCustomPath, tc::io::Path(params[0])}); } private: std::vector& mJobs; std::vector mOptStrings; tc::io::Path mCustomPath; }; nstool::SettingsInitializer::SettingsInitializer(const std::vector& args) : Settings(), mModuleLabel("nstool::SettingsInitializer"), mShowLayout(false), mShowKeydata(false), mVerbose(false), mNcaEncryptedContentKey(), mNcaContentKey(), mTikPath(), mCertPath() { // parse input arguments parse_args(args); if (infile.path.isNull()) throw tc::ArgumentException(mModuleLabel, "No input file was specified."); // determine CLI output mode opt.cli_output_mode.show_basic_info = true; if (mVerbose) { opt.cli_output_mode.show_extended_info = true; opt.cli_output_mode.show_layout = true; opt.cli_output_mode.show_keydata = true; } if (mShowKeydata) { opt.cli_output_mode.show_keydata = true; } if (mShowLayout) { opt.cli_output_mode.show_layout = true; } // locate key file, if not specfied if (mKeysetPath.isNull()) { std::string home_path_str; if (tc::os::getEnvVar("HOME", home_path_str) || tc::os::getEnvVar("USERPROFILE", home_path_str)) { tc::io::Path keyfile_path = tc::io::Path(home_path_str); keyfile_path.push_back(".switch"); keyfile_path.push_back(opt.is_dev ? "dev.keys" : "prod.keys"); try { tc::io::FileStream test = tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read); mKeysetPath = keyfile_path; } catch (tc::io::FileNotFoundException&) { fmt::print("[WARNING] Failed to load \"{}\" keyfile. Maybe specify it with \"-k \"?\n", opt.is_dev ? "dev.keys" : "prod.keys"); } } else { fmt::print("[WARNING] Failed to located \"{}\" keyfile. Maybe specify it with \"-k \"?\n", opt.is_dev ? "dev.keys" : "prod.keys"); } } // generate keybag opt.keybag = KeyBagInitializer(opt.is_dev, mKeysetPath, mTikPath, mCertPath); opt.keybag.fallback_enc_content_key = mNcaEncryptedContentKey; opt.keybag.fallback_content_key = mNcaContentKey; // dump keys if requires if (mShowKeydata) // but not opt.cli_output_mode.show_keydata, since this that enabled by toggling -v,--verbose, personally I don't think a summary of imported keydata should be included in verbose output. { dump_keys(); } // determine filetype if not manually specified if (infile.filetype == FILE_TYPE_ERROR) { determine_filetype(); if (infile.filetype == FILE_TYPE_ERROR) { throw tc::ArgumentException(mModuleLabel, "Input file type was undetermined."); } } } void nstool::SettingsInitializer::parse_args(const std::vector& args) { // check for minimum arguments if (args.size() < 2) { usage_text(); throw tc::ArgumentException(mModuleLabel, "Not enough arguments."); } // detect request for help for (auto itr = ++(args.begin()); itr != args.end(); itr++) { if (*itr == "-h" || *itr == "--help" || *itr == "-help") { usage_text(); throw tc::ArgumentException(mModuleLabel, "Help required."); } } // save input file infile.path = tc::io::Path(args.back()); // test new option parser tc::cli::OptionParser opts; // register unk option handler opts.registerUnrecognisedOptionHandler(std::shared_ptr(new UnkOptionHandler(mModuleLabel))); // register handler for deprecated options DeprecatedOptionHandler // none just yet // get option flags opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mShowLayout, {"--showlayout"}))); opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mShowKeydata, { "--showkeys" }))); opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mVerbose, {"-v", "--verbose"}))); opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(opt.verify, {"-y", "--verify"}))); opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(opt.is_dev, {"-d", "--dev"}))); // process input file type opts.registerOptionHandler(std::shared_ptr(new FileTypeOptionHandler(infile.filetype, { "-t", "--type" }))); // get user-provided keydata opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"}))); opts.registerOptionHandler(std::shared_ptr(new SingleParamAesKeyOptionHandler(mNcaEncryptedContentKey, {"--titlekey"}))); opts.registerOptionHandler(std::shared_ptr(new SingleParamAesKeyOptionHandler(mNcaContentKey, {"--contentkey", "--bodykey"}))); opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mTikPath, {"--tik"}))); opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mCertPath, {"--cert"}))); // code options opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(code.list_api, { "--listapi" }))); opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(code.list_symbols, { "--listsym" }))); opts.registerOptionHandler(std::shared_ptr(new InstructionTypeOptionHandler(code.is_64bit_instruction, { "--insttype" }))); // fs options opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(fs.show_fs_tree, { "--fstree", "--listfs" }))); opts.registerOptionHandler(std::shared_ptr(new ExtractDataPathOptionHandler(fs.extract_jobs, { "-x", "--extract" }))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--fsdir" }, tc::io::Path("/")))); // xci options opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--update" }, tc::io::Path("/update/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--normal" }, tc::io::Path("/normal/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--secure" }, tc::io::Path("/secure/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--logo" }, tc::io::Path("/logo/")))); // nca options opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part0" }, tc::io::Path("/0/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part1" }, tc::io::Path("/1/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part2" }, tc::io::Path("/2/")))); opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part3" }, tc::io::Path("/3/")))); // kip options opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(kip.extract_path, { "--kipdir" }))); // aset options opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(aset.icon_extract_path, { "--icon" }))); opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(aset.nacp_extract_path, { "--nacp" }))); // process option opts.processOptions(args, 1, args.size() - 2); } void nstool::SettingsInitializer::determine_filetype() { //std::string infile_path_str; //tc::io::PathUtil::pathToUnixUTF8(infile.path.get(), infile_path_str); //fmt::print("infile path = \"{}\"\n", infile_path_str); auto file = tc::io::StreamSource(std::make_shared(tc::io::FileStream(infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read))); auto raw_data = file.pullData(0, 0x5000); #define _TYPE_PTR(st) ((st*)(raw_data.data())) #define _ASSERT_FILE_SIZE(sz) (file.length() >= tc::io::IOUtil::castSizeToInt64(sz)) // do easy tests // detect "scene" XCI if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sGcHeader_Rsa2048Signed)) && _TYPE_PTR(nn::hac::sGcHeader_Rsa2048Signed)->header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic) { infile.filetype = FILE_TYPE_GAMECARD; } // detect "SDK" XCI else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sSdkGcHeader)) && _TYPE_PTR(nn::hac::sSdkGcHeader)->signed_header.header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic) { infile.filetype = FILE_TYPE_GAMECARD; } // detect PFS0 else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sPfsHeader)) && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic) { infile.filetype = FILE_TYPE_PARTITIONFS; } // detect HFS0 else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sPfsHeader)) && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.unwrap() == nn::hac::pfs::kHashedPfsStructMagic) { infile.filetype = FILE_TYPE_PARTITIONFS; } // detect ROMFS else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sRomfsHeader)) && _TYPE_PTR(nn::hac::sRomfsHeader)->header_size.unwrap() == sizeof(nn::hac::sRomfsHeader) && _TYPE_PTR(nn::hac::sRomfsHeader)->dir_entry.offset.unwrap() == (_TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.offset.unwrap() + _TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.size.unwrap())) { infile.filetype = FILE_TYPE_ROMFS; } // detect NPDM else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sMetaHeader)) && _TYPE_PTR(nn::hac::sMetaHeader)->st_magic.unwrap() == nn::hac::meta::kMetaStructMagic) { infile.filetype = FILE_TYPE_META; } // detect NSO else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sNsoHeader)) && _TYPE_PTR(nn::hac::sNsoHeader)->st_magic.unwrap() == nn::hac::nso::kNsoStructMagic) { infile.filetype = FILE_TYPE_NSO; } // detect NRO else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sNroHeader)) && _TYPE_PTR(nn::hac::sNroHeader)->st_magic.unwrap() == nn::hac::nro::kNroStructMagic) { infile.filetype = FILE_TYPE_NRO; } // detect INI else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sIniHeader)) && _TYPE_PTR(nn::hac::sIniHeader)->st_magic.unwrap() == nn::hac::ini::kIniStructMagic) { infile.filetype = FILE_TYPE_INI; } // detect KIP else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sKipHeader)) && _TYPE_PTR(nn::hac::sKipHeader)->st_magic.unwrap() == nn::hac::kip::kKipStructMagic) { infile.filetype = FILE_TYPE_KIP; } // detect HB ASET else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sAssetHeader)) && _TYPE_PTR(nn::hac::sAssetHeader)->st_magic.unwrap() == nn::hac::aset::kAssetStructMagic) { infile.filetype = FILE_TYPE_KIP; } // more complicated tests // detect NCA else if (determineValidNcaFromSample(raw_data)) { infile.filetype = FILE_TYPE_NCA; } // detect Certificate else if (determineValidEsCertFromSample(raw_data)) { infile.filetype = FILE_TYPE_ES_CERT; } // detect Ticket else if (determineValidEsTikFromSample(raw_data)) { infile.filetype = FILE_TYPE_ES_TIK; } // detect Ticket else if (determineValidCnmtFromSample(raw_data)) { infile.filetype = FILE_TYPE_CNMT; } // detect Ticket else if (determineValidNacpFromSample(raw_data)) { infile.filetype = FILE_TYPE_NACP; } #undef _TYPE_PTR #undef _ASSERT_FILE_SIZE } void nstool::SettingsInitializer::usage_text() const { fmt::print("{:s} v{:d}.{:d}.{:d} (C) {:s}\n", APP_NAME, VER_MAJOR, VER_MINOR, VER_PATCH, AUTHORS); fmt::print("Built: {:s} {:s}\n\n", __TIME__, __DATE__); fmt::print("Usage: {:s} [options... ] \n", BIN_NAME); fmt::print("\n General Options:\n"); fmt::print(" -d, --dev Use devkit keyset.\n"); fmt::print(" -k, --keyset Specify keyset file.\n"); fmt::print(" -t, --type Specify input file type. [xci, pfs, romfs, nca, meta, cnmt, nso, nro, ini, kip, nacp, aset, cert, tik]\n"); fmt::print(" -y, --verify Verify file.\n"); fmt::print("\n Output Options:\n"); fmt::print(" --showkeys Show keys generated.\n"); fmt::print(" --showlayout Show layout metadata.\n"); fmt::print(" -v, --verbose Verbose output.\n"); fmt::print("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Nintendo Submission Package)\n"); fmt::print(" {:s} [--fstree] [-x [] ] \n", BIN_NAME); fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print("\n XCI (GameCard Image)\n"); fmt::print(" {:s} [--fstree] [-x [] ] <.xci file>\n", BIN_NAME); fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print(" --update Extract \"update\" partition to directory. (Alias for \"-x /update \")\n"); fmt::print(" --logo Extract \"logo\" partition to directory. (Alias for \"-x /logo \")\n"); fmt::print(" --normal Extract \"normal\" partition to directory. (Alias for \"-x /normal \")\n"); fmt::print(" --secure Extract \"secure\" partition to directory. (Alias for \"-x /secure \")\n"); fmt::print("\n NCA (Nintendo Content Archive)\n"); fmt::print(" {:s} [--fstree] [-x [] ] [--bodykey --titlekey -tik ] <.nca file>\n", BIN_NAME); fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print(" --titlekey Specify (encrypted) title key extracted from ticket.\n"); fmt::print(" --contentkey Specify content key.\n"); fmt::print(" --tik Specify ticket to source title key.\n"); fmt::print(" --cert Specify certificate chain to verify ticket.\n"); fmt::print(" --part0 Extract partition \"0\" to directory. (Alias for \"-x /0 \")\n"); fmt::print(" --part1 Extract partition \"1\" to directory. (Alias for \"-x /1 \")\n"); fmt::print(" --part2 Extract partition \"2\" to directory. (Alias for \"-x /2 \")\n"); fmt::print(" --part3 Extract partition \"3\" to directory. (Alias for \"-x /3 \")\n"); fmt::print("\n NSO (Nintendo Shared Object), NRO (Nintendo Relocatable Object)\n"); fmt::print(" {:s} [--listapi --listsym] [--insttype ] \n", BIN_NAME); fmt::print(" --listapi Print SDK API List.\n"); fmt::print(" --listsym Print Code Symbols.\n"); fmt::print(" --insttype Specify instruction type [64bit|32bit] (64bit is assumed).\n"); fmt::print("\n INI (Initial Program Bundle)\n"); fmt::print(" {:s} [--kipdir ] \n", BIN_NAME); fmt::print(" --kipdir Extract embedded Initial Programs to directory.\n"); fmt::print("\n ASET (Homebrew Asset Blob)\n"); fmt::print(" {:s} [--fstree] [-x [] ] [--icon --nacp ] \n", BIN_NAME); fmt::print(" --fstree Print RomFs filesystem tree.\n"); fmt::print(" -x, --extract Extract a file or directory from RomFs to local filesystem.\n"); fmt::print(" --icon Extract icon partition to file.\n"); fmt::print(" --nacp Extract NACP partition to file.\n"); } void nstool::SettingsInitializer::dump_keys() const { fmt::print("[KeyConfiguration]\n"); fmt::print(" NCA Keys:\n"); for (auto itr = opt.keybag.nca_header_sign0_key.begin(); itr != opt.keybag.nca_header_sign0_key.end(); itr++) { dump_rsa_key(itr->second, fmt::format("Header0-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); } for (auto itr = opt.keybag.acid_sign_key.begin(); itr != opt.keybag.acid_sign_key.end(); itr++) { dump_rsa_key(itr->second, fmt::format("Acid-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); } if (opt.keybag.nca_header_key.isSet()) { fmt::print(" Header-EncryptionKey:\n"); fmt::print(" Key0: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[0].data(), opt.keybag.nca_header_key.get()[0].size(), true, "")); fmt::print(" Key1: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[1].data(), opt.keybag.nca_header_key.get()[1].size(), true, "")); } std::vector kaek_label = {"Application", "Ocean", "System"}; for (size_t kaek_index = 0; kaek_index < opt.keybag.nca_key_area_encryption_key.size(); kaek_index++) { for (auto itr = opt.keybag.nca_key_area_encryption_key[kaek_index].begin(); itr != opt.keybag.nca_key_area_encryption_key[kaek_index].end(); itr++) { fmt::print(" KeyAreaEncryptionKey-{:s}-{:02x}:\n {:s}\n", kaek_label[kaek_index], itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } } for (size_t kaek_index = 0; kaek_index < opt.keybag.nca_key_area_encryption_key_hw.size(); kaek_index++) { for (auto itr = opt.keybag.nca_key_area_encryption_key_hw[kaek_index].begin(); itr != opt.keybag.nca_key_area_encryption_key_hw[kaek_index].end(); itr++) { fmt::print(" KeyAreaEncryptionKeyHw-{:s}-{:02x}:\n {:s}\n", kaek_label[kaek_index], itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } } fmt::print(" NRR Keys:\n"); for (auto itr = opt.keybag.nrr_certificate_sign_key.begin(); itr != opt.keybag.nrr_certificate_sign_key.end(); itr++) { dump_rsa_key(itr->second, fmt::format("Certificate-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); } fmt::print(" XCI Keys:\n"); if (opt.keybag.xci_header_sign_key.isSet()) { dump_rsa_key(opt.keybag.xci_header_sign_key.get(), fmt::format("Header-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); } for (auto itr = opt.keybag.xci_header_key.begin(); itr != opt.keybag.xci_header_key.end(); itr++) { fmt::print(" ExtendedHeader-EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } if (opt.keybag.xci_cert_sign_key.isSet()) { dump_rsa_key(opt.keybag.xci_cert_sign_key.get(), fmt::format("CERT-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); } fmt::print(" Package1 Keys:\n"); for (auto itr = opt.keybag.pkg1_key.begin(); itr != opt.keybag.pkg1_key.end(); itr++) { fmt::print(" EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } fmt::print(" Package2 Keys:\n"); if (opt.keybag.pkg2_sign_key.isSet()) { dump_rsa_key(opt.keybag.pkg2_sign_key.get(), fmt::format("Header-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); } for (auto itr = opt.keybag.pkg2_key.begin(); itr != opt.keybag.pkg2_key.end(); itr++) { fmt::print(" EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } fmt::print(" ETicket Keys:\n"); for (auto itr = opt.keybag.etik_common_key.begin(); itr != opt.keybag.etik_common_key.end(); itr++) { fmt::print(" CommonKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); } fmt::print(" BroadOn Signer Profiles:\n"); for (auto itr = opt.keybag.broadon_signer.begin(); itr != opt.keybag.broadon_signer.end(); itr++) { fmt::print(" {:s}:\n", itr->first); fmt::print(" SignType: "); switch(itr->second.key_type) { case nn::pki::sign::SIGN_ALGO_RSA2048: fmt::print("RSA-2048\n"); break; case nn::pki::sign::SIGN_ALGO_RSA4096: fmt::print("RSA-4096\n"); break; case nn::pki::sign::SIGN_ALGO_ECDSA240: fmt::print("ECDSA-240\n"); break; default: fmt::print("Unknown\n"); } switch(itr->second.key_type) { case nn::pki::sign::SIGN_ALGO_RSA2048: case nn::pki::sign::SIGN_ALGO_RSA4096: dump_rsa_key(itr->second.rsa_key, "RsaKey", 6, opt.cli_output_mode.show_extended_info); break; case nn::pki::sign::SIGN_ALGO_ECDSA240: default: break; } } } void nstool::SettingsInitializer::dump_rsa_key(const KeyBag::rsa_key_t& key, const std::string& label, size_t indent, bool expanded_key_data) const { std::string indent_str; indent_str.clear(); for (size_t i = 0; i < indent; i++) { indent_str += " "; } fmt::print("{:s}{:s}:\n", indent_str, label); if (key.n.size() > 0) { if (expanded_key_data) { fmt::print("{:s} Modulus:\n", indent_str); fmt::print("{:s} {:s}", indent_str, tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(key.n.data(), key.n.size(), true, "", 0x10, indent + 4, false)); } else { fmt::print("{:s} Modulus: {:s}\n", indent_str, getTruncatedBytesString(key.n.data(), key.n.size())); } } if (key.d.size() > 0) { if (expanded_key_data) { fmt::print("{:s} Private Exponent:\n", indent_str); fmt::print("{:s} {:s}", indent_str, tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(key.d.data(), key.d.size(), true, "", 0x10, indent + 4, false)); } else { fmt::print("{:s} Private Exponent: {:s}\n", indent_str, getTruncatedBytesString(key.d.data(), key.d.size())); } } } bool nstool::SettingsInitializer::determineValidNcaFromSample(const tc::ByteData& sample) const { if (sample.size() < nn::hac::nca::kHeaderSize) { return false; } if (opt.keybag.nca_header_key.isNull()) { fmt::print("[WARNING] Failed to load NCA Header Key.\n"); return false; } nn::hac::detail::aes128_xtskey_t key = opt.keybag.nca_header_key.get(); //fmt::print("NCA header key: {} {}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[0].data(), opt.keybag.nca_header_key.get()[0].size(), true, ""), tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[1].data(), opt.keybag.nca_header_key.get()[1].size(), true, "")); // init aes-xts tc::crypto::Aes128XtsEncryptor enc; enc.initialize(key[0].data(), key[0].size(), key[1].data(), key[1].size(), nn::hac::nca::kSectorSize, false); // decrypt main header byte_t raw_hdr[nn::hac::nca::kSectorSize]; enc.decrypt(raw_hdr, sample.data() + nn::hac::ContentArchiveUtil::sectorToOffset(1), nn::hac::nca::kSectorSize, 1); nn::hac::sContentArchiveHeader* hdr = (nn::hac::sContentArchiveHeader*)(raw_hdr); /* fmt::print("NCA Header Raw:\n"); fmt::print("{:s}\n", tc::cli::FormatUtil::formatBytesAsHxdHexString(sample.data() + nn::hac::ContentArchiveUtil::sectorToOffset(1), nn::hac::nca::kSectorSize)); fmt::print("{:s}\n", tc::cli::FormatUtil::formatBytesAsHxdHexString(raw_hdr, nn::hac::nca::kSectorSize)); */ if (hdr->st_magic.unwrap() != nn::hac::nca::kNca2StructMagic && hdr->st_magic.unwrap() != nn::hac::nca::kNca3StructMagic) { return false; } return true; } bool nstool::SettingsInitializer::determineValidCnmtFromSample(const tc::ByteData& sample) const { if (sample.size() < sizeof(nn::hac::sContentMetaHeader)) return false; const nn::hac::sContentMetaHeader* data = (const nn::hac::sContentMetaHeader*)sample.data(); size_t minimum_size = sizeof(nn::hac::sContentMetaHeader) + data->exhdr_size.unwrap() + data->content_count.unwrap() * sizeof(nn::hac::sContentInfo) + data->content_meta_count.unwrap() * sizeof(nn::hac::sContentMetaInfo) + nn::hac::cnmt::kDigestLen; if (sample.size() < minimum_size) return false; // include exthdr/data check if applicable if (data->exhdr_size.unwrap() > 0) { if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Application) { const nn::hac::sApplicationMetaExtendedHeader* meta = (const nn::hac::sApplicationMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); if ((meta->patch_id.unwrap() & data->id.unwrap()) != data->id.unwrap()) return false; } else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Patch) { const nn::hac::sPatchMetaExtendedHeader* meta = (const nn::hac::sPatchMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) return false; minimum_size += meta->extended_data_size.unwrap(); } else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::AddOnContent) { const nn::hac::sAddOnContentMetaExtendedHeader* meta = (const nn::hac::sAddOnContentMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) return false; } else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Delta) { const nn::hac::sDeltaMetaExtendedHeader* meta = (const nn::hac::sDeltaMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) return false; minimum_size += meta->extended_data_size.unwrap(); } else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::SystemUpdate) { const nn::hac::sSystemUpdateMetaExtendedHeader* meta = (const nn::hac::sSystemUpdateMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); minimum_size += meta->extended_data_size.unwrap(); } } if (sample.size() != minimum_size) return false; return true; } bool nstool::SettingsInitializer::determineValidNacpFromSample(const tc::ByteData& sample) const { if (sample.size() != sizeof(nn::hac::sApplicationControlProperty)) return false; const nn::hac::sApplicationControlProperty* data = (const nn::hac::sApplicationControlProperty*)sample.data(); if (data->logo_type > (byte_t)nn::hac::nacp::LogoType::Nintendo) return false; if (data->display_version[0] == 0) return false; if (data->user_account_save_data_size.unwrap() == 0 && data->user_account_save_data_journal_size.unwrap() != 0) return false; if (data->user_account_save_data_journal_size.unwrap() == 0 && data->user_account_save_data_size.unwrap() != 0) return false; if (*((uint32_t*)(&data->supported_language_flag)) == 0) return false; return true; } bool nstool::SettingsInitializer::determineValidEsCertFromSample(const tc::ByteData& sample) const { nn::pki::SignatureBlock sign; try { sign.fromBytes(sample.data(), sample.size()); } catch (...) { return false; } if (sign.isLittleEndian() == true) return false; if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA4096_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_ECDSA240_SHA256) return false; return true; } bool nstool::SettingsInitializer::determineValidEsTikFromSample(const tc::ByteData& sample) const { nn::pki::SignatureBlock sign; try { sign.fromBytes(sample.data(), sample.size()); } catch (...) { return false; } if (sign.isLittleEndian() == false) return false; if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256) return false; const nn::es::sTicketBody_v2* body = (const nn::es::sTicketBody_v2*)(sample.data() + sign.getBytes().size()); if ((body->issuer.str().substr(0, 5) == "Root-" && body->issuer.str().substr(16, 2) == "XS") == false) return false; return true; }