implement player records command
This commit is contained in:
+2
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user