Compare commits

..

33 Commits

Author SHA1 Message Date
Martin Michelsen cd008ab0ba rewrite DeckState::draw_card_by_ref 2024-03-23 21:02:00 -07:00
Martin Michelsen 53b36d7074 put an extra \n in choice search result text 2024-03-23 21:02:00 -07:00
Martin Michelsen 5a1880bd65 allow sender_c to be null in Ep3 server command handlers 2024-03-23 21:02:00 -07:00
Martin Michelsen 8e280a1464 fix wrong type in default ep3 behavior flags 2024-03-22 22:25:14 -07:00
Martin Michelsen 0bcdd9997e define choice_search_config in gc char file format 2024-03-22 22:25:04 -07:00
Martin Michelsen d5351c4580 set BB player mag color at char creation time 2024-03-22 22:24:45 -07:00
Martin Michelsen 76bc2385ca add PSOBB Hangame functions 2024-03-22 22:24:04 -07:00
Martin Michelsen 325f7c6efc add UnlockAllAreas config option 2024-03-18 10:03:37 -07:00
Martin Michelsen 93d97d3e5b factor out debug mode check 2024-03-17 21:16:31 -07:00
Martin Michelsen 66b64603a0 add $sb command 2024-03-17 19:03:24 -07:00
Martin Michelsen 7405eaea0b add format-ep3-battle-record command 2024-03-17 14:12:57 -07:00
Martin Michelsen 477e433361 update some command notes 2024-03-17 14:12:57 -07:00
Martin Michelsen 7ca2012bc4 add CA commands into Ep3 battle record format 2024-03-16 18:48:27 -07:00
Martin Michelsen dace165ef2 fix enemy data json in /y/data/common-tables 2024-03-16 18:45:35 -07:00
Martin Michelsen f6df2b5b45 add note about C4 crash 2024-03-16 18:45:11 -07:00
Martin Michelsen 1a310df17e fix choice search crash 2024-03-16 09:57:35 -07:00
Martin Michelsen 31edec701b refine game info messages 2024-03-15 22:59:50 -07:00
Martin Michelsen dc36d2ae8d fix quest expr checks from lobby 2024-03-15 10:20:19 -07:00
Martin Michelsen 4e733b0dc6 add object type name in map disassembly 2024-03-15 00:32:00 -07:00
Martin Michelsen 6eadaaca66 use pthreads for libevent on windows 2024-03-15 00:31:50 -07:00
Martin Michelsen d778340999 add BB format of 6x6F command 2024-03-15 00:31:33 -07:00
Martin Michelsen e2d76f77be extend switch assist to 4-player doors 2024-03-14 00:14:40 -07:00
Martin Michelsen 0b80af3f41 fix format code in event action stream disassembly 2024-03-13 22:04:39 -07:00
Martin Michelsen f65acda803 reorder initializers in Map::Object construction 2024-03-13 10:06:07 -07:00
Martin Michelsen 53f485b8f2 fix variable overshadow in 6x6F queued case 2024-03-13 09:53:47 -07:00
Martin Michelsen 69f40f9157 extend persistence to enemy, set, and switch flags 2024-03-12 23:43:08 -07:00
Martin Michelsen 84bb946e05 fix error message for bad entry in trap card list 2024-03-12 20:15:53 -07:00
Martin Michelsen eb132f38d2 fix Ep3 map formatting bug 2024-03-12 20:15:53 -07:00
Martin Michelsen 0f1fbb1069 fix infinite loop edge case in text transcoding 2024-03-12 12:09:12 -07:00
Martin Michelsen c9f7ca2259 add BULK and DEATH_GUNNER to rare tables 2024-03-10 15:21:29 -07:00
Martin Michelsen 8594e5af3c add condition clearing and auto-revive to infinite hp mode 2024-03-10 12:07:30 -07:00
Martin Michelsen 6b5e657630 make name colors appear correctly in v2/v3 crossplay 2024-03-10 12:07:30 -07:00
Martin Michelsen a7845e4b0e add logging for p36 target mode in Ep3 2024-03-10 12:07:30 -07:00
69 changed files with 10789 additions and 2514 deletions
+5 -4
View File
@@ -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.
+19
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+17 -3
View File
@@ -219,6 +219,7 @@ public:
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<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;
+51 -29
View File
@@ -2491,6 +2491,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 +3831,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 +4432,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 +4645,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 +4764,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 +4783,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 +4973,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 +5015,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 +5257,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 +5274,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 +5385,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
View File
@@ -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({
+119 -108
View File
@@ -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:
+1
View File
@@ -20,6 +20,7 @@ enum class EnemyType {
BOOMA,
BOOTA,
BULCLAW,
BULK,
CANADINE,
CANADINE_GROUP,
CANANE,
+72
View File
@@ -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++;
+6
View File
@@ -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;
+15
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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 {
+1 -2
View File
@@ -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;
}
+9 -4
View File
@@ -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) {
+6
View File
@@ -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");
+2
View File
@@ -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
View File
@@ -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 {
+5 -4
View File
@@ -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
View File
@@ -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;
}
}
+1
View File
@@ -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;
+55 -7
View File
@@ -1115,6 +1115,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 +1476,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 +2285,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,12 +2357,7 @@ 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) {
if (evthread_use_pthreads()) {
throw runtime_error("failed to setup libevent threads");
}
+191 -38
View File
@@ -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
View File
@@ -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 {
+67
View File
@@ -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
}};
+28
View File
@@ -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;
+7 -9
View File
@@ -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);
}
-1
View File
@@ -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
View File
@@ -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;
+146 -118
View File
@@ -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
View File
@@ -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},
+132
View File
@@ -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;
}
+4 -1
View File
@@ -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
View File
@@ -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);
}
}
+1
View File
@@ -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(
+7 -4
View File
@@ -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()));
}
}
}
+2
View File
@@ -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};
+13
View File
@@ -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};
+1
View File
@@ -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);
+1
View File
@@ -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
+14 -1
View File
@@ -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
+88 -8
View File
@@ -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"]],
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -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
+16 -20
View File
@@ -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
+3 -3
View File
@@ -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)
+7 -3
View File
@@ -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)
+13 -5
View File
@@ -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)
+3 -5
View File
@@ -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
+4
View File
@@ -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"],