mirror of
https://github.com/Neo-Desktop/WindowsXPKg
synced 2025-04-18 22:07:29 +03:00
669 lines
18 KiB
C++
669 lines
18 KiB
C++
/**
|
|
* This file is a part of the UMSKT Project
|
|
*
|
|
* Copyleft (C) 2019-2024 UMSKT Contributors (et.al.)
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* @FileCreated by Neo on 01/02/2024
|
|
* @Maintainer Neo
|
|
*/
|
|
|
|
#include "cli.h"
|
|
|
|
// define static storage
|
|
std::array<CLIHelpOptions, CLIHelpOptionID_END> CLI::helpOptions;
|
|
|
|
void CLI::SetHelpText()
|
|
{
|
|
helpOptions[OPTION_HELP] = {"h", "help", "show this help text", false, "", &DisplayHelp};
|
|
helpOptions[OPTION_HELP2] = {"?", "", "show this help text", false, "", &DisplayHelp};
|
|
|
|
helpOptions[OPTION_VERSION] = {"", "version", "show UMSKT CLI / libumskt versions", false, "", nullptr};
|
|
|
|
helpOptions[OPTION_VERBOSE] = {"v", "verbose", "enable verbose output", false, "", &SetVerboseOption};
|
|
|
|
helpOptions[OPTION_DEBUG] = {"d", "debug", "enable debug output", false, "", &SetDebugOption};
|
|
|
|
helpOptions[OPTION_FILE] = {
|
|
"", "file", "(advanced) specify which keys JSON file to load", true, "[embedded file]", &SetFileOption};
|
|
|
|
helpOptions[OPTION_LIST] = {"l", "list", "list supported products", false, "", &SetListOption};
|
|
|
|
helpOptions[OPTION_PRODUCT] = {"p", "product", "[REQUIRED] which product to generate keys for",
|
|
true, options.productCode, &SetProductCodeOption};
|
|
|
|
helpOptions[OPTION_FLAVOUR] = {
|
|
"f", "flavour", "which product flavour to generate keys for (required in some instances)",
|
|
true, "", &SetFlavourOption};
|
|
|
|
helpOptions[OPTION_NUMBER] = {"n", "number", "(PIDGEN only) number of keys to generate",
|
|
true, "1", &SetNumberOption};
|
|
|
|
helpOptions[OPTION_ACTIVATIONID] = {
|
|
"I", "installationID", "(activation only) installation ID used to generate confirmation ID", true,
|
|
"", &SetActivationIDOption};
|
|
|
|
helpOptions[OPTION_ACTIVATIONPID] = {
|
|
"P", "productID", "(Office activation only) product ID to generate confirmation ID for",
|
|
true, "", &SetProductIDOption};
|
|
|
|
helpOptions[OPTION_OEM] = {"O", "oem", "(PIDGEN) generate an OEM key", false, "", &SetOEMOption};
|
|
|
|
helpOptions[OPTION_UPGRADE] = {"U", "upgrade", "(PIDGEN 3 only) generate an upgrade key",
|
|
false, "", &SetUpgradeOption};
|
|
|
|
helpOptions[OPTION_BINK] = {"", "bink", "(advanced) override which BINK identifier to load",
|
|
true, "", &SetBINKOption};
|
|
|
|
helpOptions[OPTION_CHANNELID] = {"", "channel", "(advanced) override which product channel to use",
|
|
true, "", &SetChannelIDOption};
|
|
|
|
helpOptions[OPTION_SERIAL] = {
|
|
"", "serial", "(advanced, PIDGEN 2/3 [BINK 1998] only) specify a serial to generate",
|
|
true, "", &SetSerialOption};
|
|
|
|
helpOptions[OPTION_AUTHDATA] = {
|
|
"a", "authdata", "(advanced, PIDGEN 3 [BINK 2002] only) specify a value for the authentication data field",
|
|
true, "", &SetAuthDataOption};
|
|
|
|
helpOptions[OPTION_VALIDATE] = {
|
|
"V", "validate", "validate a specified product ID against known BINKs and algorithms",
|
|
true, "", &SetValidateOption};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return success
|
|
*/
|
|
BOOL CLI::parseCommandLine()
|
|
{
|
|
for (DWORD32 i = 1; i < options.argc; i++)
|
|
{
|
|
std::string arg = options.argv[i];
|
|
|
|
if (arg[0] == '-' || arg[0] == '/')
|
|
{
|
|
arg.erase(0, 1 + (arg[1] == '-' ? 1 : 0));
|
|
}
|
|
|
|
if (arg.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (BYTE j = 0; j < CLIHelpOptionID_END; j++)
|
|
{
|
|
auto thisOption = helpOptions[j];
|
|
if (arg != thisOption.Short && arg != thisOption.Long)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::string nextarg;
|
|
if (thisOption.hasArguments)
|
|
{
|
|
if (i == options.argc - 1)
|
|
{
|
|
options.error = true;
|
|
goto CommandLineParseEnd;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
nextarg = std::string(options.argv[i]);
|
|
}
|
|
}
|
|
|
|
if (thisOption.handler == nullptr)
|
|
{
|
|
// prevent accidental segmentation faults
|
|
continue;
|
|
}
|
|
|
|
auto success = thisOption.handler(nextarg);
|
|
|
|
if (!success)
|
|
{
|
|
options.error = true;
|
|
goto CommandLineParseEnd;
|
|
}
|
|
if (options.help)
|
|
{
|
|
goto CommandLineParseEnd;
|
|
}
|
|
goto ParseNextCommandLineOption;
|
|
}
|
|
|
|
fmt::print("unknown option: {}\n", arg);
|
|
options.error = true;
|
|
goto CommandLineParseEnd;
|
|
|
|
ParseNextCommandLineOption:
|
|
continue;
|
|
}
|
|
|
|
CommandLineParseEnd:
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("\n");
|
|
}
|
|
if (options.error)
|
|
{
|
|
DisplayErrorMessage("");
|
|
}
|
|
return !options.error;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return success
|
|
*/
|
|
BOOL CLI::processOptions()
|
|
{
|
|
if (!loadJSON(options.keysFilename))
|
|
{
|
|
options.error = true;
|
|
return false;
|
|
}
|
|
|
|
if (options.list)
|
|
{
|
|
return processListCommand();
|
|
}
|
|
|
|
if (options.productCode.empty())
|
|
{
|
|
fmt::print("ERROR: product code is required. Exiting...\n");
|
|
DisplayHelp("");
|
|
return false;
|
|
}
|
|
|
|
if (!keys["products"].contains(options.productCode))
|
|
{
|
|
fmt::print("ERROR: Product \"{}\" is unknown\n", options.productCode);
|
|
return false;
|
|
}
|
|
|
|
auto product = keys["products"][options.productCode];
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Selecting product: {}\n", options.productCode);
|
|
}
|
|
|
|
json flavour;
|
|
if (product.contains("flavours"))
|
|
{
|
|
// no default flavour, no flavour specified
|
|
if (!product["meta"].contains("default") && options.productFlavour.empty())
|
|
{
|
|
fmt::print("ERROR: Product \"{}\n does not have a default flavour. Please specify a flavour.",
|
|
options.productCode);
|
|
return false;
|
|
}
|
|
// yes flavour specified, but not found
|
|
else if (!product["flavours"].contains(options.productFlavour) && !options.productFlavour.empty())
|
|
{
|
|
fmt::print("ERROR: Product \"{}\" does not have a flavour named \"{}\"\n", options.productCode,
|
|
options.productFlavour);
|
|
return false;
|
|
}
|
|
// yes default flavour, no flavour specified
|
|
else if (product["meta"].contains("default") && options.productFlavour.empty())
|
|
{
|
|
flavour = product["flavours"][product["meta"]["default"]];
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Selecting default flavour: {}\n", product["meta"]["default"]);
|
|
}
|
|
}
|
|
// yes flavour specified, and is found
|
|
else
|
|
{
|
|
flavour = product["flavours"][options.productFlavour];
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Selecting flavour: {}\n", options.productFlavour);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no variants, just go with what we have
|
|
flavour = product;
|
|
}
|
|
|
|
if (options.state != Options::STATE_PIDGEN_GENERATE && options.state != Options::STATE_PIDGEN_VALIDATE)
|
|
{
|
|
// exit early if we're not doing PIDGEN
|
|
goto processOptionsExitEarly;
|
|
}
|
|
|
|
if (flavour["meta"]["type"] == "PIDGEN3")
|
|
{
|
|
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_3;
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting PIDGEN type to \"PIDGEN3\"\n");
|
|
}
|
|
|
|
if (options.oem)
|
|
{
|
|
flavour["BINK"][1].get_to(options.binkID);
|
|
}
|
|
else
|
|
{
|
|
flavour["BINK"][0].get_to(options.binkID);
|
|
}
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Selected BINK: {}\n", options.binkID);
|
|
}
|
|
}
|
|
else if (flavour["meta"]["type"] == "PIDGEN2")
|
|
{
|
|
options.pidgenversion = Options::PIDGEN_VERSION::PIDGEN_2;
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting PIDGEN type to \"PIDGEN2\"\n");
|
|
}
|
|
}
|
|
|
|
if (options.state != Options::STATE_PIDGEN_GENERATE)
|
|
{
|
|
// exit early if we're only validating
|
|
goto processOptionsExitEarly;
|
|
}
|
|
|
|
if (flavour.contains("DPC") && flavour["DPC"].contains(options.binkID) && options.channelID == 0)
|
|
{
|
|
std::vector<json> filtered;
|
|
for (auto const &i : flavour["DPC"][options.binkID].items())
|
|
{
|
|
auto el = i.value();
|
|
if (!el["isEvaluation"].get<bool>())
|
|
{
|
|
filtered.emplace_back(el);
|
|
}
|
|
}
|
|
|
|
// roll a die to choose which DPC entry to pick
|
|
auto rand = UMSKT::getRandom<BYTE>();
|
|
auto dpc = filtered[rand % filtered.size()];
|
|
auto min = dpc["min"].get<WORD>(), max = dpc["max"].get<WORD>();
|
|
|
|
if (min == max)
|
|
{
|
|
options.channelID = min;
|
|
}
|
|
else
|
|
{
|
|
options.channelID = min + (rand % (max - min));
|
|
}
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Selected channel ID: {} (DPC entry {}/{})\n", options.channelID, (rand % filtered.size()) + 1,
|
|
filtered.size());
|
|
}
|
|
}
|
|
|
|
if (options.channelID.IsZero())
|
|
{
|
|
options.channelID.Randomize(UMSKT::rng, sizeof(DWORD32) * 8);
|
|
options.channelID %= PIDGEN::MaxChannelID;
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Generated channel ID: {}\n", options.channelID);
|
|
}
|
|
}
|
|
|
|
// FE and FF are BINK 1998, but use a different, currently unhandled encoding scheme, we return an error here
|
|
if (options.binkID == "TS00" || options.binkID == "TS01")
|
|
{
|
|
fmt::print("ERROR: Terminal Services BINKs (TS00 and TS01) are unsupported at this time\n");
|
|
return false;
|
|
}
|
|
|
|
processOptionsExitEarly:
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Displays the contents of the input JSON file in an
|
|
* intuitive and attractive pattern
|
|
*
|
|
* @return false
|
|
*/
|
|
BOOL CLI::processListCommand()
|
|
{
|
|
// the following code is absolutely unhinged
|
|
// I'm so sorry
|
|
|
|
#if defined(UNICODE) || defined(__GNUC__)
|
|
auto *leaf = "\u251C", *last = "\u2514", *line = "\u2500";
|
|
#else
|
|
auto *leaf = "\xC3", *last = "\xC0", *line = "\xC4";
|
|
#endif
|
|
|
|
fmt::print("Listing known products and flavours: \n\n");
|
|
|
|
fmt::print("* The following product list uses this style of formatting:\n");
|
|
fmt::print("{}: {} \n", fmt::styled("PRODUCT", fmt::emphasis::bold), "Product name");
|
|
fmt::print("{}{}{} {}: {} \n", last, line, line, "FLAVOUR", "Flavour name");
|
|
fmt::print("* Products that require a flavour are noted with {}\n\n",
|
|
fmt::styled("(no default)", fmt::emphasis::bold));
|
|
|
|
for (auto const &i : keys["products"].items())
|
|
{
|
|
auto el = i.value();
|
|
auto containsFlavours = el.contains("flavours");
|
|
|
|
fmt::print("{:<9} {} ", fmt::styled(fmt::format("{}:", i.key()), fmt::emphasis::bold), el["name"]);
|
|
if (el.contains("BINK"))
|
|
{
|
|
fmt::print("{}\n", el["BINK"]);
|
|
}
|
|
else if (el["meta"].contains("default"))
|
|
{
|
|
fmt::print("(default: {} {})\n", fmt::styled(el["meta"]["default"], fmt::emphasis::bold),
|
|
el["flavours"][el["meta"]["default"]]["BINK"]);
|
|
}
|
|
else if (el["meta"]["type"] == "PIDGEN3")
|
|
{
|
|
fmt::print("[{}]\n", el["meta"]["type"]);
|
|
}
|
|
else
|
|
{
|
|
fmt::print("{}\n", fmt::styled("(no default)", fmt::emphasis::bold));
|
|
}
|
|
|
|
if (containsFlavours)
|
|
{
|
|
auto flavours = el["flavours"];
|
|
for (auto j = flavours.begin(); j != flavours.end(); j++)
|
|
{
|
|
auto el2 = j.value();
|
|
BOOL isLast = j == --flavours.end();
|
|
|
|
fmt::print("{}{}{} {:<9} {} ", !isLast ? leaf : last, line, line, fmt::format("{}:", j.key()),
|
|
fmt::format("{} {}", el["name"], el2["name"]));
|
|
|
|
if (el2.contains("meta") && el2["meta"].contains("type"))
|
|
{
|
|
fmt::print("[{}]\n", el2["meta"]["type"]);
|
|
}
|
|
else
|
|
{
|
|
fmt::print("{}\n", el2["BINK"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt::print("\n");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return success
|
|
*/
|
|
BOOL CLI::DisplayHelp(const std::string &)
|
|
{
|
|
options.help = true;
|
|
fmt::print("usage: {} \n", options.argv[0]);
|
|
|
|
for (BYTE i = 0; i < CLIHelpOptionID_END; i++)
|
|
{
|
|
CLIHelpOptions o = helpOptions[i];
|
|
|
|
if (o.Short.empty())
|
|
{
|
|
fmt::print("\t{:>2} --{:<15} {}", "", o.Long, o.HelpText);
|
|
}
|
|
else if (o.Long.empty())
|
|
{
|
|
fmt::print("\t-{} {:<15} {}", o.Short, "", o.HelpText);
|
|
}
|
|
else
|
|
{
|
|
fmt::print("\t-{} --{:<15} {}", o.Short, o.Long, o.HelpText);
|
|
}
|
|
|
|
if (!o.Default.empty())
|
|
{
|
|
fmt::print(" (defaults to {})", o.Default);
|
|
}
|
|
fmt::print("\n");
|
|
}
|
|
|
|
fmt::print("\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::DisplayErrorMessage(const std::string &)
|
|
{
|
|
fmt::print("Error parsing command line options\n");
|
|
DisplayHelp("");
|
|
options.error = true;
|
|
return false;
|
|
}
|
|
|
|
BOOL CLI::SetVerboseOption(const std::string &)
|
|
{
|
|
options.verbose = true;
|
|
UMSKT::setVerboseOutput(stdout);
|
|
fmt::print(UMSKT::verbose, "Enabling verbose option\n");
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetDebugOption(const std::string &)
|
|
{
|
|
options.verbose = true;
|
|
UMSKT::setDebugOutput(stdout);
|
|
fmt::print(UMSKT::debug, "Enabling debug option\n");
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetListOption(const std::string &)
|
|
{
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting list option\n");
|
|
}
|
|
options.list = true;
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetOEMOption(const std::string &)
|
|
{
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting OEM option\n");
|
|
}
|
|
options.oem = true;
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetUpgradeOption(const std::string &)
|
|
{
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting upgrade option\n");
|
|
}
|
|
options.upgrade = true;
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetFileOption(const std::string &file)
|
|
{
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting file option to: {}\n", file);
|
|
}
|
|
options.keysFilename = file;
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetNumberOption(const std::string &num)
|
|
{
|
|
auto nKeys = UMSKT::IntegerS(num);
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting generation number option to: {}\n", num);
|
|
}
|
|
|
|
options.numKeys = nKeys.ConvertToLong();
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetChannelIDOption(const std::string &channum)
|
|
{
|
|
Integer channelID = UMSKT::IntegerS(channum);
|
|
|
|
// channel ids must be between 000 and 999
|
|
if (channelID > PIDGEN::MaxChannelID)
|
|
{
|
|
fmt::print("ERROR: refusing to create a key with a Channel ID greater than 999\n");
|
|
return false;
|
|
}
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting Channel ID option to: {}\n", channelID);
|
|
}
|
|
|
|
options.channelID = channelID;
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetBINKOption(const std::string &bink)
|
|
{
|
|
auto strbinkid = std::string(bink);
|
|
options.binkID = UMSKT::strtoupper(strbinkid);
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting BINK option to: {}\n", strbinkid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetFlavourOption(const std::string &flavour)
|
|
{
|
|
auto strFlavour = UMSKT::strtoupper(flavour);
|
|
options.productFlavour = strFlavour;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting flavour option to: {}\n", strFlavour);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetSerialOption(const std::string &arg)
|
|
{
|
|
Integer Serial = UMSKT::IntegerS(arg);
|
|
|
|
// serials must be between 000000 and 999999
|
|
if (Serial > PIDGEN::MaxSerial)
|
|
{
|
|
fmt::print("ERROR: refusing to create a key with a Serial not between 000000 and 999999\n");
|
|
return false;
|
|
}
|
|
|
|
options.serial = Serial;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting serial number option to: {}\n", Serial);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetActivationIDOption(const std::string &aid)
|
|
{
|
|
options.installationID = aid;
|
|
options.state = Options::STATE_CONFIRMATION_ID;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting program state to Confirmation ID Generation\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetProductIDOption(const std::string &product)
|
|
{
|
|
options.productID = product;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting product ID option to: {}\n", product);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetValidateOption(const std::string &productID)
|
|
{
|
|
options.keyToCheck = productID;
|
|
options.state = Options::STATE_PIDGEN_VALIDATE;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting program state to PIDGEN Validation\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetProductCodeOption(const std::string &product)
|
|
{
|
|
auto strProduct = std::string(product);
|
|
options.productCode = UMSKT::strtoupper(strProduct);
|
|
options.productFlavour = "";
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting product code to: {}\n", strProduct);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
BOOL CLI::SetAuthDataOption(const std::string &authData)
|
|
{
|
|
auto strAuthData = std::string(authData);
|
|
options.authInfo = strAuthData;
|
|
|
|
if (options.verbose)
|
|
{
|
|
fmt::print("Setting authdata option to: {}\n", strAuthData);
|
|
}
|
|
|
|
return true;
|
|
}
|