clean up some CLI option handling

This commit is contained in:
Martin Michelsen
2023-01-17 21:06:22 -08:00
parent b5b7345e5f
commit a937e50681
4 changed files with 159 additions and 157 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ foreach(TestCase IN ITEMS ${TestCases})
add_test( add_test(
NAME ${TestCase} NAME ${TestCase}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log ${TestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json --require-password=password --require-access-key=111111111111) COMMAND ${CMAKE_BINARY_DIR}/newserv replay-log ${TestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json --require-password=password --require-access-key=111111111111)
endforeach() endforeach()
+16 -16
View File
@@ -136,23 +136,23 @@ For example, the GameCube version of Lost HEAT SWORD is in two files named `q058
There are multiple PSO quest formats out there; newserv supports most of them. It can also decode any known format to standard .bin/.dat format. Specifically: There are multiple PSO quest formats out there; newserv supports most of them. It can also decode any known format to standard .bin/.dat format. Specifically:
| Format | Extension | Supported online? | Offline decode option | | Format | Extension | Supported online? | Offline decode option |
|---------------------------|-----------------------|-------------------|---------------------------| |---------------------------|-----------------------|-------------------|-----------------------|
| Compressed | .bin and .dat | Yes | None (1) | | Compressed | .bin and .dat | Yes | None (1) |
| Compressed Ep3 | .bin or .mnm | Download only | None (1) | | Compressed Ep3 | .bin or .mnm | Download only | None (1) |
| Uncompressed | .bind and .datd | Yes | --compress-data (2) | | Uncompressed | .bind and .datd | Yes | compress-data (2) |
| Uncompressed Ep3 | .bind or .mnmd | Download only | --compress-data (2) | | Uncompressed Ep3 | .bind or .mnmd | Download only | compress-data (2) |
| Unencrypted GCI | .bin.gci and .dat.gci | Yes | --decode-gci=FILENAME | | Unencrypted GCI | .bin.gci and .dat.gci | Yes | decode-gci |
| Encrypted GCI with key | .bin.gci and .dat.gci | Yes | --decode-gci=FILENAME | | Encrypted GCI with key | .bin.gci and .dat.gci | Yes | decode-gci |
| Encrypted GCI without key | .bin.gci and .dat.gci | No | --decode-gci=FILENAME (3) | | Encrypted GCI without key | .bin.gci and .dat.gci | No | decode-gci (3) |
| Ep3 GCI | .bin.gci or .mnm.gci | Download only | --decode-gci=FILENAME | | Ep3 GCI | .bin.gci or .mnm.gci | Download only | decode-gci |
| Encrypted DLQ | .bin.dlq and .dat.dlq | Yes | --decode-dlq=FILENAME | | Encrypted DLQ | .bin.dlq and .dat.dlq | Yes | decode-dlq |
| Ep3 DLQ | .bin.dlq or .mnm.dlq | Download only | --decode-dlq=FILENAME | | Ep3 DLQ | .bin.dlq or .mnm.dlq | Download only | decode-dlq |
| QST | .qst | Yes | --decode-qst=FILENAME | | QST | .qst | Yes | decode-qst |
*Notes:* *Notes:*
1. *This is the default format. You can convert these to uncompressed format like this: `newserv --decompress-data < FILENAME.bin > FILENAME.bind`* 1. *This is the default format. You can convert these to uncompressed format like this: `newserv decompress-prs FILENAME.bin FILENAME.bind`*
2. *Similar to (1), to compress an uncompressed quest file: `newserv --compress-data < FILENAME.bind > FILENAME.bin`* 2. *Similar to (1), to compress an uncompressed quest file: `newserv compress-prs FILENAME.bind FILENAME.bin`*
3. *If you know the encryption seed (serial number), pass it in as a hex string with the `--seed=` option. If you don't know the encryption seed, newserv will find it for you, which will likely take a long time.* 3. *If you know the encryption seed (serial number), pass it in as a hex string with the `--seed=` option. If you don't know the encryption seed, newserv will find it for you, which will likely take a long time.*
Episode 3 download quests consist only of a .bin file - there is no corresponding .dat file. Episode 3 download quest files may be named with the .mnm extension instead of .bin, since the format is the same as the standard map files (in system/ep3/). These files can be encoded in any of the formats described above, except .qst. There are no encrypted Episode 3 GCI formats because the game doesn't encrypt quests saved to the memory card, unlike Episodes 1&2. Episode 3 download quests consist only of a .bin file - there is no corresponding .dat file. Episode 3 download quest files may be named with the .mnm extension instead of .bin, since the format is the same as the standard map files (in system/ep3/). These files can be encoded in any of the formats described above, except .qst. There are no encrypted Episode 3 GCI formats because the game doesn't encrypt quests saved to the memory card, unlike Episodes 1&2.
@@ -312,7 +312,7 @@ For GC clients, you'll have to use newserv's built-in DNS server or set up your
### Non-server usage ### Non-server usage
newserv has many CLI options, which can be used to access functionality other than the game/proxy server. Run `newserv --help` to see these options and how to use them. The non-server things newserv can do are: newserv has many CLI options, which can be used to access functionality other than the game/proxy server. Run `newserv help` to see these options and how to use them. The non-server things newserv can do are:
* Compress or decompress data in the PRS and BC0 formats * Compress or decompress data in the PRS and BC0 formats
* Compute the decompressed size of compressed PRS data without decompressing it * Compute the decompressed size of compressed PRS data without decompressing it
+3 -3
View File
@@ -82,9 +82,9 @@ void PRSCompressor::advance() {
// - As a long copy if offset in [-0x1FFF, -1] and size in [3, 9] // - As a long copy if offset in [-0x1FFF, -1] and size in [3, 9]
// - As an extended copy if offset in [-0x1FFF, -1] and size in [1, 0x100] // - As an extended copy if offset in [-0x1FFF, -1] and size in [1, 0x100]
// Because an extended copy costs two control bits and three data bytes, // Because an extended copy costs two control bits and three data bytes,
// it's not worth it to use an extended copy for sizes 1 and 2. In those // it's not worth it to use an extended copy for sizes 1 and 2 (and 3, but
// cases, if a short copy can't reach back far enough, we just write a // that case is always done via a long copy instead). In cases 1 and 2, if a
// literal instead. // short copy can't reach back far enough, we just write literal(s) instead.
ssize_t backreference_offset = best_match_offset - this->compression_offset; ssize_t backreference_offset = best_match_offset - this->compression_offset;
if ((backreference_offset >= -0x100) && (best_match_size <= 5)) { if ((backreference_offset >= -0x100) && (best_match_size <= 5)) {
+139 -137
View File
@@ -271,101 +271,98 @@ void drop_privileges(const string& username) {
void print_usage() { void print_usage() {
fputs("\ fputs("\
newserv - a Phantasy Star Online Swiss Army knife\n\
\n\
Usage:\n\ Usage:\n\
newserv [options] [input-filename [output-filename]]\n\ newserv [ACTION [OPTIONS...]]\n\
\n\ \n\
With no options, newserv runs in server mode. PSO clients can connect normally,\n\ If ACTION is not specified, newserv runs in server mode. PSO clients can\n\
join lobbies, play games, and use the proxy server. See README.md and\n\ connect normally, join lobbies, play games, and use the proxy server. See\n\
system/config.json for more information.\n\ README.md and system/config.json for more information.\n\
\n\ \n\
When options are given, newserv will do things other than running the server.\n\ When ACTION is given, newserv will do things other than running the server.\n\
\n\ \n\
Some modes accept input and/or output filenames; see the descriptions below for\n\ Some actions accept input and/or output filenames; see the descriptions below\n\
details. If input-filename is missing or is '-', newserv reads from stdin. If\n\ for details. If INPUT-FILENAME is missing or is '-', newserv reads from stdin.\n\
output-filename is missing and the input is not from stdin, newserv writes the\n\ If OUTPUT-FILENAME is missing and the input is not from stdin, newserv writes\n\
output to <input-filename>.dec; if output-filename is '-', newserv writes the\n\ the output to INPUT-FILENAME.dec; if OUTPUT-FILENAME is '-', newserv writes the\n\
output to stdout. If stdout is a terminal, data written there is formatted in a\n\ output to stdout. If stdout is a terminal, data written there is formatted in a\n\
hex/ASCII view; otherwise, raw (binary) data is written there.\n\ hex/ASCII view; otherwise, raw (binary) data is written there.\n\
\n\ \n\
The options are:\n\ The actions are:\n\
--compress-prs\n\ help\n\
--decompress-prs\n\ You\'re reading it now.\n\
--compress-bc0\n\ compress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
--decompress-bc0\n\ decompress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
Compress or decompress data using the PRS or BC0 algorithms.\n\ compress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
--prs-size\n\ decompress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
Compute the decompressed size of the PRS-compressed input data, but don\'t\n\ Compress or decompress data using the PRS or BC0 algorithms.\n\
write the decompressed data anywhere.\n\ prs-size [INPUT-FILENAME]\n\
--encrypt-data\n\ Compute the decompressed size of the PRS-compressed input data, but don\'t\n\
--decrypt-data\n\ write the decompressed data anywhere.\n\
Encrypt or decrypt data using PSO\'s standard network protocol encryption.\n\ encrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\
By default, PSO V3 (GameCube/XBOX) encryption is used, but this can be\n\ decrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\
overridden with the --pc or --bb options. The --seed= option specifies\n\ Encrypt or decrypt data using PSO\'s standard network protocol encryption.\n\
the encryption seed (4 hex bytes for PC or GC, or 48 hex bytes for BB).\n\ By default, PSO V3 (GameCube/XBOX) encryption is used, but this can be\n\
For BB, the --key option is required as well, and refers to a .nsk file\n\ overridden with the --pc or --bb options. The --seed=SEED option specifies\n\
in system/blueburst/keys (without the directory or .nsk extension). For\n\ the encryption seed (4 hex bytes for PC or GC, or 48 hex bytes for BB). For\n\
non-BB ciphers, the --big-endian option applies the cipher masks as\n\ BB, the --key=KEY-NAME option is required as well, and refers to a .nsk\n\
big-endian instead of little-endian, which is necessary for some GameCube\n\ file in system/blueburst/keys (without the directory or .nsk extension).\n\
file formats.\n\ For non-BB ciphers, the --big-endian option applies the cipher masks as\n\
--decrypt-trivial-data\n\ big-endian instead of little-endian, which is necessary for some GameCube\n\
Decrypt (or encrypt; the algorithm is symmetric) data using the Episode\n\ file formats.\n\
3 trivial algorithm. --seed should be specified as one hex byte. If\n\ decrypt-trivial-data [--seed=SEED] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
--seed is not given, newserv will truy all possible seeds and return the\n\ Decrypt (or encrypt; the algorithm is symmetric) data using the Episode 3\n\
one that results in the greatest number of zero bytes in the output.\n\ trivial algorithm. If SEED is given, it should be specified as one hex\n\
--find-decryption-seed\n\ byte. If SEED is not given, newserv will try all possible seeds and return\n\
Perform a brute-force search for a decryption seed of the given data.\n\ the one that results in the greatest number of zero bytes in the output.\n\
The ciphertext is specified with the --encrypted= option and the expected\n\ find-decryption-seed <OPTIONS...>\n\
plaintext is specified with the --decrypted= option. The plaintext may\n\ Perform a brute-force search for a decryption seed of the given data. The\n\
include unmatched bytes (specified with the Phosg parse_data_string ?\n\ ciphertext is specified with the --encrypted=DATA option and the expected\n\
operator), but overall it must be the same length as the ciphertext. By\n\ plaintext is specified with the --decrypted=DATA option. The plaintext may\n\
default, this option uses PSO V3 encryption, but this can be overridden\n\ include unmatched bytes (specified with the Phosg parse_data_string ?\n\
with --pc. (BB encryption seeds are too long to be searched for with this\n\ operator), but overall it must be the same length as the ciphertext. By\n\
function.) By default, the number of worker threads is equal the the\n\ default, this option uses PSO V3 encryption, but this can be overridden\n\
number of CPU cores in the system, but this can be overridden with the\n\ with --pc. (BB encryption seeds are too long to be searched for with this\n\
--threads= option.\n\ function.) By default, the number of worker threads is equal the the number\n\
--decode-sjis\n\ of CPU cores in the system, but this can be overridden with the\n\
Apply newserv\'s text decoding algorithm to the data on stdin, producing\n\ --threads=NUM-THREADS option.\n\
little-endian UTF-16 data on stdout. Both input-filename and\n\ decode-sjis [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
output-filename may be specified.\n\ Apply newserv\'s text decoding algorithm to the input data, producing\n\
--decode-gci\n\ little-endian UTF-16 output data.\n\
--decode-dlq\n\ decode-gci INPUT-FILENAME [OPTIONS...]]\n\
--decode-qst\n\ decode-dlq INPUT-FILENAME\n\
Decode the input quest file into a compressed, unencrypted .bin or .dat\n\ decode-qst INPUT-FILENAME\n\
file (or in the case of --decode-qst, both a .bin and a .dat file).\n\ Decode the input quest file into a compressed, unencrypted .bin or .dat\n\
input-filename must be specified, but output-filename must not be; the\n\ file (or in the case of decode-qst, both a .bin and a .dat file).\n\
output is written to <input-filename>.dec (or .bin, or .dat). DLQ and QST\n\ INPUT-FILENAME must be specified, but there is no OUTPUT-FILENAME; the\n\
decoding is a relatively simple operation, but GCI decoding can be\n\ output is written to INPUT-FILENAME.dec (or .bin, or .dat). DLQ and QST\n\
computationally expensive if the file is encrypted and doesn\'t contain an\n\ decoding is a relatively simple operation, but GCI decoding can be\n\
embedded seed. If you know the player\'s serial number who generated the\n\ computationally expensive if the file is encrypted and doesn\'t contain an\n\
GCI file, use the --seed= option and give the serial number (as a\n\ embedded seed. If you know the player\'s serial number who generated the\n\
hex-encoded 32-bit integer). If you don\'t know the serial number, newserv\n\ GCI file, use the --seed=SEED option and give the serial number (as a\n\
will find it via a brute-force search, but this will take a long time.\n\ hex-encoded 32-bit integer). If you don\'t know the serial number, newserv\n\
--cat-client=ADDR:PORT\n\ will find it via a brute-force search, but this will take a long time.\n\
Connect to the given server and simulate a PSO client. newserv will then\n\ cat-client ADDR:PORT\n\
print all the received commands to stdout, and forward any commands typed\n\ Connect to the given server and simulate a PSO client. newserv will then\n\
into stdin to the remote server. It is assumed that the input and output\n\ print all the received commands to stdout, and forward any commands typed\n\
are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\ into stdin to the remote server. It is assumed that the input and output\n\
--gc, and --bb options can be used to select the command format and\n\ are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\
encryption. If --bb is used, the --key option is also required (as in\n\ --gc, and --bb options can be used to select the command format and\n\
--decrypt-data above).\n\ encryption. If --bb is used, the --key=KEY-NAME option is also required (as\n\
--show-ep3-data\n\ in decrypt-data above).\n\
Print the Episode 3 maps and card definitions from the system/ep3\n\ show-ep3-data\n\
directory in a (sort of) human-readable format.\n\ Print the Episode 3 maps and card definitions from the system/ep3 directory\n\
--show-ep3-card=ID\n\ in a (sort of) human-readable format.\n\
Describe the Episode 3 card definition with the given ID (hex).\n\ replay-log [INPUT-FILENAME] [OPTIONS...]\n\
--replay-log\n\ Replay a terminal log as if it were a client session. input-filename may be\n\
Replay a terminal log as if it were a client session. input-filename may\n\ specified for this option. This is used for regression testing, to make\n\
be specified for this option. This is used for regression testing, to\n\ sure client sessions are repeatable and code changes don\'t affect existing\n\
make sure client sessions are repeatable and code changes don\'t affect\n\ (working) functionality.\n\
existing (working) functionality.\n\ extract-gsl [INPUT-FILENAME] [--big-endian]\n\
--extract-gsl\n\ Extract all files from a GSL archive into the current directory.\n\
Extract all files from a GSL archive into the current directory.\n\ input-filename may be specified. If output-filename is specified, then it\n\
input-filename may be specified. If output-filename is specified, then it\n\ is treated as a prefix which is prepended to the filename of each file\n\
is treated as a prefix which is prepended to the filename of each file\n\ contained in the GSL archive. If --big-endian is given, the GSL header is\n\
contained in the GSL archive. If --big-endian is given, the GSL header is\n\ read in GameCube format; otherwise it is read in PC/BB format.\n\
read in GameCube format; otherwise it is read in PC/BB format.\n\
\n\ \n\
A few options apply to multiple modes described above:\n\ A few options apply to multiple modes described above:\n\
--parse-data\n\ --parse-data\n\
@@ -449,43 +446,10 @@ int main(int argc, char** argv) {
const char* replay_required_password = ""; const char* replay_required_password = "";
uint32_t root_object_address = 0; uint32_t root_object_address = 0;
uint16_t ep3_card_id = 0xFFFF; uint16_t ep3_card_id = 0xFFFF;
struct sockaddr_storage cat_client_remote;
for (int x = 1; x < argc; x++) { for (int x = 1; x < argc; x++) {
if (!strcmp(argv[x], "--help")) { if (!strcmp(argv[x], "--help")) {
print_usage(); print_usage();
return 0; return 0;
} else if (!strcmp(argv[x], "--compress-prs")) {
behavior = Behavior::COMPRESS_PRS;
} else if (!strcmp(argv[x], "--decompress-prs")) {
behavior = Behavior::DECOMPRESS_PRS;
} else if (!strcmp(argv[x], "--compress-bc0")) {
behavior = Behavior::COMPRESS_BC0;
} else if (!strcmp(argv[x], "--decompress-bc0")) {
behavior = Behavior::DECOMPRESS_BC0;
} else if (!strcmp(argv[x], "--prs-size")) {
behavior = Behavior::PRS_SIZE;
} else if (!strcmp(argv[x], "--encrypt-data")) {
behavior = Behavior::ENCRYPT_DATA;
} else if (!strcmp(argv[x], "--decrypt-data")) {
behavior = Behavior::DECRYPT_DATA;
} else if (!strcmp(argv[x], "--decrypt-trivial-data")) {
behavior = Behavior::DECRYPT_TRIVIAL_DATA;
} else if (!strcmp(argv[x], "--find-decryption-seed")) {
behavior = Behavior::FIND_DECRYPTION_SEED;
} else if (!strcmp(argv[x], "--decode-sjis")) {
behavior = Behavior::DECODE_SJIS;
} else if (!strcmp(argv[x], "--decode-gci")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::GCI;
} else if (!strcmp(argv[x], "--decode-dlq")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::DLQ;
} else if (!strcmp(argv[x], "--decode-qst")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::QST;
} else if (!strncmp(argv[x], "--cat-client=", 13)) {
behavior = Behavior::CAT_CLIENT;
cat_client_remote = make_sockaddr_storage(parse_netloc(&argv[x][13])).first;
} else if (!strncmp(argv[x], "--threads=", 10)) { } else if (!strncmp(argv[x], "--threads=", 10)) {
num_threads = strtoull(&argv[x][10], nullptr, 0); num_threads = strtoull(&argv[x][10], nullptr, 0);
} else if (!strcmp(argv[x], "--patch")) { } else if (!strcmp(argv[x], "--patch")) {
@@ -516,17 +480,6 @@ int main(int argc, char** argv) {
skip_little_endian = true; skip_little_endian = true;
} else if (!strcmp(argv[x], "--skip-big-endian")) { } else if (!strcmp(argv[x], "--skip-big-endian")) {
skip_big_endian = true; skip_big_endian = true;
} else if (!strcmp(argv[x], "--show-ep3-data")) {
behavior = Behavior::SHOW_EP3_DATA;
} else if (!strncmp(argv[x], "--show-ep3-card=", 16)) {
behavior = Behavior::SHOW_EP3_DATA;
ep3_card_id = strtoul(&argv[x][16], nullptr, 16);
} else if (!strcmp(argv[x], "--parse-object-graph")) {
behavior = Behavior::PARSE_OBJECT_GRAPH;
} else if (!strcmp(argv[x], "--replay-log")) {
behavior = Behavior::REPLAY_LOG;
} else if (!strcmp(argv[x], "--extract-gsl")) {
behavior = Behavior::EXTRACT_GSL;
} else if (!strncmp(argv[x], "--require-password=", 19)) { } else if (!strncmp(argv[x], "--require-password=", 19)) {
replay_required_password = &argv[x][19]; replay_required_password = &argv[x][19];
} else if (!strncmp(argv[x], "--require-access-key=", 21)) { } else if (!strncmp(argv[x], "--require-access-key=", 21)) {
@@ -535,16 +488,64 @@ int main(int argc, char** argv) {
root_object_address = strtoul(&argv[x][12], nullptr, 16); root_object_address = strtoul(&argv[x][12], nullptr, 16);
} else if (!strncmp(argv[x], "--config=", 9)) { } else if (!strncmp(argv[x], "--config=", 9)) {
config_filename = &argv[x][9]; config_filename = &argv[x][9];
} else if (!strcmp(argv[x], "-") || argv[x][0] != '-') { } else if (!strcmp(argv[x], "-") || argv[x][0] != '-') {
if (!input_filename && behavior_takes_input_filename(behavior)) { if (behavior == Behavior::RUN_SERVER) {
if (!strcmp(argv[x], "help")) {
print_usage();
return 0;
} if (!strcmp(argv[x], "compress-prs")) {
behavior = Behavior::COMPRESS_PRS;
} else if (!strcmp(argv[x], "decompress-prs")) {
behavior = Behavior::DECOMPRESS_PRS;
} else if (!strcmp(argv[x], "compress-bc0")) {
behavior = Behavior::COMPRESS_BC0;
} else if (!strcmp(argv[x], "decompress-bc0")) {
behavior = Behavior::DECOMPRESS_BC0;
} else if (!strcmp(argv[x], "prs-size")) {
behavior = Behavior::PRS_SIZE;
} else if (!strcmp(argv[x], "encrypt-data")) {
behavior = Behavior::ENCRYPT_DATA;
} else if (!strcmp(argv[x], "decrypt-data")) {
behavior = Behavior::DECRYPT_DATA;
} else if (!strcmp(argv[x], "decrypt-trivial-data")) {
behavior = Behavior::DECRYPT_TRIVIAL_DATA;
} else if (!strcmp(argv[x], "find-decryption-seed")) {
behavior = Behavior::FIND_DECRYPTION_SEED;
} else if (!strcmp(argv[x], "decode-sjis")) {
behavior = Behavior::DECODE_SJIS;
} else if (!strcmp(argv[x], "decode-gci")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::GCI;
} else if (!strcmp(argv[x], "decode-dlq")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::DLQ;
} else if (!strcmp(argv[x], "decode-qst")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::QST;
} else if (!strcmp(argv[x], "cat-client")) {
behavior = Behavior::CAT_CLIENT;
} else if (!strcmp(argv[x], "show-ep3-data")) {
behavior = Behavior::SHOW_EP3_DATA;
} else if (!strcmp(argv[x], "parse-object-graph")) {
behavior = Behavior::PARSE_OBJECT_GRAPH;
} else if (!strcmp(argv[x], "replay-log")) {
behavior = Behavior::REPLAY_LOG;
} else if (!strcmp(argv[x], "extract-gsl")) {
behavior = Behavior::EXTRACT_GSL;
} else {
throw invalid_argument(string_printf("unknown command: %s (try --help)", argv[x]));
}
} else if (!input_filename && behavior_takes_input_filename(behavior)) {
input_filename = argv[x]; input_filename = argv[x];
} else if (!output_filename && behavior_takes_output_filename(behavior)) { } else if (!output_filename && behavior_takes_output_filename(behavior)) {
output_filename = argv[x]; output_filename = argv[x];
} else { } else {
throw invalid_argument(string_printf("unknown option: %s", argv[x])); throw invalid_argument(string_printf("unknown option: %s (try --help)", argv[x]));
} }
} else { } else {
throw invalid_argument(string_printf("unknown option: %s", argv[x])); throw invalid_argument(string_printf("unknown option: %s (try --help)", argv[x]));
} }
} }
@@ -592,8 +593,8 @@ int main(int argc, char** argv) {
auto progress_fn = [&](size_t input_progress, size_t output_progress) -> void { auto progress_fn = [&](size_t input_progress, size_t output_progress) -> void {
float progress = static_cast<float>(input_progress * 100) / input_bytes; float progress = static_cast<float>(input_progress * 100) / input_bytes;
float size_ratio = static_cast<float>(output_progress * 100) / input_progress; float size_ratio = static_cast<float>(output_progress * 100) / input_progress;
fprintf(stderr, "... %zu (%g%%) <= %zu/%zu (%g%%) \r", fprintf(stderr, "... %zu/%zu (%g%%) => %zu (%g%%) \r",
output_progress, size_ratio, input_progress, input_bytes, progress); input_progress, input_bytes, progress, output_progress, size_ratio);
}; };
if (behavior == Behavior::COMPRESS_PRS) { if (behavior == Behavior::COMPRESS_PRS) {
@@ -843,6 +844,7 @@ int main(int argc, char** argv) {
load_object_file<PSOBBEncryption::KeyFile>("system/blueburst/keys/" + key_file_name + ".nsk"))); load_object_file<PSOBBEncryption::KeyFile>("system/blueburst/keys/" + key_file_name + ".nsk")));
} }
shared_ptr<struct event_base> base(event_base_new(), event_base_free); shared_ptr<struct event_base> base(event_base_new(), event_base_free);
auto cat_client_remote = make_sockaddr_storage(parse_netloc(input_filename)).first;
CatSession session(base, cat_client_remote, cli_version, key); CatSession session(base, cat_client_remote, cli_version, key);
event_base_dispatch(base.get()); event_base_dispatch(base.get());
break; break;