implement BB challenge param commands

This commit is contained in:
Martin Michelsen
2023-11-08 22:08:59 -08:00
parent 3ea65ccc75
commit cabd03a82e
12 changed files with 224 additions and 86 deletions
+34 -30
View File
@@ -2727,38 +2727,42 @@ struct S_RareMonsterList_BB_DE {
parray<le_uint16_t, 0x10> 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<TextEncoding::UTF16, 0x0C> unknown_a2;
struct C_SetChallengeRankText_BB_05DF {
le_uint32_t rank_color = 0; // ARGB8888
pstring<TextEncoding::CHALLENGE16, 0x0C> rank_text;
} __packed__;
struct C_Unknown_BB_06DF {
parray<le_uint32_t, 3> 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<le_uint32_t, 5> 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<uint8_t, 0x4E0> symbol_chats;
} __packed__;
struct C_UpdateAccountChatShortcuts_BB_03ED {
struct C_UpdateChatShortcuts_BB_03ED {
parray<uint8_t, 0xA40> chat_shortcuts;
} __packed__;
struct C_UpdateAccountKeyConfig_BB_04ED {
struct C_UpdateKeyConfig_BB_04ED {
parray<uint8_t, 0x16C> key_config;
} __packed__;
struct C_UpdateAccountPadConfig_BB_05ED {
struct C_UpdatePadConfig_BB_05ED {
parray<uint8_t, 0x38> pad_config;
} __packed__;
struct C_UpdateAccountTechMenu_BB_06ED {
struct C_UpdateTechMenu_BB_06ED {
parray<uint8_t, 0x28> tech_menu;
} __packed__;
struct C_UpdateAccountCustomizeMenu_BB_07ED {
struct C_UpdateCustomizeMenu_BB_07ED {
parray<uint8_t, 0xE8> customize;
} __packed__;
struct C_UpdateAccountChallengeAndBattleConfig_BB_08ED {
parray<uint8_t, 0x140> challenge_battle_config;
struct C_UpdateChallengeRecords_BB_08ED {
PlayerRecordsBB_Challenge records;
} __packed__;
// EE: Trade cards (Episode 3)
+4 -4
View File
@@ -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;
+8 -1
View File
@@ -23,7 +23,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
episode(Episode::NONE),
mode(GameMode::NORMAL),
difficulty(0),
exp_multiplier(1),
exp_multiplier(1.0f),
random_seed(random_object<uint32_t>()),
event(0),
block(0),
@@ -43,6 +43,13 @@ shared_ptr<ServerState> Lobby::require_server_state() const {
return s;
}
shared_ptr<Lobby::ChallengeParameters> 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();
+14 -1
View File
@@ -81,7 +81,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
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<Lobby> {
std::shared_ptr<PSOLFGEncryption> random_crypt;
std::shared_ptr<ItemCreator> 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<RankThreshold, 3> rank_thresholds;
};
std::shared_ptr<ChallengeParameters> 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<Lobby> {
}
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
void create_item_creator();
void create_ep3_server();
+15 -2
View File
@@ -312,6 +312,13 @@ struct PlayerLobbyDataBB {
void clear();
} __attribute__((packed));
template <bool IsBigEndian>
struct ChallengeAwardState {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T rank_award_flags = 0;
U32T maximum_rank = 0; // Encrypted; see decrypt_challenge_time
} __attribute__((packed));
template <TextEncoding UnencryptedEncoding, TextEncoding EncryptedEncoding>
struct PlayerRecordsDCPC_Challenge {
/* 00 */ le_uint16_t title_color = 0x7FFF;
@@ -354,7 +361,10 @@ struct PlayerRecordsV3_Challenge {
/* 7C:98 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
/* 90:AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
/* B0:CC */ parray<uint8_t, 4> unknown_m5;
/* B4:D0 */ parray<U32T, 9> unknown_t6;
/* B4:D0 */ parray<U32T, 3> unknown_t6;
/* C0:DC */ ChallengeAwardState<IsBigEndian> ep1_online_award_state;
/* C8:E4 */ ChallengeAwardState<IsBigEndian> ep2_online_award_state;
/* D0:EC */ ChallengeAwardState<IsBigEndian> ep1_offline_award_state;
/* D8:F4 */
} __attribute__((packed));
/* 0000:001C */ Stats stats;
@@ -383,7 +393,10 @@ struct PlayerRecordsBB_Challenge {
/* 007C */ pstring<TextEncoding::UTF16, 0x14> grave_team;
/* 00A4 */ pstring<TextEncoding::UTF16, 0x20> grave_message;
/* 00E4 */ parray<uint8_t, 4> unknown_m5;
/* 00E8 */ parray<le_uint32_t, 9> unknown_t6;
/* 00E8 */ parray<le_uint32_t, 3> unknown_t6;
/* 00F4 */ ChallengeAwardState<false> ep1_online_award_state;
/* 00FC */ ChallengeAwardState<false> ep2_online_award_state;
/* 0104 */ ChallengeAwardState<false> ep1_offline_award_state;
/* 010C */ pstring<TextEncoding::UTF16, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 0124 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0140 */
+1 -1
View File
@@ -1650,7 +1650,7 @@ static HandlerResult C_81(shared_ptr<ProxyServer::LinkedSession> 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;
}
+3 -3
View File
@@ -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
+120 -31
View File
@@ -2940,12 +2940,12 @@ static void on_06(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
static void on_00E0_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E0_BB(shared_ptr<Client> 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<Client> c, uint16_t, uint32_t, string& data) {
static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_PlayerPreviewRequest_BB_E3>(data);
if (c->bb_connection_phase != 0x00) {
@@ -2973,7 +2973,7 @@ static void on_00E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
static void on_00E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
static void on_E8_BB(shared_ptr<Client> 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<Client> c, uint16_t, uint32_t, string& data) {
}
}
static void on_xxEB_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
static void on_EB_BB(shared_ptr<Client> 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<Client> c, uint16_t command, uint32_t flag, st
}
}
static void on_00EC_BB(shared_ptr<Client>, uint16_t, uint32_t, string& data) {
static void on_EC_BB(shared_ptr<Client>, uint16_t, uint32_t, string& data) {
check_size_t<C_LeaveCharacterSelect_BB_00EC>(data);
}
static void on_00E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<SC_PlayerPreview_CreateCharacter_BB_00E5>(data);
if (!c->license) {
@@ -3169,46 +3169,46 @@ static void on_00E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_approve_player_choice_bb(c);
}
static void on_xxED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
switch (command) {
case 0x01ED: {
const auto& cmd = check_size_t<C_UpdateAccountOptionFlags_BB_01ED>(data);
const auto& cmd = check_size_t<C_UpdateOptionFlags_BB_01ED>(data);
c->game_data.account()->option_flags = cmd.option_flags;
break;
}
case 0x02ED: {
const auto& cmd = check_size_t<C_UpdateAccountSymbolChats_BB_02ED>(data);
const auto& cmd = check_size_t<C_UpdateSymbolChats_BB_02ED>(data);
c->game_data.account()->symbol_chats = cmd.symbol_chats;
break;
}
case 0x03ED: {
const auto& cmd = check_size_t<C_UpdateAccountChatShortcuts_BB_03ED>(data);
const auto& cmd = check_size_t<C_UpdateChatShortcuts_BB_03ED>(data);
c->game_data.account()->shortcuts = cmd.chat_shortcuts;
break;
}
case 0x04ED: {
const auto& cmd = check_size_t<C_UpdateAccountKeyConfig_BB_04ED>(data);
const auto& cmd = check_size_t<C_UpdateKeyConfig_BB_04ED>(data);
c->game_data.account()->system_file.key_config = cmd.key_config;
break;
}
case 0x05ED: {
const auto& cmd = check_size_t<C_UpdateAccountPadConfig_BB_05ED>(data);
const auto& cmd = check_size_t<C_UpdatePadConfig_BB_05ED>(data);
c->game_data.account()->system_file.joystick_config = cmd.pad_config;
break;
}
case 0x06ED: {
const auto& cmd = check_size_t<C_UpdateAccountTechMenu_BB_06ED>(data);
const auto& cmd = check_size_t<C_UpdateTechMenu_BB_06ED>(data);
c->game_data.player()->tech_menu_config = cmd.tech_menu;
break;
}
case 0x07ED: {
const auto& cmd = check_size_t<C_UpdateAccountCustomizeMenu_BB_07ED>(data);
const auto& cmd = check_size_t<C_UpdateCustomizeMenu_BB_07ED>(data);
c->game_data.player()->disp.config = cmd.customize;
break;
}
case 0x08ED: {
check_size_t<C_UpdateAccountChallengeAndBattleConfig_BB_08ED>(data);
// TODO: Save this data appropriately
const auto& cmd = check_size_t<C_UpdateChallengeRecords_BB_08ED>(data);
c->game_data.player()->challenge_records = cmd.records;
break;
}
default:
@@ -3216,7 +3216,7 @@ static void on_xxED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string&
}
}
static void on_00E7_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E7_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<SC_SyncCharacterSaveFile_BB_00E7>(data);
// We only trust the player's quest data and challenge data.
@@ -3230,11 +3230,97 @@ static void on_00E7_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
p->quest_data2 = cmd.quest_data2;
}
static void on_00E2_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E2_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto& cmd = check_size_t<PSOBBSystemFile>(data);
c->game_data.account()->system_file = cmd;
}
static void on_DF_BB(shared_ptr<Client> 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<C_SetChallengeModeStageNumber_BB_01DF>(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<C_SetChallengeModeCharacterTemplate_BB_02DF>(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<C_SetChallengeModeDifficulty_BB_03DF>(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<C_SetChallengeModeEXPMultiplier_BB_04DF>(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<C_SetChallengeRankText_BB_05DF>(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<C_SetChallengeRankThreshold_BB_06DF>(data);
auto& threshold = cp->rank_thresholds[cmd.rank];
threshold.bitmask = cmd.rank_bitmask;
threshold.seconds = cmd.seconds;
string time_str = format_duration(static_cast<uint64_t>(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<C_CreateChallengeModeAwardItem_BB_07DF>(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<Client> c, uint16_t, uint32_t flag, string& data) {
check_size_v(data.size(), 0);
@@ -3520,6 +3606,9 @@ shared_ptr<Lobby> 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<Client> 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<Client> c, uint16_t, uint32_t, string& data) {
send_ep3_card_auction(l);
}
static void on_xxEA_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string&) {
static void on_EA_BB(shared_ptr<Client> 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},
+4 -1
View File
@@ -2318,7 +2318,10 @@ void send_set_exp_multiplier(std::shared_ptr<Lobby> 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<uint16_t>(l->exp_multiplier)}};
send_command_t(l, 0x60, 0x00, cmd);
}
+7
View File
@@ -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];
}
+2
View File
@@ -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);
+12 -12
View File
@@ -395,22 +395,22 @@ struct pstring {
try {
switch (Encoding) {
case TextEncoding::CHALLENGE8: {
std::string decrypted(reinterpret_cast<const char*>(this->data), this->used_bytes_8());
std::string decrypted(reinterpret_cast<const char*>(this->data), this->used_chars_8());
decrypt_challenge_rank_text_t<uint8_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<const char*>(&this->data[0]), this->used_bytes_8());
return std::string(reinterpret_cast<const char*>(&this->data[0]), this->used_chars_8());
case TextEncoding::CHALLENGE16: {
std::string decrypted(reinterpret_cast<const char*>(&this->data[0]), this->used_bytes_8());
std::string decrypted(reinterpret_cast<const char*>(&this->data[0]), this->used_chars_16() * 2);
decrypt_challenge_rank_text_t<le_uint16_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 {