diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 05973ab3..db0060fe 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2727,38 +2727,42 @@ struct S_RareMonsterList_BB_DE { parray enemy_ids; } __packed__; -// DF (C->S): Unknown (BB) -// This command has many subcommands. It's not clear what any of them do. +// DF (C->S): Set Challenge Mode parameters (BB) +// This command has 7 subcommands, most of which are self-explanatory. -struct C_Unknown_BB_01DF { - le_uint32_t unknown_a1 = 0; +struct C_SetChallengeModeStageNumber_BB_01DF { + le_uint32_t stage = 0; } __packed__; -struct C_Unknown_BB_02DF { - le_uint32_t unknown_a1 = 0; +struct C_SetChallengeModeCharacterTemplate_BB_02DF { + le_uint32_t template_index = 0; } __packed__; -struct C_Unknown_BB_03DF { - le_uint32_t unknown_a1 = 0; +struct C_SetChallengeModeDifficulty_BB_03DF { + // No existing challenge mode quest sets this to a value other than zero. + le_uint32_t difficulty = 0; } __packed__; -struct C_Unknown_BB_04DF { - le_uint32_t unknown_a1 = 0; +struct C_SetChallengeModeEXPMultiplier_BB_04DF { + le_float exp_multiplier = 0; } __packed__; -struct C_Unknown_BB_05DF { - le_uint32_t unknown_a1 = 0; - pstring unknown_a2; +struct C_SetChallengeRankText_BB_05DF { + le_uint32_t rank_color = 0; // ARGB8888 + pstring rank_text; } __packed__; -struct C_Unknown_BB_06DF { - parray unknown_a1; +// This is sent once for each rank (so, 3 times in total) +struct C_SetChallengeRankThreshold_BB_06DF { + le_uint32_t rank = 0; // 0 = B, 1 = A, 2 = S + le_uint32_t seconds = 0; + le_uint32_t rank_bitmask = 0; // 1 = B, 2 = A, 4 = S } __packed__; -struct C_Unknown_BB_07DF { - le_uint32_t unused1 = 0xFFFFFFFF; - le_uint32_t unused2 = 0; // Always 0 - parray unknown_a1; +struct C_CreateChallengeModeAwardItem_BB_07DF { + le_uint32_t prize_rank = 0xFFFFFFFF; // 0 = B, 1 = A, 2 = S, anything else = error + le_uint32_t rank_bitmask = 0; // Same as in 06DF + ItemData item; } __packed__; // E0 (S->C): Tournament list (Episode 3) @@ -3458,34 +3462,34 @@ struct C_LeaveCharacterSelect_BB_00EC { // sending an 84 to be added to a lobby. This is used when a spectator team is // disbanded because the target game ends. -// ED (C->S): Update account data (BB) +// ED (C->S): Update save file data (BB) // There are several subcommands (noted in the structs below) that each update a -// specific kind of account data. +// specific kind of data. // TODO: Actually define these structures and don't just treat them as raw data -struct C_UpdateAccountOptionFlags_BB_01ED { +struct C_UpdateOptionFlags_BB_01ED { le_uint32_t option_flags = 0; } __packed__; -struct C_UpdateAccountSymbolChats_BB_02ED { +struct C_UpdateSymbolChats_BB_02ED { parray symbol_chats; } __packed__; -struct C_UpdateAccountChatShortcuts_BB_03ED { +struct C_UpdateChatShortcuts_BB_03ED { parray chat_shortcuts; } __packed__; -struct C_UpdateAccountKeyConfig_BB_04ED { +struct C_UpdateKeyConfig_BB_04ED { parray key_config; } __packed__; -struct C_UpdateAccountPadConfig_BB_05ED { +struct C_UpdatePadConfig_BB_05ED { parray pad_config; } __packed__; -struct C_UpdateAccountTechMenu_BB_06ED { +struct C_UpdateTechMenu_BB_06ED { parray tech_menu; } __packed__; -struct C_UpdateAccountCustomizeMenu_BB_07ED { +struct C_UpdateCustomizeMenu_BB_07ED { parray customize; } __packed__; -struct C_UpdateAccountChallengeAndBattleConfig_BB_08ED { - parray challenge_battle_config; +struct C_UpdateChallengeRecords_BB_08ED { + PlayerRecordsBB_Challenge records; } __packed__; // EE: Trade cards (Episode 3) diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index ba5e8c72..4726f298 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -2578,16 +2578,16 @@ const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language e.modification_tiles = vm->map->modification_tiles; e.name_offset = strings_w.size(); - strings_w.write(vm->map->name.data, vm->map->name.used_bytes_8()); + strings_w.write(vm->map->name.data, vm->map->name.used_chars_8()); strings_w.put_u8(0); e.location_name_offset = strings_w.size(); - strings_w.write(vm->map->location_name.data, vm->map->location_name.used_bytes_8()); + strings_w.write(vm->map->location_name.data, vm->map->location_name.used_chars_8()); strings_w.put_u8(0); e.quest_name_offset = strings_w.size(); - strings_w.write(vm->map->quest_name.data, vm->map->quest_name.used_bytes_8()); + strings_w.write(vm->map->quest_name.data, vm->map->quest_name.used_chars_8()); strings_w.put_u8(0); e.description_offset = strings_w.size(); - strings_w.write(vm->map->description.data, vm->map->description.used_bytes_8()); + strings_w.write(vm->map->description.data, vm->map->description.used_chars_8()); strings_w.put_u8(0); e.map_category = vm->map->map_category; diff --git a/src/Lobby.cc b/src/Lobby.cc index a26b3660..ea5330ce 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -23,7 +23,7 @@ Lobby::Lobby(shared_ptr s, uint32_t id) episode(Episode::NONE), mode(GameMode::NORMAL), difficulty(0), - exp_multiplier(1), + exp_multiplier(1.0f), random_seed(random_object()), event(0), block(0), @@ -43,6 +43,13 @@ shared_ptr Lobby::require_server_state() const { return s; } +shared_ptr Lobby::require_challenge_params() const { + if (!this->challenge_params) { + throw runtime_error("challenge params are missing"); + } + return this->challenge_params; +} + void Lobby::create_item_creator() { auto s = this->require_server_state(); diff --git a/src/Lobby.hh b/src/Lobby.hh index 45fa9b58..025e1b72 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -81,7 +81,7 @@ struct Lobby : public std::enable_shared_from_this { Episode episode; GameMode mode; uint8_t difficulty; // 0-3 - uint16_t exp_multiplier; + float exp_multiplier; std::string password; std::string name; // This seed is also sent to the client for rare enemy generation @@ -89,6 +89,18 @@ struct Lobby : public std::enable_shared_from_this { std::shared_ptr random_crypt; std::shared_ptr item_creator; + struct ChallengeParameters { + uint8_t stage_number = 0; + uint32_t rank_color = 0xFFFFFFFF; + std::string rank_text; + struct RankThreshold { + uint32_t bitmask = 0; + uint32_t seconds = 0; + }; + parray rank_thresholds; + }; + std::shared_ptr challenge_params; + // Ep3 stuff // There are three kinds of Episode 3 games. All of these types have the flag // EPISODE_3_ONLY; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag. @@ -139,6 +151,7 @@ struct Lobby : public std::enable_shared_from_this { } std::shared_ptr require_server_state() const; + std::shared_ptr require_challenge_params() const; void create_item_creator(); void create_ep3_server(); diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 5a6a8468..76e22867 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -312,6 +312,13 @@ struct PlayerLobbyDataBB { void clear(); } __attribute__((packed)); +template +struct ChallengeAwardState { + using U32T = typename std::conditional::type; + U32T rank_award_flags = 0; + U32T maximum_rank = 0; // Encrypted; see decrypt_challenge_time +} __attribute__((packed)); + template struct PlayerRecordsDCPC_Challenge { /* 00 */ le_uint16_t title_color = 0x7FFF; @@ -354,7 +361,10 @@ struct PlayerRecordsV3_Challenge { /* 7C:98 */ pstring grave_team; /* 90:AC */ pstring grave_message; /* B0:CC */ parray unknown_m5; - /* B4:D0 */ parray unknown_t6; + /* B4:D0 */ parray unknown_t6; + /* C0:DC */ ChallengeAwardState ep1_online_award_state; + /* C8:E4 */ ChallengeAwardState ep2_online_award_state; + /* D0:EC */ ChallengeAwardState ep1_offline_award_state; /* D8:F4 */ } __attribute__((packed)); /* 0000:001C */ Stats stats; @@ -383,7 +393,10 @@ struct PlayerRecordsBB_Challenge { /* 007C */ pstring grave_team; /* 00A4 */ pstring grave_message; /* 00E4 */ parray unknown_m5; - /* 00E8 */ parray unknown_t6; + /* 00E8 */ parray unknown_t6; + /* 00F4 */ ChallengeAwardState ep1_online_award_state; + /* 00FC */ ChallengeAwardState ep2_online_award_state; + /* 0104 */ ChallengeAwardState ep1_offline_award_state; /* 010C */ pstring rank_title; // Encrypted; see decrypt_challenge_rank_text /* 0124 */ parray unknown_l7; /* 0140 */ diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 37107474..2c1eefe7 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1650,7 +1650,7 @@ static HandlerResult C_81(shared_ptr ses, uint16_t, } } // GC clients send uninitialized memory here; don't forward it - cmd.text.clear_after(cmd.text.used_bytes_8()); + cmd.text.clear_after(cmd.text.used_chars_8()); return HandlerResult::Type::MODIFIED; } diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 7685c123..0bae05b8 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -484,7 +484,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF821, "nop_F821", {{REG_SET_FIXED, 9}}, F_V2_V4}, // regsA[3-8] specify first 6 bytes of an ItemData. This opcode consumes an item ID, but does nothing else. {0xF822, "nop_F822", {REG}, F_V2_V4}, {0xF823, "set_cmode_char_template", {INT32}, F_V2_V4 | F_ARGS}, - {0xF824, "set_cmode_diff", {INT32}, F_V2_V4 | F_ARGS}, + {0xF824, "set_cmode_difficulty", {INT32}, F_V2_V4 | F_ARGS}, {0xF825, "exp_multiplication", {{REG_SET_FIXED, 3}}, F_V2_V4}, {0xF826, "if_player_alive_cm", {REG}, F_V2_V4}, {0xF827, "get_user_is_dead", {REG}, F_V2_V4}, @@ -537,11 +537,11 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF862, "give_s_rank_weapon", {INT32, REG, CSTRING}, F_V3_V4 | F_ARGS}, {0xF863, "get_mag_levels", {{REG32_SET_FIXED, 4}}, F_V2}, {0xF863, "get_mag_levels", {{REG_SET_FIXED, 4}}, F_V3_V4}, - {0xF864, "cmode_rank", {INT32, CSTRING}, F_V2_V4 | F_ARGS}, + {0xF864, "set_cmode_rank_result", {INT32, CSTRING}, F_V2_V4 | F_ARGS}, {0xF865, "award_item_name", {}, F_V2_V4}, {0xF866, "award_item_select", {}, F_V2_V4}, {0xF867, "award_item_give_to", {REG}, F_V2_V4}, - {0xF868, "set_cmode_rank", {REG, REG}, F_V2_V4}, + {0xF868, "set_cmode_rank_threshold", {REG, REG}, F_V2_V4}, {0xF869, "check_rank_time", {REG, REG}, F_V2_V4}, {0xF86A, "item_create_cmode", {{REG_SET_FIXED, 6}, REG}, F_V2_V4}, // regsA specifies item.data1[0-5] {0xF86B, "ba_set_box_drop_area", {REG}, F_V2_V4}, // TODO: This sets override_area in TItemDropSub; use this in ItemCreator diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 8b8a605e..95cec4cd 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2940,12 +2940,12 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { } } -static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { +static void on_E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_system_file_bb(c); } -static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { +static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (c->bb_connection_phase != 0x00) { @@ -2973,7 +2973,7 @@ static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } } -static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { +static void on_E8_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry); constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB); switch (command) { @@ -3118,7 +3118,7 @@ static void on_DC_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } } -static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { +static void on_EB_BB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { check_size_v(data.size(), 0); if (command == 0x04EB) { @@ -3130,11 +3130,11 @@ static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, st } } -static void on_00EC_BB(shared_ptr, uint16_t, uint32_t, string& data) { +static void on_EC_BB(shared_ptr, uint16_t, uint32_t, string& data) { check_size_t(data); } -static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { +static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (!c->license) { @@ -3169,46 +3169,46 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { send_approve_player_choice_bb(c); } -static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { +static void on_ED_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { switch (command) { case 0x01ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.account()->option_flags = cmd.option_flags; break; } case 0x02ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.account()->symbol_chats = cmd.symbol_chats; break; } case 0x03ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.account()->shortcuts = cmd.chat_shortcuts; break; } case 0x04ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.account()->system_file.key_config = cmd.key_config; break; } case 0x05ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.account()->system_file.joystick_config = cmd.pad_config; break; } case 0x06ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.player()->tech_menu_config = cmd.tech_menu; break; } case 0x07ED: { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); c->game_data.player()->disp.config = cmd.customize; break; } case 0x08ED: { - check_size_t(data); - // TODO: Save this data appropriately + const auto& cmd = check_size_t(data); + c->game_data.player()->challenge_records = cmd.records; break; } default: @@ -3216,7 +3216,7 @@ static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, string& } } -static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { +static void on_E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); // We only trust the player's quest data and challenge data. @@ -3230,11 +3230,97 @@ static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { p->quest_data2 = cmd.quest_data2; } -static void on_00E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { +static void on_E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); c->game_data.account()->system_file = cmd; } +static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + if (!l->is_game()) { + throw runtime_error("challenge mode config command sent outside of game"); + } + if (l->mode != GameMode::CHALLENGE) { + throw runtime_error("challenge mode config command sent in non-challenge game"); + } + auto cp = l->require_challenge_params(); + + switch (command) { + case 0x01DF: { + const auto& cmd = check_size_t(data); + cp->stage_number = cmd.stage; + l->log.info("(Challenge mode) Stage number set to %02hhX", cp->stage_number); + break; + } + + case 0x02DF: { + const auto& cmd = check_size_t(data); + if (!l->quest) { + throw runtime_error("challenge mode character template config command sent in non-challenge game"); + } + if (l->quest->challenge_template_index != cmd.template_index) { + throw runtime_error("challenge template index in quest metadata does not match index sent by client"); + } + // Do nothing: we've already created the player overlay by the time this + // opcode is run on the client. We can't easily move the overlay creation + // here, since non-BB versions do not send anything when they create the + // overlay, so we have to do it when the quest loads instead. + break; + } + + case 0x03DF: { + const auto& cmd = check_size_t(data); + if (l->difficulty != cmd.difficulty) { + l->difficulty = cmd.difficulty; + l->create_item_creator(); + } + l->log.info("(Challenge mode) Difficulty set to %02hhX", l->difficulty); + break; + } + + case 0x04DF: { + const auto& cmd = check_size_t(data); + l->exp_multiplier = cmd.exp_multiplier; + l->log.info("(Challenge mode) EXP multiplier set to %g", l->exp_multiplier); + break; + } + + case 0x05DF: { + const auto& cmd = check_size_t(data); + cp->rank_color = cmd.rank_color; + cp->rank_text = cmd.rank_text.decode(); + l->log.info("(Challenge mode) Rank text set to %s (color %08" PRIX32 ")", cp->rank_text.c_str(), cp->rank_color); + break; + } + + case 0x06DF: { + const auto& cmd = check_size_t(data); + auto& threshold = cp->rank_thresholds[cmd.rank]; + threshold.bitmask = cmd.rank_bitmask; + threshold.seconds = cmd.seconds; + string time_str = format_duration(static_cast(threshold.seconds) * 1000000); + l->log.info("(Challenge mode) Rank %c threshold set to %s (bitmask %08" PRIX32 ")", + char_for_challenge_rank(cmd.rank), time_str.c_str(), threshold.bitmask); + break; + } + + case 0x07DF: { + const auto& cmd = check_size_t(data); + auto p = c->game_data.player(true, false); + auto& award_state = (l->episode == Episode::EP2) + ? p->challenge_records.ep2_online_award_state + : p->challenge_records.ep1_online_award_state; + award_state.rank_award_flags |= cmd.rank_bitmask; + p->add_item(cmd.item); + l->on_item_id_generated_externally(cmd.item.id); + string desc = s->describe_item(GameVersion::BB, cmd.item, false); + l->log.info("(Challenge mode) Item awarded to player %hhu: %s", c->lobby_client_id, desc.c_str()); + break; + } + } +} + static void on_89(shared_ptr c, uint16_t, uint32_t flag, string& data) { check_size_v(data.size(), 0); @@ -3520,6 +3606,9 @@ shared_ptr create_game_generic( : p->disp.visual.section_id; game->episode = episode; game->mode = mode; + if (game->mode == GameMode::CHALLENGE) { + game->challenge_params.reset(new Lobby::ChallengeParameters()); + } game->difficulty = difficulty; if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) { game->random_seed = c->config.override_random_seed; @@ -3758,7 +3847,7 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data c->config.clear_flag(Client::Flag::LOADING); send_resume_game(l, c); - if (l->base_version == GameVersion::BB) { + if ((l->base_version == GameVersion::BB) && (l->mode != GameMode::CHALLENGE)) { send_set_exp_multiplier(l); } send_server_time(c); @@ -4055,7 +4144,7 @@ static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { send_ep3_card_auction(l); } -static void on_xxEA_BB(shared_ptr c, uint16_t command, uint32_t, string&) { +static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t, string&) { // TODO: Implement teams. This command has a very large number of subcommands // (up to 20EA!). if (command == 0x01EA) { @@ -4461,22 +4550,22 @@ static on_command_t handlers[0x100][6] = { /* DC */ {nullptr, nullptr, nullptr, on_DC_Ep3, nullptr, on_DC_BB}, /* DD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* DE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, + /* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_DF_BB}, // PATCH DC PC GC XB BB - /* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_00E0_BB}, + /* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E0_BB}, /* E1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* E2 */ {nullptr, nullptr, nullptr, on_E2_Ep3, nullptr, on_00E2_BB}, - /* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_00E3_BB}, + /* E2 */ {nullptr, nullptr, nullptr, on_E2_Ep3, nullptr, on_E2_BB}, + /* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E3_BB}, /* E4 */ {nullptr, nullptr, nullptr, on_E4_Ep3, nullptr, nullptr}, - /* E5 */ {nullptr, nullptr, nullptr, on_E5_Ep3, nullptr, on_00E5_BB}, + /* E5 */ {nullptr, nullptr, nullptr, on_E5_Ep3, nullptr, on_E5_BB}, /* E6 */ {nullptr, nullptr, nullptr, on_08_E6, nullptr, nullptr}, - /* E7 */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_00E7_BB}, - /* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_00E8_BB}, + /* E7 */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_E7_BB}, + /* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_E8_BB}, /* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, - /* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_xxEA_BB}, - /* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_xxEB_BB}, - /* EC */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_00EC_BB}, - /* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_xxED_BB}, + /* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_EA_BB}, + /* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_EB_BB}, + /* EC */ {nullptr, nullptr, nullptr, on_0C_C1_E7_EC, nullptr, on_EC_BB}, + /* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_ED_BB}, /* EE */ {nullptr, nullptr, nullptr, on_EE_Ep3, nullptr, nullptr}, /* EF */ {nullptr, nullptr, nullptr, on_EF_Ep3, nullptr, nullptr}, /* F0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 2c918001..85238cce 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2318,7 +2318,10 @@ void send_set_exp_multiplier(std::shared_ptr l) { if (!l->is_game()) { throw logic_error("6xDD can only be sent in games (not in lobbies)"); } - G_SetEXPMultiplier_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, l->exp_multiplier}}; + if (floorf(l->exp_multiplier) != l->exp_multiplier) { + throw runtime_error("EXP multiplier is not an integer"); + } + G_SetEXPMultiplier_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, static_cast(l->exp_multiplier)}}; send_command_t(l, 0x60, 0x00, cmd); } diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 81358a4d..29bbc417 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -762,3 +762,10 @@ uint32_t class_flags_for_class(uint8_t char_class) { } return flags[char_class]; } + +char char_for_challenge_rank(uint8_t rank) { + if (rank > 2) { + return '?'; + } + return "BAS"[rank]; +} diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 0c976c67..25d6a719 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -76,3 +76,5 @@ uint8_t area_for_name(const std::string& name); const char* name_for_area(Episode episode, uint8_t area); uint32_t class_flags_for_class(uint8_t char_class); + +char char_for_challenge_rank(uint8_t rank); diff --git a/src/Text.hh b/src/Text.hh index 436a488f..6940bb24 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -395,22 +395,22 @@ struct pstring { try { switch (Encoding) { case TextEncoding::CHALLENGE8: { - std::string decrypted(reinterpret_cast(this->data), this->used_bytes_8()); + std::string decrypted(reinterpret_cast(this->data), this->used_chars_8()); decrypt_challenge_rank_text_t(decrypted.data(), decrypted.size()); return tt_ascii_to_utf8(decrypted.data(), decrypted.size()); } case TextEncoding::ASCII: - return tt_ascii_to_utf8(this->data, this->used_bytes_8()); + return tt_ascii_to_utf8(this->data, this->used_chars_8()); case TextEncoding::ISO8859: - return tt_8859_to_utf8(this->data, this->used_bytes_8()); + return tt_8859_to_utf8(this->data, this->used_chars_8()); case TextEncoding::SJIS: - return tt_sjis_to_utf8(this->data, this->used_bytes_8()); + return tt_sjis_to_utf8(this->data, this->used_chars_8()); case TextEncoding::UTF16: - return tt_utf16_to_utf8(this->data, this->used_bytes_16()); + return tt_utf16_to_utf8(this->data, this->used_chars_16()); case TextEncoding::UTF8: - return std::string(reinterpret_cast(&this->data[0]), this->used_bytes_8()); + return std::string(reinterpret_cast(&this->data[0]), this->used_chars_8()); case TextEncoding::CHALLENGE16: { - std::string decrypted(reinterpret_cast(&this->data[0]), this->used_bytes_8()); + std::string decrypted(reinterpret_cast(&this->data[0]), this->used_chars_16() * 2); decrypt_challenge_rank_text_t(decrypted.data(), decrypted.size()); return tt_utf16_to_utf8(decrypted.data(), decrypted.size()); } @@ -426,8 +426,8 @@ struct pstring { } } return client_language - ? tt_8859_to_utf8(&this->data[offset], this->used_bytes_8() - offset) - : tt_sjis_to_utf8(&this->data[offset], this->used_bytes_8() - offset); + ? tt_8859_to_utf8(&this->data[offset], this->used_chars_8() - offset) + : tt_sjis_to_utf8(&this->data[offset], this->used_chars_8() - offset); } default: throw std::logic_error("unknown text encoding"); @@ -449,7 +449,7 @@ struct pstring { return this->decode(language) == other; } - size_t used_bytes_8() const { + size_t used_chars_8() const { size_t size = 0; for (size = 0; size < Bytes; size++) { if (!this->data[size]) { @@ -459,7 +459,7 @@ struct pstring { return Bytes; } - size_t used_bytes_16() const { + size_t used_chars_16() const { if (Bytes & 1) { throw std::logic_error("used_bytes_16 must not be called on an odd-length pstring"); } @@ -468,7 +468,7 @@ struct pstring { return z; } } - return Bytes; + return Bytes >> 1; } bool empty() const {