Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f99b3b1c8 | |||
| da9765f1aa | |||
| b7897cddf2 | |||
| ce2300b116 | |||
| cb05dce764 | |||
| a762c0f8f8 | |||
| cd008ab0ba | |||
| 53b36d7074 | |||
| 5a1880bd65 | |||
| 8e280a1464 | |||
| 0bcdd9997e | |||
| d5351c4580 | |||
| 76bc2385ca | |||
| 325f7c6efc | |||
| 93d97d3e5b | |||
| 66b64603a0 | |||
| 7405eaea0b | |||
| 477e433361 | |||
| 7ca2012bc4 | |||
| dace165ef2 | |||
| f6df2b5b45 | |||
| 1a310df17e | |||
| 31edec701b | |||
| dc36d2ae8d | |||
| 4e733b0dc6 | |||
| 6eadaaca66 | |||
| d778340999 | |||
| e2d76f77be | |||
| 0b80af3f41 | |||
| f65acda803 | |||
| 53f485b8f2 | |||
| 69f40f9157 | |||
| 84bb946e05 | |||
| eb132f38d2 | |||
| 0f1fbb1069 | |||
| c9f7ca2259 | |||
| 8594e5af3c | |||
| 6b5e657630 | |||
| a7845e4b0e |
@@ -385,7 +385,7 @@ There are many options available when starting a proxy session. All options are
|
||||
* **Block pings**: blocks automatic pings sent by the client, and responds to ping commands from the server automatically. This works around a bug in Sylverant's login server.
|
||||
* **Infinite HP**: automatically heals you whenever you get hit. An attack that kills you in one hit will still kill you, however.
|
||||
* **Infinite TP**: automatically restores your TP whenever you use any technique.
|
||||
* **Switch assist**: attempts to unlock doors that require two players in a one-player game.
|
||||
* **Switch assist**: attempts to unlock doors that require two or four players in a one-player game.
|
||||
* **Infinite Meseta** (Episode 3 only): gives you 1,000,000 Meseta, regardless of the value sent by the remote server.
|
||||
* **Block events**: disables holiday events sent by the remote server.
|
||||
* **Block patches**: prevents any B2 (patch) commands from reaching the client.
|
||||
@@ -439,7 +439,8 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$qsyncall <reg-num> <value>`: Set a quest register's value for everyone in the game. `<reg-num>` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `<value>` is parsed as a floating-point value instead of as an integer.
|
||||
* `$gc` (game server only): Send your own Guild Card to yourself.
|
||||
* `$sc <data>`: Send a command to yourself.
|
||||
* `$ss <data>` (proxy server only): Send a command to the remote server.
|
||||
* `$ss <data>`: Send a command to the remote server (if in a proxy session) or to the game server.
|
||||
* `$sb <data>`: Send a command to yourself, and to the remote server or game server.
|
||||
* `$meseta <amount>` (game server only; Episode 3 only): Add the given amount to your Meseta total.
|
||||
* `$auction` (Episode 3 only): Bring up the CARD Auction menu, regardless of how many players are in the game or if you have a VIP card.
|
||||
* `$ep3battledebug` (game server only; Episode 3 only): Enable or disable TCard00_Select. If enabled, the game will enter the debug menu when you start a battle.
|
||||
@@ -449,7 +450,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$secid <section-id>`: Sets your override section ID. After running this command, any games you create will use your override section ID for rare drops instead of your character's actual section ID. If you're in a game and you are the leader of the game, this also immediately changes the item tables used by the server when creating items. To revert to your actual section id, run `$secid` with no name after it. On the proxy server, this will not work if the remote server controls item drops (e.g. on BB, or on Schtserv with server drops enabled). If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing.
|
||||
* `$rand <seed>`: Sets your override random seed (specified as a 32-bit hex value). This will make any games you create use the given seed for rare enemies. This also makes item drops deterministic in Blue Burst games hosted by newserv. On the proxy server, this command can cause desyncs with other players in the same game, since they will not see the overridden random seed. To remove the override, run `$rand` with no arguments. If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing.
|
||||
* `$ln [name-or-type]`: Sets the lobby number. Visible only to you. This command exists because some non-lobby maps can be loaded as lobbies with invalid lobby numbers. See the "GC lobby types" and "Ep3 lobby types" entries in the information menu for acceptable values here. Note that non-lobby maps do not have a lobby counter, so there's no way to exit the lobby without using either `$ln` again or `$exit`. On the game server, `$ln` reloads the lobby immediately; on the proxy server, it doesn't take effect until you load another lobby yourself (which means you'll like have to use `$exit` to escape). Run this command with no argument to return to the default lobby.
|
||||
* `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player doors in non-quest games if you step on both switches sequentially.
|
||||
* `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player and four-player doors in non-quest games if you step on all the required switches sequentially.
|
||||
* `$exit`: If you're in a lobby, sends you to the main menu (which ends your proxy session, if you're in one). If you're in a game or spectator team, sends you to the lobby (but does not end your proxy session if you're in one). Does nothing if you're in a non-Episode 3 game and no quest is in progress.
|
||||
* `$patch <name>`: Run a patch on your client. `<name>` must exactly match the name of a patch on the server.
|
||||
|
||||
@@ -468,7 +469,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$minlevel <level>`: Sets the minimum level for players to join the current game.
|
||||
* `$password <password>`: Sets the game's join password. To unlock the game, run `$password` with nothing after it.
|
||||
* `$dropmode [mode]`: Changes the way item drops behave in the current game. `mode` can be `none`, `client`, `shared`, `private`, or `duplicate`. If `mode` is not given, tells you the current drop mode without changing it. See the "Item tables and drop modes" section for more information.
|
||||
* `$persist`: Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The state of enemies on the map will be reset when the last player leaves, but dropped items will not be deleted. If the game is empty for too long (15 minutes by default), it is then deleted.
|
||||
* `$persist`: Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The states of enemies, objects, and switches will be saved, and items left on the floor will not be deleted (except items only visible to the leaving player). If the game is empty for too long (15 minutes by default), it is then deleted. There is an edge case with persistence: if the player defeats a boss, leaves the room, joins again, and returns to the boss arena, neither the boss nor the exit warp will spawn, so they will be stuck there and have to use $warp or Quit Game to get out. For this reason, `$warp 0` is allowed in boss arenas once the boss is defeated, even if cheat mode is disabled.
|
||||
|
||||
* Episode 3 commands (game server only)
|
||||
* `$spec`: Toggles the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they will be sent back to the lobby.
|
||||
|
||||
@@ -1231,3 +1231,22 @@ F95D --------:???????? --------:???????? 006B93FC:???????? none
|
||||
F95E --------:???????? --------:???????? 006B941C:???????? LLL
|
||||
F95F --------:???????? --------:???????? 006B9104:???????? LLLLL
|
||||
F960 --------:???????? --------:???????? 006B915C:???????? L
|
||||
|
||||
Event action stream opcodes
|
||||
DC-NTE-------------- DCv1---------------- GC12US11------------ BB------------------
|
||||
00 => 8C14A304-?? 8C14A41C 8C165BA8-?? 8C165CC0 8020784C-() 80207760 0080CC8C-?? 0061CDB0 nop
|
||||
01 => 8C14A304-?? 8C14A420 8C165BA8-?? 8C165CC4 8020784C-() 80207754 0080CC8C-?? 0080CD40 stop
|
||||
02 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
03 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
04 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
05 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
06 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
07 => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
08 => 8C14A364-?? 8C14A42C 8C165C08-?? 8C165CD0 802077AC-WW 80207734 0080CCB4-?? 0080CD4C construct_object section, group
|
||||
09 => 8C14A364-?? 8C14A438 8C165C08-?? 8C165CDC 802077AC-WW 80207714 0080CCB4-?? 0080CD64 construct_enemy section, wave_number
|
||||
0A => 8C14A318-?? 8C14A444 8C165BBC-?? 8C165CE8 80207804-W 80207694 0080CC9C-?? 0080CD7C send_room_unlock? id // sends 6x05 with flags=1 or flags=3
|
||||
0B => 8C14A318-?? 8C14A4A0 8C165BBC-?? 8C165D44 80207804-W 80207634 0080CC9C-?? 0080CDDC send_room_unlock? id // sends 6x05 with flags=0
|
||||
0C => 8C14A3D4-?? 8C14A4E8 8C165C78-?? 8C165D8C 80207764-L 802075C8 0080CD00-?? 0080CE24 trigger_event event_id // sends 6x67
|
||||
0D => 8C14A364-?? 8C14A53C 8C165C08-?? 8C165DE0 802077AC-WW 802075A0 0080CCB4-?? 0080CE74 construct_enemy_stop section, wave_number // equivalent to construct_enemy then stop
|
||||
0E => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
0F => ----------- -------- ----------- -------- ----------- -------- ----------- --------
|
||||
|
||||
+56
-37
@@ -65,6 +65,12 @@ static void check_is_ep3(shared_ptr<Client> c, bool is_ep3) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_debug_enabled(shared_ptr<Client> c) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
throw precondition_failed("$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && !c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) {
|
||||
throw precondition_failed("$C6This command can\nonly be used in\ncheat mode.");
|
||||
@@ -290,10 +296,7 @@ static void server_command_debug(shared_ptr<Client> c, const std::string&) {
|
||||
}
|
||||
|
||||
static void server_command_quest(shared_ptr<Client> c, const std::string& args) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
|
||||
Version effective_version = is_ep3(c->version()) ? Version::GC_V3 : c->version();
|
||||
|
||||
@@ -330,10 +333,7 @@ static void server_command_qcheck(shared_ptr<Client> c, const std::string& args)
|
||||
}
|
||||
|
||||
static void server_command_qset_qclear(shared_ptr<Client> c, const std::string& args, bool should_set) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
|
||||
@@ -419,10 +419,7 @@ static void server_command_qgwrite(shared_ptr<Client> c, const std::string& args
|
||||
send_text_message(c, "$C6This command can\nonly be used on BB");
|
||||
return;
|
||||
}
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
|
||||
@@ -449,10 +446,7 @@ static void server_command_qgwrite(shared_ptr<Client> c, const std::string& args
|
||||
}
|
||||
|
||||
static void server_command_qsync_qsyncall(shared_ptr<Client> c, const std::string& args, bool send_to_lobby) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
|
||||
@@ -531,10 +525,7 @@ static void proxy_command_qsyncall(shared_ptr<ProxyServer::LinkedSession> ses, c
|
||||
}
|
||||
|
||||
static void server_command_qcall(shared_ptr<Client> c, const std::string& args) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game() && l->quest) {
|
||||
@@ -597,8 +588,7 @@ static void server_command_patch(shared_ptr<Client> c, const std::string& args)
|
||||
auto s = c->require_server_state();
|
||||
// Note: We can't look this up outside of the closure because
|
||||
// c->specific_version can change during prepare_client_for_patches
|
||||
auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at(
|
||||
string_printf("%s-%08" PRIX32, args.c_str(), c->config.specific_version));
|
||||
auto fn = s->function_code_index->get_patch(args, c->config.specific_version);
|
||||
send_function_call(c, fn);
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -617,8 +607,7 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
|
||||
ses->log.info("Version detected as %08" PRIX32, ses->config.specific_version);
|
||||
}
|
||||
auto s = ses->require_server_state();
|
||||
auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at(
|
||||
string_printf("%s-%08" PRIX32, args.c_str(), ses->config.specific_version));
|
||||
auto fn = s->function_code_index->get_patch(args, ses->config.specific_version);
|
||||
send_function_call(ses->client_channel, ses->config, fn);
|
||||
// Don't forward the patch response to the server
|
||||
ses->function_call_return_handler_queue.emplace_back(empty_patch_return_handler);
|
||||
@@ -684,6 +673,7 @@ static void server_command_exit(shared_ptr<Client> c, const std::string&) {
|
||||
G_UnusedHeader cmd = {0x73, 0x01, 0x0000};
|
||||
c->channel.send(0x60, 0x00, cmd);
|
||||
c->floor = 0;
|
||||
c->recent_switch_flags.clear();
|
||||
} else if (is_ep3(c->version())) {
|
||||
c->channel.send(0xED, 0x00);
|
||||
} else {
|
||||
@@ -743,11 +733,19 @@ static void proxy_command_get_player_card(shared_ptr<ProxyServer::LinkedSession>
|
||||
}
|
||||
|
||||
static void server_command_send_client(shared_ptr<Client> c, const std::string& args) {
|
||||
check_debug_enabled(c);
|
||||
string data = parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
c->channel.send(data);
|
||||
}
|
||||
|
||||
static void server_command_send_server(shared_ptr<Client> c, const std::string& args) {
|
||||
check_debug_enabled(c);
|
||||
string data = parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
on_command_with_header(c, data);
|
||||
}
|
||||
|
||||
static void proxy_command_send_client(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
string data = parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
@@ -760,6 +758,16 @@ static void proxy_command_send_server(shared_ptr<ProxyServer::LinkedSession> ses
|
||||
ses->server_channel.send(data);
|
||||
}
|
||||
|
||||
static void server_command_send_both(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_send_client(c, args);
|
||||
server_command_send_server(c, args);
|
||||
}
|
||||
|
||||
static void proxy_command_send_both(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
proxy_command_send_client(ses, args);
|
||||
proxy_command_send_server(ses, args);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Lobby commands
|
||||
|
||||
@@ -933,10 +941,7 @@ static void server_command_playrec(shared_ptr<Client> c, const std::string& args
|
||||
|
||||
static void server_command_meseta(shared_ptr<Client> c, const std::string& args) {
|
||||
check_is_ep3(c, true);
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
|
||||
uint32_t amount = stoul(args, nullptr, 0);
|
||||
c->license->ep3_current_meseta += amount;
|
||||
@@ -1473,18 +1478,34 @@ static void server_command_warp(shared_ptr<Client> c, const std::string& args, b
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
|
||||
uint32_t floor = stoul(args, nullptr, 0);
|
||||
if (c->floor == floor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: $warp[me] 0 is allowed in boss arenas if the boss is already
|
||||
// defeated, even if cheats are disabled. This is because if a player returns
|
||||
// to a boss arena after a persistence gap in the game, the exit warp won't
|
||||
// exist, so they need a way to get out.
|
||||
bool should_check_cheats = is_warpall || (floor != 0) || !floor_is_boss_arena(l->episode, c->floor);
|
||||
if (!should_check_cheats) {
|
||||
for (const auto* event : l->map->get_events(c->floor)) {
|
||||
if (!(event->flags & 0x18)) {
|
||||
should_check_cheats = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should_check_cheats) {
|
||||
check_cheats_enabled(l, c);
|
||||
}
|
||||
|
||||
size_t limit = floor_limit_for_episode(l->episode);
|
||||
if (limit == 0) {
|
||||
return;
|
||||
} else if (floor > limit) {
|
||||
send_text_message_printf(c, "$C6Area numbers must\nbe %zu or less.", limit);
|
||||
send_text_message_printf(c, "$C6Area numbers must\nbe %zu or less", limit);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1638,7 +1659,7 @@ static void server_command_infinite_hp(shared_ptr<Client> c, const std::string&)
|
||||
c->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
bool enabled = c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
send_text_message_printf(c, "$C6Infinite HP %s", enabled ? "enabled" : "disabled");
|
||||
if (enabled && l->is_game() && is_v1_or_v2(c->version())) {
|
||||
if (enabled && l->is_game()) {
|
||||
send_remove_conditions(c);
|
||||
}
|
||||
}
|
||||
@@ -1649,7 +1670,7 @@ static void proxy_command_infinite_hp(shared_ptr<ProxyServer::LinkedSession> ses
|
||||
ses->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
bool enabled = ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", enabled ? "enabled" : "disabled");
|
||||
if (enabled && ses->is_in_game && is_v1_or_v2(ses->version())) {
|
||||
if (enabled && ses->is_in_game) {
|
||||
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
}
|
||||
@@ -1859,10 +1880,7 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
}
|
||||
|
||||
static void server_command_enable_ep3_battle_debug_menu(shared_ptr<Client> c, const std::string& args) {
|
||||
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
|
||||
return;
|
||||
}
|
||||
check_debug_enabled(c);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
@@ -2227,6 +2245,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$save", {server_command_save, nullptr}},
|
||||
{"$savechar", {server_command_savechar, nullptr}},
|
||||
{"$saverec", {server_command_saverec, nullptr}},
|
||||
{"$sb", {server_command_send_both, proxy_command_send_both}},
|
||||
{"$sc", {server_command_send_client, proxy_command_send_client}},
|
||||
{"$secid", {server_command_secid, proxy_command_secid}},
|
||||
{"$setassist", {server_command_ep3_replace_assist_card, nullptr}},
|
||||
@@ -2234,7 +2253,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$silence", {server_command_silence, nullptr}},
|
||||
{"$song", {server_command_song, proxy_command_song}},
|
||||
{"$spec", {server_command_toggle_spectator_flag, nullptr}},
|
||||
{"$ss", {nullptr, proxy_command_send_server}},
|
||||
{"$ss", {server_command_send_server, proxy_command_send_server}},
|
||||
{"$stat", {server_command_get_ep3_battle_stat, nullptr}},
|
||||
{"$surrender", {server_command_surrender, nullptr}},
|
||||
{"$swa", {server_command_switch_assist, proxy_command_switch_assist}},
|
||||
|
||||
+23
-8
@@ -201,6 +201,7 @@ Client::Client(
|
||||
card_battle_table_number(-1),
|
||||
card_battle_table_seat_number(0),
|
||||
card_battle_table_seat_state(0),
|
||||
last_game_info_requested(0),
|
||||
should_update_play_time(false),
|
||||
bb_character_index(-1),
|
||||
next_exp_value(0),
|
||||
@@ -215,7 +216,6 @@ Client::Client(
|
||||
}
|
||||
this->config.specific_version = default_specific_version_for_version(version, -1);
|
||||
|
||||
this->last_switch_enabled_command.header.subcommand = 0;
|
||||
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
||||
|
||||
this->reschedule_save_game_data_event();
|
||||
@@ -266,7 +266,7 @@ void Client::set_license(shared_ptr<License> l) {
|
||||
if (this->version() == Version::BB_V4) {
|
||||
// Make sure bb_username is filename-safe
|
||||
for (char ch : l->bb_username) {
|
||||
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
|
||||
if (!isalnum(ch) && (ch != '-') && (ch != '_') && (ch != '@')) {
|
||||
throw runtime_error("invalid characters in username");
|
||||
}
|
||||
}
|
||||
@@ -351,6 +351,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
|
||||
bool Client::evaluate_quest_availability_expression(
|
||||
shared_ptr<const QuestAvailabilityExpression> expr,
|
||||
shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
@@ -361,10 +362,12 @@ bool Client::evaluate_quest_availability_expression(
|
||||
if (!expr) {
|
||||
return true;
|
||||
}
|
||||
auto l = this->lobby.lock();
|
||||
if (game && !game->quest_flag_values) {
|
||||
throw logic_error("quest flags are missing from game");
|
||||
}
|
||||
auto p = this->character();
|
||||
QuestAvailabilityExpression::Env env = {
|
||||
.flags = (l && !l->quest_flags_known) ? &l->quest_flag_values->data.at(difficulty) : &p->quest_flags.data.at(difficulty),
|
||||
.flags = (game && !game->quest_flags_known) ? &game->quest_flag_values->data.at(difficulty) : &p->quest_flags.data.at(difficulty),
|
||||
.challenge_records = &p->challenge_records,
|
||||
.team = this->team(),
|
||||
.num_players = num_players,
|
||||
@@ -379,12 +382,24 @@ bool Client::evaluate_quest_availability_expression(
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const {
|
||||
return this->evaluate_quest_availability_expression(q->available_expression, event, difficulty, num_players, v1_present);
|
||||
bool Client::can_see_quest(
|
||||
shared_ptr<const Quest> q,
|
||||
shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
return this->evaluate_quest_availability_expression(q->available_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const {
|
||||
return this->evaluate_quest_availability_expression(q->enabled_expression, event, difficulty, num_players, v1_present);
|
||||
bool Client::can_play_quest(
|
||||
shared_ptr<const Quest> q,
|
||||
shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
return this->evaluate_quest_availability_expression(q->enabled_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
bool Client::can_use_chat_commands() const {
|
||||
|
||||
+18
-4
@@ -217,8 +217,9 @@ public:
|
||||
uint16_t card_battle_table_seat_number;
|
||||
uint16_t card_battle_table_seat_state;
|
||||
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
|
||||
std::shared_ptr<Episode3::BattleRecord> ep3_prev_battle_record;
|
||||
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
|
||||
std::shared_ptr<const Menu> last_menu_sent;
|
||||
uint32_t last_game_info_requested;
|
||||
struct JoinCommand {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
@@ -250,7 +251,7 @@ public:
|
||||
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
bool can_chat;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const License> license;
|
||||
@@ -294,12 +295,25 @@ public:
|
||||
|
||||
bool evaluate_quest_availability_expression(
|
||||
std::shared_ptr<const QuestAvailabilityExpression> expr,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_see_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_play_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const;
|
||||
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const;
|
||||
|
||||
bool can_use_chat_commands() const;
|
||||
|
||||
|
||||
+59
-30
@@ -709,7 +709,14 @@ struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03<TextEncoding:
|
||||
// memory card), use A7 instead.
|
||||
// All chunks except the last must have 0x400 data bytes. When downloading an
|
||||
// online quest, the .bin and .dat chunks may be interleaved (although newserv
|
||||
// currently sends them sequentially).
|
||||
// currently sends them sequentially). There is a client bug in BB (and
|
||||
// probably all other versions) where if the quest file's size is a multiple
|
||||
// of 0x400, the last chunk will have size 0x400, and the client will never
|
||||
// consider the download complete since it only checks if the last chunk has
|
||||
// size < 0x400; it does not check if all expected bytes have been received.
|
||||
// To work around this, newserv appends an extra zero byte if the quest file's
|
||||
// size is a multiple of 0x400; this byte will be ignored since the PRS
|
||||
// decompression algorithm contains a stop command, so it will never read it.
|
||||
|
||||
// header.flag = file chunk index (start offset / 0x400)
|
||||
struct S_WriteFile_13_A7 {
|
||||
@@ -2491,6 +2498,9 @@ struct C_CreateGame_BB_C1 : C_CreateGame<TextEncoding::UTF16> {
|
||||
|
||||
// C4 (S->C): Choice search results (DCv2 and later versions)
|
||||
// Internal name: RcvChoiceAns
|
||||
// There is a bug that can cause the client to crash or display garbage if this
|
||||
// command is sent with no entries. To work around this, newserv sends a blank
|
||||
// entry (but still with header.flag = 0) if there are no results.
|
||||
|
||||
// Command is a list of these; header.flag is the entry count
|
||||
template <typename HeaderT, TextEncoding NameEncoding, TextEncoding DescEncoding, TextEncoding LocatorEncoding>
|
||||
@@ -3828,21 +3838,28 @@ struct G_ExtendedHeader {
|
||||
struct G_Unknown_6x04 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x05: Switch state changed
|
||||
// Some things that don't look like switches are implemented as switches using
|
||||
// this subcommand. For example, when all enemies in a room are defeated, this
|
||||
// subcommand is used to unlock the doors.
|
||||
// Note: In the client, this is a subclass of 6x04, similar to how 6xA2 is a
|
||||
// subclass of 6x60.
|
||||
|
||||
struct G_SwitchStateChanged_6x05 {
|
||||
// Note: header.object_id is 0xFFFF for room clear when all enemies defeated
|
||||
G_ObjectIDHeader header;
|
||||
parray<uint8_t, 2> unknown_a1;
|
||||
// TODO: Some of these might be big-endian on GC; it only byteswaps
|
||||
// unknown_a3. Are the others actually uint16, or are they uint8[2]?
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
parray<uint8_t, 2> unknown_a3;
|
||||
uint8_t floor = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t switch_flag_floor = 0;
|
||||
// Only two bits in flags have meanings:
|
||||
// 01 - set unlock flag (if not set, the flag is cleared instead)
|
||||
// 02 - play room unlock sound if floor matches client's floor
|
||||
uint8_t flags = 0; // Bit field, with 2 lowest bits having meaning
|
||||
} __packed__;
|
||||
|
||||
@@ -4422,15 +4439,15 @@ struct G_PlayerDied_6x4D {
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x4E: Player died (protected on V3/V4)
|
||||
// 6x4E: Player is dead can be revived (protected on V3/V4)
|
||||
|
||||
struct G_PlayerDied_6x4E {
|
||||
struct G_PlayerRevivable_6x4E {
|
||||
G_ClientIDHeader header;
|
||||
} __packed__;
|
||||
|
||||
// 6x4F: Player resurrected (via Scape Doll) (protected on V3/V4)
|
||||
// 6x4F: Player revived (protected on V3/V4)
|
||||
|
||||
struct G_PlayerUsedScapeDoll_6x4F {
|
||||
struct G_PlayerRevived_6x4F {
|
||||
G_ClientIDHeader header;
|
||||
} __packed__;
|
||||
|
||||
@@ -4635,7 +4652,7 @@ struct G_TriggerSetEvent_6x67 {
|
||||
G_UnusedHeader header;
|
||||
le_uint32_t floor = 0;
|
||||
le_uint32_t event_id = 0; // NOT event index
|
||||
le_uint32_t unused = 0;
|
||||
le_uint32_t client_id = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x68: Create telepipe / cast Ryuker
|
||||
@@ -4754,11 +4771,11 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
|
||||
le_uint16_t total_size = 0; // == sum of the following 3 fields
|
||||
le_uint16_t entity_set_flags_size = 0;
|
||||
le_uint16_t event_set_flags_size = 0;
|
||||
le_uint16_t unused_size = 0;
|
||||
le_uint16_t switch_flags_size = 0;
|
||||
// Variable-length fields follow here:
|
||||
// EntitySetFlags entity_set_flags; // Total size is set_flags_size
|
||||
// EntitySetFlags entity_set_flags; // Total size is entity_set_flags_size
|
||||
// le_uint16_t event_set_flags[event_set_flags_size / 2]; // Same order as in map files (NOT sorted by event_id)
|
||||
// uint8_t unused[is_v1 ? 0x200 : 0x240]; // Possibly an early implementation of 6x6F; unused even in DC NTE
|
||||
// SwitchFlags switch_flags; // 0x200 bytes (0x10 floors) on v1 and earlier; 0x240 bytes (0x12 floors) on v2 and later
|
||||
|
||||
struct EntitySetFlags {
|
||||
le_uint32_t object_set_flags_offset = 0;
|
||||
@@ -4773,19 +4790,29 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
|
||||
|
||||
// 6x6F: Set quest flags (used while loading into game)
|
||||
|
||||
struct G_SetQuestFlagsV1_6x6F {
|
||||
struct G_SetQuestFlags_DCv1_6x6F {
|
||||
G_UnusedHeader header;
|
||||
QuestFlagsV1 quest_flags;
|
||||
} __packed__;
|
||||
|
||||
struct G_SetQuestFlagsV2V3V4_6x6F {
|
||||
struct G_SetQuestFlags_V2_V3_6x6F {
|
||||
G_UnusedHeader header;
|
||||
QuestFlags quest_flags;
|
||||
} __packed__;
|
||||
|
||||
struct G_SetQuestFlags_BB_6x6F {
|
||||
G_UnusedHeader header;
|
||||
QuestFlags quest_flags;
|
||||
// If use_apply_mask is 1, only the flags set in bb_quest_flag_apply_mask
|
||||
// (in PlayerSubordinates.cc) are overwritten on the receiving client's end.
|
||||
// The client always sends this with use_apply_mask = 1.
|
||||
le_uint32_t use_apply_mask = 1;
|
||||
} __packed__;
|
||||
|
||||
// 6x70: Sync player disp data and inventory (used while loading into game)
|
||||
// Annoyingly, they didn't use the same format as the 65/67/68 commands here,
|
||||
// and instead rearranged a bunch of things.
|
||||
// and instead rearranged a bunch of things. This is presumably because this
|
||||
// structure also includes transient state (e.g. current HP).
|
||||
|
||||
struct Telepipe {
|
||||
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
|
||||
@@ -4953,9 +4980,11 @@ struct G_DoneLoadingIntoGame_6x72 {
|
||||
G_UnusedHeader header;
|
||||
} __packed__;
|
||||
|
||||
// 6x73: Unknown
|
||||
// 6x73: Exit quest
|
||||
// This command misbehaves if sent in a lobby or in a game when no quest is
|
||||
// loaded.
|
||||
|
||||
struct G_Unknown_6x73 {
|
||||
struct G_ExitQuest_6x73 {
|
||||
G_UnusedHeader header;
|
||||
} __packed__;
|
||||
|
||||
@@ -4993,11 +5022,11 @@ struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 {
|
||||
le_uint16_t unused = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x76: Set entity flags
|
||||
// This command can only be used to set flags, since the game performs a bitwise
|
||||
// OR operation instead of a simple assignment.
|
||||
// 6x76: Set entity set flags
|
||||
// This command can only be used to set set flags, since the game performs a
|
||||
// bitwise OR operation instead of a simple assignment.
|
||||
|
||||
struct G_SetEntityFlags_6x76 {
|
||||
struct G_SetEntitySetFlags_6x76 {
|
||||
G_EnemyIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t flags = 0;
|
||||
@@ -5235,8 +5264,9 @@ struct G_Unknown_6x91 {
|
||||
le_uint32_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
le_uint16_t unknown_a5 = 0;
|
||||
parray<uint8_t, 2> unknown_a6;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
uint8_t switch_flag_floor = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x92: Unknown (not valid on Episode 3)
|
||||
@@ -5251,9 +5281,9 @@ struct G_Unknown_6x92 {
|
||||
|
||||
struct G_ActivateTimedSwitch_6x93 {
|
||||
G_UnusedHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t switch_id = 0;
|
||||
uint8_t unknown_a1 = 0; // Logic is different if this is 1 vs. any other value
|
||||
le_uint16_t switch_flag_floor = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
parray<uint8_t, 3> unused;
|
||||
} __packed__;
|
||||
|
||||
@@ -5362,10 +5392,9 @@ struct G_GalGryphonBossActions_6xA0 {
|
||||
parray<le_uint32_t, 4> unknown_a4;
|
||||
} __packed__;
|
||||
|
||||
// 6xA1: Unknown (not valid on pre-V3) (protected on V3/V4)
|
||||
// Part of revive process. Occurs right after revive command; function unclear.
|
||||
// 6xA1: Revive player (not valid on pre-V3) (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6xA1 {
|
||||
struct G_RevivePlayer_V3_BB_6xA1 {
|
||||
G_ClientIDHeader header;
|
||||
} __packed__;
|
||||
|
||||
|
||||
+4
-11
@@ -50,19 +50,12 @@ JSON CommonItemSet::Table::json() const {
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
JSON enemy_meseta_ranges_episode_json = JSON::dict();
|
||||
JSON enemy_type_drop_probs_episode_json = JSON::dict();
|
||||
JSON enemy_item_classes_episode_json = JSON::dict();
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string type_str = name_for_enum(type);
|
||||
enemy_meseta_ranges_episode_json.emplace(type_str, to_json(this->enemy_meseta_ranges[z]));
|
||||
enemy_type_drop_probs_episode_json.emplace(type_str, this->enemy_type_drop_probs[z]);
|
||||
enemy_item_classes_episode_json.emplace(type_str, this->enemy_item_classes[z]);
|
||||
string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type));
|
||||
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
|
||||
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
|
||||
}
|
||||
string name = name_for_episode(episode);
|
||||
enemy_meseta_ranges_json.emplace(name, std::move(enemy_meseta_ranges_episode_json));
|
||||
enemy_type_drop_probs_json.emplace(name, std::move(enemy_type_drop_probs_episode_json));
|
||||
enemy_item_classes_json.emplace(name, std::move(enemy_item_classes_episode_json));
|
||||
}
|
||||
}
|
||||
return JSON::dict({
|
||||
|
||||
@@ -438,6 +438,45 @@ string prs_compress_optimal(const string& data, ProgressCallback progress_fn) {
|
||||
return prs_compress_optimal(data.data(), data.size(), progress_fn);
|
||||
}
|
||||
|
||||
string prs_compress_pessimal(const void* vdata, size_t size) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(vdata);
|
||||
|
||||
// The worst possible encoding we can do is a literal byte when no byte with
|
||||
// the same value is within the window, or an extended copy if there is a byte
|
||||
// with the same value in the window.
|
||||
WindowIndex<0x1FFF, 1> window(in_data, size);
|
||||
LZSSInterleavedWriter w;
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
auto match = window.get_best_match();
|
||||
if (match.second >= 1) {
|
||||
// Write extended copy
|
||||
int16_t offset = match.first - window.offset;
|
||||
w.write_control(false);
|
||||
w.flush_if_ready();
|
||||
w.write_control(true);
|
||||
uint16_t a = (offset << 3);
|
||||
w.write_data(a & 0xFF);
|
||||
w.write_data(a >> 8);
|
||||
w.write_data(0);
|
||||
} else {
|
||||
// Write literal
|
||||
w.write_control(true);
|
||||
w.write_data(in_data[z]);
|
||||
}
|
||||
w.flush_if_ready();
|
||||
window.advance();
|
||||
}
|
||||
|
||||
// Write stop command
|
||||
w.write_control(false);
|
||||
w.flush_if_ready();
|
||||
w.write_control(true);
|
||||
w.write_data(0);
|
||||
w.write_data(0);
|
||||
|
||||
return std::move(w.close());
|
||||
}
|
||||
|
||||
PRSCompressor::PRSCompressor(
|
||||
ssize_t compression_level, ProgressCallback progress_fn)
|
||||
: compression_level(compression_level),
|
||||
|
||||
@@ -177,6 +177,10 @@ std::string prs_compress_indexed(
|
||||
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Compresses data using PRS to the LARGEST possible output size. There is no
|
||||
// practical use for this function except for amusement.
|
||||
std::string prs_compress_pessimal(const void* vdata, size_t size);
|
||||
|
||||
// Decompresses PRS-compressed data.
|
||||
struct PRSDecompressResult {
|
||||
std::string data;
|
||||
|
||||
+119
-108
@@ -38,6 +38,8 @@ const char* name_for_enum<EnemyType>(EnemyType type) {
|
||||
return "BOOTA";
|
||||
case EnemyType::BULCLAW:
|
||||
return "BULCLAW";
|
||||
case EnemyType::BULK:
|
||||
return "BULK";
|
||||
case EnemyType::CANADINE:
|
||||
return "CANADINE";
|
||||
case EnemyType::CANADINE_GROUP:
|
||||
@@ -287,6 +289,7 @@ EnemyType enum_for_name<EnemyType>(const char* name) {
|
||||
{"BOOMA", EnemyType::BOOMA},
|
||||
{"BOOTA", EnemyType::BOOTA},
|
||||
{"BULCLAW", EnemyType::BULCLAW},
|
||||
{"BULK", EnemyType::BULK},
|
||||
{"CANADINE", EnemyType::CANADINE},
|
||||
{"CANADINE_GROUP", EnemyType::CANADINE_GROUP},
|
||||
{"CANANE", EnemyType::CANANE},
|
||||
@@ -409,150 +412,152 @@ bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type) {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
switch (enemy_type) {
|
||||
case EnemyType::MOTHMANT:
|
||||
case EnemyType::MONEST:
|
||||
case EnemyType::SAVAGE_WOLF:
|
||||
case EnemyType::BARBAROUS_WOLF:
|
||||
case EnemyType::POISON_LILY:
|
||||
case EnemyType::NAR_LILY:
|
||||
case EnemyType::SINOW_BEAT:
|
||||
case EnemyType::CANADINE:
|
||||
case EnemyType::CANADINE_GROUP:
|
||||
case EnemyType::CANANE:
|
||||
case EnemyType::CHAOS_SORCERER:
|
||||
case EnemyType::CHAOS_BRINGER:
|
||||
case EnemyType::DARK_BELRA:
|
||||
case EnemyType::DE_ROL_LE:
|
||||
case EnemyType::DRAGON:
|
||||
case EnemyType::SINOW_GOLD:
|
||||
case EnemyType::RAG_RAPPY:
|
||||
case EnemyType::AL_RAPPY:
|
||||
case EnemyType::NANO_DRAGON:
|
||||
case EnemyType::DUBCHIC:
|
||||
case EnemyType::GILLCHIC:
|
||||
case EnemyType::GARANZ:
|
||||
case EnemyType::DARK_GUNNER:
|
||||
case EnemyType::BARBAROUS_WOLF:
|
||||
case EnemyType::BOOMA:
|
||||
case EnemyType::BULCLAW:
|
||||
case EnemyType::BULK:
|
||||
case EnemyType::CANADINE_GROUP:
|
||||
case EnemyType::CANADINE:
|
||||
case EnemyType::CANANE:
|
||||
case EnemyType::CHAOS_BRINGER:
|
||||
case EnemyType::CHAOS_SORCERER:
|
||||
case EnemyType::CLAW:
|
||||
case EnemyType::VOL_OPT_2:
|
||||
case EnemyType::POUILLY_SLIME:
|
||||
case EnemyType::POFUILLY_SLIME:
|
||||
case EnemyType::PAN_ARMS:
|
||||
case EnemyType::HIDOOM:
|
||||
case EnemyType::MIGIUM:
|
||||
case EnemyType::DARVANT:
|
||||
case EnemyType::DARVANT_ULTIMATE:
|
||||
case EnemyType::DARK_BELRA:
|
||||
case EnemyType::DARK_FALZ_1:
|
||||
case EnemyType::DARK_FALZ_2:
|
||||
case EnemyType::DARK_FALZ_3:
|
||||
case EnemyType::HILDEBEAR:
|
||||
case EnemyType::HILDEBLUE:
|
||||
case EnemyType::BOOMA:
|
||||
case EnemyType::GOBOOMA:
|
||||
case EnemyType::GIGOBOOMA:
|
||||
case EnemyType::GRASS_ASSASSIN:
|
||||
case EnemyType::EVIL_SHARK:
|
||||
case EnemyType::PAL_SHARK:
|
||||
case EnemyType::GUIL_SHARK:
|
||||
case EnemyType::DARK_GUNNER:
|
||||
case EnemyType::DARVANT_ULTIMATE:
|
||||
case EnemyType::DARVANT:
|
||||
case EnemyType::DE_ROL_LE:
|
||||
case EnemyType::DEATH_GUNNER:
|
||||
case EnemyType::DELSABER:
|
||||
case EnemyType::DIMENIAN:
|
||||
case EnemyType::DRAGON:
|
||||
case EnemyType::DUBCHIC:
|
||||
case EnemyType::EVIL_SHARK:
|
||||
case EnemyType::GARANZ:
|
||||
case EnemyType::GIGOBOOMA:
|
||||
case EnemyType::GILLCHIC:
|
||||
case EnemyType::GOBOOMA:
|
||||
case EnemyType::GRASS_ASSASSIN:
|
||||
case EnemyType::GUIL_SHARK:
|
||||
case EnemyType::HIDOOM:
|
||||
case EnemyType::HILDEBEAR:
|
||||
case EnemyType::HILDEBLUE:
|
||||
case EnemyType::LA_DIMENIAN:
|
||||
case EnemyType::MIGIUM:
|
||||
case EnemyType::MONEST:
|
||||
case EnemyType::MOTHMANT:
|
||||
case EnemyType::NANO_DRAGON:
|
||||
case EnemyType::NAR_LILY:
|
||||
case EnemyType::PAL_SHARK:
|
||||
case EnemyType::PAN_ARMS:
|
||||
case EnemyType::POFUILLY_SLIME:
|
||||
case EnemyType::POISON_LILY:
|
||||
case EnemyType::POUILLY_SLIME:
|
||||
case EnemyType::RAG_RAPPY:
|
||||
case EnemyType::SAVAGE_WOLF:
|
||||
case EnemyType::SINOW_BEAT:
|
||||
case EnemyType::SINOW_GOLD:
|
||||
case EnemyType::SO_DIMENIAN:
|
||||
case EnemyType::VOL_OPT_2:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case Episode::EP2:
|
||||
switch (enemy_type) {
|
||||
case EnemyType::MOTHMANT:
|
||||
case EnemyType::MONEST:
|
||||
case EnemyType::SAVAGE_WOLF:
|
||||
case EnemyType::BARBAROUS_WOLF:
|
||||
case EnemyType::POISON_LILY:
|
||||
case EnemyType::NAR_LILY:
|
||||
case EnemyType::SINOW_BERILL:
|
||||
case EnemyType::GEE:
|
||||
case EnemyType::CHAOS_SORCERER:
|
||||
case EnemyType::DELBITER:
|
||||
case EnemyType::DARK_BELRA:
|
||||
case EnemyType::BARBA_RAY:
|
||||
case EnemyType::GOL_DRAGON:
|
||||
case EnemyType::SINOW_SPIGELL:
|
||||
case EnemyType::RAG_RAPPY:
|
||||
case EnemyType::LOVE_RAPPY:
|
||||
case EnemyType::SAINT_RAPPY:
|
||||
case EnemyType::EGG_RAPPY:
|
||||
case EnemyType::HALLO_RAPPY:
|
||||
case EnemyType::GI_GUE:
|
||||
case EnemyType::DUBCHIC:
|
||||
case EnemyType::GILLCHIC:
|
||||
case EnemyType::GARANZ:
|
||||
case EnemyType::GAL_GRYPHON:
|
||||
case EnemyType::EPSILON:
|
||||
case EnemyType::BARBAROUS_WOLF:
|
||||
case EnemyType::CHAOS_SORCERER:
|
||||
case EnemyType::DARK_BELRA:
|
||||
case EnemyType::DEL_LILY:
|
||||
case EnemyType::ILL_GILL:
|
||||
case EnemyType::OLGA_FLOW_1:
|
||||
case EnemyType::OLGA_FLOW_2:
|
||||
case EnemyType::GAEL:
|
||||
case EnemyType::DELBITER:
|
||||
case EnemyType::DELDEPTH:
|
||||
case EnemyType::PAN_ARMS:
|
||||
case EnemyType::HIDOOM:
|
||||
case EnemyType::MIGIUM:
|
||||
case EnemyType::MERICAROL:
|
||||
case EnemyType::UL_GIBBON:
|
||||
case EnemyType::ZOL_GIBBON:
|
||||
case EnemyType::GIBBLES:
|
||||
case EnemyType::MORFOS:
|
||||
case EnemyType::RECOBOX:
|
||||
case EnemyType::RECON:
|
||||
case EnemyType::SINOW_ZOA:
|
||||
case EnemyType::SINOW_ZELE:
|
||||
case EnemyType::MERIKLE:
|
||||
case EnemyType::MERICUS:
|
||||
case EnemyType::HILDEBEAR:
|
||||
case EnemyType::HILDEBLUE:
|
||||
case EnemyType::MERILLIA:
|
||||
case EnemyType::MERILTAS:
|
||||
case EnemyType::GRASS_ASSASSIN:
|
||||
case EnemyType::DOLMOLM:
|
||||
case EnemyType::DOLMDARL:
|
||||
case EnemyType::DELSABER:
|
||||
case EnemyType::DIMENIAN:
|
||||
case EnemyType::DOLMDARL:
|
||||
case EnemyType::DOLMOLM:
|
||||
case EnemyType::DUBCHIC:
|
||||
case EnemyType::EGG_RAPPY:
|
||||
case EnemyType::EPSILON:
|
||||
case EnemyType::GAEL:
|
||||
case EnemyType::GAL_GRYPHON:
|
||||
case EnemyType::GARANZ:
|
||||
case EnemyType::GEE:
|
||||
case EnemyType::GI_GUE:
|
||||
case EnemyType::GIBBLES:
|
||||
case EnemyType::GILLCHIC:
|
||||
case EnemyType::GOL_DRAGON:
|
||||
case EnemyType::GRASS_ASSASSIN:
|
||||
case EnemyType::HALLO_RAPPY:
|
||||
case EnemyType::HIDOOM:
|
||||
case EnemyType::HILDEBEAR:
|
||||
case EnemyType::HILDEBLUE:
|
||||
case EnemyType::ILL_GILL:
|
||||
case EnemyType::LA_DIMENIAN:
|
||||
case EnemyType::LOVE_RAPPY:
|
||||
case EnemyType::MERICAROL:
|
||||
case EnemyType::MERICUS:
|
||||
case EnemyType::MERIKLE:
|
||||
case EnemyType::MERILLIA:
|
||||
case EnemyType::MERILTAS:
|
||||
case EnemyType::MIGIUM:
|
||||
case EnemyType::MONEST:
|
||||
case EnemyType::MORFOS:
|
||||
case EnemyType::MOTHMANT:
|
||||
case EnemyType::NAR_LILY:
|
||||
case EnemyType::OLGA_FLOW_1:
|
||||
case EnemyType::OLGA_FLOW_2:
|
||||
case EnemyType::PAN_ARMS:
|
||||
case EnemyType::POISON_LILY:
|
||||
case EnemyType::RAG_RAPPY:
|
||||
case EnemyType::RECOBOX:
|
||||
case EnemyType::RECON:
|
||||
case EnemyType::SAINT_RAPPY:
|
||||
case EnemyType::SAVAGE_WOLF:
|
||||
case EnemyType::SINOW_BERILL:
|
||||
case EnemyType::SINOW_SPIGELL:
|
||||
case EnemyType::SINOW_ZELE:
|
||||
case EnemyType::SINOW_ZOA:
|
||||
case EnemyType::SO_DIMENIAN:
|
||||
case EnemyType::UL_GIBBON:
|
||||
case EnemyType::ZOL_GIBBON:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case Episode::EP4:
|
||||
switch (enemy_type) {
|
||||
case EnemyType::BOOTA:
|
||||
case EnemyType::ZE_BOOTA:
|
||||
case EnemyType::BA_BOOTA:
|
||||
case EnemyType::SAND_RAPPY:
|
||||
case EnemyType::DEL_RAPPY:
|
||||
case EnemyType::ZU:
|
||||
case EnemyType::PAZUZU:
|
||||
case EnemyType::ASTARK:
|
||||
case EnemyType::SATELLITE_LIZARD:
|
||||
case EnemyType::YOWIE:
|
||||
case EnemyType::DORPHON:
|
||||
case EnemyType::DORPHON_ECLAIR:
|
||||
case EnemyType::GORAN:
|
||||
case EnemyType::PYRO_GORAN:
|
||||
case EnemyType::GORAN_DETONATOR:
|
||||
case EnemyType::SAND_RAPPY_ALT:
|
||||
case EnemyType::BA_BOOTA:
|
||||
case EnemyType::BOOTA:
|
||||
case EnemyType::DEL_RAPPY_ALT:
|
||||
case EnemyType::DEL_RAPPY:
|
||||
case EnemyType::DORPHON_ECLAIR:
|
||||
case EnemyType::DORPHON:
|
||||
case EnemyType::GIRTABLULU:
|
||||
case EnemyType::GORAN_DETONATOR:
|
||||
case EnemyType::GORAN:
|
||||
case EnemyType::KONDRIEU:
|
||||
case EnemyType::MERISSA_A:
|
||||
case EnemyType::MERISSA_AA:
|
||||
case EnemyType::ZU_ALT:
|
||||
case EnemyType::PAZUZU_ALT:
|
||||
case EnemyType::SATELLITE_LIZARD_ALT:
|
||||
case EnemyType::YOWIE_ALT:
|
||||
case EnemyType::GIRTABLULU:
|
||||
case EnemyType::PAZUZU:
|
||||
case EnemyType::PYRO_GORAN:
|
||||
case EnemyType::SAINT_MILLION:
|
||||
case EnemyType::SAND_RAPPY_ALT:
|
||||
case EnemyType::SAND_RAPPY:
|
||||
case EnemyType::SATELLITE_LIZARD_ALT:
|
||||
case EnemyType::SATELLITE_LIZARD:
|
||||
case EnemyType::SHAMBERTIN:
|
||||
case EnemyType::KONDRIEU:
|
||||
case EnemyType::YOWIE_ALT:
|
||||
case EnemyType::YOWIE:
|
||||
case EnemyType::ZE_BOOTA:
|
||||
case EnemyType::ZU_ALT:
|
||||
case EnemyType::ZU:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -611,8 +616,10 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
case EnemyType::GARANZ:
|
||||
return 0x1D;
|
||||
case EnemyType::DARK_GUNNER:
|
||||
case EnemyType::DEATH_GUNNER:
|
||||
return 0x1E;
|
||||
case EnemyType::BULCLAW:
|
||||
case EnemyType::BULK:
|
||||
return 0x1F;
|
||||
case EnemyType::CLAW:
|
||||
return 0x20;
|
||||
@@ -865,6 +872,8 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
return 0x09;
|
||||
case EnemyType::BOOTA:
|
||||
return 0x4D;
|
||||
case EnemyType::BULK:
|
||||
return 0x27;
|
||||
case EnemyType::BULCLAW:
|
||||
return 0x28;
|
||||
case EnemyType::CANADINE:
|
||||
@@ -886,6 +895,8 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
return 0x2F;
|
||||
case EnemyType::DARK_GUNNER:
|
||||
return 0x22;
|
||||
case EnemyType::DEATH_GUNNER:
|
||||
return 0x23;
|
||||
case EnemyType::DE_ROL_LE:
|
||||
return 0x2D;
|
||||
case EnemyType::DEL_LILY:
|
||||
|
||||
@@ -20,6 +20,7 @@ enum class EnemyType {
|
||||
BOOMA,
|
||||
BOOTA,
|
||||
BULCLAW,
|
||||
BULK,
|
||||
CANADINE,
|
||||
CANADINE_GROUP,
|
||||
CANANE,
|
||||
|
||||
@@ -9,6 +9,12 @@ using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
void BattleRecord::PlayerEntry::print(FILE* stream) const {
|
||||
// TODO: Format this nicely somehow. Maybe factor out the functions in
|
||||
// QuestScript that format some of these structures
|
||||
print_data(stream, this, sizeof(this));
|
||||
}
|
||||
|
||||
BattleRecord::Event::Event(StringReader& r) {
|
||||
this->type = r.get<Event::Type>();
|
||||
this->timestamp = r.get_u64l();
|
||||
@@ -32,6 +38,7 @@ BattleRecord::Event::Event(StringReader& r) {
|
||||
case Event::Type::GAME_COMMAND:
|
||||
case Event::Type::BATTLE_COMMAND:
|
||||
case Event::Type::EP3_GAME_COMMAND:
|
||||
case Event::Type::SERVER_DATA_COMMAND:
|
||||
this->data = r.read(r.get_u16l());
|
||||
break;
|
||||
default:
|
||||
@@ -64,6 +71,7 @@ void BattleRecord::Event::serialize(StringWriter& w) const {
|
||||
case Event::Type::GAME_COMMAND:
|
||||
case Event::Type::BATTLE_COMMAND:
|
||||
case Event::Type::EP3_GAME_COMMAND:
|
||||
case Event::Type::SERVER_DATA_COMMAND:
|
||||
w.put_u16l(this->data.size());
|
||||
w.write(this->data);
|
||||
break;
|
||||
@@ -72,6 +80,51 @@ void BattleRecord::Event::serialize(StringWriter& w) const {
|
||||
}
|
||||
}
|
||||
|
||||
void BattleRecord::Event::print(FILE* stream) const {
|
||||
string time_str = format_time(this->timestamp);
|
||||
fprintf(stream, "Event @%016" PRIX64 " (%s) ", this->timestamp, time_str.c_str());
|
||||
switch (this->type) {
|
||||
case Type::PLAYER_JOIN:
|
||||
fprintf(stream, "PLAYER_JOIN %02" PRIX32 "\n", this->players[0].lobby_data.client_id.load());
|
||||
this->players[0].print(stream);
|
||||
break;
|
||||
case Type::PLAYER_LEAVE:
|
||||
fprintf(stream, "PLAYER_LEAVE %02hhu\n", this->leaving_client_id);
|
||||
break;
|
||||
case Type::SET_INITIAL_PLAYERS:
|
||||
fprintf(stream, "SET_INITIAL_PLAYERS");
|
||||
for (const auto& player : this->players) {
|
||||
fprintf(stream, " %02" PRIX32, player.lobby_data.client_id.load());
|
||||
}
|
||||
for (const auto& player : this->players) {
|
||||
player.print(stream);
|
||||
}
|
||||
break;
|
||||
case Type::BATTLE_COMMAND:
|
||||
fprintf(stream, "BATTLE_COMMAND\n");
|
||||
print_data(stream, this->data);
|
||||
break;
|
||||
case Type::GAME_COMMAND:
|
||||
fprintf(stream, "GAME_COMMAND\n");
|
||||
print_data(stream, this->data);
|
||||
break;
|
||||
case Type::EP3_GAME_COMMAND:
|
||||
fprintf(stream, "EP3_GAME_COMMAND\n");
|
||||
print_data(stream, this->data);
|
||||
break;
|
||||
case Type::CHAT_MESSAGE:
|
||||
fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number);
|
||||
print_data(stream, this->data);
|
||||
break;
|
||||
case Type::SERVER_DATA_COMMAND:
|
||||
fprintf(stream, "SERVER_DATA_COMMAND\n");
|
||||
print_data(stream, this->data);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown event type in batlte record");
|
||||
}
|
||||
}
|
||||
|
||||
BattleRecord::BattleRecord(uint32_t behavior_flags)
|
||||
: is_writable(true),
|
||||
behavior_flags(behavior_flags),
|
||||
@@ -274,6 +327,21 @@ void BattleRecord::set_battle_end_timestamp() {
|
||||
this->battle_end_timestamp = now();
|
||||
}
|
||||
|
||||
void BattleRecord::print(FILE* stream) const {
|
||||
string start_str = format_time(this->battle_start_timestamp);
|
||||
string end_str = format_time(this->battle_end_timestamp);
|
||||
fprintf(stream, "BattleRecord %s behavior_flags=%08" PRIX32 " start=%016" PRIX64 " (%s) end=%016" PRIX64 " (%s); %zu events\n",
|
||||
this->is_writable ? "writable" : "read-only",
|
||||
this->behavior_flags,
|
||||
this->battle_start_timestamp,
|
||||
start_str.c_str(),
|
||||
this->battle_end_timestamp,
|
||||
end_str.c_str(), this->events.size());
|
||||
for (const auto& event : this->events) {
|
||||
event.print(stream);
|
||||
}
|
||||
}
|
||||
|
||||
BattleRecordPlayer::BattleRecordPlayer(
|
||||
shared_ptr<const BattleRecord> rec,
|
||||
shared_ptr<struct event_base> base)
|
||||
@@ -356,6 +424,10 @@ void BattleRecordPlayer::schedule_events() {
|
||||
case BattleRecord::Event::Type::CHAT_MESSAGE:
|
||||
send_prepared_chat_message(l, ev.guild_card_number, ev.data);
|
||||
break;
|
||||
case BattleRecord::Event::Type::SERVER_DATA_COMMAND:
|
||||
// These are not replayed, since the battle record also contains
|
||||
// the results of these commands.
|
||||
break;
|
||||
}
|
||||
this->event_it++;
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ public:
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
le_uint32_t level;
|
||||
|
||||
void print(FILE* stream) const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Event {
|
||||
@@ -35,6 +37,7 @@ public:
|
||||
GAME_COMMAND = 4,
|
||||
EP3_GAME_COMMAND = 5,
|
||||
CHAT_MESSAGE = 6,
|
||||
SERVER_DATA_COMMAND = 7,
|
||||
};
|
||||
|
||||
// Fields used for all events
|
||||
@@ -52,6 +55,7 @@ public:
|
||||
Event() = default;
|
||||
explicit Event(StringReader& r);
|
||||
void serialize(StringWriter& w) const;
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
explicit BattleRecord(uint32_t behavior_flags);
|
||||
@@ -79,6 +83,8 @@ public:
|
||||
void set_battle_start_timestamp();
|
||||
void set_battle_end_timestamp();
|
||||
|
||||
void print(FILE* stream) const;
|
||||
|
||||
private:
|
||||
static constexpr uint64_t SIGNATURE = 0x14C946D56D1DAC50;
|
||||
|
||||
|
||||
@@ -3150,20 +3150,35 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
}
|
||||
break;
|
||||
case 0x24: { // p36
|
||||
auto log36 = log.sub("(p36) ");
|
||||
// On NTE, this includes SCs and items; on other versions, it's SCs only
|
||||
static const auto should_include = +[](shared_ptr<const CardIndex::CardEntry> ce, bool is_nte) -> bool {
|
||||
return (ce && (ce->def.is_sc() || (is_nte ? (ce->def.type == CardType::ITEM) : false)));
|
||||
};
|
||||
bool is_nte = s->options.is_nte();
|
||||
if (as.original_attacker_card_ref == 0xFFFF) {
|
||||
log36.debug("original_attacker_card_ref missing");
|
||||
// debug_str_for_card_ref
|
||||
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(as.target_card_refs[z]);
|
||||
log36.debug("examining %s", debug_ref_str.c_str());
|
||||
auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]);
|
||||
if (result_card && should_include(result_card->get_definition(), is_nte)) {
|
||||
log36.debug("adding %s", debug_ref_str.c_str());
|
||||
ret.emplace_back(result_card);
|
||||
} else {
|
||||
log36.debug("skipping %s", debug_ref_str.c_str());
|
||||
}
|
||||
}
|
||||
} else if (card2 && should_include(card2->get_definition(), is_nte)) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(card2->get_card_ref());
|
||||
log36.debug("original_attacker_card_ref present; adding card2 = %s", debug_ref_str.c_str());
|
||||
ret.emplace_back(card2);
|
||||
} else if (card2) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(card2->get_card_ref());
|
||||
log36.debug("original_attacker_card_ref present and card2 (%s) not eligible", debug_ref_str.c_str());
|
||||
} else {
|
||||
log36.debug("original_attacker_card_ref present and card2 missing");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1723,7 +1723,7 @@ string MapDefinition::str(const CardIndex* card_index, uint8_t language) const {
|
||||
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
|
||||
for (size_t y = 0; y < this->height; y++) {
|
||||
string line = " ";
|
||||
for (size_t x = 0; x < this->height; x++) {
|
||||
for (size_t x = 0; x < this->width; x++) {
|
||||
line += string_printf(" %02hhX", tiles[y][x]);
|
||||
}
|
||||
lines.emplace_back(std::move(line));
|
||||
|
||||
+30
-28
@@ -84,43 +84,45 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
|
||||
}
|
||||
|
||||
uint8_t index = index_for_card_ref(card_ref);
|
||||
if (index > this->entries.size()) {
|
||||
if (index >= this->entries.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->entries[index].state == CardState::DISCARDED) {
|
||||
auto& entry = this->entries[index];
|
||||
if (entry.state == CardState::DISCARDED) {
|
||||
// If the card is discarded, then it should be before the draw index, and we
|
||||
// can just change its state.
|
||||
this->entries[index].state = CardState::IN_HAND;
|
||||
entry.state = CardState::IN_HAND;
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (this->entries[index].state == CardState::DRAWABLE) {
|
||||
// If the card is still drawable, we need to move it so it's just in front
|
||||
// of the draw index, then immediately draw it. Ep3 NTE does not handle this
|
||||
// case, but we do even when playing NTE.
|
||||
ssize_t ref_index;
|
||||
for (ref_index = this->card_refs.size(); ref_index >= 0; ref_index--) {
|
||||
if (this->card_refs[ref_index] == card_ref) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ref_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t ref_uindex = ref_index;
|
||||
for (; ref_uindex > this->draw_index; ref_uindex--) {
|
||||
// Note: draw_index is also unsigned, so ref_uindex cannot be zero here
|
||||
this->card_refs[ref_uindex] = this->card_refs[ref_uindex - 1];
|
||||
}
|
||||
this->card_refs[this->draw_index] = card_ref;
|
||||
this->entries[index].state = CardState::IN_HAND;
|
||||
this->draw_index++;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if (entry.state != CardState::DRAWABLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the card is still drawable, we need to move it so it's just in front of
|
||||
// the draw index, then immediately draw it. Ep3 NTE does not handle this
|
||||
// case, but we do even when playing NTE.
|
||||
size_t ref_index;
|
||||
for (ref_index = 0; ref_index < this->card_refs.size(); ref_index++) {
|
||||
if (this->card_refs[ref_index] == card_ref) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ref_index >= this->card_refs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (; ref_index > this->draw_index; ref_index--) {
|
||||
// this->draw_index is also unsigned, so ref_index cannot be zero here
|
||||
this->card_refs[ref_index] = this->card_refs[ref_index - 1];
|
||||
}
|
||||
this->card_refs[this->draw_index] = card_ref;
|
||||
|
||||
// Draw the card
|
||||
entry.state = CardState::IN_HAND;
|
||||
this->draw_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t DeckState::card_id_for_card_ref(uint16_t card_ref) const {
|
||||
|
||||
@@ -224,8 +224,7 @@ void PlayerState::apply_assist_card_effect_on_set(
|
||||
|
||||
size_t log_index;
|
||||
for (log_index = 0; log_index < 0x10; log_index++) {
|
||||
auto ce = s->definition_for_card_ref(
|
||||
this->discard_log_card_refs[log_index]);
|
||||
auto ce = s->definition_for_card_ref(this->discard_log_card_refs[log_index]);
|
||||
if (ce && ((ce->def.type == CardType::ITEM || ce->def.type == CardType::CREATURE))) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1837,7 +1837,12 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
throw runtime_error("unknown CAx subsubcommand");
|
||||
}
|
||||
|
||||
if ((sender_c->version() == Version::GC_EP3_NTE) || !header.mask_key) {
|
||||
auto l = this->lobby.lock();
|
||||
if (l && l->battle_record && l->battle_record->writable()) {
|
||||
l->battle_record->add_command(BattleRecord::Event::Type::SERVER_DATA_COMMAND, data.data(), data.size());
|
||||
}
|
||||
|
||||
if ((sender_c && (sender_c->version() == Version::GC_EP3_NTE)) || !header.mask_key) {
|
||||
(this->*handler)(sender_c, data);
|
||||
} else {
|
||||
string unmasked_data = data;
|
||||
@@ -2167,7 +2172,8 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
|
||||
// in the case of NTE, no values at all, since the Rules structure is
|
||||
// smaller). So, use the values from the last chosen map if applicable, or
|
||||
// the values from the $dicerange command if available.
|
||||
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(c->language())->map->default_rules : nullptr;
|
||||
uint8_t language = c ? c->language() : 1;
|
||||
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr;
|
||||
auto& server_rules = this->map_and_rules->rules;
|
||||
// NTE can specify the DEF dice value range in its Rules struct, so we use
|
||||
// that unless the map or $dicerange overrides it.
|
||||
@@ -2554,8 +2560,7 @@ void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& d
|
||||
|
||||
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_CAx40>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "MAP LIST");
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
|
||||
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
|
||||
@@ -345,6 +345,12 @@ bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> FunctionCodeIndex::get_patch(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return this->name_and_specific_version_to_patch_function.at(
|
||||
string_printf("%s-%08" PRIX32, name.c_str(), specific_version));
|
||||
}
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!function_compiler_available()) {
|
||||
function_compiler_log.info("Function compiler is not available");
|
||||
|
||||
@@ -70,6 +70,8 @@ struct FunctionCodeIndex {
|
||||
|
||||
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
|
||||
};
|
||||
|
||||
struct DOLFileIndex {
|
||||
|
||||
+1
-4
@@ -848,10 +848,7 @@ JSON HTTPServer::generate_common_tables_json() const {
|
||||
auto [set_v2, set_v3_v4] = call_on_event_thread<pair<shared_ptr<const CommonItemSet>, shared_ptr<const CommonItemSet>>>(this->state->base, [&]() {
|
||||
return make_pair(this->state->common_item_set_v2, this->state->common_item_set_v3_v4);
|
||||
});
|
||||
return JSON::dict({
|
||||
{"v1_v2", set_v2->json()},
|
||||
{"v3_v4", set_v3_v4->json()},
|
||||
});
|
||||
return JSON::dict({{"v1_v2", set_v2->json()}, {"v3_v4", set_v3_v4->json()}});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_rare_tables_json() const {
|
||||
|
||||
@@ -607,12 +607,13 @@ private:
|
||||
class MagEvolutionTable {
|
||||
public:
|
||||
struct TableOffsets {
|
||||
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[0x53], offset -> (same as first offset)]
|
||||
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[0x53]
|
||||
// num_mags = 0x53 in BB, 0x43 in V3
|
||||
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[num_mags], offset -> (same as first offset)]
|
||||
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[num_mags]
|
||||
/* 08 / 04AE */ le_uint32_t unknown_a3; // -> (0xA8 bytes; possibly (8-byte struct)[0x15])
|
||||
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[0x53]
|
||||
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags]
|
||||
/* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48]
|
||||
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[0x53]
|
||||
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags]
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EvolutionNumberTable {
|
||||
|
||||
+6
-4
@@ -816,8 +816,9 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
|
||||
if (this->quest) {
|
||||
size_t num_clients = this->count_clients() + 1;
|
||||
bool v1_present = is_v1(c->version()) || this->any_v1_clients_present();
|
||||
if (!c->can_see_quest(this->quest, this->event, this->difficulty, num_clients, v1_present) ||
|
||||
!c->can_play_quest(this->quest, this->event, this->difficulty, num_clients, v1_present)) {
|
||||
auto this_sh = this->shared_from_this();
|
||||
if (!c->can_see_quest(this->quest, this_sh, this->event, this->difficulty, num_clients, v1_present) ||
|
||||
!c->can_play_quest(this->quest, this_sh, this->event, this->difficulty, num_clients, v1_present)) {
|
||||
return JoinError::NO_ACCESS_TO_QUEST;
|
||||
}
|
||||
}
|
||||
@@ -930,10 +931,11 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||
return [this, num_players, v1_present](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||
bool is_enabled = true;
|
||||
for (const auto& lc : this->clients) {
|
||||
if (lc && !lc->can_see_quest(q, this->event, this->difficulty, num_players, v1_present)) {
|
||||
auto this_sh = this->shared_from_this();
|
||||
if (lc && !lc->can_see_quest(q, this_sh, this->event, this->difficulty, num_players, v1_present)) {
|
||||
return QuestIndex::IncludeState::HIDDEN;
|
||||
}
|
||||
if (lc && !lc->can_play_quest(q, this->event, this->difficulty, num_players, v1_present)) {
|
||||
if (lc && !lc->can_play_quest(q, this_sh, this->event, this->difficulty, num_players, v1_present)) {
|
||||
is_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
|
||||
+83
-8
@@ -266,6 +266,7 @@ static void a_compress_decompress_fn(Arguments& args) {
|
||||
bool is_decompress = starts_with(action, "decompress-");
|
||||
bool is_big_endian = args.get<bool>("big-endian");
|
||||
bool is_optimal = args.get<bool>("optimal");
|
||||
bool is_pessimal = args.get<bool>("pessimal");
|
||||
int8_t compression_level = args.get<int8_t>("compression-level", 0);
|
||||
size_t bytes = args.get<size_t>("bytes", 0);
|
||||
string seed = args.get<string>("seed");
|
||||
@@ -298,6 +299,8 @@ static void a_compress_decompress_fn(Arguments& args) {
|
||||
if (!is_decompress && (is_prs || is_pr2 || is_prc)) {
|
||||
if (is_optimal) {
|
||||
data = prs_compress_optimal(data.data(), data.size(), optimal_progress_fn);
|
||||
} else if (is_pessimal) {
|
||||
data = prs_compress_pessimal(data.data(), data.size());
|
||||
} else {
|
||||
data = prs_compress(data, compression_level, progress_fn);
|
||||
}
|
||||
@@ -1115,6 +1118,43 @@ Action a_disassemble_quest_map(
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
string result = Map::disassemble_quest_data(data.data(), data.size());
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_free_map(
|
||||
"disassemble-free-map", "\
|
||||
disassemble-free-map INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||
Disassemble the input free-roam map (.dat or .evt file) into a text\n\
|
||||
representation of the data it contains. Unlike othe disassembly actions,\n\
|
||||
this action expects its input to be already decompressed. If the input is\n\
|
||||
compressed, use the --compressed option. Also unlike other options, the\n\
|
||||
input must be from a file (that is, INPUT-FILENAME is required and cannot\n\
|
||||
be \"-\").\n",
|
||||
+[](Arguments& args) {
|
||||
const string& input_filename = args.get<string>(1, true);
|
||||
bool is_events = ends_with(input_filename, ".evt");
|
||||
bool is_enemies = ends_with(input_filename, "e.dat") || ends_with(input_filename, "e_s.dat") || ends_with(input_filename, "e_c1.dat") || ends_with(input_filename, "e_d.dat");
|
||||
bool is_objects = ends_with(input_filename, "o.dat") || ends_with(input_filename, "o_s.dat") || ends_with(input_filename, "o_c1.dat") || ends_with(input_filename, "o_d.dat");
|
||||
if (!is_objects && !is_enemies && !is_events) {
|
||||
throw runtime_error("cannot determine input file type");
|
||||
}
|
||||
|
||||
string data = read_input_data(args);
|
||||
if (args.get<bool>("compressed")) {
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
|
||||
string result;
|
||||
if (is_objects) {
|
||||
result = Map::disassemble_objects_data(data.data(), data.size());
|
||||
} else if (is_enemies) {
|
||||
result = Map::disassemble_enemies_data(data.data(), data.size());
|
||||
} else if (is_events) {
|
||||
result = Map::disassemble_wave_events_data(data.data(), data.size());
|
||||
} else {
|
||||
throw logic_error("unhandled input type");
|
||||
}
|
||||
result.push_back('\n');
|
||||
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_set_data_table(
|
||||
@@ -1439,7 +1479,7 @@ Action a_convert_rare_item_set(
|
||||
+[](Arguments& args) {
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
double rate_factor = args.get<double>("multiply");
|
||||
double rate_factor = args.get<double>("multiply", 1.0);
|
||||
auto s = make_shared<ServerState>("system/config.json");
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes(false);
|
||||
@@ -2248,6 +2288,22 @@ Action a_diff_dol_files(
|
||||
}
|
||||
});
|
||||
|
||||
Action a_generate_hangame_creds(
|
||||
"generate-hangame-creds", nullptr, +[](Arguments& args) {
|
||||
const string& user_id = args.get<string>(1);
|
||||
const string& token = args.get<string>(2);
|
||||
const string& unused = args.get<string>(3, false);
|
||||
string hex = format_data_string(encode_psobb_hangame_credentials(user_id, token, unused));
|
||||
fprintf(stdout, "psobb.exe 1196310600 %s\n", hex.c_str());
|
||||
});
|
||||
|
||||
Action a_format_ep3_battle_record(
|
||||
"format-ep3-battle-record", nullptr, +[](Arguments& args) {
|
||||
string data = read_input_data(args);
|
||||
Episode3::BattleRecord rec(data);
|
||||
rec.print(stdout);
|
||||
});
|
||||
|
||||
Action a_replay_ep3_battle_commands(
|
||||
"replay-ep3-battle-commands", nullptr, +[](Arguments& args) {
|
||||
auto s = make_shared<ServerState>();
|
||||
@@ -2304,13 +2360,8 @@ Action a_run_server_replay_log(
|
||||
config_log.info("newserv %s compiled at %s", GIT_REVISION_HASH, build_date.c_str());
|
||||
}
|
||||
|
||||
#ifdef PHOSG_WINDOWS
|
||||
int evthread_ret = evthread_use_windows_threads();
|
||||
#else
|
||||
int evthread_ret = evthread_use_pthreads();
|
||||
#endif
|
||||
if (evthread_ret) {
|
||||
throw runtime_error("failed to setup libevent threads");
|
||||
if (evthread_use_pthreads()) {
|
||||
throw runtime_error("failed to set up libevent threads");
|
||||
}
|
||||
|
||||
if (!isdir("system/players")) {
|
||||
@@ -2599,6 +2650,30 @@ int main(int argc, char** argv) {
|
||||
log_error("Unknown or invalid action; try --help");
|
||||
return 1;
|
||||
}
|
||||
#ifdef PHOSG_WINDOWS
|
||||
// Cygwin just gives a stackdump when an exception falls out of main(), so
|
||||
// unlike Linux and macOS, we have to manually catch exceptions here just to
|
||||
// see what the exception message was.
|
||||
try {
|
||||
a->run(args);
|
||||
} catch (const cannot_open_file& e) {
|
||||
log_error("Top-level exception (cannot_open_file): %s", e.what());
|
||||
throw;
|
||||
} catch (const invalid_argument& e) {
|
||||
log_error("Top-level exception (invalid_argument): %s", e.what());
|
||||
throw;
|
||||
} catch (const out_of_range& e) {
|
||||
log_error("Top-level exception (out_of_range): %s", e.what());
|
||||
throw;
|
||||
} catch (const runtime_error& e) {
|
||||
log_error("Top-level exception (runtime_error): %s", e.what());
|
||||
throw;
|
||||
} catch (const exception& e) {
|
||||
log_error("Top-level exception: %s", e.what());
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
a->run(args);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
+191
-38
@@ -14,6 +14,10 @@ using namespace std;
|
||||
|
||||
static constexpr float UINT32_MAX_AS_FLOAT = 4294967296.0f;
|
||||
|
||||
static uint64_t section_index_key(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
return (static_cast<uint64_t>(floor) << 32) | (static_cast<uint64_t>(section) << 16) | static_cast<uint64_t>(wave_number);
|
||||
}
|
||||
|
||||
const char* Map::name_for_object_type(uint16_t type) {
|
||||
switch (type) {
|
||||
case 0x0000:
|
||||
@@ -603,8 +607,10 @@ JSON Map::RareEnemyRates::json() const {
|
||||
}
|
||||
|
||||
string Map::ObjectEntry::str() const {
|
||||
return string_printf("[ObjectEntry type=%04hX flags=%04hX index=%04hX a2=%04hX entity_id=%04hX group=%04hX section=%04hX a3=%04hX x=%g y=%g z=%g x_angle=%08" PRIX32 " y_angle=%08" PRIX32 " z_angle=%08" PRIX32 " params=[%g %g %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] unused=%08" PRIX32 "]",
|
||||
string name_str = Map::name_for_object_type(this->base_type);
|
||||
return string_printf("[ObjectEntry type=%04hX \"%s\" flags=%04hX index=%04hX a2=%04hX entity_id=%04hX group=%04hX section=%04hX a3=%04hX x=%g y=%g z=%g x_angle=%08" PRIX32 " y_angle=%08" PRIX32 " z_angle=%08" PRIX32 " params=[%g %g %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] unused=%08" PRIX32 "]",
|
||||
this->base_type.load(),
|
||||
name_str.c_str(),
|
||||
this->flags.load(),
|
||||
this->index.load(),
|
||||
this->unknown_a2.load(),
|
||||
@@ -655,23 +661,34 @@ string Map::EnemyEntry::str() const {
|
||||
this->unused.load());
|
||||
}
|
||||
|
||||
Map::Enemy::Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type)
|
||||
Map::Enemy::Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type)
|
||||
: source_index(source_index),
|
||||
set_index(set_index),
|
||||
enemy_id(enemy_id),
|
||||
total_damage(0),
|
||||
game_flags(0),
|
||||
section(section),
|
||||
wave_number(wave_number),
|
||||
type(type),
|
||||
floor(floor),
|
||||
state_flags(0) {}
|
||||
|
||||
string Map::Enemy::str() const {
|
||||
return string_printf("[Map::Enemy E-%hX source %zX %s%s floor=%02hhX flags=%02hhX]",
|
||||
return string_printf("[Map::Enemy E-%hX source %zX %s%s floor=%02hhX section=%04hX wave_number=%04hX flags=%02hhX]",
|
||||
this->enemy_id,
|
||||
this->source_index,
|
||||
name_for_enum(this->type),
|
||||
enemy_type_is_rare(this->type) ? " RARE" : "",
|
||||
this->floor,
|
||||
this->section,
|
||||
this->wave_number,
|
||||
this->state_flags);
|
||||
}
|
||||
|
||||
@@ -721,10 +738,11 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
uint16_t object_id = this->objects.size();
|
||||
this->objects.emplace_back(Object{
|
||||
.source_index = z,
|
||||
.object_id = object_id,
|
||||
.floor = floor,
|
||||
.object_id = object_id,
|
||||
.base_type = objects[z].base_type,
|
||||
.section = objects[z].section,
|
||||
.group = objects[z].group,
|
||||
.param1 = objects[z].param1,
|
||||
.param3 = objects[z].param3,
|
||||
.param4 = objects[z].param4,
|
||||
@@ -734,6 +752,8 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
|
||||
.set_flags = 0,
|
||||
.item_drop_checked = false,
|
||||
});
|
||||
uint64_t k = section_index_key(floor, objects[z].section, objects[z].group);
|
||||
this->floor_section_and_group_to_object_index.emplace(k, object_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,7 +802,9 @@ void Map::add_enemy(
|
||||
|
||||
auto add = [&](EnemyType type) -> void {
|
||||
uint16_t enemy_id = this->enemies.size();
|
||||
this->enemies.emplace_back(enemy_id, source_index, set_index, floor, type);
|
||||
this->enemies.emplace_back(enemy_id, source_index, set_index, floor, e.section, e.wave_number, type);
|
||||
uint64_t k = section_index_key(floor, e.section, e.wave_number);
|
||||
this->floor_section_and_wave_number_to_enemy_index.emplace(k, enemy_id);
|
||||
};
|
||||
|
||||
EnemyType child_type = EnemyType::UNKNOWN;
|
||||
@@ -1434,7 +1456,7 @@ void Map::add_random_enemies_from_map_data(
|
||||
}
|
||||
if (remaining_waves) {
|
||||
/* ev.delay = */ random_state->rand_int_biased(entry.min_delay, entry.max_delay);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, this->event_action_stream.size());
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, entry.section, wave_number, this->event_action_stream.size());
|
||||
this->event_action_stream.push_back(0x0C);
|
||||
wave_next_event_id = entry.event_id + wave_number + 10000;
|
||||
this->event_action_stream.append(reinterpret_cast<const char*>(&wave_next_event_id), sizeof(wave_next_event_id));
|
||||
@@ -1444,15 +1466,17 @@ void Map::add_random_enemies_from_map_data(
|
||||
}
|
||||
|
||||
/* ev.delay = */ random_state->rand_int_biased(entry.min_delay, entry.max_delay);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, action_stream_base_offset + entry.action_stream_offset);
|
||||
this->add_event(wave_next_event_id, entry.flags, floor, entry.section, wave_number, action_stream_base_offset + entry.action_stream_offset);
|
||||
wave_number++;
|
||||
}
|
||||
}
|
||||
|
||||
void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t action_stream_offset) {
|
||||
void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint16_t section, uint16_t wave_number, uint32_t action_stream_offset) {
|
||||
size_t index = this->events.size();
|
||||
auto& ev = this->events.emplace_back();
|
||||
ev.event_id = event_id;
|
||||
ev.section = section;
|
||||
ev.wave_number = wave_number;
|
||||
ev.flags = flags;
|
||||
ev.floor = floor;
|
||||
ev.action_stream_offset = action_stream_offset;
|
||||
@@ -1460,6 +1484,9 @@ void Map::add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t a
|
||||
if (!this->floor_and_event_id_to_index.emplace(k, index).second) {
|
||||
this->log.warning("Duplicate event ID: W-%02hhX-%" PRIX32, floor, event_id);
|
||||
}
|
||||
|
||||
k = section_index_key(floor, section, wave_number);
|
||||
this->floor_section_and_wave_number_to_event_index.emplace(k, index);
|
||||
}
|
||||
|
||||
Map::Event& Map::get_event(uint8_t floor, uint32_t event_id) {
|
||||
@@ -1486,7 +1513,7 @@ void Map::add_events_from_map_data(uint8_t floor, const void* data, size_t size)
|
||||
auto events_r = r.sub(header.entries_offset, sizeof(Event1Entry) * header.entry_count);
|
||||
while (!events_r.eof()) {
|
||||
const auto& entry = events_r.get<Event1Entry>();
|
||||
this->add_event(entry.event_id, entry.flags, floor, entry.action_stream_offset + action_stream_base_offset);
|
||||
this->add_event(entry.event_id, entry.flags, floor, entry.section, entry.wave_number, entry.action_stream_offset + action_stream_base_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1638,6 +1665,154 @@ Map::Enemy& Map::find_enemy(uint8_t floor, EnemyType type) {
|
||||
throw out_of_range("enemy not found");
|
||||
}
|
||||
|
||||
std::vector<Map::Object*> Map::get_objects(uint8_t floor, uint16_t section, uint16_t group) {
|
||||
uint64_t k = section_index_key(floor, section, group);
|
||||
vector<Object*> ret;
|
||||
for (auto its = this->floor_section_and_group_to_object_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->objects.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Enemy*> Map::get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
uint64_t k = section_index_key(floor, section, wave_number);
|
||||
vector<Enemy*> ret;
|
||||
for (auto its = this->floor_section_and_wave_number_to_enemy_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->enemies.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Event*> Map::get_events(uint8_t floor, uint16_t section, uint16_t wave_number) {
|
||||
uint64_t k = section_index_key(floor, section, wave_number);
|
||||
vector<Event*> ret;
|
||||
for (auto its = this->floor_section_and_wave_number_to_event_index.equal_range(k); its.first != its.second; its.first++) {
|
||||
ret.emplace_back(&this->events.at(its.first->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Map::Event*> Map::get_events(uint8_t floor) {
|
||||
uint64_t k_start = (static_cast<uint64_t>(floor) << 32);
|
||||
uint64_t k_end = (static_cast<uint64_t>(floor + 1) << 32);
|
||||
vector<Event*> ret;
|
||||
for (auto it = this->floor_and_event_id_to_index.lower_bound(k_start);
|
||||
(it != this->floor_and_event_id_to_index.end()) && (it->first < k_end);
|
||||
it++) {
|
||||
ret.emplace_back(&this->events.at(it->second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename EntryT>
|
||||
static string disassemble_vector_file_t(const void* data, size_t size, size_t* entry_number, char type_ch) {
|
||||
deque<string> ret;
|
||||
StringReader r(data, size);
|
||||
|
||||
size_t local_entry_number = 0;
|
||||
if (!entry_number) {
|
||||
entry_number = &local_entry_number;
|
||||
}
|
||||
|
||||
while (r.remaining() >= sizeof(EntryT)) {
|
||||
string o_str = r.get<EntryT>().str();
|
||||
ret.emplace_back(string_printf("/* %c-%zX */ %s", type_ch, (*entry_number)++, o_str.c_str()));
|
||||
}
|
||||
if (r.remaining()) {
|
||||
ret.emplace_back("// Warning: section size is not a multiple of entry size");
|
||||
size_t size = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(size), size));
|
||||
}
|
||||
return join(ret, "\n");
|
||||
}
|
||||
|
||||
string Map::disassemble_objects_data(const void* data, size_t size, size_t* object_number) {
|
||||
return disassemble_vector_file_t<ObjectEntry>(data, size, object_number, 'K');
|
||||
}
|
||||
|
||||
string Map::disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number) {
|
||||
return disassemble_vector_file_t<EnemyEntry>(data, size, enemy_number, 'S');
|
||||
}
|
||||
|
||||
string Map::disassemble_wave_events_data(const void* data, size_t size, uint8_t floor) {
|
||||
deque<string> ret;
|
||||
StringReader r(data, size);
|
||||
|
||||
const auto& evt_header = r.get<EventsSectionHeader>();
|
||||
if (evt_header.format == 0x65767432) { // 'evt2'
|
||||
ret.emplace_back(".evt2_format"); // TODO
|
||||
size_t size = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(size), size));
|
||||
} else {
|
||||
auto action_stream_r = r.sub(evt_header.action_stream_offset);
|
||||
for (size_t z = 0; z < evt_header.entry_count; z++) {
|
||||
const auto& entry = r.get<Event1Entry>();
|
||||
ret.emplace_back(string_printf("/* W-%02hhX-%" PRIX32 " */ [Event1Entry flags=%04hX type=%04hX section=%04hX wave_number=%04hX delay=%" PRIu32 "]",
|
||||
floor,
|
||||
entry.event_id.load(),
|
||||
entry.flags.load(),
|
||||
entry.event_type.load(),
|
||||
entry.section.load(),
|
||||
entry.wave_number.load(),
|
||||
entry.delay.load()));
|
||||
auto ev_actions_r = action_stream_r.sub(entry.action_stream_offset);
|
||||
bool should_continue = true;
|
||||
while (!ev_actions_r.eof() && should_continue) {
|
||||
uint8_t opcode = ev_actions_r.get_u8();
|
||||
switch (opcode) {
|
||||
case 0x00:
|
||||
ret.emplace_back(string_printf(" 00 nop"));
|
||||
break;
|
||||
case 0x01:
|
||||
ret.emplace_back(string_printf(" 01 stop"));
|
||||
should_continue = false;
|
||||
break;
|
||||
case 0x08: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t group = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 08 %04hX %04hX construct_objects section=%04hX group=%04hX",
|
||||
section, group, section, group));
|
||||
break;
|
||||
}
|
||||
case 0x09: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t wave_number = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 09 %04hX %04hX construct_enemies section=%04hX wave_number=%04hX",
|
||||
section, wave_number, section, wave_number));
|
||||
break;
|
||||
}
|
||||
case 0x0A: {
|
||||
uint16_t id = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0A %04hX enable_switch_flag id=%04hX", id, id));
|
||||
break;
|
||||
}
|
||||
case 0x0B: {
|
||||
uint16_t id = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0B %04hX disable_switch_flag id=%04hX", id, id));
|
||||
break;
|
||||
}
|
||||
case 0x0C: {
|
||||
uint32_t event_id = ev_actions_r.get_u32l();
|
||||
ret.emplace_back(string_printf(" 0C %08" PRIX32 " trigger_event event_id=%08" PRIX32, event_id, event_id));
|
||||
break;
|
||||
}
|
||||
case 0x0D: {
|
||||
uint16_t section = ev_actions_r.get_u16l();
|
||||
uint16_t wave_number = ev_actions_r.get_u16l();
|
||||
ret.emplace_back(string_printf(" 0D %04hX %04hX construct_enemies_stop section=%04hX wave_number=%04hX",
|
||||
section, wave_number, section, wave_number));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret.emplace_back(string_printf(" %02hhX .invalid", opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return join(ret, "\n");
|
||||
}
|
||||
|
||||
string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
auto all_floor_sections = Map::collect_quest_map_data_sections(data, size);
|
||||
|
||||
@@ -1651,56 +1826,34 @@ string Map::disassemble_quest_data(const void* data, size_t size) {
|
||||
if (floor_sections.objects != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".objects %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.objects);
|
||||
auto sub_r = r.sub(floor_sections.objects + sizeof(SectionHeader), header.data_size);
|
||||
while (sub_r.remaining() >= sizeof(ObjectEntry)) {
|
||||
string o_str = sub_r.get<ObjectEntry>().str();
|
||||
ret.emplace_back(string_printf("/* K-%zX */ %s", object_number++, o_str.c_str()));
|
||||
}
|
||||
if (sub_r.remaining()) {
|
||||
ret.emplace_back("// Warning: object section size is not a multiple of object entry size");
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader) + r.where();
|
||||
size_t bytes = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), bytes, offset));
|
||||
}
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader);
|
||||
ret.emplace_back(Map::disassemble_objects_data(r.pgetv(offset, header.data_size), header.data_size, &object_number));
|
||||
}
|
||||
|
||||
if (floor_sections.enemies != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".enemies %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.enemies);
|
||||
auto sub_r = r.sub(floor_sections.enemies + sizeof(SectionHeader), header.data_size);
|
||||
while (sub_r.remaining() >= sizeof(EnemyEntry)) {
|
||||
string e_str = sub_r.get<EnemyEntry>().str();
|
||||
ret.emplace_back(string_printf("/* entry %zX */ %s", enemy_number++, e_str.c_str()));
|
||||
}
|
||||
if (sub_r.remaining()) {
|
||||
ret.emplace_back("// Warning: enemy section size is not a multiple of enemy entry size");
|
||||
size_t offset = floor_sections.objects + sizeof(SectionHeader) + r.where();
|
||||
size_t bytes = r.remaining();
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), bytes, offset));
|
||||
}
|
||||
size_t offset = floor_sections.enemies + sizeof(SectionHeader);
|
||||
ret.emplace_back(Map::disassemble_enemies_data(r.pgetv(offset, header.data_size), header.data_size, &enemy_number));
|
||||
}
|
||||
|
||||
// TODO: Add disassembly for these section types
|
||||
if (floor_sections.wave_events != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".wave_events %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.wave_events);
|
||||
size_t offset = floor_sections.wave_events + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(Map::disassemble_wave_events_data(r.pgetv(offset, header.data_size), header.data_size, floor));
|
||||
}
|
||||
if (floor_sections.random_enemy_locations != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".random_enemy_locations %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
||||
size_t offset = floor_sections.random_enemy_locations + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(format_data(sub_r.getv(sub_r.remaining()), header.data_size, offset));
|
||||
}
|
||||
if (floor_sections.random_enemy_definitions != 0xFFFFFFFF) {
|
||||
ret.emplace_back(string_printf(".random_enemy_definitions %zu", floor));
|
||||
const auto& header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
||||
size_t offset = floor_sections.random_enemy_definitions + sizeof(SectionHeader);
|
||||
auto sub_r = r.sub(offset, header.data_size);
|
||||
ret.emplace_back(format_data(r.getv(r.remaining()), header.data_size, offset));
|
||||
ret.emplace_back(format_data(sub_r.getv(sub_r.remaining()), header.data_size, offset));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-6
@@ -103,6 +103,10 @@ struct Map {
|
||||
|
||||
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
// Bits in flags:
|
||||
// 0004 = is active
|
||||
// 0008 = post-wave actions have been run
|
||||
// 0010 = all enemies killed
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
@@ -208,10 +212,11 @@ struct Map {
|
||||
// TODO: Add more fields in here if we ever care about them. Currently we
|
||||
// only care about boxes with fixed item drops.
|
||||
size_t source_index;
|
||||
uint16_t object_id;
|
||||
uint8_t floor;
|
||||
uint16_t object_id;
|
||||
uint16_t base_type;
|
||||
uint16_t section;
|
||||
uint16_t group;
|
||||
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
float param3; // If == 0, the item should be varied by difficulty and area
|
||||
uint32_t param4;
|
||||
@@ -239,23 +244,35 @@ struct Map {
|
||||
uint16_t enemy_id;
|
||||
uint16_t total_damage;
|
||||
uint32_t game_flags; // From 6x0A
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
EnemyType type;
|
||||
uint8_t floor;
|
||||
uint8_t state_flags;
|
||||
|
||||
Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type);
|
||||
Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type);
|
||||
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
struct Event {
|
||||
uint32_t event_id;
|
||||
uint16_t flags;
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
uint8_t floor;
|
||||
uint32_t action_stream_offset;
|
||||
std::vector<size_t> enemy_indexes;
|
||||
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
@@ -306,7 +323,13 @@ struct Map {
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
|
||||
void add_event(uint32_t event_id, uint16_t flags, uint8_t floor, uint32_t action_stream_offset);
|
||||
void add_event(
|
||||
uint32_t event_id,
|
||||
uint16_t flags,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
uint32_t action_stream_offset);
|
||||
Event& get_event(uint8_t floor, uint32_t event_id);
|
||||
const Event& get_event(uint8_t floor, uint32_t event_id) const;
|
||||
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
@@ -330,7 +353,14 @@ struct Map {
|
||||
|
||||
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
|
||||
Enemy& find_enemy(uint8_t floor, EnemyType type);
|
||||
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor);
|
||||
|
||||
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
|
||||
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
|
||||
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
@@ -343,7 +373,10 @@ struct Map {
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
std::vector<Event> events;
|
||||
std::string event_action_stream;
|
||||
std::unordered_map<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::map<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
|
||||
};
|
||||
|
||||
class SetDataTableBase {
|
||||
|
||||
+20
-6
@@ -395,17 +395,31 @@ void PatchServer::on_client_error(Channel& ch, short events) {
|
||||
}
|
||||
|
||||
PatchServer::PatchServer(shared_ptr<const Config> config)
|
||||
: base(event_base_new(), event_base_free),
|
||||
config(config),
|
||||
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free),
|
||||
th(&PatchServer::thread_fn, this) {}
|
||||
: config(config) {
|
||||
if (config->shared_base) {
|
||||
this->base = config->shared_base;
|
||||
this->base_is_shared = true;
|
||||
} else {
|
||||
this->base.reset(event_base_new(), event_base_free);
|
||||
this->base_is_shared = false;
|
||||
}
|
||||
this->destroy_clients_ev.reset(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
|
||||
if (!this->base_is_shared) {
|
||||
this->th = thread(&PatchServer::thread_fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::schedule_stop() {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
if (!this->base_is_shared) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::wait_for_stop() {
|
||||
this->th.join();
|
||||
if (!this->base_is_shared) {
|
||||
this->th.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
std::string message;
|
||||
std::shared_ptr<const LicenseIndex> license_index;
|
||||
std::shared_ptr<const PatchFileIndex> patch_file_index;
|
||||
std::shared_ptr<struct event_base> shared_base;
|
||||
};
|
||||
|
||||
PatchServer() = delete;
|
||||
@@ -86,6 +87,7 @@ private:
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
bool base_is_shared;
|
||||
std::shared_ptr<const Config> config;
|
||||
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
|
||||
@@ -1091,3 +1091,70 @@ SymbolChat::SymbolChat()
|
||||
: spec(0),
|
||||
corner_objects(0x00FF),
|
||||
face_parts() {}
|
||||
|
||||
void RecentSwitchFlags::add(uint16_t flag_num) {
|
||||
if ((flag_num != ((this->flag_nums >> 48) & 0xFFFF)) &&
|
||||
(flag_num != ((this->flag_nums >> 32) & 0xFFFF)) &&
|
||||
(flag_num != ((this->flag_nums >> 16) & 0xFFFF)) &&
|
||||
(flag_num != (this->flag_nums & 0xFFFF))) {
|
||||
this->flag_nums = this->flag_nums << 16 | flag_num;
|
||||
}
|
||||
}
|
||||
|
||||
string RecentSwitchFlags::enable_commands(uint8_t floor) const {
|
||||
StringWriter w;
|
||||
uint64_t flag_nums = this->flag_nums;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
uint16_t flag_num = flag_nums;
|
||||
if (flag_num == 0xFFFF) {
|
||||
continue;
|
||||
}
|
||||
w.put(G_SwitchStateChanged_6x05{{0x05, 0x03, 0xFFFF}, 0, 0, flag_num, static_cast<uint8_t>(floor), 0x01});
|
||||
flag_nums >>= 16;
|
||||
}
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
const QuestFlagsForDifficulty bb_quest_flag_apply_mask{{
|
||||
// clang-format off
|
||||
/* 0000 */ 0x00, 0x3F, 0xFF, 0xE3, 0xE0, 0xFF, 0xFF, 0x00,
|
||||
/* 0040 */ 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
|
||||
/* 0080 */ 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 00C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x00,
|
||||
/* 0140 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
/* 0180 */ 0xFE, 0x00, 0x7F, 0xFE, 0x0F, 0xFF, 0xFF, 0x80,
|
||||
/* 01C0 */ 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF,
|
||||
/* 0200 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
|
||||
/* 0240 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0280 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
|
||||
/* 02C0 */ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0300 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0340 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0380 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 03C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// clang-format on
|
||||
|
||||
// The flags in the above mask are:
|
||||
// 000A 000B 000C 000D 000E 000F 0010 0011 0012 0013 0014 0015 0016 0017
|
||||
// 0018 0019 001A 001E 001F 0020 0021 0022 0028 0029 002A 002B 002C 002D
|
||||
// 002E 002F 0030 0031 0032 0033 0034 0035 0036 0037 0046 0047 0048 0049
|
||||
// 004A 004B 004C 004D 004E 004F 0050 0051 0052 0053 0054 0055 0056 0057
|
||||
// 0058 0059 005A 005B 005C 005D 005E 005F 0060 0061 0062 0063 0097 0098
|
||||
// 0099 009A 012D 012E 012F 0130 0131 0132 0133 0134 0135 0140 0141 0142
|
||||
// 0143 0144 0145 0146 0147 0148 0149 014A 014B 014C 014D 014E 014F 0150
|
||||
// 0151 0152 0153 0154 0155 0156 0157 0158 0159 015A 015B 015C 015D 015E
|
||||
// 015F 0160 0161 0162 0163 0164 0165 0166 0167 0168 0169 016A 016B 016C
|
||||
// 016D 016E 016F 0170 0171 0172 0173 0174 0175 0176 0177 0178 0179 017A
|
||||
// 017B 017C 017D 017E 017F 0180 0181 0182 0183 0184 0185 0186 0191 0192
|
||||
// 0193 0194 0195 0196 0197 0198 0199 019A 019B 019C 019D 019E 01A4 01A5
|
||||
// 01A6 01A7 01A8 01A9 01AA 01AB 01AC 01AD 01AE 01AF 01B0 01B1 01B2 01B3
|
||||
// 01B4 01B5 01B6 01B7 01B8 01C2 01C3 01C4 01C5 01C6 01C7 01C8 01C9 01CA
|
||||
// 01CB 01CC 01CD 01CE 01CF 01D0 01D1 01D2 01D3 01D4 01D5 01D6 01F4 01F5
|
||||
// 01F6 01F7 01F8 01F9 01FA 01FB 01FC 01FD 01FE 01FF 0200 0201 0202 0203
|
||||
// 0204 0205 0206 0207 0208 0209 020A 020B 020C 020D 020E 020F 0210 0211
|
||||
// 0212 0213 0214 0215 0216 0217 0218 0219 021A 021B 021C 021D 021E 021F
|
||||
// 0220 0221 0222 0223 0224 0225 0226 0227 0228 0229 022A 022B 022C 022D
|
||||
// 022E 022F 0230 0231 0232 0233 0234 0235 02BD 02BE 02BF 02C0 02C1 02C2
|
||||
// 02C3 02C4
|
||||
}};
|
||||
|
||||
@@ -565,6 +565,20 @@ struct QuestFlagsV1 {
|
||||
operator QuestFlags() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct SwitchFlags {
|
||||
parray<parray<uint8_t, 0x20>, 0x12> data;
|
||||
|
||||
inline bool get(uint8_t floor, uint16_t flag_num) const {
|
||||
return this->data[floor][flag_num >> 3] & (0x80 >> (flag_num & 7));
|
||||
}
|
||||
inline void set(uint8_t floor, uint16_t flag_num) {
|
||||
this->data[floor][flag_num >> 3] |= (0x80 >> (flag_num & 7));
|
||||
}
|
||||
inline void clear(uint8_t floor, uint16_t flag_num) {
|
||||
this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
struct BattleRules {
|
||||
enum class TechDiskMode : uint8_t {
|
||||
ALLOW = 0,
|
||||
@@ -719,3 +733,17 @@ struct SymbolChat {
|
||||
|
||||
SymbolChat();
|
||||
} __attribute__((packed));
|
||||
|
||||
struct RecentSwitchFlags {
|
||||
uint64_t flag_nums = 0xFFFFFFFFFFFFFFFF;
|
||||
|
||||
inline void clear() {
|
||||
this->flag_nums = 0xFFFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
void add(uint16_t flag_num);
|
||||
|
||||
std::string enable_commands(uint8_t floor) const;
|
||||
};
|
||||
|
||||
extern const QuestFlagsForDifficulty bb_quest_flag_apply_mask;
|
||||
|
||||
@@ -1946,15 +1946,13 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
if (!data.empty()) {
|
||||
if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
|
||||
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
|
||||
if (ses->last_switch_enabled_command.header.subcommand == 0x05) {
|
||||
ses->log.info("Switch assist: replaying previous enable command");
|
||||
ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
if ((cmd.flags & 1) && (cmd.header.object_id != 0xFFFF)) {
|
||||
ses->recent_switch_flags.add(cmd.switch_flag_num);
|
||||
string commands = ses->recent_switch_flags.enable_commands(ses->floor);
|
||||
if (!commands.empty()) {
|
||||
ses->server_channel.send(0x60, 0x00, commands);
|
||||
ses->client_channel.send(0x60, 0x00, commands);
|
||||
}
|
||||
ses->last_switch_enabled_command = cmd;
|
||||
}
|
||||
|
||||
} else if (data[0] == 0x21) {
|
||||
@@ -1962,7 +1960,7 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
ses->floor = cmd.floor;
|
||||
|
||||
} else if (data[0] == 0x0C) {
|
||||
if (is_v1_or_v2(ses->version()) && ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
}
|
||||
|
||||
@@ -537,7 +537,6 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
lobby_mode(GameMode::NORMAL),
|
||||
lobby_episode(Episode::EP1),
|
||||
lobby_random_seed(0) {
|
||||
this->last_switch_enabled_command.header.subcommand = 0;
|
||||
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public:
|
||||
Client::Config config;
|
||||
// A null handler in here means to forward the response to the remote server
|
||||
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
|
||||
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
ItemData next_drop_item;
|
||||
uint32_t next_item_id;
|
||||
|
||||
|
||||
+16
-10
@@ -490,7 +490,7 @@ QuestIndex::QuestIndex(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto add_file = [&](map<string, FileData>& files, const string& basename, const string& filename, string&& value) {
|
||||
auto add_file = [&](map<string, FileData>& files, const string& basename, const string& filename, string&& value, bool check_chunk_size) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("file " + basename + " exists in multiple categories");
|
||||
}
|
||||
@@ -498,6 +498,12 @@ QuestIndex::QuestIndex(
|
||||
if (!files.emplace(basename, FileData{filename, data_ptr}).second) {
|
||||
throw runtime_error("file " + basename + " already exists");
|
||||
}
|
||||
// There is a bug in the client that prevents quests from loading properly
|
||||
// if any file's size is a multiple of 0x400. See the comments on the 13
|
||||
// command in CommandFormats.hh for more details.
|
||||
if (check_chunk_size && !(data_ptr->size() & 0x3FF)) {
|
||||
data_ptr->push_back(0x00);
|
||||
}
|
||||
};
|
||||
|
||||
string cat_path = directory + "/" + cat->directory_name;
|
||||
@@ -544,26 +550,26 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
|
||||
if (extension == "json") {
|
||||
add_file(json_files, file_basename, orig_filename, std::move(file_data));
|
||||
add_file(json_files, file_basename, orig_filename, std::move(file_data), false);
|
||||
} else if (extension == "bin" || extension == "mnm") {
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(file_data));
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
} else if (extension == "bind" || extension == "mnmd") {
|
||||
add_file(bin_files, file_basename, orig_filename, prs_compress_optimal(file_data));
|
||||
add_file(bin_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
} else if (extension == "dat") {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(file_data));
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
} else if (extension == "datd") {
|
||||
add_file(dat_files, file_basename, orig_filename, prs_compress_optimal(file_data));
|
||||
add_file(dat_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
} else if (extension == "pvr") {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(file_data));
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
} else if (extension == "qst") {
|
||||
auto files = decode_qst_data(file_data);
|
||||
for (auto& it : files) {
|
||||
if (ends_with(it.first, ".bin")) {
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(it.second));
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else if (ends_with(it.first, ".dat")) {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(it.second));
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else if (ends_with(it.first, ".pvr")) {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(it.second));
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else {
|
||||
throw runtime_error("qst file contains unsupported file type: " + it.first);
|
||||
}
|
||||
|
||||
+146
-118
@@ -1720,7 +1720,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
}
|
||||
}
|
||||
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
|
||||
send_text_message(l, "$C6Recording enabled");
|
||||
send_text_message(l, "$C7Recording enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1795,10 +1795,10 @@ static void on_E2_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string&) {
|
||||
if (tourn) {
|
||||
send_ep3_tournament_entry_list(c, tourn, false);
|
||||
} else {
|
||||
send_lobby_message_box(c, "$C6The tournament\nhas concluded.");
|
||||
send_lobby_message_box(c, "$C7The tournament\nhas concluded.");
|
||||
}
|
||||
} else {
|
||||
send_lobby_message_box(c, "$C6You are not\nregistered in a\ntournament.");
|
||||
send_lobby_message_box(c, "$C7You are not\nregistered in a\ntournament.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1822,7 +1822,7 @@ static void on_E2_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string&) {
|
||||
}
|
||||
case 0x03: // Create tournament spectator team (get battle list)
|
||||
case 0x04: // Join tournament spectator team (get team list)
|
||||
send_lobby_message_box(c, "$C6Use View Regular\nBattle for this");
|
||||
send_lobby_message_box(c, "$C7Use View Regular\nBattle for this");
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid tournament operation");
|
||||
@@ -1857,7 +1857,7 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
bool is_download_quest = !c->lobby.lock();
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_quest_info(c, "$C6Quests are not available.", is_download_quest);
|
||||
send_quest_info(c, "$C7Quests are not available.", is_download_quest);
|
||||
} else {
|
||||
auto q = quest_index->get(cmd.item_id);
|
||||
if (!q) {
|
||||
@@ -1889,72 +1889,43 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
} else {
|
||||
string info;
|
||||
for (size_t x = 0; x < game->max_clients; x++) {
|
||||
const auto& game_c = game->clients[x];
|
||||
if (game_c.get()) {
|
||||
auto player = game_c->character();
|
||||
string name = escape_player_name(player->disp.name.decode(game_c->language()));
|
||||
if (game->is_ep3()) {
|
||||
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
|
||||
x + 1, name.c_str(), player->disp.stats.level + 1);
|
||||
} else {
|
||||
info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n",
|
||||
x + 1, name.c_str(),
|
||||
abbreviation_for_char_class(player->disp.visual.char_class),
|
||||
player->disp.stats.level + 1);
|
||||
if (c->last_game_info_requested != game->lobby_id) {
|
||||
// Send page 1 (players)
|
||||
c->last_game_info_requested = game->lobby_id;
|
||||
for (size_t x = 0; x < game->max_clients; x++) {
|
||||
const auto& game_c = game->clients[x];
|
||||
if (game_c.get()) {
|
||||
auto player = game_c->character();
|
||||
string name = escape_player_name(player->disp.name.decode(game_c->language()));
|
||||
info += string_printf("%s\n %s Lv%" PRIu32 " %c\n",
|
||||
name.c_str(),
|
||||
name_for_char_class(player->disp.visual.char_class),
|
||||
player->disp.stats.level + 1,
|
||||
char_for_language_code(game_c->language()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info += string_printf("%s %c %s %s\n",
|
||||
abbreviation_for_episode(game->episode),
|
||||
abbreviation_for_difficulty(game->difficulty),
|
||||
abbreviation_for_mode(game->mode),
|
||||
abbreviation_for_section_id(game->effective_section_id()));
|
||||
// If page 1 is blank (there are no players) or we sent page 1 last
|
||||
// time, send page 2 (extended info)
|
||||
if (info.empty()) {
|
||||
c->last_game_info_requested = 0;
|
||||
info += string_printf("Section ID: %s\n", name_for_section_id(game->effective_section_id()));
|
||||
if (game->max_level != 0xFFFFFFFF) {
|
||||
info += string_printf("Req. level: %" PRIu32 "-%" PRIu32 "\n", game->min_level + 1, game->max_level + 1);
|
||||
} else if (game->min_level != 0) {
|
||||
info += string_printf("Req. level: %" PRIu32 "+\n", game->min_level + 1);
|
||||
}
|
||||
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
info += string_printf("%s\n", name_for_enum(game->base_version));
|
||||
}
|
||||
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
vector<const char*> flags_tokens;
|
||||
string quest_name;
|
||||
if (game->check_flag(Lobby::Flag::CHEATS_ENABLED)) {
|
||||
flags_tokens.emplace_back("$C6C$C7");
|
||||
info += "$C6Cheats enabled$C7\n";
|
||||
}
|
||||
if (game->check_flag(Lobby::Flag::PERSISTENT)) {
|
||||
flags_tokens.emplace_back("$C6P$C7");
|
||||
}
|
||||
if (!game->password.empty()) {
|
||||
flags_tokens.emplace_back("$C4L$C7");
|
||||
}
|
||||
if (game->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) {
|
||||
flags_tokens.emplace_back("$C8ST$C7");
|
||||
}
|
||||
if (game->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
|
||||
flags_tokens.emplace_back("$C8NS$C7");
|
||||
}
|
||||
if (game->quest) {
|
||||
flags_tokens.emplace_back(game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) ? "$C3JQ$C7" : "$C3Q$C7");
|
||||
quest_name = remove_color(game->quest->name);
|
||||
} else if (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) {
|
||||
flags_tokens.emplace_back("$C3JQ$C7");
|
||||
} else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||
flags_tokens.emplace_back("$C3Q$C7");
|
||||
} else if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
|
||||
flags_tokens.emplace_back("$C3B$C7");
|
||||
}
|
||||
info += ("Flags: " + join(flags_tokens, ",") + "\n");
|
||||
if (!quest_name.empty()) {
|
||||
info += ("Q: $C6" + quest_name + "$C7\n");
|
||||
}
|
||||
info += string_printf("Version: %s\n", name_for_enum(game->base_version));
|
||||
|
||||
} else {
|
||||
bool cheats_enabled = game->check_flag(Lobby::Flag::CHEATS_ENABLED);
|
||||
bool locked = !game->password.empty();
|
||||
if (cheats_enabled && locked) {
|
||||
info += "$C4Locked$C7, $C6cheats on$C7\n";
|
||||
} else if (cheats_enabled) {
|
||||
info += "$C6Cheats on$C7\n";
|
||||
} else if (locked) {
|
||||
info += "$C4Locked$C7\n";
|
||||
info += "$C6Persistence enabled$C7\n";
|
||||
}
|
||||
|
||||
if (game->quest) {
|
||||
@@ -1965,15 +1936,26 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
info += "$C6Quest in progress\n";
|
||||
} else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||
info += "$C4Quest in progress\n";
|
||||
} else if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
|
||||
info += "$C4Battle in progress\n";
|
||||
}
|
||||
|
||||
if (game->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
|
||||
info += "$C4View Battle forbidden\n";
|
||||
switch (game->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
info += "$C6Drops disabled$C7\n";
|
||||
break;
|
||||
case Lobby::DropMode::CLIENT:
|
||||
info += "$C6Client drops$C7\n";
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
info += "$C6Server drops$C7\n";
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_PRIVATE:
|
||||
info += "$C6Private drops$C7\n";
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_DUPLICATE:
|
||||
info += "$C6Duplicate drops$C7\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
strip_trailing_whitespace(info);
|
||||
send_ship_info(c, info);
|
||||
}
|
||||
@@ -2500,7 +2482,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
auto s = c->require_server_state();
|
||||
auto game = s->find_lobby(item_id);
|
||||
if (!game) {
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because it no\nlonger exists.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because it no\nlonger exists.");
|
||||
break;
|
||||
}
|
||||
switch (game->join_error_for_client(c, &password)) {
|
||||
@@ -2515,54 +2497,50 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// leader)
|
||||
if (game->count_clients() == 1) {
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
|
||||
// TODO: Eventually, we want to send the enemy and set states too,
|
||||
// but currently this doesn't work well. Instead, we reset their
|
||||
// flags so it's as if they were never defeated.
|
||||
// c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE);
|
||||
if (game->map) {
|
||||
for (auto& enemy : game->map->enemies) {
|
||||
enemy.game_flags = 0;
|
||||
enemy.total_damage = 0;
|
||||
enemy.state_flags = 0;
|
||||
}
|
||||
}
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_OBJECT_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Lobby::JoinError::FULL:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\nfull.");
|
||||
break;
|
||||
case Lobby::JoinError::VERSION_CONFLICT:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
|
||||
break;
|
||||
case Lobby::JoinError::QUEST_IN_PROGRESS:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because a\nquest is already\nin progress.");
|
||||
break;
|
||||
case Lobby::JoinError::BATTLE_IN_PROGRESS:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because a\nbattle is already\nin progress.");
|
||||
break;
|
||||
case Lobby::JoinError::LOADING:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
|
||||
break;
|
||||
case Lobby::JoinError::SOLO:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\na Solo Mode game.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\na Solo Mode game.");
|
||||
break;
|
||||
case Lobby::JoinError::INCORRECT_PASSWORD:
|
||||
send_lobby_message_box(c, "$C6Incorrect password.");
|
||||
send_lobby_message_box(c, "$C7Incorrect password.");
|
||||
break;
|
||||
case Lobby::JoinError::LEVEL_TOO_LOW:
|
||||
send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame.");
|
||||
case Lobby::JoinError::LEVEL_TOO_LOW: {
|
||||
string msg = string_printf("$C7You must be level\n%zu or above to\njoin this game.",
|
||||
static_cast<size_t>(game->min_level + 1));
|
||||
send_lobby_message_box(c, msg);
|
||||
break;
|
||||
case Lobby::JoinError::LEVEL_TOO_HIGH:
|
||||
send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame.");
|
||||
}
|
||||
case Lobby::JoinError::LEVEL_TOO_HIGH: {
|
||||
string msg = string_printf("$C7You must be level\n%zu or below to\njoin this game.",
|
||||
static_cast<size_t>(game->max_level + 1));
|
||||
send_lobby_message_box(c, msg);
|
||||
break;
|
||||
}
|
||||
case Lobby::JoinError::NO_ACCESS_TO_QUEST:
|
||||
send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game, or there\nis no space for another\nplayer in the quest.");
|
||||
send_lobby_message_box(c, "$C7You don't have access\nto the quest in progress\nin this game, or there\nis no space for another\nplayer in the quest.");
|
||||
break;
|
||||
default:
|
||||
send_lobby_message_box(c, "$C6You cannot join this\ngame.");
|
||||
send_lobby_message_box(c, "$C7You cannot join this\ngame.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -2572,7 +2550,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
auto s = c->require_server_state();
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_lobby_message_box(c, "$C6Quests are not available.");
|
||||
send_lobby_message_box(c, "$C7Quests are not available.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2593,12 +2571,12 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
auto s = c->require_server_state();
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_lobby_message_box(c, "$C6Quests are not\navailable.");
|
||||
send_lobby_message_box(c, "$C7Quests are not\navailable.");
|
||||
break;
|
||||
}
|
||||
auto q = quest_index->get(item_id);
|
||||
if (!q) {
|
||||
send_lobby_message_box(c, "$C6Quest does not exist.");
|
||||
send_lobby_message_box(c, "$C7Quest does not exist.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2606,21 +2584,21 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// Otherwise, they must be in a game to load a quest.
|
||||
auto l = c->lobby.lock();
|
||||
if (l && !l->is_game()) {
|
||||
send_lobby_message_box(c, "$C6Quests cannot be\nloaded in lobbies.");
|
||||
send_lobby_message_box(c, "$C7Quests cannot be\nloaded in lobbies.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (l) {
|
||||
if (q->episode == Episode::EP3) {
|
||||
send_lobby_message_box(c, "$C6Episode 3 quests\ncannot be loaded\nvia this interface.");
|
||||
send_lobby_message_box(c, "$C7Episode 3 quests\ncannot be loaded\nvia this interface.");
|
||||
break;
|
||||
}
|
||||
if (l->quest) {
|
||||
send_lobby_message_box(c, "$C6A quest is already\nin progress.");
|
||||
send_lobby_message_box(c, "$C7A quest is already\nin progress.");
|
||||
break;
|
||||
}
|
||||
if (l->quest_include_condition()(q) != QuestIndex::IncludeState::AVAILABLE) {
|
||||
send_lobby_message_box(c, "$C6This quest has not\nbeen unlocked for\nall players in this\ngame.");
|
||||
send_lobby_message_box(c, "$C7This quest has not\nbeen unlocked for\nall players in this\ngame.");
|
||||
break;
|
||||
}
|
||||
set_lobby_quest(l, q);
|
||||
@@ -2628,7 +2606,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
} else {
|
||||
auto vq = q->version(c->version(), c->language());
|
||||
if (!vq) {
|
||||
send_lobby_message_box(c, "$C6Quest does not exist\nfor this game version.");
|
||||
send_lobby_message_box(c, "$C7Quest does not exist\nfor this game version.");
|
||||
break;
|
||||
}
|
||||
// Episode 3 uses the download quest commands (A6/A7) but does not
|
||||
@@ -2709,7 +2687,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
throw runtime_error("non-Episode 3 client attempted to join tournament");
|
||||
}
|
||||
if (c->ep3_tournament_team.lock()) {
|
||||
send_lobby_message_box(c, "$C6You are registered\nin a different\ntournament already");
|
||||
send_lobby_message_box(c, "$C7You are registered\nin a different\ntournament already");
|
||||
break;
|
||||
}
|
||||
if (team_name.empty()) {
|
||||
@@ -2909,14 +2887,14 @@ static void on_A2(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
|
||||
|
||||
auto l = c->lobby.lock();
|
||||
if (!l || !l->is_game()) {
|
||||
send_lobby_message_box(c, "$C6Quests are not available\nin lobbies.");
|
||||
send_lobby_message_box(c, "$C7Quests are not available\nin lobbies.");
|
||||
return;
|
||||
}
|
||||
|
||||
// In Episode 3, there are no quest categories, so skip directly to the quest
|
||||
// filter menu.
|
||||
if (is_ep3(c->version())) {
|
||||
send_lobby_message_box(c, "$C6Episode 3 does not\nprovide online quests\nvia this interface.");
|
||||
send_lobby_message_box(c, "$C7Episode 3 does not\nprovide online quests\nvia this interface.");
|
||||
} else {
|
||||
QuestMenuType menu_type;
|
||||
if ((c->version() == Version::BB_V4) && flag) {
|
||||
@@ -3298,7 +3276,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
bb_player->choice_search_config = player->choice_search_config;
|
||||
try {
|
||||
Client::save_character_file(filename, c->system_file(), bb_player);
|
||||
send_text_message(c, "$C6Character data saved");
|
||||
send_text_message(c, "$C7Character data saved");
|
||||
} catch (const exception& e) {
|
||||
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
|
||||
}
|
||||
@@ -3881,19 +3859,18 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
|
||||
auto& result = results.emplace_back();
|
||||
result.guild_card_number = lc->license->serial_number;
|
||||
result.name.encode(lp->disp.name.decode(lc->language()), c->language());
|
||||
string info_string = string_printf("%s Lv%zu %s",
|
||||
string info_string = string_printf("%s Lv%zu\n%s\n",
|
||||
name_for_char_class(lp->disp.visual.char_class),
|
||||
static_cast<size_t>(lp->disp.stats.level + 1),
|
||||
name_for_section_id(lp->disp.visual.section_id));
|
||||
abbreviation_for_section_id(lp->disp.visual.section_id));
|
||||
result.info_string.encode(info_string, c->language());
|
||||
string lobby_name = l->is_game() ? l->name : string_printf("BLOCK01-%02" PRIu32, l->lobby_id);
|
||||
string location_string;
|
||||
if (l->is_game()) {
|
||||
location_string = string_printf("%s,BLOCK01,%s", l->name.c_str(), s->name.c_str());
|
||||
location_string = string_printf("%s,,BLOCK01,%s", l->name.c_str(), s->name.c_str());
|
||||
} else if (l->is_ep3()) {
|
||||
location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", l->lobby_id - 15, s->name.c_str());
|
||||
location_string = string_printf("BLOCK01-C%02" PRIu32 ",,BLOCK01,%s", l->lobby_id - 15, s->name.c_str());
|
||||
} else {
|
||||
location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", l->lobby_id, s->name.c_str());
|
||||
location_string = string_printf("BLOCK01-%02" PRIu32 ",,BLOCK01,%s", l->lobby_id, s->name.c_str());
|
||||
}
|
||||
result.location_string.encode(location_string, c->language());
|
||||
result.reconnect_command_header.command = 0x19;
|
||||
@@ -3904,6 +3881,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
|
||||
result.meet_user.lobby_refs[0].menu_id = MenuID::LOBBY;
|
||||
result.meet_user.lobby_refs[0].item_id = l->lobby_id;
|
||||
result.meet_user.player_name.encode(lp->disp.name.decode(lc->language()), c->language());
|
||||
// The client can only handle 32 results
|
||||
if (results.size() >= 0x20) {
|
||||
break;
|
||||
}
|
||||
@@ -3911,7 +3889,21 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
|
||||
}
|
||||
}
|
||||
|
||||
send_command_vt(c, 0xC4, results.size(), results);
|
||||
if (results.empty()) {
|
||||
// There is a client bug that causes garbage to appear in the info window
|
||||
// when the server returns no entries in this command, since the client
|
||||
// tries to display the first entry in the list even if the list contains
|
||||
// "No player". If the server sends no entries at all, the entry will
|
||||
// uninitialized memory which can cause crashes on v2, so we send a blank
|
||||
// entry to prevent this.
|
||||
auto& result = results.emplace_back();
|
||||
result.reconnect_command_header.command = 0x00;
|
||||
result.reconnect_command_header.flag = 0x00;
|
||||
result.reconnect_command_header.size = 0x0000;
|
||||
send_command_vt(c, 0xC4, 0, results);
|
||||
} else {
|
||||
send_command_vt(c, 0xC4, results.size(), results);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_C3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
@@ -4117,7 +4109,8 @@ shared_ptr<Lobby> create_game_generic(
|
||||
if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
|
||||
// Note: We don't throw here because this is a situation players might
|
||||
// actually encounter while playing the game normally
|
||||
send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty");
|
||||
string msg = string_printf("You must be level %zu\nor above to play\nthis difficulty.", static_cast<size_t>(min_level + 1));
|
||||
send_lobby_message_box(c, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -4330,6 +4323,41 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->quest_flags_known = make_unique<QuestFlags>();
|
||||
}
|
||||
|
||||
if (s->unlock_all_areas) {
|
||||
static const vector<uint16_t> flags_ep1_v123 = {0x0017, 0x0020, 0x002A};
|
||||
static const vector<uint16_t> flags_ep1_v4 = {0x01F9, 0x0201, 0x0207};
|
||||
static const vector<uint16_t> flags_ep2_v123 = {0x004C, 0x004F, 0x0052};
|
||||
static const vector<uint16_t> flags_ep2_v4 = {0x021B, 0x0225, 0x022F};
|
||||
static const vector<uint16_t> flags_ep4_v4 = {0x02BD, 0x02BE, 0x02BF, 0x02C0, 0x02C1};
|
||||
|
||||
const vector<uint16_t>* flags_to_enable;
|
||||
switch (game->episode) {
|
||||
case Episode::EP1:
|
||||
flags_to_enable = is_v4(game->base_version) ? &flags_ep1_v4 : &flags_ep1_v123;
|
||||
break;
|
||||
case Episode::EP2:
|
||||
flags_to_enable = is_v4(game->base_version) ? &flags_ep2_v4 : &flags_ep2_v123;
|
||||
break;
|
||||
case Episode::EP4:
|
||||
flags_to_enable = &flags_ep1_v4;
|
||||
break;
|
||||
default:
|
||||
flags_to_enable = nullptr;
|
||||
}
|
||||
|
||||
if (flags_to_enable) {
|
||||
for (uint16_t flag_num : *flags_to_enable) {
|
||||
game->quest_flag_values->set(game->difficulty, flag_num);
|
||||
if (game->quest_flags_known) {
|
||||
game->quest_flags_known->set(game->difficulty, flag_num);
|
||||
}
|
||||
}
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
game->switch_flags = make_unique<SwitchFlags>();
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
@@ -4398,11 +4426,11 @@ static void on_0C_C1_E7_EC(shared_ptr<Client> c, uint16_t command, uint32_t, str
|
||||
}
|
||||
watched_lobby = s->find_lobby(cmd.item_id);
|
||||
if (!watched_lobby) {
|
||||
send_lobby_message_box(c, "$C6This game no longer\nexists");
|
||||
send_lobby_message_box(c, "$C7This game no longer\nexists");
|
||||
return;
|
||||
}
|
||||
if (watched_lobby->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
|
||||
send_lobby_message_box(c, "$C6This game does not\nallow spectators");
|
||||
send_lobby_message_box(c, "$C7This game does not\nallow spectators");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4460,11 +4488,11 @@ static void on_C1_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
episode = Episode::EP4;
|
||||
// Disallow battle/challenge in Ep4
|
||||
if (mode == GameMode::BATTLE) {
|
||||
send_lobby_message_box(c, "$C6Episode 4 does not\nsupport Battle Mode.");
|
||||
send_lobby_message_box(c, "$C7Episode 4 does not\nsupport Battle Mode.");
|
||||
return;
|
||||
}
|
||||
if (mode == GameMode::CHALLENGE) {
|
||||
send_lobby_message_box(c, "$C6Episode 4 does not\nsupport Challenge Mode.");
|
||||
send_lobby_message_box(c, "$C7Episode 4 does not\nsupport Challenge Mode.");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -4524,7 +4552,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
if (c) {
|
||||
c->ep3_prev_battle_record = l->battle_record;
|
||||
if ((s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
send_text_message(l, "$C6Recording complete");
|
||||
send_text_message(l, "$C7Recording complete");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+290
-58
@@ -485,11 +485,10 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
send_game_item_state(target);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x6E: {
|
||||
StringReader r(decompressed);
|
||||
const auto& dec_header = r.get<G_SyncSetFlagState_6x6E_Decompressed>();
|
||||
if (dec_header.total_size != dec_header.entity_set_flags_size + dec_header.event_set_flags_size + dec_header.unused_size) {
|
||||
if (dec_header.total_size != dec_header.entity_set_flags_size + dec_header.event_set_flags_size + dec_header.switch_flags_size) {
|
||||
throw runtime_error("incorrect size fields in 6x6E header");
|
||||
}
|
||||
|
||||
@@ -518,8 +517,9 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
for (size_t z = 0; z < min<size_t>(set_flags_header.num_object_sets, l->map->objects.size()); z++) {
|
||||
uint16_t flags = set_flags_r.get_u16l();
|
||||
if (flags != l->map->objects[z].set_flags) {
|
||||
l->log.warning("(K-%zX) Set flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
l->log.warning("(K-%zX) Set flags from client (%04hX) do not match set flags from map (%04hX)",
|
||||
z, flags, l->map->objects[z].set_flags);
|
||||
l->map->objects[z].set_flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,8 +533,9 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
for (size_t z = 0; z < min<size_t>(set_flags_header.num_enemy_sets, l->map->enemy_set_flags.size()); z++) {
|
||||
uint16_t flags = set_flags_r.get_u16l();
|
||||
if (flags != l->map->enemy_set_flags[z]) {
|
||||
l->log.warning("(S-%zX) Set flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
l->log.warning("(S-%zX) Set flags from client (%04hX) do not match set flags from map (%04hX)",
|
||||
z, flags, l->map->enemy_set_flags[z]);
|
||||
l->map->enemy_set_flags[z] = flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,32 +553,60 @@ static void on_sync_joining_player_compressed_state(shared_ptr<Client> c, uint8_
|
||||
}
|
||||
for (size_t z = 0; z < min<size_t>(num_event_flags, l->map->events.size()); z++) {
|
||||
uint16_t flags = event_set_flags_r.get_u16l();
|
||||
const auto& event = l->map->events[z];
|
||||
auto& event = l->map->events[z];
|
||||
if (flags != event.flags) {
|
||||
l->log.warning("(W-%02hhX-%" PRIX32 ") Event flags from client (%04hX) do not match flags from map (%04hX)",
|
||||
event.floor, event.event_id, flags, event.flags);
|
||||
event.flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t expected_unused_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
size_t target_unused_size = is_v1(target->version()) ? 0x200 : 0x240;
|
||||
if (dec_header.unused_size != expected_unused_size) {
|
||||
l->log.warning("Unused data size (0x%" PRIX32 ") does not match expected size (0x%zX)",
|
||||
dec_header.unused_size.load(), expected_unused_size);
|
||||
size_t expected_switch_flag_num_floors = is_v1(c->version()) ? 0x10 : 0x12;
|
||||
size_t expected_switch_flags_size = expected_switch_flag_num_floors * 0x20;
|
||||
if (dec_header.switch_flags_size != expected_switch_flags_size) {
|
||||
l->log.warning("Switch flags size (0x%" PRIX32 ") does not match expected size (0x%zX)",
|
||||
dec_header.switch_flags_size.load(), expected_switch_flags_size);
|
||||
} else {
|
||||
l->log.info("Switch flags size matches expected size (0x%zX)", expected_switch_flags_size);
|
||||
}
|
||||
if (dec_header.unused_size != target_unused_size) {
|
||||
l->log.info("Resizing unused data from 0x%" PRIX32 " bytes to 0x%zX bytes",
|
||||
dec_header.unused_size.load(), target_unused_size);
|
||||
if (dec_header.unused_size >= decompressed.size()) {
|
||||
throw runtime_error("unused size is too large");
|
||||
if (l->switch_flags) {
|
||||
StringReader switch_flags_r = r.sub(r.where() + dec_header.entity_set_flags_size + dec_header.event_set_flags_size);
|
||||
for (size_t floor = 0; floor < expected_switch_flag_num_floors; floor++) {
|
||||
// There is a bug in most (perhaps all) versions of the game, which
|
||||
// causes this array to be too small. It looks like Sega forgot to
|
||||
// account for the header (G_SyncSetFlagState_6x6E_Decompressed)
|
||||
// before compressing the buffer, so the game cuts off the last 8
|
||||
// bytes of the switch flags. Since this only affects the last floor,
|
||||
// which rarely has any switches on it (or is even accessible by the
|
||||
// player), it's not surprising that no one noticed this. But it does
|
||||
// mean we have to check switch_flags_r.eof() here.
|
||||
for (size_t z = 0; (z < 0x20) && !switch_flags_r.eof(); z++) {
|
||||
uint8_t& l_flags = l->switch_flags->data[floor][z];
|
||||
uint8_t r_flags = switch_flags_r.get_u8();
|
||||
if (l_flags != r_flags) {
|
||||
l->log.warning("Switch flags do not match at %02zX[%02zX] (expected %02hhX, received %02hhX)",
|
||||
floor, z, l_flags, r_flags);
|
||||
l_flags = r_flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
decompressed.resize(decompressed.size() - dec_header.unused_size.load() + target_unused_size, '\0');
|
||||
auto* wdec_header = reinterpret_cast<G_SyncSetFlagState_6x6E_Decompressed*>(decompressed.data());
|
||||
wdec_header->unused_size = target_unused_size;
|
||||
wdec_header->total_size = wdec_header->entity_set_flags_size + wdec_header->event_set_flags_size + wdec_header->unused_size;
|
||||
}
|
||||
|
||||
// size_t target_switch_flag_num_floors = is_v1(target->version()) ? 0x10 : 0x12;
|
||||
// size_t target_switch_flags_size = target_switch_flag_num_floors * 0x20;
|
||||
// if (dec_header.switch_flags_size != target_switch_flags_size) {
|
||||
// l->log.info("Resizing switch flags from 0x%" PRIX32 " bytes to 0x%zX bytes",
|
||||
// dec_header.switch_flags_size.load(), target_switch_flags_size);
|
||||
// if (dec_header.switch_flags_size >= decompressed.size()) {
|
||||
// throw runtime_error("switch flags size is too large");
|
||||
// }
|
||||
// decompressed.resize(decompressed.size() - dec_header.switch_flags_size.load() + target_switch_flags_size, '\0');
|
||||
// auto* wdec_header = reinterpret_cast<G_SyncSetFlagState_6x6E_Decompressed*>(decompressed.data());
|
||||
// wdec_header->switch_flags_size = target_switch_flags_size;
|
||||
// wdec_header->total_size = wdec_header->entity_set_flags_size + wdec_header->event_set_flags_size + wdec_header->switch_flags_size;
|
||||
// }
|
||||
|
||||
send_game_join_sync_command_compressed(
|
||||
target,
|
||||
compressed_data,
|
||||
@@ -615,9 +644,11 @@ static void on_sync_joining_player_quest_flags_t(shared_ptr<Client> c, uint8_t c
|
||||
|
||||
static void on_sync_joining_player_quest_flags(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
if (is_v1(c->version())) {
|
||||
on_sync_joining_player_quest_flags_t<G_SetQuestFlagsV1_6x6F>(c, command, flag, data, size);
|
||||
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_DCv1_6x6F>(c, command, flag, data, size);
|
||||
} else if (!is_v4(c->version())) {
|
||||
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_V2_V3_6x6F>(c, command, flag, data, size);
|
||||
} else {
|
||||
on_sync_joining_player_quest_flags_t<G_SetQuestFlagsV2V3V4_6x6F>(c, command, flag, data, size);
|
||||
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_BB_6x6F>(c, command, flag, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,7 +1038,7 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
parsed->visual.enforce_lobby_join_limits_for_version(target_v);
|
||||
if (s->version_name_colors) {
|
||||
parsed->visual.name_color = s->name_color_for_version(c_v);
|
||||
if (is_v1_or_v2(c_v)) {
|
||||
if (is_v1_or_v2(target_v)) {
|
||||
parsed->visual.compute_name_color_checksum();
|
||||
}
|
||||
}
|
||||
@@ -1335,8 +1366,9 @@ static void on_change_floor_6x1F(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
|
||||
} else {
|
||||
const auto& cmd = check_size_t<G_SetPlayerFloor_6x1F>(data, size);
|
||||
if (cmd.floor >= 0) {
|
||||
if (cmd.floor >= 0 && c->floor != static_cast<uint32_t>(cmd.floor)) {
|
||||
c->floor = cmd.floor;
|
||||
c->recent_switch_flags.clear();
|
||||
}
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -1344,21 +1376,22 @@ static void on_change_floor_6x1F(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
|
||||
static void on_change_floor_6x21(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_InterLevelWarp_6x21>(data, size);
|
||||
if (cmd.floor >= 0) {
|
||||
if (cmd.floor >= 0 && c->floor != static_cast<uint32_t>(cmd.floor)) {
|
||||
c->floor = cmd.floor;
|
||||
c->recent_switch_flags.clear();
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
// When a player dies, decrease their mag's synchro
|
||||
static void on_player_died(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_ClientIDHeader>(data, size, 0xFFFF);
|
||||
const auto& cmd = check_size_t<G_PlayerDied_6x4D>(data, size, 0xFFFF);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || (cmd.client_id != c->lobby_client_id)) {
|
||||
if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrease MAG's synchro
|
||||
try {
|
||||
auto& inventory = c->character()->inventory;
|
||||
size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG);
|
||||
@@ -1370,13 +1403,58 @@ static void on_player_died(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_player_revivable(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_PlayerRevivable_6x4E>(data, size, 0xFFFF);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
|
||||
// Revive if infinite HP is enabled
|
||||
bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE));
|
||||
if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
G_UseMedicalCenter_6x31 v2_cmd = {0x31, 0x01, c->lobby_client_id};
|
||||
G_RevivePlayer_V3_BB_6xA1 v3_cmd = {0xA1, 0x01, c->lobby_client_id};
|
||||
for (auto lc : l->clients) {
|
||||
if (!lc) {
|
||||
continue;
|
||||
}
|
||||
bool use_v3 = !is_v1_or_v2(lc->version()) || (lc->version() == Version::GC_NTE);
|
||||
const void* data = use_v3 ? static_cast<const void*>(&v3_cmd) : static_cast<const void*>(&v2_cmd);
|
||||
size_t size = use_v3 ? sizeof(v3_cmd) : sizeof(v2_cmd);
|
||||
if (lc == c) {
|
||||
send_protected_command(lc, data, size, false);
|
||||
} else {
|
||||
send_command(lc, 0x60, 0x00, data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_player_revived(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
check_size_t<G_PlayerRevived_6x4F>(data, size, 0xFFFF);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game()) {
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
bool player_cheats_enabled = !is_v1(c->version()) &&
|
||||
(l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE)));
|
||||
if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_received_condition(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_ClientIDHeader>(data, size, 0xFFFF);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game()) {
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
if (is_v1_or_v2(c->version()) && (cmd.client_id == c->lobby_client_id)) {
|
||||
if (cmd.client_id == c->lobby_client_id) {
|
||||
bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE));
|
||||
if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_remove_conditions(c);
|
||||
@@ -1469,18 +1547,25 @@ static void on_switch_state_changed(shared_ptr<Client> c, uint8_t command, uint8
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
|
||||
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
|
||||
if (!l->quest &&
|
||||
c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) &&
|
||||
(c->last_switch_enabled_command.header.subcommand == 0x05)) {
|
||||
c->log.info("[Switch assist] Replaying previous enable command");
|
||||
if (l->switch_flags) {
|
||||
if (cmd.flags & 1) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
} else {
|
||||
l->switch_flags->clear(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
}
|
||||
|
||||
if ((cmd.flags & 1) && cmd.header.object_id != 0xFFFF) {
|
||||
c->recent_switch_flags.add(cmd.switch_flag_num);
|
||||
if (!l->quest && c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message(c, "$C5Switch assist");
|
||||
}
|
||||
forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command));
|
||||
send_command_t(c, command, flag, c->last_switch_enabled_command);
|
||||
string commands = c->recent_switch_flags.enable_commands(c->floor);
|
||||
if (!commands.empty()) {
|
||||
send_command(c, 0x60, 0x00, commands);
|
||||
}
|
||||
}
|
||||
c->last_switch_enabled_command = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1505,8 +1590,9 @@ void on_movement_with_floor(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
}
|
||||
c->x = cmd.x;
|
||||
c->z = cmd.z;
|
||||
if (cmd.floor >= 0) {
|
||||
if (cmd.floor >= 0 && c->floor != static_cast<uint32_t>(cmd.floor)) {
|
||||
c->floor = cmd.floor;
|
||||
c->recent_switch_flags.clear();
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
@@ -2408,10 +2494,19 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
// rt_indexes in Episode 4 don't match those sent in the command; we just
|
||||
// ignore what the client sends.
|
||||
if ((episode != Episode::EP4) && (cmd.rt_index != res.effective_rt_index)) {
|
||||
log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, res.effective_rt_index);
|
||||
if (!is_v4(version)) {
|
||||
res.effective_rt_index = cmd.rt_index;
|
||||
// Special cases: BULCLAW => BULK and DARK_GUNNER => DEATH_GUNNER
|
||||
if (cmd.rt_index == 0x27 && map_enemy->type == EnemyType::BULCLAW) {
|
||||
log.info("E-%hX killed as BULK instead of BULCLAW", map_enemy->enemy_id);
|
||||
res.effective_rt_index = 0x27;
|
||||
} else if (cmd.rt_index == 0x23 && map_enemy->type == EnemyType::DARK_GUNNER) {
|
||||
log.info("E-%hX killed as DEATH_GUNNER instead of DARK_GUNNER", map_enemy->enemy_id);
|
||||
res.effective_rt_index = 0x23;
|
||||
} else {
|
||||
log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, res.effective_rt_index);
|
||||
if (!is_v4(version)) {
|
||||
res.effective_rt_index = cmd.rt_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cmd.floor != map_enemy->floor) {
|
||||
@@ -2664,25 +2759,130 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
||||
}
|
||||
}
|
||||
|
||||
static void on_set_entity_flag(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
static void on_set_entity_set_flag(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& cmd = check_size_t<G_SetEntityFlags_6x76>(data, size);
|
||||
const auto& cmd = check_size_t<G_SetEntitySetFlags_6x76>(data, size);
|
||||
if (l->map) {
|
||||
if (cmd.header.enemy_id >= 0x1000 && cmd.header.enemy_id < 0x4000) {
|
||||
if (cmd.header.enemy_id >= 0x4000) {
|
||||
uint16_t object_index = cmd.header.enemy_id - 0x4000;
|
||||
try {
|
||||
l->map->enemies.at(cmd.header.enemy_id - 0x1000).game_flags |= cmd.flags;
|
||||
uint16_t& set_flags = l->map->objects.at(object_index).set_flags;
|
||||
set_flags |= cmd.flags;
|
||||
l->log.info("Client set set flags %04hX on K-%hX (flags are now %04hX)",
|
||||
cmd.flags.load(), object_index, cmd.flags.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing object");
|
||||
}
|
||||
|
||||
} else if (cmd.header.enemy_id >= 0x1000) {
|
||||
int32_t section = -1;
|
||||
int32_t wave_number = -1;
|
||||
uint16_t enemy_index = cmd.header.enemy_id - 0x1000;
|
||||
try {
|
||||
const auto& enemy = l->map->enemies.at(enemy_index);
|
||||
uint16_t& set_flags = l->map->enemy_set_flags.at(enemy.set_index);
|
||||
set_flags |= cmd.flags;
|
||||
section = enemy.section;
|
||||
wave_number = enemy.wave_number;
|
||||
l->log.info("Client set set flags %04hX on E-%hX (flags are now %04hX)",
|
||||
cmd.flags.load(), enemy_index, cmd.flags.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing enemy");
|
||||
}
|
||||
} else if (cmd.header.enemy_id >= 0x4000) {
|
||||
try {
|
||||
l->map->objects.at(cmd.header.enemy_id - 0x4000).game_flags |= cmd.flags;
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Flag update refers to missing object");
|
||||
|
||||
if ((section >= 0) && (wave_number >= 0)) {
|
||||
// When all enemies in a wave event have (set_flags & 8), which means
|
||||
// they are defeated, set event_flags = (event_flags | 0x18) & (~4),
|
||||
// which means it is done and should not trigger
|
||||
bool all_enemies_defeated = true;
|
||||
l->log.info("Checking for defeated enemies with section=%04" PRIX32 " wave_number=%04" PRIX32,
|
||||
section, wave_number);
|
||||
for (const Map::Enemy* enemy : l->map->get_enemies(cmd.floor, section, wave_number)) {
|
||||
if (!(l->map->enemy_set_flags.at(enemy->set_index) & 8)) {
|
||||
l->log.info("E-%hX is not defeated; cannot advance event to finished state", enemy->enemy_id);
|
||||
all_enemies_defeated = false;
|
||||
break;
|
||||
} else {
|
||||
l->log.info("E-%hX is defeated", enemy->enemy_id);
|
||||
}
|
||||
}
|
||||
if (all_enemies_defeated) {
|
||||
l->log.info("All enemies defeated; setting events with section=%04" PRIX32 " wave_number=%04" PRIX32 " to finished state",
|
||||
section, wave_number);
|
||||
for (Map::Event* event : l->map->get_events(cmd.floor, section, wave_number)) {
|
||||
event->flags = (event->flags | 0x18) & (~4);
|
||||
l->log.info("Set flags on W-%02hhX-%" PRIX32 " to %04hX", event->floor, event->event_id, event->flags);
|
||||
|
||||
StringReader actions_r(l->map->event_action_stream);
|
||||
actions_r.go(event->action_stream_offset);
|
||||
while (!actions_r.eof()) {
|
||||
uint8_t opcode = actions_r.get_u8();
|
||||
switch (opcode) {
|
||||
case 0x00: // nop
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) nop", event->floor, event->event_id);
|
||||
break;
|
||||
case 0x01: // stop
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) stop", event->floor, event->event_id);
|
||||
actions_r.go(actions_r.size());
|
||||
break;
|
||||
case 0x08: { // construct_objects
|
||||
uint16_t section = actions_r.get_u16l();
|
||||
uint16_t group = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) construct_objects %04hX %04hX", event->floor, event->event_id, section, group);
|
||||
for (auto* obj : l->map->get_objects(event->floor, section, group)) {
|
||||
if (!(obj->set_flags & 0x0A)) {
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) Setting flags 0012 on object K-%hX", event->floor, event->event_id, obj->object_id);
|
||||
obj->set_flags |= 0x12;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x09: // construct_enemies
|
||||
case 0x0D: { // construct_enemies_stop
|
||||
uint16_t section = actions_r.get_u16l();
|
||||
uint16_t wave_number = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) construct_enemies %04hX %04hX", event->floor, event->event_id, section, wave_number);
|
||||
for (auto* enemy : l->map->get_enemies(event->floor, section, wave_number)) {
|
||||
uint16_t& set_flags = l->map->enemy_set_flags.at(enemy->set_index);
|
||||
if (!(set_flags & 0x0A)) {
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) Setting flags 0002 on enemy set S-%zX (from E-%hX)", event->floor, event->event_id, enemy->set_index, enemy->enemy_id);
|
||||
set_flags |= 0x02;
|
||||
}
|
||||
}
|
||||
if (opcode == 0x0D) {
|
||||
actions_r.go(actions_r.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0A: // enable_switch_flag
|
||||
case 0x0B: { // disable_switch_flag
|
||||
// These opcodes cause the client to send 6x05 commands, so
|
||||
// we don't have to do anything here.
|
||||
uint16_t switch_flag_num = actions_r.get_u16l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) %sable_switch_flag %04hX",
|
||||
event->floor, event->event_id, (opcode & 1) ? "dis" : "en", switch_flag_num);
|
||||
break;
|
||||
}
|
||||
case 0x0C: { // trigger_event
|
||||
// This opcode causes the client to send a 6x67 command, so
|
||||
// we don't have to do anything here.
|
||||
uint32_t event_id = actions_r.get_u32l();
|
||||
l->log.info("(W-%02hhX-%" PRIX32 " script) trigger_event W-%02hhX-%" PRIX32,
|
||||
event->floor, event->event_id, event->floor, event_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
l->log.warning("(W-%02hhX-%" PRIX32 ") Invalid opcode %02hhX at offset %zX in event action stream",
|
||||
event->floor, event->event_id, opcode, actions_r.where() - 1);
|
||||
actions_r.go(actions_r.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2698,18 +2898,50 @@ static void on_trigger_set_event(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
|
||||
const auto& cmd = check_size_t<G_TriggerSetEvent_6x67>(data, size);
|
||||
if (l->map) {
|
||||
// TODO: The game's logic is significantly more complex than this. Do we
|
||||
// need to do anything fancy here?
|
||||
try {
|
||||
l->map->get_event(cmd.floor, cmd.event_id).flags |= 0x04;
|
||||
l->log.info("Client triggered set event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
} catch (const out_of_range&) {
|
||||
l->log.warning("Client triggered missing event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
l->log.warning("Client triggered missing set event W-%02" PRIX32 "-%" PRIX32, cmd.floor.load(), cmd.event_id.load());
|
||||
}
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_unknown_6x91(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_Unknown_6x91>(data, size);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
if (l->switch_flags &&
|
||||
(cmd.should_set == 1) &&
|
||||
(cmd.switch_flag_num < 0x100) &&
|
||||
(cmd.switch_flag_floor < 0x12) &&
|
||||
(cmd.header.object_id >= 0x4000) &&
|
||||
(cmd.header.object_id != 0xFFFF)) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_activate_timed_switch(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_ActivateTimedSwitch_6x93>(data, size);
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
if (l->switch_flags) {
|
||||
if (cmd.should_set == 1) {
|
||||
l->switch_flags->set(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
} else {
|
||||
l->switch_flags->clear(cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
}
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_battle_scores(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_BattleScores_6x7F<false>>(data, size);
|
||||
|
||||
@@ -4126,8 +4358,8 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x4B */ {0x40, 0x46, 0x4B, on_change_hp<G_ClientIDHeader>},
|
||||
/* 6x4C */ {0x41, 0x47, 0x4C, on_change_hp<G_ClientIDHeader>},
|
||||
/* 6x4D */ {0x42, 0x48, 0x4D, on_player_died},
|
||||
/* 6x4E */ {0x00, 0x00, 0x4E, on_forward_check_game_client},
|
||||
/* 6x4F */ {0x43, 0x49, 0x4F, on_forward_check_game_client},
|
||||
/* 6x4E */ {0x00, 0x00, 0x4E, on_player_revivable},
|
||||
/* 6x4F */ {0x43, 0x49, 0x4F, on_player_revived},
|
||||
/* 6x50 */ {0x44, 0x4A, 0x50, on_forward_check_game_client},
|
||||
/* 6x51 */ {0x00, 0x00, 0x51, on_invalid},
|
||||
/* 6x52 */ {0x46, 0x4C, 0x52, on_set_animation_state},
|
||||
@@ -4166,7 +4398,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x73 */ {0x00, 0x00, 0x73, on_forward_check_game_quest},
|
||||
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
||||
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_flag},
|
||||
/* 6x76 */ {0x00, 0x00, 0x76, on_set_entity_set_flag},
|
||||
/* 6x77 */ {0x00, 0x00, 0x77, on_forward_check_game},
|
||||
/* 6x78 */ {0x00, 0x00, 0x78, forward_subcommand_m},
|
||||
/* 6x79 */ {0x00, 0x00, 0x79, on_forward_check_lobby},
|
||||
@@ -4193,9 +4425,9 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x8E */ {0x00, 0x00, 0x8E, on_forward_check_game},
|
||||
/* 6x8F */ {0x00, 0x00, 0x8F, on_forward_check_game},
|
||||
/* 6x90 */ {0x00, 0x00, 0x90, on_forward_check_game},
|
||||
/* 6x91 */ {0x00, 0x00, 0x91, on_forward_check_game},
|
||||
/* 6x91 */ {0x00, 0x00, 0x91, on_unknown_6x91},
|
||||
/* 6x92 */ {0x00, 0x00, 0x92, on_forward_check_game},
|
||||
/* 6x93 */ {0x00, 0x00, 0x93, on_forward_check_game},
|
||||
/* 6x93 */ {0x00, 0x00, 0x93, on_activate_timed_switch},
|
||||
/* 6x94 */ {0x00, 0x00, 0x94, on_warp},
|
||||
/* 6x95 */ {0x00, 0x00, 0x95, on_forward_check_game},
|
||||
/* 6x96 */ {0x00, 0x00, 0x96, on_forward_check_game},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "SaveFileFormats.hh"
|
||||
|
||||
#include <phosg/Hash.hh>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -355,6 +356,31 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
for (size_t z = 0; z < initial_items.size(); z++) {
|
||||
ret->inventory.items[z] = initial_items[z];
|
||||
}
|
||||
|
||||
// Set mag color based on initial costume
|
||||
static const array<array<uint8_t, 25>, 12> mag_colors = {{
|
||||
{0x09, 0x01, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x0B, 0x05, 0x00, 0x07, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x08, 0x11, 0x0D, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x0E, 0x06, 0x01, 0x0E, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x04, 0x11, 0x0D, 0x01, 0x0B, 0x11, 0x0D, 0x05, 0x06},
|
||||
{0x00, 0x01, 0x0B, 0x11, 0x04, 0x05, 0x06, 0x0F, 0x05, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x0F, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x0B, 0x11, 0x0A, 0x05, 0x06, 0x06, 0x09, 0x09, 0x01, 0x02, 0x11, 0x0A, 0x0E, 0x06, 0x01, 0x04, 0x0D, 0x07, 0x01, 0x0C, 0x0A, 0x05, 0x06},
|
||||
{0x10, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x0A, 0x00, 0x07, 0x00, 0x01, 0x08, 0x11, 0x04, 0x09, 0x0F, 0x0D, 0x02, 0x0A, 0x07, 0x02, 0x0C, 0x04, 0x0E, 0x0E},
|
||||
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x07, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x0D, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x00, 0x11, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x04, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x10, 0x05, 0x09, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x0F, 0x0A, 0x04, 0x0D, 0x01, 0x08, 0x11, 0x04, 0x05, 0x0F, 0x05, 0x10, 0x10, 0x07, 0x02, 0x0B, 0x0A, 0x0A, 0x0F},
|
||||
{0x00, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0D, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x09, 0x0C, 0x00, 0x01, 0x02, 0x11, 0x0D, 0x05, 0x10, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
}};
|
||||
uint8_t char_class = (visual.char_class > 0x0B) ? 0 : visual.char_class;
|
||||
uint8_t mag_color_index;
|
||||
if (char_class == 2 || char_class == 4 || char_class == 5 || char_class == 9) {
|
||||
mag_color_index = (visual.skin >= 25) ? 0 : visual.skin.load();
|
||||
} else {
|
||||
mag_color_index = (visual.costume >= 18) ? 0 : visual.costume.load();
|
||||
}
|
||||
ret->inventory.items[2].data.data2[3] = mag_colors.at(char_class).at(mag_color_index);
|
||||
|
||||
ret->inventory.items[13].extension_data2 = 1;
|
||||
|
||||
const auto& config = (ret->disp.visual.class_flags & 0x80) ? config_force : config_hunter_ranger;
|
||||
@@ -633,3 +659,109 @@ const array<uint8_t, 0x0038> PSOBBBaseSystemFile::DEFAULT_JOYSTICK_CONFIG = {
|
||||
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
static uint16_t crc16(const void* data, size_t size) {
|
||||
static const uint16_t table[0x100] = {
|
||||
// clang-format off
|
||||
/* 00 */ 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
|
||||
/* 08 */ 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
|
||||
/* 10 */ 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
|
||||
/* 18 */ 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
|
||||
/* 20 */ 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
|
||||
/* 28 */ 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
|
||||
/* 30 */ 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
|
||||
/* 38 */ 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
|
||||
/* 40 */ 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
|
||||
/* 48 */ 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
|
||||
/* 50 */ 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
|
||||
/* 58 */ 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
|
||||
/* 60 */ 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
|
||||
/* 68 */ 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
|
||||
/* 70 */ 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
|
||||
/* 78 */ 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
|
||||
/* 80 */ 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
|
||||
/* 88 */ 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
|
||||
/* 90 */ 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
|
||||
/* 98 */ 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
|
||||
/* A0 */ 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
|
||||
/* A8 */ 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
|
||||
/* B0 */ 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
|
||||
/* B8 */ 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
|
||||
/* C0 */ 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
|
||||
/* C8 */ 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
|
||||
/* D0 */ 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
|
||||
/* D8 */ 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
|
||||
/* E0 */ 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
|
||||
/* E8 */ 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
|
||||
/* F0 */ 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
|
||||
/* F8 */ 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
uint16_t ret = 0xFFFF;
|
||||
StringReader r(data, size);
|
||||
while (!r.eof()) {
|
||||
ret = (ret >> 8) ^ table[r.get_u8() ^ (ret & 0xFF)];
|
||||
}
|
||||
return ret ^ 0xFFFF;
|
||||
}
|
||||
|
||||
string encode_psobb_hangame_credentials(const string& user_id, const string& token, const string& unused) {
|
||||
if (user_id.size() < 4) {
|
||||
throw runtime_error("user_id must be at least 4 characters");
|
||||
}
|
||||
if (user_id.size() > 12) {
|
||||
throw runtime_error("user_id must be at most 12 characters");
|
||||
}
|
||||
if (!ends_with(user_id, "@HG")) {
|
||||
throw runtime_error("user_id must end with \"@HG\"");
|
||||
}
|
||||
if (token.empty()) {
|
||||
throw runtime_error("token must not be empty");
|
||||
}
|
||||
if (token.size() > 8) {
|
||||
throw runtime_error("token must be at most 8 characters");
|
||||
}
|
||||
for (char ch : token) {
|
||||
if (!isdigit(ch)) {
|
||||
throw runtime_error("token must contain only decimal digits");
|
||||
}
|
||||
}
|
||||
if (unused.size() > 0xFF) {
|
||||
throw runtime_error("unused must be at most 255 characters");
|
||||
}
|
||||
|
||||
// The encoded format is:
|
||||
// parray<uint8_t, 4> mask_key; // xor this with all bytes starting with checksum
|
||||
// le_uint16_t checksum; // crc16(&unused, EOF - &unused)
|
||||
// uint8_t unused;
|
||||
// uint8_t user_id_size;
|
||||
// char user_id[user_id_size]; // Length must be in [4, 12] and must end with "@HG"
|
||||
// uint8_t token_size;
|
||||
// char token[token_size]; // Length must be in [1, 8] and must be all decimal digits
|
||||
// uint8_t unused_size;
|
||||
// char unused[unused_size]; // Ignored (possibly email address?)
|
||||
// We'll fill in mask_key and checksum after all the other fields.
|
||||
string data(7, '\0'); // mask_key, checksum, unused
|
||||
data.push_back(user_id.size());
|
||||
data += user_id;
|
||||
data.push_back(token.size());
|
||||
data += token;
|
||||
data.push_back(unused.size());
|
||||
data += unused;
|
||||
|
||||
uint16_t checksum = crc16(data.data() + 6, data.size() - 6);
|
||||
uint32_t timestamp = time(nullptr);
|
||||
data[0] = (timestamp & 0xFF);
|
||||
data[1] = ((timestamp >> 8) & 0xFF);
|
||||
data[2] = ((timestamp >> 16) & 0xFF);
|
||||
data[3] = ((timestamp >> 24) & 0xFF);
|
||||
data[4] = checksum & 0xFF;
|
||||
data[5] = (checksum >> 8) & 0xFF;
|
||||
|
||||
for (size_t z = 0; z < data.size() - 4; z++) {
|
||||
data[z + 4] ^= data[z % 3];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -360,7 +360,8 @@ struct PSOGCCharacterFile {
|
||||
/* 25DC:21C0 */ parray<uint8_t, 4> unknown_a2;
|
||||
/* 25E0:21C4 */ PlayerRecordsV3_Challenge<true> challenge_records;
|
||||
/* 26E0:22C4 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
|
||||
/* 2708:22EC */ parray<uint8_t, 0x28> unknown_a6;
|
||||
/* 2708:22EC */ ChoiceSearchConfig choice_search_config;
|
||||
/* 2720:2304 */ parray<uint8_t, 0x10> unknown_a6;
|
||||
/* 2730:2314 */ parray<be_uint32_t, 0x10> quest_counters;
|
||||
/* 2770:2354 */ PlayerRecords_Battle<true> offline_battle_records;
|
||||
/* 2788:236C */ parray<uint8_t, 4> unknown_f5;
|
||||
@@ -788,3 +789,5 @@ struct LegacySavedAccountDataBB { // .nsa file format
|
||||
/* F060 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> team_name;
|
||||
/* F080 */
|
||||
} __attribute__((packed));
|
||||
|
||||
std::string encode_psobb_hangame_credentials(const std::string& user_id, const std::string& token, const std::string& unused = "");
|
||||
|
||||
+87
-19
@@ -477,6 +477,62 @@ void send_function_call(
|
||||
ch.send(0xB2, code ? code->index : 0x00, data);
|
||||
}
|
||||
|
||||
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby) {
|
||||
switch (c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
case Version::GC_NTE:
|
||||
if (echo_to_lobby) {
|
||||
send_command(c->require_lobby(), 0x60, 0x00, data, size);
|
||||
} else {
|
||||
send_command(c, 0x60, 0x00, data, size);
|
||||
}
|
||||
return true;
|
||||
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3: {
|
||||
auto s = c->require_server_state();
|
||||
if (!s->enable_v3_v4_protected_subcommands ||
|
||||
c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) ||
|
||||
c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), data = string(reinterpret_cast<const char*>(data), size), echo_to_lobby]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("CallProtectedHandler", c->config.specific_version);
|
||||
uint32_t size_label_value = is_big_endian(c->version()) ? data.size() : bswap32(data.size());
|
||||
send_function_call(c, fn, {{"size", size_label_value}}, data);
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
if (echo_to_lobby) {
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
send_command_excluding_client(l, c, 0x60, 0x00, data.data(), data.size());
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Failed to send protected command: %s", e.what());
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
||||
S_Reconnect_19 cmd = {{address, port, 0}};
|
||||
send_command_t(c, is_patch(c->version()) ? 0x14 : 0x19, 0x00, cmd);
|
||||
@@ -1057,11 +1113,11 @@ void send_card_search_result_t(
|
||||
|
||||
string location_string;
|
||||
if (result_lobby->is_game()) {
|
||||
location_string = string_printf("%s,BLOCK01,%s", result_lobby->name.c_str(), s->name.c_str());
|
||||
location_string = string_printf("%s,,BLOCK01,%s", result_lobby->name.c_str(), s->name.c_str());
|
||||
} else if (result_lobby->is_ep3()) {
|
||||
location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id - 15, s->name.c_str());
|
||||
location_string = string_printf("BLOCK01-C%02" PRIu32 ",,BLOCK01,%s", result_lobby->lobby_id - 15, s->name.c_str());
|
||||
} else {
|
||||
location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id, s->name.c_str());
|
||||
location_string = string_printf("BLOCK01-%02" PRIu32 ",,BLOCK01,%s", result_lobby->lobby_id, s->name.c_str());
|
||||
}
|
||||
cmd.location_string.encode(location_string, c->language());
|
||||
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
|
||||
@@ -2354,12 +2410,14 @@ void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange
|
||||
}
|
||||
|
||||
void send_remove_conditions(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
for (auto& lc : l->clients) {
|
||||
if (lc) {
|
||||
send_remove_conditions(lc->channel, c->lobby_client_id);
|
||||
}
|
||||
parray<G_AddOrRemoveCondition_6x0C_6x0D, 4> cmds;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto& cmd = cmds[z];
|
||||
cmd.header = {0x0D, sizeof(G_AddOrRemoveCondition_6x0C_6x0D) >> 2, c->lobby_client_id};
|
||||
cmd.unknown_a1 = z;
|
||||
cmd.unknown_a2 = 0;
|
||||
}
|
||||
send_protected_command(c, &cmds, sizeof(cmds), true);
|
||||
}
|
||||
|
||||
void send_remove_conditions(Channel& ch, uint16_t client_id) {
|
||||
@@ -2381,6 +2439,7 @@ void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private)
|
||||
void send_warp(shared_ptr<Client> c, uint32_t floor, bool is_private) {
|
||||
send_warp(c->channel, c->lobby_client_id, floor, is_private);
|
||||
c->floor = floor;
|
||||
c->recent_switch_flags.clear();
|
||||
}
|
||||
|
||||
void send_warp(shared_ptr<Lobby> l, uint32_t floor, bool is_private) {
|
||||
@@ -2557,8 +2616,8 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
G_SyncSetFlagState_6x6E_Decompressed header;
|
||||
header.entity_set_flags_size = sizeof(entity_set_flags_header) + (num_object_sets + num_enemy_sets) * sizeof(le_uint16_t);
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * l->map->events.size();
|
||||
header.unused_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.unused_size;
|
||||
header.switch_flags_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.switch_flags_size;
|
||||
|
||||
StringWriter w;
|
||||
w.put(header);
|
||||
@@ -2572,7 +2631,12 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
for (const auto& event : l->map->events) {
|
||||
w.put_u16l(event.flags);
|
||||
}
|
||||
w.extend_by(header.unused_size, 0x00);
|
||||
if (l->switch_flags) {
|
||||
static_assert(sizeof(SwitchFlags) == 0x240, "switch_flags size is incorrect");
|
||||
w.write(l->switch_flags->data.data(), header.switch_flags_size);
|
||||
} else {
|
||||
w.extend_by(header.switch_flags_size, 0x00);
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, w.str(), 0x5F, 0x66, 0x6E);
|
||||
}
|
||||
@@ -2632,10 +2696,10 @@ void send_game_flag_state_t(shared_ptr<Client> c) {
|
||||
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive join commands; adding to queue");
|
||||
auto& cmd = c->game_join_command_queue->emplace_back();
|
||||
cmd.command = 0x0062;
|
||||
cmd.flag = c->lobby_client_id;
|
||||
cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
|
||||
auto& queue_cmd = c->game_join_command_queue->emplace_back();
|
||||
queue_cmd.command = 0x0062;
|
||||
queue_cmd.flag = c->lobby_client_id;
|
||||
queue_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
|
||||
} else {
|
||||
send_command_t(c, 0x62, c->lobby_client_id, cmd);
|
||||
}
|
||||
@@ -2645,10 +2709,14 @@ void send_game_flag_state_t(shared_ptr<Client> c) {
|
||||
void send_game_flag_state(shared_ptr<Client> c) {
|
||||
// DC NTE and 11/2000 don't have this command at all; v1 has it but it doesn't
|
||||
// include flags for Ultimate.
|
||||
if (!is_v1(c->version())) {
|
||||
send_game_flag_state_t<G_SetQuestFlagsV2V3V4_6x6F>(c);
|
||||
} else if (!is_pre_v1(c->version())) {
|
||||
send_game_flag_state_t<G_SetQuestFlagsV1_6x6F>(c);
|
||||
if (is_pre_v1(c->version())) {
|
||||
return;
|
||||
} else if (is_v1(c->version())) {
|
||||
send_game_flag_state_t<G_SetQuestFlags_DCv1_6x6F>(c);
|
||||
} else if (!is_v4(c->version())) {
|
||||
send_game_flag_state_t<G_SetQuestFlags_V2_V3_6x6F>(c);
|
||||
} else {
|
||||
send_game_flag_state_t<G_SetQuestFlags_BB_6x6F>(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ void send_function_call(
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0,
|
||||
uint32_t override_relocations_offset = 0);
|
||||
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
|
||||
|
||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||
void send_pc_console_split_reconnect(
|
||||
|
||||
+15
-4
@@ -734,6 +734,7 @@ void ServerState::load_config_early() {
|
||||
this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2);
|
||||
this->default_rare_notifs_enabled_v3_v4 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4);
|
||||
this->ep3_send_function_call_enabled = this->config_json->get_bool("EnableEpisode3SendFunctionCall", false);
|
||||
this->enable_v3_v4_protected_subcommands = this->config_json->get_bool("EnableV3V4ProtectedSubcommands", false);
|
||||
this->catch_handler_exceptions = this->config_json->get_bool("CatchHandlerExceptions", true);
|
||||
|
||||
auto parse_int_list = +[](const JSON& json) -> vector<uint32_t> {
|
||||
@@ -757,7 +758,7 @@ void ServerState::load_config_early() {
|
||||
}
|
||||
this->ep3_final_round_meseta_bonus = this->config_json->get_int("Episode3FinalRoundMesetaBonus", 300);
|
||||
this->ep3_jukebox_is_free = this->config_json->get_bool("Episode3JukeboxIsFree", false);
|
||||
this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", false);
|
||||
this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", 0);
|
||||
this->ep3_card_auction_points = this->config_json->get_int("CardAuctionPoints", 0);
|
||||
this->hide_download_commands = this->config_json->get_bool("HideDownloadCommands", true);
|
||||
this->proxy_allow_save_files = this->config_json->get_bool("ProxyAllowSaveFiles", true);
|
||||
@@ -889,6 +890,7 @@ void ServerState::load_config_early() {
|
||||
this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true);
|
||||
this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true);
|
||||
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
|
||||
this->unlock_all_areas = this->config_json->get_bool("UnlockAllAreas", false);
|
||||
|
||||
this->version_name_colors.reset();
|
||||
try {
|
||||
@@ -1139,14 +1141,15 @@ void ServerState::load_config_late() {
|
||||
for (size_t trap_type = 0; trap_type < 5; trap_type++) {
|
||||
auto& trap_card_ids = this->ep3_trap_card_ids[trap_type];
|
||||
for (const auto& card_it : ep3_trap_cards_json.at(trap_type)->as_list()) {
|
||||
const string& card_name = card_it->as_string();
|
||||
try {
|
||||
const auto& card = this->ep3_card_index->definition_for_name_normalized(card_it->as_string());
|
||||
const auto& card = this->ep3_card_index->definition_for_name_normalized(card_name);
|
||||
if (card->def.type != Episode3::CardType::ASSIST) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", card_name.c_str()));
|
||||
}
|
||||
trap_card_ids.emplace_back(card->def.card_id);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", card_name.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1784,6 +1787,14 @@ void ServerState::load_all() {
|
||||
|
||||
shared_ptr<PatchServer::Config> ServerState::generate_patch_server_config(bool is_bb) const {
|
||||
auto ret = make_shared<PatchServer::Config>();
|
||||
#ifdef PHOSG_WINDOWS
|
||||
// libevent doesn't play nice with Cygwin, so we run the patch server on the
|
||||
// main thread there. The problem seems to be that the locking structures are
|
||||
// never set up, presumably since we call event_use_pthreads() since
|
||||
// event_use_windows_threads() doesn't exist. (Does literally no one else use
|
||||
// libevent with Cygwin??)
|
||||
ret->shared_base = this->base;
|
||||
#endif
|
||||
ret->allow_unregistered_users = this->allow_unregistered_users;
|
||||
ret->hide_data_from_logs = this->hide_download_commands;
|
||||
ret->idle_timeout_usecs = this->patch_client_idle_timeout_usecs;
|
||||
|
||||
@@ -91,6 +91,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
bool allow_dc_pc_games = true;
|
||||
bool allow_gc_xb_games = true;
|
||||
bool enable_chat_commands = true;
|
||||
bool unlock_all_areas = false;
|
||||
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
|
||||
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
|
||||
@@ -113,6 +114,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
QuestFlagsForDifficulty quest_flag_persist_mask;
|
||||
uint64_t persistent_game_idle_timeout_usecs = 0;
|
||||
bool ep3_send_function_call_enabled = false;
|
||||
bool enable_v3_v4_protected_subcommands = false;
|
||||
bool catch_handler_exceptions = true;
|
||||
bool ep3_infinite_meseta = false;
|
||||
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
|
||||
|
||||
@@ -775,6 +775,19 @@ const char* name_for_floor(Episode episode, uint8_t floor) {
|
||||
}
|
||||
}
|
||||
|
||||
bool floor_is_boss_arena(Episode episode, uint8_t floor) {
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return (floor >= 0x0B) && (floor <= 0x0E);
|
||||
case Episode::EP2:
|
||||
return (floor >= 0x0C) && (floor <= 0x0F);
|
||||
case Episode::EP4:
|
||||
return (floor == 0x09);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t class_flags_for_class(uint8_t char_class) {
|
||||
static constexpr uint8_t flags[12] = {
|
||||
0x25, 0x2A, 0x31, 0x45, 0x51, 0x52, 0x86, 0x89, 0x8A, 0x32, 0x85, 0x46};
|
||||
|
||||
@@ -76,6 +76,7 @@ extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
|
||||
size_t floor_limit_for_episode(Episode ep);
|
||||
uint8_t floor_for_name(const std::string& name);
|
||||
const char* name_for_floor(Episode episode, uint8_t floor);
|
||||
bool floor_is_boss_arena(Episode episode, uint8_t floor);
|
||||
|
||||
uint32_t class_flags_for_class(uint8_t char_class);
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ TextTranscoder::Result TextTranscoder::operator()(
|
||||
if (!truncate_oversize_result) {
|
||||
throw runtime_error("string does not fit in buffer");
|
||||
} else {
|
||||
src_bytes = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805C5650
|
||||
.data 0x801E3F9C
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805CC630
|
||||
.data 0x801E3F9C
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805D5E50
|
||||
.data 0x801E405C
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805C4D58
|
||||
.data 0x801E3B38
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805CF320
|
||||
.data 0x801E40BC
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805D67A0
|
||||
.data 0x801E4290
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805D6540
|
||||
.data 0x801E4008
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerGC
|
||||
.data 0x805D2090
|
||||
.data 0x801E4698
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x00723F68
|
||||
.data 0x002DDB00
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x007237E8
|
||||
.data 0x002DE000
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x0071E8C8
|
||||
.data 0x002DBBA0
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x0071EF28
|
||||
.data 0x002DC720
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x00726A68
|
||||
.data 0x002DDFE0
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x00723F68
|
||||
.data 0x002DDB30
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,14 @@
|
||||
.meta hide_from_patches_menu
|
||||
.meta name="CallProtectedHandler"
|
||||
.meta description=""
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CallProtectedHandlerXB
|
||||
.data 0x007242E8
|
||||
.data 0x002DE030
|
||||
size:
|
||||
.data 0x00000000
|
||||
data:
|
||||
@@ -0,0 +1,32 @@
|
||||
stwu [r1 - 0x10], r1
|
||||
mflr r0
|
||||
stw [r1 + 0x14], r0
|
||||
stw [r1 + 0x08], r31
|
||||
stw [r1 + 0x0C], r30
|
||||
|
||||
b get_data_addr
|
||||
resume:
|
||||
mflr r31
|
||||
|
||||
lwz r30, [r31]
|
||||
li r0, 1
|
||||
stw [r30], r0
|
||||
|
||||
addi r3, r31, 0x0C
|
||||
lwz r4, [r31 + 8]
|
||||
lwz r0, [r31 + 4]
|
||||
mtctr r0
|
||||
bctrl
|
||||
|
||||
li r0, 0
|
||||
stw [r30], r0
|
||||
|
||||
lwz r30, [r1 + 0x0C]
|
||||
lwz r31, [r1 + 0x08]
|
||||
lwz r0, [r1 + 0x14]
|
||||
mtlr r0
|
||||
addi r1, r1, 0x10
|
||||
blr
|
||||
|
||||
get_data_addr:
|
||||
bl resume
|
||||
@@ -0,0 +1,20 @@
|
||||
jmp get_data_addr
|
||||
resume:
|
||||
xchg ebx, [esp]
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 1
|
||||
|
||||
mov edx, [ebx + 4]
|
||||
lea ecx, [ebx + 0x0C]
|
||||
mov eax, [ebx + 8]
|
||||
call edx
|
||||
|
||||
mov edx, [ebx]
|
||||
mov dword [edx], 0
|
||||
|
||||
pop ebx
|
||||
ret
|
||||
|
||||
get_data_addr:
|
||||
call resume
|
||||
@@ -87,6 +87,7 @@
|
||||
"pc": [9300, "pc", "login_server"],
|
||||
"pc-patch": [10000, "patch", "patch_server_pc"],
|
||||
"bb-patch": [11000, "patch", "patch_server_bb"],
|
||||
"bb-patch-hg": [11200, "patch", "patch_server_bb"],
|
||||
"bb-init": [12000, "bb", "login_server"],
|
||||
|
||||
// PSO Xbox tunnels its connections through the Xbox Live service (or, in
|
||||
@@ -957,6 +958,12 @@
|
||||
// available on the proxy server.
|
||||
"CheatModeBehavior": "OnByDefault",
|
||||
|
||||
// Whether to unlock all areas by default in Ep1/2/4 games. If this is on,
|
||||
// the Ragol warp in Pioneer 2 will allow access to all base areas (Forest 1,
|
||||
// Cave 1, Mine 1, and Ruins 1 in Episode 1, for example) even if the player
|
||||
// who created the game does not yet have access to those areas.
|
||||
"UnlockAllAreas": false,
|
||||
|
||||
// Whether to enable rare drop notifications by default. Players can toggle
|
||||
// this behavior for themselves with the $itemnotifs command.
|
||||
"RareNotificationsEnabledByDefaultV1V2": false,
|
||||
@@ -966,7 +973,13 @@
|
||||
// exploiting a bug in Episode 3, and while it seems to work reliably on
|
||||
// Dolphin, it hasn't been tested on a real GameCube. So, newserv doesn't
|
||||
// enable Episode 3 USA patches by default; it only does if this option is on.
|
||||
// "EnableEpisode3SendFunctionCall": true,
|
||||
"EnableEpisode3SendFunctionCall": false,
|
||||
|
||||
// Whether to enable protected subcommands on GC and Xbox. This enables the
|
||||
// infinite HP cheat to also automatically revive players and clear conditions
|
||||
// like poison and freeze. (On v1 and v2, those functions are always enabled
|
||||
// in infinite HP mode.)
|
||||
"EnableV3V4ProtectedSubcommands": false,
|
||||
|
||||
// Whether to allow cross-play for various game versions. DCv1 and DCv2 are
|
||||
// always allowed to join each other's games (though DCv2 can deny permission
|
||||
|
||||
@@ -968,6 +968,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000406, "VJAYA"]],
|
||||
"Box-Forest2": [["1/256", 0x000405, "BRIONAC"]],
|
||||
@@ -981,6 +982,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001101, "SOUL BANISH"]],
|
||||
@@ -1018,6 +1020,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x000E02, "TWIN BRAND"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000706, "WALS-MK2"]],
|
||||
"Box-Forest2": [["1/256", 0x000705, "VISK-235W"]],
|
||||
@@ -1031,6 +1034,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001500, "FLAME VISIT"]],
|
||||
@@ -1068,6 +1072,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000306, "BLOODY ART"]],
|
||||
"Box-Forest2": [["1/256", 0x000305, "BLADE DANCE"]],
|
||||
@@ -1081,6 +1086,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000E02, "TWIN BRAND"]],
|
||||
@@ -1118,6 +1124,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000C05, "ICE STAFF:DAGON"]],
|
||||
"Box-Forest2": [["1/256", 0x000C04, "FIRE SCEPTER:AGNI"]],
|
||||
@@ -1131,6 +1138,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002400, "MAGICAL PIECE"]],
|
||||
@@ -1168,6 +1176,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000806, "H&S25 JUSTICE"]],
|
||||
"Box-Forest2": [["1/256", 0x000805, "M&A60 VISE"]],
|
||||
@@ -1176,11 +1185,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010123, "SENSE PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001400, "INFERNO BAZOOKA"]],
|
||||
@@ -1218,6 +1228,7 @@
|
||||
"BARBAROUS_WOLF": [["1/32768", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/4096", 0x010121, "PARASITE WEAR:Nelgal"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x010119, "RANGER FIELD"]],
|
||||
"Box-Forest2": [["1/256", 0x010118, "HUNTER FIELD"]],
|
||||
@@ -1231,6 +1242,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["1/32768", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010218, "S-PARTS ver2.01"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x010122, "PARASITE WEAR:Vajulla"]],
|
||||
@@ -1268,6 +1280,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["13/536870912", 0x001001, "AGITO"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000206, "LAST SURVIVOR"]],
|
||||
"Box-Forest2": [["1/256", 0x000205, "FLOWEN'S SWORD"]],
|
||||
@@ -1281,6 +1294,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002100, "CHAIN SAWD"]],
|
||||
@@ -1318,6 +1332,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x000905, "CRUSH BULLET"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000906, "METEOR SMASH"]],
|
||||
"Box-Forest2": [["3/1024", 0x000905, "CRUSH BULLET"]],
|
||||
@@ -1331,6 +1346,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001200, "SPREAD NEEDLE"]],
|
||||
@@ -1368,6 +1384,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x002100, "CHAIN SAWD"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000506, "DISKA OF LIBERATOR"]],
|
||||
"Box-Forest2": [["3/1024", 0x000505, "SLICER OF ASSASSIN"]],
|
||||
@@ -1381,6 +1398,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000D01, "SILENCE CLAW"]],
|
||||
@@ -1418,6 +1436,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["3/512", 0x000605, "VARISTA"]],
|
||||
"Box-Forest2": [["5/1024", 0x000106, "KALADBOLG"]],
|
||||
@@ -1426,11 +1445,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010124, "GRAVITON PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002300, "STING TIP"]],
|
||||
@@ -2443,6 +2463,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000406, "VJAYA"]],
|
||||
"Box-Forest2": [["1/256", 0x000405, "BRIONAC"]],
|
||||
@@ -2456,6 +2477,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001101, "SOUL BANISH"]],
|
||||
@@ -2493,6 +2515,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x000E02, "TWIN BRAND"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000706, "WALS-MK2"]],
|
||||
"Box-Forest2": [["1/256", 0x000705, "VISK-235W"]],
|
||||
@@ -2506,6 +2529,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001500, "FLAME VISIT"]],
|
||||
@@ -2543,6 +2567,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000306, "BLOODY ART"]],
|
||||
"Box-Forest2": [["1/256", 0x000305, "BLADE DANCE"]],
|
||||
@@ -2556,6 +2581,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000E02, "TWIN BRAND"]],
|
||||
@@ -2593,6 +2619,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000C05, "ICE STAFF:DAGON"]],
|
||||
"Box-Forest2": [["1/256", 0x000C04, "FIRE SCEPTER:AGNI"]],
|
||||
@@ -2606,6 +2633,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002400, "MAGICAL PIECE"]],
|
||||
@@ -2643,6 +2671,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000806, "H&S25 JUSTICE"]],
|
||||
"Box-Forest2": [["1/256", 0x000805, "M&A60 VISE"]],
|
||||
@@ -2651,11 +2680,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010123, "SENSE PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001400, "INFERNO BAZOOKA"]],
|
||||
@@ -2693,6 +2723,7 @@
|
||||
"BARBAROUS_WOLF": [["1/32768", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/4096", 0x010121, "PARASITE WEAR:Nelgal"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x010119, "RANGER FIELD"]],
|
||||
"Box-Forest2": [["1/256", 0x010118, "HUNTER FIELD"]],
|
||||
@@ -2706,6 +2737,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["1/32768", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010218, "S-PARTS ver2.01"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x010122, "PARASITE WEAR:Vajulla"]],
|
||||
@@ -2743,6 +2775,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["13/536870912", 0x001001, "AGITO"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000206, "LAST SURVIVOR"]],
|
||||
"Box-Forest2": [["1/256", 0x000205, "FLOWEN'S SWORD"]],
|
||||
@@ -2756,6 +2789,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002100, "CHAIN SAWD"]],
|
||||
@@ -2793,6 +2827,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x000905, "CRUSH BULLET"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000906, "METEOR SMASH"]],
|
||||
"Box-Forest2": [["3/1024", 0x000905, "CRUSH BULLET"]],
|
||||
@@ -2806,6 +2841,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001200, "SPREAD NEEDLE"]],
|
||||
@@ -2843,6 +2879,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x002100, "CHAIN SAWD"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000506, "DISKA OF LIBERATOR"]],
|
||||
"Box-Forest2": [["3/1024", 0x000505, "SLICER OF ASSASSIN"]],
|
||||
@@ -2856,6 +2893,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000D01, "SILENCE CLAW"]],
|
||||
@@ -2893,6 +2931,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["3/512", 0x000605, "VARISTA"]],
|
||||
"Box-Forest2": [["5/1024", 0x000106, "KALADBOLG"]],
|
||||
@@ -2901,11 +2940,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010124, "GRAVITON PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002300, "STING TIP"]],
|
||||
@@ -3918,6 +3958,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000406, "VJAYA"]],
|
||||
"Box-Forest2": [["1/256", 0x000405, "BRIONAC"]],
|
||||
@@ -3931,6 +3972,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001101, "SOUL BANISH"]],
|
||||
@@ -3968,6 +4010,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x000E02, "TWIN BRAND"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000706, "WALS-MK2"]],
|
||||
"Box-Forest2": [["1/256", 0x000705, "VISK-235W"]],
|
||||
@@ -3981,6 +4024,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001500, "FLAME VISIT"]],
|
||||
@@ -4018,6 +4062,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000306, "BLOODY ART"]],
|
||||
"Box-Forest2": [["1/256", 0x000305, "BLADE DANCE"]],
|
||||
@@ -4031,6 +4076,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000E02, "TWIN BRAND"]],
|
||||
@@ -4068,6 +4114,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000C05, "ICE STAFF:DAGON"]],
|
||||
"Box-Forest2": [["1/256", 0x000C04, "FIRE SCEPTER:AGNI"]],
|
||||
@@ -4081,6 +4128,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002400, "MAGICAL PIECE"]],
|
||||
@@ -4118,6 +4166,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000806, "H&S25 JUSTICE"]],
|
||||
"Box-Forest2": [["1/256", 0x000805, "M&A60 VISE"]],
|
||||
@@ -4126,11 +4175,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010123, "SENSE PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001400, "INFERNO BAZOOKA"]],
|
||||
@@ -4168,6 +4218,7 @@
|
||||
"BARBAROUS_WOLF": [["1/32768", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/4096", 0x010121, "PARASITE WEAR:Nelgal"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x010119, "RANGER FIELD"]],
|
||||
"Box-Forest2": [["1/256", 0x010118, "HUNTER FIELD"]],
|
||||
@@ -4181,6 +4232,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["1/32768", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010218, "S-PARTS ver2.01"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x010122, "PARASITE WEAR:Vajulla"]],
|
||||
@@ -4218,6 +4270,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["13/536870912", 0x001001, "AGITO"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000206, "LAST SURVIVOR"]],
|
||||
"Box-Forest2": [["1/256", 0x000205, "FLOWEN'S SWORD"]],
|
||||
@@ -4231,6 +4284,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002100, "CHAIN SAWD"]],
|
||||
@@ -4268,6 +4322,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x000905, "CRUSH BULLET"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000906, "METEOR SMASH"]],
|
||||
"Box-Forest2": [["3/1024", 0x000905, "CRUSH BULLET"]],
|
||||
@@ -4281,6 +4336,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001200, "SPREAD NEEDLE"]],
|
||||
@@ -4318,6 +4374,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x002100, "CHAIN SAWD"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000506, "DISKA OF LIBERATOR"]],
|
||||
"Box-Forest2": [["3/1024", 0x000505, "SLICER OF ASSASSIN"]],
|
||||
@@ -4331,6 +4388,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000D01, "SILENCE CLAW"]],
|
||||
@@ -4368,6 +4426,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["3/512", 0x000605, "VARISTA"]],
|
||||
"Box-Forest2": [["5/1024", 0x000106, "KALADBOLG"]],
|
||||
@@ -4376,11 +4435,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010124, "GRAVITON PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002300, "STING TIP"]],
|
||||
@@ -5393,6 +5453,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000406, "VJAYA"]],
|
||||
"Box-Forest2": [["1/256", 0x000405, "BRIONAC"]],
|
||||
@@ -5406,6 +5467,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001101, "SOUL BANISH"]],
|
||||
@@ -5443,6 +5505,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x000E02, "TWIN BRAND"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000706, "WALS-MK2"]],
|
||||
"Box-Forest2": [["1/256", 0x000705, "VISK-235W"]],
|
||||
@@ -5456,6 +5519,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001500, "FLAME VISIT"]],
|
||||
@@ -5493,6 +5557,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000306, "BLOODY ART"]],
|
||||
"Box-Forest2": [["1/256", 0x000305, "BLADE DANCE"]],
|
||||
@@ -5506,6 +5571,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000E02, "TWIN BRAND"]],
|
||||
@@ -5543,6 +5609,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000C05, "ICE STAFF:DAGON"]],
|
||||
"Box-Forest2": [["1/256", 0x000C04, "FIRE SCEPTER:AGNI"]],
|
||||
@@ -5556,6 +5623,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021C, "RANGER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002400, "MAGICAL PIECE"]],
|
||||
@@ -5593,6 +5661,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000806, "H&S25 JUSTICE"]],
|
||||
"Box-Forest2": [["1/256", 0x000805, "M&A60 VISE"]],
|
||||
@@ -5601,11 +5670,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010123, "SENSE PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001400, "INFERNO BAZOOKA"]],
|
||||
@@ -5643,6 +5713,7 @@
|
||||
"BARBAROUS_WOLF": [["1/32768", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/4096", 0x010121, "PARASITE WEAR:Nelgal"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001003, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x010119, "RANGER FIELD"]],
|
||||
"Box-Forest2": [["1/256", 0x010118, "HUNTER FIELD"]],
|
||||
@@ -5656,6 +5727,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["1/32768", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021E, "ATTRIBUTE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010218, "S-PARTS ver2.01"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x010122, "PARASITE WEAR:Vajulla"]],
|
||||
@@ -5693,6 +5765,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["13/536870912", 0x001001, "AGITO"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/512", 0x001005, "AGITO"]],
|
||||
"Box-Caves2": [["9/16384", 0x001004, "AGITO"]],
|
||||
"Box-Caves3": [["5/1024", 0x000206, "LAST SURVIVOR"]],
|
||||
"Box-Forest2": [["1/256", 0x000205, "FLOWEN'S SWORD"]],
|
||||
@@ -5706,6 +5779,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["7/33554432", 0x001001, "AGITO"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002100, "CHAIN SAWD"]],
|
||||
@@ -5743,6 +5817,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x000905, "CRUSH BULLET"]],
|
||||
"Box-Caves2": [["9/16384", 0x001002, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000906, "METEOR SMASH"]],
|
||||
"Box-Forest2": [["3/1024", 0x000905, "CRUSH BULLET"]],
|
||||
@@ -5756,6 +5831,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021B, "FORCE WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["5/512", 0x030D03, "Delsaber's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x001200, "SPREAD NEEDLE"]],
|
||||
@@ -5793,6 +5869,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["7/2097152", 0x030C03, "Heart of Opa Opa"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["9/65536", 0x002100, "CHAIN SAWD"]],
|
||||
"Box-Caves2": [["9/16384", 0x001006, "AGITO"]],
|
||||
"Box-Caves3": [["7/2048", 0x000506, "DISKA OF LIBERATOR"]],
|
||||
"Box-Forest2": [["3/1024", 0x000505, "SLICER OF ASSASSIN"]],
|
||||
@@ -5806,6 +5883,7 @@
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x000D01, "SILENCE CLAW"]],
|
||||
@@ -5843,6 +5921,7 @@
|
||||
"BARBAROUS_WOLF": [["9/2097152", 0x010214, "Celestial Shield"]],
|
||||
"BOOMA": [["11/1024", 0x000F00, "BRAVE KNUCKLE"]],
|
||||
"BULCLAW": [["3/262144", 0x000F02, "GOD HAND"]],
|
||||
"BULK": [["5/2048", 0x002600, "SUPPRESSED GUN"]],
|
||||
"Box-Caves2": [["9/16384", 0x001005, "AGITO"]],
|
||||
"Box-Caves3": [["3/512", 0x000605, "VARISTA"]],
|
||||
"Box-Forest2": [["5/1024", 0x000106, "KALADBOLG"]],
|
||||
@@ -5851,11 +5930,12 @@
|
||||
"CANADINE": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANADINE_GROUP": [["13/524288", 0x010222, "REGENERATE GEAR"]],
|
||||
"CANANE": [["7/16384", 0x010124, "GRAVITON PLATE"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsabre's Left Arm"]],
|
||||
"CHAOS_BRINGER": [["1/2097152", 0x030D05, "Delsaber's Left Arm"]],
|
||||
"CHAOS_SORCERER": [["9/2097152", 0x030D00, "Sorcerer's Right Arm"]],
|
||||
"CLAW": [["1/32768", 0x001200, "SPREAD NEEDLE"]],
|
||||
"DARK_BELRA": [["9/2097152", 0x010117, "Celestial Armor"]],
|
||||
"DARK_GUNNER": [["9/65536", 0x01021D, "HUNTER WALL"]],
|
||||
"DEATH_GUNNER": [["7/16384", 0x010217, "S-PARTS ver1.16"]],
|
||||
"DELSABER": [["3/4096", 0x030D04, "C-bringer's Right Arm"]],
|
||||
"DE_ROL_LE": [["3/8", 0x010120, "PARASITE WEAR:De Rol"]],
|
||||
"DIMENIAN": [["13/16384", 0x002300, "STING TIP"]],
|
||||
|
||||
+8851
-1953
File diff suppressed because it is too large
Load Diff
@@ -1251,10 +1251,9 @@ I 91446 2023-12-31 21:06:28 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=
|
||||
I 91446 2023-12-31 21:06:30 - [Commands] Received from C-4 (BBBBBBBBBBBB) (version=DC_NTE command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 91446 2023-12-31 21:06:30 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=11 flag=00)
|
||||
0000 | 11 00 3C 00 00 00 00 00 00 00 00 00 31 3A 20 41 | < 1: A
|
||||
0010 | 42 43 44 45 46 47 48 49 4A 4B 4C 20 52 41 63 74 | BCDEFGHIJKL RAct
|
||||
0020 | 20 4C 31 0A 45 70 31 20 4E 20 4E 6D 6C 20 52 65 | L1 Ep1 N Nml Re
|
||||
0030 | 64 0A 43 68 65 61 74 73 20 6F 6E 00 | d Cheats on
|
||||
0000 | 11 00 28 00 00 00 00 00 00 00 00 00 41 42 43 44 | ( ABCD
|
||||
0010 | 45 46 47 48 49 4A 4B 4C 0A 20 20 52 41 63 61 73 | EFGHIJKL RAcas
|
||||
0020 | 74 20 4C 76 31 20 4A 00 | t Lv1 J
|
||||
I 91446 2023-12-31 21:06:36 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 1C 00 36 06 00 00 00 00 00 00 00 00 00 00 | ` 6
|
||||
0010 | 5C 0F C6 43 00 00 00 00 C3 75 95 42 | \ C u B
|
||||
|
||||
@@ -1170,19 +1170,17 @@ I 94381 2023-12-29 15:36:34 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 c
|
||||
I 94381 2023-12-29 15:36:34 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 94381 2023-12-29 15:36:34 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
|
||||
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
|
||||
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
|
||||
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
|
||||
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
|
||||
0040 | 00 00 00 00 |
|
||||
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 4A 65 73 73 | $ Jess
|
||||
0010 | 0A 20 20 52 41 6D 61 72 6C 20 4C 76 32 39 20 45 | RAmarl Lv29 E
|
||||
0020 | 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:36:36 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 94381 2023-12-29 15:36:36 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
|
||||
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
|
||||
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
|
||||
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
|
||||
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
|
||||
0040 | 00 00 00 00 |
|
||||
0000 | 11 00 48 00 00 00 00 00 00 00 00 00 53 65 63 74 | H Sect
|
||||
0010 | 69 6F 6E 20 49 44 3A 20 50 69 6E 6B 61 6C 0A 09 | ion ID: Pinkal
|
||||
0020 | 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 64 | C6Cheats enabled
|
||||
0030 | 09 43 37 0A 09 43 36 43 6C 69 65 6E 74 20 64 72 | C7 C6Client dr
|
||||
0040 | 6F 70 73 09 43 37 00 00 | ops C7
|
||||
I 94381 2023-12-29 15:36:44 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 00 00 | ` ?
|
||||
0010 | 00 00 20 41 00 00 00 00 00 00 70 43 | A pC
|
||||
@@ -1198,11 +1196,9 @@ I 94381 2023-12-29 15:36:44 - [Commands] Sending to C-2 (Jess) (version=GC_V3 co
|
||||
I 94381 2023-12-29 15:36:45 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 94381 2023-12-29 15:36:45 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
|
||||
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
|
||||
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
|
||||
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
|
||||
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
|
||||
0040 | 00 00 00 00 |
|
||||
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 4A 65 73 73 | $ Jess
|
||||
0010 | 0A 20 20 52 41 6D 61 72 6C 20 4C 76 32 39 20 45 | RAmarl Lv29 E
|
||||
0020 | 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:36:45 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=99 flag=00)
|
||||
0000 | 99 00 04 00 |
|
||||
I 94381 2023-12-29 15:36:49 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=10 flag=00)
|
||||
@@ -7967,11 +7963,11 @@ I 94381 2023-12-29 15:42:16 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 c
|
||||
I 94381 2023-12-29 15:42:16 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 94381 2023-12-29 15:42:16 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
|
||||
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
|
||||
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
|
||||
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
|
||||
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
|
||||
0040 | 00 00 00 00 |
|
||||
0000 | 11 00 48 00 00 00 00 00 00 00 00 00 53 65 63 74 | H Sect
|
||||
0010 | 69 6F 6E 20 49 44 3A 20 50 69 6E 6B 61 6C 0A 09 | ion ID: Pinkal
|
||||
0020 | 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 64 | C6Cheats enabled
|
||||
0030 | 09 43 37 0A 09 43 36 50 72 69 76 61 74 65 20 64 | C7 C6Private d
|
||||
0040 | 72 6F 70 73 09 43 37 00 | rops C7
|
||||
I 94381 2023-12-29 15:42:18 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=10 flag=00)
|
||||
0000 | 10 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 94381 2023-12-29 15:42:18 - [C-5] Assigned inventory item IDs
|
||||
|
||||
@@ -4023,7 +4023,7 @@ I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
|
||||
0020 | 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
|
||||
0020 | 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
@@ -30338,7 +30338,7 @@ I 16332 2023-09-17 10:22:26 - [Commands] Received from C-2 (Tali) (version=GC co
|
||||
I 16332 2023-09-17 10:22:26 - [Commands] Received from C-2 (Tali) (version=GC command=6F flag=00)
|
||||
0000 | 6F 00 04 00 | o
|
||||
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 63 6F 6D 70 6C 65 74 | ecording complet
|
||||
0020 | 65 00 00 00 | e
|
||||
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
|
||||
@@ -30424,7 +30424,7 @@ I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
|
||||
0020 | 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
|
||||
0020 | 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
|
||||
@@ -4023,7 +4023,7 @@ I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
|
||||
0020 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
|
||||
0020 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
@@ -77175,7 +77175,7 @@ I 17097 2023-09-19 22:11:24 - [Commands] Received from C-2 (Tali) (version=GC co
|
||||
I 17097 2023-09-19 22:11:24 - [Commands] Received from C-2 (Tali) (version=GC command=6F flag=00)
|
||||
0000 | 6F 00 04 00 | o
|
||||
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 63 6F 6D 70 6C 65 74 | ecording complet
|
||||
0020 | 65 00 00 00 | e
|
||||
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
|
||||
@@ -77261,7 +77261,7 @@ I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
|
||||
0020 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
|
||||
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
|
||||
0020 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
|
||||
|
||||
@@ -5281,9 +5281,13 @@ I 23921 2024-03-03 21:21:17 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
I 23921 2024-03-03 21:21:19 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 23921 2024-03-03 21:21:19 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=11 flag=00)
|
||||
0000 | 11 00 2C 00 00 00 00 00 00 00 00 00 45 70 32 20 | , Ep2
|
||||
0010 | 4E 20 4E 6D 6C 20 56 69 72 0A 09 43 36 43 68 65 | N Nml Vir C6Che
|
||||
0020 | 61 74 73 20 6F 6E 09 43 37 00 00 00 | ats on C7
|
||||
0000 | 11 00 64 00 00 00 00 00 00 00 00 00 53 65 63 74 | d Sect
|
||||
0010 | 69 6F 6E 20 49 44 3A 20 56 69 72 69 64 69 61 0A | ion ID: Viridia
|
||||
0020 | 09 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 | C6Cheats enable
|
||||
0030 | 64 09 43 37 0A 09 43 36 50 65 72 73 69 73 74 65 | d C7 C6Persiste
|
||||
0040 | 6E 63 65 20 65 6E 61 62 6C 65 64 09 43 37 0A 09 | nce enabled C7
|
||||
0050 | 43 36 43 6C 69 65 6E 74 20 64 72 6F 70 73 09 43 | C6Client drops C
|
||||
0060 | 37 00 00 00 | 7
|
||||
I 23921 2024-03-03 21:21:22 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 52 03 00 00 00 00 00 00 00 80 FF FF | ` R
|
||||
I 23921 2024-03-03 21:21:22 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
|
||||
@@ -6244,6 +6244,8 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0010 | 00 00 00 00 |
|
||||
I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 5A 42 00 00 00 00 65 00 03 01 | ` ZB e
|
||||
I 56327 2024-03-03 23:40:27 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 FF FF 00 00 00 00 65 00 03 01 | ` e
|
||||
I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 42 03 00 00 89 8E 65 C4 32 06 7B 43 | ` B e 2 {C
|
||||
I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -6260,7 +6262,8 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 01 | ` YB d
|
||||
I 56327 2024-03-03 23:40:27 - [C-2] [Switch assist] Replaying previous enable command
|
||||
I 56327 2024-03-03 23:40:27 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 5A 42 00 00 00 00 65 00 03 01 | ` ZB e
|
||||
0000 | 60 00 1C 00 05 03 FF FF 00 00 00 00 64 00 03 01 | ` d
|
||||
0010 | 05 03 FF FF 00 00 00 00 65 00 03 01 | e
|
||||
I 56327 2024-03-03 23:40:28 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 0B 03 58 42 01 00 00 00 58 02 00 00 | ` XB X
|
||||
I 56327 2024-03-03 23:40:28 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -11671,7 +11674,9 @@ I 56327 2024-03-03 23:45:37 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0000 | 60 00 10 00 05 03 08 42 00 00 00 00 6C 00 03 01 | ` B l
|
||||
I 56327 2024-03-03 23:45:37 - [C-2] [Switch assist] Replaying previous enable command
|
||||
I 56327 2024-03-03 23:45:37 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 01 | ` YB d
|
||||
0000 | 60 00 28 00 05 03 FF FF 00 00 00 00 6C 00 03 01 | ` ( l
|
||||
0010 | 05 03 FF FF 00 00 00 00 64 00 03 01 05 03 FF FF | d
|
||||
0020 | 00 00 00 00 65 00 03 01 | e
|
||||
I 56327 2024-03-03 23:45:37 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 00 | ` YB d
|
||||
I 56327 2024-03-03 23:45:38 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -15242,7 +15247,7 @@ I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0000 | 60 00 10 00 05 03 66 43 00 00 00 00 68 00 04 01 | ` fC h
|
||||
I 56327 2024-03-03 23:49:06 - [C-2] [Switch assist] Replaying previous enable command
|
||||
I 56327 2024-03-03 23:49:06 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 08 42 00 00 00 00 6C 00 03 01 | ` B l
|
||||
0000 | 60 00 10 00 05 03 FF FF 00 00 00 00 68 00 04 01 | ` h
|
||||
I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 42 03 00 00 AB 88 9B C3 D3 0F 10 44 | ` B D
|
||||
I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -15267,7 +15272,8 @@ I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 01 | ` eC g
|
||||
I 56327 2024-03-03 23:49:08 - [C-2] [Switch assist] Replaying previous enable command
|
||||
I 56327 2024-03-03 23:49:08 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 66 43 00 00 00 00 68 00 04 01 | ` fC h
|
||||
0000 | 60 00 1C 00 05 03 FF FF 00 00 00 00 67 00 04 01 | ` g
|
||||
0010 | 05 03 FF FF 00 00 00 00 68 00 04 01 | h
|
||||
I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 0B 03 3B 43 01 00 00 00 3B 03 00 00 | ` ;C ;
|
||||
I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -16093,7 +16099,9 @@ I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3
|
||||
0000 | 60 00 10 00 05 03 41 43 00 00 00 00 66 00 04 01 | ` AC f
|
||||
I 56327 2024-03-03 23:50:04 - [C-2] [Switch assist] Replaying previous enable command
|
||||
I 56327 2024-03-03 23:50:04 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 01 | ` eC g
|
||||
0000 | 60 00 28 00 05 03 FF FF 00 00 00 00 66 00 04 01 | ` ( f
|
||||
0010 | 05 03 FF FF 00 00 00 00 67 00 04 01 05 03 FF FF | g
|
||||
0020 | 00 00 00 00 68 00 04 01 | h
|
||||
I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 00 | ` eC g
|
||||
I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
|
||||
@@ -839,11 +839,9 @@ I 97037 2023-12-29 15:57:03 - [Commands] Sending to C-5 (88888888) (version=DC_V
|
||||
I 97037 2023-12-29 15:57:05 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 97037 2023-12-29 15:57:05 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=11 flag=00)
|
||||
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
|
||||
0010 | 43 36 54 61 6C 69 09 43 37 20 52 41 63 6C 20 4C | C6Tali C7 RAcl L
|
||||
0020 | 31 38 35 0A 45 70 31 20 4E 20 4E 6D 6C 20 50 72 | 185 Ep1 N Nml Pr
|
||||
0030 | 70 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 | p C6Cheats on C
|
||||
0040 | 37 00 00 00 | 7
|
||||
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 54 61 6C 69 | $ Tali
|
||||
0010 | 0A 20 20 52 41 63 61 73 65 61 6C 20 4C 76 31 38 | RAcaseal Lv18
|
||||
0020 | 35 20 45 00 | 5 E
|
||||
I 97037 2023-12-29 15:57:06 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=10 flag=00)
|
||||
0000 | 10 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 97037 2023-12-29 15:57:06 - [C-5] Assigned inventory item IDs
|
||||
|
||||
+17
-1
@@ -20,6 +20,10 @@ echo "... compress with level=0"
|
||||
$EXECUTABLE compress-$SCHEME --compression-level=0 $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.l0
|
||||
echo "... compress with level=1"
|
||||
$EXECUTABLE compress-$SCHEME --compression-level=1 $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.l1
|
||||
echo "... compress optimally"
|
||||
$EXECUTABLE compress-$SCHEME --optimal $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.lo
|
||||
echo "... compress pessimally"
|
||||
$EXECUTABLE compress-$SCHEME --pessimal $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.lp
|
||||
|
||||
echo "... decompress from level=-1 (no compression)"
|
||||
$EXECUTABLE decompress-$SCHEME $BASENAME.mnrd.$SCHEME.lN $BASENAME.mnrd.$SCHEME.lN.dec
|
||||
@@ -27,6 +31,10 @@ echo "... decompress from level=0"
|
||||
$EXECUTABLE decompress-$SCHEME $BASENAME.mnrd.$SCHEME.l0 $BASENAME.mnrd.$SCHEME.l0.dec
|
||||
echo "... decompress from level=1"
|
||||
$EXECUTABLE decompress-$SCHEME $BASENAME.mnrd.$SCHEME.l1 $BASENAME.mnrd.$SCHEME.l1.dec
|
||||
echo "... decompress from optimal"
|
||||
$EXECUTABLE decompress-$SCHEME $BASENAME.mnrd.$SCHEME.lo $BASENAME.mnrd.$SCHEME.lo.dec
|
||||
echo "... decompress from pessimal"
|
||||
$EXECUTABLE decompress-$SCHEME $BASENAME.mnrd.$SCHEME.lp $BASENAME.mnrd.$SCHEME.lp.dec
|
||||
|
||||
echo "... check result from level=-1 (no compression)"
|
||||
diff $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.lN.dec
|
||||
@@ -34,12 +42,20 @@ echo "... check result from level=0"
|
||||
diff $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.l0.dec
|
||||
echo "... check result from level=1"
|
||||
diff $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.l1.dec
|
||||
echo "... check result from optimal"
|
||||
diff $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.lo.dec
|
||||
echo "... check result from pessimal"
|
||||
diff $BASENAME.mnrd $BASENAME.mnrd.$SCHEME.lp.dec
|
||||
|
||||
echo "... clean up"
|
||||
rm $BASENAME.mnrd \
|
||||
$BASENAME.mnrd.$SCHEME.lN \
|
||||
$BASENAME.mnrd.$SCHEME.l0 \
|
||||
$BASENAME.mnrd.$SCHEME.l1 \
|
||||
$BASENAME.mnrd.$SCHEME.lo \
|
||||
$BASENAME.mnrd.$SCHEME.lp \
|
||||
$BASENAME.mnrd.$SCHEME.lN.dec \
|
||||
$BASENAME.mnrd.$SCHEME.l0.dec \
|
||||
$BASENAME.mnrd.$SCHEME.l1.dec
|
||||
$BASENAME.mnrd.$SCHEME.l1.dec \
|
||||
$BASENAME.mnrd.$SCHEME.lo.dec \
|
||||
$BASENAME.mnrd.$SCHEME.lp.dec
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"DefaultDropModeV4Battle": "SERVER_SHARED",
|
||||
"DefaultDropModeV4Challenge": "SERVER_SHARED",
|
||||
"CheatModeBehavior": "OnByDefault",
|
||||
"UnlockAllAreas": false,
|
||||
"RareNotificationsEnabledByDefault": false,
|
||||
|
||||
"LocalAddress": "en0",
|
||||
@@ -40,6 +41,8 @@
|
||||
"PPPStackListen": [],
|
||||
"HTTPListen": [],
|
||||
"Episode3BehaviorFlags": 0xFA,
|
||||
"EnableEpisode3SendFunctionCall": false,
|
||||
"EnableV3V4ProtectedSubcommands": false,
|
||||
|
||||
"Episode3InfiniteMeseta": false,
|
||||
"Episode3DefeatPlayerMeseta": [400, 500, 600, 700, 800],
|
||||
@@ -63,6 +66,7 @@
|
||||
"xb": [9500, "xb", "login_server"],
|
||||
"pc-patch": [10000, "patch", "patch_server_pc"],
|
||||
"bb-patch": [11000, "patch", "patch_server_bb"],
|
||||
"bb-patch-hg": [11200, "patch", "patch_server_bb"],
|
||||
"bb-patch2": [11100, "patch", "patch_server_bb"],
|
||||
"bb-patch3": [10500, "patch", "patch_server_bb"],
|
||||
"bb-init": [12000, "bb", "login_server"],
|
||||
|
||||
Reference in New Issue
Block a user