diff --git a/CMakeLists.txt b/CMakeLists.txt index c073d4c9..2d62254a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,7 @@ foreach(TestCase IN ITEMS ${TestCases}) add_test( NAME ${TestCase} 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() diff --git a/README.md b/README.md index 3727e2c0..d3ee5a28 100644 --- a/README.md +++ b/README.md @@ -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: -| Format | Extension | Supported online? | Offline decode option | -|---------------------------|-----------------------|-------------------|---------------------------| -| Compressed | .bin and .dat | Yes | None (1) | -| Compressed Ep3 | .bin or .mnm | Download only | None (1) | -| Uncompressed | .bind and .datd | Yes | --compress-data (2) | -| Uncompressed Ep3 | .bind or .mnmd | Download only | --compress-data (2) | -| Unencrypted GCI | .bin.gci and .dat.gci | Yes | --decode-gci=FILENAME | -| Encrypted GCI with key | .bin.gci and .dat.gci | Yes | --decode-gci=FILENAME | -| Encrypted GCI without key | .bin.gci and .dat.gci | No | --decode-gci=FILENAME (3) | -| Ep3 GCI | .bin.gci or .mnm.gci | Download only | --decode-gci=FILENAME | -| Encrypted DLQ | .bin.dlq and .dat.dlq | Yes | --decode-dlq=FILENAME | -| Ep3 DLQ | .bin.dlq or .mnm.dlq | Download only | --decode-dlq=FILENAME | -| QST | .qst | Yes | --decode-qst=FILENAME | +| Format | Extension | Supported online? | Offline decode option | +|---------------------------|-----------------------|-------------------|-----------------------| +| Compressed | .bin and .dat | Yes | None (1) | +| Compressed Ep3 | .bin or .mnm | Download only | None (1) | +| Uncompressed | .bind and .datd | Yes | compress-data (2) | +| Uncompressed Ep3 | .bind or .mnmd | Download only | compress-data (2) | +| Unencrypted GCI | .bin.gci and .dat.gci | Yes | decode-gci | +| Encrypted GCI with key | .bin.gci and .dat.gci | Yes | decode-gci | +| Encrypted GCI without key | .bin.gci and .dat.gci | No | decode-gci (3) | +| Ep3 GCI | .bin.gci or .mnm.gci | Download only | decode-gci | +| Encrypted DLQ | .bin.dlq and .dat.dlq | Yes | decode-dlq | +| Ep3 DLQ | .bin.dlq or .mnm.dlq | Download only | decode-dlq | +| QST | .qst | Yes | decode-qst | *Notes:* -1. *This is the default format. You can convert these to uncompressed format like this: `newserv --decompress-data < FILENAME.bin > FILENAME.bind`* -2. *Similar to (1), to compress an uncompressed quest file: `newserv --compress-data < FILENAME.bind > FILENAME.bin`* +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-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.* 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 -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 * Compute the decompressed size of compressed PRS data without decompressing it diff --git a/src/Compression.cc b/src/Compression.cc index aedd4892..045d090f 100644 --- a/src/Compression.cc +++ b/src/Compression.cc @@ -82,9 +82,9 @@ void PRSCompressor::advance() { // - 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] // 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 - // cases, if a short copy can't reach back far enough, we just write a - // literal instead. + // it's not worth it to use an extended copy for sizes 1 and 2 (and 3, but + // that case is always done via a long copy instead). In cases 1 and 2, if a + // short copy can't reach back far enough, we just write literal(s) instead. ssize_t backreference_offset = best_match_offset - this->compression_offset; if ((backreference_offset >= -0x100) && (best_match_size <= 5)) { diff --git a/src/Main.cc b/src/Main.cc index d9582d74..ae398754 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -271,101 +271,98 @@ void drop_privileges(const string& username) { void print_usage() { fputs("\ -newserv - a Phantasy Star Online Swiss Army knife\n\ -\n\ Usage:\n\ - newserv [options] [input-filename [output-filename]]\n\ + newserv [ACTION [OPTIONS...]]\n\ \n\ -With no options, newserv runs in server mode. PSO clients can connect normally,\n\ -join lobbies, play games, and use the proxy server. See README.md and\n\ -system/config.json for more information.\n\ +If ACTION is not specified, newserv runs in server mode. PSO clients can\n\ +connect normally, join lobbies, play games, and use the proxy server. See\n\ +README.md and system/config.json for more information.\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\ -Some modes accept input and/or output filenames; see the descriptions below for\n\ -details. If input-filename is missing or is '-', newserv reads from stdin. If\n\ -output-filename is missing and the input is not from stdin, newserv writes the\n\ -output to .dec; if output-filename is '-', newserv writes the\n\ +Some actions accept input and/or output filenames; see the descriptions below\n\ +for details. If INPUT-FILENAME is missing or is '-', newserv reads from stdin.\n\ +If OUTPUT-FILENAME is missing and the input is not from stdin, newserv writes\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\ hex/ASCII view; otherwise, raw (binary) data is written there.\n\ \n\ -The options are:\n\ - --compress-prs\n\ - --decompress-prs\n\ - --compress-bc0\n\ - --decompress-bc0\n\ - Compress or decompress data using the PRS or BC0 algorithms.\n\ - --prs-size\n\ - Compute the decompressed size of the PRS-compressed input data, but don\'t\n\ - write the decompressed data anywhere.\n\ - --encrypt-data\n\ - --decrypt-data\n\ - Encrypt or decrypt data using PSO\'s standard network protocol encryption.\n\ - By default, PSO V3 (GameCube/XBOX) encryption is used, but this can be\n\ - overridden with the --pc or --bb options. The --seed= option specifies\n\ - the encryption seed (4 hex bytes for PC or GC, or 48 hex bytes for BB).\n\ - For BB, the --key option is required as well, and refers to a .nsk file\n\ - in system/blueburst/keys (without the directory or .nsk extension). For\n\ - non-BB ciphers, the --big-endian option applies the cipher masks as\n\ - big-endian instead of little-endian, which is necessary for some GameCube\n\ - file formats.\n\ - --decrypt-trivial-data\n\ - Decrypt (or encrypt; the algorithm is symmetric) data using the Episode\n\ - 3 trivial algorithm. --seed should be specified as one hex byte. If\n\ - --seed is not given, newserv will truy all possible seeds and return the\n\ - one that results in the greatest number of zero bytes in the output.\n\ - --find-decryption-seed\n\ - Perform a brute-force search for a decryption seed of the given data.\n\ - The ciphertext is specified with the --encrypted= option and the expected\n\ - plaintext is specified with the --decrypted= option. The plaintext may\n\ - include unmatched bytes (specified with the Phosg parse_data_string ?\n\ - operator), but overall it must be the same length as the ciphertext. By\n\ - default, this option uses PSO V3 encryption, but this can be overridden\n\ - with --pc. (BB encryption seeds are too long to be searched for with this\n\ - function.) By default, the number of worker threads is equal the the\n\ - number of CPU cores in the system, but this can be overridden with the\n\ - --threads= option.\n\ - --decode-sjis\n\ - Apply newserv\'s text decoding algorithm to the data on stdin, producing\n\ - little-endian UTF-16 data on stdout. Both input-filename and\n\ - output-filename may be specified.\n\ - --decode-gci\n\ - --decode-dlq\n\ - --decode-qst\n\ - Decode the input quest file into a compressed, unencrypted .bin or .dat\n\ - file (or in the case of --decode-qst, both a .bin and a .dat file).\n\ - input-filename must be specified, but output-filename must not be; the\n\ - output is written to .dec (or .bin, or .dat). DLQ and QST\n\ - decoding is a relatively simple operation, but GCI decoding can be\n\ - computationally expensive if the file is encrypted and doesn\'t contain an\n\ - embedded seed. If you know the player\'s serial number who generated the\n\ - GCI file, use the --seed= option and give the serial number (as a\n\ - hex-encoded 32-bit integer). If you don\'t know the serial number, newserv\n\ - will find it via a brute-force search, but this will take a long time.\n\ - --cat-client=ADDR:PORT\n\ - Connect to the given server and simulate a PSO client. newserv will then\n\ - print all the received commands to stdout, and forward any commands typed\n\ - into stdin to the remote server. It is assumed that the input and output\n\ - are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\ - --gc, and --bb options can be used to select the command format and\n\ - encryption. If --bb is used, the --key option is also required (as in\n\ - --decrypt-data above).\n\ - --show-ep3-data\n\ - Print the Episode 3 maps and card definitions from the system/ep3\n\ - directory in a (sort of) human-readable format.\n\ - --show-ep3-card=ID\n\ - Describe the Episode 3 card definition with the given ID (hex).\n\ - --replay-log\n\ - Replay a terminal log as if it were a client session. input-filename may\n\ - be specified for this option. This is used for regression testing, to\n\ - make sure client sessions are repeatable and code changes don\'t affect\n\ - existing (working) functionality.\n\ - --extract-gsl\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\ - 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\ - read in GameCube format; otherwise it is read in PC/BB format.\n\ +The actions are:\n\ + help\n\ + You\'re reading it now.\n\ + compress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + decompress-prs [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + compress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + decompress-bc0 [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Compress or decompress data using the PRS or BC0 algorithms.\n\ + prs-size [INPUT-FILENAME]\n\ + Compute the decompressed size of the PRS-compressed input data, but don\'t\n\ + write the decompressed data anywhere.\n\ + encrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\ + decrypt-data [INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS...]]\n\ + Encrypt or decrypt data using PSO\'s standard network protocol encryption.\n\ + By default, PSO V3 (GameCube/XBOX) encryption is used, but this can be\n\ + overridden with the --pc or --bb options. The --seed=SEED option specifies\n\ + the encryption seed (4 hex bytes for PC or GC, or 48 hex bytes for BB). For\n\ + BB, the --key=KEY-NAME option is required as well, and refers to a .nsk\n\ + file in system/blueburst/keys (without the directory or .nsk extension).\n\ + For non-BB ciphers, the --big-endian option applies the cipher masks as\n\ + big-endian instead of little-endian, which is necessary for some GameCube\n\ + file formats.\n\ + decrypt-trivial-data [--seed=SEED] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Decrypt (or encrypt; the algorithm is symmetric) data using the Episode 3\n\ + trivial algorithm. If SEED is given, it should be specified as one hex\n\ + byte. If SEED is not given, newserv will try all possible seeds and return\n\ + the one that results in the greatest number of zero bytes in the output.\n\ + find-decryption-seed \n\ + Perform a brute-force search for a decryption seed of the given data. The\n\ + ciphertext is specified with the --encrypted=DATA option and the expected\n\ + plaintext is specified with the --decrypted=DATA option. The plaintext may\n\ + include unmatched bytes (specified with the Phosg parse_data_string ?\n\ + operator), but overall it must be the same length as the ciphertext. By\n\ + default, this option uses PSO V3 encryption, but this can be overridden\n\ + with --pc. (BB encryption seeds are too long to be searched for with this\n\ + function.) By default, the number of worker threads is equal the the number\n\ + of CPU cores in the system, but this can be overridden with the\n\ + --threads=NUM-THREADS option.\n\ + decode-sjis [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ + Apply newserv\'s text decoding algorithm to the input data, producing\n\ + little-endian UTF-16 output data.\n\ + decode-gci INPUT-FILENAME [OPTIONS...]]\n\ + decode-dlq INPUT-FILENAME\n\ + decode-qst INPUT-FILENAME\n\ + Decode the input quest file into a compressed, unencrypted .bin or .dat\n\ + file (or in the case of decode-qst, both a .bin and a .dat file).\n\ + INPUT-FILENAME must be specified, but there is no OUTPUT-FILENAME; the\n\ + output is written to INPUT-FILENAME.dec (or .bin, or .dat). DLQ and QST\n\ + decoding is a relatively simple operation, but GCI decoding can be\n\ + computationally expensive if the file is encrypted and doesn\'t contain an\n\ + embedded seed. If you know the player\'s serial number who generated the\n\ + GCI file, use the --seed=SEED option and give the serial number (as a\n\ + hex-encoded 32-bit integer). If you don\'t know the serial number, newserv\n\ + will find it via a brute-force search, but this will take a long time.\n\ + cat-client ADDR:PORT\n\ + Connect to the given server and simulate a PSO client. newserv will then\n\ + print all the received commands to stdout, and forward any commands typed\n\ + into stdin to the remote server. It is assumed that the input and output\n\ + are terminals, so all commands are hex-encoded. The --patch, --dc, --pc,\n\ + --gc, and --bb options can be used to select the command format and\n\ + encryption. If --bb is used, the --key=KEY-NAME option is also required (as\n\ + in decrypt-data above).\n\ + show-ep3-data\n\ + Print the Episode 3 maps and card definitions from the system/ep3 directory\n\ + in a (sort of) human-readable format.\n\ + replay-log [INPUT-FILENAME] [OPTIONS...]\n\ + Replay a terminal log as if it were a client session. input-filename may be\n\ + specified for this option. This is used for regression testing, to make\n\ + sure client sessions are repeatable and code changes don\'t affect existing\n\ + (working) functionality.\n\ + extract-gsl [INPUT-FILENAME] [--big-endian]\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\ + 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\ + read in GameCube format; otherwise it is read in PC/BB format.\n\ \n\ A few options apply to multiple modes described above:\n\ --parse-data\n\ @@ -449,43 +446,10 @@ int main(int argc, char** argv) { const char* replay_required_password = ""; uint32_t root_object_address = 0; uint16_t ep3_card_id = 0xFFFF; - struct sockaddr_storage cat_client_remote; for (int x = 1; x < argc; x++) { if (!strcmp(argv[x], "--help")) { print_usage(); 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)) { num_threads = strtoull(&argv[x][10], nullptr, 0); } else if (!strcmp(argv[x], "--patch")) { @@ -516,17 +480,6 @@ int main(int argc, char** argv) { skip_little_endian = true; } else if (!strcmp(argv[x], "--skip-big-endian")) { 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)) { replay_required_password = &argv[x][19]; } 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); } else if (!strncmp(argv[x], "--config=", 9)) { config_filename = &argv[x][9]; + } 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]; } else if (!output_filename && behavior_takes_output_filename(behavior)) { output_filename = argv[x]; } else { - throw invalid_argument(string_printf("unknown option: %s", argv[x])); + throw invalid_argument(string_printf("unknown option: %s (try --help)", argv[x])); } + } 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 { float progress = static_cast(input_progress * 100) / input_bytes; float size_ratio = static_cast(output_progress * 100) / input_progress; - fprintf(stderr, "... %zu (%g%%) <= %zu/%zu (%g%%) \r", - output_progress, size_ratio, input_progress, input_bytes, progress); + fprintf(stderr, "... %zu/%zu (%g%%) => %zu (%g%%) \r", + input_progress, input_bytes, progress, output_progress, size_ratio); }; if (behavior == Behavior::COMPRESS_PRS) { @@ -843,6 +844,7 @@ int main(int argc, char** argv) { load_object_file("system/blueburst/keys/" + key_file_name + ".nsk"))); } shared_ptr 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); event_base_dispatch(base.get()); break;