implement player records command

This commit is contained in:
Martin Michelsen
2023-07-09 16:22:11 -07:00
parent 7dd00c75a9
commit b1531139c0
25 changed files with 478 additions and 111 deletions
+2 -3
View File
@@ -829,10 +829,9 @@ int main(int argc, char** argv) {
case Behavior::ENCRYPT_CHALLENGE_DATA:
case Behavior::DECRYPT_CHALLENGE_DATA: {
string data = read_input_data();
const uint8_t* u8data = reinterpret_cast<const uint8_t*>(data.data());
string result = (behavior == Behavior::DECRYPT_CHALLENGE_DATA)
? decrypt_challenge_rank_text(u8data, data.size())
: encrypt_challenge_rank_text(u8data, data.size());
? decrypt_challenge_rank_text(data)
: encrypt_challenge_rank_text(data);
write_output_data(result.data(), result.size());
break;
}
+28 -12
View File
@@ -887,13 +887,13 @@ void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis) {
}
template <typename StringT, bool IsEncrypt>
StringT crypt_challenge_rank_text(const void* src, size_t size) {
if (size == 0) {
StringT crypt_challenge_rank_text(const void* src, size_t count) {
if (count == 0) {
return StringT();
}
StringT ret;
StringReader r(src, size);
StringReader r(src, count * sizeof(typename StringT::value_type));
typename StringT::value_type prev = 0;
while (!r.eof()) {
typename StringT::value_type ch = r.get<typename StringT::value_type>();
@@ -903,7 +903,7 @@ StringT crypt_challenge_rank_text(const void* src, size_t size) {
if (ret.empty()) {
ret.push_back(ch ^ 0x7F);
} else {
ret.push_back(IsEncrypt ? ((ch - prev) ^ 0x7F) : ((ch ^ 0x7F) + ret.back()));
ret.push_back((IsEncrypt ? ((ch - prev) ^ 0x7F) : ((ch ^ 0x7F) + ret.back())) & 0xFF);
}
prev = ch;
}
@@ -911,18 +911,34 @@ StringT crypt_challenge_rank_text(const void* src, size_t size) {
return ret;
}
string encrypt_challenge_rank_text(const uint8_t* src, size_t size) {
return crypt_challenge_rank_text<string, true>(src, size);
string encrypt_challenge_rank_text(const char* src, size_t count) {
return crypt_challenge_rank_text<string, true>(src, count);
}
string decrypt_challenge_rank_text(const uint8_t* src, size_t size) {
return crypt_challenge_rank_text<string, false>(src, size);
string decrypt_challenge_rank_text(const char* src, size_t count) {
return crypt_challenge_rank_text<string, false>(src, count);
}
u16string encrypt_challenge_rank_text(const char16_t* src, size_t size) {
return crypt_challenge_rank_text<u16string, true>(src, size);
u16string encrypt_challenge_rank_text(const char16_t* src, size_t count) {
return crypt_challenge_rank_text<u16string, true>(src, count);
}
u16string decrypt_challenge_rank_text(const char16_t* src, size_t size) {
return crypt_challenge_rank_text<u16string, false>(src, size);
u16string decrypt_challenge_rank_text(const char16_t* src, size_t count) {
return crypt_challenge_rank_text<u16string, false>(src, count);
}
std::string decrypt_challenge_rank_text(const std::string& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
std::string encrypt_challenge_rank_text(const std::string& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
std::u16string decrypt_challenge_rank_text(const std::u16string& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
std::u16string encrypt_challenge_rank_text(const std::u16string& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
+28 -4
View File
@@ -241,7 +241,31 @@ private:
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
std::string decrypt_challenge_rank_text(const uint8_t* data, size_t size);
std::string encrypt_challenge_rank_text(const uint8_t* data, size_t size);
std::u16string decrypt_challenge_rank_text(const le_uint16_t* data, size_t size);
std::u16string encrypt_challenge_rank_text(const le_uint16_t* data, size_t size);
std::string decrypt_challenge_rank_text(const char* data, size_t count);
std::string decrypt_challenge_rank_text(const std::string& data);
std::string encrypt_challenge_rank_text(const char* data, size_t count);
std::string encrypt_challenge_rank_text(const std::string& data);
std::u16string decrypt_challenge_rank_text(const char16_t* data, size_t count);
std::u16string decrypt_challenge_rank_text(const std::u16string& data);
std::u16string encrypt_challenge_rank_text(const char16_t* data, size_t count);
std::u16string encrypt_challenge_rank_text(const std::u16string& data);
template <size_t Size>
std::string decrypt_challenge_rank_text(const ptext<char, Size>& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::u16string decrypt_challenge_rank_text(const ptext<char16_t, Size>& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::string encrypt_challenge_rank_text(const ptext<char, Size>& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::u16string encrypt_challenge_rank_text(const ptext<char16_t, Size>& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
+124 -13
View File
@@ -11,6 +11,7 @@
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
@@ -18,10 +19,8 @@
using namespace std;
// Originally there was going to be a language-based header, but then I decided
// against it. These strings were already in use for that parser, so I didn't
// bother changing them.
static const string PLAYER_FILE_SIGNATURE =
"newserv player file format; 10 sections present; sequential;";
// against it. This string was already in use for that parser, so I didn't
// bother changing it.
static const string ACCOUNT_FILE_SIGNATURE =
"newserv account file format; 7 sections present; sequential;";
@@ -226,7 +225,6 @@ shared_ptr<SavedPlayerDataBB> ClientGameData::player(bool should_load) {
if (!this->player_data.get() && should_load) {
if (this->bb_username.empty()) {
this->player_data.reset(new SavedPlayerDataBB());
this->player_data->signature = PLAYER_FILE_SIGNATURE;
} else {
this->load_player_data();
}
@@ -273,9 +271,7 @@ void ClientGameData::create_player(
shared_ptr<const LevelTable> level_table) {
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
load_object_file<SavedPlayerDataBB>(player_template_filename(preview.visual.char_class))));
if (data->signature != PLAYER_FILE_SIGNATURE) {
throw runtime_error("player data header is incorrect");
}
data->update_to_latest_version();
try {
data->disp.apply_preview(preview);
@@ -335,13 +331,15 @@ void ClientGameData::save_account_data() const {
void ClientGameData::load_player_data() {
this->last_play_time_update = now();
string filename = this->player_data_filename();
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
this->player_data.reset(new SavedPlayerDataBB(
player_files_cache.get_obj_or_load<SavedPlayerDataBB>(filename).obj));
if (data->signature != PLAYER_FILE_SIGNATURE) {
try {
this->player_data->update_to_latest_version();
} catch (const exception&) {
this->player_data.reset();
player_files_cache.delete_key(filename);
throw runtime_error("player data header is incorrect");
throw;
}
this->player_data = data;
player_data_log.info("Loaded player data file %s", filename.c_str());
}
@@ -412,7 +410,108 @@ void PlayerLobbyDataBB::clear() {
this->unknown_a2 = 0;
}
////////////////////////////////////////////////////////////////////////////////
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
times_ep2_online(0),
times_ep1_offline(0),
unknown_g3(rec.unknown_g3),
grave_deaths(rec.grave_deaths),
unknown_u4(0),
grave_coords_time(rec.grave_coords_time),
grave_team(rec.grave_team),
grave_message(rec.grave_message),
unknown_m5(0),
unknown_t6(0),
rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
times_ep2_online(0),
times_ep1_offline(0),
unknown_g3(rec.unknown_g3),
grave_deaths(rec.grave_deaths),
unknown_u4(0),
grave_coords_time(rec.grave_coords_time),
grave_team(rec.grave_team),
grave_message(rec.grave_message),
unknown_m5(0),
unknown_t6(0),
rank_title(rec.rank_title),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge<false>& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
times_ep2_online(rec.times_ep2_online),
times_ep1_offline(rec.times_ep1_offline),
unknown_g3(rec.unknown_g3),
grave_deaths(rec.grave_deaths),
unknown_u4(rec.unknown_u4),
grave_coords_time(rec.grave_coords_time),
grave_team(rec.grave_team),
grave_message(rec.grave_message),
unknown_m5(rec.unknown_m5),
unknown_t6(rec.unknown_t6),
rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))),
unknown_l7(rec.unknown_l7) {}
PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
PlayerRecordsDC_Challenge ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title)));
ret.times_ep1_online = this->times_ep1_online;
ret.unknown_g3 = 0;
ret.grave_deaths = this->grave_deaths;
ret.grave_coords_time = this->grave_coords_time;
ret.grave_team = this->grave_team;
ret.grave_message = this->grave_message;
ret.times_ep1_offline = this->times_ep1_offline;
ret.unknown_l4.clear(0);
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
PlayerRecordsPC_Challenge ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title = this->rank_title;
ret.times_ep1_online = this->times_ep1_online;
ret.unknown_g3 = 0;
ret.grave_deaths = this->grave_deaths;
ret.grave_coords_time = this->grave_coords_time;
ret.grave_team = this->grave_team;
ret.grave_message = this->grave_message;
ret.times_ep1_offline = this->times_ep1_offline;
ret.unknown_l4.clear(0);
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() const {
PlayerRecordsV3_Challenge<false> ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.times_ep1_online = this->times_ep1_online;
ret.times_ep2_online = this->times_ep2_online;
ret.times_ep1_offline = this->times_ep1_offline;
ret.unknown_g3 = this->unknown_g3;
ret.grave_deaths = this->grave_deaths;
ret.unknown_u4 = this->unknown_u4;
ret.grave_coords_time = this->grave_coords_time;
ret.grave_team = this->grave_team;
ret.grave_message = this->grave_message;
ret.unknown_m5 = this->unknown_m5;
ret.unknown_t6 = this->unknown_t6;
ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title)));
ret.unknown_l7 = this->unknown_l7;
return ret;
}
PlayerInventoryItem::PlayerInventoryItem() {
this->clear();
@@ -454,6 +553,18 @@ PlayerInventory::PlayerInventory()
tp_materials_used(0),
language(0) {}
void SavedPlayerDataBB::update_to_latest_version() {
if (this->signature == PLAYER_FILE_SIGNATURE_V0) {
this->signature = PLAYER_FILE_SIGNATURE_V1;
this->unused.clear();
this->battle_records.place_counts.clear(0);
this->battle_records.disconnect_count = 0;
this->battle_records.unknown_a1.clear(0);
} else if (this->signature != PLAYER_FILE_SIGNATURE_V1) {
throw runtime_error("player data has incorrect signature");
}
}
// TODO: Eliminate duplication between this function and the parallel function
// in PlayerBank
void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) {
+67 -52
View File
@@ -167,15 +167,15 @@ struct PlayerDispDataDCPCV3 {
PlayerDispDataBB to_bb() const;
} __attribute__((packed));
// BB player preview format
struct PlayerDispDataBBPreview {
le_uint32_t experience;
le_uint32_t level;
/* 00 */ le_uint32_t experience;
/* 04 */ le_uint32_t level;
// The name field in this structure is used for the player's Guild Card
// number, apparently (possibly because it's a char array and this is BB)
PlayerVisualConfig visual;
ptext<char16_t, 0x10> name;
uint32_t play_time;
/* 08 */ PlayerVisualConfig visual;
/* 58 */ ptext<char16_t, 0x10> name;
/* 78 */ uint32_t play_time;
/* 7C */
PlayerDispDataBBPreview() noexcept;
} __attribute__((packed));
@@ -329,24 +329,24 @@ struct PlayerLobbyDataBB {
template <bool IsWideChar>
struct PlayerRecordsDCPC_Challenge {
using CharT = typename std::conditional<IsWideChar, char16_t, char>::type;
using CharBinT = typename std::conditional<IsWideChar, le_uint16_t, uint8_t>::type;
/* 00 */ le_uint16_t title_color;
/* 02 */ parray<uint8_t, 2> unknown_a0;
/* 04 */ parray<CharBinT, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 10 */ parray<uint8_t, 0x24> unknown_a1; // TODO: This might be online or offline times
/* 34 */ le_uint16_t unknown_a2;
/* 36 */ le_uint16_t grave_deaths;
/* 00 */ le_uint16_t title_color = 0x7FFF;
/* 02 */ parray<uint8_t, 2> unknown_u0;
/* 04 */ ptext<CharT, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 10 */ parray<le_uint32_t, 9> times_ep1_online; // TODO: This might be offline times
/* 34 */ le_uint16_t unknown_g3 = 0;
/* 36 */ le_uint16_t grave_deaths = 0;
/* 38 */ parray<le_uint32_t, 5> grave_coords_time;
/* 4C */ ptext<CharT, 0x14> grave_team;
/* 60 */ ptext<CharT, 0x18> grave_message;
/* 78 */ parray<le_uint32_t, 9> times_ep1; // TODO: This might be offline times
/* 9C */ parray<uint8_t, 4> unknown_a3;
/* 78 */ parray<le_uint32_t, 9> times_ep1_offline; // TODO: This might be online times
/* 9C */ parray<uint8_t, 4> unknown_l4;
/* A0 */
} __attribute__((packed));
struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge<false> {
} __attribute__((packed));
struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge<true> {
} __attribute__((packed));
@@ -357,43 +357,53 @@ struct PlayerRecordsV3_Challenge {
// Offsets are (1) relative to start of C5 entry, and (2) relative to start
// of save file structure
/* 0000:001C */ U16T title_color; // XRGB1555
/* 0002:001E */ parray<uint8_t, 2> unknown_a2; // Probably actually unused
/* 0000:001C */ U16T title_color = 0x7FFF; // XRGB1555
/* 0002:001E */ parray<uint8_t, 2> unknown_u0;
/* 0004:0020 */ parray<U32T, 9> times_ep1_online;
/* 0028:0044 */ parray<U32T, 5> times_ep2_online;
/* 003C:0058 */ parray<U32T, 9> times_ep1_offline;
/* 0060:007C */ parray<uint8_t, 4> unknown_a3;
/* 0064:0080 */ U16T grave_deaths;
/* 0066:0082 */ parray<uint8_t, 2> unknown_a4; // Probably actually unused
/* 0060:007C */ parray<uint8_t, 4> unknown_g3;
/* 0064:0080 */ U16T grave_deaths = 0;
/* 0066:0082 */ parray<uint8_t, 2> unknown_u4;
/* 0068:0084 */ parray<U32T, 5> grave_coords_time;
/* 007C:0098 */ ptext<char, 0x14> grave_team;
/* 0090:00AC */ ptext<char, 0x20> grave_message;
/* 00B0:00CC */ parray<uint8_t, 4> unknown_a5;
/* 00B4:00D0 */ parray<U32T, 9> unknown_a6;
/* 00D8:00F4 */ parray<uint8_t, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 00E4:0100 */ parray<uint8_t, 0x1C> unknown_a7;
/* 00B0:00CC */ parray<uint8_t, 4> unknown_m5;
/* 00B4:00D0 */ parray<U32T, 9> unknown_t6;
/* 00D8:00F4 */ ptext<char, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 00E4:0100 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0100:011C */
} __attribute__((packed));
struct PlayerRecordsBB_Challenge {
// TODO: Figure out the rest of this structure. Probably it's very similar to
// the V3 structure, but it's a bit larger due to various text fields.
/* 0000 */ le_uint16_t title_color; // XRGB1555
/* 0002 */ parray<uint8_t, 2> unknown_a2; // Probably actually unused
/* 0004 */ parray<le_uint32_t, 9> times_ep1;
/* 0028 */ parray<le_uint32_t, 5> times_ep2;
/* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555
/* 0002 */ parray<uint8_t, 2> unknown_u0;
/* 0004 */ parray<le_uint32_t, 9> times_ep1_online;
/* 0028 */ parray<le_uint32_t, 5> times_ep2_online;
/* 003C */ parray<le_uint32_t, 9> times_ep1_offline;
/* 0060 */ parray<uint8_t, 4> unknown_a3;
/* 0064 */ le_uint16_t grave_deaths;
/* 0066 */ parray<uint8_t, 2> unknown_a4; // Probably actually unused
/* 0060 */ parray<uint8_t, 4> unknown_g3;
/* 0064 */ le_uint16_t grave_deaths = 0;
/* 0066 */ parray<uint8_t, 2> unknown_u4;
/* 0068 */ parray<le_uint32_t, 5> grave_coords_time;
/* 007C */ ptext<char16_t, 0x14> grave_team;
/* 00A4 */ ptext<char16_t, 0x20> grave_message;
/* 00E4 */ parray<uint8_t, 4> unknown_a5;
/* 00E8 */ parray<le_uint32_t, 9> unknown_a6;
/* 010C */ parray<le_uint16_t, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 0124 */ parray<uint8_t, 0x1C> unknown_a7;
/* 00E4 */ parray<uint8_t, 4> unknown_m5;
/* 00E8 */ parray<le_uint32_t, 9> unknown_t6;
/* 010C */ ptext<char16_t, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 0124 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0140 */
PlayerRecordsBB_Challenge() = default;
PlayerRecordsBB_Challenge(const PlayerRecordsBB_Challenge& other) = default;
PlayerRecordsBB_Challenge& operator=(const PlayerRecordsBB_Challenge& other) = default;
PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec);
PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec);
PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge<false>& rec);
operator PlayerRecordsDC_Challenge() const;
operator PlayerRecordsPC_Challenge() const;
operator PlayerRecordsV3_Challenge<false>() const;
} __attribute__((packed));
template <bool IsBigEndian>
@@ -417,22 +427,27 @@ struct ChoiceSearchConfig {
parray<Entry, 5> entries;
} __attribute__((packed));
constexpr uint64_t PLAYER_FILE_SIGNATURE_V0 = 0x6E65777365727620;
constexpr uint64_t PLAYER_FILE_SIGNATURE_V1 = 0xA904332D5CEF0296;
struct SavedPlayerDataBB { // .nsc file format
ptext<char, 0x40> signature;
PlayerDispDataBBPreview preview;
ptext<char16_t, 0x00AC> auto_reply;
PlayerBank bank;
PlayerRecordsBB_Challenge challenge_records;
PlayerDispDataBB disp;
ptext<char16_t, 0x0058> guild_card_description;
ptext<char16_t, 0x00AC> info_board;
PlayerInventory inventory;
parray<uint8_t, 0x0208> quest_data1;
parray<uint8_t, 0x0058> quest_data2;
parray<uint8_t, 0x0028> tech_menu_config;
// TODO: We don't save battle records in this structure, which is wrong.
// Make a new save file format that doesn't have the super-long signature,
// and also can save battle records.
/* 0000 */ be_uint64_t signature = PLAYER_FILE_SIGNATURE_V1;
/* 0008 */ parray<uint8_t, 0x20> unused;
/* 0028 */ PlayerRecords_Battle<false> battle_records;
/* 0040 */ PlayerDispDataBBPreview preview;
/* 00BC */ ptext<char16_t, 0x00AC> auto_reply;
/* 0214 */ PlayerBank bank;
/* 14DC */ PlayerRecordsBB_Challenge challenge_records;
/* 161C */ PlayerDispDataBB disp;
/* 17AC */ ptext<char16_t, 0x0058> guild_card_description;
/* 185C */ ptext<char16_t, 0x00AC> info_board;
/* 19B4 */ PlayerInventory inventory;
/* 1D00 */ parray<uint8_t, 0x0208> quest_data1;
/* 1F08 */ parray<uint8_t, 0x0058> quest_data2;
/* 1F60 */ parray<uint8_t, 0x0028> tech_menu_config;
/* 1F88 */
void update_to_latest_version();
void add_item(const PlayerInventoryItem& item);
PlayerInventoryItem remove_item(
+2
View File
@@ -1027,6 +1027,7 @@ static HandlerResult C_GXB_61(shared_ptr<ServerState>,
}
if (session.options.red_name && pd.disp.visual.name_color != 0xFFFF0000) {
pd.disp.visual.name_color = 0xFFFF0000;
pd.records.challenge.title_color = 0x7C00;
modified = true;
} else if (session.options.blank_name && pd.disp.visual.name_color != 0x00000000) {
pd.disp.visual.name_color = 0x00000000;
@@ -1059,6 +1060,7 @@ static HandlerResult C_GXB_61(shared_ptr<ServerState>,
}
if (session.options.red_name && pd->disp.visual.name_color != 0xFFFF0000) {
pd->disp.visual.name_color = 0xFFFF0000;
pd->records.challenge.title_color = 0x7C00;
modified = true;
} else if (session.options.blank_name && pd->disp.visual.name_color != 0x00000000) {
pd->disp.visual.name_color = 0x00000000;
+9 -6
View File
@@ -74,7 +74,7 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(
}
if (s->proxy_enable_login_options) {
add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name,
u"Red name", u"Set your name\ncolor to red");
u"Red name", u"Set the colors\nof your name and\nChallenge Mode\nrank to red");
add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name,
u"Blank name", u"Suppress your\ncharacter name\nduring login");
add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login,
@@ -2446,7 +2446,8 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto player = c->game_data.player();
player->inventory = pd.inventory;
player->disp = pd.disp.to_bb();
// TODO: Parse pd.records and send C5 at an appropriate time
player->battle_records = pd.records.battle;
player->challenge_records = pd.records.challenge;
// TODO: Parse choice search config
}
break;
@@ -2457,7 +2458,8 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto account = c->game_data.account();
player->inventory = pd.inventory;
player->disp = pd.disp.to_bb();
// TODO: Parse pd.records and send C5 at an appropriate time
player->battle_records = pd.records.battle;
player->challenge_records = pd.records.challenge;
// TODO: Parse choice search config
account->blocked_senders = pd.blocked_senders;
if (pd.auto_reply_enabled) {
@@ -2490,7 +2492,8 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
}
player->disp = cmd->disp.to_bb();
// TODO: Parse cmd->records and send C5 at an appropriate time
player->battle_records = cmd->records.battle;
player->challenge_records = cmd->records.challenge;
// TODO: Parse choice search config
player->info_board = cmd->info_board;
account->blocked_senders = cmd->blocked_senders;
@@ -2499,7 +2502,6 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else {
player->auto_reply.clear(0);
}
break;
}
case GameVersion::BB: {
@@ -2508,7 +2510,8 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto player = c->game_data.player();
// Note: we don't copy the inventory and disp here because we already have
// them (we sent the player data to the client in the first place)
// TODO: Parse pd.records and send C5 at an appropriate time
player->battle_records = cmd.records.battle;
player->challenge_records = cmd.records.challenge;
// TODO: Parse choice search config
player->info_board = cmd.info_board;
account->blocked_senders = cmd.blocked_senders;
+49 -21
View File
@@ -632,10 +632,8 @@ void send_complete_player_bb(shared_ptr<Client> c) {
cmd.shortcuts = account->shortcuts;
cmd.auto_reply = player->auto_reply;
cmd.info_board = player->info_board;
cmd.battle_records.place_counts.clear(0);
cmd.battle_records.disconnect_count = 0;
cmd.battle_records.unknown_a1.clear(0);
cmd.unknown_a4 = 0;
cmd.battle_records = player->battle_records;
cmd.unknown_a4.clear(0);
cmd.challenge_records = player->challenge_records;
cmd.tech_menu_config = player->tech_menu_config;
cmd.unknown_a6.clear(0);
@@ -1383,6 +1381,30 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
////////////////////////////////////////////////////////////////////////////////
// lobby joining
template <typename EntryT>
void send_player_records(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
vector<EntryT> entries;
auto add_client = [&](shared_ptr<Client> lc) -> void {
auto lp = lc->game_data.player();
auto& e = entries.emplace_back();
e.client_id = lc->lobby_client_id;
e.challenge = lp->challenge_records;
e.battle = lp->battle_records;
};
if (joining_client) {
add_client(joining_client);
} else {
entries.reserve(12);
for (auto lc : l->clients) {
if (lc) {
add_client(lc);
}
}
}
send_command_vt(c->channel, 0xC5, entries.size(), entries);
}
static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l) {
if (!(c->flags & Client::Flag::IS_EPISODE_3)) {
throw runtime_error("lobby is not Episode 3");
@@ -1598,7 +1620,7 @@ void send_join_game_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l) {
send_command_t(c, 0x64, player_count, cmd);
}
template <typename LobbyDataT, typename DispDataT>
template <typename LobbyDataT, typename DispDataT, typename RecordsT>
void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
shared_ptr<Client> joining_client = nullptr) {
uint8_t command;
@@ -1612,6 +1634,10 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
command = joining_client ? 0x68 : 0x67;
}
if ((c->version() != GameVersion::DC) || !(c->flags & Client::Flag::IS_DC_V1)) {
send_player_records<RecordsT>(c, l, joining_client);
}
uint8_t lobby_type = (l->type > 14) ? (l->block - 1) : l->type;
// Allow non-canonical lobby types on GC. They may work on other versions too,
// but I haven't verified which values don't crash on each version.
@@ -1745,23 +1771,24 @@ void send_join_lobby(shared_ptr<Client> c, shared_ptr<Lobby> l) {
}
} else {
switch (c->version()) {
case GameVersion::PC:
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3>(c, l);
break;
case GameVersion::DC:
if (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) {
send_join_lobby_dc_nte(c, l);
break;
} else {
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_DC>(c, l);
}
[[fallthrough]];
break;
case GameVersion::PC:
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3, PlayerRecordsEntry_PC>(c, l);
break;
case GameVersion::GC:
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>(c, l);
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3>(c, l);
break;
case GameVersion::XB:
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3>(c, l);
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3>(c, l);
break;
case GameVersion::BB:
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l);
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB, PlayerRecordsEntry_BB>(c, l);
break;
default:
throw logic_error("unimplemented versioned command");
@@ -1779,23 +1806,24 @@ void send_join_lobby(shared_ptr<Client> c, shared_ptr<Lobby> l) {
void send_player_join_notification(shared_ptr<Client> c,
shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
switch (c->version()) {
case GameVersion::PC:
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3>(c, l, joining_client);
break;
case GameVersion::DC:
if (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) {
send_join_lobby_dc_nte(c, l, joining_client);
break;
} else {
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_DC>(c, l, joining_client);
}
[[fallthrough]];
break;
case GameVersion::PC:
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3, PlayerRecordsEntry_PC>(c, l, joining_client);
break;
case GameVersion::GC:
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>(c, l, joining_client);
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3>(c, l, joining_client);
break;
case GameVersion::XB:
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3>(c, l, joining_client);
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3>(c, l, joining_client);
break;
case GameVersion::BB:
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l, joining_client);
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB, PlayerRecordsEntry_BB>(c, l, joining_client);
break;
default:
throw logic_error("unimplemented versioned command");
+1
View File
@@ -264,6 +264,7 @@ void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
std::shared_ptr<const QuestCategoryIndex> category_index, uint8_t flags);
void send_lobby_list(std::shared_ptr<Client> c, std::shared_ptr<ServerState> s);
void send_player_records(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client = nullptr);
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_player_join_notification(std::shared_ptr<Client> c,
std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);