convert all CRLF line endings to LF only

This commit is contained in:
Martin Michelsen
2024-06-16 21:03:00 -07:00
parent 24656d587b
commit aa9d2beffe
86 changed files with 60914 additions and 62454 deletions
+1003 -1003
View File
File diff suppressed because it is too large Load Diff
+263 -263
View File
@@ -1,263 +1,263 @@
#pragma once
#include <memory>
#include <mutex>
#include <phosg/JSON.hh>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "Text.hh"
class LicenseIndex;
struct DCNTELicense {
std::string serial_number;
std::string access_key;
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
JSON json() const;
};
struct V1V2License {
uint32_t serial_number = 0;
std::string access_key;
static std::shared_ptr<V1V2License> from_json(const JSON& json);
JSON json() const;
};
struct GCLicense {
uint32_t serial_number = 0;
std::string access_key;
std::string password;
static std::shared_ptr<GCLicense> from_json(const JSON& json);
JSON json() const;
};
struct XBLicense {
std::string gamertag;
uint64_t user_id = 0;
uint64_t account_id = 0;
static std::shared_ptr<XBLicense> from_json(const JSON& json);
JSON json() const;
};
struct BBLicense {
std::string username;
std::string password;
static std::shared_ptr<BBLicense> from_json(const JSON& json);
JSON json() const;
};
struct Account {
enum class Flag : uint32_t {
// clang-format off
KICK_USER = 0x00000001,
BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004,
CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040,
DEBUG = 0x01000000,
CHEAT_ANYWHERE = 0x02000000,
DISABLE_QUEST_REQUIREMENTS = 0x04000000,
ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000,
MODERATOR = 0x00000007,
ADMINISTRATOR = 0x000000FF,
ROOT = 0x7FFFFFFF,
IS_SHARED_ACCOUNT = 0x80000000,
// NOTE: When adding or changing license flags, don't forget to change the
// documentation in the shell's help text.
UNUSED_BITS = 0x70FFFF00,
// clang-format on
};
// account_id is also the account's guild card number
uint32_t account_id = 0;
uint32_t flags = 0;
uint64_t ban_end_time = 0; // 0 = not banned
std::string last_player_name;
std::string auto_reply_message;
uint32_t ep3_current_meseta = 0;
uint32_t ep3_total_meseta_earned = 0;
uint32_t bb_team_id = 0;
bool is_temporary = false; // If true, isn't saved to disk
std::unordered_set<std::string> auto_patches_enabled;
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<GCLicense>> gc_licenses;
std::unordered_map<std::string, std::shared_ptr<XBLicense>> xb_licenses;
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
Account() = default;
explicit Account(const JSON& json);
virtual ~Account() = default;
JSON json() const;
virtual void save() const;
virtual void delete_file() const;
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->flags & static_cast<uint32_t>(flag));
}
inline void set_flag(Flag flag) {
this->flags |= static_cast<uint32_t>(flag);
}
inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint32_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->flags ^= static_cast<uint32_t>(flag);
}
inline void replace_all_flags(Flag mask) {
this->flags = static_cast<uint32_t>(mask);
}
void print(FILE* stream) const;
};
struct Login {
bool account_was_created = false;
// This field will never be null
std::shared_ptr<Account> account;
// Exactly one of the following will be non-null, representing the license
// that the client logged in with
std::shared_ptr<DCNTELicense> dc_nte_license;
std::shared_ptr<V1V2License> dc_license;
std::shared_ptr<V1V2License> pc_license;
std::shared_ptr<GCLicense> gc_license;
std::shared_ptr<XBLicense> xb_license;
std::shared_ptr<BBLicense> bb_license;
};
class AccountIndex {
public:
class no_username : public std::invalid_argument {
public:
no_username() : invalid_argument("serial number is zero or username is missing") {}
};
class incorrect_password : public std::invalid_argument {
public:
incorrect_password() : invalid_argument("incorrect password") {}
};
class incorrect_access_key : public std::invalid_argument {
public:
incorrect_access_key() : invalid_argument("incorrect access key") {}
};
class missing_account : public std::invalid_argument {
public:
missing_account() : invalid_argument("missing account") {}
};
explicit AccountIndex(bool force_all_temporary);
virtual ~AccountIndex() = default;
std::shared_ptr<Account> create_account(bool is_temporary) const;
size_t count() const;
std::vector<std::shared_ptr<Account>> all() const;
void add(std::shared_ptr<Account> a);
void remove(uint32_t serial_number);
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license);
void add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
void add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
void add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license);
void add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license);
void add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license);
void remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number);
void remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username);
std::shared_ptr<Account> from_account_id(uint32_t account_id) const;
std::shared_ptr<Login> from_dc_nte_credentials(
const std::string& serial_number,
const std::string& access_key,
bool allow_create);
std::shared_ptr<Login> from_dc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_pc_nte_credentials(
uint32_t guild_card_number,
bool allow_create);
std::shared_ptr<Login> from_pc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_gc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string* password,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_xb_credentials(
const std::string& gamertag,
uint64_t user_id,
uint64_t account_id,
bool allow_create);
std::shared_ptr<Login> from_bb_credentials(
const std::string& username,
const std::string* password,
bool allow_create);
std::shared_ptr<Account> create_temporary_account_for_shared_account(
std::shared_ptr<const Account> src_a, const std::string& variation_data) const;
protected:
bool force_all_temporary;
// This class must be thread-safe because it's used by both the patch server
// and game server threads
mutable std::shared_mutex lock;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_account_id;
std::unordered_map<std::string, std::shared_ptr<Account>> by_dc_nte_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_dc_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_pc_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_serial_number;
std::unordered_map<std::string, std::shared_ptr<Account>> by_xb_gamertag;
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
void add_locked(std::shared_ptr<Account> a);
std::shared_ptr<Login> from_dc_nte_credentials_locked(
const std::string& serial_number,
const std::string& access_key);
std::shared_ptr<Login> from_dc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name);
std::shared_ptr<Login> from_pc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name);
std::shared_ptr<Login> from_gc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string* password,
const std::string& character_name);
std::shared_ptr<Login> from_xb_credentials_locked(
const std::string& gamertag,
uint64_t user_id,
uint64_t account_id);
std::shared_ptr<Login> from_bb_credentials_locked(
const std::string& username,
const std::string* password);
};
#pragma once
#include <memory>
#include <mutex>
#include <phosg/JSON.hh>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "Text.hh"
class LicenseIndex;
struct DCNTELicense {
std::string serial_number;
std::string access_key;
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
JSON json() const;
};
struct V1V2License {
uint32_t serial_number = 0;
std::string access_key;
static std::shared_ptr<V1V2License> from_json(const JSON& json);
JSON json() const;
};
struct GCLicense {
uint32_t serial_number = 0;
std::string access_key;
std::string password;
static std::shared_ptr<GCLicense> from_json(const JSON& json);
JSON json() const;
};
struct XBLicense {
std::string gamertag;
uint64_t user_id = 0;
uint64_t account_id = 0;
static std::shared_ptr<XBLicense> from_json(const JSON& json);
JSON json() const;
};
struct BBLicense {
std::string username;
std::string password;
static std::shared_ptr<BBLicense> from_json(const JSON& json);
JSON json() const;
};
struct Account {
enum class Flag : uint32_t {
// clang-format off
KICK_USER = 0x00000001,
BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004,
CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040,
DEBUG = 0x01000000,
CHEAT_ANYWHERE = 0x02000000,
DISABLE_QUEST_REQUIREMENTS = 0x04000000,
ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000,
MODERATOR = 0x00000007,
ADMINISTRATOR = 0x000000FF,
ROOT = 0x7FFFFFFF,
IS_SHARED_ACCOUNT = 0x80000000,
// NOTE: When adding or changing license flags, don't forget to change the
// documentation in the shell's help text.
UNUSED_BITS = 0x70FFFF00,
// clang-format on
};
// account_id is also the account's guild card number
uint32_t account_id = 0;
uint32_t flags = 0;
uint64_t ban_end_time = 0; // 0 = not banned
std::string last_player_name;
std::string auto_reply_message;
uint32_t ep3_current_meseta = 0;
uint32_t ep3_total_meseta_earned = 0;
uint32_t bb_team_id = 0;
bool is_temporary = false; // If true, isn't saved to disk
std::unordered_set<std::string> auto_patches_enabled;
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<GCLicense>> gc_licenses;
std::unordered_map<std::string, std::shared_ptr<XBLicense>> xb_licenses;
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
Account() = default;
explicit Account(const JSON& json);
virtual ~Account() = default;
JSON json() const;
virtual void save() const;
virtual void delete_file() const;
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->flags & static_cast<uint32_t>(flag));
}
inline void set_flag(Flag flag) {
this->flags |= static_cast<uint32_t>(flag);
}
inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint32_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->flags ^= static_cast<uint32_t>(flag);
}
inline void replace_all_flags(Flag mask) {
this->flags = static_cast<uint32_t>(mask);
}
void print(FILE* stream) const;
};
struct Login {
bool account_was_created = false;
// This field will never be null
std::shared_ptr<Account> account;
// Exactly one of the following will be non-null, representing the license
// that the client logged in with
std::shared_ptr<DCNTELicense> dc_nte_license;
std::shared_ptr<V1V2License> dc_license;
std::shared_ptr<V1V2License> pc_license;
std::shared_ptr<GCLicense> gc_license;
std::shared_ptr<XBLicense> xb_license;
std::shared_ptr<BBLicense> bb_license;
};
class AccountIndex {
public:
class no_username : public std::invalid_argument {
public:
no_username() : invalid_argument("serial number is zero or username is missing") {}
};
class incorrect_password : public std::invalid_argument {
public:
incorrect_password() : invalid_argument("incorrect password") {}
};
class incorrect_access_key : public std::invalid_argument {
public:
incorrect_access_key() : invalid_argument("incorrect access key") {}
};
class missing_account : public std::invalid_argument {
public:
missing_account() : invalid_argument("missing account") {}
};
explicit AccountIndex(bool force_all_temporary);
virtual ~AccountIndex() = default;
std::shared_ptr<Account> create_account(bool is_temporary) const;
size_t count() const;
std::vector<std::shared_ptr<Account>> all() const;
void add(std::shared_ptr<Account> a);
void remove(uint32_t serial_number);
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license);
void add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
void add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
void add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license);
void add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license);
void add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license);
void remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number);
void remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number);
void remove_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username);
std::shared_ptr<Account> from_account_id(uint32_t account_id) const;
std::shared_ptr<Login> from_dc_nte_credentials(
const std::string& serial_number,
const std::string& access_key,
bool allow_create);
std::shared_ptr<Login> from_dc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_pc_nte_credentials(
uint32_t guild_card_number,
bool allow_create);
std::shared_ptr<Login> from_pc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_gc_credentials(
uint32_t serial_number,
const std::string& access_key,
const std::string* password,
const std::string& character_name,
bool allow_create);
std::shared_ptr<Login> from_xb_credentials(
const std::string& gamertag,
uint64_t user_id,
uint64_t account_id,
bool allow_create);
std::shared_ptr<Login> from_bb_credentials(
const std::string& username,
const std::string* password,
bool allow_create);
std::shared_ptr<Account> create_temporary_account_for_shared_account(
std::shared_ptr<const Account> src_a, const std::string& variation_data) const;
protected:
bool force_all_temporary;
// This class must be thread-safe because it's used by both the patch server
// and game server threads
mutable std::shared_mutex lock;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_account_id;
std::unordered_map<std::string, std::shared_ptr<Account>> by_dc_nte_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_dc_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_pc_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_serial_number;
std::unordered_map<std::string, std::shared_ptr<Account>> by_xb_gamertag;
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
void add_locked(std::shared_ptr<Account> a);
std::shared_ptr<Login> from_dc_nte_credentials_locked(
const std::string& serial_number,
const std::string& access_key);
std::shared_ptr<Login> from_dc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name);
std::shared_ptr<Login> from_pc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name);
std::shared_ptr<Login> from_gc_credentials_locked(
uint32_t serial_number,
const std::string& access_key,
const std::string* password,
const std::string& character_name);
std::shared_ptr<Login> from_xb_credentials_locked(
const std::string& gamertag,
uint64_t user_id,
uint64_t account_id);
std::shared_ptr<Login> from_bb_credentials_locked(
const std::string& username,
const std::string* password);
};
+83 -83
View File
@@ -1,83 +1,83 @@
#include "BattleParamsIndex.hh"
#include <phosg/Filesystem.hh>
#include <phosg/Strings.hh>
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
using namespace std;
void BattleParamsIndex::Table::print(FILE* stream) const {
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
fprintf(stream,
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
e.char_stats.atp.load(),
e.char_stats.mst.load(),
e.char_stats.evp.load(),
e.char_stats.hp.load(),
e.char_stats.dfp.load(),
e.char_stats.ata.load(),
e.char_stats.lck.load(),
e.esp.load(),
e.experience.load(),
e.meseta.load());
};
for (size_t diff = 0; diff < 4; diff++) {
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
abbreviation_for_difficulty(diff));
for (size_t z = 0; z < 0x60; z++) {
fprintf(stream, " %02zX ", z);
print_entry(stream, this->stats[diff][z]);
fputc('\n', stream);
}
}
}
BattleParamsIndex::BattleParamsIndex(
shared_ptr<const string> data_on_ep1,
shared_ptr<const string> data_on_ep2,
shared_ptr<const string> data_on_ep4,
shared_ptr<const string> data_off_ep1,
shared_ptr<const string> data_off_ep2,
shared_ptr<const string> data_off_ep4) {
this->files[0][0].data = data_on_ep1;
this->files[0][1].data = data_on_ep2;
this->files[0][2].data = data_on_ep4;
this->files[1][0].data = data_off_ep1;
this->files[1][1].data = data_off_ep2;
this->files[1][2].data = data_off_ep4;
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
for (uint8_t episode = 0; episode < 3; episode++) {
auto& file = this->files[is_solo][episode];
if (file.data->size() < sizeof(Table)) {
throw runtime_error(string_printf(
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
sizeof(Table), file.data->size(), is_solo, episode));
}
file.table = reinterpret_cast<const Table*>(file.data->data());
}
}
}
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
uint8_t ep_index;
switch (episode) {
case Episode::EP1:
ep_index = 0;
break;
case Episode::EP2:
ep_index = 1;
break;
case Episode::EP4:
ep_index = 2;
break;
default:
throw invalid_argument("invalid episode");
}
return *this->files[!!solo][ep_index].table;
}
#include "BattleParamsIndex.hh"
#include <phosg/Filesystem.hh>
#include <phosg/Strings.hh>
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
using namespace std;
void BattleParamsIndex::Table::print(FILE* stream) const {
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
fprintf(stream,
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
e.char_stats.atp.load(),
e.char_stats.mst.load(),
e.char_stats.evp.load(),
e.char_stats.hp.load(),
e.char_stats.dfp.load(),
e.char_stats.ata.load(),
e.char_stats.lck.load(),
e.esp.load(),
e.experience.load(),
e.meseta.load());
};
for (size_t diff = 0; diff < 4; diff++) {
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
abbreviation_for_difficulty(diff));
for (size_t z = 0; z < 0x60; z++) {
fprintf(stream, " %02zX ", z);
print_entry(stream, this->stats[diff][z]);
fputc('\n', stream);
}
}
}
BattleParamsIndex::BattleParamsIndex(
shared_ptr<const string> data_on_ep1,
shared_ptr<const string> data_on_ep2,
shared_ptr<const string> data_on_ep4,
shared_ptr<const string> data_off_ep1,
shared_ptr<const string> data_off_ep2,
shared_ptr<const string> data_off_ep4) {
this->files[0][0].data = data_on_ep1;
this->files[0][1].data = data_on_ep2;
this->files[0][2].data = data_on_ep4;
this->files[1][0].data = data_off_ep1;
this->files[1][1].data = data_off_ep2;
this->files[1][2].data = data_off_ep4;
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
for (uint8_t episode = 0; episode < 3; episode++) {
auto& file = this->files[is_solo][episode];
if (file.data->size() < sizeof(Table)) {
throw runtime_error(string_printf(
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
sizeof(Table), file.data->size(), is_solo, episode));
}
file.table = reinterpret_cast<const Table*>(file.data->data());
}
}
}
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
uint8_t ep_index;
switch (episode) {
case Episode::EP1:
ep_index = 0;
break;
case Episode::EP2:
ep_index = 1;
break;
case Episode::EP4:
ep_index = 2;
break;
default:
throw invalid_argument("invalid episode");
}
return *this->files[!!solo][ep_index].table;
}
+100 -100
View File
@@ -1,100 +1,100 @@
#pragma once
#include <inttypes.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <random>
#include <string>
#include <vector>
#include "EnemyType.hh"
#include "LevelTable.hh"
#include "StaticGameData.hh"
#include "Text.hh"
class BattleParamsIndex {
public:
// These files are little-endian, even on PSO GC.
struct AttackData {
/* 00 */ le_int16_t unknown_a1;
/* 02 */ le_int16_t atp;
/* 04 */ le_int16_t ata_bonus;
/* 06 */ le_uint16_t unknown_a4;
/* 08 */ le_float distance_x;
/* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused)
/* 10 */ le_float distance_y;
/* 14 */ le_uint16_t unknown_a8;
/* 16 */ le_uint16_t unknown_a9;
/* 18 */ le_uint16_t unknown_a10;
/* 1A */ le_uint16_t unknown_a11;
/* 1C */ le_uint32_t unknown_a12;
/* 20 */ le_uint32_t unknown_a13;
/* 24 */ le_uint32_t unknown_a14;
/* 28 */ le_uint32_t unknown_a15;
/* 2C */ le_uint32_t unknown_a16;
/* 30 */
} __packed_ws__(AttackData, 0x30);
struct ResistData {
/* 00 */ le_int16_t evp_bonus;
/* 02 */ le_uint16_t efr;
/* 04 */ le_uint16_t eic;
/* 06 */ le_uint16_t eth;
/* 08 */ le_uint16_t elt;
/* 0A */ le_uint16_t edk;
/* 0C */ le_uint32_t unknown_a6;
/* 10 */ le_uint32_t unknown_a7;
/* 14 */ le_uint32_t unknown_a8;
/* 18 */ le_uint32_t unknown_a9;
/* 1C */ le_int32_t dfp_bonus;
/* 20 */
} __packed_ws__(ResistData, 0x20);
struct MovementData {
/* 00 */ le_float idle_move_speed;
/* 04 */ le_float idle_animation_speed;
/* 08 */ le_float move_speed;
/* 0C */ le_float animation_speed;
/* 10 */ le_float unknown_a1;
/* 14 */ le_float unknown_a2;
/* 18 */ le_uint32_t unknown_a3;
/* 1C */ le_uint32_t unknown_a4;
/* 20 */ le_uint32_t unknown_a5;
/* 24 */ le_uint32_t unknown_a6;
/* 28 */ le_uint32_t unknown_a7;
/* 2C */ le_uint32_t unknown_a8;
/* 30 */
} __packed_ws__(MovementData, 0x30);
struct Table {
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
/* F600 */
void print(FILE* stream) const;
} __packed_ws__(Table, 0xF600);
BattleParamsIndex(
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
std::shared_ptr<const std::string> data_on_ep2, // BattleParamEntry_lab_on.dat
std::shared_ptr<const std::string> data_on_ep4, // BattleParamEntry_ep4_on.dat
std::shared_ptr<const std::string> data_off_ep1, // BattleParamEntry.dat
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
const Table& get_table(bool solo, Episode episode) const;
private:
struct File {
std::shared_ptr<const std::string> data;
const Table* table;
};
// Indexed as [online/offline][episode]
std::array<std::array<File, 3>, 2> files;
};
#pragma once
#include <inttypes.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <random>
#include <string>
#include <vector>
#include "EnemyType.hh"
#include "LevelTable.hh"
#include "StaticGameData.hh"
#include "Text.hh"
class BattleParamsIndex {
public:
// These files are little-endian, even on PSO GC.
struct AttackData {
/* 00 */ le_int16_t unknown_a1;
/* 02 */ le_int16_t atp;
/* 04 */ le_int16_t ata_bonus;
/* 06 */ le_uint16_t unknown_a4;
/* 08 */ le_float distance_x;
/* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused)
/* 10 */ le_float distance_y;
/* 14 */ le_uint16_t unknown_a8;
/* 16 */ le_uint16_t unknown_a9;
/* 18 */ le_uint16_t unknown_a10;
/* 1A */ le_uint16_t unknown_a11;
/* 1C */ le_uint32_t unknown_a12;
/* 20 */ le_uint32_t unknown_a13;
/* 24 */ le_uint32_t unknown_a14;
/* 28 */ le_uint32_t unknown_a15;
/* 2C */ le_uint32_t unknown_a16;
/* 30 */
} __packed_ws__(AttackData, 0x30);
struct ResistData {
/* 00 */ le_int16_t evp_bonus;
/* 02 */ le_uint16_t efr;
/* 04 */ le_uint16_t eic;
/* 06 */ le_uint16_t eth;
/* 08 */ le_uint16_t elt;
/* 0A */ le_uint16_t edk;
/* 0C */ le_uint32_t unknown_a6;
/* 10 */ le_uint32_t unknown_a7;
/* 14 */ le_uint32_t unknown_a8;
/* 18 */ le_uint32_t unknown_a9;
/* 1C */ le_int32_t dfp_bonus;
/* 20 */
} __packed_ws__(ResistData, 0x20);
struct MovementData {
/* 00 */ le_float idle_move_speed;
/* 04 */ le_float idle_animation_speed;
/* 08 */ le_float move_speed;
/* 0C */ le_float animation_speed;
/* 10 */ le_float unknown_a1;
/* 14 */ le_float unknown_a2;
/* 18 */ le_uint32_t unknown_a3;
/* 1C */ le_uint32_t unknown_a4;
/* 20 */ le_uint32_t unknown_a5;
/* 24 */ le_uint32_t unknown_a6;
/* 28 */ le_uint32_t unknown_a7;
/* 2C */ le_uint32_t unknown_a8;
/* 30 */
} __packed_ws__(MovementData, 0x30);
struct Table {
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
/* F600 */
void print(FILE* stream) const;
} __packed_ws__(Table, 0xF600);
BattleParamsIndex(
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
std::shared_ptr<const std::string> data_on_ep2, // BattleParamEntry_lab_on.dat
std::shared_ptr<const std::string> data_on_ep4, // BattleParamEntry_ep4_on.dat
std::shared_ptr<const std::string> data_off_ep1, // BattleParamEntry.dat
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
const Table& get_table(bool solo, Episode episode) const;
private:
struct File {
std::shared_ptr<const std::string> data;
const Table* table;
};
// Indexed as [online/offline][episode]
std::array<std::array<File, 3>, 2> files;
};
+188 -188
View File
@@ -1,188 +1,188 @@
#include "CatSession.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh>
#include <phosg/Network.hh>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ProxyCommands.hh"
#include "ReceiveCommands.hh"
#include "ReceiveSubcommands.hh"
#include "SendCommands.hh"
using namespace std;
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
CatSession::CatSession(
shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
Version version,
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
base(base),
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
bb_key_file(bb_key_file) {
if (remote.ss_family != AF_INET) {
throw runtime_error("remote is not AF_INET");
}
string netloc_str = render_sockaddr_storage(remote);
this->log.info("Connecting to %s", netloc_str.c_str());
struct bufferevent* bev = bufferevent_socket_new(
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
if (!bev) {
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
}
this->channel.set_bufferevent(bev, 0);
if (bufferevent_socket_connect(this->channel.bev.get(),
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
}
event_add(this->read_event.get(), nullptr);
this->poll.add(0, POLLIN);
}
void CatSession::execute_command(const std::string& command) {
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
}
void CatSession::dispatch_on_channel_input(
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_input(command, flag, data);
}
void CatSession::on_channel_input(
uint16_t command, uint32_t flag, std::string& data) {
if (!uses_v4_encryption(this->channel.version)) {
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
if (uses_v3_encryption(this->channel.version)) {
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load());
} else { // PC, DC, or patch server
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load());
}
}
} else { // BB
if (command == 0x03 || command == 0x9B) {
if (!this->bb_key_file) {
throw runtime_error("BB encryption requires a key file");
}
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
this->channel.crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
this->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
this->log.info("Enabled BB encryption");
}
}
// TODO: Use the iovec form of print_data here instead of
// prepend_command_header (which copies the string)
string full_cmd = prepend_command_header(
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
}
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_error(events);
}
void CatSession::on_channel_error(short events) {
if (events & BEV_EVENT_CONNECTED) {
this->log.info("Channel connected");
}
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log.warning("Error %d (%s) in unlinked client stream", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
this->log.info("Session endpoint has disconnected");
this->channel.disconnect();
event_base_loopexit(this->base.get(), nullptr);
}
}
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
reinterpret_cast<CatSession*>(ctx)->read_stdin();
}
void CatSession::read_stdin() {
bool any_command_read = false;
for (;;) {
auto poll_result = this->poll.poll();
short fd_events = 0;
try {
fd_events = poll_result.at(0);
} catch (const out_of_range&) {
}
if (!(fd_events & POLLIN)) {
break;
}
string command(2048, '\0');
if (!fgets(command.data(), command.size(), stdin)) {
if (!any_command_read) {
// ctrl+d probably; we should exit
fputc('\n', stderr);
event_base_loopexit(this->base.get(), nullptr);
return;
} else {
break; // probably not EOF; just no more commands for now
}
}
// trim the extra data off the string
size_t len = strlen(command.c_str());
if (len == 0) {
break;
}
if (command[len - 1] == '\n') {
len--;
}
command.resize(len);
any_command_read = true;
try {
execute_command(command);
} catch (const exit_shell&) {
event_base_loopexit(this->base.get(), nullptr);
return;
} catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what());
}
}
}
#include "CatSession.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh>
#include <phosg/Network.hh>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ProxyCommands.hh"
#include "ReceiveCommands.hh"
#include "ReceiveSubcommands.hh"
#include "SendCommands.hh"
using namespace std;
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
CatSession::CatSession(
shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
Version version,
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
base(base),
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
bb_key_file(bb_key_file) {
if (remote.ss_family != AF_INET) {
throw runtime_error("remote is not AF_INET");
}
string netloc_str = render_sockaddr_storage(remote);
this->log.info("Connecting to %s", netloc_str.c_str());
struct bufferevent* bev = bufferevent_socket_new(
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
if (!bev) {
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
}
this->channel.set_bufferevent(bev, 0);
if (bufferevent_socket_connect(this->channel.bev.get(),
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
}
event_add(this->read_event.get(), nullptr);
this->poll.add(0, POLLIN);
}
void CatSession::execute_command(const std::string& command) {
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
}
void CatSession::dispatch_on_channel_input(
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_input(command, flag, data);
}
void CatSession::on_channel_input(
uint16_t command, uint32_t flag, std::string& data) {
if (!uses_v4_encryption(this->channel.version)) {
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
if (uses_v3_encryption(this->channel.version)) {
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load());
} else { // PC, DC, or patch server
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load());
}
}
} else { // BB
if (command == 0x03 || command == 0x9B) {
if (!this->bb_key_file) {
throw runtime_error("BB encryption requires a key file");
}
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
this->channel.crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
this->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
this->log.info("Enabled BB encryption");
}
}
// TODO: Use the iovec form of print_data here instead of
// prepend_command_header (which copies the string)
string full_cmd = prepend_command_header(
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
}
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_error(events);
}
void CatSession::on_channel_error(short events) {
if (events & BEV_EVENT_CONNECTED) {
this->log.info("Channel connected");
}
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log.warning("Error %d (%s) in unlinked client stream", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
this->log.info("Session endpoint has disconnected");
this->channel.disconnect();
event_base_loopexit(this->base.get(), nullptr);
}
}
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
reinterpret_cast<CatSession*>(ctx)->read_stdin();
}
void CatSession::read_stdin() {
bool any_command_read = false;
for (;;) {
auto poll_result = this->poll.poll();
short fd_events = 0;
try {
fd_events = poll_result.at(0);
} catch (const out_of_range&) {
}
if (!(fd_events & POLLIN)) {
break;
}
string command(2048, '\0');
if (!fgets(command.data(), command.size(), stdin)) {
if (!any_command_read) {
// ctrl+d probably; we should exit
fputc('\n', stderr);
event_base_loopexit(this->base.get(), nullptr);
return;
} else {
break; // probably not EOF; just no more commands for now
}
}
// trim the extra data off the string
size_t len = strlen(command.c_str());
if (len == 0) {
break;
}
if (command[len - 1] == '\n') {
len--;
}
command.resize(len);
any_command_read = true;
try {
execute_command(command);
} catch (const exit_shell&) {
event_base_loopexit(this->base.get(), nullptr);
return;
} catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what());
}
}
}
+54 -54
View File
@@ -1,54 +1,54 @@
#pragma once
#include <event2/event.h>
#include <functional>
#include <map>
#include <memory>
#include <phosg/Filesystem.hh>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
class CatSession {
public:
CatSession(
std::shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
Version version,
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
CatSession(const CatSession&) = delete;
CatSession(CatSession&&) = delete;
CatSession& operator=(const CatSession&) = delete;
CatSession& operator=(CatSession&&) = delete;
virtual ~CatSession() = default;
protected:
PrefixedLogger log;
std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
Poll poll;
Channel channel;
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
class exit_shell : public std::runtime_error {
public:
exit_shell();
~exit_shell() = default;
};
virtual void execute_command(const std::string& command);
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void dispatch_on_channel_error(Channel& ch, short events);
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
void on_channel_error(short events);
void read_stdin();
};
#pragma once
#include <event2/event.h>
#include <functional>
#include <map>
#include <memory>
#include <phosg/Filesystem.hh>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
class CatSession {
public:
CatSession(
std::shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
Version version,
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
CatSession(const CatSession&) = delete;
CatSession(CatSession&&) = delete;
CatSession& operator=(const CatSession&) = delete;
CatSession& operator=(CatSession&&) = delete;
virtual ~CatSession() = default;
protected:
PrefixedLogger log;
std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
Poll poll;
Channel channel;
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
class exit_shell : public std::runtime_error {
public:
exit_shell();
~exit_shell() = default;
};
virtual void execute_command(const std::string& command);
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void dispatch_on_channel_error(Channel& ch, short events);
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
void on_channel_error(short events);
void read_stdin();
};
+421 -421
View File
@@ -1,421 +1,421 @@
#include "Channel.hh"
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <string.h>
#include <unistd.h>
#include <phosg/Network.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "Version.hh"
using namespace std;
extern bool use_terminal_colors;
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
Channel::Channel(
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
virtual_network_id(0),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
}
Channel::Channel(
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
this->set_bufferevent(bev, virtual_network_id);
}
void Channel::replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name) {
this->set_bufferevent(other.bev.release(), other.virtual_network_id);
this->local_addr = other.local_addr;
this->remote_addr = other.remote_addr;
this->version = other.version;
this->language = other.language;
this->crypt_in = other.crypt_in;
this->crypt_out = other.crypt_out;
this->name = name;
this->terminal_send_color = other.terminal_send_color;
this->terminal_recv_color = other.terminal_recv_color;
this->on_command_received = on_command_received;
this->on_error = on_error;
this->context_obj = context_obj;
other.disconnect(); // Clears crypts, addrs, etc.
}
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
this->bev.reset(bev);
this->virtual_network_id = virtual_network_id;
if (this->bev.get()) {
int fd = bufferevent_getfd(this->bev.get());
if (fd < 0) {
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else {
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
}
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
} else {
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
}
}
void Channel::disconnect() {
if (this->bev.get()) {
// If the output buffer is not empty, move the bufferevent into the draining
// pool instead of disconnecting it, to make sure all the data gets sent.
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
if (evbuffer_get_length(out_buffer) == 0) {
this->bev.reset(); // Destructor flushes and frees the bufferevent
} else {
// The callbacks will free it when all the data is sent or the client
// disconnects
auto on_output = +[](struct bufferevent* bev, void*) -> void {
flush_and_free_bufferevent(bev);
};
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
channel_exceptions_log.warning(
"Disconnecting channel caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
};
struct bufferevent* bev = this->bev.release();
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
bufferevent_disable(bev, EV_READ);
}
}
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
this->virtual_network_id = false;
this->crypt_in.reset();
this->crypt_out.reset();
}
Channel::Message Channel::recv() {
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
throw out_of_range("no command available");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(&header, header_size, false);
}
size_t command_logical_size = header.size(version);
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if encryption
// is not yet enabled.
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
? ((command_logical_size + 7) & ~7)
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
throw out_of_range("no command available");
}
// If we get here, then there is a full command in the buffer. Some encryption
// algorithms' advancement depends on the decrypted data, so we have to
// actually decrypt the header again (with advance=true) to keep them in a
// consistent state.
string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(header_data.data(), header_data.size());
}
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size()) < static_cast<ssize_t>(command_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
// Some versions of PSO DC can send commands whose sizes are not a multiple
// of 4, but the server is expected to always use a multiple of 4 bytes when
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
// we have to round up the size for DC commands here.
size_t orig_size = command_data.size();
command_data.resize((orig_size + 3) & (~3), 0);
this->crypt_in->decrypt(command_data.data(), command_data.size());
command_data.resize(orig_size);
}
command_data.resize(command_logical_size - header_size);
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == Version::BB_V4) {
command_data_log.info(
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(),
header.command(this->version),
header.flag(this->version));
} else {
command_data_log.info(
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(),
name_for_enum(this->version),
header.command(this->version),
header.flag(this->version));
}
vector<struct iovec> iovs;
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
return {
.command = header.command(this->version),
.flag = header.flag(this->version),
.data = std::move(command_data),
};
}
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
this->send(cmd, flag, nullptr, 0, silent);
}
void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
if (!this->connected()) {
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
return;
}
size_t size = 0;
for (const auto& b : blocks) {
size += b.second;
}
string send_data;
size_t logical_size;
size_t send_data_size = 0;
switch (this->version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (this->crypt_out.get() &&
(this->version != Version::DC_NTE) &&
(this->version != Version::DC_V1_11_2000_PROTOTYPE) &&
(this->version != Version::DC_V1)) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.command = cmd;
header.flag = flag;
header.size = send_data_size;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.size = send_data_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case Version::BB_V4: {
// BB has an annoying behavior here: command lengths must be multiples of
// 4, but the actual data length must be a multiple of 8. If the size
// field is not divisible by 8, 4 extra bytes are sent anyway. This
// behavior only applies when encryption is enabled - any commands sent
// before encryption is enabled have no size restrictions (except they
// must include a full header and must fit in the client's receive
// buffer), and no implicit extra bytes are sent.
PSOCommandHeaderBB header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 7) & ~7;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = (sizeof(header) + size + 3) & ~3;
header.size = logical_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
default:
throw logic_error("unimplemented game version in send_command");
}
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00
// bytes in size
if (send_data_size > 0x7C00) {
throw runtime_error("outbound command too large");
}
send_data.reserve(send_data_size);
for (const auto& b : blocks) {
send_data.append(reinterpret_cast<const char*>(b.first), b.second);
}
send_data.resize(send_data_size, '\0');
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == Version::BB_V4) {
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(), cmd, flag);
} else {
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(), name_for_enum(version), cmd, flag);
}
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
if (this->crypt_out.get()) {
this->crypt_out->encrypt(send_data.data(), send_data.size());
}
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
evbuffer_add(buf, send_data.data(), send_data.size());
}
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
this->send(cmd, flag, {make_pair(data, size)}, silent);
}
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
this->send(cmd, flag, data.data(), data.size(), silent);
}
void Channel::send(const void* data, size_t size, bool silent) {
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send(
header->command(this->version),
header->flag(this->version),
reinterpret_cast<const uint8_t*>(data) + header_size,
size - header_size,
silent);
}
void Channel::send(const string& data, bool silent) {
return this->send(data.data(), data.size(), silent);
}
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
// The client can be disconnected during on_command_received, so we have to
// make sure ch->bev is valid every time before calling recv()
while (ch->bev.get()) {
Message msg;
try {
msg = ch->recv();
} catch (const out_of_range&) {
break;
} catch (const exception& e) {
channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
ch->on_error(*ch, BEV_EVENT_ERROR);
break;
}
if (ch->on_command_received) {
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
}
}
}
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
if (ch->on_error) {
ch->on_error(*ch, events);
} else {
ch->disconnect();
}
}
#include "Channel.hh"
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <string.h>
#include <unistd.h>
#include <phosg/Network.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "Version.hh"
using namespace std;
extern bool use_terminal_colors;
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
Channel::Channel(
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
virtual_network_id(0),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
}
Channel::Channel(
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
this->set_bufferevent(bev, virtual_network_id);
}
void Channel::replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name) {
this->set_bufferevent(other.bev.release(), other.virtual_network_id);
this->local_addr = other.local_addr;
this->remote_addr = other.remote_addr;
this->version = other.version;
this->language = other.language;
this->crypt_in = other.crypt_in;
this->crypt_out = other.crypt_out;
this->name = name;
this->terminal_send_color = other.terminal_send_color;
this->terminal_recv_color = other.terminal_recv_color;
this->on_command_received = on_command_received;
this->on_error = on_error;
this->context_obj = context_obj;
other.disconnect(); // Clears crypts, addrs, etc.
}
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
this->bev.reset(bev);
this->virtual_network_id = virtual_network_id;
if (this->bev.get()) {
int fd = bufferevent_getfd(this->bev.get());
if (fd < 0) {
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else {
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
}
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
} else {
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
}
}
void Channel::disconnect() {
if (this->bev.get()) {
// If the output buffer is not empty, move the bufferevent into the draining
// pool instead of disconnecting it, to make sure all the data gets sent.
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
if (evbuffer_get_length(out_buffer) == 0) {
this->bev.reset(); // Destructor flushes and frees the bufferevent
} else {
// The callbacks will free it when all the data is sent or the client
// disconnects
auto on_output = +[](struct bufferevent* bev, void*) -> void {
flush_and_free_bufferevent(bev);
};
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
channel_exceptions_log.warning(
"Disconnecting channel caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
};
struct bufferevent* bev = this->bev.release();
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
bufferevent_disable(bev, EV_READ);
}
}
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
this->virtual_network_id = false;
this->crypt_in.reset();
this->crypt_out.reset();
}
Channel::Message Channel::recv() {
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
throw out_of_range("no command available");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(&header, header_size, false);
}
size_t command_logical_size = header.size(version);
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if encryption
// is not yet enabled.
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
? ((command_logical_size + 7) & ~7)
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
throw out_of_range("no command available");
}
// If we get here, then there is a full command in the buffer. Some encryption
// algorithms' advancement depends on the decrypted data, so we have to
// actually decrypt the header again (with advance=true) to keep them in a
// consistent state.
string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(header_data.data(), header_data.size());
}
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size()) < static_cast<ssize_t>(command_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
// Some versions of PSO DC can send commands whose sizes are not a multiple
// of 4, but the server is expected to always use a multiple of 4 bytes when
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
// we have to round up the size for DC commands here.
size_t orig_size = command_data.size();
command_data.resize((orig_size + 3) & (~3), 0);
this->crypt_in->decrypt(command_data.data(), command_data.size());
command_data.resize(orig_size);
}
command_data.resize(command_logical_size - header_size);
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == Version::BB_V4) {
command_data_log.info(
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(),
header.command(this->version),
header.flag(this->version));
} else {
command_data_log.info(
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(),
name_for_enum(this->version),
header.command(this->version),
header.flag(this->version));
}
vector<struct iovec> iovs;
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
return {
.command = header.command(this->version),
.flag = header.flag(this->version),
.data = std::move(command_data),
};
}
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
this->send(cmd, flag, nullptr, 0, silent);
}
void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
if (!this->connected()) {
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
return;
}
size_t size = 0;
for (const auto& b : blocks) {
size += b.second;
}
string send_data;
size_t logical_size;
size_t send_data_size = 0;
switch (this->version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (this->crypt_out.get() &&
(this->version != Version::DC_NTE) &&
(this->version != Version::DC_V1_11_2000_PROTOTYPE) &&
(this->version != Version::DC_V1)) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.command = cmd;
header.flag = flag;
header.size = send_data_size;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.size = send_data_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case Version::BB_V4: {
// BB has an annoying behavior here: command lengths must be multiples of
// 4, but the actual data length must be a multiple of 8. If the size
// field is not divisible by 8, 4 extra bytes are sent anyway. This
// behavior only applies when encryption is enabled - any commands sent
// before encryption is enabled have no size restrictions (except they
// must include a full header and must fit in the client's receive
// buffer), and no implicit extra bytes are sent.
PSOCommandHeaderBB header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 7) & ~7;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = (sizeof(header) + size + 3) & ~3;
header.size = logical_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
default:
throw logic_error("unimplemented game version in send_command");
}
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00
// bytes in size
if (send_data_size > 0x7C00) {
throw runtime_error("outbound command too large");
}
send_data.reserve(send_data_size);
for (const auto& b : blocks) {
send_data.append(reinterpret_cast<const char*>(b.first), b.second);
}
send_data.resize(send_data_size, '\0');
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == Version::BB_V4) {
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(), cmd, flag);
} else {
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(), name_for_enum(version), cmd, flag);
}
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
if (this->crypt_out.get()) {
this->crypt_out->encrypt(send_data.data(), send_data.size());
}
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
evbuffer_add(buf, send_data.data(), send_data.size());
}
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
this->send(cmd, flag, {make_pair(data, size)}, silent);
}
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
this->send(cmd, flag, data.data(), data.size(), silent);
}
void Channel::send(const void* data, size_t size, bool silent) {
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send(
header->command(this->version),
header->flag(this->version),
reinterpret_cast<const uint8_t*>(data) + header_size,
size - header_size,
silent);
}
void Channel::send(const string& data, bool silent) {
return this->send(data.data(), data.size(), silent);
}
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
// The client can be disconnected during on_command_received, so we have to
// make sure ch->bev is valid every time before calling recv()
while (ch->bev.get()) {
Message msg;
try {
msg = ch->recv();
} catch (const out_of_range&) {
break;
} catch (const exception& e) {
channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
ch->on_error(*ch, BEV_EVENT_ERROR);
break;
}
if (ch->on_command_received) {
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
}
}
}
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
if (ch->on_error) {
ch->on_error(*ch, events);
} else {
ch->disconnect();
}
}
+103 -103
View File
@@ -1,103 +1,103 @@
#pragma once
#include <netinet/in.h>
#include <memory>
#include <string>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "Version.hh"
struct Channel {
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
uint64_t virtual_network_id; // 0 = normal TCP connection
Version version;
uint8_t language;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
std::string name;
TerminalFormat terminal_send_color;
TerminalFormat terminal_recv_color;
struct Message {
uint16_t command;
uint32_t flag;
std::string data;
};
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
typedef void (*on_error_t)(Channel&, short);
on_command_received_t on_command_received;
on_error_t on_error;
void* context_obj;
// Creates an unconnected channel
Channel(
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name,
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
// Creates a connected channel
Channel(
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "",
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
Channel(const Channel& other) = delete;
Channel(Channel&& other) = delete;
Channel& operator=(const Channel& other) = delete;
Channel& operator=(Channel&& other) = delete;
void replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "");
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
inline bool connected() const {
return this->bev.get() != nullptr;
}
void disconnect();
// Receives a message. Throws std::out_of_range if no messages are available.
Message recv();
// Sends a message with an automatically-constructed header.
void send(uint16_t cmd, uint32_t flag = 0, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false);
template <typename CmdT>
requires(!std::is_pointer_v<CmdT>)
void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) {
this->send(cmd, flag, &data, sizeof(data), silent);
}
// Sends a message with a pre-existing header (as the first few bytes in the
// data)
void send(const void* data, size_t size, bool silent = false);
void send(const std::string& data, bool silent = false);
private:
static void dispatch_on_input(struct bufferevent*, void* ctx);
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
};
#pragma once
#include <netinet/in.h>
#include <memory>
#include <string>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "Version.hh"
struct Channel {
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
uint64_t virtual_network_id; // 0 = normal TCP connection
Version version;
uint8_t language;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
std::string name;
TerminalFormat terminal_send_color;
TerminalFormat terminal_recv_color;
struct Message {
uint16_t command;
uint32_t flag;
std::string data;
};
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
typedef void (*on_error_t)(Channel&, short);
on_command_received_t on_command_received;
on_error_t on_error;
void* context_obj;
// Creates an unconnected channel
Channel(
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name,
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
// Creates a connected channel
Channel(
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "",
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
Channel(const Channel& other) = delete;
Channel(Channel&& other) = delete;
Channel& operator=(const Channel& other) = delete;
Channel& operator=(Channel&& other) = delete;
void replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "");
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
inline bool connected() const {
return this->bev.get() != nullptr;
}
void disconnect();
// Receives a message. Throws std::out_of_range if no messages are available.
Message recv();
// Sends a message with an automatically-constructed header.
void send(uint16_t cmd, uint32_t flag = 0, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent = false);
void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false);
template <typename CmdT>
requires(!std::is_pointer_v<CmdT>)
void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) {
this->send(cmd, flag, &data, sizeof(data), silent);
}
// Sends a message with a pre-existing header (as the first few bytes in the
// data)
void send(const void* data, size_t size, bool silent = false);
void send(const std::string& data, bool silent = false);
private:
static void dispatch_on_input(struct bufferevent*, void* ctx);
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
};
+2603 -2603
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -1,14 +1,14 @@
#pragma once
#include <stdint.h>
#include <memory>
#include <string>
#include "Client.hh"
#include "Lobby.hh"
#include "ProxyServer.hh"
#include "ServerState.hh"
void on_chat_command(std::shared_ptr<Client> c, const std::string& text);
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text);
#pragma once
#include <stdint.h>
#include <memory>
#include <string>
#include "Client.hh"
#include "Lobby.hh"
#include "ProxyServer.hh"
#include "ServerState.hh"
void on_chat_command(std::shared_ptr<Client> c, const std::string& text);
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text);
+150 -150
View File
@@ -1,150 +1,150 @@
#include "ChoiceSearch.hh"
#include <inttypes.h>
#include <string.h>
#include "Client.hh"
using namespace std;
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
ChoiceSearchCategory{
.id = 0x0001,
.name = "Level",
.choices = {
{0x0000, "Any"},
{0x0001, "Own level +/- 5"},
{0x0002, "Level 1-10"},
{0x0003, "Level 11-20"},
{0x0004, "Level 21-40"},
{0x0005, "Level 41-60"},
{0x0006, "Level 61-80"},
{0x0007, "Level 81-100"},
{0x0008, "Level 101-120"},
{0x0009, "Level 121-160"},
{0x000A, "Level 161-200"},
},
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) {
return true;
}
uint32_t target_level = target_c->character()->disp.stats.level + 1;
switch (choice_id) {
case 0x0001:
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
case 0x0002:
return (target_level <= 10);
case 0x0003:
return (target_level > 10) && (target_level <= 20);
case 0x0004:
return (target_level > 20) && (target_level <= 40);
case 0x0005:
return (target_level > 40) && (target_level <= 60);
case 0x0006:
return (target_level > 60) && (target_level <= 80);
case 0x0007:
return (target_level > 80) && (target_level <= 100);
case 0x0008:
return (target_level > 100) && (target_level <= 120);
case 0x0009:
return (target_level > 120) && (target_level <= 160);
case 0x000A:
return (target_level > 160) && (target_level <= 200);
}
return false;
},
},
ChoiceSearchCategory{
.id = 0x0002,
.name = "Class",
.choices = {
{0x0000, "Any"},
{0x0010, "Hunter"},
{0x0001, "HUmar"},
{0x0002, "HUnewearl"},
{0x0003, "HUcast"},
{0x000A, "HUcaseal"},
{0x0011, "Ranger"},
{0x0004, "RAmar"},
{0x000C, "RAmarl"},
{0x0005, "RAcast"},
{0x0006, "RAcaseal"},
{0x0012, "Force"},
{0x000B, "FOmar"},
{0x0007, "FOmarl"},
{0x0008, "FOnewm"},
{0x0009, "FOnewearl"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
switch (choice_id) {
case 0x0000:
return true;
case 0x0010:
return target_c->character()->disp.visual.class_flags & 0x20;
case 0x0011:
return target_c->character()->disp.visual.class_flags & 0x40;
case 0x0012:
return target_c->character()->disp.visual.class_flags & 0x80;
default:
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
}
},
},
ChoiceSearchCategory{
.id = 0x0003,
.name = "Platform",
.choices = {
{0x0000, "Any"},
{0x0001, "DC betas"},
{0x0002, "DC V1"},
{0x0003, "DC V2 / PC"},
{0x0004, "GC / Xbox Episodes 1&2"},
{0x0005, "GC Episode 3"},
{0x0006, "BB"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) {
return true;
}
switch (target_c->version()) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
return (choice_id == 0x0001);
case Version::DC_V1:
return (choice_id == 0x0002);
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
return (choice_id == 0x0003);
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
return (choice_id == 0x0004);
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return (choice_id == 0x0005);
case Version::BB_V4:
return (choice_id == 0x0006);
default:
return false;
}
},
},
ChoiceSearchCategory{
.id = 0x0204,
.name = "Game mode",
.choices = {
{0x0000, "Any"},
{0x0001, "Normal"},
{0x0002, "Hard"},
{0x0003, "Very Hard"},
{0x0004, "Ultimate"},
{0x0005, "Battle"},
{0x0006, "Challenge"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
},
},
});
#include "ChoiceSearch.hh"
#include <inttypes.h>
#include <string.h>
#include "Client.hh"
using namespace std;
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
ChoiceSearchCategory{
.id = 0x0001,
.name = "Level",
.choices = {
{0x0000, "Any"},
{0x0001, "Own level +/- 5"},
{0x0002, "Level 1-10"},
{0x0003, "Level 11-20"},
{0x0004, "Level 21-40"},
{0x0005, "Level 41-60"},
{0x0006, "Level 61-80"},
{0x0007, "Level 81-100"},
{0x0008, "Level 101-120"},
{0x0009, "Level 121-160"},
{0x000A, "Level 161-200"},
},
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) {
return true;
}
uint32_t target_level = target_c->character()->disp.stats.level + 1;
switch (choice_id) {
case 0x0001:
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
case 0x0002:
return (target_level <= 10);
case 0x0003:
return (target_level > 10) && (target_level <= 20);
case 0x0004:
return (target_level > 20) && (target_level <= 40);
case 0x0005:
return (target_level > 40) && (target_level <= 60);
case 0x0006:
return (target_level > 60) && (target_level <= 80);
case 0x0007:
return (target_level > 80) && (target_level <= 100);
case 0x0008:
return (target_level > 100) && (target_level <= 120);
case 0x0009:
return (target_level > 120) && (target_level <= 160);
case 0x000A:
return (target_level > 160) && (target_level <= 200);
}
return false;
},
},
ChoiceSearchCategory{
.id = 0x0002,
.name = "Class",
.choices = {
{0x0000, "Any"},
{0x0010, "Hunter"},
{0x0001, "HUmar"},
{0x0002, "HUnewearl"},
{0x0003, "HUcast"},
{0x000A, "HUcaseal"},
{0x0011, "Ranger"},
{0x0004, "RAmar"},
{0x000C, "RAmarl"},
{0x0005, "RAcast"},
{0x0006, "RAcaseal"},
{0x0012, "Force"},
{0x000B, "FOmar"},
{0x0007, "FOmarl"},
{0x0008, "FOnewm"},
{0x0009, "FOnewearl"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
switch (choice_id) {
case 0x0000:
return true;
case 0x0010:
return target_c->character()->disp.visual.class_flags & 0x20;
case 0x0011:
return target_c->character()->disp.visual.class_flags & 0x40;
case 0x0012:
return target_c->character()->disp.visual.class_flags & 0x80;
default:
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
}
},
},
ChoiceSearchCategory{
.id = 0x0003,
.name = "Platform",
.choices = {
{0x0000, "Any"},
{0x0001, "DC betas"},
{0x0002, "DC V1"},
{0x0003, "DC V2 / PC"},
{0x0004, "GC / Xbox Episodes 1&2"},
{0x0005, "GC Episode 3"},
{0x0006, "BB"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) {
return true;
}
switch (target_c->version()) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
return (choice_id == 0x0001);
case Version::DC_V1:
return (choice_id == 0x0002);
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
return (choice_id == 0x0003);
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
return (choice_id == 0x0004);
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return (choice_id == 0x0005);
case Version::BB_V4:
return (choice_id == 0x0006);
default:
return false;
}
},
},
ChoiceSearchCategory{
.id = 0x0204,
.name = "Game mode",
.choices = {
{0x0000, "Any"},
{0x0001, "Normal"},
{0x0002, "Hard"},
{0x0003, "Very Hard"},
{0x0004, "Ultimate"},
{0x0005, "Battle"},
{0x0006, "Challenge"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
},
},
});
+64 -64
View File
@@ -1,64 +1,64 @@
#pragma once
#include <functional>
#include <memory>
#include <phosg/Encoding.hh>
#include <string>
#include <vector>
#include "Text.hh"
class Client;
template <bool IsBigEndian>
struct ChoiceSearchConfigT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
struct Entry {
U16T parent_choice_id = 0;
U16T choice_id = 0;
} __packed_ws__(Entry, 4);
parray<Entry, 5> entries;
int32_t get_setting(uint16_t parent_choice_id) const {
for (size_t z = 0; z < this->entries.size(); z++) {
if (this->entries[z].parent_choice_id == parent_choice_id) {
return this->entries[z].choice_id;
}
}
return -1;
}
operator ChoiceSearchConfigT<!IsBigEndian>() const {
ChoiceSearchConfigT<!IsBigEndian> ret;
ret.disabled = this->disabled.load();
for (size_t z = 0; z < this->entries.size(); z++) {
auto& ret_e = ret.entries[z];
const auto& this_e = this->entries[z];
ret_e.parent_choice_id = this_e.parent_choice_id.load();
ret_e.choice_id = this_e.choice_id.load();
}
return ret;
}
} __packed__;
using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
check_struct_size(ChoiceSearchConfig, 0x18);
check_struct_size(ChoiceSearchConfigBE, 0x18);
struct ChoiceSearchCategory {
struct Choice {
uint16_t id;
const char* name;
};
uint16_t id;
const char* name;
std::vector<Choice> choices;
std::function<bool(std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id)> client_matches;
};
extern const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES;
#pragma once
#include <functional>
#include <memory>
#include <phosg/Encoding.hh>
#include <string>
#include <vector>
#include "Text.hh"
class Client;
template <bool IsBigEndian>
struct ChoiceSearchConfigT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
struct Entry {
U16T parent_choice_id = 0;
U16T choice_id = 0;
} __packed_ws__(Entry, 4);
parray<Entry, 5> entries;
int32_t get_setting(uint16_t parent_choice_id) const {
for (size_t z = 0; z < this->entries.size(); z++) {
if (this->entries[z].parent_choice_id == parent_choice_id) {
return this->entries[z].choice_id;
}
}
return -1;
}
operator ChoiceSearchConfigT<!IsBigEndian>() const {
ChoiceSearchConfigT<!IsBigEndian> ret;
ret.disabled = this->disabled.load();
for (size_t z = 0; z < this->entries.size(); z++) {
auto& ret_e = ret.entries[z];
const auto& this_e = this->entries[z];
ret_e.parent_choice_id = this_e.parent_choice_id.load();
ret_e.choice_id = this_e.choice_id.load();
}
return ret;
}
} __packed__;
using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
check_struct_size(ChoiceSearchConfig, 0x18);
check_struct_size(ChoiceSearchConfigBE, 0x18);
struct ChoiceSearchCategory {
struct Choice {
uint16_t id;
const char* name;
};
uint16_t id;
const char* name;
std::vector<Choice> choices;
std::function<bool(std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id)> client_matches;
};
extern const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES;
+1120 -1120
View File
File diff suppressed because it is too large Load Diff
+412 -412
View File
@@ -1,412 +1,412 @@
#pragma once
#include <netinet/in.h>
#include <memory>
#include <stdexcept>
#include "Account.hh"
#include "Channel.hh"
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
#include "Episode3/Tournament.hh"
#include "FileContentsCache.hh"
#include "FunctionCompiler.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "PatchFileIndex.hh"
#include "Quest.hh"
#include "QuestScript.hh"
#include "TeamIndex.hh"
#include "Text.hh"
extern const uint64_t CLIENT_CONFIG_MAGIC;
class Server;
struct Lobby;
class Parsed6x70Data;
class Client : public std::enable_shared_from_this<Client> {
public:
enum class Flag : uint64_t {
// clang-format off
// This mask specifies which flags are sent to the client
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
// in the high bits) but that would require re-recording or manually
// rewriting all the tests
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
// Version-related flags
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
NO_D6_AFTER_LOBBY = 0x0000000000000100,
NO_D6 = 0x0000000000000200,
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
// Flags describing the behavior for send_function_call
NO_SEND_FUNCTION_CALL = 0x0000000000001000,
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
// State flags
LOADING = 0x0000000000100000, // Server-side only
LOADING_QUEST = 0x0000000000200000, // Server-side only
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only
LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only
IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only
AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only
SAVE_ENABLED = 0x0000000004000000,
HAS_EP3_CARD_DEFS = 0x0000000008000000,
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
HAS_AUTO_PATCHES = 0x0000004000000000,
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
// Cheat mode and option flags
INFINITE_HP_ENABLED = 0x0000000200000000,
INFINITE_TP_ENABLED = 0x0000000400000000,
DEBUG_ENABLED = 0x0000000800000000,
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
// Proxy option flags
PROXY_SAVE_FILES = 0x0000001000000000,
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
// clang-format on
};
enum class ItemDropNotificationMode {
NOTHING = 0,
RARES_ONLY = 1,
ALL_ITEMS = 2,
ALL_ITEMS_INCLUDING_MESETA = 3,
};
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
struct Config {
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
uint32_t specific_version = 0;
int32_t override_random_seed = 0;
uint8_t override_section_id = 0xFF; // FF = no override
uint8_t override_lobby_event = 0xFF; // FF = no override
uint8_t override_lobby_number = 0x80; // 80 = no override
uint32_t proxy_destination_address = 0;
uint16_t proxy_destination_port = 0;
Config() = default;
bool operator==(const Config& other) const = default;
bool operator!=(const Config& other) const = default;
bool should_update_vs(const Config& other) const;
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
return !!(enabled_flags & static_cast<uint64_t>(flag));
}
[[nodiscard]] inline bool check_flag(Flag flag) const {
return this->check_flag(this->enabled_flags, flag);
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint64_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint64_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint64_t>(flag);
}
void set_flags_for_version(Version version, int64_t sub_version);
ItemDropNotificationMode get_drop_notification_mode() const;
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
template <size_t Bytes>
void parse_from(const parray<uint8_t, Bytes>& data) {
StringReader r(data.data(), data.size());
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
throw std::invalid_argument("config signature is incorrect");
}
this->specific_version = r.get_u32l();
this->enabled_flags = r.get_u64l();
this->override_random_seed = r.get_u32l();
this->proxy_destination_address = r.get_u32b();
this->proxy_destination_port = r.get_u16l();
this->override_section_id = r.get_u8();
this->override_lobby_event = r.get_u8();
this->override_lobby_number = r.get_u8();
}
template <size_t Bytes>
void serialize_into(parray<uint8_t, Bytes>& data) const {
StringWriter w;
w.put_u32l(CLIENT_CONFIG_MAGIC);
w.put_u32l(this->specific_version);
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
w.put_u32l(this->override_random_seed);
w.put_u32b(this->proxy_destination_address);
w.put_u16l(this->proxy_destination_port);
w.put_u8(this->override_section_id);
w.put_u8(this->override_lobby_event);
w.put_u8(this->override_lobby_number);
const auto& s = w.str();
for (size_t z = 0; z < s.size(); z++) {
data[z] = s[z];
}
data.clear_after(s.size(), 0xFF);
}
};
std::weak_ptr<Server> server;
uint64_t id;
PrefixedLogger log;
std::shared_ptr<Login> login;
// Network
Channel channel;
struct sockaddr_storage next_connection_addr;
ServerBehavior server_behavior;
bool should_disconnect;
bool should_send_to_lobby_server;
bool should_send_to_proxy_server;
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
std::shared_ptr<XBNetworkLocation> xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint8_t bb_connection_phase;
uint64_t ping_start_time;
// Lobby/positioning
Config config;
Config synced_config;
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
int32_t sub_version;
float x;
float z;
uint32_t floor;
std::weak_ptr<Lobby> lobby;
uint8_t lobby_client_id;
uint8_t lobby_arrow_color;
int64_t preferred_lobby_id; // <0 = no preference
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
int16_t card_battle_table_number;
uint16_t card_battle_table_seat_number;
uint16_t card_battle_table_seat_state;
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
std::shared_ptr<const Menu> last_menu_sent;
uint32_t last_game_info_requested;
struct JoinCommand {
uint16_t command;
uint32_t flag;
std::string data;
};
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
// Character / game data
struct PendingItemTrade {
uint8_t other_client_id;
bool confirmed; // true if client has sent a D2 command
std::vector<ItemData> items;
};
struct PendingCardTrade {
uint8_t other_client_id;
bool confirmed; // true if client has sent an EE D2 command
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
};
bool should_update_play_time;
std::unordered_set<uint32_t> blocked_senders;
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
std::unique_ptr<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade;
uint32_t telepipe_lobby_id;
G_SetTelepipeState_6x68 telepipe_state;
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
int8_t bb_character_index;
ItemData bb_identify_result;
std::array<std::vector<ItemData>, 3> bb_shop_contents;
// Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give
RecentSwitchFlags recent_switch_flags; // used for switch assist
bool can_chat;
struct PendingCharacterExport {
std::shared_ptr<const Account> dest_account;
ssize_t character_index = -1;
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
};
std::unique_ptr<PendingCharacterExport> pending_character_export;
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
// File loading state
uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
Client(
std::shared_ptr<Server> server,
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
ServerBehavior server_behavior);
~Client();
void update_channel_name();
void reschedule_save_game_data_event();
void reschedule_ping_and_timeout_events();
inline Version version() const {
return this->channel.version;
}
inline uint8_t language() const {
return this->channel.language;
}
void convert_account_to_temporary_if_nte();
void sync_config();
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<Lobby> require_lobby() const;
std::shared_ptr<const TeamIndex::Team> team() const;
bool evaluate_quest_availability_expression(
std::shared_ptr<const IntegralExpression> expr,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_see_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_play_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_use_chat_commands() const;
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
void save_game_data();
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
void send_ping();
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
void suspend_timeouts();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
inline void delete_overlay() {
this->overlay_character_data.reset();
}
inline bool has_overlay() const {
return this->overlay_character_data.get() != nullptr;
}
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const;
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
void create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
std::string system_filename() const;
static std::string character_filename(const std::string& bb_username, int8_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index);
std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const;
std::string shared_bank_filename() const;
std::string legacy_player_filename() const;
std::string legacy_account_filename() const;
void save_all();
void save_system_file() const;
static void save_character_file(
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character);
// Note: This function is not const because it updates the player's play time.
void save_character_file();
void save_guild_card_file() const;
void load_backup_character(uint32_t account_id, size_t index);
void save_and_unload_character();
PlayerBank200& current_bank();
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
void use_character_bank(int8_t bb_character_index);
void use_default_bank();
void print_inventory(FILE* stream) const;
void print_bank(FILE* stream) const;
private:
// The overlay character data is used in battle and challenge modes, when
// character data is temporarily replaced in-game. In other play modes and in
// lobbies, overlay_character_data is null.
std::shared_ptr<PSOBBBaseSystemFile> system_data;
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
int8_t external_bank_character_index;
uint64_t last_play_time_update;
void save_and_clear_external_bank();
void load_all_files();
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
};
#pragma once
#include <netinet/in.h>
#include <memory>
#include <stdexcept>
#include "Account.hh"
#include "Channel.hh"
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
#include "Episode3/Tournament.hh"
#include "FileContentsCache.hh"
#include "FunctionCompiler.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "PatchFileIndex.hh"
#include "Quest.hh"
#include "QuestScript.hh"
#include "TeamIndex.hh"
#include "Text.hh"
extern const uint64_t CLIENT_CONFIG_MAGIC;
class Server;
struct Lobby;
class Parsed6x70Data;
class Client : public std::enable_shared_from_this<Client> {
public:
enum class Flag : uint64_t {
// clang-format off
// This mask specifies which flags are sent to the client
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
// in the high bits) but that would require re-recording or manually
// rewriting all the tests
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
// Version-related flags
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
NO_D6_AFTER_LOBBY = 0x0000000000000100,
NO_D6 = 0x0000000000000200,
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
// Flags describing the behavior for send_function_call
NO_SEND_FUNCTION_CALL = 0x0000000000001000,
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
// State flags
LOADING = 0x0000000000100000, // Server-side only
LOADING_QUEST = 0x0000000000200000, // Server-side only
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only
LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only
IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only
AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only
SAVE_ENABLED = 0x0000000004000000,
HAS_EP3_CARD_DEFS = 0x0000000008000000,
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
HAS_AUTO_PATCHES = 0x0000004000000000,
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
// Cheat mode and option flags
INFINITE_HP_ENABLED = 0x0000000200000000,
INFINITE_TP_ENABLED = 0x0000000400000000,
DEBUG_ENABLED = 0x0000000800000000,
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
// Proxy option flags
PROXY_SAVE_FILES = 0x0000001000000000,
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
// clang-format on
};
enum class ItemDropNotificationMode {
NOTHING = 0,
RARES_ONLY = 1,
ALL_ITEMS = 2,
ALL_ITEMS_INCLUDING_MESETA = 3,
};
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
struct Config {
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
uint32_t specific_version = 0;
int32_t override_random_seed = 0;
uint8_t override_section_id = 0xFF; // FF = no override
uint8_t override_lobby_event = 0xFF; // FF = no override
uint8_t override_lobby_number = 0x80; // 80 = no override
uint32_t proxy_destination_address = 0;
uint16_t proxy_destination_port = 0;
Config() = default;
bool operator==(const Config& other) const = default;
bool operator!=(const Config& other) const = default;
bool should_update_vs(const Config& other) const;
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
return !!(enabled_flags & static_cast<uint64_t>(flag));
}
[[nodiscard]] inline bool check_flag(Flag flag) const {
return this->check_flag(this->enabled_flags, flag);
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint64_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint64_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint64_t>(flag);
}
void set_flags_for_version(Version version, int64_t sub_version);
ItemDropNotificationMode get_drop_notification_mode() const;
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
template <size_t Bytes>
void parse_from(const parray<uint8_t, Bytes>& data) {
StringReader r(data.data(), data.size());
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
throw std::invalid_argument("config signature is incorrect");
}
this->specific_version = r.get_u32l();
this->enabled_flags = r.get_u64l();
this->override_random_seed = r.get_u32l();
this->proxy_destination_address = r.get_u32b();
this->proxy_destination_port = r.get_u16l();
this->override_section_id = r.get_u8();
this->override_lobby_event = r.get_u8();
this->override_lobby_number = r.get_u8();
}
template <size_t Bytes>
void serialize_into(parray<uint8_t, Bytes>& data) const {
StringWriter w;
w.put_u32l(CLIENT_CONFIG_MAGIC);
w.put_u32l(this->specific_version);
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
w.put_u32l(this->override_random_seed);
w.put_u32b(this->proxy_destination_address);
w.put_u16l(this->proxy_destination_port);
w.put_u8(this->override_section_id);
w.put_u8(this->override_lobby_event);
w.put_u8(this->override_lobby_number);
const auto& s = w.str();
for (size_t z = 0; z < s.size(); z++) {
data[z] = s[z];
}
data.clear_after(s.size(), 0xFF);
}
};
std::weak_ptr<Server> server;
uint64_t id;
PrefixedLogger log;
std::shared_ptr<Login> login;
// Network
Channel channel;
struct sockaddr_storage next_connection_addr;
ServerBehavior server_behavior;
bool should_disconnect;
bool should_send_to_lobby_server;
bool should_send_to_proxy_server;
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
std::shared_ptr<XBNetworkLocation> xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint8_t bb_connection_phase;
uint64_t ping_start_time;
// Lobby/positioning
Config config;
Config synced_config;
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
int32_t sub_version;
float x;
float z;
uint32_t floor;
std::weak_ptr<Lobby> lobby;
uint8_t lobby_client_id;
uint8_t lobby_arrow_color;
int64_t preferred_lobby_id; // <0 = no preference
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
int16_t card_battle_table_number;
uint16_t card_battle_table_seat_number;
uint16_t card_battle_table_seat_state;
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
std::shared_ptr<const Menu> last_menu_sent;
uint32_t last_game_info_requested;
struct JoinCommand {
uint16_t command;
uint32_t flag;
std::string data;
};
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
// Character / game data
struct PendingItemTrade {
uint8_t other_client_id;
bool confirmed; // true if client has sent a D2 command
std::vector<ItemData> items;
};
struct PendingCardTrade {
uint8_t other_client_id;
bool confirmed; // true if client has sent an EE D2 command
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
};
bool should_update_play_time;
std::unordered_set<uint32_t> blocked_senders;
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
std::unique_ptr<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade;
uint32_t telepipe_lobby_id;
G_SetTelepipeState_6x68 telepipe_state;
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
int8_t bb_character_index;
ItemData bb_identify_result;
std::array<std::vector<ItemData>, 3> bb_shop_contents;
// Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give
RecentSwitchFlags recent_switch_flags; // used for switch assist
bool can_chat;
struct PendingCharacterExport {
std::shared_ptr<const Account> dest_account;
ssize_t character_index = -1;
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
};
std::unique_ptr<PendingCharacterExport> pending_character_export;
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
// File loading state
uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
Client(
std::shared_ptr<Server> server,
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
ServerBehavior server_behavior);
~Client();
void update_channel_name();
void reschedule_save_game_data_event();
void reschedule_ping_and_timeout_events();
inline Version version() const {
return this->channel.version;
}
inline uint8_t language() const {
return this->channel.language;
}
void convert_account_to_temporary_if_nte();
void sync_config();
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<Lobby> require_lobby() const;
std::shared_ptr<const TeamIndex::Team> team() const;
bool evaluate_quest_availability_expression(
std::shared_ptr<const IntegralExpression> expr,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_see_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_play_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_use_chat_commands() const;
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
void save_game_data();
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
void send_ping();
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
void suspend_timeouts();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
inline void delete_overlay() {
this->overlay_character_data.reset();
}
inline bool has_overlay() const {
return this->overlay_character_data.get() != nullptr;
}
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const;
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
void create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
std::string system_filename() const;
static std::string character_filename(const std::string& bb_username, int8_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index);
std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const;
std::string shared_bank_filename() const;
std::string legacy_player_filename() const;
std::string legacy_account_filename() const;
void save_all();
void save_system_file() const;
static void save_character_file(
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character);
// Note: This function is not const because it updates the player's play time.
void save_character_file();
void save_guild_card_file() const;
void load_backup_character(uint32_t account_id, size_t index);
void save_and_unload_character();
PlayerBank200& current_bank();
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
void use_character_bank(int8_t bb_character_index);
void use_default_bank();
void print_inventory(FILE* stream) const;
void print_bank(FILE* stream) const;
private:
// The overlay character data is used in battle and challenge modes, when
// character data is temporarily replaced in-game. In other play modes and in
// lobbies, overlay_character_data is null.
std::shared_ptr<PSOBBBaseSystemFile> system_data;
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
int8_t external_bank_character_index;
uint64_t last_play_time_update;
void save_and_clear_external_bank();
void load_all_files();
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
};
+7233 -7233
View File
File diff suppressed because it is too large Load Diff
+1357 -1357
View File
File diff suppressed because it is too large Load Diff
+226 -226
View File
@@ -1,226 +1,226 @@
#pragma once
#include <stddef.h>
#include <array>
#include <deque>
#include <functional>
#include <phosg/Tools.hh>
#include <string>
#include "Text.hh"
enum class CompressPhase {
INDEX = 0,
CONSTRUCT_PATHS,
BACKTRACE_OPTIMAL_PATH,
GENERATE_RESULT,
};
template <>
const char* name_for_enum<CompressPhase>(CompressPhase v);
typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
////////////////////////////////////////////////////////////////////////////////
// PRS compression
////////////////////////////////////////////////////////////////////////////////
// Use this class if you need to compress from multiple input buffers, or need
// to compress multiple chunks and don't want to copy their contents
// unnecessarily. (For most common use cases, use prs_compress, below, instead.)
// To use this class, instantiate it, then call .add() one or more times, then
// call .close() and use the returned string as the compressed result.
class PRSCompressor {
public:
// compression_level specifies how aggressively to search for alternate paths:
// -1: Don't perform any compression at all, but produce output that can be
// understood by prs_decompress. The output will be about 9/8 the size
// of the input.
// 0: Greedily search for the longest backreference at every point. Don't
// consider any alternate paths. Generally offers a good balance between
// speed and output size.
// 1: Consider two paths at each point when a backreference is found: using
// the backreference or ignoring it.
// 2+: Consider further chains of paths at each point. Using values 2 or
// greater for compression_level generally yields diminishing returns.
explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr);
~PRSCompressor() = default;
// Adds more input data to be compressed, which logically comes after all
// previous data provided via add() calls. Cannot be called after close() is
// called.
void add(const void* data, size_t size);
void add(const std::string& data);
// Ends compression and returns the complete compressed result. It's OK to
// std::move() from the returned string reference.
std::string& close();
// Returns the total number of bytes passed to add() calls so far.
inline size_t input_size() const {
return this->input_bytes;
}
private:
template <size_t Size>
struct WrappedLog {
parray<uint8_t, Size> data;
WrappedLog() : data(0) {}
~WrappedLog() = default;
inline uint8_t at(size_t offset) const {
return this->data[offset % this->data.size()];
}
inline uint8_t& at(size_t offset) {
return this->data[offset % this->data.size()];
}
};
template <size_t Size>
struct IndexedLog : WrappedLog<Size> {
size_t offset;
size_t size;
std::array<std::deque<size_t>, 0x100> index;
IndexedLog()
: WrappedLog<Size>(),
offset(0),
size(0) {}
~IndexedLog() = default;
inline size_t end_offset() const {
return this->offset + this->size;
}
void push_back(uint8_t v) {
if (this->size == Size) {
this->pop_front();
}
size_t write_offset = this->offset + this->size;
this->at(write_offset) = v;
this->index[v].push_back(write_offset);
this->size++;
}
uint8_t pop_back() {
if (!this->size) {
throw std::logic_error("pop_back called on empty IndexedLog");
}
this->size--;
size_t offset = this->offset + this->size;
uint8_t v = this->at(offset);
this->index[v].pop_back();
return v;
}
uint8_t pop_front() {
uint8_t v = this->at(this->offset);
this->index[v].pop_front();
this->offset++;
this->size--;
return v;
}
const std::deque<size_t>& find(uint8_t v) {
return this->index[v];
}
};
void add_byte(uint8_t v);
void advance();
void move_forward_data_to_reverse_log(size_t size);
void advance_literal();
void advance_short_copy(ssize_t offset, size_t size);
void advance_long_copy(ssize_t offset, size_t size);
void advance_extended_copy(ssize_t offset, size_t size);
void write_control(bool z);
void flush_control();
ssize_t compression_level;
ProgressCallback progress_fn;
bool closed;
size_t control_byte_offset;
uint16_t pending_control_bits;
size_t input_bytes;
WrappedLog<0x101> forward_log;
IndexedLog<0x2000> reverse_log;
StringWriter output;
};
// These functions use PRSCompressor to compress a buffer of data. This is
// essentially a shortcut for constructing a PRSCompressor, calling .add() on
// it once, then calling .close().
std::string prs_compress(
const void* vdata,
size_t size,
ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr);
std::string prs_compress(
const std::string& data,
ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr);
// A faster form of prs_compress that doesn't have a tunable compression level.
std::string prs_compress_indexed(
const void* vdata,
size_t size,
ProgressCallback progress_fn = nullptr);
std::string prs_compress_indexed(
const std::string& data,
ProgressCallback progress_fn = nullptr);
// Compresses data using PRS to the smallest possible output size. This function
// is slow, but produces results significantly smaller than even Sega's original
// compressor.
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr);
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn = nullptr);
// Compresses data using PRS to the LARGEST possible output size. There is no
// practical use for this function except for amusement.
std::string prs_compress_pessimal(const void* vdata, size_t size);
// Decompresses PRS-compressed data.
struct PRSDecompressResult {
std::string data;
size_t input_bytes_used;
};
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Returns the decompressed size of PRS-compressed data, without actually
// decompressing it.
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Prints the command stream from a PRS-compressed buffer.
void prs_disassemble(FILE* stream, const void* data, size_t size);
void prs_disassemble(FILE* stream, const std::string& data);
////////////////////////////////////////////////////////////////////////////////
// BC0 compression
////////////////////////////////////////////////////////////////////////////////
// Compresses data using the BC0 algorithm. Like with PRS, the optimal variant
// is slow, but produces the smallest possible output.
std::string bc0_compress_optimal(
const void* in_data_v,
size_t in_size,
ProgressCallback progress_fn = nullptr);
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn = nullptr);
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn = nullptr);
// Encodes data in a BC0-compatible format without compression (similar to using
// compression_level=-1 with prs_compress).
std::string bc0_encode(const void* in_data_v, size_t in_size);
// Decompresses BC0-compressed data.
std::string bc0_decompress(const std::string& data);
std::string bc0_decompress(const void* data, size_t size);
// Prints the command stream from a BC0-compressed buffer.
void bc0_disassemble(FILE* stream, const std::string& data);
void bc0_disassemble(FILE* stream, const void* data, size_t size);
#pragma once
#include <stddef.h>
#include <array>
#include <deque>
#include <functional>
#include <phosg/Tools.hh>
#include <string>
#include "Text.hh"
enum class CompressPhase {
INDEX = 0,
CONSTRUCT_PATHS,
BACKTRACE_OPTIMAL_PATH,
GENERATE_RESULT,
};
template <>
const char* name_for_enum<CompressPhase>(CompressPhase v);
typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
////////////////////////////////////////////////////////////////////////////////
// PRS compression
////////////////////////////////////////////////////////////////////////////////
// Use this class if you need to compress from multiple input buffers, or need
// to compress multiple chunks and don't want to copy their contents
// unnecessarily. (For most common use cases, use prs_compress, below, instead.)
// To use this class, instantiate it, then call .add() one or more times, then
// call .close() and use the returned string as the compressed result.
class PRSCompressor {
public:
// compression_level specifies how aggressively to search for alternate paths:
// -1: Don't perform any compression at all, but produce output that can be
// understood by prs_decompress. The output will be about 9/8 the size
// of the input.
// 0: Greedily search for the longest backreference at every point. Don't
// consider any alternate paths. Generally offers a good balance between
// speed and output size.
// 1: Consider two paths at each point when a backreference is found: using
// the backreference or ignoring it.
// 2+: Consider further chains of paths at each point. Using values 2 or
// greater for compression_level generally yields diminishing returns.
explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr);
~PRSCompressor() = default;
// Adds more input data to be compressed, which logically comes after all
// previous data provided via add() calls. Cannot be called after close() is
// called.
void add(const void* data, size_t size);
void add(const std::string& data);
// Ends compression and returns the complete compressed result. It's OK to
// std::move() from the returned string reference.
std::string& close();
// Returns the total number of bytes passed to add() calls so far.
inline size_t input_size() const {
return this->input_bytes;
}
private:
template <size_t Size>
struct WrappedLog {
parray<uint8_t, Size> data;
WrappedLog() : data(0) {}
~WrappedLog() = default;
inline uint8_t at(size_t offset) const {
return this->data[offset % this->data.size()];
}
inline uint8_t& at(size_t offset) {
return this->data[offset % this->data.size()];
}
};
template <size_t Size>
struct IndexedLog : WrappedLog<Size> {
size_t offset;
size_t size;
std::array<std::deque<size_t>, 0x100> index;
IndexedLog()
: WrappedLog<Size>(),
offset(0),
size(0) {}
~IndexedLog() = default;
inline size_t end_offset() const {
return this->offset + this->size;
}
void push_back(uint8_t v) {
if (this->size == Size) {
this->pop_front();
}
size_t write_offset = this->offset + this->size;
this->at(write_offset) = v;
this->index[v].push_back(write_offset);
this->size++;
}
uint8_t pop_back() {
if (!this->size) {
throw std::logic_error("pop_back called on empty IndexedLog");
}
this->size--;
size_t offset = this->offset + this->size;
uint8_t v = this->at(offset);
this->index[v].pop_back();
return v;
}
uint8_t pop_front() {
uint8_t v = this->at(this->offset);
this->index[v].pop_front();
this->offset++;
this->size--;
return v;
}
const std::deque<size_t>& find(uint8_t v) {
return this->index[v];
}
};
void add_byte(uint8_t v);
void advance();
void move_forward_data_to_reverse_log(size_t size);
void advance_literal();
void advance_short_copy(ssize_t offset, size_t size);
void advance_long_copy(ssize_t offset, size_t size);
void advance_extended_copy(ssize_t offset, size_t size);
void write_control(bool z);
void flush_control();
ssize_t compression_level;
ProgressCallback progress_fn;
bool closed;
size_t control_byte_offset;
uint16_t pending_control_bits;
size_t input_bytes;
WrappedLog<0x101> forward_log;
IndexedLog<0x2000> reverse_log;
StringWriter output;
};
// These functions use PRSCompressor to compress a buffer of data. This is
// essentially a shortcut for constructing a PRSCompressor, calling .add() on
// it once, then calling .close().
std::string prs_compress(
const void* vdata,
size_t size,
ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr);
std::string prs_compress(
const std::string& data,
ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr);
// A faster form of prs_compress that doesn't have a tunable compression level.
std::string prs_compress_indexed(
const void* vdata,
size_t size,
ProgressCallback progress_fn = nullptr);
std::string prs_compress_indexed(
const std::string& data,
ProgressCallback progress_fn = nullptr);
// Compresses data using PRS to the smallest possible output size. This function
// is slow, but produces results significantly smaller than even Sega's original
// compressor.
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr);
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn = nullptr);
// Compresses data using PRS to the LARGEST possible output size. There is no
// practical use for this function except for amusement.
std::string prs_compress_pessimal(const void* vdata, size_t size);
// Decompresses PRS-compressed data.
struct PRSDecompressResult {
std::string data;
size_t input_bytes_used;
};
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Returns the decompressed size of PRS-compressed data, without actually
// decompressing it.
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Prints the command stream from a PRS-compressed buffer.
void prs_disassemble(FILE* stream, const void* data, size_t size);
void prs_disassemble(FILE* stream, const std::string& data);
////////////////////////////////////////////////////////////////////////////////
// BC0 compression
////////////////////////////////////////////////////////////////////////////////
// Compresses data using the BC0 algorithm. Like with PRS, the optimal variant
// is slow, but produces the smallest possible output.
std::string bc0_compress_optimal(
const void* in_data_v,
size_t in_size,
ProgressCallback progress_fn = nullptr);
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn = nullptr);
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn = nullptr);
// Encodes data in a BC0-compatible format without compression (similar to using
// compression_level=-1 with prs_compress).
std::string bc0_encode(const void* in_data_v, size_t in_size);
// Decompresses BC0-compressed data.
std::string bc0_decompress(const std::string& data);
std::string bc0_decompress(const void* data, size_t size);
// Prints the command stream from a BC0-compressed buffer.
void bc0_disassemble(FILE* stream, const std::string& data);
void bc0_disassemble(FILE* stream, const void* data, size_t size);
+121 -121
View File
@@ -1,121 +1,121 @@
#include "DNSServer.hh"
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <string>
#include <vector>
#include "Loggers.hh"
#include "NetworkAddresses.hh"
using namespace std;
DNSServer::DNSServer(
shared_ptr<struct event_base> base,
uint32_t local_connect_address,
uint32_t external_connect_address,
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
: base(base),
local_connect_address(local_connect_address),
external_connect_address(external_connect_address),
banned_ipv4_ranges(banned_ipv4_ranges) {}
DNSServer::~DNSServer() {
for (const auto& it : this->fd_to_receive_event) {
close(it.first);
}
}
void DNSServer::listen(const std::string& socket_path) {
this->add_socket(::listen(socket_path, 0, 0));
}
void DNSServer::listen(const std::string& addr, int port) {
this->add_socket(::listen(addr, port, 0));
}
void DNSServer::listen(int port) {
this->add_socket(::listen("", port, 0));
}
void DNSServer::add_socket(int fd) {
unique_ptr<struct event, void (*)(struct event*)> e(
event_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
event_free);
event_add(e.get(), nullptr);
this->fd_to_receive_event.emplace(fd, std::move(e));
}
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
short events, void* ctx) {
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
}
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
if (size < 0x0C) {
throw invalid_argument("query too small");
}
const char* data = reinterpret_cast<const char*>(vdata);
size_t name_len = strlen(&data[12]) + 1;
be_uint32_t be_resolved_address = resolved_address;
string response;
response.append(data, 2);
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
response.append(&data[12], name_len);
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
return response;
}
string DNSServer::response_for_query(
const string& query, uint32_t resolved_address) {
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
}
void DNSServer::on_receive_message(int fd, short) {
for (;;) {
struct sockaddr_storage remote;
socklen_t remote_size = sizeof(sockaddr_in);
memset(&remote, 0, remote_size);
string input(2048, 0);
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
if (bytes < 0) {
if (errno != EAGAIN) {
dns_server_log.error("input error %d", errno);
throw runtime_error("cannot read from udp socket");
}
break;
} else if (bytes == 0) {
break;
} else if (bytes < 0x0C) {
dns_server_log.warning("input query too small");
print_data(stderr, input.data(), bytes);
} else if (!this->banned_ipv4_ranges->check(remote)) {
input.resize(bytes);
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
uint32_t connect_address = is_local_address(remote_address)
? this->local_connect_address
: this->external_connect_address;
string response = this->response_for_query(input, connect_address);
sendto(fd, response.data(), response.size(), 0,
reinterpret_cast<const sockaddr*>(&remote), remote_size);
}
}
}
#include "DNSServer.hh"
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <string>
#include <vector>
#include "Loggers.hh"
#include "NetworkAddresses.hh"
using namespace std;
DNSServer::DNSServer(
shared_ptr<struct event_base> base,
uint32_t local_connect_address,
uint32_t external_connect_address,
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
: base(base),
local_connect_address(local_connect_address),
external_connect_address(external_connect_address),
banned_ipv4_ranges(banned_ipv4_ranges) {}
DNSServer::~DNSServer() {
for (const auto& it : this->fd_to_receive_event) {
close(it.first);
}
}
void DNSServer::listen(const std::string& socket_path) {
this->add_socket(::listen(socket_path, 0, 0));
}
void DNSServer::listen(const std::string& addr, int port) {
this->add_socket(::listen(addr, port, 0));
}
void DNSServer::listen(int port) {
this->add_socket(::listen("", port, 0));
}
void DNSServer::add_socket(int fd) {
unique_ptr<struct event, void (*)(struct event*)> e(
event_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
event_free);
event_add(e.get(), nullptr);
this->fd_to_receive_event.emplace(fd, std::move(e));
}
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
short events, void* ctx) {
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
}
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
if (size < 0x0C) {
throw invalid_argument("query too small");
}
const char* data = reinterpret_cast<const char*>(vdata);
size_t name_len = strlen(&data[12]) + 1;
be_uint32_t be_resolved_address = resolved_address;
string response;
response.append(data, 2);
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
response.append(&data[12], name_len);
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
return response;
}
string DNSServer::response_for_query(
const string& query, uint32_t resolved_address) {
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
}
void DNSServer::on_receive_message(int fd, short) {
for (;;) {
struct sockaddr_storage remote;
socklen_t remote_size = sizeof(sockaddr_in);
memset(&remote, 0, remote_size);
string input(2048, 0);
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
if (bytes < 0) {
if (errno != EAGAIN) {
dns_server_log.error("input error %d", errno);
throw runtime_error("cannot read from udp socket");
}
break;
} else if (bytes == 0) {
break;
} else if (bytes < 0x0C) {
dns_server_log.warning("input query too small");
print_data(stderr, input.data(), bytes);
} else if (!this->banned_ipv4_ranges->check(remote)) {
input.resize(bytes);
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
uint32_t connect_address = is_local_address(remote_address)
? this->local_connect_address
: this->external_connect_address;
string response = this->response_for_query(input, connect_address);
sendto(fd, response.data(), response.size(), 0,
reinterpret_cast<const sockaddr*>(&remote), remote_size);
}
}
}
+44 -44
View File
@@ -1,44 +1,44 @@
#pragma once
#include <event2/event.h>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "IPV4RangeSet.hh"
class DNSServer {
public:
DNSServer(
std::shared_ptr<struct event_base> base,
uint32_t local_connect_address,
uint32_t external_connect_address,
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
DNSServer(const DNSServer&) = delete;
DNSServer(DNSServer&&) = delete;
virtual ~DNSServer();
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
this->banned_ipv4_ranges = banned_ipv4_ranges;
}
void listen(const std::string& socket_path);
void listen(const std::string& addr, int port);
void listen(int port);
void add_socket(int fd);
static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address);
static std::string response_for_query(const std::string& query, uint32_t resolved_address);
private:
std::shared_ptr<struct event_base> base;
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
uint32_t local_connect_address;
uint32_t external_connect_address;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
void on_receive_message(int fd, short event);
};
#pragma once
#include <event2/event.h>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "IPV4RangeSet.hh"
class DNSServer {
public:
DNSServer(
std::shared_ptr<struct event_base> base,
uint32_t local_connect_address,
uint32_t external_connect_address,
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
DNSServer(const DNSServer&) = delete;
DNSServer(DNSServer&&) = delete;
virtual ~DNSServer();
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
this->banned_ipv4_ranges = banned_ipv4_ranges;
}
void listen(const std::string& socket_path);
void listen(const std::string& addr, int port);
void listen(int port);
void add_socket(int fd);
static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address);
static std::string response_for_query(const std::string& query, uint32_t resolved_address);
private:
std::shared_ptr<struct event_base> base;
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
uint32_t local_connect_address;
uint32_t external_connect_address;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
void on_receive_message(int fd, short event);
};
+1120 -1120
View File
File diff suppressed because it is too large Load Diff
+149 -149
View File
@@ -1,149 +1,149 @@
#pragma once
#include <inttypes.h>
#include <phosg/Tools.hh>
#include "StaticGameData.hh"
enum class EnemyType {
UNKNOWN = -1,
NONE = 0,
NON_ENEMY_NPC,
AL_RAPPY,
ASTARK,
BA_BOOTA,
BARBA_RAY,
BARBAROUS_WOLF,
BEE_L,
BEE_R,
BOOMA,
BOOTA,
BULCLAW,
BULK,
CANADINE,
CANADINE_GROUP,
CANANE,
CHAOS_BRINGER,
CHAOS_SORCERER,
CLAW,
DARK_BELRA,
DARK_FALZ_1,
DARK_FALZ_2,
DARK_FALZ_3,
DARK_GUNNER,
DARVANT,
DARVANT_ULTIMATE,
DE_ROL_LE,
DE_ROL_LE_BODY,
DE_ROL_LE_MINE,
DEATH_GUNNER,
DEL_LILY,
DEL_RAPPY,
DEL_RAPPY_ALT,
DELBITER,
DELDEPTH,
DELSABER,
DIMENIAN,
DOLMDARL,
DOLMOLM,
DORPHON,
DORPHON_ECLAIR,
DRAGON,
DUBCHIC,
DUBWITCH, // Has no entry in battle params
EGG_RAPPY,
EPSIGUARD,
EPSILON,
EVIL_SHARK,
GAEL,
GAL_GRYPHON,
GARANZ,
GEE,
GI_GUE,
GIBBLES,
GIGOBOOMA,
GILLCHIC,
GIRTABLULU,
GOBOOMA,
GOL_DRAGON,
GORAN,
GORAN_DETONATOR,
GRASS_ASSASSIN,
GUIL_SHARK,
HALLO_RAPPY,
HIDOOM,
HILDEBEAR,
HILDEBLUE,
ILL_GILL,
KONDRIEU,
LA_DIMENIAN,
LOVE_RAPPY,
MERICAROL,
MERICUS,
MERIKLE,
MERILLIA,
MERILTAS,
MERISSA_A,
MERISSA_AA,
MIGIUM,
MONEST,
MORFOS,
MOTHMANT,
NANO_DRAGON,
NAR_LILY,
OLGA_FLOW_1,
OLGA_FLOW_2,
PAL_SHARK,
PAN_ARMS,
PAZUZU,
PAZUZU_ALT,
PIG_RAY,
POFUILLY_SLIME,
POUILLY_SLIME,
POISON_LILY,
PYRO_GORAN,
RAG_RAPPY,
RECOBOX,
RECON,
SAINT_MILLION,
SAINT_RAPPY,
SAND_RAPPY,
SAND_RAPPY_ALT,
SATELLITE_LIZARD,
SATELLITE_LIZARD_ALT,
SAVAGE_WOLF,
SHAMBERTIN,
SINOW_BEAT,
SINOW_BERILL,
SINOW_GOLD,
SINOW_SPIGELL,
SINOW_ZELE,
SINOW_ZOA,
SO_DIMENIAN,
UL_GIBBON,
VOL_OPT_1,
VOL_OPT_2,
VOL_OPT_AMP,
VOL_OPT_CORE,
VOL_OPT_MONITOR,
VOL_OPT_PILLAR,
YOWIE,
YOWIE_ALT,
ZE_BOOTA,
ZOL_GIBBON,
ZU,
ZU_ALT,
MAX_ENEMY_TYPE,
};
template <>
const char* name_for_enum<EnemyType>(EnemyType type);
template <>
EnemyType enum_for_name<EnemyType>(const char* name);
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type);
uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
bool enemy_type_is_rare(EnemyType type);
#pragma once
#include <inttypes.h>
#include <phosg/Tools.hh>
#include "StaticGameData.hh"
enum class EnemyType {
UNKNOWN = -1,
NONE = 0,
NON_ENEMY_NPC,
AL_RAPPY,
ASTARK,
BA_BOOTA,
BARBA_RAY,
BARBAROUS_WOLF,
BEE_L,
BEE_R,
BOOMA,
BOOTA,
BULCLAW,
BULK,
CANADINE,
CANADINE_GROUP,
CANANE,
CHAOS_BRINGER,
CHAOS_SORCERER,
CLAW,
DARK_BELRA,
DARK_FALZ_1,
DARK_FALZ_2,
DARK_FALZ_3,
DARK_GUNNER,
DARVANT,
DARVANT_ULTIMATE,
DE_ROL_LE,
DE_ROL_LE_BODY,
DE_ROL_LE_MINE,
DEATH_GUNNER,
DEL_LILY,
DEL_RAPPY,
DEL_RAPPY_ALT,
DELBITER,
DELDEPTH,
DELSABER,
DIMENIAN,
DOLMDARL,
DOLMOLM,
DORPHON,
DORPHON_ECLAIR,
DRAGON,
DUBCHIC,
DUBWITCH, // Has no entry in battle params
EGG_RAPPY,
EPSIGUARD,
EPSILON,
EVIL_SHARK,
GAEL,
GAL_GRYPHON,
GARANZ,
GEE,
GI_GUE,
GIBBLES,
GIGOBOOMA,
GILLCHIC,
GIRTABLULU,
GOBOOMA,
GOL_DRAGON,
GORAN,
GORAN_DETONATOR,
GRASS_ASSASSIN,
GUIL_SHARK,
HALLO_RAPPY,
HIDOOM,
HILDEBEAR,
HILDEBLUE,
ILL_GILL,
KONDRIEU,
LA_DIMENIAN,
LOVE_RAPPY,
MERICAROL,
MERICUS,
MERIKLE,
MERILLIA,
MERILTAS,
MERISSA_A,
MERISSA_AA,
MIGIUM,
MONEST,
MORFOS,
MOTHMANT,
NANO_DRAGON,
NAR_LILY,
OLGA_FLOW_1,
OLGA_FLOW_2,
PAL_SHARK,
PAN_ARMS,
PAZUZU,
PAZUZU_ALT,
PIG_RAY,
POFUILLY_SLIME,
POUILLY_SLIME,
POISON_LILY,
PYRO_GORAN,
RAG_RAPPY,
RECOBOX,
RECON,
SAINT_MILLION,
SAINT_RAPPY,
SAND_RAPPY,
SAND_RAPPY_ALT,
SATELLITE_LIZARD,
SATELLITE_LIZARD_ALT,
SAVAGE_WOLF,
SHAMBERTIN,
SINOW_BEAT,
SINOW_BERILL,
SINOW_GOLD,
SINOW_SPIGELL,
SINOW_ZELE,
SINOW_ZOA,
SO_DIMENIAN,
UL_GIBBON,
VOL_OPT_1,
VOL_OPT_2,
VOL_OPT_AMP,
VOL_OPT_CORE,
VOL_OPT_MONITOR,
VOL_OPT_PILLAR,
YOWIE,
YOWIE_ALT,
ZE_BOOTA,
ZOL_GIBBON,
ZU,
ZU_ALT,
MAX_ENEMY_TYPE,
};
template <>
const char* name_for_enum<EnemyType>(EnemyType type);
template <>
EnemyType enum_for_name<EnemyType>(const char* name);
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type);
uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
bool enemy_type_is_rare(EnemyType type);
+528 -528
View File
File diff suppressed because it is too large Load Diff
+101 -101
View File
@@ -1,101 +1,101 @@
#pragma once
#include <inttypes.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "Menu.hh"
bool function_compiler_available();
void set_function_compiler_available(bool is_available);
// TODO: Support x86 and SH4 function calls in the future. Currently we only
// support PPC32 because I haven't written an appropriate x86 assembler yet.
struct CompiledFunctionCode {
enum class Architecture {
UNKNOWN = 0,
POWERPC, // GC
X86, // PC, XB, BB
SH4, // Dreamcast
};
Architecture arch;
std::string code;
std::vector<uint16_t> relocation_deltas;
std::unordered_map<std::string, uint32_t> label_offsets;
uint32_t entrypoint_offset_offset;
std::string source_path; // Path to source file from newserv root
std::string short_name; // Based on filename
std::string long_name; // From .meta name directive
std::string description; // From .meta description directive
uint8_t index; // 0 = unused (not registered in index_to_function)
uint32_t menu_item_id;
bool hide_from_patches_menu;
uint32_t specific_version;
bool is_big_endian() const;
template <typename FooterT>
std::string generate_client_command_t(
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const;
std::string generate_client_command(
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const;
};
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
std::shared_ptr<CompiledFunctionCode> compile_function_code(
CompiledFunctionCode::Architecture arch,
const std::string& directory,
const std::string& name,
const std::string& text);
struct FunctionCodeIndex {
FunctionCodeIndex() = default;
explicit FunctionCodeIndex(const std::string& directory);
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
std::unordered_map<uint8_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
bool patch_menu_empty(uint32_t specific_version) const;
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
};
struct DOLFileIndex {
struct File {
uint32_t menu_item_id;
std::string name;
std::string data;
bool is_compressed;
};
std::vector<std::shared_ptr<File>> item_id_to_file;
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
std::shared_ptr<const Menu> menu;
DOLFileIndex() = default;
explicit DOLFileIndex(const std::string& directory);
inline bool empty() const {
return this->name_to_file.empty() && this->item_id_to_file.empty();
}
};
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
#pragma once
#include <inttypes.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "Menu.hh"
bool function_compiler_available();
void set_function_compiler_available(bool is_available);
// TODO: Support x86 and SH4 function calls in the future. Currently we only
// support PPC32 because I haven't written an appropriate x86 assembler yet.
struct CompiledFunctionCode {
enum class Architecture {
UNKNOWN = 0,
POWERPC, // GC
X86, // PC, XB, BB
SH4, // Dreamcast
};
Architecture arch;
std::string code;
std::vector<uint16_t> relocation_deltas;
std::unordered_map<std::string, uint32_t> label_offsets;
uint32_t entrypoint_offset_offset;
std::string source_path; // Path to source file from newserv root
std::string short_name; // Based on filename
std::string long_name; // From .meta name directive
std::string description; // From .meta description directive
uint8_t index; // 0 = unused (not registered in index_to_function)
uint32_t menu_item_id;
bool hide_from_patches_menu;
uint32_t specific_version;
bool is_big_endian() const;
template <typename FooterT>
std::string generate_client_command_t(
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const;
std::string generate_client_command(
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const;
};
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
std::shared_ptr<CompiledFunctionCode> compile_function_code(
CompiledFunctionCode::Architecture arch,
const std::string& directory,
const std::string& name,
const std::string& text);
struct FunctionCodeIndex {
FunctionCodeIndex() = default;
explicit FunctionCodeIndex(const std::string& directory);
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
std::unordered_map<uint8_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
bool patch_menu_empty(uint32_t specific_version) const;
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
};
struct DOLFileIndex {
struct File {
uint32_t menu_item_id;
std::string name;
std::string data;
bool is_compressed;
};
std::vector<std::shared_ptr<File>> item_id_to_file;
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
std::shared_ptr<const Menu> menu;
DOLFileIndex() = default;
explicit DOLFileIndex(const std::string& directory);
inline bool empty() const {
return this->name_to_file.empty() && this->item_id_to_file.empty();
}
};
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
+1031 -1031
View File
File diff suppressed because it is too large Load Diff
+76 -76
View File
@@ -1,76 +1,76 @@
#pragma once
#include <event2/buffer.h>
#include <event2/event.h>
#include <event2/http.h>
#include <stdlib.h>
#include <memory>
#include <string>
#include "ProxyServer.hh"
#include "ServerState.hh"
class HTTPServer {
public:
HTTPServer(std::shared_ptr<ServerState> state);
HTTPServer(const HTTPServer&) = delete;
HTTPServer(HTTPServer&&) = delete;
HTTPServer& operator=(const HTTPServer&) = delete;
HTTPServer& operator=(HTTPServer&&) = delete;
virtual ~HTTPServer() = default;
void listen(const std::string& socket_path);
void listen(const std::string& addr, int port);
void listen(int port);
void add_socket(int fd);
void schedule_stop();
void wait_for_stop();
protected:
class http_error : public std::runtime_error {
public:
http_error(int code, const std::string& what);
int code;
};
std::shared_ptr<ServerState> state;
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct evhttp> http;
std::thread th;
void thread_fn();
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
void handle_request(struct evhttp_request* req);
static const std::unordered_map<int, const char*> explanation_for_response_code;
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
static const std::string& get_url_param(
const std::unordered_multimap<std::string, std::string>& params,
const std::string& key,
const std::string* _default = nullptr);
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
static JSON generate_client_config_json_st(const Client::Config& config);
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
JSON generate_game_server_clients_json() const;
JSON generate_proxy_server_clients_json() const;
JSON generate_server_info_json() const;
JSON generate_lobbies_json() const;
JSON generate_summary_json() const;
JSON generate_all_json() const;
JSON generate_ep3_cards_json(bool trial) const;
JSON generate_common_tables_json() const;
JSON generate_rare_tables_json() const;
JSON generate_rare_table_json(const std::string& table_name) const;
};
#pragma once
#include <event2/buffer.h>
#include <event2/event.h>
#include <event2/http.h>
#include <stdlib.h>
#include <memory>
#include <string>
#include "ProxyServer.hh"
#include "ServerState.hh"
class HTTPServer {
public:
HTTPServer(std::shared_ptr<ServerState> state);
HTTPServer(const HTTPServer&) = delete;
HTTPServer(HTTPServer&&) = delete;
HTTPServer& operator=(const HTTPServer&) = delete;
HTTPServer& operator=(HTTPServer&&) = delete;
virtual ~HTTPServer() = default;
void listen(const std::string& socket_path);
void listen(const std::string& addr, int port);
void listen(int port);
void add_socket(int fd);
void schedule_stop();
void wait_for_stop();
protected:
class http_error : public std::runtime_error {
public:
http_error(int code, const std::string& what);
int code;
};
std::shared_ptr<ServerState> state;
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct evhttp> http;
std::thread th;
void thread_fn();
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
void handle_request(struct evhttp_request* req);
static const std::unordered_map<int, const char*> explanation_for_response_code;
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
static const std::string& get_url_param(
const std::unordered_multimap<std::string, std::string>& params,
const std::string& key,
const std::string* _default = nullptr);
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
static JSON generate_client_config_json_st(const Client::Config& config);
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
JSON generate_game_server_clients_json() const;
JSON generate_proxy_server_clients_json() const;
JSON generate_server_info_json() const;
JSON generate_lobbies_json() const;
JSON generate_summary_json() const;
JSON generate_all_json() const;
JSON generate_ep3_cards_json(bool trial) const;
JSON generate_common_tables_json() const;
JSON generate_rare_tables_json() const;
JSON generate_rare_table_json(const std::string& table_name) const;
};
+73 -73
View File
@@ -1,73 +1,73 @@
#include "IPV4RangeSet.hh"
#include <arpa/inet.h>
using namespace std;
IPV4RangeSet::IPV4RangeSet(const JSON& json) {
for (const auto& it : json.as_list()) {
// String should be of the form a.b.c.d or a.b.c.d/e
auto tokens = split(it->as_string(), '/');
size_t mask_bits;
if (tokens.size() == 1) {
mask_bits = 32;
} else if (tokens.size() == 2) {
mask_bits = stoul(tokens[1], nullptr, 10);
if (mask_bits > 32) {
throw runtime_error("invalid IPv4 address range");
}
} else {
throw runtime_error("invalid IPv4 address range");
}
auto addr_tokens = split(tokens[0], '.');
if (addr_tokens.size() != 4) {
throw runtime_error("invalid IPv4 address");
}
uint32_t addr = 0;
for (size_t z = 0; z < 4; z++) {
size_t end_pos = 0;
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
throw runtime_error("invalid IPv4 address");
}
addr = (addr << 8) | new_byte;
}
addr &= (0xFFFFFFFF << (32 - mask_bits));
this->ranges.emplace(addr, mask_bits);
}
}
JSON IPV4RangeSet::json() const {
auto ret = JSON::list();
for (const auto& it : this->ranges) {
uint32_t addr = it.first;
uint8_t mask_bits = it.second;
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
static_cast<uint8_t>((addr >> 24) & 0xFF),
static_cast<uint8_t>((addr >> 16) & 0xFF),
static_cast<uint8_t>((addr >> 8) & 0xFF),
static_cast<uint8_t>(addr & 0xFF),
mask_bits));
}
return ret;
}
bool IPV4RangeSet::check(uint32_t addr) const {
auto it = this->ranges.upper_bound(addr);
if (it == this->ranges.begin()) {
return false; // addr is before any range
}
const auto& range = *(--it);
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
}
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
if (ss.ss_family != AF_INET) {
return false;
}
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
return this->check(ntohl(sin->sin_addr.s_addr));
}
#include "IPV4RangeSet.hh"
#include <arpa/inet.h>
using namespace std;
IPV4RangeSet::IPV4RangeSet(const JSON& json) {
for (const auto& it : json.as_list()) {
// String should be of the form a.b.c.d or a.b.c.d/e
auto tokens = split(it->as_string(), '/');
size_t mask_bits;
if (tokens.size() == 1) {
mask_bits = 32;
} else if (tokens.size() == 2) {
mask_bits = stoul(tokens[1], nullptr, 10);
if (mask_bits > 32) {
throw runtime_error("invalid IPv4 address range");
}
} else {
throw runtime_error("invalid IPv4 address range");
}
auto addr_tokens = split(tokens[0], '.');
if (addr_tokens.size() != 4) {
throw runtime_error("invalid IPv4 address");
}
uint32_t addr = 0;
for (size_t z = 0; z < 4; z++) {
size_t end_pos = 0;
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
throw runtime_error("invalid IPv4 address");
}
addr = (addr << 8) | new_byte;
}
addr &= (0xFFFFFFFF << (32 - mask_bits));
this->ranges.emplace(addr, mask_bits);
}
}
JSON IPV4RangeSet::json() const {
auto ret = JSON::list();
for (const auto& it : this->ranges) {
uint32_t addr = it.first;
uint8_t mask_bits = it.second;
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
static_cast<uint8_t>((addr >> 24) & 0xFF),
static_cast<uint8_t>((addr >> 16) & 0xFF),
static_cast<uint8_t>((addr >> 8) & 0xFF),
static_cast<uint8_t>(addr & 0xFF),
mask_bits));
}
return ret;
}
bool IPV4RangeSet::check(uint32_t addr) const {
auto it = this->ranges.upper_bound(addr);
if (it == this->ranges.begin()) {
return false; // addr is before any range
}
const auto& range = *(--it);
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
}
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
if (ss.ss_family != AF_INET) {
return false;
}
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
return this->check(ntohl(sin->sin_addr.s_addr));
}
+18 -18
View File
@@ -1,18 +1,18 @@
#pragma once
#include <phosg/JSON.hh>
#include <set>
class IPV4RangeSet {
public:
IPV4RangeSet() = default;
explicit IPV4RangeSet(const JSON& json);
JSON json() const;
bool check(uint32_t addr) const;
bool check(const struct sockaddr_storage& ss) const;
protected:
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
};
#pragma once
#include <phosg/JSON.hh>
#include <set>
class IPV4RangeSet {
public:
IPV4RangeSet() = default;
explicit IPV4RangeSet(const JSON& json);
JSON json() const;
bool check(uint32_t addr) const;
bool check(const struct sockaddr_storage& ss) const;
protected:
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
};
+455 -455
View File
@@ -1,455 +1,455 @@
#include "IntegralExpression.hh"
#include <algorithm>
#include <mutex>
#include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Tools.hh>
#include <string>
#include <unordered_map>
#include "CommandFormats.hh"
#include "Compression.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "QuestScript.hh"
#include "SaveFileFormats.hh"
#include "Text.hh"
using namespace std;
IntegralExpression::IntegralExpression(const string& text)
: root(this->parse_expr(text)) {}
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
: type(type),
left(std::move(left)),
right(std::move(right)) {}
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
try {
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_OR:
return this->left->evaluate(env) || this->right->evaluate(env);
case Type::LOGICAL_AND:
return this->left->evaluate(env) && this->right->evaluate(env);
case Type::BITWISE_OR:
return this->left->evaluate(env) | this->right->evaluate(env);
case Type::BITWISE_AND:
return this->left->evaluate(env) & this->right->evaluate(env);
case Type::BITWISE_XOR:
return this->left->evaluate(env) ^ this->right->evaluate(env);
case Type::LEFT_SHIFT:
return this->left->evaluate(env) << this->right->evaluate(env);
case Type::RIGHT_SHIFT:
return this->left->evaluate(env) >> this->right->evaluate(env);
case Type::LESS_THAN:
return this->left->evaluate(env) < this->right->evaluate(env);
case Type::GREATER_THAN:
return this->left->evaluate(env) > this->right->evaluate(env);
case Type::LESS_OR_EQUAL:
return this->left->evaluate(env) <= this->right->evaluate(env);
case Type::GREATER_OR_EQUAL:
return this->left->evaluate(env) >= this->right->evaluate(env);
case Type::EQUAL:
return this->left->evaluate(env) == this->right->evaluate(env);
case Type::NOT_EQUAL:
return this->left->evaluate(env) != this->right->evaluate(env);
case Type::ADD:
return this->left->evaluate(env) + this->right->evaluate(env);
case Type::SUBTRACT:
return this->left->evaluate(env) - this->right->evaluate(env);
case Type::MULTIPLY:
return this->left->evaluate(env) * this->right->evaluate(env);
case Type::DIVIDE:
return this->left->evaluate(env) / this->right->evaluate(env);
case Type::MODULUS:
return this->left->evaluate(env) % this->right->evaluate(env);
default:
throw logic_error("invalid binary operator type");
}
}
string IntegralExpression::BinaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_OR:
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
case Type::LOGICAL_AND:
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
case Type::BITWISE_OR:
return "(" + this->left->str() + ") | (" + this->right->str() + ")";
case Type::BITWISE_AND:
return "(" + this->left->str() + ") & (" + this->right->str() + ")";
case Type::BITWISE_XOR:
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
case Type::LEFT_SHIFT:
return "(" + this->left->str() + ") << (" + this->right->str() + ")";
case Type::RIGHT_SHIFT:
return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
case Type::LESS_THAN:
return "(" + this->left->str() + ") < (" + this->right->str() + ")";
case Type::GREATER_THAN:
return "(" + this->left->str() + ") > (" + this->right->str() + ")";
case Type::LESS_OR_EQUAL:
return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
case Type::GREATER_OR_EQUAL:
return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
case Type::EQUAL:
return "(" + this->left->str() + ") == (" + this->right->str() + ")";
case Type::NOT_EQUAL:
return "(" + this->left->str() + ") != (" + this->right->str() + ")";
case Type::ADD:
return "(" + this->left->str() + ") + (" + this->right->str() + ")";
case Type::SUBTRACT:
return "(" + this->left->str() + ") - (" + this->right->str() + ")";
case Type::MULTIPLY:
return "(" + this->left->str() + ") * (" + this->right->str() + ")";
case Type::DIVIDE:
return "(" + this->left->str() + ") / (" + this->right->str() + ")";
case Type::MODULUS:
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
default:
throw logic_error("invalid binary operator type");
}
}
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
: type(type),
sub(std::move(sub)) {}
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
try {
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
return other_un.type == this->type && *other_un.sub == *this->sub;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_NOT:
return !this->sub->evaluate(env);
case Type::BITWISE_NOT:
return ~this->sub->evaluate(env);
case Type::NEGATIVE:
return -this->sub->evaluate(env);
default:
throw logic_error("invalid unary operator type");
}
}
string IntegralExpression::UnaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_NOT:
return "!(" + this->sub->str() + ")";
case Type::BITWISE_NOT:
return "~(" + this->sub->str() + ")";
case Type::NEGATIVE:
return "-(" + this->sub->str() + ")";
default:
throw logic_error("invalid unary operator type");
}
}
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
: flag_index(flag_index) {}
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
try {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
if (!env.flags) {
throw runtime_error("quest flags not available");
}
return env.flags->get(this->flag_index) ? 1 : 0;
}
string IntegralExpression::FlagLookupNode::str() const {
return string_printf("F_%04hX", this->flag_index);
}
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
Episode episode, uint8_t stage_index)
: episode(episode),
stage_index(stage_index) {}
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
try {
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
if (!env.challenge_records) {
throw runtime_error("challenge records not available");
}
if (this->episode == Episode::EP1) {
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
} else if (this->episode == Episode::EP2) {
return env.challenge_records->times_ep2_online.at(this->stage_index).has_value();
}
return false;
}
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
}
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
: reward_name(reward_name) {}
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
try {
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
return other_team_reward.reward_name == this->reward_name;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
}
string IntegralExpression::TeamRewardLookupNode::str() const {
return "T_" + this->reward_name;
}
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
return env.num_players;
}
string IntegralExpression::NumPlayersLookupNode::str() const {
return "V_NumPlayers";
}
IntegralExpression::EventLookupNode::EventLookupNode() {}
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
return env.event;
}
string IntegralExpression::EventLookupNode::str() const {
return "V_Event";
}
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
return env.v1_present ? 1 : 0;
}
string IntegralExpression::V1PresenceLookupNode::str() const {
return "V_V1Present";
}
IntegralExpression::ConstantNode::ConstantNode(int64_t value)
: value(value) {}
bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
try {
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
return other_const.value == this->value;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
return this->value;
}
string IntegralExpression::ConstantNode::str() const {
return string_printf("%" PRId64, this->value);
}
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
// Strip off spaces and fully-enclosing parentheses
for (;;) {
size_t starting_size = text.size();
while (text.at(0) == ' ') {
text = text.substr(1);
}
while (text.at(text.size() - 1) == ' ') {
text = text.substr(0, text.size() - 1);
}
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
// It doesn't suffice to just check the first ant last characters, since
// text could be like "(a) && (b)". Instead, we ignore the first and last
// characters, and don't strip anything if the internal parentheses are
// unbalanced.
size_t paren_level = 1;
for (size_t z = 1; z < text.size() - 1; z++) {
if (text[z] == '(') {
paren_level++;
} else if (text[z] == ')') {
paren_level--;
if (paren_level == 0) {
break;
}
}
}
if (paren_level > 0) {
text = text.substr(1, text.size() - 2);
}
}
if (text.size() == starting_size) {
break;
}
}
if (text.empty()) {
throw runtime_error("invalid expression");
}
// Check for binary operators at the root level
using BinType = BinaryOperatorNode::Type;
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
{{make_pair("&", BinType::BITWISE_AND)}},
{{make_pair("^", BinType::BITWISE_XOR)}},
{{make_pair("|", BinType::BITWISE_OR)}},
{{make_pair("&&", BinType::LOGICAL_AND)}},
{{make_pair("||", BinType::LOGICAL_OR)}},
};
for (const auto& operators : binary_operator_levels) {
size_t paren_level = 0;
for (size_t z = 0; z < text.size() - 1; z++) {
if (text[z] == '(') {
paren_level++;
continue;
} else if (text[z] == ')') {
paren_level--;
continue;
}
if (!paren_level) {
for (const auto& oper : operators) {
// Awful hack (because I'm too lazy to add a tokenization step): if
// the operator is followed or preceded by another copy of itself,
// don't match it (this prevents us from matching & when the token is
// actually &&)
if ((text.size() > z + oper.first.size()) &&
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
(text.compare(z, oper.first.size(), oper.first) == 0) &&
(text.compare(z + oper.first.size(), oper.first.size(), oper.first) != 0)) {
auto left = IntegralExpression::parse_expr(text.substr(0, z));
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
}
}
}
}
}
// Check for unary operators
if (text[0] == '!') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '~') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '-') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
IntegralExpression::parse_expr(text.substr(1)));
}
// Check for env lookups
if (text.starts_with("F_")) {
char* endptr = nullptr;
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
if (endptr != text.data() + text.size()) {
throw runtime_error("invalid flag lookup token");
}
if (flag >= 0x400) {
throw runtime_error("invalid flag index");
}
return make_unique<FlagLookupNode>(flag);
}
if (text.starts_with("CC_")) {
Episode episode;
if (text.starts_with("CC_Ep1_")) {
episode = Episode::EP1;
} else if (text.starts_with("CC_Ep2_")) {
episode = Episode::EP2;
} else {
throw runtime_error("invalid challenge episode");
}
char* endptr = nullptr;
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
if (endptr != text.data() + text.size()) {
throw runtime_error("invalid challenge completion lookup token");
}
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
throw runtime_error("invalid challenge stage index");
}
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
}
if (text.starts_with("T_")) {
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
}
if (text == "V_NumPlayers") {
return make_unique<NumPlayersLookupNode>();
}
if (text == "V_Event") {
return make_unique<EventLookupNode>();
}
if (text == "V_V1Present") {
return make_unique<V1PresenceLookupNode>();
}
// Check for constants
if (text == "true") {
return make_unique<ConstantNode>(1);
}
if (text == "false") {
return make_unique<ConstantNode>(0);
}
try {
size_t endpos;
int64_t v = stoll(string(text), &endpos, 0);
if (endpos == text.size()) {
return make_unique<ConstantNode>(v);
}
} catch (const exception&) {
}
throw runtime_error("unparseable expression");
}
#include "IntegralExpression.hh"
#include <algorithm>
#include <mutex>
#include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Tools.hh>
#include <string>
#include <unordered_map>
#include "CommandFormats.hh"
#include "Compression.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "QuestScript.hh"
#include "SaveFileFormats.hh"
#include "Text.hh"
using namespace std;
IntegralExpression::IntegralExpression(const string& text)
: root(this->parse_expr(text)) {}
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
: type(type),
left(std::move(left)),
right(std::move(right)) {}
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
try {
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_OR:
return this->left->evaluate(env) || this->right->evaluate(env);
case Type::LOGICAL_AND:
return this->left->evaluate(env) && this->right->evaluate(env);
case Type::BITWISE_OR:
return this->left->evaluate(env) | this->right->evaluate(env);
case Type::BITWISE_AND:
return this->left->evaluate(env) & this->right->evaluate(env);
case Type::BITWISE_XOR:
return this->left->evaluate(env) ^ this->right->evaluate(env);
case Type::LEFT_SHIFT:
return this->left->evaluate(env) << this->right->evaluate(env);
case Type::RIGHT_SHIFT:
return this->left->evaluate(env) >> this->right->evaluate(env);
case Type::LESS_THAN:
return this->left->evaluate(env) < this->right->evaluate(env);
case Type::GREATER_THAN:
return this->left->evaluate(env) > this->right->evaluate(env);
case Type::LESS_OR_EQUAL:
return this->left->evaluate(env) <= this->right->evaluate(env);
case Type::GREATER_OR_EQUAL:
return this->left->evaluate(env) >= this->right->evaluate(env);
case Type::EQUAL:
return this->left->evaluate(env) == this->right->evaluate(env);
case Type::NOT_EQUAL:
return this->left->evaluate(env) != this->right->evaluate(env);
case Type::ADD:
return this->left->evaluate(env) + this->right->evaluate(env);
case Type::SUBTRACT:
return this->left->evaluate(env) - this->right->evaluate(env);
case Type::MULTIPLY:
return this->left->evaluate(env) * this->right->evaluate(env);
case Type::DIVIDE:
return this->left->evaluate(env) / this->right->evaluate(env);
case Type::MODULUS:
return this->left->evaluate(env) % this->right->evaluate(env);
default:
throw logic_error("invalid binary operator type");
}
}
string IntegralExpression::BinaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_OR:
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
case Type::LOGICAL_AND:
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
case Type::BITWISE_OR:
return "(" + this->left->str() + ") | (" + this->right->str() + ")";
case Type::BITWISE_AND:
return "(" + this->left->str() + ") & (" + this->right->str() + ")";
case Type::BITWISE_XOR:
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
case Type::LEFT_SHIFT:
return "(" + this->left->str() + ") << (" + this->right->str() + ")";
case Type::RIGHT_SHIFT:
return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
case Type::LESS_THAN:
return "(" + this->left->str() + ") < (" + this->right->str() + ")";
case Type::GREATER_THAN:
return "(" + this->left->str() + ") > (" + this->right->str() + ")";
case Type::LESS_OR_EQUAL:
return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
case Type::GREATER_OR_EQUAL:
return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
case Type::EQUAL:
return "(" + this->left->str() + ") == (" + this->right->str() + ")";
case Type::NOT_EQUAL:
return "(" + this->left->str() + ") != (" + this->right->str() + ")";
case Type::ADD:
return "(" + this->left->str() + ") + (" + this->right->str() + ")";
case Type::SUBTRACT:
return "(" + this->left->str() + ") - (" + this->right->str() + ")";
case Type::MULTIPLY:
return "(" + this->left->str() + ") * (" + this->right->str() + ")";
case Type::DIVIDE:
return "(" + this->left->str() + ") / (" + this->right->str() + ")";
case Type::MODULUS:
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
default:
throw logic_error("invalid binary operator type");
}
}
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
: type(type),
sub(std::move(sub)) {}
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
try {
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
return other_un.type == this->type && *other_un.sub == *this->sub;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_NOT:
return !this->sub->evaluate(env);
case Type::BITWISE_NOT:
return ~this->sub->evaluate(env);
case Type::NEGATIVE:
return -this->sub->evaluate(env);
default:
throw logic_error("invalid unary operator type");
}
}
string IntegralExpression::UnaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_NOT:
return "!(" + this->sub->str() + ")";
case Type::BITWISE_NOT:
return "~(" + this->sub->str() + ")";
case Type::NEGATIVE:
return "-(" + this->sub->str() + ")";
default:
throw logic_error("invalid unary operator type");
}
}
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
: flag_index(flag_index) {}
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
try {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
if (!env.flags) {
throw runtime_error("quest flags not available");
}
return env.flags->get(this->flag_index) ? 1 : 0;
}
string IntegralExpression::FlagLookupNode::str() const {
return string_printf("F_%04hX", this->flag_index);
}
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
Episode episode, uint8_t stage_index)
: episode(episode),
stage_index(stage_index) {}
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
try {
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
if (!env.challenge_records) {
throw runtime_error("challenge records not available");
}
if (this->episode == Episode::EP1) {
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
} else if (this->episode == Episode::EP2) {
return env.challenge_records->times_ep2_online.at(this->stage_index).has_value();
}
return false;
}
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
}
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
: reward_name(reward_name) {}
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
try {
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
return other_team_reward.reward_name == this->reward_name;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
}
string IntegralExpression::TeamRewardLookupNode::str() const {
return "T_" + this->reward_name;
}
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
return env.num_players;
}
string IntegralExpression::NumPlayersLookupNode::str() const {
return "V_NumPlayers";
}
IntegralExpression::EventLookupNode::EventLookupNode() {}
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
return env.event;
}
string IntegralExpression::EventLookupNode::str() const {
return "V_Event";
}
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
}
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
return env.v1_present ? 1 : 0;
}
string IntegralExpression::V1PresenceLookupNode::str() const {
return "V_V1Present";
}
IntegralExpression::ConstantNode::ConstantNode(int64_t value)
: value(value) {}
bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
try {
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
return other_const.value == this->value;
} catch (const bad_cast&) {
return false;
}
}
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
return this->value;
}
string IntegralExpression::ConstantNode::str() const {
return string_printf("%" PRId64, this->value);
}
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
// Strip off spaces and fully-enclosing parentheses
for (;;) {
size_t starting_size = text.size();
while (text.at(0) == ' ') {
text = text.substr(1);
}
while (text.at(text.size() - 1) == ' ') {
text = text.substr(0, text.size() - 1);
}
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
// It doesn't suffice to just check the first ant last characters, since
// text could be like "(a) && (b)". Instead, we ignore the first and last
// characters, and don't strip anything if the internal parentheses are
// unbalanced.
size_t paren_level = 1;
for (size_t z = 1; z < text.size() - 1; z++) {
if (text[z] == '(') {
paren_level++;
} else if (text[z] == ')') {
paren_level--;
if (paren_level == 0) {
break;
}
}
}
if (paren_level > 0) {
text = text.substr(1, text.size() - 2);
}
}
if (text.size() == starting_size) {
break;
}
}
if (text.empty()) {
throw runtime_error("invalid expression");
}
// Check for binary operators at the root level
using BinType = BinaryOperatorNode::Type;
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
{{make_pair("&", BinType::BITWISE_AND)}},
{{make_pair("^", BinType::BITWISE_XOR)}},
{{make_pair("|", BinType::BITWISE_OR)}},
{{make_pair("&&", BinType::LOGICAL_AND)}},
{{make_pair("||", BinType::LOGICAL_OR)}},
};
for (const auto& operators : binary_operator_levels) {
size_t paren_level = 0;
for (size_t z = 0; z < text.size() - 1; z++) {
if (text[z] == '(') {
paren_level++;
continue;
} else if (text[z] == ')') {
paren_level--;
continue;
}
if (!paren_level) {
for (const auto& oper : operators) {
// Awful hack (because I'm too lazy to add a tokenization step): if
// the operator is followed or preceded by another copy of itself,
// don't match it (this prevents us from matching & when the token is
// actually &&)
if ((text.size() > z + oper.first.size()) &&
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
(text.compare(z, oper.first.size(), oper.first) == 0) &&
(text.compare(z + oper.first.size(), oper.first.size(), oper.first) != 0)) {
auto left = IntegralExpression::parse_expr(text.substr(0, z));
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
}
}
}
}
}
// Check for unary operators
if (text[0] == '!') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '~') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '-') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
IntegralExpression::parse_expr(text.substr(1)));
}
// Check for env lookups
if (text.starts_with("F_")) {
char* endptr = nullptr;
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
if (endptr != text.data() + text.size()) {
throw runtime_error("invalid flag lookup token");
}
if (flag >= 0x400) {
throw runtime_error("invalid flag index");
}
return make_unique<FlagLookupNode>(flag);
}
if (text.starts_with("CC_")) {
Episode episode;
if (text.starts_with("CC_Ep1_")) {
episode = Episode::EP1;
} else if (text.starts_with("CC_Ep2_")) {
episode = Episode::EP2;
} else {
throw runtime_error("invalid challenge episode");
}
char* endptr = nullptr;
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
if (endptr != text.data() + text.size()) {
throw runtime_error("invalid challenge completion lookup token");
}
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
throw runtime_error("invalid challenge stage index");
}
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
}
if (text.starts_with("T_")) {
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
}
if (text == "V_NumPlayers") {
return make_unique<NumPlayersLookupNode>();
}
if (text == "V_Event") {
return make_unique<EventLookupNode>();
}
if (text == "V_V1Present") {
return make_unique<V1PresenceLookupNode>();
}
// Check for constants
if (text == "true") {
return make_unique<ConstantNode>(1);
}
if (text == "false") {
return make_unique<ConstantNode>(0);
}
try {
size_t endpos;
int64_t v = stoll(string(text), &endpos, 0);
if (endpos == text.size()) {
return make_unique<ConstantNode>(v);
}
} catch (const exception&) {
}
throw runtime_error("unparseable expression");
}
+188 -188
View File
@@ -1,188 +1,188 @@
#pragma once
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "PlayerSubordinates.hh"
#include "QuestScript.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
class IntegralExpression {
public:
struct Env {
const QuestFlagsForDifficulty* flags;
const PlayerRecordsChallengeBB* challenge_records;
std::shared_ptr<const TeamIndex::Team> team;
size_t num_players;
uint8_t event;
bool v1_present;
};
IntegralExpression(const std::string& text);
~IntegralExpression() = default;
inline bool operator==(const IntegralExpression& other) const {
return this->root->operator==(*other.root);
}
inline bool operator!=(const IntegralExpression& other) const {
return !this->operator==(other);
}
inline int64_t evaluate(const Env& env) const {
return this->root->evaluate(env);
}
inline std::string str() const {
return this->root->str();
}
protected:
class Node {
public:
virtual ~Node() = default;
virtual bool operator==(const Node& other) const = 0;
inline bool operator!=(const Node& other) const {
return !this->operator==(other);
}
virtual int64_t evaluate(const Env& env) const = 0;
virtual std::string str() const = 0;
protected:
Node() = default;
};
class BinaryOperatorNode : public Node {
public:
enum class Type {
LOGICAL_OR = 0,
LOGICAL_AND,
BITWISE_OR,
BITWISE_AND,
BITWISE_XOR,
LEFT_SHIFT,
RIGHT_SHIFT,
LESS_THAN,
GREATER_THAN,
LESS_OR_EQUAL,
GREATER_OR_EQUAL,
EQUAL,
NOT_EQUAL,
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
MODULUS,
};
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
virtual ~BinaryOperatorNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Type type;
std::unique_ptr<const Node> left;
std::unique_ptr<const Node> right;
};
class UnaryOperatorNode : public Node {
public:
enum class Type {
LOGICAL_NOT = 0,
BITWISE_NOT,
NEGATIVE,
};
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
virtual ~UnaryOperatorNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Type type;
std::unique_ptr<const Node> sub;
};
class FlagLookupNode : public Node {
public:
FlagLookupNode(uint16_t flag_index);
virtual ~FlagLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
uint16_t flag_index;
};
class ChallengeCompletionLookupNode : public Node {
public:
ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index);
virtual ~ChallengeCompletionLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Episode episode;
uint8_t stage_index;
};
class TeamRewardLookupNode : public Node {
public:
TeamRewardLookupNode(const std::string& reward_name);
virtual ~TeamRewardLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
std::string reward_name;
};
class NumPlayersLookupNode : public Node {
public:
NumPlayersLookupNode();
virtual ~NumPlayersLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class EventLookupNode : public Node {
public:
EventLookupNode();
virtual ~EventLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class V1PresenceLookupNode : public Node {
public:
V1PresenceLookupNode();
virtual ~V1PresenceLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class ConstantNode : public Node {
public:
ConstantNode(int64_t value);
virtual ~ConstantNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
int64_t value;
};
std::unique_ptr<const Node> parse_expr(std::string_view text);
std::unique_ptr<const Node> root;
};
#pragma once
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "PlayerSubordinates.hh"
#include "QuestScript.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
class IntegralExpression {
public:
struct Env {
const QuestFlagsForDifficulty* flags;
const PlayerRecordsChallengeBB* challenge_records;
std::shared_ptr<const TeamIndex::Team> team;
size_t num_players;
uint8_t event;
bool v1_present;
};
IntegralExpression(const std::string& text);
~IntegralExpression() = default;
inline bool operator==(const IntegralExpression& other) const {
return this->root->operator==(*other.root);
}
inline bool operator!=(const IntegralExpression& other) const {
return !this->operator==(other);
}
inline int64_t evaluate(const Env& env) const {
return this->root->evaluate(env);
}
inline std::string str() const {
return this->root->str();
}
protected:
class Node {
public:
virtual ~Node() = default;
virtual bool operator==(const Node& other) const = 0;
inline bool operator!=(const Node& other) const {
return !this->operator==(other);
}
virtual int64_t evaluate(const Env& env) const = 0;
virtual std::string str() const = 0;
protected:
Node() = default;
};
class BinaryOperatorNode : public Node {
public:
enum class Type {
LOGICAL_OR = 0,
LOGICAL_AND,
BITWISE_OR,
BITWISE_AND,
BITWISE_XOR,
LEFT_SHIFT,
RIGHT_SHIFT,
LESS_THAN,
GREATER_THAN,
LESS_OR_EQUAL,
GREATER_OR_EQUAL,
EQUAL,
NOT_EQUAL,
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
MODULUS,
};
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
virtual ~BinaryOperatorNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Type type;
std::unique_ptr<const Node> left;
std::unique_ptr<const Node> right;
};
class UnaryOperatorNode : public Node {
public:
enum class Type {
LOGICAL_NOT = 0,
BITWISE_NOT,
NEGATIVE,
};
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
virtual ~UnaryOperatorNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Type type;
std::unique_ptr<const Node> sub;
};
class FlagLookupNode : public Node {
public:
FlagLookupNode(uint16_t flag_index);
virtual ~FlagLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
uint16_t flag_index;
};
class ChallengeCompletionLookupNode : public Node {
public:
ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index);
virtual ~ChallengeCompletionLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Episode episode;
uint8_t stage_index;
};
class TeamRewardLookupNode : public Node {
public:
TeamRewardLookupNode(const std::string& reward_name);
virtual ~TeamRewardLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
std::string reward_name;
};
class NumPlayersLookupNode : public Node {
public:
NumPlayersLookupNode();
virtual ~NumPlayersLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class EventLookupNode : public Node {
public:
EventLookupNode();
virtual ~EventLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class V1PresenceLookupNode : public Node {
public:
V1PresenceLookupNode();
virtual ~V1PresenceLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class ConstantNode : public Node {
public:
ConstantNode(int64_t value);
virtual ~ConstantNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
int64_t value;
};
std::unique_ptr<const Node> parse_expr(std::string_view text);
std::unique_ptr<const Node> root;
};
+513 -513
View File
File diff suppressed because it is too large Load Diff
+25 -25
View File
@@ -1,25 +1,25 @@
#pragma once
#include <stdint.h>
#include <memory>
#include <random>
#include "Client.hh"
#include "ItemData.hh"
#include "ItemParameterTable.hh"
#include "PSOEncryption.hh"
#include "ServerState.hh"
#include "StaticGameData.hh"
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
void apply_mag_feed_result(
ItemData& mag_item,
const ItemData& fed_item,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
uint8_t char_class,
uint8_t section_id,
bool version_has_rare_mags);
#pragma once
#include <stdint.h>
#include <memory>
#include <random>
#include "Client.hh"
#include "ItemData.hh"
#include "ItemParameterTable.hh"
#include "PSOEncryption.hh"
#include "ServerState.hh"
#include "StaticGameData.hh"
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
void apply_mag_feed_result(
ItemData& mag_item,
const ItemData& fed_item,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
uint8_t char_class,
uint8_t section_id,
bool version_has_rare_mags);
+190 -190
View File
@@ -1,190 +1,190 @@
#include "LevelTable.hh"
#include <string.h>
#include <phosg/Filesystem.hh>
#include "Compression.hh"
#include "PSOEncryption.hh"
using namespace std;
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 0;
stats.experience = 0;
stats.char_stats = this->base_stats_for_class(char_class);
}
void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
for (; stats.level < level; stats.level++) {
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1);
// The original code clamps the resulting stat values to [0, max_stat]; we
// don't have max_stat handy so we just allow them to be unbounded
stats.char_stats.atp += level_stats.atp;
stats.char_stats.mst += level_stats.mst;
stats.char_stats.evp += level_stats.evp;
stats.char_stats.hp += level_stats.hp;
stats.char_stats.dfp += level_stats.dfp;
stats.char_stats.ata += level_stats.ata;
// Note: It is not a bug that lck is ignored here; the original code
// ignores it too.
stats.experience = level_stats.experience;
}
}
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
struct Offsets {
// TODO: The overall format of this file on V2 has much more data than we
// actually use. What's known of the structure so far:
le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200]
le_uint32_t unknown_a1; // -> float[6]
le_uint32_t max_stats; // -> PlayerStats[9]
le_uint32_t level_100_stats; // -> Level100Entry[9]
le_uint32_t base_stats; // -> u32[9] -> CharacterStats
le_uint32_t unknown_a2; // -> (0x120 zero bytes)
le_uint32_t attack_data; // -> AttackData[9]
le_uint32_t unknown_a4; // -> (0x14-byte struct)[9]
le_uint32_t unknown_a5; // -> float[9]
le_uint32_t unknown_a6; // -> (0x30 bytes)
le_uint32_t unknown_a7; // -> (0x2D bytes)
le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D]
le_uint32_t unknown_a9; // -> (0x90 bytes)
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
} __packed_ws__(Offsets, 0x40);
StringReader r;
string decompressed_data;
if (compressed) {
decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 9; char_class++) {
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[level];
}
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
this->level_100_stats[char_class] = r.pget<Level100Entry>(offsets.level_100_stats + char_class * sizeof(Level100Entry));
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
}
}
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
return this->level_100_stats.at(char_class);
}
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
return this->max_stats.at(char_class);
}
const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
StringReader r;
string decompressed_data;
if (encrypted) {
auto decrypted = decrypt_pr2_data<true>(data);
decompressed_data = prs_decompress(decrypted.compressed_data);
if (decompressed_data.size() != decrypted.decompressed_size) {
throw runtime_error("decompressed data size does not match expected size");
}
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
// The GC format is very simple (but everything is big-endian):
// root:
// u32 offset:
// u32[12] offsets:
// LevelStatsDeltaBE[200] level_deltas
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
const auto& src_delta = src_deltas[level];
auto& dest_delta = this->level_deltas[char_class][level];
dest_delta.atp = src_delta.atp;
dest_delta.mst = src_delta.mst;
dest_delta.evp = src_delta.evp;
dest_delta.hp = src_delta.hp;
dest_delta.dfp = src_delta.dfp;
dest_delta.ata = src_delta.ata;
dest_delta.lck = src_delta.lck;
dest_delta.tp = src_delta.tp;
dest_delta.experience = src_delta.experience.load();
}
}
}
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
static const array<CharacterStats, 12> data = {
// ATP MST EVP HP DFP ATA LCK
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A},
CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A},
CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
};
return data.at(char_class);
}
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
struct Offsets {
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
} __packed_ws__(Offsets, 8);
StringReader r;
string decompressed_data;
if (compressed) {
decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[level];
}
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
}
}
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
#include "LevelTable.hh"
#include <string.h>
#include <phosg/Filesystem.hh>
#include "Compression.hh"
#include "PSOEncryption.hh"
using namespace std;
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 0;
stats.experience = 0;
stats.char_stats = this->base_stats_for_class(char_class);
}
void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
for (; stats.level < level; stats.level++) {
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1);
// The original code clamps the resulting stat values to [0, max_stat]; we
// don't have max_stat handy so we just allow them to be unbounded
stats.char_stats.atp += level_stats.atp;
stats.char_stats.mst += level_stats.mst;
stats.char_stats.evp += level_stats.evp;
stats.char_stats.hp += level_stats.hp;
stats.char_stats.dfp += level_stats.dfp;
stats.char_stats.ata += level_stats.ata;
// Note: It is not a bug that lck is ignored here; the original code
// ignores it too.
stats.experience = level_stats.experience;
}
}
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
struct Offsets {
// TODO: The overall format of this file on V2 has much more data than we
// actually use. What's known of the structure so far:
le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200]
le_uint32_t unknown_a1; // -> float[6]
le_uint32_t max_stats; // -> PlayerStats[9]
le_uint32_t level_100_stats; // -> Level100Entry[9]
le_uint32_t base_stats; // -> u32[9] -> CharacterStats
le_uint32_t unknown_a2; // -> (0x120 zero bytes)
le_uint32_t attack_data; // -> AttackData[9]
le_uint32_t unknown_a4; // -> (0x14-byte struct)[9]
le_uint32_t unknown_a5; // -> float[9]
le_uint32_t unknown_a6; // -> (0x30 bytes)
le_uint32_t unknown_a7; // -> (0x2D bytes)
le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D]
le_uint32_t unknown_a9; // -> (0x90 bytes)
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
} __packed_ws__(Offsets, 0x40);
StringReader r;
string decompressed_data;
if (compressed) {
decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 9; char_class++) {
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[level];
}
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
this->level_100_stats[char_class] = r.pget<Level100Entry>(offsets.level_100_stats + char_class * sizeof(Level100Entry));
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
}
}
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
return this->level_100_stats.at(char_class);
}
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
return this->max_stats.at(char_class);
}
const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
StringReader r;
string decompressed_data;
if (encrypted) {
auto decrypted = decrypt_pr2_data<true>(data);
decompressed_data = prs_decompress(decrypted.compressed_data);
if (decompressed_data.size() != decrypted.decompressed_size) {
throw runtime_error("decompressed data size does not match expected size");
}
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
// The GC format is very simple (but everything is big-endian):
// root:
// u32 offset:
// u32[12] offsets:
// LevelStatsDeltaBE[200] level_deltas
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
const auto& src_delta = src_deltas[level];
auto& dest_delta = this->level_deltas[char_class][level];
dest_delta.atp = src_delta.atp;
dest_delta.mst = src_delta.mst;
dest_delta.evp = src_delta.evp;
dest_delta.hp = src_delta.hp;
dest_delta.dfp = src_delta.dfp;
dest_delta.ata = src_delta.ata;
dest_delta.lck = src_delta.lck;
dest_delta.tp = src_delta.tp;
dest_delta.experience = src_delta.experience.load();
}
}
}
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
static const array<CharacterStats, 12> data = {
// ATP MST EVP HP DFP ATA LCK
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A},
CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A},
CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
};
return data.at(char_class);
}
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
struct Offsets {
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
} __packed_ws__(Offsets, 8);
StringReader r;
string decompressed_data;
if (compressed) {
decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data);
} else {
r = StringReader(data);
}
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[level];
}
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
}
}
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class);
}
const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level);
}
+173 -173
View File
@@ -1,173 +1,173 @@
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <string>
#include "Text.hh"
class LevelTable;
template <bool IsBigEndian>
struct CharacterStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ U16T atp = 0;
/* 02 */ U16T mst = 0;
/* 04 */ U16T evp = 0;
/* 06 */ U16T hp = 0;
/* 08 */ U16T dfp = 0;
/* 0A */ U16T ata = 0;
/* 0C */ U16T lck = 0;
/* 0E */
operator CharacterStatsT<!IsBigEndian>() const {
CharacterStatsT<!IsBigEndian> ret;
ret.atp = this->atp.load();
ret.mst = this->mst.load();
ret.evp = this->evp.load();
ret.hp = this->hp.load();
ret.dfp = this->dfp.load();
ret.ata = this->ata.load();
ret.lck = this->lck.load();
return ret;
}
} __packed__;
using CharacterStats = CharacterStatsT<false>;
using CharacterStatsBE = CharacterStatsT<true>;
check_struct_size(CharacterStats, 0x0E);
check_struct_size(CharacterStatsBE, 0x0E);
template <bool IsBigEndian>
struct PlayerStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
/* 00 */ CharacterStatsT<IsBigEndian> char_stats;
/* 0E */ U16T esp = 0;
/* 10 */ F32T height = 0.0;
/* 14 */ F32T unknown_a3 = 0.0;
/* 18 */ U32T level = 0;
/* 1C */ U32T experience = 0;
/* 20 */ U32T meseta = 0;
/* 24 */
operator PlayerStatsT<!IsBigEndian>() const {
PlayerStatsT<!IsBigEndian> ret;
ret.char_stats = this->char_stats;
ret.esp = this->esp.load();
ret.height = this->height.load();
ret.unknown_a3 = this->unknown_a3.load();
ret.level = this->level.load();
ret.experience = this->experience.load();
ret.meseta = this->meseta.load();
return ret;
}
} __packed__;
using PlayerStats = PlayerStatsT<false>;
using PlayerStatsBE = PlayerStatsT<true>;
check_struct_size(PlayerStats, 0x24);
check_struct_size(PlayerStatsBE, 0x24);
template <bool IsBigEndian>
struct LevelStatsDeltaT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ uint8_t atp;
/* 01 */ uint8_t mst;
/* 02 */ uint8_t evp;
/* 03 */ uint8_t hp;
/* 04 */ uint8_t dfp;
/* 05 */ uint8_t ata;
/* 06 */ uint8_t lck;
/* 07 */ uint8_t tp;
/* 08 */ U32T experience;
/* 0C */
void apply(CharacterStats& ps) const {
ps.ata += this->ata;
ps.atp += this->atp;
ps.dfp += this->dfp;
ps.evp += this->evp;
ps.hp += this->hp;
ps.mst += this->mst;
ps.lck += this->lck;
}
} __packed__;
using LevelStatsDelta = LevelStatsDeltaT<false>;
using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
check_struct_size(LevelStatsDelta, 0x0C);
check_struct_size(LevelStatsDeltaBE, 0x0C);
class LevelTable {
// This is the base class for all the LevelTable implementations. The public
// interface here only defines functions that the server needs to handle
// requests, but some subclasses implement more functionality. See the
// comments and Offsets structures inside the subclasses' constructor
// implementations for more details on the file formats.
public:
virtual ~LevelTable() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
protected:
LevelTable() = default;
};
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
public:
struct Level100Entry {
/* 00 */ CharacterStats char_stats;
/* 0E */ le_uint16_t unknown_a1 = 0;
/* 10 */ le_float height = 0.0;
/* 14 */ le_float unknown_a3 = 0.0;
/* 18 */ le_uint32_t level = 0;
/* 1C */
} __packed_ws__(Level100Entry, 0x1C);
LevelTableV2(const std::string& data, bool compressed);
virtual ~LevelTableV2() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
const Level100Entry& level_100_stats_for_class(uint8_t char_class) const;
const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<CharacterStats, 9> base_stats;
std::array<Level100Entry, 9> level_100_stats;
std::array<PlayerStats, 9> max_stats;
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
};
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
public:
LevelTableV3BE(const std::string& data, bool encrypted);
virtual ~LevelTableV3BE() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
public:
LevelTableV4(const std::string& data, bool compressed);
virtual ~LevelTableV4() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<CharacterStats, 12> base_stats;
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <string>
#include "Text.hh"
class LevelTable;
template <bool IsBigEndian>
struct CharacterStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ U16T atp = 0;
/* 02 */ U16T mst = 0;
/* 04 */ U16T evp = 0;
/* 06 */ U16T hp = 0;
/* 08 */ U16T dfp = 0;
/* 0A */ U16T ata = 0;
/* 0C */ U16T lck = 0;
/* 0E */
operator CharacterStatsT<!IsBigEndian>() const {
CharacterStatsT<!IsBigEndian> ret;
ret.atp = this->atp.load();
ret.mst = this->mst.load();
ret.evp = this->evp.load();
ret.hp = this->hp.load();
ret.dfp = this->dfp.load();
ret.ata = this->ata.load();
ret.lck = this->lck.load();
return ret;
}
} __packed__;
using CharacterStats = CharacterStatsT<false>;
using CharacterStatsBE = CharacterStatsT<true>;
check_struct_size(CharacterStats, 0x0E);
check_struct_size(CharacterStatsBE, 0x0E);
template <bool IsBigEndian>
struct PlayerStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
/* 00 */ CharacterStatsT<IsBigEndian> char_stats;
/* 0E */ U16T esp = 0;
/* 10 */ F32T height = 0.0;
/* 14 */ F32T unknown_a3 = 0.0;
/* 18 */ U32T level = 0;
/* 1C */ U32T experience = 0;
/* 20 */ U32T meseta = 0;
/* 24 */
operator PlayerStatsT<!IsBigEndian>() const {
PlayerStatsT<!IsBigEndian> ret;
ret.char_stats = this->char_stats;
ret.esp = this->esp.load();
ret.height = this->height.load();
ret.unknown_a3 = this->unknown_a3.load();
ret.level = this->level.load();
ret.experience = this->experience.load();
ret.meseta = this->meseta.load();
return ret;
}
} __packed__;
using PlayerStats = PlayerStatsT<false>;
using PlayerStatsBE = PlayerStatsT<true>;
check_struct_size(PlayerStats, 0x24);
check_struct_size(PlayerStatsBE, 0x24);
template <bool IsBigEndian>
struct LevelStatsDeltaT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ uint8_t atp;
/* 01 */ uint8_t mst;
/* 02 */ uint8_t evp;
/* 03 */ uint8_t hp;
/* 04 */ uint8_t dfp;
/* 05 */ uint8_t ata;
/* 06 */ uint8_t lck;
/* 07 */ uint8_t tp;
/* 08 */ U32T experience;
/* 0C */
void apply(CharacterStats& ps) const {
ps.ata += this->ata;
ps.atp += this->atp;
ps.dfp += this->dfp;
ps.evp += this->evp;
ps.hp += this->hp;
ps.mst += this->mst;
ps.lck += this->lck;
}
} __packed__;
using LevelStatsDelta = LevelStatsDeltaT<false>;
using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
check_struct_size(LevelStatsDelta, 0x0C);
check_struct_size(LevelStatsDeltaBE, 0x0C);
class LevelTable {
// This is the base class for all the LevelTable implementations. The public
// interface here only defines functions that the server needs to handle
// requests, but some subclasses implement more functionality. See the
// comments and Offsets structures inside the subclasses' constructor
// implementations for more details on the file formats.
public:
virtual ~LevelTable() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
protected:
LevelTable() = default;
};
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
public:
struct Level100Entry {
/* 00 */ CharacterStats char_stats;
/* 0E */ le_uint16_t unknown_a1 = 0;
/* 10 */ le_float height = 0.0;
/* 14 */ le_float unknown_a3 = 0.0;
/* 18 */ le_uint32_t level = 0;
/* 1C */
} __packed_ws__(Level100Entry, 0x1C);
LevelTableV2(const std::string& data, bool compressed);
virtual ~LevelTableV2() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
const Level100Entry& level_100_stats_for_class(uint8_t char_class) const;
const PlayerStats& max_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<CharacterStats, 9> base_stats;
std::array<Level100Entry, 9> level_100_stats;
std::array<PlayerStats, 9> max_stats;
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
};
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
public:
LevelTableV3BE(const std::string& data, bool encrypted);
virtual ~LevelTableV3BE() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
public:
LevelTableV4(const std::string& data, bool compressed);
virtual ~LevelTableV4() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private:
std::array<CharacterStats, 12> base_stats;
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
};
+1066 -1066
View File
File diff suppressed because it is too large Load Diff
+322 -322
View File
@@ -1,322 +1,322 @@
#pragma once
#include <event2/event.h>
#include <inttypes.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <random>
#include <string>
#include <unordered_map>
#include <vector>
#include "Client.hh"
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
#include "Episode3/Server.hh"
#include "ItemCreator.hh"
#include "Map.hh"
#include "Quest.hh"
#include "StaticGameData.hh"
#include "Text.hh"
struct ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem {
ItemData data;
float x;
float z;
uint64_t drop_number;
// The low 12 bits of flags are visibility flags, specifying which clients
// can see the item. (In practice, only the lowest 4 of these bits are used,
// but the game has fields for 12 players so we do too.)
// The 13th bit (0x1000) specifies whether a rare item notification should
// be sent to all players when the item is picked up. This has no effect for
// non-rare items.
uint16_t flags;
bool visible_to_client(uint8_t client_id) const;
};
struct FloorItemManager {
PrefixedLogger log;
uint64_t next_drop_number;
// It's important that this is a map and not an unordered_map. See the
// comment in send_game_item_state for more details.
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
FloorItemManager(uint32_t lobby_id, uint8_t floor);
~FloorItemManager() = default;
bool exists(uint32_t item_id) const;
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
void add(const ItemData& item, float x, float z, uint16_t flags);
void add(std::shared_ptr<FloorItem> fi);
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
std::unordered_set<std::shared_ptr<FloorItem>> evict();
void clear_inaccessible(uint16_t remaining_clients_mask);
void clear_private();
void clear();
uint32_t reassign_all_item_ids(uint32_t next_item_id);
};
enum class Flag {
// clang-format off
GAME = 0x00000001,
PERSISTENT = 0x00000002,
// Flags used only for games
CHEATS_ENABLED = 0x00000100,
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
QUEST_IN_PROGRESS = 0x00000400,
BATTLE_IN_PROGRESS = 0x00000800,
JOINABLE_QUEST_IN_PROGRESS = 0x00001000,
IS_CLIENT_CUSTOMIZATION = 0x00002000,
IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also
SPECTATORS_FORBIDDEN = 0x00008000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000,
CANNOT_CHANGE_CHEAT_MODE = 0x00020000,
USE_CREATOR_SECTION_ID = 0x00040000,
// Flags used only for lobbies
PUBLIC = 0x01000000,
DEFAULT = 0x02000000,
IS_OVERFLOW = 0x08000000,
// clang-format on
};
enum class DropMode {
DISABLED = 0,
CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2,
SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4,
};
std::weak_ptr<ServerState>
server_state;
PrefixedLogger log;
uint32_t lobby_id;
uint32_t min_level;
uint32_t max_level;
// Game state
std::array<uint32_t, 12> next_item_id_for_client;
uint32_t next_game_item_id;
std::vector<FloorItemManager> floor_item_managers;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
std::shared_ptr<Map> map;
parray<le_uint32_t, 0x20> variations;
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
std::unique_ptr<QuestFlags> quest_flag_values;
std::unique_ptr<SwitchFlags> switch_flags;
// Game config
Version base_version;
// Bits in allowed_versions specify who is allowed to join this game. The
// bits are indexed as (1 << version), where version is a value from the
// Version enum.
uint16_t allowed_versions;
uint8_t creator_section_id;
uint8_t override_section_id;
Episode episode;
GameMode mode;
uint8_t difficulty; // 0-3
uint16_t base_exp_multiplier;
float exp_share_multiplier;
float challenge_exp_multiplier;
std::string password;
std::string name;
// This seed is also sent to the client for rare enemy generation
uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
uint8_t allowed_drop_modes;
DropMode drop_mode;
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;
};
std::array<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 episode
// set to EP3; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
// 1. Primary games. These are the lobbies where battles may take place.
// 2. Watcher games. These lobbies receive all the battle and chat commands
// from a primary game. (This the implementation of spectator teams.)
// 3. Replay games. These lobbies replay a sequence of battle commands and
// chat commands from a previous primary game.
// Types 2 and 3 may be distinguished by the presence of the battle_record
// field - in replay games, it will be present; in watcher games it will be
// absent.
std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games
std::weak_ptr<Lobby> watched_lobby; // Only used in watcher games
std::unordered_set<std::shared_ptr<Lobby>> watcher_lobbies; // Only used in primary games
std::shared_ptr<Episode3::BattleRecord> battle_record; // Not used in watcher games
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
std::shared_ptr<Episode3::Tournament::Match> tournament_match;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
// Lobby stuff
uint8_t event;
uint8_t block;
uint8_t leader_id;
uint8_t max_clients;
uint32_t enabled_flags;
std::shared_ptr<const Quest> quest;
std::array<std::shared_ptr<Client>, 12> clients;
// Keys in this map are client_id
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
// is not zero
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
Lobby(const Lobby&) = delete;
Lobby(Lobby&&) = delete;
~Lobby();
Lobby& operator=(const Lobby&) = delete;
Lobby& operator=(Lobby&&) = delete;
void reset_next_item_ids();
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->enabled_flags & static_cast<uint32_t>(flag));
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint32_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint32_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint32_t>(flag);
}
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
void set_drop_mode(DropMode new_mode);
void create_item_creator();
void change_section_id();
uint8_t effective_section_id() const;
static std::shared_ptr<Map> load_maps(
Version version,
Episode episode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
static std::shared_ptr<Map> load_maps(
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::shared_ptr<const SetDataTableBase> sdt,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const parray<le_uint32_t, 0x20>& variations,
const PrefixedLogger* log = nullptr);
static std::shared_ptr<Map> load_maps(
const std::vector<std::string>& enemy_filenames,
const std::vector<std::string>& object_filenames,
const std::vector<std::string>& event_filenames,
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const PrefixedLogger* log = nullptr);
void load_maps();
void create_ep3_server();
[[nodiscard]] inline bool is_game() const {
return this->check_flag(Flag::GAME);
}
[[nodiscard]] inline bool is_ep3() const {
return this->episode == Episode::EP3;
}
[[nodiscard]] inline bool version_is_allowed(Version v) const {
return this->allowed_versions & (1 << static_cast<size_t>(v));
}
inline void allow_version(Version v) {
this->allowed_versions |= (1 << static_cast<size_t>(v));
}
void reassign_leader_on_client_departure(size_t leaving_client_id);
size_t count_clients() const;
bool any_v1_clients_present() const;
bool any_client_loading() const;
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
void remove_client(std::shared_ptr<Client> c);
void move_client_to_lobby(
std::shared_ptr<Lobby> dest_lobby,
std::shared_ptr<Client> c,
ssize_t required_client_id = -1);
std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0);
enum class JoinError {
ALLOWED = 0,
FULL,
VERSION_CONFLICT,
QUEST_SELECTION_IN_PROGRESS,
QUEST_IN_PROGRESS,
BATTLE_IN_PROGRESS,
LOADING,
SOLO,
INCORRECT_PASSWORD,
LEVEL_TOO_LOW,
LEVEL_TOO_HIGH,
NO_ACCESS_TO_QUEST,
};
JoinError join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const;
bool item_exists(uint8_t floor, uint32_t item_id) const;
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
void evict_items_from_floor(uint8_t floor);
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
uint32_t generate_item_id(uint8_t client_id);
void on_item_id_generated_externally(uint32_t item_id);
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
QuestIndex::IncludeCondition quest_include_condition() const;
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
};
template <>
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
template <>
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
#pragma once
#include <event2/event.h>
#include <inttypes.h>
#include <array>
#include <memory>
#include <phosg/Encoding.hh>
#include <random>
#include <string>
#include <unordered_map>
#include <vector>
#include "Client.hh"
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
#include "Episode3/Server.hh"
#include "ItemCreator.hh"
#include "Map.hh"
#include "Quest.hh"
#include "StaticGameData.hh"
#include "Text.hh"
struct ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem {
ItemData data;
float x;
float z;
uint64_t drop_number;
// The low 12 bits of flags are visibility flags, specifying which clients
// can see the item. (In practice, only the lowest 4 of these bits are used,
// but the game has fields for 12 players so we do too.)
// The 13th bit (0x1000) specifies whether a rare item notification should
// be sent to all players when the item is picked up. This has no effect for
// non-rare items.
uint16_t flags;
bool visible_to_client(uint8_t client_id) const;
};
struct FloorItemManager {
PrefixedLogger log;
uint64_t next_drop_number;
// It's important that this is a map and not an unordered_map. See the
// comment in send_game_item_state for more details.
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
FloorItemManager(uint32_t lobby_id, uint8_t floor);
~FloorItemManager() = default;
bool exists(uint32_t item_id) const;
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
void add(const ItemData& item, float x, float z, uint16_t flags);
void add(std::shared_ptr<FloorItem> fi);
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
std::unordered_set<std::shared_ptr<FloorItem>> evict();
void clear_inaccessible(uint16_t remaining_clients_mask);
void clear_private();
void clear();
uint32_t reassign_all_item_ids(uint32_t next_item_id);
};
enum class Flag {
// clang-format off
GAME = 0x00000001,
PERSISTENT = 0x00000002,
// Flags used only for games
CHEATS_ENABLED = 0x00000100,
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
QUEST_IN_PROGRESS = 0x00000400,
BATTLE_IN_PROGRESS = 0x00000800,
JOINABLE_QUEST_IN_PROGRESS = 0x00001000,
IS_CLIENT_CUSTOMIZATION = 0x00002000,
IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also
SPECTATORS_FORBIDDEN = 0x00008000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000,
CANNOT_CHANGE_CHEAT_MODE = 0x00020000,
USE_CREATOR_SECTION_ID = 0x00040000,
// Flags used only for lobbies
PUBLIC = 0x01000000,
DEFAULT = 0x02000000,
IS_OVERFLOW = 0x08000000,
// clang-format on
};
enum class DropMode {
DISABLED = 0,
CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2,
SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4,
};
std::weak_ptr<ServerState>
server_state;
PrefixedLogger log;
uint32_t lobby_id;
uint32_t min_level;
uint32_t max_level;
// Game state
std::array<uint32_t, 12> next_item_id_for_client;
uint32_t next_game_item_id;
std::vector<FloorItemManager> floor_item_managers;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
std::shared_ptr<Map> map;
parray<le_uint32_t, 0x20> variations;
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
std::unique_ptr<QuestFlags> quest_flag_values;
std::unique_ptr<SwitchFlags> switch_flags;
// Game config
Version base_version;
// Bits in allowed_versions specify who is allowed to join this game. The
// bits are indexed as (1 << version), where version is a value from the
// Version enum.
uint16_t allowed_versions;
uint8_t creator_section_id;
uint8_t override_section_id;
Episode episode;
GameMode mode;
uint8_t difficulty; // 0-3
uint16_t base_exp_multiplier;
float exp_share_multiplier;
float challenge_exp_multiplier;
std::string password;
std::string name;
// This seed is also sent to the client for rare enemy generation
uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
uint8_t allowed_drop_modes;
DropMode drop_mode;
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;
};
std::array<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 episode
// set to EP3; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
// 1. Primary games. These are the lobbies where battles may take place.
// 2. Watcher games. These lobbies receive all the battle and chat commands
// from a primary game. (This the implementation of spectator teams.)
// 3. Replay games. These lobbies replay a sequence of battle commands and
// chat commands from a previous primary game.
// Types 2 and 3 may be distinguished by the presence of the battle_record
// field - in replay games, it will be present; in watcher games it will be
// absent.
std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games
std::weak_ptr<Lobby> watched_lobby; // Only used in watcher games
std::unordered_set<std::shared_ptr<Lobby>> watcher_lobbies; // Only used in primary games
std::shared_ptr<Episode3::BattleRecord> battle_record; // Not used in watcher games
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
std::shared_ptr<Episode3::Tournament::Match> tournament_match;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
// Lobby stuff
uint8_t event;
uint8_t block;
uint8_t leader_id;
uint8_t max_clients;
uint32_t enabled_flags;
std::shared_ptr<const Quest> quest;
std::array<std::shared_ptr<Client>, 12> clients;
// Keys in this map are client_id
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
// is not zero
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
Lobby(const Lobby&) = delete;
Lobby(Lobby&&) = delete;
~Lobby();
Lobby& operator=(const Lobby&) = delete;
Lobby& operator=(Lobby&&) = delete;
void reset_next_item_ids();
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->enabled_flags & static_cast<uint32_t>(flag));
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint32_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint32_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint32_t>(flag);
}
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
void set_drop_mode(DropMode new_mode);
void create_item_creator();
void change_section_id();
uint8_t effective_section_id() const;
static std::shared_ptr<Map> load_maps(
Version version,
Episode episode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
static std::shared_ptr<Map> load_maps(
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::shared_ptr<const SetDataTableBase> sdt,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const parray<le_uint32_t, 0x20>& variations,
const PrefixedLogger* log = nullptr);
static std::shared_ptr<Map> load_maps(
const std::vector<std::string>& enemy_filenames,
const std::vector<std::string>& object_filenames,
const std::vector<std::string>& event_filenames,
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const PrefixedLogger* log = nullptr);
void load_maps();
void create_ep3_server();
[[nodiscard]] inline bool is_game() const {
return this->check_flag(Flag::GAME);
}
[[nodiscard]] inline bool is_ep3() const {
return this->episode == Episode::EP3;
}
[[nodiscard]] inline bool version_is_allowed(Version v) const {
return this->allowed_versions & (1 << static_cast<size_t>(v));
}
inline void allow_version(Version v) {
this->allowed_versions |= (1 << static_cast<size_t>(v));
}
void reassign_leader_on_client_departure(size_t leaving_client_id);
size_t count_clients() const;
bool any_v1_clients_present() const;
bool any_client_loading() const;
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
void remove_client(std::shared_ptr<Client> c);
void move_client_to_lobby(
std::shared_ptr<Lobby> dest_lobby,
std::shared_ptr<Client> c,
ssize_t required_client_id = -1);
std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0);
enum class JoinError {
ALLOWED = 0,
FULL,
VERSION_CONFLICT,
QUEST_SELECTION_IN_PROGRESS,
QUEST_IN_PROGRESS,
BATTLE_IN_PROGRESS,
LOADING,
SOLO,
INCORRECT_PASSWORD,
LEVEL_TOO_LOW,
LEVEL_TOO_HIGH,
NO_ACCESS_TO_QUEST,
};
JoinError join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const;
bool item_exists(uint8_t floor, uint32_t item_id) const;
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
void evict_items_from_floor(uint8_t floor);
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
uint32_t generate_item_id(uint8_t client_id);
void on_item_id_generated_externally(uint32_t item_id);
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
QuestIndex::IncludeCondition quest_include_condition() const;
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
};
template <>
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
template <>
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
+48 -48
View File
@@ -1,48 +1,48 @@
#include "Loggers.hh"
#include <phosg/Strings.hh>
using namespace std;
PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT);
PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT);
PrefixedLogger client_log("", LogLevel::USE_DEFAULT);
PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT);
PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
static void set_log_level_from_json(
PrefixedLogger& log, const JSON& d, const char* json_key) {
try {
string name = toupper(d.at(json_key).as_string());
log.min_level = enum_for_name<LogLevel>(name.c_str());
} catch (const out_of_range&) {
}
}
void set_log_levels_from_json(const JSON& json) {
set_log_level_from_json(ax_messages_log, json, "AXMessages");
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
set_log_level_from_json(client_log, json, "Clients");
set_log_level_from_json(command_data_log, json, "CommandData");
set_log_level_from_json(config_log, json, "Config");
set_log_level_from_json(dns_server_log, json, "DNSServer");
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
set_log_level_from_json(lobby_log, json, "Lobbies");
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
set_log_level_from_json(player_data_log, json, "PlayerData");
set_log_level_from_json(proxy_server_log, json, "ProxyServer");
set_log_level_from_json(replay_log, json, "Replay");
set_log_level_from_json(server_log, json, "GameServer");
set_log_level_from_json(static_game_data_log, json, "StaticGameData");
}
#include "Loggers.hh"
#include <phosg/Strings.hh>
using namespace std;
PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT);
PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT);
PrefixedLogger client_log("", LogLevel::USE_DEFAULT);
PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT);
PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
static void set_log_level_from_json(
PrefixedLogger& log, const JSON& d, const char* json_key) {
try {
string name = toupper(d.at(json_key).as_string());
log.min_level = enum_for_name<LogLevel>(name.c_str());
} catch (const out_of_range&) {
}
}
void set_log_levels_from_json(const JSON& json) {
set_log_level_from_json(ax_messages_log, json, "AXMessages");
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
set_log_level_from_json(client_log, json, "Clients");
set_log_level_from_json(command_data_log, json, "CommandData");
set_log_level_from_json(config_log, json, "Config");
set_log_level_from_json(dns_server_log, json, "DNSServer");
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
set_log_level_from_json(lobby_log, json, "Lobbies");
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
set_log_level_from_json(player_data_log, json, "PlayerData");
set_log_level_from_json(proxy_server_log, json, "ProxyServer");
set_log_level_from_json(replay_log, json, "Replay");
set_log_level_from_json(server_log, json, "GameServer");
set_log_level_from_json(static_game_data_log, json, "StaticGameData");
}
+22 -22
View File
@@ -1,22 +1,22 @@
#pragma once
#include <phosg/JSON.hh>
#include <phosg/Strings.hh>
extern PrefixedLogger ax_messages_log;
extern PrefixedLogger channel_exceptions_log;
extern PrefixedLogger client_log;
extern PrefixedLogger command_data_log;
extern PrefixedLogger config_log;
extern PrefixedLogger dns_server_log;
extern PrefixedLogger function_compiler_log;
extern PrefixedLogger ip_stack_simulator_log;
extern PrefixedLogger lobby_log;
extern PrefixedLogger patch_index_log;
extern PrefixedLogger player_data_log;
extern PrefixedLogger proxy_server_log;
extern PrefixedLogger replay_log;
extern PrefixedLogger server_log;
extern PrefixedLogger static_game_data_log;
void set_log_levels_from_json(const JSON& json);
#pragma once
#include <phosg/JSON.hh>
#include <phosg/Strings.hh>
extern PrefixedLogger ax_messages_log;
extern PrefixedLogger channel_exceptions_log;
extern PrefixedLogger client_log;
extern PrefixedLogger command_data_log;
extern PrefixedLogger config_log;
extern PrefixedLogger dns_server_log;
extern PrefixedLogger function_compiler_log;
extern PrefixedLogger ip_stack_simulator_log;
extern PrefixedLogger lobby_log;
extern PrefixedLogger patch_index_log;
extern PrefixedLogger player_data_log;
extern PrefixedLogger proxy_server_log;
extern PrefixedLogger replay_log;
extern PrefixedLogger server_log;
extern PrefixedLogger static_game_data_log;
void set_log_levels_from_json(const JSON& json);
+2820 -2820
View File
File diff suppressed because it is too large Load Diff
+2567 -2567
View File
File diff suppressed because it is too large Load Diff
+494 -494
View File
@@ -1,494 +1,494 @@
#pragma once
#include <inttypes.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include <vector>
#include "BattleParamsIndex.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
#include "Text.hh"
struct Map {
static const char* name_for_object_type(uint16_t type);
struct SectionHeader {
enum class Type {
END = 0,
OBJECTS = 1,
ENEMIES = 2,
WAVE_EVENTS = 3,
RANDOM_ENEMY_LOCATIONS = 4,
RANDOM_ENEMY_DEFINITIONS = 5,
};
le_uint32_t le_type;
le_uint32_t section_size; // Includes this header
le_uint32_t floor;
le_uint32_t data_size;
inline Type type() const {
return static_cast<Type>(this->le_type.load());
}
} __packed_ws__(SectionHeader, 0x10);
struct ObjectEntry { // Section type 1 (OBJECTS)
/* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t unknown_a2;
/* 08 */ le_uint16_t entity_id; // == index + 0x4000
/* 0A */ le_uint16_t group;
/* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t unknown_a3;
/* 10 */ le_float x;
/* 14 */ le_float y;
/* 18 */ le_float z;
/* 1C */ le_uint32_t x_angle;
/* 20 */ le_uint32_t y_angle;
/* 24 */ le_uint32_t z_angle;
/* 28 */ le_float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
/* 2C */ le_float param2;
/* 30 */ le_float param3;
/* 34 */ le_uint32_t param4;
/* 38 */ le_uint32_t param5;
/* 3C */ le_uint32_t param6;
/* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 44 */
std::string str() const;
} __packed_ws__(ObjectEntry, 0x44);
struct EnemyEntry { // Section type 2 (ENEMIES)
/* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t num_children;
/* 08 */ le_uint16_t floor;
/* 0A */ le_uint16_t entity_id; // == index + 0x1000
/* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t wave_number;
/* 10 */ le_uint16_t wave_number2;
/* 12 */ le_uint16_t unknown_a1;
/* 14 */ le_float x;
/* 18 */ le_float y;
/* 1C */ le_float z;
/* 20 */ le_uint32_t x_angle;
/* 24 */ le_uint32_t y_angle;
/* 28 */ le_uint32_t z_angle;
/* 2C */ le_float fparam1;
/* 30 */ le_float fparam2;
/* 34 */ le_float fparam3;
/* 38 */ le_float fparam4;
/* 3C */ le_float fparam5;
/* 40 */ le_uint16_t uparam1;
/* 42 */ le_uint16_t uparam2;
/* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 48 */
std::string str() const;
} __packed_ws__(EnemyEntry, 0x48);
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
/* 00 */ le_uint32_t action_stream_offset;
/* 04 */ le_uint32_t entries_offset;
/* 08 */ le_uint32_t entry_count;
/* 0C */ be_uint32_t format; // 0 or 'evt2'
/* 10 */
} __packed_ws__(EventsSectionHeader, 0x10);
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
/* 00 */ le_uint32_t event_id;
// Bits in flags:
// 0004 = is active
// 0008 = post-wave actions have been run
// 0010 = all enemies killed
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint32_t delay;
/* 10 */ le_uint32_t action_stream_offset;
/* 14 */
} __packed_ws__(Event1Entry, 0x14);
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
/* 00 */ le_uint32_t event_id;
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint16_t min_delay;
/* 0E */ le_uint16_t max_delay;
/* 10 */ uint8_t min_enemies;
/* 11 */ uint8_t max_enemies;
/* 12 */ le_uint16_t max_waves;
/* 14 */ le_uint32_t action_stream_offset;
/* 18 */
} __packed_ws__(Event2Entry, 0x18);
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct
/* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
/* 08 */ le_uint32_t num_sections;
/* 0C */
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint16_t section;
/* 02 */ le_uint16_t count;
/* 04 */ le_uint32_t offset;
/* 08 */
} __packed_ws__(RandomEnemyLocationSection, 8);
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_float x;
/* 04 */ le_float y;
/* 08 */ le_float z;
/* 0C */ le_uint32_t x_angle;
/* 10 */ le_uint32_t y_angle;
/* 14 */ le_uint32_t z_angle;
/* 18 */ uint16_t unknown_a9;
/* 1A */ uint16_t unknown_a10;
/* 1C */
} __packed_ws__(RandomEnemyLocationEntry, 0x1C);
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct
/* 04 */ le_uint32_t weight_entries_offset; // Offset to RandomEnemyDefinitionWeights structs, from start of this struct
/* 08 */ le_uint32_t entry_count;
/* 0C */ le_uint32_t weight_entry_count;
/* 10 */
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
// All fields through entry_num map to the corresponding fields in
// EnemyEntry. Note that the order of the uparam fields is switched!
/* 00 */ le_float fparam1;
/* 04 */ le_float fparam2;
/* 08 */ le_float fparam3;
/* 0C */ le_float fparam4;
/* 10 */ le_float fparam5;
/* 14 */ le_uint16_t uparam2;
/* 16 */ le_uint16_t uparam1;
/* 18 */ le_uint32_t entry_num;
/* 1C */ le_uint16_t min_children;
/* 1E */ le_uint16_t max_children;
/* 20 */
} __packed_ws__(RandomEnemyDefinition, 0x20);
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ uint8_t base_type_index;
/* 01 */ uint8_t definition_entry_num;
/* 02 */ uint8_t weight;
/* 03 */ uint8_t unknown_a4;
/* 04 */
} __packed_ws__(RandomEnemyWeight, 4);
struct RareEnemyRates {
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
uint32_t nar_lily; // POISON_LILY -> NAR_LILY
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
explicit RareEnemyRates(const JSON& json);
JSON json() const;
};
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
struct Object {
// TODO: Add more fields in here if we ever care about them. Currently we
// only care about boxes with fixed item drops.
size_t source_index;
uint8_t floor;
uint16_t object_id;
uint16_t base_type;
uint16_t section;
uint16_t group;
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
float param3; // If == 0, the item should be varied by difficulty and area
uint32_t param4;
uint32_t param5;
uint32_t param6;
uint16_t game_flags;
// Technically set_flags shouldn't be part of the Object struct, but all
// object entries always generate exactly one object, so we store it here.
uint16_t set_flags;
bool item_drop_checked;
std::string str() const;
};
struct Enemy {
enum Flag {
LAST_HIT_MASK = 0x03,
EXP_GIVEN = 0x04,
ITEM_DROPPED = 0x08,
ALL_HITS_MASK_FIRST = 0x10,
ALL_HITS_MASK = 0xF0,
};
size_t source_index;
size_t set_index;
uint16_t enemy_id;
uint16_t total_damage;
uint32_t game_flags; // From 6x0A
uint16_t section;
uint16_t wave_number;
EnemyType type;
uint8_t floor;
uint8_t server_flags;
Enemy(
uint16_t enemy_id,
size_t source_index,
size_t set_index,
uint8_t floor,
uint16_t section,
uint16_t wave_number,
EnemyType type);
std::string str() const;
inline bool ever_hit_by_client_id(uint8_t client_id) const {
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
}
inline bool last_hit_by_client_id(uint8_t client_id) const {
return (this->server_flags & Flag::LAST_HIT_MASK) == client_id;
}
inline void set_last_hit_by_client_id(uint8_t client_id) {
this->server_flags = (this->server_flags & (~Flag::LAST_HIT_MASK)) | (Flag::ALL_HITS_MASK_FIRST << client_id) | (client_id & 3);
}
};
struct Event {
uint32_t event_id;
uint16_t flags;
uint16_t section;
uint16_t wave_number;
uint8_t floor;
uint32_t action_stream_offset;
std::vector<size_t> enemy_indexes;
std::string str() const;
};
struct DATParserRandomState {
PSOV2Encryption random;
PSOV2Encryption location_table_random;
std::array<uint32_t, 0x20> location_index_table;
uint32_t location_indexes_populated;
uint32_t location_indexes_used;
uint32_t location_entries_base_offset;
DATParserRandomState(uint32_t rare_seed);
size_t rand_int_biased(size_t min_v, size_t max_v);
uint32_t next_location_index();
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
};
Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
~Map() = default;
void clear();
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size);
bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
void add_enemy(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
size_t index,
const EnemyEntry& e,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
const void* data,
size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_random_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
StringReader wave_events_r,
StringReader random_enemy_locations_r,
StringReader random_enemy_definitions_r,
std::shared_ptr<DATParserRandomState> random_state,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_event(
uint32_t event_id,
uint16_t flags,
uint8_t floor,
uint16_t section,
uint16_t wave_number,
uint32_t action_stream_offset);
std::vector<Event*> get_events(uint8_t floor, uint32_t event_id);
std::vector<const Event*> get_events(uint8_t floor, uint32_t event_id) const;
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
struct DATSectionsForFloor {
uint32_t objects = 0xFFFFFFFF;
uint32_t enemies = 0xFFFFFFFF;
uint32_t wave_events = 0xFFFFFFFF;
uint32_t random_enemy_locations = 0xFFFFFFFF;
uint32_t random_enemy_definitions = 0xFFFFFFFF;
};
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
void add_entities_from_quest_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
const void* data,
size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
Enemy& find_enemy(uint8_t floor, EnemyType type);
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Event*> get_events(uint8_t floor);
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
static std::string disassemble_quest_data(const void* data, size_t size);
PrefixedLogger log;
Version version;
uint32_t rare_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::vector<Object> objects;
std::vector<Enemy> enemies;
std::vector<uint16_t> enemy_set_flags;
std::vector<size_t> rare_enemy_indexes;
std::vector<Event> events;
std::string event_action_stream;
std::multimap<uint64_t, size_t> floor_and_event_id_to_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
};
class SetDataTableBase {
public:
virtual ~SetDataTableBase() = default;
parray<le_uint32_t, 0x20> generate_variations(
Episode episode,
bool is_solo,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
enum class FilenameType {
OBJECTS = 0,
ENEMIES,
EVENTS,
};
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const = 0;
std::vector<std::string> map_filenames_for_variations(
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, FilenameType type) const;
uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
protected:
explicit SetDataTableBase(Version version);
Version version;
};
class SetDataTable : public SetDataTableBase {
public:
struct SetEntry {
std::string object_list_basename;
std::string enemy_and_event_list_basename;
std::string area_setup_filename;
};
SetDataTable(Version version, const std::string& data);
virtual ~SetDataTable() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
std::string str() const;
private:
template <bool IsBigEndian>
void load_table_t(const std::string& data);
// Indexes are [floor][variation1][variation2]
// floor is cumulative per episode, so Ep2 starts at floor=18.
std::vector<std::vector<std::vector<SetEntry>>> entries;
};
class SetDataTableDCNTE : public SetDataTableBase {
public:
SetDataTableDCNTE();
virtual ~SetDataTableDCNTE() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
};
class SetDataTableDC112000 : public SetDataTableBase {
public:
SetDataTableDC112000();
virtual ~SetDataTableDC112000() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
};
void generate_variations_deprecated(
parray<le_uint32_t, 0x20>& variations,
std::shared_ptr<PSOLFGEncryption> random,
Version version,
Episode episode,
bool is_solo);
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo);
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo);
std::vector<std::string> map_filenames_for_variation_deprecated(
uint8_t floor, uint32_t var1, uint32_t var2, Version version, Episode episode, GameMode mode, bool is_enemies);
std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
const parray<le_uint32_t, 0x20>& variations,
Version version,
Episode episode,
GameMode mode,
bool is_enemies);
#pragma once
#include <inttypes.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include <vector>
#include "BattleParamsIndex.hh"
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
#include "Text.hh"
struct Map {
static const char* name_for_object_type(uint16_t type);
struct SectionHeader {
enum class Type {
END = 0,
OBJECTS = 1,
ENEMIES = 2,
WAVE_EVENTS = 3,
RANDOM_ENEMY_LOCATIONS = 4,
RANDOM_ENEMY_DEFINITIONS = 5,
};
le_uint32_t le_type;
le_uint32_t section_size; // Includes this header
le_uint32_t floor;
le_uint32_t data_size;
inline Type type() const {
return static_cast<Type>(this->le_type.load());
}
} __packed_ws__(SectionHeader, 0x10);
struct ObjectEntry { // Section type 1 (OBJECTS)
/* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t unknown_a2;
/* 08 */ le_uint16_t entity_id; // == index + 0x4000
/* 0A */ le_uint16_t group;
/* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t unknown_a3;
/* 10 */ le_float x;
/* 14 */ le_float y;
/* 18 */ le_float z;
/* 1C */ le_uint32_t x_angle;
/* 20 */ le_uint32_t y_angle;
/* 24 */ le_uint32_t z_angle;
/* 28 */ le_float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
/* 2C */ le_float param2;
/* 30 */ le_float param3;
/* 34 */ le_uint32_t param4;
/* 38 */ le_uint32_t param5;
/* 3C */ le_uint32_t param6;
/* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 44 */
std::string str() const;
} __packed_ws__(ObjectEntry, 0x44);
struct EnemyEntry { // Section type 2 (ENEMIES)
/* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t num_children;
/* 08 */ le_uint16_t floor;
/* 0A */ le_uint16_t entity_id; // == index + 0x1000
/* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t wave_number;
/* 10 */ le_uint16_t wave_number2;
/* 12 */ le_uint16_t unknown_a1;
/* 14 */ le_float x;
/* 18 */ le_float y;
/* 1C */ le_float z;
/* 20 */ le_uint32_t x_angle;
/* 24 */ le_uint32_t y_angle;
/* 28 */ le_uint32_t z_angle;
/* 2C */ le_float fparam1;
/* 30 */ le_float fparam2;
/* 34 */ le_float fparam3;
/* 38 */ le_float fparam4;
/* 3C */ le_float fparam5;
/* 40 */ le_uint16_t uparam1;
/* 42 */ le_uint16_t uparam2;
/* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 48 */
std::string str() const;
} __packed_ws__(EnemyEntry, 0x48);
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
/* 00 */ le_uint32_t action_stream_offset;
/* 04 */ le_uint32_t entries_offset;
/* 08 */ le_uint32_t entry_count;
/* 0C */ be_uint32_t format; // 0 or 'evt2'
/* 10 */
} __packed_ws__(EventsSectionHeader, 0x10);
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
/* 00 */ le_uint32_t event_id;
// Bits in flags:
// 0004 = is active
// 0008 = post-wave actions have been run
// 0010 = all enemies killed
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint32_t delay;
/* 10 */ le_uint32_t action_stream_offset;
/* 14 */
} __packed_ws__(Event1Entry, 0x14);
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
/* 00 */ le_uint32_t event_id;
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint16_t min_delay;
/* 0E */ le_uint16_t max_delay;
/* 10 */ uint8_t min_enemies;
/* 11 */ uint8_t max_enemies;
/* 12 */ le_uint16_t max_waves;
/* 14 */ le_uint32_t action_stream_offset;
/* 18 */
} __packed_ws__(Event2Entry, 0x18);
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct
/* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
/* 08 */ le_uint32_t num_sections;
/* 0C */
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint16_t section;
/* 02 */ le_uint16_t count;
/* 04 */ le_uint32_t offset;
/* 08 */
} __packed_ws__(RandomEnemyLocationSection, 8);
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_float x;
/* 04 */ le_float y;
/* 08 */ le_float z;
/* 0C */ le_uint32_t x_angle;
/* 10 */ le_uint32_t y_angle;
/* 14 */ le_uint32_t z_angle;
/* 18 */ uint16_t unknown_a9;
/* 1A */ uint16_t unknown_a10;
/* 1C */
} __packed_ws__(RandomEnemyLocationEntry, 0x1C);
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct
/* 04 */ le_uint32_t weight_entries_offset; // Offset to RandomEnemyDefinitionWeights structs, from start of this struct
/* 08 */ le_uint32_t entry_count;
/* 0C */ le_uint32_t weight_entry_count;
/* 10 */
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
// All fields through entry_num map to the corresponding fields in
// EnemyEntry. Note that the order of the uparam fields is switched!
/* 00 */ le_float fparam1;
/* 04 */ le_float fparam2;
/* 08 */ le_float fparam3;
/* 0C */ le_float fparam4;
/* 10 */ le_float fparam5;
/* 14 */ le_uint16_t uparam2;
/* 16 */ le_uint16_t uparam1;
/* 18 */ le_uint32_t entry_num;
/* 1C */ le_uint16_t min_children;
/* 1E */ le_uint16_t max_children;
/* 20 */
} __packed_ws__(RandomEnemyDefinition, 0x20);
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ uint8_t base_type_index;
/* 01 */ uint8_t definition_entry_num;
/* 02 */ uint8_t weight;
/* 03 */ uint8_t unknown_a4;
/* 04 */
} __packed_ws__(RandomEnemyWeight, 4);
struct RareEnemyRates {
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
uint32_t nar_lily; // POISON_LILY -> NAR_LILY
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
explicit RareEnemyRates(const JSON& json);
JSON json() const;
};
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
struct Object {
// TODO: Add more fields in here if we ever care about them. Currently we
// only care about boxes with fixed item drops.
size_t source_index;
uint8_t floor;
uint16_t object_id;
uint16_t base_type;
uint16_t section;
uint16_t group;
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
float param3; // If == 0, the item should be varied by difficulty and area
uint32_t param4;
uint32_t param5;
uint32_t param6;
uint16_t game_flags;
// Technically set_flags shouldn't be part of the Object struct, but all
// object entries always generate exactly one object, so we store it here.
uint16_t set_flags;
bool item_drop_checked;
std::string str() const;
};
struct Enemy {
enum Flag {
LAST_HIT_MASK = 0x03,
EXP_GIVEN = 0x04,
ITEM_DROPPED = 0x08,
ALL_HITS_MASK_FIRST = 0x10,
ALL_HITS_MASK = 0xF0,
};
size_t source_index;
size_t set_index;
uint16_t enemy_id;
uint16_t total_damage;
uint32_t game_flags; // From 6x0A
uint16_t section;
uint16_t wave_number;
EnemyType type;
uint8_t floor;
uint8_t server_flags;
Enemy(
uint16_t enemy_id,
size_t source_index,
size_t set_index,
uint8_t floor,
uint16_t section,
uint16_t wave_number,
EnemyType type);
std::string str() const;
inline bool ever_hit_by_client_id(uint8_t client_id) const {
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
}
inline bool last_hit_by_client_id(uint8_t client_id) const {
return (this->server_flags & Flag::LAST_HIT_MASK) == client_id;
}
inline void set_last_hit_by_client_id(uint8_t client_id) {
this->server_flags = (this->server_flags & (~Flag::LAST_HIT_MASK)) | (Flag::ALL_HITS_MASK_FIRST << client_id) | (client_id & 3);
}
};
struct Event {
uint32_t event_id;
uint16_t flags;
uint16_t section;
uint16_t wave_number;
uint8_t floor;
uint32_t action_stream_offset;
std::vector<size_t> enemy_indexes;
std::string str() const;
};
struct DATParserRandomState {
PSOV2Encryption random;
PSOV2Encryption location_table_random;
std::array<uint32_t, 0x20> location_index_table;
uint32_t location_indexes_populated;
uint32_t location_indexes_used;
uint32_t location_entries_base_offset;
DATParserRandomState(uint32_t rare_seed);
size_t rand_int_biased(size_t min_v, size_t max_v);
uint32_t next_location_index();
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
};
Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
~Map() = default;
void clear();
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size);
bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
void add_enemy(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
size_t index,
const EnemyEntry& e,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
const void* data,
size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_random_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
uint8_t floor,
StringReader wave_events_r,
StringReader random_enemy_locations_r,
StringReader random_enemy_definitions_r,
std::shared_ptr<DATParserRandomState> random_state,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_event(
uint32_t event_id,
uint16_t flags,
uint8_t floor,
uint16_t section,
uint16_t wave_number,
uint32_t action_stream_offset);
std::vector<Event*> get_events(uint8_t floor, uint32_t event_id);
std::vector<const Event*> get_events(uint8_t floor, uint32_t event_id) const;
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
struct DATSectionsForFloor {
uint32_t objects = 0xFFFFFFFF;
uint32_t enemies = 0xFFFFFFFF;
uint32_t wave_events = 0xFFFFFFFF;
uint32_t random_enemy_locations = 0xFFFFFFFF;
uint32_t random_enemy_definitions = 0xFFFFFFFF;
};
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
void add_entities_from_quest_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
const void* data,
size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
Enemy& find_enemy(uint8_t floor, EnemyType type);
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
std::vector<Event*> get_events(uint8_t floor);
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
static std::string disassemble_quest_data(const void* data, size_t size);
PrefixedLogger log;
Version version;
uint32_t rare_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::vector<Object> objects;
std::vector<Enemy> enemies;
std::vector<uint16_t> enemy_set_flags;
std::vector<size_t> rare_enemy_indexes;
std::vector<Event> events;
std::string event_action_stream;
std::multimap<uint64_t, size_t> floor_and_event_id_to_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
};
class SetDataTableBase {
public:
virtual ~SetDataTableBase() = default;
parray<le_uint32_t, 0x20> generate_variations(
Episode episode,
bool is_solo,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
enum class FilenameType {
OBJECTS = 0,
ENEMIES,
EVENTS,
};
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const = 0;
std::vector<std::string> map_filenames_for_variations(
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, FilenameType type) const;
uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
protected:
explicit SetDataTableBase(Version version);
Version version;
};
class SetDataTable : public SetDataTableBase {
public:
struct SetEntry {
std::string object_list_basename;
std::string enemy_and_event_list_basename;
std::string area_setup_filename;
};
SetDataTable(Version version, const std::string& data);
virtual ~SetDataTable() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
std::string str() const;
private:
template <bool IsBigEndian>
void load_table_t(const std::string& data);
// Indexes are [floor][variation1][variation2]
// floor is cumulative per episode, so Ep2 starts at floor=18.
std::vector<std::vector<std::vector<SetEntry>>> entries;
};
class SetDataTableDCNTE : public SetDataTableBase {
public:
SetDataTableDCNTE();
virtual ~SetDataTableDCNTE() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
};
class SetDataTableDC112000 : public SetDataTableBase {
public:
SetDataTableDC112000();
virtual ~SetDataTableDC112000() = default;
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
};
void generate_variations_deprecated(
parray<le_uint32_t, 0x20>& variations,
std::shared_ptr<PSOLFGEncryption> random,
Version version,
Episode episode,
bool is_solo);
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo);
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo);
std::vector<std::string> map_filenames_for_variation_deprecated(
uint8_t floor, uint32_t var1, uint32_t var2, Version version, Episode episode, GameMode mode, bool is_enemies);
std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
const parray<le_uint32_t, 0x20>& variations,
Version version,
Episode episode,
GameMode mode,
bool is_enemies);
+149 -149
View File
@@ -1,149 +1,149 @@
#pragma once
#include <stdint.h>
#include <functional>
#include <string>
#include <vector>
// Note: These aren't enums because neither enum nor enum class does what we
// want. Specifically, we need GO_BACK to be valid in multiple enums (and enums
// aren't namespaced unless they're enum classes), so we can't use enums. But we
// also want to be able to use non-enum values in switch statements without
// casting values all over the place, so we can't use enum classes either.
namespace MenuID {
constexpr uint32_t MAIN = 0x11000011;
constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111;
constexpr uint32_t INFORMATION = 0x22000022;
constexpr uint32_t LOBBY = 0x33000033;
constexpr uint32_t GAME = 0x44000044;
constexpr uint32_t QUEST_EP1 = 0x55010155;
constexpr uint32_t QUEST_EP2 = 0x55020255;
// See the decsription of the A2 command in CommandFormats.hh for why these
// menu IDs don't fit the rest of the pattern.
constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001;
constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002;
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
constexpr uint32_t PROGRAMS = 0x88000088;
constexpr uint32_t PATCHES = 0x99000099;
constexpr uint32_t PATCH_SWITCHES = 0x99010199;
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC;
} // namespace MenuID
namespace MainMenuItemID {
constexpr uint32_t GO_TO_LOBBY = 0x11222211;
constexpr uint32_t INFORMATION = 0x11333311;
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
constexpr uint32_t PATCHES = 0x11666611;
constexpr uint32_t PATCH_SWITCHES = 0x11676711;
constexpr uint32_t PROGRAMS = 0x11777711;
constexpr uint32_t DISCONNECT = 0x11888811;
constexpr uint32_t CLEAR_LICENSE = 0x11999911;
} // namespace MainMenuItemID
namespace ClearLicenseConfirmationMenuItemID {
constexpr uint32_t CANCEL = 0x01010101;
constexpr uint32_t CLEAR_LICENSE = 0x02020202;
} // namespace ClearLicenseConfirmationMenuItemID
namespace InformationMenuItemID {
constexpr uint32_t GO_BACK = 0x22FFFF22;
}
namespace ProxyDestinationsMenuItemID {
constexpr uint32_t GO_BACK = 0x77FFFF77;
constexpr uint32_t OPTIONS = 0x77EEEE77;
} // namespace ProxyDestinationsMenuItemID
namespace ProgramsMenuItemID {
constexpr uint32_t GO_BACK = 0x88FFFF88;
}
namespace PatchesMenuItemID {
constexpr uint32_t GO_BACK = 0x99FFFF99;
}
namespace ProxyOptionsMenuItemID {
constexpr uint32_t GO_BACK = 0xAAFFFFAA;
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
constexpr uint32_t INFINITE_HP = 0xAA0505AA;
constexpr uint32_t INFINITE_TP = 0xAA0606AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
constexpr uint32_t RED_NAME = 0xAA0B0BAA;
constexpr uint32_t BLANK_NAME = 0xAA0C0CAA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA;
constexpr uint32_t SKIP_CARD = 0xAA0E0EAA;
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA;
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA;
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA;
} // namespace ProxyOptionsMenuItemID
namespace TeamRewardMenuItemID {
constexpr uint32_t TEAM_FLAG = 0x01010101;
constexpr uint32_t DRESSING_ROOM = 0x02020202;
constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303;
constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404;
constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505;
constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606;
// constexpr uint32_t POINT_OF_DISASTER = ...;
// constexpr uint32_t TOYS_TWILIGHT = ...;
// constexpr uint32_t COMMANDER_BLADE = ...;
// constexpr uint32_t UNION_GUARD = ...;
// constexpr uint32_t TEAM_POINTS_500 = ...;
// constexpr uint32_t TEAM_POINTS_1000 = ...;
// constexpr uint32_t TEAM_POINTS_5000 = ...;
// constexpr uint32_t TEAM_POINTS_10000 = ...;
} // namespace TeamRewardMenuItemID
struct MenuItem {
enum Flag {
// For menu items to be visible on DC NTE, they must not have either of the
// following two flags. (The INVISIBLE_ON_GC_NTE flag behaves similarly.)
INVISIBLE_ON_DC_PROTOS = 0x001,
INVISIBLE_ON_DC = 0x002,
INVISIBLE_ON_PC_NTE = 0x004,
INVISIBLE_ON_PC = 0x008,
INVISIBLE_ON_GC_NTE = 0x010,
INVISIBLE_ON_GC = 0x020,
INVISIBLE_ON_XB = 0x040,
INVISIBLE_ON_BB = 0x080,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
XB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
REQUIRES_MESSAGE_BOXES = 0x100,
REQUIRES_SEND_FUNCTION_CALL = 0x200,
REQUIRES_SAVE_DISABLED = 0x400,
INVISIBLE_IN_INFO_MENU = 0x800,
};
uint32_t item_id;
std::string name;
std::string description;
std::function<std::string()> get_description;
uint32_t flags;
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
};
struct Menu {
uint32_t menu_id;
std::string name;
std::vector<MenuItem> items;
Menu() = delete;
Menu(uint32_t menu_id, const std::string& name);
};
#pragma once
#include <stdint.h>
#include <functional>
#include <string>
#include <vector>
// Note: These aren't enums because neither enum nor enum class does what we
// want. Specifically, we need GO_BACK to be valid in multiple enums (and enums
// aren't namespaced unless they're enum classes), so we can't use enums. But we
// also want to be able to use non-enum values in switch statements without
// casting values all over the place, so we can't use enum classes either.
namespace MenuID {
constexpr uint32_t MAIN = 0x11000011;
constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111;
constexpr uint32_t INFORMATION = 0x22000022;
constexpr uint32_t LOBBY = 0x33000033;
constexpr uint32_t GAME = 0x44000044;
constexpr uint32_t QUEST_EP1 = 0x55010155;
constexpr uint32_t QUEST_EP2 = 0x55020255;
// See the decsription of the A2 command in CommandFormats.hh for why these
// menu IDs don't fit the rest of the pattern.
constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001;
constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002;
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
constexpr uint32_t PROGRAMS = 0x88000088;
constexpr uint32_t PATCHES = 0x99000099;
constexpr uint32_t PATCH_SWITCHES = 0x99010199;
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC;
} // namespace MenuID
namespace MainMenuItemID {
constexpr uint32_t GO_TO_LOBBY = 0x11222211;
constexpr uint32_t INFORMATION = 0x11333311;
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
constexpr uint32_t PATCHES = 0x11666611;
constexpr uint32_t PATCH_SWITCHES = 0x11676711;
constexpr uint32_t PROGRAMS = 0x11777711;
constexpr uint32_t DISCONNECT = 0x11888811;
constexpr uint32_t CLEAR_LICENSE = 0x11999911;
} // namespace MainMenuItemID
namespace ClearLicenseConfirmationMenuItemID {
constexpr uint32_t CANCEL = 0x01010101;
constexpr uint32_t CLEAR_LICENSE = 0x02020202;
} // namespace ClearLicenseConfirmationMenuItemID
namespace InformationMenuItemID {
constexpr uint32_t GO_BACK = 0x22FFFF22;
}
namespace ProxyDestinationsMenuItemID {
constexpr uint32_t GO_BACK = 0x77FFFF77;
constexpr uint32_t OPTIONS = 0x77EEEE77;
} // namespace ProxyDestinationsMenuItemID
namespace ProgramsMenuItemID {
constexpr uint32_t GO_BACK = 0x88FFFF88;
}
namespace PatchesMenuItemID {
constexpr uint32_t GO_BACK = 0x99FFFF99;
}
namespace ProxyOptionsMenuItemID {
constexpr uint32_t GO_BACK = 0xAAFFFFAA;
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
constexpr uint32_t INFINITE_HP = 0xAA0505AA;
constexpr uint32_t INFINITE_TP = 0xAA0606AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
constexpr uint32_t RED_NAME = 0xAA0B0BAA;
constexpr uint32_t BLANK_NAME = 0xAA0C0CAA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA;
constexpr uint32_t SKIP_CARD = 0xAA0E0EAA;
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA;
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA;
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA;
} // namespace ProxyOptionsMenuItemID
namespace TeamRewardMenuItemID {
constexpr uint32_t TEAM_FLAG = 0x01010101;
constexpr uint32_t DRESSING_ROOM = 0x02020202;
constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303;
constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404;
constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505;
constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606;
// constexpr uint32_t POINT_OF_DISASTER = ...;
// constexpr uint32_t TOYS_TWILIGHT = ...;
// constexpr uint32_t COMMANDER_BLADE = ...;
// constexpr uint32_t UNION_GUARD = ...;
// constexpr uint32_t TEAM_POINTS_500 = ...;
// constexpr uint32_t TEAM_POINTS_1000 = ...;
// constexpr uint32_t TEAM_POINTS_5000 = ...;
// constexpr uint32_t TEAM_POINTS_10000 = ...;
} // namespace TeamRewardMenuItemID
struct MenuItem {
enum Flag {
// For menu items to be visible on DC NTE, they must not have either of the
// following two flags. (The INVISIBLE_ON_GC_NTE flag behaves similarly.)
INVISIBLE_ON_DC_PROTOS = 0x001,
INVISIBLE_ON_DC = 0x002,
INVISIBLE_ON_PC_NTE = 0x004,
INVISIBLE_ON_PC = 0x008,
INVISIBLE_ON_GC_NTE = 0x010,
INVISIBLE_ON_GC = 0x020,
INVISIBLE_ON_XB = 0x040,
INVISIBLE_ON_BB = 0x080,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
XB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
REQUIRES_MESSAGE_BOXES = 0x100,
REQUIRES_SEND_FUNCTION_CALL = 0x200,
REQUIRES_SAVE_DISABLED = 0x400,
INVISIBLE_IN_INFO_MENU = 0x800,
};
uint32_t item_id;
std::string name;
std::string description;
std::function<std::string()> get_description;
uint32_t flags;
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
};
struct Menu {
uint32_t menu_id;
std::string name;
std::vector<MenuItem> items;
Menu() = delete;
Menu(uint32_t menu_id, const std::string& name);
};
+104 -104
View File
@@ -1,104 +1,104 @@
#include "NetworkAddresses.hh"
#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <stdexcept>
using namespace std;
uint32_t resolve_address(const char* address) {
struct addrinfo* res0;
if (getaddrinfo(address, nullptr, nullptr, &res0)) {
auto e = string_for_error(errno);
throw runtime_error(string_printf(
"can\'t resolve hostname %s: %s", address, e.c_str()));
}
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
struct addrinfo* res4 = nullptr;
for (struct addrinfo* res = res0; res; res = res->ai_next) {
if (res->ai_family == AF_INET) {
res4 = res;
}
}
if (!res4) {
throw runtime_error(string_printf(
"can\'t resolve hostname %s: no usable data", address));
}
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
return ntohl(res_sin->sin_addr.s_addr);
}
map<string, uint32_t> get_local_addresses() {
struct ifaddrs* ifa_raw;
if (getifaddrs(&ifa_raw)) {
auto s = string_for_error(errno);
throw runtime_error(string_printf("failed to get interface addresses: %s", s.c_str()));
}
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
map<string, uint32_t> ret;
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
if (!i->ifa_addr) {
continue;
}
auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
if (sin->sin_family != AF_INET) {
continue;
}
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
}
return ret;
}
bool is_local_address(uint32_t addr) {
uint8_t net = (addr >> 24) & 0xFF;
return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
}
bool is_local_address(const sockaddr_storage& daddr) {
if (daddr.ss_family != AF_INET) {
return false;
}
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
return is_local_address(ntohl(sin->sin_addr.s_addr));
}
string string_for_address(uint32_t address) {
return string_printf("%hhu.%hhu.%hhu.%hhu",
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
}
uint32_t address_for_string(const char* address) {
return ntohl(inet_addr(address));
}
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port) {
// It seems the address part of the number is fixed-width, but the port is
// not. Why did they do it this way?
if (port & 0xF000) {
return (static_cast<uint64_t>(addr) << 16) | port;
} else if (port & 0x0F00) {
return (static_cast<uint64_t>(addr) << 12) | port;
} else if (port & 0x00F0) {
return (static_cast<uint64_t>(addr) << 8) | port;
} else {
return (static_cast<uint64_t>(addr) << 4) | port;
}
}
#include "NetworkAddresses.hh"
#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <stdexcept>
using namespace std;
uint32_t resolve_address(const char* address) {
struct addrinfo* res0;
if (getaddrinfo(address, nullptr, nullptr, &res0)) {
auto e = string_for_error(errno);
throw runtime_error(string_printf(
"can\'t resolve hostname %s: %s", address, e.c_str()));
}
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
struct addrinfo* res4 = nullptr;
for (struct addrinfo* res = res0; res; res = res->ai_next) {
if (res->ai_family == AF_INET) {
res4 = res;
}
}
if (!res4) {
throw runtime_error(string_printf(
"can\'t resolve hostname %s: no usable data", address));
}
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
return ntohl(res_sin->sin_addr.s_addr);
}
map<string, uint32_t> get_local_addresses() {
struct ifaddrs* ifa_raw;
if (getifaddrs(&ifa_raw)) {
auto s = string_for_error(errno);
throw runtime_error(string_printf("failed to get interface addresses: %s", s.c_str()));
}
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
map<string, uint32_t> ret;
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
if (!i->ifa_addr) {
continue;
}
auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
if (sin->sin_family != AF_INET) {
continue;
}
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
}
return ret;
}
bool is_local_address(uint32_t addr) {
uint8_t net = (addr >> 24) & 0xFF;
return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
}
bool is_local_address(const sockaddr_storage& daddr) {
if (daddr.ss_family != AF_INET) {
return false;
}
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
return is_local_address(ntohl(sin->sin_addr.s_addr));
}
string string_for_address(uint32_t address) {
return string_printf("%hhu.%hhu.%hhu.%hhu",
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
}
uint32_t address_for_string(const char* address) {
return ntohl(inet_addr(address));
}
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port) {
// It seems the address part of the number is fixed-width, but the port is
// not. Why did they do it this way?
if (port & 0xF000) {
return (static_cast<uint64_t>(addr) << 16) | port;
} else if (port & 0x0F00) {
return (static_cast<uint64_t>(addr) << 12) | port;
} else if (port & 0x00F0) {
return (static_cast<uint64_t>(addr) << 8) | port;
} else {
return (static_cast<uint64_t>(addr) << 4) | port;
}
}
+21 -21
View File
@@ -1,21 +1,21 @@
#pragma once
#include <netinet/in.h>
#include <stdint.h>
#include <map>
#include <string>
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
// it's easier
uint32_t resolve_address(const char* address);
std::map<std::string, uint32_t> get_local_addresses();
uint32_t get_connected_address(int fd);
bool is_local_address(uint32_t daddr);
bool is_local_address(const sockaddr_storage& daddr);
std::string string_for_address(uint32_t address);
uint32_t address_for_string(const char* address);
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port);
#pragma once
#include <netinet/in.h>
#include <stdint.h>
#include <map>
#include <string>
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
// it's easier
uint32_t resolve_address(const char* address);
std::map<std::string, uint32_t> get_local_addresses();
uint32_t get_connected_address(int fd);
bool is_local_address(uint32_t daddr);
bool is_local_address(const sockaddr_storage& daddr);
std::string string_for_address(uint32_t address);
uint32_t address_for_string(const char* address);
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port);
+914 -914
View File
File diff suppressed because it is too large Load Diff
+359 -359
View File
@@ -1,359 +1,359 @@
#pragma once
#include <inttypes.h>
#include <stddef.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/Random.hh>
#include <stdexcept>
#include <string>
#include <vector>
#include "Compression.hh"
#include "Text.hh"
class PSOEncryption {
public:
enum class Type {
V2 = 0,
V3,
BB,
JSD0,
};
virtual ~PSOEncryption() = default;
virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
virtual void decrypt(void* data, size_t size, bool advance = true);
inline void encrypt(std::string& data, bool advance = true) {
this->encrypt(data.data(), data.size(), advance);
}
inline void decrypt(std::string& data, bool advance = true) {
this->decrypt(data.data(), data.size(), advance);
}
virtual Type type() const = 0;
protected:
PSOEncryption() = default;
};
class PSOLFGEncryption : public PSOEncryption {
public:
virtual void encrypt(void* data, size_t size, bool advance = true);
void encrypt_big_endian(void* data, size_t size, bool advance = true);
void encrypt_minus(void* data, size_t size, bool advance = true);
void encrypt_big_endian_minus(void* data, size_t size, bool advance = true);
void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
template <bool IsBigEndian>
void encrypt_t(void* data, size_t size, bool advance = true);
template <bool IsBigEndian>
void encrypt_minus_t(void* data, size_t size, bool advance = true);
uint32_t next(bool advance = true);
inline uint32_t seed() const {
return this->initial_seed;
}
uint32_t absolute_offset() const {
return (this->cycles * this->end_offset) + this->offset;
}
protected:
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
virtual void update_stream() = 0;
std::vector<uint32_t> stream;
size_t offset;
size_t end_offset;
uint32_t initial_seed;
size_t cycles;
};
class PSOV2Encryption : public PSOLFGEncryption {
public:
explicit PSOV2Encryption(uint32_t seed);
virtual Type type() const;
protected:
virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 0x38;
};
class PSOV3Encryption : public PSOLFGEncryption {
public:
explicit PSOV3Encryption(uint32_t key);
virtual Type type() const;
protected:
virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 521;
};
class PSOBBEncryption : public PSOEncryption {
public:
enum Subtype : uint8_t {
STANDARD = 0x00,
MOCB1 = 0x01,
JSD1 = 0x02,
TFS1 = 0x03,
};
struct KeyFile {
// initial_keys are actually a stream of uint32_ts, but we treat them as
// bytes for code simplicity
union InitialKeys {
uint8_t jsd1_stream_offset;
parray<uint8_t, 0x48> as8;
parray<le_uint32_t, 0x12> as32;
InitialKeys() : as32() {}
InitialKeys(const InitialKeys& other) : as32(other.as32) {}
} __packed_ws__(InitialKeys, 0x48);
union PrivateKeys {
parray<uint8_t, 0x1000> as8;
parray<le_uint32_t, 0x400> as32;
PrivateKeys() : as32() {}
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
} __packed_ws__(PrivateKeys, 0x1000);
InitialKeys initial_keys;
PrivateKeys private_keys;
// This field only really needs to be one byte, but annoyingly, some
// compilers pad this structure to a longer alignment, presumably because
// the unions above contain structures with 32-bit alignment. To prevent
// this structure's size from not matching the .nsk files' sizes, we use
// an unnecessarily large size for this field.
le_uint64_t subtype;
} __packed_ws__(KeyFile, 0x1050);
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
KeyFile state;
void tfs1_scramble(uint32_t* out1, uint32_t* out2) const;
void apply_seed(const void* original_seed, size_t seed_size);
};
// The following classes provide support for automatically detecting which type
// of encryption a client is using based on their initial response to the server
class PSOV2OrV3DetectorEncryption : public PSOEncryption {
public:
PSOV2OrV3DetectorEncryption(
uint32_t key,
const std::unordered_set<uint32_t>& v2_matches,
const std::unordered_set<uint32_t>& v3_matches);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
uint32_t key;
const std::unordered_set<uint32_t>& v2_matches;
const std::unordered_set<uint32_t>& v3_matches;
std::unique_ptr<PSOEncryption> active_crypt;
};
class PSOV2OrV3ImitatorEncryption : public PSOEncryption {
public:
PSOV2OrV3ImitatorEncryption(
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
uint32_t key;
std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt;
std::shared_ptr<PSOEncryption> active_crypt;
};
// The following classes provide support for multiple PSOBB private keys, and
// the ability to automatically detect which key the client is using based on
// the first 8 bytes they send
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyDetectorEncryption(
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const std::unordered_set<std::string>& expected_first_data,
const void* seed,
size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
return this->active_key;
}
inline const std::string& get_seed() const {
return this->seed;
}
virtual Type type() const;
protected:
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
std::shared_ptr<PSOBBEncryption> active_crypt;
const std::unordered_set<std::string>& expected_first_data;
std::string seed;
};
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyImitatorEncryption(
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
const void* seed,
size_t seed_size,
bool jsd1_use_detector_seed);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
std::shared_ptr<PSOBBEncryption> ensure_crypt();
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<PSOBBEncryption> active_crypt;
std::string seed;
bool jsd1_use_detector_seed;
};
class JSD0Encryption : public PSOEncryption {
public:
JSD0Encryption(const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const = 0;
private:
uint8_t key;
};
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
uint32_t encrypt_challenge_time(uint16_t value);
uint16_t decrypt_challenge_time(uint32_t value);
template <bool IsBigEndian>
class ChallengeTimeT {
private:
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T value;
public:
ChallengeTimeT() = default;
ChallengeTimeT(uint16_t v) {
this->encode(v);
}
ChallengeTimeT(const ChallengeTimeT& other) = default;
ChallengeTimeT(ChallengeTimeT&& other) = default;
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
bool has_value() const {
return this->value != 0;
}
uint32_t load_raw() const {
return this->value;
}
void store_raw(uint32_t value) {
this->value = value;
}
uint16_t decode() const {
return decrypt_challenge_time(this->value);
}
void encode(uint16_t v) {
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
}
operator ChallengeTimeT<!IsBigEndian>() const {
ChallengeTimeT<!IsBigEndian> ret;
ret.store_raw(this->value);
return ret;
}
} __packed__;
using ChallengeTime = ChallengeTimeT<false>;
using ChallengeTimeBE = ChallengeTimeT<true>;
check_struct_size(ChallengeTime, 4);
check_struct_size(ChallengeTimeBE, 4);
std::string decrypt_v2_registry_value(const void* data, size_t size);
struct DecryptedPR2 {
std::string compressed_data;
size_t decompressed_size;
};
template <bool IsBigEndian>
DecryptedPR2 decrypt_pr2_data(const std::string& data) {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
if (data.size() < 8) {
throw std::runtime_error("not enough data for PR2 header");
}
StringReader r(data);
DecryptedPR2 ret = {
.compressed_data = data.substr(8),
.decompressed_size = r.get<U32T>()};
PSOV2Encryption crypt(r.get<U32T>());
if (IsBigEndian) {
crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size());
} else {
crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size());
}
return ret;
}
template <bool IsBigEndian>
std::string decrypt_and_decompress_pr2_data(const std::string& data) {
auto decrypted = decrypt_pr2_data<IsBigEndian>(data);
std::string decompressed = prs_decompress(decrypted.compressed_data);
if (decompressed.size() != decrypted.decompressed_size) {
throw std::runtime_error("decompressed size does not match expected size");
}
return decompressed;
}
template <bool IsBigEndian>
std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, uint32_t seed) {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
StringWriter w;
w.put<U32T>(decompressed_size);
w.put<U32T>(seed);
w.write(data);
std::string ret = std::move(w.str());
PSOV2Encryption crypt(seed);
if (IsBigEndian) {
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
} else {
crypt.decrypt(ret.data() + 8, ret.size() - 8);
}
return ret;
}
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
return random_crypt ? random_crypt->next() : random_object<uint32_t>();
}
#pragma once
#include <inttypes.h>
#include <stddef.h>
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/Random.hh>
#include <stdexcept>
#include <string>
#include <vector>
#include "Compression.hh"
#include "Text.hh"
class PSOEncryption {
public:
enum class Type {
V2 = 0,
V3,
BB,
JSD0,
};
virtual ~PSOEncryption() = default;
virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
virtual void decrypt(void* data, size_t size, bool advance = true);
inline void encrypt(std::string& data, bool advance = true) {
this->encrypt(data.data(), data.size(), advance);
}
inline void decrypt(std::string& data, bool advance = true) {
this->decrypt(data.data(), data.size(), advance);
}
virtual Type type() const = 0;
protected:
PSOEncryption() = default;
};
class PSOLFGEncryption : public PSOEncryption {
public:
virtual void encrypt(void* data, size_t size, bool advance = true);
void encrypt_big_endian(void* data, size_t size, bool advance = true);
void encrypt_minus(void* data, size_t size, bool advance = true);
void encrypt_big_endian_minus(void* data, size_t size, bool advance = true);
void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
template <bool IsBigEndian>
void encrypt_t(void* data, size_t size, bool advance = true);
template <bool IsBigEndian>
void encrypt_minus_t(void* data, size_t size, bool advance = true);
uint32_t next(bool advance = true);
inline uint32_t seed() const {
return this->initial_seed;
}
uint32_t absolute_offset() const {
return (this->cycles * this->end_offset) + this->offset;
}
protected:
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
virtual void update_stream() = 0;
std::vector<uint32_t> stream;
size_t offset;
size_t end_offset;
uint32_t initial_seed;
size_t cycles;
};
class PSOV2Encryption : public PSOLFGEncryption {
public:
explicit PSOV2Encryption(uint32_t seed);
virtual Type type() const;
protected:
virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 0x38;
};
class PSOV3Encryption : public PSOLFGEncryption {
public:
explicit PSOV3Encryption(uint32_t key);
virtual Type type() const;
protected:
virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 521;
};
class PSOBBEncryption : public PSOEncryption {
public:
enum Subtype : uint8_t {
STANDARD = 0x00,
MOCB1 = 0x01,
JSD1 = 0x02,
TFS1 = 0x03,
};
struct KeyFile {
// initial_keys are actually a stream of uint32_ts, but we treat them as
// bytes for code simplicity
union InitialKeys {
uint8_t jsd1_stream_offset;
parray<uint8_t, 0x48> as8;
parray<le_uint32_t, 0x12> as32;
InitialKeys() : as32() {}
InitialKeys(const InitialKeys& other) : as32(other.as32) {}
} __packed_ws__(InitialKeys, 0x48);
union PrivateKeys {
parray<uint8_t, 0x1000> as8;
parray<le_uint32_t, 0x400> as32;
PrivateKeys() : as32() {}
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
} __packed_ws__(PrivateKeys, 0x1000);
InitialKeys initial_keys;
PrivateKeys private_keys;
// This field only really needs to be one byte, but annoyingly, some
// compilers pad this structure to a longer alignment, presumably because
// the unions above contain structures with 32-bit alignment. To prevent
// this structure's size from not matching the .nsk files' sizes, we use
// an unnecessarily large size for this field.
le_uint64_t subtype;
} __packed_ws__(KeyFile, 0x1050);
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
KeyFile state;
void tfs1_scramble(uint32_t* out1, uint32_t* out2) const;
void apply_seed(const void* original_seed, size_t seed_size);
};
// The following classes provide support for automatically detecting which type
// of encryption a client is using based on their initial response to the server
class PSOV2OrV3DetectorEncryption : public PSOEncryption {
public:
PSOV2OrV3DetectorEncryption(
uint32_t key,
const std::unordered_set<uint32_t>& v2_matches,
const std::unordered_set<uint32_t>& v3_matches);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
uint32_t key;
const std::unordered_set<uint32_t>& v2_matches;
const std::unordered_set<uint32_t>& v3_matches;
std::unique_ptr<PSOEncryption> active_crypt;
};
class PSOV2OrV3ImitatorEncryption : public PSOEncryption {
public:
PSOV2OrV3ImitatorEncryption(
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
uint32_t key;
std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt;
std::shared_ptr<PSOEncryption> active_crypt;
};
// The following classes provide support for multiple PSOBB private keys, and
// the ability to automatically detect which key the client is using based on
// the first 8 bytes they send
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyDetectorEncryption(
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const std::unordered_set<std::string>& expected_first_data,
const void* seed,
size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
return this->active_key;
}
inline const std::string& get_seed() const {
return this->seed;
}
virtual Type type() const;
protected:
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
std::shared_ptr<PSOBBEncryption> active_crypt;
const std::unordered_set<std::string>& expected_first_data;
std::string seed;
};
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyImitatorEncryption(
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
const void* seed,
size_t seed_size,
bool jsd1_use_detector_seed);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const;
protected:
std::shared_ptr<PSOBBEncryption> ensure_crypt();
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<PSOBBEncryption> active_crypt;
std::string seed;
bool jsd1_use_detector_seed;
};
class JSD0Encryption : public PSOEncryption {
public:
JSD0Encryption(const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const = 0;
private:
uint8_t key;
};
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
uint32_t encrypt_challenge_time(uint16_t value);
uint16_t decrypt_challenge_time(uint32_t value);
template <bool IsBigEndian>
class ChallengeTimeT {
private:
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T value;
public:
ChallengeTimeT() = default;
ChallengeTimeT(uint16_t v) {
this->encode(v);
}
ChallengeTimeT(const ChallengeTimeT& other) = default;
ChallengeTimeT(ChallengeTimeT&& other) = default;
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
bool has_value() const {
return this->value != 0;
}
uint32_t load_raw() const {
return this->value;
}
void store_raw(uint32_t value) {
this->value = value;
}
uint16_t decode() const {
return decrypt_challenge_time(this->value);
}
void encode(uint16_t v) {
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
}
operator ChallengeTimeT<!IsBigEndian>() const {
ChallengeTimeT<!IsBigEndian> ret;
ret.store_raw(this->value);
return ret;
}
} __packed__;
using ChallengeTime = ChallengeTimeT<false>;
using ChallengeTimeBE = ChallengeTimeT<true>;
check_struct_size(ChallengeTime, 4);
check_struct_size(ChallengeTimeBE, 4);
std::string decrypt_v2_registry_value(const void* data, size_t size);
struct DecryptedPR2 {
std::string compressed_data;
size_t decompressed_size;
};
template <bool IsBigEndian>
DecryptedPR2 decrypt_pr2_data(const std::string& data) {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
if (data.size() < 8) {
throw std::runtime_error("not enough data for PR2 header");
}
StringReader r(data);
DecryptedPR2 ret = {
.compressed_data = data.substr(8),
.decompressed_size = r.get<U32T>()};
PSOV2Encryption crypt(r.get<U32T>());
if (IsBigEndian) {
crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size());
} else {
crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size());
}
return ret;
}
template <bool IsBigEndian>
std::string decrypt_and_decompress_pr2_data(const std::string& data) {
auto decrypted = decrypt_pr2_data<IsBigEndian>(data);
std::string decompressed = prs_decompress(decrypted.compressed_data);
if (decompressed.size() != decrypted.decompressed_size) {
throw std::runtime_error("decompressed size does not match expected size");
}
return decompressed;
}
template <bool IsBigEndian>
std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, uint32_t seed) {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
StringWriter w;
w.put<U32T>(decompressed_size);
w.put<U32T>(seed);
w.write(data);
std::string ret = std::move(w.str());
PSOV2Encryption crypt(seed);
if (IsBigEndian) {
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
} else {
crypt.decrypt(ret.data() + 8, ret.size() - 8);
}
return ret;
}
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
return random_crypt ? random_crypt->next() : random_object<uint32_t>();
}
+270 -270
View File
@@ -1,270 +1,270 @@
#include "PSOProtocol.hh"
#include <event2/buffer.h>
#include <phosg/Strings.hh>
#include <stdexcept>
#include "Text.hh"
using namespace std;
extern bool use_terminal_colors;
PSOCommandHeader::PSOCommandHeader() {
this->bb.size = 0;
this->bb.command = 0;
this->bb.flag = 0;
}
uint16_t PSOCommandHeader::command(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.command;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.command;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.command;
case Version::XB_V3:
return this->xb.command;
case Version::BB_V4:
return this->bb.command;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_command(Version version, uint16_t command) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.command = command;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.command = command;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.command = command;
break;
case Version::XB_V3:
this->xb.command = command;
break;
case Version::BB_V4:
this->bb.command = command;
break;
default:
throw logic_error("unknown game version");
}
}
uint16_t PSOCommandHeader::size(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.size;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.size;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.size;
case Version::XB_V3:
return this->xb.size;
case Version::BB_V4:
return this->bb.size;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_size(Version version, uint32_t size) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.size = size;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.size = size;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.size = size;
break;
case Version::XB_V3:
this->xb.size = size;
break;
case Version::BB_V4:
this->bb.size = size;
break;
default:
throw logic_error("unknown game version");
}
}
uint32_t PSOCommandHeader::flag(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.flag;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.flag;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.flag;
case Version::XB_V3:
return this->xb.flag;
case Version::BB_V4:
return this->bb.flag;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.flag = flag;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.flag = flag;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.flag = flag;
break;
case Version::XB_V3:
this->xb.flag = flag;
break;
case Version::BB_V4:
this->bb.flag = flag;
break;
default:
throw logic_error("unknown game version");
}
}
void check_size_v(size_t size, size_t min_size, size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (max_size < min_size) {
max_size = min_size;
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
}
std::string prepend_command_header(
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
const std::string& data) {
StringWriter ret;
switch (version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
case Version::BB_V4: {
PSOCommandHeaderBB header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
default:
throw logic_error("unimplemented game version in prepend_command_header");
}
ret.write(data);
return std::move(ret.str());
}
#include "PSOProtocol.hh"
#include <event2/buffer.h>
#include <phosg/Strings.hh>
#include <stdexcept>
#include "Text.hh"
using namespace std;
extern bool use_terminal_colors;
PSOCommandHeader::PSOCommandHeader() {
this->bb.size = 0;
this->bb.command = 0;
this->bb.flag = 0;
}
uint16_t PSOCommandHeader::command(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.command;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.command;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.command;
case Version::XB_V3:
return this->xb.command;
case Version::BB_V4:
return this->bb.command;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_command(Version version, uint16_t command) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.command = command;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.command = command;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.command = command;
break;
case Version::XB_V3:
this->xb.command = command;
break;
case Version::BB_V4:
this->bb.command = command;
break;
default:
throw logic_error("unknown game version");
}
}
uint16_t PSOCommandHeader::size(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.size;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.size;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.size;
case Version::XB_V3:
return this->xb.size;
case Version::BB_V4:
return this->bb.size;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_size(Version version, uint32_t size) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.size = size;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.size = size;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.size = size;
break;
case Version::XB_V3:
this->xb.size = size;
break;
case Version::BB_V4:
this->bb.size = size;
break;
default:
throw logic_error("unknown game version");
}
}
uint32_t PSOCommandHeader::flag(Version version) const {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
return this->pc.flag;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.flag;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return this->gc.flag;
case Version::XB_V3:
return this->xb.flag;
case Version::BB_V4:
return this->bb.flag;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2:
this->pc.flag = flag;
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.flag = flag;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
this->gc.flag = flag;
break;
case Version::XB_V3:
this->xb.flag = flag;
break;
case Version::BB_V4:
this->bb.flag = flag;
break;
default:
throw logic_error("unknown game version");
}
}
void check_size_v(size_t size, size_t min_size, size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (max_size < min_size) {
max_size = min_size;
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
}
std::string prepend_command_header(
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
const std::string& data) {
StringWriter ret;
switch (version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
case Version::BB_V4: {
PSOCommandHeaderBB header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
header.command = cmd;
header.flag = flag;
ret.put(header);
break;
}
default:
throw logic_error("unimplemented game version in prepend_command_header");
}
ret.write(data);
return std::move(ret.str());
}
+130 -130
View File
@@ -1,130 +1,130 @@
#pragma once
#include <event2/bufferevent.h>
#include <inttypes.h>
#include <functional>
#include <phosg/Strings.hh>
#include "PSOEncryption.hh"
#include "Version.hh"
struct PSOCommandHeaderPC {
le_uint16_t size;
uint8_t command;
uint8_t flag;
} __packed_ws__(PSOCommandHeaderPC, 4);
struct PSOCommandHeaderDCV3 {
uint8_t command;
uint8_t flag;
le_uint16_t size;
} __packed_ws__(PSOCommandHeaderDCV3, 4);
struct PSOCommandHeaderBB {
le_uint16_t size;
le_uint16_t command;
le_uint32_t flag;
} __packed_ws__(PSOCommandHeaderBB, 8);
union PSOCommandHeader {
PSOCommandHeaderDCV3 dc;
PSOCommandHeaderPC pc;
PSOCommandHeaderDCV3 gc;
PSOCommandHeaderDCV3 xb;
PSOCommandHeaderBB bb;
uint16_t command(Version version) const;
void set_command(Version version, uint16_t command);
uint16_t size(Version version) const;
void set_size(Version version, uint32_t size);
uint32_t flag(Version version) const;
void set_flag(Version version, uint32_t flag);
static inline size_t header_size(Version version) {
return (version == Version::BB_V4) ? 8 : 4;
}
PSOCommandHeader();
} __packed_ws__(PSOCommandHeader, 8);
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename RetT, typename PtrT>
RetT& check_size_generic(
PtrT data,
size_t size,
size_t min_size,
size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
return *reinterpret_cast<RetT*>(data);
}
template <typename T>
const T& check_size_t(const std::string& data, size_t min_size, size_t max_size) {
return check_size_generic<const T, const void*>(data.data(), data.size(), min_size, max_size);
}
template <typename T>
const T& check_size_t(const std::string& data, size_t max_size) {
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), max_size);
}
template <typename T>
const T& check_size_t(const std::string& data) {
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), sizeof(T));
}
template <typename T>
T& check_size_t(std::string& data, size_t min_size, size_t max_size) {
return check_size_generic<T, void*>(data.data(), data.size(), min_size, max_size);
}
template <typename T>
T& check_size_t(std::string& data, size_t max_size) {
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), max_size);
}
template <typename T>
T& check_size_t(std::string& data) {
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T));
}
template <typename T>
const T& check_size_t(const void* data, size_t size, size_t min_size, size_t max_size) {
return check_size_generic<const T, const void*>(data, size, min_size, max_size);
}
template <typename T>
const T& check_size_t(const void* data, size_t size, size_t max_size) {
return check_size_generic<const T, const void*>(data, size, sizeof(T), max_size);
}
template <typename T>
const T& check_size_t(const void* data, size_t size) {
return check_size_generic<const T, const void*>(data, size, sizeof(T), sizeof(T));
}
template <typename T>
T& check_size_t(void* data, size_t size, size_t min_size, size_t max_size) {
return check_size_generic<T, void*>(data, size, min_size, max_size);
}
template <typename T>
T& check_size_t(void* data, size_t size, size_t max_size) {
return check_size_generic<T, void*>(data, size, sizeof(T), max_size);
}
template <typename T>
T& check_size_t(void* data, size_t size) {
return check_size_generic<T, void*>(data, size, sizeof(T), sizeof(T));
}
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
std::string prepend_command_header(
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
const std::string& data);
#pragma once
#include <event2/bufferevent.h>
#include <inttypes.h>
#include <functional>
#include <phosg/Strings.hh>
#include "PSOEncryption.hh"
#include "Version.hh"
struct PSOCommandHeaderPC {
le_uint16_t size;
uint8_t command;
uint8_t flag;
} __packed_ws__(PSOCommandHeaderPC, 4);
struct PSOCommandHeaderDCV3 {
uint8_t command;
uint8_t flag;
le_uint16_t size;
} __packed_ws__(PSOCommandHeaderDCV3, 4);
struct PSOCommandHeaderBB {
le_uint16_t size;
le_uint16_t command;
le_uint32_t flag;
} __packed_ws__(PSOCommandHeaderBB, 8);
union PSOCommandHeader {
PSOCommandHeaderDCV3 dc;
PSOCommandHeaderPC pc;
PSOCommandHeaderDCV3 gc;
PSOCommandHeaderDCV3 xb;
PSOCommandHeaderBB bb;
uint16_t command(Version version) const;
void set_command(Version version, uint16_t command);
uint16_t size(Version version) const;
void set_size(Version version, uint32_t size);
uint32_t flag(Version version) const;
void set_flag(Version version, uint32_t flag);
static inline size_t header_size(Version version) {
return (version == Version::BB_V4) ? 8 : 4;
}
PSOCommandHeader();
} __packed_ws__(PSOCommandHeader, 8);
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename RetT, typename PtrT>
RetT& check_size_generic(
PtrT data,
size_t size,
size_t min_size,
size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
return *reinterpret_cast<RetT*>(data);
}
template <typename T>
const T& check_size_t(const std::string& data, size_t min_size, size_t max_size) {
return check_size_generic<const T, const void*>(data.data(), data.size(), min_size, max_size);
}
template <typename T>
const T& check_size_t(const std::string& data, size_t max_size) {
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), max_size);
}
template <typename T>
const T& check_size_t(const std::string& data) {
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), sizeof(T));
}
template <typename T>
T& check_size_t(std::string& data, size_t min_size, size_t max_size) {
return check_size_generic<T, void*>(data.data(), data.size(), min_size, max_size);
}
template <typename T>
T& check_size_t(std::string& data, size_t max_size) {
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), max_size);
}
template <typename T>
T& check_size_t(std::string& data) {
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T));
}
template <typename T>
const T& check_size_t(const void* data, size_t size, size_t min_size, size_t max_size) {
return check_size_generic<const T, const void*>(data, size, min_size, max_size);
}
template <typename T>
const T& check_size_t(const void* data, size_t size, size_t max_size) {
return check_size_generic<const T, const void*>(data, size, sizeof(T), max_size);
}
template <typename T>
const T& check_size_t(const void* data, size_t size) {
return check_size_generic<const T, const void*>(data, size, sizeof(T), sizeof(T));
}
template <typename T>
T& check_size_t(void* data, size_t size, size_t min_size, size_t max_size) {
return check_size_generic<T, void*>(data, size, min_size, max_size);
}
template <typename T>
T& check_size_t(void* data, size_t size, size_t max_size) {
return check_size_generic<T, void*>(data, size, sizeof(T), max_size);
}
template <typename T>
T& check_size_t(void* data, size_t size) {
return check_size_generic<T, void*>(data, size, sizeof(T), sizeof(T));
}
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
std::string prepend_command_header(
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
const std::string& data);
+162 -162
View File
@@ -1,162 +1,162 @@
#include "PatchFileIndex.hh"
#include <stdio.h>
#include <string.h>
#include <functional>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Strings.hh>
#include <stdexcept>
#include "Loggers.hh"
using namespace std;
PatchFileIndex::File::File(PatchFileIndex* index)
: index(index),
crc32(0),
size(0) {}
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
if (!this->loaded_data) {
string relative_path = join(this->path_directories, "/") + "/" + this->name;
string full_path = this->index->root_dir + "/" + relative_path;
patch_index_log.info("Loading data for %s", relative_path.c_str());
this->loaded_data = make_shared<string>(load_file(full_path));
this->size = this->loaded_data->size();
}
return this->loaded_data;
}
PatchFileIndex::PatchFileIndex(const string& root_dir)
: root_dir(root_dir) {
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
JSON metadata_cache_json;
try {
string metadata_text = load_file(metadata_cache_filename);
metadata_cache_json = JSON::parse(metadata_text);
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
} catch (const exception& e) {
metadata_cache_json = JSON::dict();
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
}
// Assuming it's rare for patch files to change, we skip writing the metadata
// cache if no files were changed at all (which should usually be the case)
bool should_write_metadata_cache = false;
JSON new_metadata_cache_json = JSON::dict();
vector<string> path_directories;
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
path_directories.emplace_back(dir);
string relative_dirs = join(path_directories, "/");
string full_dir_path = root_dir + '/' + relative_dirs;
patch_index_log.info("Listing directory %s", full_dir_path.c_str());
for (const auto& item : list_directory(full_dir_path)) {
// Skip invisible files (e.g. .DS_Store on macOS)
if (starts_with(item, ".")) {
continue;
}
string relative_item_path = relative_dirs + '/' + item;
string full_item_path = root_dir + '/' + relative_item_path;
if (isdir(full_item_path)) {
collect_dir(item);
} else if (isfile(full_item_path)) {
auto st = stat(full_item_path);
auto f = make_shared<File>(this);
f->path_directories = path_directories;
f->name = item;
string compute_crc32s_message; // If not empty, should compute crc32s
JSON cache_item_json;
try {
cache_item_json = metadata_cache_json.at(relative_item_path);
uint64_t cached_size = cache_item_json.get_int(0);
uint64_t cached_mtime = cache_item_json.get_int(1);
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
throw runtime_error("file has been modified");
}
if (static_cast<uint64_t>(st.st_size) != cached_size) {
throw runtime_error("file size has changed");
}
f->size = cached_size;
f->crc32 = cache_item_json.get_int(2);
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
}
} catch (const exception& e) {
compute_crc32s_message = e.what();
}
if (!compute_crc32s_message.empty()) {
auto data = f->load_data(); // Sets f->size
f->crc32 = crc32(data->data(), f->size);
for (size_t x = 0; x < data->size(); x += 0x4000) {
size_t chunk_bytes = min<size_t>(f->size - x, 0x4000);
f->chunk_crcs.emplace_back(::crc32(data->data() + x, chunk_bytes));
}
// File was modified or cache item was missing; make a new cache item
auto chunk_crcs_item = JSON::list();
for (uint32_t chunk_crc : f->chunk_crcs) {
chunk_crcs_item.emplace_back(chunk_crc);
}
new_metadata_cache_json.emplace(
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
should_write_metadata_cache = true;
} else {
// File was not modified and cache item was valid; just use the
// existing cache item
new_metadata_cache_json.emplace(
relative_item_path, std::move(cache_item_json));
}
this->files_by_patch_order.emplace_back(f);
this->files_by_name.emplace(relative_item_path, f);
if (compute_crc32s_message.empty()) {
patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
} else {
patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])",
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
}
}
}
path_directories.pop_back();
};
collect_dir(".");
if (should_write_metadata_cache) {
try {
save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
} catch (const exception& e) {
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
}
} else {
patch_index_log.info("No files were modified; skipping metadata cache update");
}
}
const vector<shared_ptr<PatchFileIndex::File>>&
PatchFileIndex::all_files() const {
return this->files_by_patch_order;
}
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(
const string& filename) const {
return this->files_by_name.at(filename);
}
#include "PatchFileIndex.hh"
#include <stdio.h>
#include <string.h>
#include <functional>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Strings.hh>
#include <stdexcept>
#include "Loggers.hh"
using namespace std;
PatchFileIndex::File::File(PatchFileIndex* index)
: index(index),
crc32(0),
size(0) {}
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
if (!this->loaded_data) {
string relative_path = join(this->path_directories, "/") + "/" + this->name;
string full_path = this->index->root_dir + "/" + relative_path;
patch_index_log.info("Loading data for %s", relative_path.c_str());
this->loaded_data = make_shared<string>(load_file(full_path));
this->size = this->loaded_data->size();
}
return this->loaded_data;
}
PatchFileIndex::PatchFileIndex(const string& root_dir)
: root_dir(root_dir) {
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
JSON metadata_cache_json;
try {
string metadata_text = load_file(metadata_cache_filename);
metadata_cache_json = JSON::parse(metadata_text);
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
} catch (const exception& e) {
metadata_cache_json = JSON::dict();
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
}
// Assuming it's rare for patch files to change, we skip writing the metadata
// cache if no files were changed at all (which should usually be the case)
bool should_write_metadata_cache = false;
JSON new_metadata_cache_json = JSON::dict();
vector<string> path_directories;
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
path_directories.emplace_back(dir);
string relative_dirs = join(path_directories, "/");
string full_dir_path = root_dir + '/' + relative_dirs;
patch_index_log.info("Listing directory %s", full_dir_path.c_str());
for (const auto& item : list_directory(full_dir_path)) {
// Skip invisible files (e.g. .DS_Store on macOS)
if (starts_with(item, ".")) {
continue;
}
string relative_item_path = relative_dirs + '/' + item;
string full_item_path = root_dir + '/' + relative_item_path;
if (isdir(full_item_path)) {
collect_dir(item);
} else if (isfile(full_item_path)) {
auto st = stat(full_item_path);
auto f = make_shared<File>(this);
f->path_directories = path_directories;
f->name = item;
string compute_crc32s_message; // If not empty, should compute crc32s
JSON cache_item_json;
try {
cache_item_json = metadata_cache_json.at(relative_item_path);
uint64_t cached_size = cache_item_json.get_int(0);
uint64_t cached_mtime = cache_item_json.get_int(1);
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
throw runtime_error("file has been modified");
}
if (static_cast<uint64_t>(st.st_size) != cached_size) {
throw runtime_error("file size has changed");
}
f->size = cached_size;
f->crc32 = cache_item_json.get_int(2);
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
}
} catch (const exception& e) {
compute_crc32s_message = e.what();
}
if (!compute_crc32s_message.empty()) {
auto data = f->load_data(); // Sets f->size
f->crc32 = crc32(data->data(), f->size);
for (size_t x = 0; x < data->size(); x += 0x4000) {
size_t chunk_bytes = min<size_t>(f->size - x, 0x4000);
f->chunk_crcs.emplace_back(::crc32(data->data() + x, chunk_bytes));
}
// File was modified or cache item was missing; make a new cache item
auto chunk_crcs_item = JSON::list();
for (uint32_t chunk_crc : f->chunk_crcs) {
chunk_crcs_item.emplace_back(chunk_crc);
}
new_metadata_cache_json.emplace(
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
should_write_metadata_cache = true;
} else {
// File was not modified and cache item was valid; just use the
// existing cache item
new_metadata_cache_json.emplace(
relative_item_path, std::move(cache_item_json));
}
this->files_by_patch_order.emplace_back(f);
this->files_by_name.emplace(relative_item_path, f);
if (compute_crc32s_message.empty()) {
patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
} else {
patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])",
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
}
}
}
path_directories.pop_back();
};
collect_dir(".");
if (should_write_metadata_cache) {
try {
save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
} catch (const exception& e) {
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
}
} else {
patch_index_log.info("No files were modified; skipping metadata cache update");
}
}
const vector<shared_ptr<PatchFileIndex::File>>&
PatchFileIndex::all_files() const {
return this->files_by_patch_order;
}
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(
const string& filename) const {
return this->files_by_name.at(filename);
}
+52 -52
View File
@@ -1,52 +1,52 @@
#pragma once
#include <inttypes.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
struct PatchFileIndex {
explicit PatchFileIndex(const std::string& root_dir);
struct File {
PatchFileIndex* index;
std::vector<std::string> path_directories;
std::string name;
std::shared_ptr<const std::string> loaded_data;
std::vector<uint32_t> chunk_crcs;
uint32_t crc32;
uint32_t size;
explicit File(PatchFileIndex* index);
std::shared_ptr<const std::string> load_data();
};
const std::vector<std::shared_ptr<File>>& all_files() const;
std::shared_ptr<File> get(const std::string& filename) const;
private:
std::vector<std::shared_ptr<File>> files_by_patch_order;
std::unordered_map<std::string, std::shared_ptr<File>> files_by_name;
std::string root_dir;
};
struct PatchFileChecksumRequest {
std::shared_ptr<PatchFileIndex::File> file;
uint32_t crc32;
uint32_t size;
bool response_received;
explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file)
: file(file),
crc32(0),
size(0),
response_received(false) {}
inline bool needs_update() const {
return !this->response_received ||
(this->crc32 != this->file->crc32) ||
(this->size != this->file->size);
}
};
#pragma once
#include <inttypes.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
struct PatchFileIndex {
explicit PatchFileIndex(const std::string& root_dir);
struct File {
PatchFileIndex* index;
std::vector<std::string> path_directories;
std::string name;
std::shared_ptr<const std::string> loaded_data;
std::vector<uint32_t> chunk_crcs;
uint32_t crc32;
uint32_t size;
explicit File(PatchFileIndex* index);
std::shared_ptr<const std::string> load_data();
};
const std::vector<std::shared_ptr<File>>& all_files() const;
std::shared_ptr<File> get(const std::string& filename) const;
private:
std::vector<std::shared_ptr<File>> files_by_patch_order;
std::unordered_map<std::string, std::shared_ptr<File>> files_by_name;
std::string root_dir;
};
struct PatchFileChecksumRequest {
std::shared_ptr<PatchFileIndex::File> file;
uint32_t crc32;
uint32_t size;
bool response_received;
explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file)
: file(file),
crc32(0),
size(0),
response_received(false) {}
inline bool needs_update() const {
return !this->response_received ||
(this->crc32 != this->file->crc32) ||
(this->size != this->file->size);
}
};
+478 -478
View File
@@ -1,478 +1,478 @@
#include "PatchServer.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "EventUtils.hh"
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
static atomic<uint64_t> next_id(1);
PatchServer::Client::Client(
shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs)
: server(server),
id(next_id++),
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
channel(bev, 0, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
idle_timeout_usecs(idle_timeout_usecs),
idle_timeout_event(
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
event_free) {
this->reschedule_timeout_event();
// Don't print data sent to patch clients to the logs. The patch server
// protocol is fully understood and data logs for patch clients are generally
// more annoying than helpful at this point.
if (hide_data_from_logs) {
this->channel.terminal_recv_color = TerminalFormat::END;
this->channel.terminal_send_color = TerminalFormat::END;
}
this->log.info("Created");
}
void PatchServer::Client::reschedule_timeout_event() {
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &idle_tv);
}
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Client*>(ctx)->idle_timeout();
}
void PatchServer::Client::idle_timeout() {
this->log.info("Idle timeout expired");
auto s = this->server.lock();
if (s) {
auto c = this->shared_from_this();
s->disconnect_client(c);
} else {
this->channel.disconnect();
this->log.info("Server is deleted; cannot disconnect client");
}
}
void PatchServer::send_server_init(shared_ptr<Client> c) const {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
S_ServerInit_Patch_02 cmd;
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
cmd.server_key = server_key;
cmd.client_key = client_key;
c->channel.send(0x02, 0x00, cmd);
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
}
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
StringWriter w;
try {
if (c->version() == Version::PC_PATCH) {
w.write(tt_encode_marked_optional(text, c->channel.language, true));
} else if (c->version() == Version::BB_PATCH) {
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
} else {
throw logic_error("non-patch client on patch server");
}
} catch (const runtime_error& e) {
log_warning("Failed to encode message for patch message box command: %s", e.what());
return;
}
w.put_u16(0);
while (w.str().size() & 3) {
w.put_u8(0);
}
c->channel.send(0x13, 0x00, w.str());
}
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
c->channel.send(0x09, 0x00, cmd);
}
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
check_size_v(data.size(), 0);
c->channel.send(0x04, 0x00); // This requests the user's login information
}
void PatchServer::change_to_directory(
shared_ptr<Client> c,
vector<string>& client_path_directories,
const vector<string>& file_path_directories) const {
// First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
c->channel.send(0x0A, 0x00);
client_path_directories.pop_back();
}
// At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories");
}
for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path");
}
}
// Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()];
this->send_enter_directory(c, dir);
client_path_directories.emplace_back(dir);
}
}
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
string username = cmd.username.decode();
string password = cmd.password.decode();
// There are 3 cases here:
// - No login information at all: just proceed without checking credentials
// - Username: check that account exists if allow_unregistered_users is off
// - Username and password: call verify_bb
if (!username.empty() && !password.empty()) {
try {
this->config->account_index->from_bb_credentials(username, &password, false);
} catch (const AccountIndex::incorrect_password& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
} catch (const AccountIndex::missing_account& e) {
if (!this->config->allow_unregistered_users) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
} else if (!username.empty() && !this->config->allow_unregistered_users) {
try {
this->config->account_index->from_bb_credentials(username, nullptr, false);
} catch (const AccountIndex::missing_account& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
if (!this->config->message.empty()) {
this->send_message_box(c, this->config->message.c_str());
}
const auto& index = this->config->patch_file_index;
if (index.get()) {
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories;
for (const auto& file : index->all_files()) {
this->change_to_directory(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
c->channel.send(0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file);
}
this->change_to_directory(c, path_directories, {});
c->channel.send(0x0D, 0x00); // End of checksum requests
} else {
// No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files
this->send_enter_directory(c, ".");
this->send_enter_directory(c, "data");
this->send_enter_directory(c, "scene");
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x12, 0x00);
}
}
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum;
req.size = cmd.size;
req.response_received = true;
}
void PatchServer::on_10(shared_ptr<Client> c, string&) {
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) {
throw runtime_error("client did not respond to checksum request");
}
if (req.needs_update()) {
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
start_cmd.total_bytes += req.file->size;
start_cmd.num_files++;
} else {
c->log.info("File %s is up to date", req.file->name.c_str());
}
}
if (start_cmd.num_files) {
c->channel.send(0x11, 0x00, start_cmd);
vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) {
this->change_to_directory(c, path_directories, req.file->path_directories);
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
c->channel.send(0x06, 0x00, open_cmd);
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
auto data = req.file->load_data();
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
vector<pair<const void*, size_t>> blocks;
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
c->channel.send(0x07, 0x00, blocks);
}
S_CloseCurrentFile_Patch_08 close_cmd = {0};
c->channel.send(0x08, 0x00, close_cmd);
}
}
this->change_to_directory(c, path_directories, {});
}
c->channel.send(0x12, 0x00);
}
void PatchServer::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else {
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
}
this->channel_to_client.erase(&c->channel);
c->channel.disconnect();
// We can't just let c be destroyed here, since disconnect_client can be
// called from within the client's channel's receive handler. So, we instead
// move it to another set, which we'll clear in an immediately-enqueued
// callback after the current event. This will also call the client's
// disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients();
}
void PatchServer::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv);
}
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
}
void PatchServer::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
}
void PatchServer::dispatch_on_listen_error(
struct evconnlistener* listener, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
}
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr);
if (this->config->banned_ipv4_ranges->check(remote_addr)) {
close(fd);
return;
}
int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket;
try {
listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
close(fd);
return;
}
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>(
this->shared_from_this(),
bev,
listening_socket->version,
this->config->idle_timeout_usecs,
this->config->hide_data_from_logs);
c->channel.on_command_received = PatchServer::on_client_input;
c->channel.on_error = PatchServer::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
this->send_server_init(c);
}
void PatchServer::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr);
}
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
try {
switch (command) {
case 0x02:
server->on_02(c, data);
break;
case 0x04:
server->on_04(c, data);
break;
case 0x0F:
server->on_0F(c, data);
break;
case 0x10:
server->on_10(c, data);
break;
default:
throw runtime_error("invalid command");
}
} catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what());
}
}
void PatchServer::on_client_error(Channel& ch, short events) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c);
}
}
PatchServer::PatchServer(shared_ptr<const Config> config)
: config(config) {
if (config->shared_base) {
this->base = config->shared_base;
this->base_is_shared = true;
} else {
this->base.reset(event_base_new(), event_base_free);
this->base_is_shared = false;
}
this->destroy_clients_ev.reset(
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
if (!this->base_is_shared) {
this->th = thread(&PatchServer::thread_fn, this);
}
}
void PatchServer::schedule_stop() {
if (!this->base_is_shared) {
event_base_loopexit(this->base.get(), nullptr);
}
}
void PatchServer::wait_for_stop() {
if (!this->base_is_shared) {
this->th.join();
}
}
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version);
}
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
if (port == 0) {
this->listen(addr_str, addr, version);
} else {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version);
}
}
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
this->listen(addr_str, "", port, version);
}
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
: addr_str(addr_str),
fd(fd),
version(version),
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
}
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
}
void PatchServer::thread_fn() {
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
}
void PatchServer::set_config(std::shared_ptr<const Config> config) {
if (this->base_is_shared) {
this->config = config;
} else {
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
s->config = config;
});
}
}
#include "PatchServer.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "EventUtils.hh"
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
static atomic<uint64_t> next_id(1);
PatchServer::Client::Client(
shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs)
: server(server),
id(next_id++),
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
channel(bev, 0, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
idle_timeout_usecs(idle_timeout_usecs),
idle_timeout_event(
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
event_free) {
this->reschedule_timeout_event();
// Don't print data sent to patch clients to the logs. The patch server
// protocol is fully understood and data logs for patch clients are generally
// more annoying than helpful at this point.
if (hide_data_from_logs) {
this->channel.terminal_recv_color = TerminalFormat::END;
this->channel.terminal_send_color = TerminalFormat::END;
}
this->log.info("Created");
}
void PatchServer::Client::reschedule_timeout_event() {
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &idle_tv);
}
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Client*>(ctx)->idle_timeout();
}
void PatchServer::Client::idle_timeout() {
this->log.info("Idle timeout expired");
auto s = this->server.lock();
if (s) {
auto c = this->shared_from_this();
s->disconnect_client(c);
} else {
this->channel.disconnect();
this->log.info("Server is deleted; cannot disconnect client");
}
}
void PatchServer::send_server_init(shared_ptr<Client> c) const {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
S_ServerInit_Patch_02 cmd;
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
cmd.server_key = server_key;
cmd.client_key = client_key;
c->channel.send(0x02, 0x00, cmd);
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
}
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
StringWriter w;
try {
if (c->version() == Version::PC_PATCH) {
w.write(tt_encode_marked_optional(text, c->channel.language, true));
} else if (c->version() == Version::BB_PATCH) {
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
} else {
throw logic_error("non-patch client on patch server");
}
} catch (const runtime_error& e) {
log_warning("Failed to encode message for patch message box command: %s", e.what());
return;
}
w.put_u16(0);
while (w.str().size() & 3) {
w.put_u8(0);
}
c->channel.send(0x13, 0x00, w.str());
}
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
c->channel.send(0x09, 0x00, cmd);
}
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
check_size_v(data.size(), 0);
c->channel.send(0x04, 0x00); // This requests the user's login information
}
void PatchServer::change_to_directory(
shared_ptr<Client> c,
vector<string>& client_path_directories,
const vector<string>& file_path_directories) const {
// First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
c->channel.send(0x0A, 0x00);
client_path_directories.pop_back();
}
// At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories");
}
for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path");
}
}
// Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()];
this->send_enter_directory(c, dir);
client_path_directories.emplace_back(dir);
}
}
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
string username = cmd.username.decode();
string password = cmd.password.decode();
// There are 3 cases here:
// - No login information at all: just proceed without checking credentials
// - Username: check that account exists if allow_unregistered_users is off
// - Username and password: call verify_bb
if (!username.empty() && !password.empty()) {
try {
this->config->account_index->from_bb_credentials(username, &password, false);
} catch (const AccountIndex::incorrect_password& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
} catch (const AccountIndex::missing_account& e) {
if (!this->config->allow_unregistered_users) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
} else if (!username.empty() && !this->config->allow_unregistered_users) {
try {
this->config->account_index->from_bb_credentials(username, nullptr, false);
} catch (const AccountIndex::missing_account& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
if (!this->config->message.empty()) {
this->send_message_box(c, this->config->message.c_str());
}
const auto& index = this->config->patch_file_index;
if (index.get()) {
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories;
for (const auto& file : index->all_files()) {
this->change_to_directory(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
c->channel.send(0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file);
}
this->change_to_directory(c, path_directories, {});
c->channel.send(0x0D, 0x00); // End of checksum requests
} else {
// No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files
this->send_enter_directory(c, ".");
this->send_enter_directory(c, "data");
this->send_enter_directory(c, "scene");
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x12, 0x00);
}
}
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum;
req.size = cmd.size;
req.response_received = true;
}
void PatchServer::on_10(shared_ptr<Client> c, string&) {
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) {
throw runtime_error("client did not respond to checksum request");
}
if (req.needs_update()) {
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
start_cmd.total_bytes += req.file->size;
start_cmd.num_files++;
} else {
c->log.info("File %s is up to date", req.file->name.c_str());
}
}
if (start_cmd.num_files) {
c->channel.send(0x11, 0x00, start_cmd);
vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) {
this->change_to_directory(c, path_directories, req.file->path_directories);
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
c->channel.send(0x06, 0x00, open_cmd);
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
auto data = req.file->load_data();
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
vector<pair<const void*, size_t>> blocks;
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
c->channel.send(0x07, 0x00, blocks);
}
S_CloseCurrentFile_Patch_08 close_cmd = {0};
c->channel.send(0x08, 0x00, close_cmd);
}
}
this->change_to_directory(c, path_directories, {});
}
c->channel.send(0x12, 0x00);
}
void PatchServer::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else {
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
}
this->channel_to_client.erase(&c->channel);
c->channel.disconnect();
// We can't just let c be destroyed here, since disconnect_client can be
// called from within the client's channel's receive handler. So, we instead
// move it to another set, which we'll clear in an immediately-enqueued
// callback after the current event. This will also call the client's
// disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients();
}
void PatchServer::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv);
}
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
}
void PatchServer::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
}
void PatchServer::dispatch_on_listen_error(
struct evconnlistener* listener, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
}
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr);
if (this->config->banned_ipv4_ranges->check(remote_addr)) {
close(fd);
return;
}
int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket;
try {
listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
close(fd);
return;
}
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>(
this->shared_from_this(),
bev,
listening_socket->version,
this->config->idle_timeout_usecs,
this->config->hide_data_from_logs);
c->channel.on_command_received = PatchServer::on_client_input;
c->channel.on_error = PatchServer::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
this->send_server_init(c);
}
void PatchServer::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr);
}
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
try {
switch (command) {
case 0x02:
server->on_02(c, data);
break;
case 0x04:
server->on_04(c, data);
break;
case 0x0F:
server->on_0F(c, data);
break;
case 0x10:
server->on_10(c, data);
break;
default:
throw runtime_error("invalid command");
}
} catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what());
}
}
void PatchServer::on_client_error(Channel& ch, short events) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c);
}
}
PatchServer::PatchServer(shared_ptr<const Config> config)
: config(config) {
if (config->shared_base) {
this->base = config->shared_base;
this->base_is_shared = true;
} else {
this->base.reset(event_base_new(), event_base_free);
this->base_is_shared = false;
}
this->destroy_clients_ev.reset(
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
if (!this->base_is_shared) {
this->th = thread(&PatchServer::thread_fn, this);
}
}
void PatchServer::schedule_stop() {
if (!this->base_is_shared) {
event_base_loopexit(this->base.get(), nullptr);
}
}
void PatchServer::wait_for_stop() {
if (!this->base_is_shared) {
this->th.join();
}
}
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version);
}
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
if (port == 0) {
this->listen(addr_str, addr, version);
} else {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version);
}
}
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
this->listen(addr_str, "", port, version);
}
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
: addr_str(addr_str),
fd(fd),
version(version),
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
}
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
}
void PatchServer::thread_fn() {
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
}
void PatchServer::set_config(std::shared_ptr<const Config> config) {
if (this->base_is_shared) {
this->config = config;
} else {
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
s->config = config;
});
}
}
+131 -131
View File
@@ -1,131 +1,131 @@
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "Account.hh"
#include "Channel.hh"
#include "IPV4RangeSet.hh"
#include "PatchFileIndex.hh"
#include "Version.hh"
class PatchServer : public std::enable_shared_from_this<PatchServer> {
public:
struct Config {
bool allow_unregistered_users;
bool hide_data_from_logs;
uint64_t idle_timeout_usecs;
std::string message;
std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<const PatchFileIndex> patch_file_index;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<struct event_base> shared_base;
};
PatchServer() = delete;
explicit PatchServer(std::shared_ptr<const Config> config);
PatchServer(const PatchServer&) = delete;
PatchServer(PatchServer&&) = delete;
PatchServer& operator=(const PatchServer&) = delete;
PatchServer& operator=(PatchServer&&) = delete;
virtual ~PatchServer() = default;
void schedule_stop();
void wait_for_stop();
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
void listen(const std::string& addr_str, int port, Version version);
void add_socket(const std::string& addr_str, int fd, Version version);
void set_config(std::shared_ptr<const Config> config);
private:
class Client : public std::enable_shared_from_this<Client> {
public:
std::weak_ptr<PatchServer> server;
uint64_t id;
PrefixedLogger log;
Channel channel;
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Client(
std::shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs);
~Client() = default;
void reschedule_timeout_event();
inline Version version() const {
return this->channel.version;
}
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
};
struct ListeningSocket {
std::string addr_str;
int fd;
Version version;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
};
std::shared_ptr<struct event_base> base;
bool base_is_shared;
std::shared_ptr<const Config> config;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<struct event> destroy_clients_ev;
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::thread th;
void send_server_init(std::shared_ptr<Client> c) const;
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
void change_to_directory(
std::shared_ptr<Client> c,
std::vector<std::string>& client_path_directories,
const std::vector<std::string>& file_path_directories) const;
void on_02(std::shared_ptr<Client> c, std::string& data);
void on_04(std::shared_ptr<Client> c, std::string& data);
void on_0F(std::shared_ptr<Client> c, std::string& data);
void on_10(std::shared_ptr<Client> c, std::string& data);
void disconnect_client(std::shared_ptr<Client> c);
void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
void thread_fn();
};
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "Account.hh"
#include "Channel.hh"
#include "IPV4RangeSet.hh"
#include "PatchFileIndex.hh"
#include "Version.hh"
class PatchServer : public std::enable_shared_from_this<PatchServer> {
public:
struct Config {
bool allow_unregistered_users;
bool hide_data_from_logs;
uint64_t idle_timeout_usecs;
std::string message;
std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<const PatchFileIndex> patch_file_index;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<struct event_base> shared_base;
};
PatchServer() = delete;
explicit PatchServer(std::shared_ptr<const Config> config);
PatchServer(const PatchServer&) = delete;
PatchServer(PatchServer&&) = delete;
PatchServer& operator=(const PatchServer&) = delete;
PatchServer& operator=(PatchServer&&) = delete;
virtual ~PatchServer() = default;
void schedule_stop();
void wait_for_stop();
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
void listen(const std::string& addr_str, int port, Version version);
void add_socket(const std::string& addr_str, int fd, Version version);
void set_config(std::shared_ptr<const Config> config);
private:
class Client : public std::enable_shared_from_this<Client> {
public:
std::weak_ptr<PatchServer> server;
uint64_t id;
PrefixedLogger log;
Channel channel;
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Client(
std::shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs);
~Client() = default;
void reschedule_timeout_event();
inline Version version() const {
return this->channel.version;
}
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
};
struct ListeningSocket {
std::string addr_str;
int fd;
Version version;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
};
std::shared_ptr<struct event_base> base;
bool base_is_shared;
std::shared_ptr<const Config> config;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<struct event> destroy_clients_ev;
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::thread th;
void send_server_init(std::shared_ptr<Client> c) const;
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
void change_to_directory(
std::shared_ptr<Client> c,
std::vector<std::string>& client_path_directories,
const std::vector<std::string>& file_path_directories) const;
void on_02(std::shared_ptr<Client> c, std::string& data);
void on_04(std::shared_ptr<Client> c, std::string& data);
void on_0F(std::shared_ptr<Client> c, std::string& data);
void on_10(std::shared_ptr<Client> c, std::string& data);
void disconnect_client(std::shared_ptr<Client> c);
void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
void thread_fn();
};
+119 -119
View File
@@ -1,119 +1,119 @@
#include "PlayerFilesManager.hh"
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <stdexcept>
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
using namespace std;
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
: base(base),
clear_expired_files_event(
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
event_free) {
auto tv = usecs_to_timeval(30 * 1000 * 1000);
event_add(this->clear_expired_files_event.get(), &tv);
}
template <typename KeyT, typename ValueT>
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
size_t ret = 0;
for (auto it = m.begin(); it != m.end();) {
if (it->second.use_count() <= 1) {
it = m.erase(it);
ret++;
} else {
it++;
}
}
return ret;
}
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
try {
return this->loaded_system_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
try {
return this->loaded_character_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
try {
return this->loaded_guild_card_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) {
try {
return this->loaded_bank_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
if (!this->loaded_system_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
if (!this->loaded_character_files.emplace(filename, file).second) {
throw runtime_error("character file already loaded: " + filename);
}
}
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
}
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
size_t num_deleted = erase_unused(self->loaded_system_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_character_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_guild_card_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_bank_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
}
}
#include "PlayerFilesManager.hh"
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <stdexcept>
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
using namespace std;
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
: base(base),
clear_expired_files_event(
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
event_free) {
auto tv = usecs_to_timeval(30 * 1000 * 1000);
event_add(this->clear_expired_files_event.get(), &tv);
}
template <typename KeyT, typename ValueT>
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
size_t ret = 0;
for (auto it = m.begin(); it != m.end();) {
if (it->second.use_count() <= 1) {
it = m.erase(it);
ret++;
} else {
it++;
}
}
return ret;
}
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
try {
return this->loaded_system_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
try {
return this->loaded_character_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
try {
return this->loaded_guild_card_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) {
try {
return this->loaded_bank_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
if (!this->loaded_system_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
if (!this->loaded_character_files.emplace(filename, file).second) {
throw runtime_error("character file already loaded: " + filename);
}
}
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
}
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
size_t num_deleted = erase_unused(self->loaded_system_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_character_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_guild_card_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_bank_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
}
}
+47 -47
View File
@@ -1,47 +1,47 @@
#pragma once
#include <event2/event.h>
#include <inttypes.h>
#include <stddef.h>
#include <array>
#include <phosg/Encoding.hh>
#include <string>
#include <utility>
#include <vector>
#include "Episode3/DataIndexes.hh"
#include "ItemCreator.hh"
#include "ItemNameIndex.hh"
#include "LevelTable.hh"
#include "PlayerSubordinates.hh"
#include "SaveFileFormats.hh"
#include "Text.hh"
#include "Version.hh"
class PlayerFilesManager {
public:
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
~PlayerFilesManager() = default;
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
std::shared_ptr<PlayerBank200> get_bank(const std::string& filename);
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
private:
std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
};
#pragma once
#include <event2/event.h>
#include <inttypes.h>
#include <stddef.h>
#include <array>
#include <phosg/Encoding.hh>
#include <string>
#include <utility>
#include <vector>
#include "Episode3/DataIndexes.hh"
#include "ItemCreator.hh"
#include "ItemNameIndex.hh"
#include "LevelTable.hh"
#include "PlayerSubordinates.hh"
#include "SaveFileFormats.hh"
#include "Text.hh"
#include "Version.hh"
class PlayerFilesManager {
public:
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
~PlayerFilesManager() = default;
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
std::shared_ptr<PlayerBank200> get_bank(const std::string& filename);
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
private:
std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
};
+2376 -2376
View File
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -1,15 +1,15 @@
#pragma once
#include <stdint.h>
#include <string>
#include "ProxyServer.hh"
#include "ServerState.hh"
void on_proxy_command(
std::shared_ptr<ProxyServer::LinkedSession> ses,
bool from_server,
uint16_t command,
uint32_t flag,
std::string& data);
#pragma once
#include <stdint.h>
#include <string>
#include "ProxyServer.hh"
#include "ServerState.hh"
void on_proxy_command(
std::shared_ptr<ProxyServer::LinkedSession> ses,
bool from_server,
uint16_t command,
uint32_t flag,
std::string& data);
+1029 -1029
View File
File diff suppressed because it is too large Load Diff
+307 -307
View File
@@ -1,307 +1,307 @@
#pragma once
#include <event2/event.h>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <phosg/Filesystem.hh>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
public:
ProxyServer() = delete;
ProxyServer(const ProxyServer&) = delete;
ProxyServer(ProxyServer&&) = delete;
ProxyServer(
std::shared_ptr<struct event_base> base,
std::shared_ptr<ServerState> state);
virtual ~ProxyServer() = default;
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
std::weak_ptr<ProxyServer> server;
uint64_t id;
PrefixedLogger log;
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
std::shared_ptr<Login> login;
Channel client_channel;
Channel server_channel;
uint16_t local_port;
struct sockaddr_storage next_destination;
enum class DisconnectAction {
LONG_TIMEOUT = 0,
MEDIUM_TIMEOUT,
SHORT_TIMEOUT,
CLOSE_IMMEDIATELY,
};
DisconnectAction disconnect_action;
uint8_t prev_server_command_bytes[6];
uint32_t remote_ip_crc;
bool enable_remote_ip_crc_patch;
uint32_t sub_version;
std::string character_name;
std::string hardware_id; // Only used for DC sessions
std::string login_command_bb;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint32_t challenge_rank_color_override;
std::string challenge_rank_title_override;
int64_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
Client::Config config;
// A null handler in here means to forward the response to the remote server
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
RecentSwitchFlags recent_switch_flags; // used for switch assist
ItemData next_drop_item;
uint32_t next_item_id;
enum class DropMode {
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
DropMode drop_mode;
std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<Map> map;
struct LobbyPlayer {
uint32_t guild_card_number = 0;
uint64_t xb_user_id = 0;
std::string name;
uint8_t language = 0;
uint8_t section_id = 0;
uint8_t char_class = 0;
};
std::vector<LobbyPlayer> lobby_players;
size_t lobby_client_id;
size_t leader_client_id;
uint16_t floor;
float x;
float z;
bool is_in_game;
bool is_in_quest;
uint8_t lobby_event;
uint8_t lobby_difficulty;
uint8_t lobby_section_id;
GameMode lobby_mode;
Episode lobby_episode;
uint32_t lobby_random_seed;
uint64_t client_ping_start_time = 0;
uint64_t server_ping_start_time = 0;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
struct SavingFile {
std::string basename;
std::string output_filename;
bool is_download;
size_t remaining_bytes;
std::deque<std::string> blocks;
SavingFile(
const std::string& basename,
const std::string& output_filename,
size_t remaining_bytes,
bool is_download);
};
std::unordered_map<std::string, SavingFile> saving_files;
// TODO: This first constructor should be private
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
Version version);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
Version version,
std::shared_ptr<Login> login,
const Client::Config& config);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
Version version,
std::shared_ptr<Login> login,
const struct sockaddr_storage& next_destination);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
Version version,
const struct sockaddr_storage& next_destination);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const {
return this->client_channel.version;
}
inline uint8_t language() const {
return this->client_channel.language;
}
void set_version(Version v);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const std::string& character_name,
const std::string& hardware_id,
const XBNetworkLocation& xb_netloc,
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
std::string&& login_command_bb);
void resume(Channel&& client_channel);
void resume_inner(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
void connect();
static uint64_t timeout_for_disconnect_action(DisconnectAction action);
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
static void on_error(Channel& ch, short events);
void on_timeout();
void update_channel_names();
void clear_lobby_players(size_t num_slots);
void set_drop_mode(DropMode new_mode);
void send_to_game_server(const char* error_message = nullptr);
void disconnect();
bool is_connected() const;
};
std::shared_ptr<LinkedSession> get_session() const;
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
std::shared_ptr<LinkedSession> create_logged_in_session(
std::shared_ptr<Login> login,
uint16_t local_port,
Version version,
const Client::Config& config);
void delete_session(uint64_t id);
size_t num_sessions() const;
size_t delete_disconnected_sessions();
private:
struct ListeningSocket {
ProxyServer* server;
PrefixedLogger log;
uint16_t port;
scoped_fd fd;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
Version version;
struct sockaddr_storage default_destination;
ListeningSocket(
ProxyServer* server,
const std::string& addr,
uint16_t port,
Version version,
const struct sockaddr_storage* default_destination);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(int fd);
void on_listen_error();
};
struct UnlinkedSession {
std::weak_ptr<ProxyServer> server;
uint64_t id;
PrefixedLogger log;
Channel channel;
uint16_t local_port;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
// Temporary state used just before resuming a LinkedSession. These aren't
// just local variables inside on_input because XB requires two commands to
// get started (9E and 9F), so we need to store this state somewhere between
// those commands.
std::shared_ptr<Login> login;
uint32_t sub_version = 0;
std::string character_name;
Client::Config config;
std::string login_command_bb;
std::string hardware_id;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
UnlinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
struct bufferevent* bev,
uint64_t virtual_network_id,
uint16_t port,
Version version);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const {
return this->channel.version;
}
void receive_and_process_commands();
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void on_error(Channel& ch, short events);
};
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_sessions_ev;
std::shared_ptr<ServerState> state;
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session;
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
uint64_t next_unlinked_session_id;
uint64_t next_logged_out_session_id;
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
void destroy_sessions();
void on_client_connect(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint16_t listen_port,
Version version,
const struct sockaddr_storage* default_destination);
static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000;
static constexpr uint64_t MIN_LINKED_LOGGED_OUT_SESSION_ID = 0x1000000000000000;
};
#pragma once
#include <event2/event.h>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <phosg/Filesystem.hh>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
public:
ProxyServer() = delete;
ProxyServer(const ProxyServer&) = delete;
ProxyServer(ProxyServer&&) = delete;
ProxyServer(
std::shared_ptr<struct event_base> base,
std::shared_ptr<ServerState> state);
virtual ~ProxyServer() = default;
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
std::weak_ptr<ProxyServer> server;
uint64_t id;
PrefixedLogger log;
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
std::shared_ptr<Login> login;
Channel client_channel;
Channel server_channel;
uint16_t local_port;
struct sockaddr_storage next_destination;
enum class DisconnectAction {
LONG_TIMEOUT = 0,
MEDIUM_TIMEOUT,
SHORT_TIMEOUT,
CLOSE_IMMEDIATELY,
};
DisconnectAction disconnect_action;
uint8_t prev_server_command_bytes[6];
uint32_t remote_ip_crc;
bool enable_remote_ip_crc_patch;
uint32_t sub_version;
std::string character_name;
std::string hardware_id; // Only used for DC sessions
std::string login_command_bb;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint32_t challenge_rank_color_override;
std::string challenge_rank_title_override;
int64_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
Client::Config config;
// A null handler in here means to forward the response to the remote server
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
RecentSwitchFlags recent_switch_flags; // used for switch assist
ItemData next_drop_item;
uint32_t next_item_id;
enum class DropMode {
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
DropMode drop_mode;
std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<Map> map;
struct LobbyPlayer {
uint32_t guild_card_number = 0;
uint64_t xb_user_id = 0;
std::string name;
uint8_t language = 0;
uint8_t section_id = 0;
uint8_t char_class = 0;
};
std::vector<LobbyPlayer> lobby_players;
size_t lobby_client_id;
size_t leader_client_id;
uint16_t floor;
float x;
float z;
bool is_in_game;
bool is_in_quest;
uint8_t lobby_event;
uint8_t lobby_difficulty;
uint8_t lobby_section_id;
GameMode lobby_mode;
Episode lobby_episode;
uint32_t lobby_random_seed;
uint64_t client_ping_start_time = 0;
uint64_t server_ping_start_time = 0;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
struct SavingFile {
std::string basename;
std::string output_filename;
bool is_download;
size_t remaining_bytes;
std::deque<std::string> blocks;
SavingFile(
const std::string& basename,
const std::string& output_filename,
size_t remaining_bytes,
bool is_download);
};
std::unordered_map<std::string, SavingFile> saving_files;
// TODO: This first constructor should be private
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
Version version);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
Version version,
std::shared_ptr<Login> login,
const Client::Config& config);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
Version version,
std::shared_ptr<Login> login,
const struct sockaddr_storage& next_destination);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
Version version,
const struct sockaddr_storage& next_destination);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const {
return this->client_channel.version;
}
inline uint8_t language() const {
return this->client_channel.language;
}
void set_version(Version v);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const std::string& character_name,
const std::string& hardware_id,
const XBNetworkLocation& xb_netloc,
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
std::string&& login_command_bb);
void resume(Channel&& client_channel);
void resume_inner(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
void connect();
static uint64_t timeout_for_disconnect_action(DisconnectAction action);
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
static void on_error(Channel& ch, short events);
void on_timeout();
void update_channel_names();
void clear_lobby_players(size_t num_slots);
void set_drop_mode(DropMode new_mode);
void send_to_game_server(const char* error_message = nullptr);
void disconnect();
bool is_connected() const;
};
std::shared_ptr<LinkedSession> get_session() const;
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
std::shared_ptr<LinkedSession> create_logged_in_session(
std::shared_ptr<Login> login,
uint16_t local_port,
Version version,
const Client::Config& config);
void delete_session(uint64_t id);
size_t num_sessions() const;
size_t delete_disconnected_sessions();
private:
struct ListeningSocket {
ProxyServer* server;
PrefixedLogger log;
uint16_t port;
scoped_fd fd;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
Version version;
struct sockaddr_storage default_destination;
ListeningSocket(
ProxyServer* server,
const std::string& addr,
uint16_t port,
Version version,
const struct sockaddr_storage* default_destination);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(int fd);
void on_listen_error();
};
struct UnlinkedSession {
std::weak_ptr<ProxyServer> server;
uint64_t id;
PrefixedLogger log;
Channel channel;
uint16_t local_port;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
// Temporary state used just before resuming a LinkedSession. These aren't
// just local variables inside on_input because XB requires two commands to
// get started (9E and 9F), so we need to store this state somewhere between
// those commands.
std::shared_ptr<Login> login;
uint32_t sub_version = 0;
std::string character_name;
Client::Config config;
std::string login_command_bb;
std::string hardware_id;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
UnlinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
struct bufferevent* bev,
uint64_t virtual_network_id,
uint16_t port,
Version version);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const {
return this->channel.version;
}
void receive_and_process_commands();
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void on_error(Channel& ch, short events);
};
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_sessions_ev;
std::shared_ptr<ServerState> state;
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session;
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
uint64_t next_unlinked_session_id;
uint64_t next_logged_out_session_id;
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
void destroy_sessions();
void on_client_connect(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint16_t listen_port,
Version version,
const struct sockaddr_storage* default_destination);
static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000;
static constexpr uint64_t MIN_LINKED_LOGGED_OUT_SESSION_ID = 0x1000000000000000;
};
+1381 -1381
View File
File diff suppressed because it is too large Load Diff
+202 -202
View File
@@ -1,202 +1,202 @@
#pragma once
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "IntegralExpression.hh"
#include "PlayerSubordinates.hh"
#include "QuestScript.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
enum class QuestFileFormat {
BIN_DAT = 0,
BIN_DAT_UNCOMPRESSED,
BIN_DAT_GCI,
BIN_DAT_VMS,
BIN_DAT_DLQ,
QST,
};
enum class QuestMenuType {
NORMAL = 0,
BATTLE = 1,
CHALLENGE = 2,
SOLO = 3,
GOVERNMENT = 4,
DOWNLOAD = 5,
EP3_DOWNLOAD = 6,
// 7 can't be used as a menu type (it enables the per-episode filter)
};
struct QuestCategoryIndex {
struct Category {
uint32_t category_id;
uint16_t enabled_flags;
std::string directory_name;
std::string name;
std::string description;
explicit Category(uint32_t category_id, const JSON& json);
[[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const {
return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type));
}
[[nodiscard]] inline bool enable_episode_filter() const {
return this->enabled_flags & 0x080;
}
[[nodiscard]] inline bool use_ep2_icon() const {
return this->enabled_flags & 0x100;
}
};
std::vector<std::shared_ptr<Category>> categories;
explicit QuestCategoryIndex(const JSON& json);
std::shared_ptr<const Category> at(uint32_t category_id) const;
};
struct VersionedQuest {
uint32_t quest_number;
uint32_t category_id;
Episode episode;
bool allow_start_from_chat_command;
bool joinable;
int16_t lock_status_register;
std::string name;
Version version;
uint8_t language;
bool is_dlq_encoded;
std::string short_description;
std::string long_description;
std::shared_ptr<const std::string> bin_contents;
std::shared_ptr<const std::string> dat_contents;
std::shared_ptr<const std::string> dat_contents_decompressed;
std::shared_ptr<const std::string> pvr_contents;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
VersionedQuest(
uint32_t quest_number,
uint32_t category_id,
Version version,
uint8_t language,
std::shared_ptr<const std::string> bin_contents,
std::shared_ptr<const std::string> dat_contents,
std::shared_ptr<const std::string> pvr_contents,
std::shared_ptr<const BattleRules> battle_rules = nullptr,
ssize_t challenge_template_index = -1,
uint8_t description_flag = 0,
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
bool allow_start_from_chat_command = false,
bool force_joinable = false,
int16_t lock_status_register = -1);
std::string bin_filename() const;
std::string dat_filename() const;
std::string pvr_filename() const;
std::string xb_filename() const;
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
std::string encode_qst() const;
};
class Quest {
public:
Quest() = delete;
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
Quest(const Quest&) = default;
Quest(Quest&&) = default;
Quest& operator=(const Quest&) = default;
Quest& operator=(Quest&&) = default;
void add_version(std::shared_ptr<const VersionedQuest> vq);
bool has_version(Version v, uint8_t language) const;
bool has_version_any_language(Version v) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
static uint32_t versions_key(Version v, uint8_t language);
uint32_t quest_number;
uint32_t category_id;
Episode episode;
bool allow_start_from_chat_command;
bool joinable;
int16_t lock_status_register;
std::string name;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
};
struct QuestIndex {
enum class IncludeState {
HIDDEN = 0,
AVAILABLE,
DISABLED,
};
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
std::string directory;
std::shared_ptr<const QuestCategoryIndex> category_index;
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
std::shared_ptr<const Quest> get(const std::string& name) const;
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
QuestMenuType menu_type,
Episode episode,
Version version,
IncludeCondition include_condition = nullptr) const;
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
Episode episode,
Version version,
uint32_t category_id,
IncludeCondition include_condition = nullptr,
size_t limit = 0) const;
};
std::string encode_download_quest_data(
const std::string& compressed_data,
size_t decompressed_size = 0,
uint32_t encryption_seed = 0);
std::string decode_gci_data(
const std::string& data,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1,
bool skip_checksum = false);
std::string decode_vms_data(
const std::string& data,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1,
bool skip_checksum = false);
std::string decode_dlq_data(const std::string& data);
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data);
std::string encode_qst_file(
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
const std::string& name,
uint32_t quest_number,
const std::string& xb_filename,
Version version,
bool is_dlq_encoded);
#pragma once
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "IntegralExpression.hh"
#include "PlayerSubordinates.hh"
#include "QuestScript.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
enum class QuestFileFormat {
BIN_DAT = 0,
BIN_DAT_UNCOMPRESSED,
BIN_DAT_GCI,
BIN_DAT_VMS,
BIN_DAT_DLQ,
QST,
};
enum class QuestMenuType {
NORMAL = 0,
BATTLE = 1,
CHALLENGE = 2,
SOLO = 3,
GOVERNMENT = 4,
DOWNLOAD = 5,
EP3_DOWNLOAD = 6,
// 7 can't be used as a menu type (it enables the per-episode filter)
};
struct QuestCategoryIndex {
struct Category {
uint32_t category_id;
uint16_t enabled_flags;
std::string directory_name;
std::string name;
std::string description;
explicit Category(uint32_t category_id, const JSON& json);
[[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const {
return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type));
}
[[nodiscard]] inline bool enable_episode_filter() const {
return this->enabled_flags & 0x080;
}
[[nodiscard]] inline bool use_ep2_icon() const {
return this->enabled_flags & 0x100;
}
};
std::vector<std::shared_ptr<Category>> categories;
explicit QuestCategoryIndex(const JSON& json);
std::shared_ptr<const Category> at(uint32_t category_id) const;
};
struct VersionedQuest {
uint32_t quest_number;
uint32_t category_id;
Episode episode;
bool allow_start_from_chat_command;
bool joinable;
int16_t lock_status_register;
std::string name;
Version version;
uint8_t language;
bool is_dlq_encoded;
std::string short_description;
std::string long_description;
std::shared_ptr<const std::string> bin_contents;
std::shared_ptr<const std::string> dat_contents;
std::shared_ptr<const std::string> dat_contents_decompressed;
std::shared_ptr<const std::string> pvr_contents;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
VersionedQuest(
uint32_t quest_number,
uint32_t category_id,
Version version,
uint8_t language,
std::shared_ptr<const std::string> bin_contents,
std::shared_ptr<const std::string> dat_contents,
std::shared_ptr<const std::string> pvr_contents,
std::shared_ptr<const BattleRules> battle_rules = nullptr,
ssize_t challenge_template_index = -1,
uint8_t description_flag = 0,
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
bool allow_start_from_chat_command = false,
bool force_joinable = false,
int16_t lock_status_register = -1);
std::string bin_filename() const;
std::string dat_filename() const;
std::string pvr_filename() const;
std::string xb_filename() const;
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
std::string encode_qst() const;
};
class Quest {
public:
Quest() = delete;
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
Quest(const Quest&) = default;
Quest(Quest&&) = default;
Quest& operator=(const Quest&) = default;
Quest& operator=(Quest&&) = default;
void add_version(std::shared_ptr<const VersionedQuest> vq);
bool has_version(Version v, uint8_t language) const;
bool has_version_any_language(Version v) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
static uint32_t versions_key(Version v, uint8_t language);
uint32_t quest_number;
uint32_t category_id;
Episode episode;
bool allow_start_from_chat_command;
bool joinable;
int16_t lock_status_register;
std::string name;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
};
struct QuestIndex {
enum class IncludeState {
HIDDEN = 0,
AVAILABLE,
DISABLED,
};
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
std::string directory;
std::shared_ptr<const QuestCategoryIndex> category_index;
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
std::shared_ptr<const Quest> get(const std::string& name) const;
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
QuestMenuType menu_type,
Episode episode,
Version version,
IncludeCondition include_condition = nullptr) const;
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
Episode episode,
Version version,
uint32_t category_id,
IncludeCondition include_condition = nullptr,
size_t limit = 0) const;
};
std::string encode_download_quest_data(
const std::string& compressed_data,
size_t decompressed_size = 0,
uint32_t encryption_seed = 0);
std::string decode_gci_data(
const std::string& data,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1,
bool skip_checksum = false);
std::string decode_vms_data(
const std::string& data,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1,
bool skip_checksum = false);
std::string decode_dlq_data(const std::string& data);
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data);
std::string encode_qst_file(
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
const std::string& name,
uint32_t quest_number,
const std::string& xb_filename,
Version version,
bool is_dlq_encoded);
+653 -653
View File
File diff suppressed because it is too large Load Diff
+113 -113
View File
@@ -1,113 +1,113 @@
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include "AFSArchive.hh"
#include "GSLArchive.hh"
#include "ItemNameIndex.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class RareItemSet {
public:
struct ExpandedDrop {
uint32_t probability = 0;
ItemData data;
std::string str() const;
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
};
RareItemSet();
RareItemSet(const AFSArchive& afs, bool is_v1);
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
RareItemSet(const std::string& rel, bool is_big_endian);
RareItemSet(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
~RareItemSet() = default;
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
std::string serialize_afs(bool is_v1) const;
std::string serialize_gsl(bool big_endian) const;
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void multiply_all_rates(double factor);
void print_collection(
FILE* stream,
GameMode mode,
Episode episode,
uint8_t difficulty,
uint8_t section_id,
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
protected:
struct SpecCollection {
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
};
struct ParsedRELData {
struct PackedDrop {
uint8_t probability = 0;
parray<uint8_t, 3> item_code;
PackedDrop() = default;
explicit PackedDrop(const ExpandedDrop&);
ExpandedDrop expand() const;
} __packed_ws__(PackedDrop, 4);
template <bool IsBigEndian>
struct OffsetsT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
/* 04 */ U32T box_count; // Usually 30 (0x1E)
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
/* 10 */
} __packed__;
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
check_struct_size(Offsets, 0x10);
check_struct_size(OffsetsBE, 0x10);
struct BoxRare {
uint8_t area;
ExpandedDrop drop;
};
std::vector<ExpandedDrop> monster_rares;
std::vector<BoxRare> box_rares;
ParsedRELData() = default;
ParsedRELData(StringReader r, bool big_endian, bool is_v1);
explicit ParsedRELData(const SpecCollection& collection);
std::string serialize(bool big_endian, bool is_v1) const;
template <bool IsBigEndian>
void parse_t(StringReader r, bool is_v1);
template <bool IsBigEndian>
std::string serialize_t(bool is_v1) const;
SpecCollection as_collection() const;
};
std::unordered_map<uint16_t, SpecCollection> collections;
const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const;
static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id);
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
static uint32_t expand_rate(uint8_t pc);
static uint8_t compress_rate(uint32_t probability);
};
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include "AFSArchive.hh"
#include "GSLArchive.hh"
#include "ItemNameIndex.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class RareItemSet {
public:
struct ExpandedDrop {
uint32_t probability = 0;
ItemData data;
std::string str() const;
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
};
RareItemSet();
RareItemSet(const AFSArchive& afs, bool is_v1);
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
RareItemSet(const std::string& rel, bool is_big_endian);
RareItemSet(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
~RareItemSet() = default;
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
std::string serialize_afs(bool is_v1) const;
std::string serialize_gsl(bool big_endian) const;
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void multiply_all_rates(double factor);
void print_collection(
FILE* stream,
GameMode mode,
Episode episode,
uint8_t difficulty,
uint8_t section_id,
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
protected:
struct SpecCollection {
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
};
struct ParsedRELData {
struct PackedDrop {
uint8_t probability = 0;
parray<uint8_t, 3> item_code;
PackedDrop() = default;
explicit PackedDrop(const ExpandedDrop&);
ExpandedDrop expand() const;
} __packed_ws__(PackedDrop, 4);
template <bool IsBigEndian>
struct OffsetsT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
/* 04 */ U32T box_count; // Usually 30 (0x1E)
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
/* 10 */
} __packed__;
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
check_struct_size(Offsets, 0x10);
check_struct_size(OffsetsBE, 0x10);
struct BoxRare {
uint8_t area;
ExpandedDrop drop;
};
std::vector<ExpandedDrop> monster_rares;
std::vector<BoxRare> box_rares;
ParsedRELData() = default;
ParsedRELData(StringReader r, bool big_endian, bool is_v1);
explicit ParsedRELData(const SpecCollection& collection);
std::string serialize(bool big_endian, bool is_v1) const;
template <bool IsBigEndian>
void parse_t(StringReader r, bool is_v1);
template <bool IsBigEndian>
std::string serialize_t(bool is_v1) const;
SpecCollection as_collection() const;
};
std::unordered_map<uint16_t, SpecCollection> collections;
const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const;
static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id);
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
static uint32_t expand_rate(uint8_t pc);
static uint8_t compress_rate(uint32_t probability);
};
+5676 -5676
View File
File diff suppressed because it is too large Load Diff
+31 -31
View File
@@ -1,31 +1,31 @@
#pragma once
#include <memory>
#include <string>
#include "Client.hh"
#include "ServerState.hh"
std::shared_ptr<Lobby> create_game_generic(
std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c,
const std::string& name,
const std::string& password = "",
Episode episode = Episode::EP1,
GameMode mode = GameMode::NORMAL,
uint8_t difficulty = 0,
bool allow_v1 = false,
std::shared_ptr<Lobby> watched_lobby = nullptr,
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
void set_lobby_quest(std::shared_ptr<Lobby> l, std::shared_ptr<const Quest> q, bool substitute_v3_for_ep3 = false);
void on_connect(std::shared_ptr<Client> c);
void on_disconnect(std::shared_ptr<Client> c);
void on_login_complete(std::shared_ptr<Client> c);
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
void send_client_to_login_server(std::shared_ptr<Client> c);
void send_client_to_lobby_server(std::shared_ptr<Client> c);
void send_client_to_proxy_server(std::shared_ptr<Client> c);
#pragma once
#include <memory>
#include <string>
#include "Client.hh"
#include "ServerState.hh"
std::shared_ptr<Lobby> create_game_generic(
std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c,
const std::string& name,
const std::string& password = "",
Episode episode = Episode::EP1,
GameMode mode = GameMode::NORMAL,
uint8_t difficulty = 0,
bool allow_v1 = false,
std::shared_ptr<Lobby> watched_lobby = nullptr,
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
void set_lobby_quest(std::shared_ptr<Lobby> l, std::shared_ptr<const Quest> q, bool substitute_v3_for_ep3 = false);
void on_connect(std::shared_ptr<Client> c);
void on_disconnect(std::shared_ptr<Client> c);
void on_login_complete(std::shared_ptr<Client> c);
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
void send_client_to_login_server(std::shared_ptr<Client> c);
void send_client_to_lobby_server(std::shared_ptr<Client> c);
void send_client_to_proxy_server(std::shared_ptr<Client> c);
+4788 -4788
View File
File diff suppressed because it is too large Load Diff
+124 -124
View File
@@ -1,124 +1,124 @@
#pragma once
#include <stdint.h>
#include "Client.hh"
#include "CommandFormats.hh"
#include "Lobby.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
bool subcommand_is_implemented(uint8_t which);
void send_item_notification_if_needed(
std::shared_ptr<ServerState> s,
Channel& ch,
const Client::Config& config,
const ItemData& item,
bool is_from_rare_table);
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
struct DropReconcileResult {
uint8_t effective_rt_index;
bool is_box;
bool should_drop;
bool ignore_def;
};
DropReconcileResult reconcile_drop_request_with_map(
PrefixedLogger& log,
Channel& client_channel,
G_SpecializableItemDropRequest_6xA2& cmd,
Version version,
Episode episode,
const Client::Config& config,
std::shared_ptr<Map> map,
bool mark_drop);
class Parsed6x70Data {
public:
Version from_version;
bool from_client_customization;
Version item_version;
G_SyncPlayerDispAndInventory_BaseDCNTE base;
uint32_t unknown_a5_nte = 0;
uint32_t unknown_a6_nte = 0;
uint16_t bonus_hp_from_materials = 0;
uint16_t bonus_tp_from_materials = 0;
parray<uint8_t, 0x10> unknown_a5_112000;
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
uint32_t language = 0;
uint32_t player_tag = 0;
uint32_t guild_card_number = 0;
uint32_t unknown_a6 = 0;
uint32_t battle_team_number = 0;
Telepipe6x70 telepipe;
uint32_t unknown_a8 = 0;
parray<uint8_t, 0x10> unknown_a9_nte_112000;
G_Unknown_6x70_SubA1 unknown_a9_final;
uint32_t area = 0;
uint32_t flags2 = 0;
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
PlayerVisualConfig visual;
std::string name;
PlayerStats stats;
uint32_t num_items = 0;
parray<PlayerInventoryItem, 0x1E> items;
uint32_t floor = 0;
uint64_t xb_user_id = 0;
uint32_t xb_unknown_a16 = 0;
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
uint32_t guild_card_number,
uint8_t language,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr<ServerState> s, Version to_version) const;
G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr<ServerState> s, Version to_version) const;
G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
uint64_t default_xb_user_id() const;
void clear_v1_unused_item_fields();
void clear_dc_protos_unused_item_fields();
protected:
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BaseV1& base,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const;
};
#pragma once
#include <stdint.h>
#include "Client.hh"
#include "CommandFormats.hh"
#include "Lobby.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
bool subcommand_is_implemented(uint8_t which);
void send_item_notification_if_needed(
std::shared_ptr<ServerState> s,
Channel& ch,
const Client::Config& config,
const ItemData& item,
bool is_from_rare_table);
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
struct DropReconcileResult {
uint8_t effective_rt_index;
bool is_box;
bool should_drop;
bool ignore_def;
};
DropReconcileResult reconcile_drop_request_with_map(
PrefixedLogger& log,
Channel& client_channel,
G_SpecializableItemDropRequest_6xA2& cmd,
Version version,
Episode episode,
const Client::Config& config,
std::shared_ptr<Map> map,
bool mark_drop);
class Parsed6x70Data {
public:
Version from_version;
bool from_client_customization;
Version item_version;
G_SyncPlayerDispAndInventory_BaseDCNTE base;
uint32_t unknown_a5_nte = 0;
uint32_t unknown_a6_nte = 0;
uint16_t bonus_hp_from_materials = 0;
uint16_t bonus_tp_from_materials = 0;
parray<uint8_t, 0x10> unknown_a5_112000;
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
uint32_t language = 0;
uint32_t player_tag = 0;
uint32_t guild_card_number = 0;
uint32_t unknown_a6 = 0;
uint32_t battle_team_number = 0;
Telepipe6x70 telepipe;
uint32_t unknown_a8 = 0;
parray<uint8_t, 0x10> unknown_a9_nte_112000;
G_Unknown_6x70_SubA1 unknown_a9_final;
uint32_t area = 0;
uint32_t flags2 = 0;
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
PlayerVisualConfig visual;
std::string name;
PlayerStats stats;
uint32_t num_items = 0;
parray<PlayerInventoryItem, 0x1E> items;
uint32_t floor = 0;
uint64_t xb_user_id = 0;
uint32_t xb_unknown_a16 = 0;
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
uint32_t guild_card_number,
uint8_t language,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr<ServerState> s, Version to_version) const;
G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr<ServerState> s, Version to_version) const;
G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr<ServerState> s) const;
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
uint64_t default_xb_user_id() const;
void clear_v1_unused_item_fields();
void clear_dc_protos_unused_item_fields();
protected:
Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BaseV1& base,
uint32_t guild_card_number,
Version from_version,
bool from_client_customization);
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const;
};
+810 -810
View File
File diff suppressed because it is too large Load Diff
+99 -99
View File
@@ -1,99 +1,99 @@
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <stdio.h>
#include <deque>
#include <memory>
#include <string>
#include "Channel.hh"
#include "ServerState.hh"
#include "Version.hh"
class ReplaySession {
public:
ReplaySession(
std::shared_ptr<struct event_base> base,
FILE* input_log,
std::shared_ptr<ServerState> state,
bool require_basic_credentials);
ReplaySession(const ReplaySession&) = delete;
ReplaySession(ReplaySession&&) = delete;
ReplaySession& operator=(const ReplaySession&) = delete;
ReplaySession& operator=(ReplaySession&&) = delete;
~ReplaySession() = default;
void start();
private:
struct Event {
enum class Type {
CONNECT = 0,
DISCONNECT,
SEND,
RECEIVE,
};
Type type;
uint64_t client_id;
std::string data; // Only used for SEND and RECEIVE
std::string mask; // Only used for RECEIVE
bool allow_size_disparity;
bool complete;
size_t line_num;
std::shared_ptr<Event> next_event;
Event(Type type, uint64_t client_id, size_t line_num);
std::string str() const;
};
struct Client {
uint64_t id;
uint16_t port;
Version version;
Channel channel;
std::deque<std::shared_ptr<Event>> receive_events;
std::shared_ptr<Event> disconnect_event;
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
std::string str() const;
};
std::shared_ptr<ServerState> state;
bool require_basic_credentials;
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::shared_ptr<Event> first_event;
std::shared_ptr<Event> last_event;
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> timeout_ev;
size_t commands_sent;
size_t bytes_sent;
size_t commands_received;
size_t bytes_received;
std::shared_ptr<ReplaySession::Event> create_event(
Event::Type type, std::shared_ptr<Client> c, size_t line_num);
void update_timeout_event();
void apply_default_mask(std::shared_ptr<Event> ev);
void check_for_password(std::shared_ptr<const Event> ev) const;
static void dispatch_on_timeout(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_command_received(
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void dispatch_on_error(Channel& ch, short events);
void on_command_received(
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_error(std::shared_ptr<Client> c, short events);
void execute_pending_events();
};
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <stdio.h>
#include <deque>
#include <memory>
#include <string>
#include "Channel.hh"
#include "ServerState.hh"
#include "Version.hh"
class ReplaySession {
public:
ReplaySession(
std::shared_ptr<struct event_base> base,
FILE* input_log,
std::shared_ptr<ServerState> state,
bool require_basic_credentials);
ReplaySession(const ReplaySession&) = delete;
ReplaySession(ReplaySession&&) = delete;
ReplaySession& operator=(const ReplaySession&) = delete;
ReplaySession& operator=(ReplaySession&&) = delete;
~ReplaySession() = default;
void start();
private:
struct Event {
enum class Type {
CONNECT = 0,
DISCONNECT,
SEND,
RECEIVE,
};
Type type;
uint64_t client_id;
std::string data; // Only used for SEND and RECEIVE
std::string mask; // Only used for RECEIVE
bool allow_size_disparity;
bool complete;
size_t line_num;
std::shared_ptr<Event> next_event;
Event(Type type, uint64_t client_id, size_t line_num);
std::string str() const;
};
struct Client {
uint64_t id;
uint16_t port;
Version version;
Channel channel;
std::deque<std::shared_ptr<Event>> receive_events;
std::shared_ptr<Event> disconnect_event;
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
std::string str() const;
};
std::shared_ptr<ServerState> state;
bool require_basic_credentials;
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::shared_ptr<Event> first_event;
std::shared_ptr<Event> last_event;
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> timeout_ev;
size_t commands_sent;
size_t bytes_sent;
size_t commands_received;
size_t bytes_received;
std::shared_ptr<ReplaySession::Event> create_event(
Event::Type type, std::shared_ptr<Client> c, size_t line_num);
void update_timeout_event();
void apply_default_mask(std::shared_ptr<Event> ev);
void check_for_password(std::shared_ptr<const Event> ev) const;
static void dispatch_on_timeout(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_command_received(
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void dispatch_on_error(Channel& ch, short events);
void on_command_received(
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_error(std::shared_ptr<Client> c, short events);
void execute_pending_events();
};
+4251 -4251
View File
File diff suppressed because it is too large Load Diff
+456 -456
View File
@@ -1,456 +1,456 @@
#pragma once
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include <memory>
#include <phosg/Strings.hh>
#include <unordered_set>
#include "Client.hh"
#include "CommandFormats.hh"
#include "FunctionCompiler.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "Quest.hh"
#include "Server.hh"
#include "Text.hh"
extern const std::unordered_set<uint32_t> v2_crypt_initial_client_commands;
extern const std::unordered_set<uint32_t> v3_crypt_initial_client_commands;
extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
constexpr size_t GC_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
// TODO: Many of these functions should take a Channel& instead of a
// shared_ptr<Client>. Refactor functions appropriately.
// Note: There are so many versions of this function for a few reasons:
// - There are a lot of different target types (sometimes we want to send a
// command to one client, sometimes to everyone in a lobby, etc.)
// - For the const void* versions, the data and size arguments should not be
// independently optional - this can lead to bugs where a non-null data
// pointer is given but size is accidentally not given (e.g. if the type of
// data in the calling function is changed from string to void*).
template <typename CmdT>
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) {
if (c->game_join_command_queue) {
c->log.info("Client not ready to receive game commands; adding to queue");
auto& q_cmd = c->game_join_command_queue->emplace_back();
q_cmd.command = command;
q_cmd.flag = flag;
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd
// and move it into q_cmd somehow, so q_cmd can free it when needed?
q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
} else {
send_command(c, command, flag, &cmd, sizeof(cmd));
}
}
void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag) {
send_command(c, command, flag, nullptr, 0);
}
void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag,
const void* data, size_t size);
inline void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag) {
send_command_excluding_client(l, c, command, flag, nullptr, 0);
}
void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const void* data, size_t size);
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const std::string& data) {
send_command_if_not_loading(l, command, flag, data.data(), data.size());
}
template <typename StructT>
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const StructT& data) {
send_command_if_not_loading(l, command, flag, &data, sizeof(data));
}
void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag,
const void* data, size_t size);
inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) {
send_command(l, command, flag, nullptr, 0);
}
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag) {
send_command(s, command, flag, nullptr, 0);
}
template <typename TargetT, typename StructT>
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data) {
send_command(c, command, flag, &data, sizeof(data));
}
template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::string& data) {
send_command(c, command, flag, data.data(), data.size());
}
template <typename TargetT, typename StructT>
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename StructT>
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) {
ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename TargetT, typename StructT, typename EntryT>
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()),
array_data.size() * sizeof(EntryT));
send_command(c, command, flag, all_data.data(), all_data.size());
}
void send_command_with_header(Channel& c, const void* data, size_t size);
enum SendServerInitFlag {
IS_INITIAL_CONNECTION = 0x01,
USE_SECONDARY_MESSAGE = 0x02,
};
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags);
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key,
uint8_t flags);
void send_server_init(std::shared_ptr<Client> c, uint8_t flags);
void send_update_client_config(std::shared_ptr<Client> c, bool always_send);
void empty_function_call_response_handler(uint32_t, uint32_t);
void send_quest_buffer_overflow(std::shared_ptr<Client> c);
void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
std::string prepare_send_function_call_data(
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data,
size_t suffix_size,
uint32_t checksum_addr,
uint32_t checksum_size,
uint32_t override_relocations_offset,
bool use_encrypted_format);
void send_function_call(
Channel& ch,
const Client::Config& client_config,
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t checksum_addr = 0,
uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 0);
void send_function_call(
std::shared_ptr<Client> c,
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t checksum_addr = 0,
uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 0);
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
void send_pc_console_split_reconnect(
std::shared_ptr<Client> c,
uint32_t address,
uint16_t pc_port,
uint16_t console_port);
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
void send_system_file_bb(std::shared_ptr<Client> c);
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataBBPreview* preview);
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
void send_guild_card_header_bb(std::shared_ptr<Client> c);
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
void send_stream_file_index_bb(std::shared_ptr<Client> c);
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
void send_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_bb(std::shared_ptr<Client> c);
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
void send_quest_info(std::shared_ptr<Client> c, const std::string& text, uint8_t flag, bool is_download_quest);
void send_lobby_message_box(std::shared_ptr<Client> c, const std::string& text, bool left_side_on_bb = false);
void send_ship_info(std::shared_ptr<Client> c, const std::string& text);
void send_ship_info(Channel& ch, const std::string& text);
void send_text_message(Channel& ch, const std::string& text);
void send_text_message(std::shared_ptr<Client> c, const std::string& text);
void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
void send_scrolling_message_bb(std::shared_ptr<Client> c, const std::string& text);
void send_text_or_scrolling_message(std::shared_ptr<Client> c, const std::string& text, const std::string& scrolling);
void send_text_or_scrolling_message(
std::shared_ptr<Lobby> l, std::shared_ptr<Client> exclude_c, const std::string& text, const std::string& scrolling);
std::string prepare_chat_data(
Version version,
uint8_t language,
uint8_t from_client_id,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_chat_message_from_client(
Channel& ch,
const std::string& text,
char private_flags);
void send_prepared_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& prepared_data);
void send_prepared_chat_message(
std::shared_ptr<Lobby> l,
uint32_t from_guild_card_number,
const std::string& prepared_data);
void send_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_simple_mail(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text);
void send_simple_mail(
std::shared_ptr<ServerState> s,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text);
template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
TargetT& t, const char* format, ...) {
va_list va;
va_start(va, format);
std::string buf = string_vprintf(format, va);
va_end(va);
return send_text_message(t, buf.c_str());
}
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
std::shared_ptr<ServerState> s, const char* format, ...);
void send_info_board(std::shared_ptr<Client> c);
void send_choice_search_choices(std::shared_ptr<Client> c);
void send_card_search_result(
std::shared_ptr<Client> c,
std::shared_ptr<Client> result,
std::shared_ptr<Lobby> result_lobby);
void send_guild_card(
Channel& ch,
uint32_t guild_card_number,
uint64_t xb_user_id,
const std::string& name,
const std::string& team_name,
const std::string& description,
uint8_t language,
uint8_t section_id,
uint8_t char_class);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
void send_game_menu(
std::shared_ptr<Client> c,
bool is_spectator_team_list,
bool is_tournament_game_list);
void send_quest_menu(
std::shared_ptr<Client> c,
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
bool is_download_menu);
void send_quest_categories_menu(
std::shared_ptr<Client> c,
std::shared_ptr<const QuestIndex> quest_index,
QuestMenuType menu_type,
Episode episode);
void send_lobby_list(std::shared_ptr<Client> c);
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_update_lobby_data_bb(std::shared_ptr<Client> c);
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
void send_self_leave_notification(std::shared_ptr<Client> c);
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false);
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
void send_arrow_update(std::shared_ptr<Lobby> l);
void send_unblock_join(std::shared_ptr<Client> c);
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
enum PlayerStatsChange {
SUBTRACT_HP = 0,
SUBTRACT_TP = 1,
SUBTRACT_MESETA = 2,
ADD_HP = 3,
ADD_TP = 4,
};
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount);
void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
void send_remove_conditions(std::shared_ptr<Client> c);
void send_remove_conditions(Channel& ch, uint16_t client_id);
void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Client> c, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
void send_ep3_change_music(Channel& ch, uint32_t song);
void send_revive_player(std::shared_ptr<Client> c);
void send_game_join_sync_command(
std::shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
void send_game_join_sync_command(
std::shared_ptr<Client> c, const std::string& data, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
void send_game_join_sync_command_compressed(
std::shared_ptr<Client> c,
const void* data,
size_t size,
size_t decompressed_size,
uint8_t dc_nte_sc,
uint8_t dc_11_2000_sc,
uint8_t sc);
void send_game_item_state(std::shared_ptr<Client> c);
void send_game_enemy_state(std::shared_ptr<Client> c);
void send_game_object_state(std::shared_ptr<Client> c);
void send_game_set_state(std::shared_ptr<Client> c);
void send_game_flag_state(std::shared_ptr<Client> c);
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_stacked_item_to_channel(
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
void send_drop_stacked_item_to_lobby(
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
void send_destroy_item_to_lobby(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
void send_destroy_floor_item_to_client(std::shared_ptr<Client> c, uint32_t item_id, uint32_t floor);
void send_item_identify_result(std::shared_ptr<Client> c);
void send_bank(std::shared_ptr<Client> c);
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
void send_level_up(std::shared_ptr<Client> c);
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
void send_set_exp_multiplier(std::shared_ptr<Lobby> l);
void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<size_t>& indexes);
void send_quest_function_call(Channel& ch, uint16_t function_id);
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t function_id);
void send_ep3_card_list_update(std::shared_ptr<Client> c);
void send_ep3_media_update(
std::shared_ptr<Client> c,
uint32_t type,
uint32_t which,
const std::string& compressed_data);
void send_ep3_rank_update(std::shared_ptr<Client> c);
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
void send_ep3_confirm_tournament_entry(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_tournament_list(
std::shared_ptr<Client> c,
bool is_for_spectator_team_create);
void send_ep3_tournament_entry_list(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t,
bool is_for_spectator_team_create);
void send_ep3_tournament_info(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
void send_ep3_tournament_details(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_game_details(
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l);
void send_ep3_card_auction(std::shared_ptr<Lobby> l);
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
// Pass mask_key = 0 to unmask the command
void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key);
enum class QuestFileType {
ONLINE = 0,
DOWNLOAD_WITHOUT_PVR,
DOWNLOAD_WITH_PVR,
EPISODE_3,
GBA_DEMO,
};
void send_open_quest_file(
std::shared_ptr<Client> c,
const std::string& quest_name,
const std::string& filename,
const std::string& xb_filename,
uint32_t quest_number,
QuestFileType type,
std::shared_ptr<const std::string> contents);
void send_quest_file_chunk(
std::shared_ptr<Client> c,
const std::string& filename,
size_t chunk_index,
const void* data,
size_t size,
bool is_download_quest);
bool send_quest_barrier_if_all_clients_ready(std::shared_ptr<Lobby> l);
bool send_ep3_start_tournament_deck_select_if_all_clients_ready(std::shared_ptr<Lobby> l);
void send_server_time(std::shared_ptr<Client> c);
void send_change_event(std::shared_ptr<Client> c, uint8_t new_event);
void send_change_event(std::shared_ptr<Lobby> l, uint8_t new_event);
void send_change_event(std::shared_ptr<ServerState> s, uint8_t new_event);
void send_team_membership_info(std::shared_ptr<Client> c); // 12EA
void send_update_team_metadata_for_client(std::shared_ptr<Client> c); // 15EA (to all clients in lobby, with only c's data)
void send_all_nearby_team_metadatas_to_client(std::shared_ptr<Client> c, bool is_13EA); // 13EA/15EA (to only c, with all lobby clients' data)
void send_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
void send_team_member_list(std::shared_ptr<Client> c); // 09EA
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
#pragma once
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include <memory>
#include <phosg/Strings.hh>
#include <unordered_set>
#include "Client.hh"
#include "CommandFormats.hh"
#include "FunctionCompiler.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "Quest.hh"
#include "Server.hh"
#include "Text.hh"
extern const std::unordered_set<uint32_t> v2_crypt_initial_client_commands;
extern const std::unordered_set<uint32_t> v3_crypt_initial_client_commands;
extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
constexpr size_t GC_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
// TODO: Many of these functions should take a Channel& instead of a
// shared_ptr<Client>. Refactor functions appropriately.
// Note: There are so many versions of this function for a few reasons:
// - There are a lot of different target types (sometimes we want to send a
// command to one client, sometimes to everyone in a lobby, etc.)
// - For the const void* versions, the data and size arguments should not be
// independently optional - this can lead to bugs where a non-null data
// pointer is given but size is accidentally not given (e.g. if the type of
// data in the calling function is changed from string to void*).
template <typename CmdT>
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) {
if (c->game_join_command_queue) {
c->log.info("Client not ready to receive game commands; adding to queue");
auto& q_cmd = c->game_join_command_queue->emplace_back();
q_cmd.command = command;
q_cmd.flag = flag;
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd
// and move it into q_cmd somehow, so q_cmd can free it when needed?
q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
} else {
send_command(c, command, flag, &cmd, sizeof(cmd));
}
}
void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag) {
send_command(c, command, flag, nullptr, 0);
}
void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag,
const void* data, size_t size);
inline void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag) {
send_command_excluding_client(l, c, command, flag, nullptr, 0);
}
void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const void* data, size_t size);
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const std::string& data) {
send_command_if_not_loading(l, command, flag, data.data(), data.size());
}
template <typename StructT>
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const StructT& data) {
send_command_if_not_loading(l, command, flag, &data, sizeof(data));
}
void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag,
const void* data, size_t size);
inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) {
send_command(l, command, flag, nullptr, 0);
}
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag) {
send_command(s, command, flag, nullptr, 0);
}
template <typename TargetT, typename StructT>
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data) {
send_command(c, command, flag, &data, sizeof(data));
}
template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::string& data) {
send_command(c, command, flag, data.data(), data.size());
}
template <typename TargetT, typename StructT>
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename StructT>
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) {
ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename TargetT, typename StructT, typename EntryT>
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()),
array_data.size() * sizeof(EntryT));
send_command(c, command, flag, all_data.data(), all_data.size());
}
void send_command_with_header(Channel& c, const void* data, size_t size);
enum SendServerInitFlag {
IS_INITIAL_CONNECTION = 0x01,
USE_SECONDARY_MESSAGE = 0x02,
};
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags);
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key,
uint8_t flags);
void send_server_init(std::shared_ptr<Client> c, uint8_t flags);
void send_update_client_config(std::shared_ptr<Client> c, bool always_send);
void empty_function_call_response_handler(uint32_t, uint32_t);
void send_quest_buffer_overflow(std::shared_ptr<Client> c);
void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
std::string prepare_send_function_call_data(
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data,
size_t suffix_size,
uint32_t checksum_addr,
uint32_t checksum_size,
uint32_t override_relocations_offset,
bool use_encrypted_format);
void send_function_call(
Channel& ch,
const Client::Config& client_config,
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t checksum_addr = 0,
uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 0);
void send_function_call(
std::shared_ptr<Client> c,
std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr,
size_t suffix_size = 0,
uint32_t checksum_addr = 0,
uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 0);
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
void send_pc_console_split_reconnect(
std::shared_ptr<Client> c,
uint32_t address,
uint16_t pc_port,
uint16_t console_port);
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
void send_system_file_bb(std::shared_ptr<Client> c);
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataBBPreview* preview);
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
void send_guild_card_header_bb(std::shared_ptr<Client> c);
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
void send_stream_file_index_bb(std::shared_ptr<Client> c);
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
void send_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_bb(std::shared_ptr<Client> c);
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
void send_quest_info(std::shared_ptr<Client> c, const std::string& text, uint8_t flag, bool is_download_quest);
void send_lobby_message_box(std::shared_ptr<Client> c, const std::string& text, bool left_side_on_bb = false);
void send_ship_info(std::shared_ptr<Client> c, const std::string& text);
void send_ship_info(Channel& ch, const std::string& text);
void send_text_message(Channel& ch, const std::string& text);
void send_text_message(std::shared_ptr<Client> c, const std::string& text);
void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
void send_scrolling_message_bb(std::shared_ptr<Client> c, const std::string& text);
void send_text_or_scrolling_message(std::shared_ptr<Client> c, const std::string& text, const std::string& scrolling);
void send_text_or_scrolling_message(
std::shared_ptr<Lobby> l, std::shared_ptr<Client> exclude_c, const std::string& text, const std::string& scrolling);
std::string prepare_chat_data(
Version version,
uint8_t language,
uint8_t from_client_id,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_chat_message_from_client(
Channel& ch,
const std::string& text,
char private_flags);
void send_prepared_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& prepared_data);
void send_prepared_chat_message(
std::shared_ptr<Lobby> l,
uint32_t from_guild_card_number,
const std::string& prepared_data);
void send_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_simple_mail(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text);
void send_simple_mail(
std::shared_ptr<ServerState> s,
uint32_t from_guild_card_number,
const std::string& from_name,
const std::string& text);
template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
TargetT& t, const char* format, ...) {
va_list va;
va_start(va, format);
std::string buf = string_vprintf(format, va);
va_end(va);
return send_text_message(t, buf.c_str());
}
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
std::shared_ptr<ServerState> s, const char* format, ...);
void send_info_board(std::shared_ptr<Client> c);
void send_choice_search_choices(std::shared_ptr<Client> c);
void send_card_search_result(
std::shared_ptr<Client> c,
std::shared_ptr<Client> result,
std::shared_ptr<Lobby> result_lobby);
void send_guild_card(
Channel& ch,
uint32_t guild_card_number,
uint64_t xb_user_id,
const std::string& name,
const std::string& team_name,
const std::string& description,
uint8_t language,
uint8_t section_id,
uint8_t char_class);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
void send_game_menu(
std::shared_ptr<Client> c,
bool is_spectator_team_list,
bool is_tournament_game_list);
void send_quest_menu(
std::shared_ptr<Client> c,
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
bool is_download_menu);
void send_quest_categories_menu(
std::shared_ptr<Client> c,
std::shared_ptr<const QuestIndex> quest_index,
QuestMenuType menu_type,
Episode episode);
void send_lobby_list(std::shared_ptr<Client> c);
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_update_lobby_data_bb(std::shared_ptr<Client> c);
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
void send_self_leave_notification(std::shared_ptr<Client> c);
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false);
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
void send_arrow_update(std::shared_ptr<Lobby> l);
void send_unblock_join(std::shared_ptr<Client> c);
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
enum PlayerStatsChange {
SUBTRACT_HP = 0,
SUBTRACT_TP = 1,
SUBTRACT_MESETA = 2,
ADD_HP = 3,
ADD_TP = 4,
};
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount);
void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
void send_remove_conditions(std::shared_ptr<Client> c);
void send_remove_conditions(Channel& ch, uint16_t client_id);
void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Client> c, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
void send_ep3_change_music(Channel& ch, uint32_t song);
void send_revive_player(std::shared_ptr<Client> c);
void send_game_join_sync_command(
std::shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
void send_game_join_sync_command(
std::shared_ptr<Client> c, const std::string& data, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
void send_game_join_sync_command_compressed(
std::shared_ptr<Client> c,
const void* data,
size_t size,
size_t decompressed_size,
uint8_t dc_nte_sc,
uint8_t dc_11_2000_sc,
uint8_t sc);
void send_game_item_state(std::shared_ptr<Client> c);
void send_game_enemy_state(std::shared_ptr<Client> c);
void send_game_object_state(std::shared_ptr<Client> c);
void send_game_set_state(std::shared_ptr<Client> c);
void send_game_flag_state(std::shared_ptr<Client> c);
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_stacked_item_to_channel(
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
void send_drop_stacked_item_to_lobby(
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
void send_destroy_item_to_lobby(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
void send_destroy_floor_item_to_client(std::shared_ptr<Client> c, uint32_t item_id, uint32_t floor);
void send_item_identify_result(std::shared_ptr<Client> c);
void send_bank(std::shared_ptr<Client> c);
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
void send_level_up(std::shared_ptr<Client> c);
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
void send_set_exp_multiplier(std::shared_ptr<Lobby> l);
void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<size_t>& indexes);
void send_quest_function_call(Channel& ch, uint16_t function_id);
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t function_id);
void send_ep3_card_list_update(std::shared_ptr<Client> c);
void send_ep3_media_update(
std::shared_ptr<Client> c,
uint32_t type,
uint32_t which,
const std::string& compressed_data);
void send_ep3_rank_update(std::shared_ptr<Client> c);
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
void send_ep3_confirm_tournament_entry(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_tournament_list(
std::shared_ptr<Client> c,
bool is_for_spectator_team_create);
void send_ep3_tournament_entry_list(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t,
bool is_for_spectator_team_create);
void send_ep3_tournament_info(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
void send_ep3_tournament_details(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_game_details(
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l);
void send_ep3_card_auction(std::shared_ptr<Lobby> l);
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
// Pass mask_key = 0 to unmask the command
void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key);
enum class QuestFileType {
ONLINE = 0,
DOWNLOAD_WITHOUT_PVR,
DOWNLOAD_WITH_PVR,
EPISODE_3,
GBA_DEMO,
};
void send_open_quest_file(
std::shared_ptr<Client> c,
const std::string& quest_name,
const std::string& filename,
const std::string& xb_filename,
uint32_t quest_number,
QuestFileType type,
std::shared_ptr<const std::string> contents);
void send_quest_file_chunk(
std::shared_ptr<Client> c,
const std::string& filename,
size_t chunk_index,
const void* data,
size_t size,
bool is_download_quest);
bool send_quest_barrier_if_all_clients_ready(std::shared_ptr<Lobby> l);
bool send_ep3_start_tournament_deck_select_if_all_clients_ready(std::shared_ptr<Lobby> l);
void send_server_time(std::shared_ptr<Client> c);
void send_change_event(std::shared_ptr<Client> c, uint8_t new_event);
void send_change_event(std::shared_ptr<Lobby> l, uint8_t new_event);
void send_change_event(std::shared_ptr<ServerState> s, uint8_t new_event);
void send_team_membership_info(std::shared_ptr<Client> c); // 12EA
void send_update_team_metadata_for_client(std::shared_ptr<Client> c); // 15EA (to all clients in lobby, with only c's data)
void send_all_nearby_team_metadatas_to_client(std::shared_ptr<Client> c, bool is_13EA); // 13EA/15EA (to only c, with all lobby clients' data)
void send_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
void send_team_member_list(std::shared_ptr<Client> c); // 09EA
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
+353 -353
View File
@@ -1,353 +1,353 @@
#include "Server.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
using namespace std::placeholders;
void Server::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else {
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
}
this->state->channel_to_client.erase(&c->channel);
c->channel.disconnect();
try {
on_disconnect(c);
} catch (const exception& e) {
server_log.warning("Error during client disconnect cleanup: %s", e.what());
}
// We can't just let c be destroyed here, since disconnect_client can be
// called from within the client's channel's receive handler. So, we instead
// move it to another set, which we'll clear in an immediately-enqueued
// callback after the current event. This will also call the client's
// disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients();
}
void Server::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv);
}
void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Server*>(ctx)->destroy_clients();
}
void Server::destroy_clients() {
for (auto c_it = this->clients_to_destroy.begin();
c_it != this->clients_to_destroy.end();
c_it = this->clients_to_destroy.erase(c_it)) {
auto c = *c_it;
// Note: It's important to move the disconnect hooks out of the client here
// because the hooks could modify c->disconnect_hooks while it's being
// iterated here, which would invalidate these iterators.
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
for (auto h_it : hooks) {
try {
h_it.second();
} catch (const exception& e) {
c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what());
}
}
}
}
void Server::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen);
}
void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
}
void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr);
if (this->state->banned_ipv4_ranges->check(remote_addr)) {
close(fd);
return;
}
int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket;
try {
listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client",
listen_fd);
close(fd);
return;
}
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>(this->shared_from_this(), bev, 0, listening_socket->version, listening_socket->behavior);
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
try {
on_connect(c);
} catch (const exception& e) {
server_log.warning("Error during client initialization: %s", e.what());
this->disconnect_client(c);
}
}
void Server::connect_virtual_client(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint32_t address,
uint16_t client_port,
uint16_t server_port,
Version version,
ServerBehavior initial_state) {
auto c = make_shared<Client>(this->shared_from_this(), bev, virtual_network_id, version, initial_state);
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info(
"Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI",
c->id,
virtual_network_id,
server_port,
name_for_enum(version),
name_for_enum(initial_state));
// Manually set the remote address, since the bufferevent has no fd and the
// Channel constructor can't figure out the virtual remote address
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
remote_sin->sin_family = AF_INET;
remote_sin->sin_addr.s_addr = htonl(address);
remote_sin->sin_port = htons(client_port);
try {
on_connect(c);
} catch (const exception& e) {
server_log.error("Error during client initialization: %s", e.what());
this->disconnect_client(c);
}
}
void Server::connect_virtual_client(shared_ptr<Client> c, Channel&& ch) {
c->channel.replace_with(std::move(ch), Server::on_client_input, Server::on_client_error, this, string_printf("C-%" PRIX64, c->id));
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client C-%" PRIX64 " added to game server", c->id);
}
void Server::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr);
}
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (c->should_disconnect) {
server->disconnect_client(c);
} else {
if (server->state->catch_handler_exceptions) {
try {
on_command(c, command, flag, data);
} catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what());
c->should_disconnect = true;
}
} else {
on_command(c, command, flag, data);
}
if (c->should_disconnect) {
server->disconnect_client(c);
}
}
}
void Server::on_client_error(Channel& ch, short events) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c);
}
}
Server::Server(
shared_ptr<struct event_base> base,
shared_ptr<ServerState> state)
: base(base),
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
state(state) {}
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s",
socket_path.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior);
}
void Server::listen(
const std::string& addr_str,
const string& addr,
int port,
Version version,
ServerBehavior behavior) {
if (port == 0) {
this->listen(addr_str, addr, version, behavior);
} else {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
server_log.info("Listening on TCP interface %s on fd %d as %s",
netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior);
}
}
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
this->listen(addr_str, "", port, version, behavior);
}
Server::ListeningSocket::ListeningSocket(
Server* s, const std::string& addr_str,
int fd, Version version, ServerBehavior behavior)
: addr_str(addr_str),
fd(fd),
version(version),
behavior(behavior),
listener(evconnlistener_new(
s->base.get(), Server::dispatch_on_listen_accept, s,
LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) {
evconnlistener_set_error_cb(
this->listener.get(),
Server::dispatch_on_listen_error);
}
void Server::add_socket(
const std::string& addr_str,
int fd,
Version version,
ServerBehavior behavior) {
this->listening_sockets.emplace(
piecewise_construct, forward_as_tuple(fd),
forward_as_tuple(this, addr_str, fd, version, behavior));
}
shared_ptr<Client> Server::get_client() const {
if (this->state->channel_to_client.empty()) {
throw runtime_error("no clients on game server");
}
if (this->state->channel_to_client.size() > 1) {
throw runtime_error("multiple clients on game server");
}
return this->state->channel_to_client.begin()->second;
}
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
int64_t account_id_hex = -1;
int64_t account_id_dec = -1;
try {
account_id_dec = stoul(ident, nullptr, 10);
} catch (const invalid_argument&) {
}
try {
account_id_hex = stoul(ident, nullptr, 16);
} catch (const invalid_argument&) {
}
// TODO: It's kind of not great that we do a linear search here, but this is
// only used in the shell, so it should be pretty rare.
vector<shared_ptr<Client>> results;
for (const auto& it : this->state->channel_to_client) {
auto c = it.second;
if (c->login && c->login->account->account_id == account_id_hex) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->account->account_id == account_id_dec) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
results.emplace_back(c);
continue;
}
auto p = c->character(false, false);
if (p && p->disp.name.eq(ident, p->inventory.language)) {
results.emplace_back(c);
continue;
}
if (c->channel.name == ident) {
results.emplace_back(c);
continue;
}
if (starts_with(c->channel.name, ident + " ")) {
results.emplace_back(c);
continue;
}
}
return results;
}
vector<shared_ptr<Client>> Server::all_clients() const {
vector<shared_ptr<Client>> ret;
for (const auto& it : this->state->channel_to_client) {
ret.emplace_back(it.second);
}
return ret;
}
shared_ptr<struct event_base> Server::get_base() const {
return this->base;
}
#include "Server.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
using namespace std::placeholders;
void Server::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else {
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
}
this->state->channel_to_client.erase(&c->channel);
c->channel.disconnect();
try {
on_disconnect(c);
} catch (const exception& e) {
server_log.warning("Error during client disconnect cleanup: %s", e.what());
}
// We can't just let c be destroyed here, since disconnect_client can be
// called from within the client's channel's receive handler. So, we instead
// move it to another set, which we'll clear in an immediately-enqueued
// callback after the current event. This will also call the client's
// disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients();
}
void Server::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv);
}
void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Server*>(ctx)->destroy_clients();
}
void Server::destroy_clients() {
for (auto c_it = this->clients_to_destroy.begin();
c_it != this->clients_to_destroy.end();
c_it = this->clients_to_destroy.erase(c_it)) {
auto c = *c_it;
// Note: It's important to move the disconnect hooks out of the client here
// because the hooks could modify c->disconnect_hooks while it's being
// iterated here, which would invalidate these iterators.
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
for (auto h_it : hooks) {
try {
h_it.second();
} catch (const exception& e) {
c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what());
}
}
}
}
void Server::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen);
}
void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
}
void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr);
if (this->state->banned_ipv4_ranges->check(remote_addr)) {
close(fd);
return;
}
int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket;
try {
listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client",
listen_fd);
close(fd);
return;
}
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>(this->shared_from_this(), bev, 0, listening_socket->version, listening_socket->behavior);
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
try {
on_connect(c);
} catch (const exception& e) {
server_log.warning("Error during client initialization: %s", e.what());
this->disconnect_client(c);
}
}
void Server::connect_virtual_client(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint32_t address,
uint16_t client_port,
uint16_t server_port,
Version version,
ServerBehavior initial_state) {
auto c = make_shared<Client>(this->shared_from_this(), bev, virtual_network_id, version, initial_state);
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info(
"Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI",
c->id,
virtual_network_id,
server_port,
name_for_enum(version),
name_for_enum(initial_state));
// Manually set the remote address, since the bufferevent has no fd and the
// Channel constructor can't figure out the virtual remote address
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
remote_sin->sin_family = AF_INET;
remote_sin->sin_addr.s_addr = htonl(address);
remote_sin->sin_port = htons(client_port);
try {
on_connect(c);
} catch (const exception& e) {
server_log.error("Error during client initialization: %s", e.what());
this->disconnect_client(c);
}
}
void Server::connect_virtual_client(shared_ptr<Client> c, Channel&& ch) {
c->channel.replace_with(std::move(ch), Server::on_client_input, Server::on_client_error, this, string_printf("C-%" PRIX64, c->id));
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client C-%" PRIX64 " added to game server", c->id);
}
void Server::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr);
}
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (c->should_disconnect) {
server->disconnect_client(c);
} else {
if (server->state->catch_handler_exceptions) {
try {
on_command(c, command, flag, data);
} catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what());
c->should_disconnect = true;
}
} else {
on_command(c, command, flag, data);
}
if (c->should_disconnect) {
server->disconnect_client(c);
}
}
}
void Server::on_client_error(Channel& ch, short events) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c);
}
}
Server::Server(
shared_ptr<struct event_base> base,
shared_ptr<ServerState> state)
: base(base),
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
state(state) {}
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s",
socket_path.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior);
}
void Server::listen(
const std::string& addr_str,
const string& addr,
int port,
Version version,
ServerBehavior behavior) {
if (port == 0) {
this->listen(addr_str, addr, version, behavior);
} else {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
server_log.info("Listening on TCP interface %s on fd %d as %s",
netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior);
}
}
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
this->listen(addr_str, "", port, version, behavior);
}
Server::ListeningSocket::ListeningSocket(
Server* s, const std::string& addr_str,
int fd, Version version, ServerBehavior behavior)
: addr_str(addr_str),
fd(fd),
version(version),
behavior(behavior),
listener(evconnlistener_new(
s->base.get(), Server::dispatch_on_listen_accept, s,
LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) {
evconnlistener_set_error_cb(
this->listener.get(),
Server::dispatch_on_listen_error);
}
void Server::add_socket(
const std::string& addr_str,
int fd,
Version version,
ServerBehavior behavior) {
this->listening_sockets.emplace(
piecewise_construct, forward_as_tuple(fd),
forward_as_tuple(this, addr_str, fd, version, behavior));
}
shared_ptr<Client> Server::get_client() const {
if (this->state->channel_to_client.empty()) {
throw runtime_error("no clients on game server");
}
if (this->state->channel_to_client.size() > 1) {
throw runtime_error("multiple clients on game server");
}
return this->state->channel_to_client.begin()->second;
}
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
int64_t account_id_hex = -1;
int64_t account_id_dec = -1;
try {
account_id_dec = stoul(ident, nullptr, 10);
} catch (const invalid_argument&) {
}
try {
account_id_hex = stoul(ident, nullptr, 16);
} catch (const invalid_argument&) {
}
// TODO: It's kind of not great that we do a linear search here, but this is
// only used in the shell, so it should be pretty rare.
vector<shared_ptr<Client>> results;
for (const auto& it : this->state->channel_to_client) {
auto c = it.second;
if (c->login && c->login->account->account_id == account_id_hex) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->account->account_id == account_id_dec) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
results.emplace_back(c);
continue;
}
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
results.emplace_back(c);
continue;
}
auto p = c->character(false, false);
if (p && p->disp.name.eq(ident, p->inventory.language)) {
results.emplace_back(c);
continue;
}
if (c->channel.name == ident) {
results.emplace_back(c);
continue;
}
if (starts_with(c->channel.name, ident + " ")) {
results.emplace_back(c);
continue;
}
}
return results;
}
vector<shared_ptr<Client>> Server::all_clients() const {
vector<shared_ptr<Client>> ret;
for (const auto& it : this->state->channel_to_client) {
ret.emplace_back(it.second);
}
return ret;
}
shared_ptr<struct event_base> Server::get_base() const {
return this->base;
}
+82 -82
View File
@@ -1,82 +1,82 @@
#pragma once
#include <event2/event.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "Client.hh"
#include "ServerState.hh"
class Server : public std::enable_shared_from_this<Server> {
public:
Server() = delete;
Server(const Server&) = delete;
Server(Server&&) = delete;
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
virtual ~Server() = default;
void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state);
void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
void connect_virtual_client(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint32_t address,
uint16_t client_port,
uint16_t server_port,
Version version,
ServerBehavior initial_state);
void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch);
void disconnect_client(std::shared_ptr<Client> c);
std::shared_ptr<Client> get_client() const;
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(const std::string& ident) const;
std::vector<std::shared_ptr<Client>> all_clients() const;
std::shared_ptr<struct event_base> get_base() const;
inline std::shared_ptr<ServerState> get_state() const {
return this->state;
}
private:
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_clients_ev;
struct ListeningSocket {
std::string addr_str;
int fd;
Version version;
ServerBehavior behavior;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(
Server* s,
const std::string& name,
int fd,
Version version,
ServerBehavior behavior);
};
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<ServerState> state;
void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
void destroy_clients();
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
};
#pragma once
#include <event2/event.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "Client.hh"
#include "ServerState.hh"
class Server : public std::enable_shared_from_this<Server> {
public:
Server() = delete;
Server(const Server&) = delete;
Server(Server&&) = delete;
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
virtual ~Server() = default;
void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state);
void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
void connect_virtual_client(
struct bufferevent* bev,
uint64_t virtual_network_id,
uint32_t address,
uint16_t client_port,
uint16_t server_port,
Version version,
ServerBehavior initial_state);
void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch);
void disconnect_client(std::shared_ptr<Client> c);
std::shared_ptr<Client> get_client() const;
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(const std::string& ident) const;
std::vector<std::shared_ptr<Client>> all_clients() const;
std::shared_ptr<struct event_base> get_base() const;
inline std::shared_ptr<ServerState> get_state() const {
return this->state;
}
private:
std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_clients_ev;
struct ListeningSocket {
std::string addr_str;
int fd;
Version version;
ServerBehavior behavior;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(
Server* s,
const std::string& name,
int fd,
Version version,
ServerBehavior behavior);
};
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<ServerState> state;
void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
void destroy_clients();
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
};
+1092 -1092
View File
File diff suppressed because it is too large Load Diff
+33 -33
View File
@@ -1,33 +1,33 @@
#pragma once
#include <memory>
#include <string>
#include <event2/event.h>
#include "ProxyServer.hh"
#include "ServerState.hh"
class ServerShell : public std::enable_shared_from_this<ServerShell> {
public:
class exit_shell : public std::runtime_error {
public:
exit_shell();
~exit_shell() = default;
};
explicit ServerShell(std::shared_ptr<ServerState> state);
ServerShell(const ServerShell&) = delete;
ServerShell(ServerShell&&) = delete;
ServerShell& operator=(const ServerShell&) = delete;
ServerShell& operator=(ServerShell&&) = delete;
~ServerShell();
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
protected:
std::shared_ptr<ServerState> state;
std::thread th;
void thread_fn();
};
#pragma once
#include <memory>
#include <string>
#include <event2/event.h>
#include "ProxyServer.hh"
#include "ServerState.hh"
class ServerShell : public std::enable_shared_from_this<ServerShell> {
public:
class exit_shell : public std::runtime_error {
public:
exit_shell();
~exit_shell() = default;
};
explicit ServerShell(std::shared_ptr<ServerState> state);
ServerShell(const ServerShell&) = delete;
ServerShell(ServerShell&&) = delete;
ServerShell& operator=(const ServerShell&) = delete;
ServerShell& operator=(ServerShell&&) = delete;
~ServerShell();
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
protected:
std::shared_ptr<ServerState> state;
std::thread th;
void thread_fn();
};
+2005 -2005
View File
File diff suppressed because it is too large Load Diff
+408 -408
View File
@@ -1,408 +1,408 @@
#pragma once
#include <event2/event.h>
#include <atomic>
#include <map>
#include <memory>
#include <phosg/JSON.hh>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "Account.hh"
#include "Client.hh"
#include "CommonItemSet.hh"
#include "DNSServer.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "EventUtils.hh"
#include "FunctionCompiler.hh"
#include "GSLArchive.hh"
#include "IPV4RangeSet.hh"
#include "ItemNameIndex.hh"
#include "ItemParameterTable.hh"
#include "LevelTable.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "PatchServer.hh"
#include "PlayerFilesManager.hh"
#include "Quest.hh"
#include "TeamIndex.hh"
#include "WordSelectTable.hh"
// Forward declarations due to reference cycles
class ProxyServer;
class Server;
class IPStackSimulator;
struct PortConfiguration {
std::string name;
std::string addr; // Blank = listen on all interfaces (default)
uint16_t port;
Version version;
ServerBehavior behavior;
};
struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior {
DEFAULT = 0,
ALWAYS,
NEVER,
};
enum class BehaviorSwitch {
OFF = 0,
OFF_BY_DEFAULT,
ON_BY_DEFAULT,
ON,
};
static inline bool behavior_enabled(BehaviorSwitch b) {
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
}
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
}
uint64_t creation_time;
std::shared_ptr<struct event_base> base;
std::string config_filename;
std::shared_ptr<const JSON> config_json;
bool is_replay = false;
bool one_time_config_loaded = false;
bool default_lobbies_created = false;
std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username;
std::string dns_server_addr;
uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses;
std::vector<std::string> ppp_raw_addresses;
std::vector<std::string> http_addresses;
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
bool use_temp_accounts_for_prototypes = true;
bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true;
bool enable_chat_commands = true;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
uint8_t allowed_drop_modes_v3_normal = 0x1F;
uint8_t allowed_drop_modes_v3_battle = 0x07;
uint8_t allowed_drop_modes_v3_challenge = 0x07;
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled = false;
bool enable_v3_v4_protected_subcommands = false;
bool catch_handler_exceptions = true;
bool ep3_infinite_meseta = false;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
uint32_t ep3_final_round_meseta_bonus = 300;
bool ep3_jukebox_is_free = false;
uint32_t ep3_behavior_flags = 0;
bool hide_download_commands = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
bool use_game_creator_section_id = false;
bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = false;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
bool notify_server_for_max_level_achieved = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> default_quest_index;
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
std::shared_ptr<const LevelTable> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
std::shared_ptr<const CommonItemSet> common_item_set_v2;
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
std::shared_ptr<const ArmorRandomSet> armor_random_set;
std::shared_ptr<const ToolRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
struct QuestF960Result {
uint32_t meseta_cost = 0;
uint32_t base_probability = 0;
uint32_t probability_upgrade = 0;
std::array<std::vector<ItemData>, 7> results;
QuestF960Result() = default;
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
};
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results;
std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier = 1;
float exp_share_multiplier = 0.5;
double server_global_drop_rate_multiplier = 1.0;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
uint16_t ep3_card_auction_points = 0;
uint16_t ep3_card_auction_min_size = 0;
uint16_t ep3_card_auction_max_size = 0;
struct CardAuctionPoolEntry {
uint64_t probability;
uint16_t card_id;
uint16_t min_price;
};
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry {
uint32_t type = 1;
uint32_t which; // See B9 documentation in CommandFormats.hh
std::string data;
};
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index;
JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<std::vector<std::string>> information_contents_v2;
std::shared_ptr<std::vector<std::string>> information_contents_v3;
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
std::pair<std::string, uint16_t> proxy_destination_patch;
std::pair<std::string, uint16_t> proxy_destination_bb;
std::string welcome_message;
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::shared_ptr<PlayerFilesManager> player_files_manager;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order;
std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0;
uint32_t external_address = 0;
bool proxy_allow_save_files = true;
bool proxy_enable_login_options = false;
std::shared_ptr<IPStackSimulator> ip_stack_simulator;
std::shared_ptr<DNSServer> dns_server;
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
std::shared_ptr<PatchServer> pc_patch_server;
std::shared_ptr<PatchServer> bb_patch_server;
explicit ServerState(const std::string& config_filename = "");
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c);
bool change_client_lobby(
std::shared_ptr<Client> c,
std::shared_ptr<Lobby> new_lobby,
bool send_join_notification = true,
ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> joining_client);
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
std::vector<std::shared_ptr<Lobby>> all_lobbies();
std::shared_ptr<Lobby> create_lobby(bool is_game);
void remove_lobby(std::shared_ptr<Lobby> l);
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr,
uint64_t account_id = 0,
std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
std::shared_ptr<const Menu> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
ItemData parse_item_description(Version version, const std::string& description) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) {
return this->client_customization_name_color;
}
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
}
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
return this->name_color_for_client(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
std::shared_ptr<const QuestIndex> quest_index(Version version) const;
size_t default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file(
const std::string& patch_index_filename,
const std::string& gsl_filename = "",
const std::string& bb_directory_filename = "") const;
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
std::pair<std::string, uint16_t> parse_port_spec(const JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const;
template <typename T>
inline void call_on_event_thread(std::function<T()>&& fn) {
return ::call_on_event_thread<T>(this->base, std::move(fn));
}
inline void forward_to_event_thread(std::function<void()>&& fn) {
::forward_to_event_thread(this->base, std::move(fn));
}
inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) {
if (from_non_event_thread) {
::forward_to_event_thread(this->base, std::move(fn));
} else {
fn();
}
}
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
void update_dependent_server_configs() const;
// The following functions may only be called from a non-event thread if they
// take a from_non_event_thread argument; any function that does not have this
// argument must be called only from the event thread.
void create_default_lobbies();
void collect_network_addresses();
void load_config_early();
void load_config_late();
void load_bb_private_keys(bool from_non_event_thread);
void load_accounts(bool from_non_event_thread);
void load_teams(bool from_non_event_thread);
void load_patch_indexes(bool from_non_event_thread);
void clear_map_file_caches();
void load_battle_params(bool from_non_event_thread);
void load_level_tables(bool from_non_event_thread);
void load_text_index(bool from_non_event_thread);
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes(bool from_non_event_thread);
void load_drop_tables(bool from_non_event_thread);
void load_item_definitions(bool from_non_event_thread);
void load_set_data_tables(bool from_non_event_thread);
void load_word_select_table(bool from_non_event_thread);
void load_ep3_cards(bool from_non_event_thread);
void load_ep3_maps(bool from_non_event_thread);
void load_ep3_tournament_state(bool from_non_event_thread);
void load_quest_index(bool from_non_event_thread);
void compile_functions(bool from_non_event_thread);
void load_dol_files(bool from_non_event_thread);
void load_all();
void enqueue_destroy_lobbies();
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
void disconnect_all_banned_clients();
std::string format_address_for_channel_name(const struct sockaddr_storage& remote_ss, uint64_t virtual_network);
};
#pragma once
#include <event2/event.h>
#include <atomic>
#include <map>
#include <memory>
#include <phosg/JSON.hh>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "Account.hh"
#include "Client.hh"
#include "CommonItemSet.hh"
#include "DNSServer.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "EventUtils.hh"
#include "FunctionCompiler.hh"
#include "GSLArchive.hh"
#include "IPV4RangeSet.hh"
#include "ItemNameIndex.hh"
#include "ItemParameterTable.hh"
#include "LevelTable.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "PatchServer.hh"
#include "PlayerFilesManager.hh"
#include "Quest.hh"
#include "TeamIndex.hh"
#include "WordSelectTable.hh"
// Forward declarations due to reference cycles
class ProxyServer;
class Server;
class IPStackSimulator;
struct PortConfiguration {
std::string name;
std::string addr; // Blank = listen on all interfaces (default)
uint16_t port;
Version version;
ServerBehavior behavior;
};
struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior {
DEFAULT = 0,
ALWAYS,
NEVER,
};
enum class BehaviorSwitch {
OFF = 0,
OFF_BY_DEFAULT,
ON_BY_DEFAULT,
ON,
};
static inline bool behavior_enabled(BehaviorSwitch b) {
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
}
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
}
uint64_t creation_time;
std::shared_ptr<struct event_base> base;
std::string config_filename;
std::shared_ptr<const JSON> config_json;
bool is_replay = false;
bool one_time_config_loaded = false;
bool default_lobbies_created = false;
std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username;
std::string dns_server_addr;
uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses;
std::vector<std::string> ppp_raw_addresses;
std::vector<std::string> http_addresses;
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
bool use_temp_accounts_for_prototypes = true;
bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true;
bool enable_chat_commands = true;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
uint8_t allowed_drop_modes_v3_normal = 0x1F;
uint8_t allowed_drop_modes_v3_battle = 0x07;
uint8_t allowed_drop_modes_v3_challenge = 0x07;
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled = false;
bool enable_v3_v4_protected_subcommands = false;
bool catch_handler_exceptions = true;
bool ep3_infinite_meseta = false;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
uint32_t ep3_final_round_meseta_bonus = 300;
bool ep3_jukebox_is_free = false;
uint32_t ep3_behavior_flags = 0;
bool hide_download_commands = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
bool use_game_creator_section_id = false;
bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = false;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
bool notify_server_for_max_level_achieved = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> default_quest_index;
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
std::shared_ptr<const LevelTable> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
std::shared_ptr<const CommonItemSet> common_item_set_v2;
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
std::shared_ptr<const ArmorRandomSet> armor_random_set;
std::shared_ptr<const ToolRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
struct QuestF960Result {
uint32_t meseta_cost = 0;
uint32_t base_probability = 0;
uint32_t probability_upgrade = 0;
std::array<std::vector<ItemData>, 7> results;
QuestF960Result() = default;
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
};
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results;
std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier = 1;
float exp_share_multiplier = 0.5;
double server_global_drop_rate_multiplier = 1.0;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
uint16_t ep3_card_auction_points = 0;
uint16_t ep3_card_auction_min_size = 0;
uint16_t ep3_card_auction_max_size = 0;
struct CardAuctionPoolEntry {
uint64_t probability;
uint16_t card_id;
uint16_t min_price;
};
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry {
uint32_t type = 1;
uint32_t which; // See B9 documentation in CommandFormats.hh
std::string data;
};
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index;
JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<std::vector<std::string>> information_contents_v2;
std::shared_ptr<std::vector<std::string>> information_contents_v3;
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
std::pair<std::string, uint16_t> proxy_destination_patch;
std::pair<std::string, uint16_t> proxy_destination_bb;
std::string welcome_message;
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::shared_ptr<PlayerFilesManager> player_files_manager;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order;
std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0;
uint32_t external_address = 0;
bool proxy_allow_save_files = true;
bool proxy_enable_login_options = false;
std::shared_ptr<IPStackSimulator> ip_stack_simulator;
std::shared_ptr<DNSServer> dns_server;
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
std::shared_ptr<PatchServer> pc_patch_server;
std::shared_ptr<PatchServer> bb_patch_server;
explicit ServerState(const std::string& config_filename = "");
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c);
bool change_client_lobby(
std::shared_ptr<Client> c,
std::shared_ptr<Lobby> new_lobby,
bool send_join_notification = true,
ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> joining_client);
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
std::vector<std::shared_ptr<Lobby>> all_lobbies();
std::shared_ptr<Lobby> create_lobby(bool is_game);
void remove_lobby(std::shared_ptr<Lobby> l);
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr,
uint64_t account_id = 0,
std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
std::shared_ptr<const Menu> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
ItemData parse_item_description(Version version, const std::string& description) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) {
return this->client_customization_name_color;
}
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
}
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
return this->name_color_for_client(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
std::shared_ptr<const QuestIndex> quest_index(Version version) const;
size_t default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file(
const std::string& patch_index_filename,
const std::string& gsl_filename = "",
const std::string& bb_directory_filename = "") const;
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
std::pair<std::string, uint16_t> parse_port_spec(const JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const;
template <typename T>
inline void call_on_event_thread(std::function<T()>&& fn) {
return ::call_on_event_thread<T>(this->base, std::move(fn));
}
inline void forward_to_event_thread(std::function<void()>&& fn) {
::forward_to_event_thread(this->base, std::move(fn));
}
inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) {
if (from_non_event_thread) {
::forward_to_event_thread(this->base, std::move(fn));
} else {
fn();
}
}
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
void update_dependent_server_configs() const;
// The following functions may only be called from a non-event thread if they
// take a from_non_event_thread argument; any function that does not have this
// argument must be called only from the event thread.
void create_default_lobbies();
void collect_network_addresses();
void load_config_early();
void load_config_late();
void load_bb_private_keys(bool from_non_event_thread);
void load_accounts(bool from_non_event_thread);
void load_teams(bool from_non_event_thread);
void load_patch_indexes(bool from_non_event_thread);
void clear_map_file_caches();
void load_battle_params(bool from_non_event_thread);
void load_level_tables(bool from_non_event_thread);
void load_text_index(bool from_non_event_thread);
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes(bool from_non_event_thread);
void load_drop_tables(bool from_non_event_thread);
void load_item_definitions(bool from_non_event_thread);
void load_set_data_tables(bool from_non_event_thread);
void load_word_select_table(bool from_non_event_thread);
void load_ep3_cards(bool from_non_event_thread);
void load_ep3_maps(bool from_non_event_thread);
void load_ep3_tournament_state(bool from_non_event_thread);
void load_quest_index(bool from_non_event_thread);
void compile_functions(bool from_non_event_thread);
void load_dol_files(bool from_non_event_thread);
void load_all();
void enqueue_destroy_lobbies();
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
void disconnect_all_banned_clients();
std::string format_address_for_channel_name(const struct sockaddr_storage& remote_ss, uint64_t virtual_network);
};
+472 -472
View File
@@ -1,472 +1,472 @@
#include "TeamIndex.hh"
#include <phosg/Filesystem.hh>
#include <phosg/Image.hh>
#include <phosg/Random.hh>
#include "BattleParamsIndex.hh"
#include "GVMEncoder.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "StaticGameData.hh"
using namespace std;
TeamIndex::Team::Member::Member(const JSON& json)
: flags(json.get_int("Flags", 0)),
points(json.get_int("Points", 0)),
name(json.get_string("Name", "")) {
try {
this->account_id = json.get_int("AccountID");
} catch (const out_of_range&) {
this->account_id = json.get_int("SerialNumber");
}
}
JSON TeamIndex::Team::Member::json() const {
return JSON::dict({
{"AccountID", this->account_id},
{"Flags", this->flags},
{"Points", this->points},
{"Name", this->name},
});
}
uint32_t TeamIndex::Team::Member::privilege_level() const {
if (this->check_flag(Member::Flag::IS_MASTER)) {
return 0x40;
} else if (this->check_flag(Member::Flag::IS_LEADER)) {
return 0x30;
} else {
return 0x00;
}
}
TeamIndex::Team::Team(uint32_t team_id) : Team() {
this->team_id = team_id;
}
string TeamIndex::Team::json_filename() const {
return string_printf("system/teams/%08" PRIX32 ".json", this->team_id);
}
string TeamIndex::Team::flag_filename() const {
return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id);
}
void TeamIndex::Team::load_config() {
auto json = JSON::parse(load_file(this->json_filename()));
this->name = json.get_string("Name");
this->spent_points = json.get_int("SpentPoints");
this->points = 0;
for (const auto& member_it : json.get_list("Members")) {
Member m(*member_it);
this->points += m.points;
uint32_t account_id = m.account_id;
if (m.check_flag(Member::Flag::IS_MASTER)) {
this->master_account_id = account_id;
}
this->members.emplace(account_id, std::move(m));
}
try {
for (const auto& it : json.get_list("RewardKeys")) {
this->reward_keys.emplace(it->as_string());
}
} catch (const out_of_range&) {
}
this->reward_flags = json.get_int("RewardFlags");
}
void TeamIndex::Team::save_config() const {
JSON members_json = JSON::list();
for (const auto& it : this->members) {
members_json.emplace_back(it.second.json());
}
JSON reward_keys_json = JSON::list();
for (const auto& it : this->reward_keys) {
reward_keys_json.emplace_back(it);
}
JSON root = JSON::dict({
{"Name", this->name},
{"SpentPoints", this->spent_points},
{"Members", std::move(members_json)},
{"RewardKeys", std::move(reward_keys_json)},
{"RewardFlags", this->reward_flags},
});
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
}
void TeamIndex::Team::load_flag() {
Image img(this->flag_filename());
if (img.get_width() != 32 || img.get_height() != 32) {
throw runtime_error("incorrect flag image dimensions");
}
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) {
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y));
}
}
}
void TeamIndex::Team::save_flag() const {
if (!this->flag_data) {
return;
}
Image img(32, 32, true);
for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) {
img.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x)));
}
}
img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP);
}
void TeamIndex::Team::delete_files() const {
string json_filename = this->json_filename();
string flag_filename = this->flag_filename();
remove(json_filename.c_str());
remove(flag_filename.c_str());
}
PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const {
const auto& m = this->members.at(account_id);
PSOBBTeamMembership ret;
ret.team_master_guild_card_number = this->master_account_id;
ret.team_id = this->team_id;
ret.unknown_a5 = 0;
ret.unknown_a6 = 0;
ret.privilege_level = m.privilege_level();
ret.unknown_a7 = 0;
ret.unknown_a8 = 0;
ret.unknown_a9 = 0;
ret.team_name.encode(this->name);
if (this->flag_data) {
ret.flag_data = *this->flag_data;
} else {
ret.flag_data.clear();
}
ret.reward_flags = this->reward_flags;
return ret;
}
bool TeamIndex::Team::has_reward(const string& key) const {
return this->reward_keys.count(key);
}
size_t TeamIndex::Team::num_members() const {
return this->members.size();
}
size_t TeamIndex::Team::num_leaders() const {
size_t count = 0;
for (const auto& it : this->members) {
if (it.second.check_flag(Member::Flag::IS_LEADER)) {
count++;
}
}
return count;
}
size_t TeamIndex::Team::max_members() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 100;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 70;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 40;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 20;
} else {
return 10;
}
}
size_t TeamIndex::Team::max_leaders() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 10;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 8;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 5;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 3;
} else {
return 2;
}
}
bool TeamIndex::Team::can_add_member() const {
return this->num_members() < this->max_members();
}
bool TeamIndex::Team::can_promote_leader() const {
return this->num_leaders() < this->max_leaders();
}
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
: menu_item_id(menu_item_id),
key(def_json.get_string("Key")),
name(def_json.get_string("Name")),
description(def_json.get_string("Description")),
is_unique(def_json.get_bool("IsUnique", true)),
team_points(def_json.get_int("Points")) {
try {
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
this->prerequisite_keys.emplace(it->as_string());
}
} catch (const out_of_range&) {
}
try {
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
} catch (const out_of_range&) {
}
try {
this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem")));
} catch (const out_of_range&) {
}
}
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
: directory(directory),
next_team_id(1) {
uint32_t reward_menu_item_id = 0;
for (const auto& it : reward_defs_json.as_list()) {
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
}
if (!isdir(this->directory)) {
mkdir(this->directory.c_str(), 0755);
return;
}
for (const auto& filename : list_directory(this->directory)) {
string file_path = this->directory + "/" + filename;
if (filename == "base.json") {
auto json = JSON::parse(load_file(file_path));
this->next_team_id = json.get_int("NextTeamID");
} else if (ends_with(filename, ".json")) {
try {
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
auto team = make_shared<Team>(team_id);
team->load_config();
try {
team->load_flag();
} catch (const exception& e) {
static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what());
}
this->add_to_indexes(team);
static_game_data_log.info("Indexed team %08" PRIX32 " (%s) (%zu members)", team_id, team->name.c_str(), team->num_members());
} catch (const exception& e) {
static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what());
}
}
}
}
size_t TeamIndex::count() const {
return this->id_to_team.size();
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
try {
return this->id_to_team.at(team_id);
} catch (const out_of_range&) {
return nullptr;
}
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
try {
return this->name_to_team.at(name);
} catch (const out_of_range&) {
return nullptr;
}
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
try {
return this->account_id_to_team.at(account_id);
} catch (const out_of_range&) {
return nullptr;
}
}
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
vector<shared_ptr<const Team>> ret;
for (const auto& it : this->id_to_team) {
ret.emplace_back(it.second);
}
return ret;
}
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
auto team = make_shared<Team>(this->next_team_id++);
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
Team::Member m;
m.account_id = master_account_id;
m.flags = 0;
m.points = 0;
m.name = master_name;
m.set_flag(Team::Member::Flag::IS_MASTER);
team->members.emplace(master_account_id, std::move(m));
team->name = name;
team->save_config();
this->add_to_indexes(team);
return team;
}
void TeamIndex::disband(uint32_t team_id) {
auto team = this->id_to_team.at(team_id);
this->remove_from_indexes(team);
team->delete_files();
}
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
auto team = this->id_to_team.at(team_id);
if (!this->name_to_team.emplace(new_team_name, team).second) {
throw runtime_error("team name is already in use");
}
this->name_to_team.erase(team->name);
team->name = new_team_name;
team->save_config();
}
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
auto team = this->id_to_team.at(team_id);
if (!this->account_id_to_team.emplace(account_id, team).second) {
throw runtime_error("user is already in a different team");
}
Team::Member m;
m.account_id = account_id;
m.flags = 0;
m.points = 0;
m.name = name;
team->members.emplace(account_id, std::move(m));
team->save_config();
}
void TeamIndex::remove_member(uint32_t account_id) {
auto team_it = this->account_id_to_team.find(account_id);
if (team_it == this->account_id_to_team.end()) {
throw runtime_error("client is not in any team");
}
auto team = std::move(team_it->second);
this->account_id_to_team.erase(team_it);
team->members.erase(account_id);
if (team->members.empty()) {
this->disband(team->team_id);
} else {
team->save_config();
}
}
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id);
m.name = name;
team->save_config();
}
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id);
m.points += points;
team->points += points;
team->save_config();
}
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
auto team = this->id_to_team.at(team_id);
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
team->save_flag();
}
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& other_m = team->members.at(leader_account_id);
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
return false;
}
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config();
return true;
}
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& other_m = team->members.at(leader_account_id);
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
return false;
}
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config();
return true;
}
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& new_master_m = team->members.at(new_master_account_id);
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
team->master_account_id = new_master_account_id;
team->save_config();
}
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
auto team = this->id_to_team.at(team_id);
if (team->spent_points + points > team->points) {
throw runtime_error("not enough points available");
}
team->reward_keys.emplace(key);
team->spent_points += points;
if (reward_flag != Team::RewardFlag::NONE) {
team->set_reward_flag(reward_flag);
}
team->save_config();
}
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
if (!this->id_to_team.emplace(team->team_id, team).second) {
throw runtime_error("team ID is already in use");
}
if (!this->name_to_team.emplace(team->name, team).second) {
this->id_to_team.erase(team->team_id);
throw runtime_error("team name is already in use");
}
for (const auto& it : team->members) {
if (!this->account_id_to_team.emplace(it.second.account_id, team).second) {
static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams",
it.second.account_id, it.second.account_id);
}
}
}
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
this->id_to_team.erase(team->team_id);
this->name_to_team.erase(team->name);
for (const auto& it : team->members) {
this->account_id_to_team.erase(it.second.account_id);
}
}
#include "TeamIndex.hh"
#include <phosg/Filesystem.hh>
#include <phosg/Image.hh>
#include <phosg/Random.hh>
#include "BattleParamsIndex.hh"
#include "GVMEncoder.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "StaticGameData.hh"
using namespace std;
TeamIndex::Team::Member::Member(const JSON& json)
: flags(json.get_int("Flags", 0)),
points(json.get_int("Points", 0)),
name(json.get_string("Name", "")) {
try {
this->account_id = json.get_int("AccountID");
} catch (const out_of_range&) {
this->account_id = json.get_int("SerialNumber");
}
}
JSON TeamIndex::Team::Member::json() const {
return JSON::dict({
{"AccountID", this->account_id},
{"Flags", this->flags},
{"Points", this->points},
{"Name", this->name},
});
}
uint32_t TeamIndex::Team::Member::privilege_level() const {
if (this->check_flag(Member::Flag::IS_MASTER)) {
return 0x40;
} else if (this->check_flag(Member::Flag::IS_LEADER)) {
return 0x30;
} else {
return 0x00;
}
}
TeamIndex::Team::Team(uint32_t team_id) : Team() {
this->team_id = team_id;
}
string TeamIndex::Team::json_filename() const {
return string_printf("system/teams/%08" PRIX32 ".json", this->team_id);
}
string TeamIndex::Team::flag_filename() const {
return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id);
}
void TeamIndex::Team::load_config() {
auto json = JSON::parse(load_file(this->json_filename()));
this->name = json.get_string("Name");
this->spent_points = json.get_int("SpentPoints");
this->points = 0;
for (const auto& member_it : json.get_list("Members")) {
Member m(*member_it);
this->points += m.points;
uint32_t account_id = m.account_id;
if (m.check_flag(Member::Flag::IS_MASTER)) {
this->master_account_id = account_id;
}
this->members.emplace(account_id, std::move(m));
}
try {
for (const auto& it : json.get_list("RewardKeys")) {
this->reward_keys.emplace(it->as_string());
}
} catch (const out_of_range&) {
}
this->reward_flags = json.get_int("RewardFlags");
}
void TeamIndex::Team::save_config() const {
JSON members_json = JSON::list();
for (const auto& it : this->members) {
members_json.emplace_back(it.second.json());
}
JSON reward_keys_json = JSON::list();
for (const auto& it : this->reward_keys) {
reward_keys_json.emplace_back(it);
}
JSON root = JSON::dict({
{"Name", this->name},
{"SpentPoints", this->spent_points},
{"Members", std::move(members_json)},
{"RewardKeys", std::move(reward_keys_json)},
{"RewardFlags", this->reward_flags},
});
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
}
void TeamIndex::Team::load_flag() {
Image img(this->flag_filename());
if (img.get_width() != 32 || img.get_height() != 32) {
throw runtime_error("incorrect flag image dimensions");
}
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) {
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y));
}
}
}
void TeamIndex::Team::save_flag() const {
if (!this->flag_data) {
return;
}
Image img(32, 32, true);
for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) {
img.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x)));
}
}
img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP);
}
void TeamIndex::Team::delete_files() const {
string json_filename = this->json_filename();
string flag_filename = this->flag_filename();
remove(json_filename.c_str());
remove(flag_filename.c_str());
}
PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const {
const auto& m = this->members.at(account_id);
PSOBBTeamMembership ret;
ret.team_master_guild_card_number = this->master_account_id;
ret.team_id = this->team_id;
ret.unknown_a5 = 0;
ret.unknown_a6 = 0;
ret.privilege_level = m.privilege_level();
ret.unknown_a7 = 0;
ret.unknown_a8 = 0;
ret.unknown_a9 = 0;
ret.team_name.encode(this->name);
if (this->flag_data) {
ret.flag_data = *this->flag_data;
} else {
ret.flag_data.clear();
}
ret.reward_flags = this->reward_flags;
return ret;
}
bool TeamIndex::Team::has_reward(const string& key) const {
return this->reward_keys.count(key);
}
size_t TeamIndex::Team::num_members() const {
return this->members.size();
}
size_t TeamIndex::Team::num_leaders() const {
size_t count = 0;
for (const auto& it : this->members) {
if (it.second.check_flag(Member::Flag::IS_LEADER)) {
count++;
}
}
return count;
}
size_t TeamIndex::Team::max_members() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 100;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 70;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 40;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 20;
} else {
return 10;
}
}
size_t TeamIndex::Team::max_leaders() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 10;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 8;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 5;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 3;
} else {
return 2;
}
}
bool TeamIndex::Team::can_add_member() const {
return this->num_members() < this->max_members();
}
bool TeamIndex::Team::can_promote_leader() const {
return this->num_leaders() < this->max_leaders();
}
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
: menu_item_id(menu_item_id),
key(def_json.get_string("Key")),
name(def_json.get_string("Name")),
description(def_json.get_string("Description")),
is_unique(def_json.get_bool("IsUnique", true)),
team_points(def_json.get_int("Points")) {
try {
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
this->prerequisite_keys.emplace(it->as_string());
}
} catch (const out_of_range&) {
}
try {
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
} catch (const out_of_range&) {
}
try {
this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem")));
} catch (const out_of_range&) {
}
}
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
: directory(directory),
next_team_id(1) {
uint32_t reward_menu_item_id = 0;
for (const auto& it : reward_defs_json.as_list()) {
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
}
if (!isdir(this->directory)) {
mkdir(this->directory.c_str(), 0755);
return;
}
for (const auto& filename : list_directory(this->directory)) {
string file_path = this->directory + "/" + filename;
if (filename == "base.json") {
auto json = JSON::parse(load_file(file_path));
this->next_team_id = json.get_int("NextTeamID");
} else if (ends_with(filename, ".json")) {
try {
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
auto team = make_shared<Team>(team_id);
team->load_config();
try {
team->load_flag();
} catch (const exception& e) {
static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what());
}
this->add_to_indexes(team);
static_game_data_log.info("Indexed team %08" PRIX32 " (%s) (%zu members)", team_id, team->name.c_str(), team->num_members());
} catch (const exception& e) {
static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what());
}
}
}
}
size_t TeamIndex::count() const {
return this->id_to_team.size();
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
try {
return this->id_to_team.at(team_id);
} catch (const out_of_range&) {
return nullptr;
}
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
try {
return this->name_to_team.at(name);
} catch (const out_of_range&) {
return nullptr;
}
}
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
try {
return this->account_id_to_team.at(account_id);
} catch (const out_of_range&) {
return nullptr;
}
}
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
vector<shared_ptr<const Team>> ret;
for (const auto& it : this->id_to_team) {
ret.emplace_back(it.second);
}
return ret;
}
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
auto team = make_shared<Team>(this->next_team_id++);
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
Team::Member m;
m.account_id = master_account_id;
m.flags = 0;
m.points = 0;
m.name = master_name;
m.set_flag(Team::Member::Flag::IS_MASTER);
team->members.emplace(master_account_id, std::move(m));
team->name = name;
team->save_config();
this->add_to_indexes(team);
return team;
}
void TeamIndex::disband(uint32_t team_id) {
auto team = this->id_to_team.at(team_id);
this->remove_from_indexes(team);
team->delete_files();
}
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
auto team = this->id_to_team.at(team_id);
if (!this->name_to_team.emplace(new_team_name, team).second) {
throw runtime_error("team name is already in use");
}
this->name_to_team.erase(team->name);
team->name = new_team_name;
team->save_config();
}
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
auto team = this->id_to_team.at(team_id);
if (!this->account_id_to_team.emplace(account_id, team).second) {
throw runtime_error("user is already in a different team");
}
Team::Member m;
m.account_id = account_id;
m.flags = 0;
m.points = 0;
m.name = name;
team->members.emplace(account_id, std::move(m));
team->save_config();
}
void TeamIndex::remove_member(uint32_t account_id) {
auto team_it = this->account_id_to_team.find(account_id);
if (team_it == this->account_id_to_team.end()) {
throw runtime_error("client is not in any team");
}
auto team = std::move(team_it->second);
this->account_id_to_team.erase(team_it);
team->members.erase(account_id);
if (team->members.empty()) {
this->disband(team->team_id);
} else {
team->save_config();
}
}
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id);
m.name = name;
team->save_config();
}
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id);
m.points += points;
team->points += points;
team->save_config();
}
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
auto team = this->id_to_team.at(team_id);
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
team->save_flag();
}
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& other_m = team->members.at(leader_account_id);
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
return false;
}
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config();
return true;
}
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& other_m = team->members.at(leader_account_id);
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
return false;
}
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config();
return true;
}
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) {
auto team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID");
}
auto& new_master_m = team->members.at(new_master_account_id);
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
team->master_account_id = new_master_account_id;
team->save_config();
}
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
auto team = this->id_to_team.at(team_id);
if (team->spent_points + points > team->points) {
throw runtime_error("not enough points available");
}
team->reward_keys.emplace(key);
team->spent_points += points;
if (reward_flag != Team::RewardFlag::NONE) {
team->set_reward_flag(reward_flag);
}
team->save_config();
}
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
if (!this->id_to_team.emplace(team->team_id, team).second) {
throw runtime_error("team ID is already in use");
}
if (!this->name_to_team.emplace(team->name, team).second) {
this->id_to_team.erase(team->team_id);
throw runtime_error("team name is already in use");
}
for (const auto& it : team->members) {
if (!this->account_id_to_team.emplace(it.second.account_id, team).second) {
static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams",
it.second.account_id, it.second.account_id);
}
}
}
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
this->id_to_team.erase(team->team_id);
this->name_to_team.erase(team->name);
for (const auto& it : team->members) {
this->account_id_to_team.erase(it.second.account_id);
}
}
+155 -155
View File
@@ -1,155 +1,155 @@
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include "ItemNameIndex.hh"
#include "SaveFileFormats.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class TeamIndex {
public:
struct Team {
struct Member {
enum class Flag {
IS_MASTER = 0x01,
IS_LEADER = 0x02,
};
uint32_t account_id = 0;
uint8_t flags = 0;
uint64_t points = 0;
std::string name;
Member() = default;
explicit Member(const JSON& json);
JSON json() const;
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(static_cast<uint8_t>(flag) & this->flags);
}
inline void set_flag(Flag flag) {
this->flags |= static_cast<uint8_t>(flag);
}
inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint8_t>(flag));
}
uint32_t privilege_level() const;
};
enum class RewardFlag {
// Only 0x00000001 and 0x00000002 are used by the client; the rest are
// free to be used however the server chooses.
NONE = 0x00000000,
TEAM_FLAG = 0x00000001,
DRESSING_ROOM = 0x00000002,
MEMBERS_20_LEADERS_3 = 0x00000004,
MEMBERS_40_LEADERS_5 = 0x00000008,
MEMBERS_70_LEADERS_8 = 0x00000010,
MEMBERS_100_LEADERS_10 = 0x00000020,
};
uint32_t team_id = 0;
uint32_t points = 0;
uint32_t spent_points = 0;
std::string name;
uint32_t master_account_id = 0;
std::unordered_map<uint32_t, Member> members;
uint32_t reward_flags = 0;
std::unordered_set<std::string> reward_keys;
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
Team() = default;
explicit Team(uint32_t team_id);
JSON json() const;
std::string json_filename() const;
std::string flag_filename() const;
void load_config();
void save_config() const;
void load_flag();
void save_flag() const;
void delete_files() const;
PSOBBTeamMembership membership_for_member(uint32_t account_id) const;
[[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const {
return !!(static_cast<uint8_t>(flag) & this->reward_flags);
}
inline void set_reward_flag(RewardFlag flag) {
this->reward_flags |= static_cast<uint8_t>(flag);
}
inline void clear_reward_flag(RewardFlag flag) {
this->reward_flags &= (~static_cast<uint8_t>(flag));
}
[[nodiscard]] bool has_reward(const std::string& key) const;
size_t num_members() const;
size_t num_leaders() const;
size_t max_members() const;
size_t max_leaders() const;
bool can_add_member() const;
bool can_promote_leader() const;
};
struct Reward {
uint32_t menu_item_id = 0;
std::string key;
std::string name;
std::string description;
std::unordered_set<std::string> prerequisite_keys;
bool is_unique = true;
uint32_t team_points = 0;
Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
ItemData reward_item;
Reward(uint32_t menu_item_id, const JSON& def_json);
};
TeamIndex(const std::string& directory, const JSON& reward_defs_json);
~TeamIndex() = default;
inline const std::vector<Reward>& reward_definitions() const {
return this->reward_defs;
}
size_t count() const;
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) const;
std::vector<std::shared_ptr<const Team>> all() const;
std::shared_ptr<const Team> create(const std::string& name, uint32_t master_account_id, const std::string& master_name);
void disband(uint32_t team_id);
void rename(uint32_t team_id, const std::string& new_name);
void add_member(uint32_t team_id, uint32_t account_id, const std::string& name);
void remove_member(uint32_t account_id);
void update_member_name(uint32_t account_id, const std::string& name);
void add_member_points(uint32_t account_id, uint32_t points);
void set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data);
bool promote_leader(uint32_t master_account_id, uint32_t leader_account_id);
bool demote_leader(uint32_t master_account_id, uint32_t leader_account_id);
void change_master(uint32_t master_account_id, uint32_t new_master_account_id);
void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
protected:
std::string directory;
uint32_t next_team_id;
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team;
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
std::vector<Reward> reward_defs;
void add_to_indexes(std::shared_ptr<Team> team);
void remove_from_indexes(std::shared_ptr<Team> team);
};
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include "ItemNameIndex.hh"
#include "SaveFileFormats.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class TeamIndex {
public:
struct Team {
struct Member {
enum class Flag {
IS_MASTER = 0x01,
IS_LEADER = 0x02,
};
uint32_t account_id = 0;
uint8_t flags = 0;
uint64_t points = 0;
std::string name;
Member() = default;
explicit Member(const JSON& json);
JSON json() const;
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(static_cast<uint8_t>(flag) & this->flags);
}
inline void set_flag(Flag flag) {
this->flags |= static_cast<uint8_t>(flag);
}
inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint8_t>(flag));
}
uint32_t privilege_level() const;
};
enum class RewardFlag {
// Only 0x00000001 and 0x00000002 are used by the client; the rest are
// free to be used however the server chooses.
NONE = 0x00000000,
TEAM_FLAG = 0x00000001,
DRESSING_ROOM = 0x00000002,
MEMBERS_20_LEADERS_3 = 0x00000004,
MEMBERS_40_LEADERS_5 = 0x00000008,
MEMBERS_70_LEADERS_8 = 0x00000010,
MEMBERS_100_LEADERS_10 = 0x00000020,
};
uint32_t team_id = 0;
uint32_t points = 0;
uint32_t spent_points = 0;
std::string name;
uint32_t master_account_id = 0;
std::unordered_map<uint32_t, Member> members;
uint32_t reward_flags = 0;
std::unordered_set<std::string> reward_keys;
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
Team() = default;
explicit Team(uint32_t team_id);
JSON json() const;
std::string json_filename() const;
std::string flag_filename() const;
void load_config();
void save_config() const;
void load_flag();
void save_flag() const;
void delete_files() const;
PSOBBTeamMembership membership_for_member(uint32_t account_id) const;
[[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const {
return !!(static_cast<uint8_t>(flag) & this->reward_flags);
}
inline void set_reward_flag(RewardFlag flag) {
this->reward_flags |= static_cast<uint8_t>(flag);
}
inline void clear_reward_flag(RewardFlag flag) {
this->reward_flags &= (~static_cast<uint8_t>(flag));
}
[[nodiscard]] bool has_reward(const std::string& key) const;
size_t num_members() const;
size_t num_leaders() const;
size_t max_members() const;
size_t max_leaders() const;
bool can_add_member() const;
bool can_promote_leader() const;
};
struct Reward {
uint32_t menu_item_id = 0;
std::string key;
std::string name;
std::string description;
std::unordered_set<std::string> prerequisite_keys;
bool is_unique = true;
uint32_t team_points = 0;
Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
ItemData reward_item;
Reward(uint32_t menu_item_id, const JSON& def_json);
};
TeamIndex(const std::string& directory, const JSON& reward_defs_json);
~TeamIndex() = default;
inline const std::vector<Reward>& reward_definitions() const {
return this->reward_defs;
}
size_t count() const;
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) const;
std::vector<std::shared_ptr<const Team>> all() const;
std::shared_ptr<const Team> create(const std::string& name, uint32_t master_account_id, const std::string& master_name);
void disband(uint32_t team_id);
void rename(uint32_t team_id, const std::string& new_name);
void add_member(uint32_t team_id, uint32_t account_id, const std::string& name);
void remove_member(uint32_t account_id);
void update_member_name(uint32_t account_id, const std::string& name);
void add_member_points(uint32_t account_id, uint32_t points);
void set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data);
bool promote_leader(uint32_t master_account_id, uint32_t leader_account_id);
bool demote_leader(uint32_t master_account_id, uint32_t leader_account_id);
void change_master(uint32_t master_account_id, uint32_t new_master_account_id);
void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
protected:
std::string directory;
uint32_t next_team_id;
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team;
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
std::vector<Reward> reward_defs;
void add_to_indexes(std::shared_ptr<Team> team);
void remove_from_indexes(std::shared_ptr<Team> team);
};
+516 -516
View File
File diff suppressed because it is too large Load Diff
+724 -724
View File
File diff suppressed because it is too large Load Diff
+345 -345
View File
@@ -1,345 +1,345 @@
#include "Version.hh"
#include <strings.h>
#include <stdexcept>
#include "Client.hh"
using namespace std;
const char* login_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "console-login";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-login";
case Version::XB_V3:
return "xb-login";
case Version::BB_V4:
return "bb-init";
default:
throw runtime_error("unknown version");
}
}
const char* lobby_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "console-lobby";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-lobby";
case Version::XB_V3:
return "xb-lobby";
case Version::BB_V4:
return "bb-lobby";
default:
throw runtime_error("unknown version");
}
}
const char* proxy_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
return "dc-proxy";
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "gc-proxy";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-proxy";
case Version::XB_V3:
return "xb-proxy";
case Version::BB_V4:
return "bb-proxy";
default:
throw runtime_error("unknown version");
}
}
template <>
const char* name_for_enum<Version>(Version v) {
switch (v) {
case Version::PC_PATCH:
return "PC_PATCH";
case Version::BB_PATCH:
return "BB_PATCH";
case Version::DC_NTE:
return "DC_NTE";
case Version::DC_V1_11_2000_PROTOTYPE:
return "DC_V1_11_2000_PROTOTYPE";
case Version::DC_V1:
return "DC_V1";
case Version::DC_V2:
return "DC_V2";
case Version::PC_NTE:
return "PC_NTE";
case Version::PC_V2:
return "PC_V2";
case Version::GC_NTE:
return "GC_NTE";
case Version::GC_V3:
return "GC_V3";
case Version::GC_EP3_NTE:
return "GC_EP3_NTE";
case Version::GC_EP3:
return "GC_EP3";
case Version::XB_V3:
return "XB_V3";
case Version::BB_V4:
return "BB_V4";
default:
throw runtime_error("unknown version");
}
}
template <>
Version enum_for_name<Version>(const char* name) {
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
return Version::PC_PATCH;
} else if (!strcmp(name, "BB_PATCH")) {
return Version::BB_PATCH;
} else if (!strcmp(name, "DC_NTE")) {
return Version::DC_NTE;
} else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) {
return Version::DC_V1_11_2000_PROTOTYPE;
} else if (!strcmp(name, "DC_V1")) {
return Version::DC_V1;
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
return Version::DC_V2;
} else if (!strcmp(name, "PC_NTE")) {
return Version::PC_NTE;
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
return Version::PC_V2;
} else if (!strcmp(name, "GC_NTE")) {
return Version::GC_NTE;
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
return Version::GC_V3;
} else if (!strcmp(name, "GC_EP3_NTE")) {
return Version::GC_EP3_NTE;
} else if (!strcmp(name, "GC_EP3")) {
return Version::GC_EP3;
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
return Version::XB_V3;
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
return Version::BB_V4;
} else {
throw invalid_argument("incorrect version name");
}
}
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
switch (behavior) {
case ServerBehavior::PC_CONSOLE_DETECT:
return "pc_console_detect";
case ServerBehavior::LOGIN_SERVER:
return "login_server";
case ServerBehavior::LOBBY_SERVER:
return "lobby_server";
case ServerBehavior::PATCH_SERVER_PC:
return "patch_server_pc";
case ServerBehavior::PATCH_SERVER_BB:
return "patch_server_bb";
case ServerBehavior::PROXY_SERVER:
return "proxy_server";
}
throw logic_error("invalid server behavior");
}
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
if (!strcasecmp(name, "pc_console_detect")) {
return ServerBehavior::PC_CONSOLE_DETECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) {
return ServerBehavior::LOGIN_SERVER;
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
return ServerBehavior::PATCH_SERVER_PC;
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
return ServerBehavior::PATCH_SERVER_BB;
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
return ServerBehavior::PROXY_SERVER;
} else {
throw invalid_argument("incorrect server behavior name");
}
}
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
// For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
switch (version) {
case Version::DC_NTE:
return SPECIFIC_VERSION_DC_NTE; // 1OJ1
case Version::DC_V1_11_2000_PROTOTYPE:
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
case Version::DC_V1:
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
case Version::DC_V2:
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
case Version::PC_V2:
return SPECIFIC_VERSION_PC_V2; // 2OJW
case Version::GC_NTE:
return SPECIFIC_VERSION_GC_NTE; // 3OJT
case Version::GC_V3:
switch (sub_version) {
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
case 0x36: // GC Ep1&2 US v1.2 (Plus)
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
case 0x34: // GC Ep1&2 JP v1.3
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
default:
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
}
throw logic_error("this should be impossible");
case Version::GC_EP3_NTE:
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
case Version::GC_EP3:
switch (sub_version) {
case 0x41: // GC Ep3 US
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x40: // GC Ep3 trial and GC Ep3 JP
default:
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
}
case Version::XB_V3:
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
case Version::BB_V4:
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
default:
return SPECIFIC_VERSION_INDETERMINATE;
}
}
bool specific_version_is_indeterminate(uint32_t specific_version) {
return ((specific_version & 0x000000FF) == 0);
}
bool specific_version_is_dc(uint32_t specific_version) {
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
uint8_t major_version = specific_version >> 24;
if (major_version < 0x31 || major_version > 0x32) {
return false;
}
return (specific_version != 0x324F4A57);
}
bool specific_version_is_gc(uint32_t specific_version) {
// GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
if ((specific_version & 0xFF000000) != 0x33000000) {
return false;
}
char game = specific_version >> 16;
if ((game != 'O') && (game != 'S')) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return (isdigit(revision) || (revision == 'T'));
}
bool specific_version_is_xb(uint32_t specific_version) {
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
}
bool specific_version_is_bb(uint32_t specific_version) {
// BB specific_versions are 5XXX, where X is an encoding of the revision number
return (specific_version & 0xFF000000) == 0x35000000;
}
const char* file_path_token_for_version(Version version) {
switch (version) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
return "dc-nte";
case Version::DC_V1_11_2000_PROTOTYPE:
return "dc-11-2000";
case Version::DC_V1:
return "dc-v1";
case Version::DC_V2:
return "dc-v2";
case Version::PC_NTE:
return "pc-nte";
case Version::PC_V2:
return "pc-v2";
case Version::GC_NTE:
return "gc-nte";
case Version::GC_V3:
return "gc-v3";
case Version::GC_EP3_NTE:
return "gc-ep3-nte";
case Version::GC_EP3:
return "gc-ep3";
case Version::XB_V3:
return "xb-v3";
case Version::BB_V4:
return "bb-v4";
default:
throw runtime_error("invalid game version");
}
}
#include "Version.hh"
#include <strings.h>
#include <stdexcept>
#include "Client.hh"
using namespace std;
const char* login_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "console-login";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-login";
case Version::XB_V3:
return "xb-login";
case Version::BB_V4:
return "bb-init";
default:
throw runtime_error("unknown version");
}
}
const char* lobby_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "console-lobby";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-lobby";
case Version::XB_V3:
return "xb-lobby";
case Version::BB_V4:
return "bb-lobby";
default:
throw runtime_error("unknown version");
}
}
const char* proxy_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
return "dc-proxy";
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
return "gc-proxy";
case Version::PC_NTE:
case Version::PC_V2:
return "pc-proxy";
case Version::XB_V3:
return "xb-proxy";
case Version::BB_V4:
return "bb-proxy";
default:
throw runtime_error("unknown version");
}
}
template <>
const char* name_for_enum<Version>(Version v) {
switch (v) {
case Version::PC_PATCH:
return "PC_PATCH";
case Version::BB_PATCH:
return "BB_PATCH";
case Version::DC_NTE:
return "DC_NTE";
case Version::DC_V1_11_2000_PROTOTYPE:
return "DC_V1_11_2000_PROTOTYPE";
case Version::DC_V1:
return "DC_V1";
case Version::DC_V2:
return "DC_V2";
case Version::PC_NTE:
return "PC_NTE";
case Version::PC_V2:
return "PC_V2";
case Version::GC_NTE:
return "GC_NTE";
case Version::GC_V3:
return "GC_V3";
case Version::GC_EP3_NTE:
return "GC_EP3_NTE";
case Version::GC_EP3:
return "GC_EP3";
case Version::XB_V3:
return "XB_V3";
case Version::BB_V4:
return "BB_V4";
default:
throw runtime_error("unknown version");
}
}
template <>
Version enum_for_name<Version>(const char* name) {
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
return Version::PC_PATCH;
} else if (!strcmp(name, "BB_PATCH")) {
return Version::BB_PATCH;
} else if (!strcmp(name, "DC_NTE")) {
return Version::DC_NTE;
} else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) {
return Version::DC_V1_11_2000_PROTOTYPE;
} else if (!strcmp(name, "DC_V1")) {
return Version::DC_V1;
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
return Version::DC_V2;
} else if (!strcmp(name, "PC_NTE")) {
return Version::PC_NTE;
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
return Version::PC_V2;
} else if (!strcmp(name, "GC_NTE")) {
return Version::GC_NTE;
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
return Version::GC_V3;
} else if (!strcmp(name, "GC_EP3_NTE")) {
return Version::GC_EP3_NTE;
} else if (!strcmp(name, "GC_EP3")) {
return Version::GC_EP3;
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
return Version::XB_V3;
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
return Version::BB_V4;
} else {
throw invalid_argument("incorrect version name");
}
}
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
switch (behavior) {
case ServerBehavior::PC_CONSOLE_DETECT:
return "pc_console_detect";
case ServerBehavior::LOGIN_SERVER:
return "login_server";
case ServerBehavior::LOBBY_SERVER:
return "lobby_server";
case ServerBehavior::PATCH_SERVER_PC:
return "patch_server_pc";
case ServerBehavior::PATCH_SERVER_BB:
return "patch_server_bb";
case ServerBehavior::PROXY_SERVER:
return "proxy_server";
}
throw logic_error("invalid server behavior");
}
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
if (!strcasecmp(name, "pc_console_detect")) {
return ServerBehavior::PC_CONSOLE_DETECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) {
return ServerBehavior::LOGIN_SERVER;
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
return ServerBehavior::PATCH_SERVER_PC;
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
return ServerBehavior::PATCH_SERVER_BB;
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
return ServerBehavior::PROXY_SERVER;
} else {
throw invalid_argument("incorrect server behavior name");
}
}
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
// For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
switch (version) {
case Version::DC_NTE:
return SPECIFIC_VERSION_DC_NTE; // 1OJ1
case Version::DC_V1_11_2000_PROTOTYPE:
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
case Version::DC_V1:
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
case Version::DC_V2:
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
case Version::PC_V2:
return SPECIFIC_VERSION_PC_V2; // 2OJW
case Version::GC_NTE:
return SPECIFIC_VERSION_GC_NTE; // 3OJT
case Version::GC_V3:
switch (sub_version) {
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
case 0x36: // GC Ep1&2 US v1.2 (Plus)
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
case 0x34: // GC Ep1&2 JP v1.3
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
default:
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
}
throw logic_error("this should be impossible");
case Version::GC_EP3_NTE:
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
case Version::GC_EP3:
switch (sub_version) {
case 0x41: // GC Ep3 US
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x40: // GC Ep3 trial and GC Ep3 JP
default:
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
}
case Version::XB_V3:
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
case Version::BB_V4:
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
default:
return SPECIFIC_VERSION_INDETERMINATE;
}
}
bool specific_version_is_indeterminate(uint32_t specific_version) {
return ((specific_version & 0x000000FF) == 0);
}
bool specific_version_is_dc(uint32_t specific_version) {
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
uint8_t major_version = specific_version >> 24;
if (major_version < 0x31 || major_version > 0x32) {
return false;
}
return (specific_version != 0x324F4A57);
}
bool specific_version_is_gc(uint32_t specific_version) {
// GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
if ((specific_version & 0xFF000000) != 0x33000000) {
return false;
}
char game = specific_version >> 16;
if ((game != 'O') && (game != 'S')) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return (isdigit(revision) || (revision == 'T'));
}
bool specific_version_is_xb(uint32_t specific_version) {
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
}
bool specific_version_is_bb(uint32_t specific_version) {
// BB specific_versions are 5XXX, where X is an encoding of the revision number
return (specific_version & 0xFF000000) == 0x35000000;
}
const char* file_path_token_for_version(Version version) {
switch (version) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
return "dc-nte";
case Version::DC_V1_11_2000_PROTOTYPE:
return "dc-11-2000";
case Version::DC_V1:
return "dc-v1";
case Version::DC_V2:
return "dc-v2";
case Version::PC_NTE:
return "pc-nte";
case Version::PC_V2:
return "pc-v2";
case Version::GC_NTE:
return "gc-nte";
case Version::GC_V3:
return "gc-v3";
case Version::GC_EP3_NTE:
return "gc-ep3-nte";
case Version::GC_EP3:
return "gc-ep3";
case Version::XB_V3:
return "xb-v3";
case Version::BB_V4:
return "bb-v4";
default:
throw runtime_error("invalid game version");
}
}
+189 -189
View File
@@ -1,189 +1,189 @@
#pragma once
#include <inttypes.h>
#include <phosg/Types.hh>
#include <string>
#include <vector>
enum class Version {
PC_PATCH = 0,
BB_PATCH = 1,
DC_NTE = 2,
DC_V1_11_2000_PROTOTYPE = 3,
DC_V1 = 4,
DC_V2 = 5,
PC_NTE = 6,
PC_V2 = 7,
GC_NTE = 8,
GC_V3 = 9,
GC_EP3_NTE = 10,
GC_EP3 = 11,
XB_V3 = 12,
BB_V4 = 13,
UNKNOWN = 15,
};
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don't forget to update VersionNameColors in config.json");
template <>
const char* name_for_enum<Version>(Version v);
template <>
Version enum_for_name<Version>(const char* name);
inline bool is_any_nte(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::PC_NTE) ||
(version == Version::GC_NTE) ||
(version == Version::GC_EP3_NTE);
}
inline bool is_patch(Version version) {
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
}
inline bool is_pre_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
}
inline bool is_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
}
inline bool is_v2(Version version) {
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
}
inline bool is_v1_or_v2(Version version) {
return is_v1(version) || is_v2(version);
}
inline bool is_v3(Version version) {
return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) ||
(version == Version::XB_V3);
}
inline bool is_v4(Version version) {
return (version == Version::BB_V4);
}
inline bool is_ep3(Version version) {
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
}
inline bool is_dc(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2);
}
inline bool is_gc(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool is_sh4(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2);
}
inline bool is_x86(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::XB_V3) ||
(version == Version::BB_V4);
}
inline bool is_ppc(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool is_big_endian(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool uses_v2_encryption(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::DC_NTE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::GC_NTE);
}
inline bool uses_v3_encryption(Version version) {
return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) ||
(version == Version::XB_V3);
}
inline bool uses_v4_encryption(Version version) {
return (version == Version::BB_V4);
}
inline bool uses_utf16(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::BB_V4);
}
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___
constexpr uint32_t SPECIFIC_VERSION_DC_V2_INDETERMINATE = 0x32000000; // 2___
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT
constexpr uint32_t SPECIFIC_VERSION_GC_V3_EU = 0x334F5030; // 3OP0
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_13 = 0x334F4A33; // 3OJ3
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_15 = 0x334F4A35; // 3OJ5
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_INDETERMINATE = 0x33534A00; // 3SJ_
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_JP = 0x33534A30; // 3SJ0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_EU = 0x33535030; // 3SP0
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__
constexpr uint32_t SPECIFIC_VERSION_BB_V4_INDETERMINATE = 0x35000000; // 5___
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
bool specific_version_is_indeterminate(uint32_t specific_version);
bool specific_version_is_dc(uint32_t specific_version);
bool specific_version_is_gc(uint32_t specific_version);
bool specific_version_is_xb(uint32_t specific_version);
bool specific_version_is_bb(uint32_t specific_version);
enum class ServerBehavior {
PC_CONSOLE_DETECT = 0,
LOGIN_SERVER,
LOBBY_SERVER,
PATCH_SERVER_PC,
PATCH_SERVER_BB,
PROXY_SERVER,
};
const char* login_port_name_for_version(Version v);
const char* lobby_port_name_for_version(Version v);
const char* proxy_port_name_for_version(Version v);
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
const char* file_path_token_for_version(Version version);
#pragma once
#include <inttypes.h>
#include <phosg/Types.hh>
#include <string>
#include <vector>
enum class Version {
PC_PATCH = 0,
BB_PATCH = 1,
DC_NTE = 2,
DC_V1_11_2000_PROTOTYPE = 3,
DC_V1 = 4,
DC_V2 = 5,
PC_NTE = 6,
PC_V2 = 7,
GC_NTE = 8,
GC_V3 = 9,
GC_EP3_NTE = 10,
GC_EP3 = 11,
XB_V3 = 12,
BB_V4 = 13,
UNKNOWN = 15,
};
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don't forget to update VersionNameColors in config.json");
template <>
const char* name_for_enum<Version>(Version v);
template <>
Version enum_for_name<Version>(const char* name);
inline bool is_any_nte(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::PC_NTE) ||
(version == Version::GC_NTE) ||
(version == Version::GC_EP3_NTE);
}
inline bool is_patch(Version version) {
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
}
inline bool is_pre_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
}
inline bool is_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
}
inline bool is_v2(Version version) {
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
}
inline bool is_v1_or_v2(Version version) {
return is_v1(version) || is_v2(version);
}
inline bool is_v3(Version version) {
return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) ||
(version == Version::XB_V3);
}
inline bool is_v4(Version version) {
return (version == Version::BB_V4);
}
inline bool is_ep3(Version version) {
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
}
inline bool is_dc(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2);
}
inline bool is_gc(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool is_sh4(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2);
}
inline bool is_x86(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::XB_V3) ||
(version == Version::BB_V4);
}
inline bool is_ppc(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool is_big_endian(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3);
}
inline bool uses_v2_encryption(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::DC_NTE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::GC_NTE);
}
inline bool uses_v3_encryption(Version version) {
return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) ||
(version == Version::XB_V3);
}
inline bool uses_v4_encryption(Version version) {
return (version == Version::BB_V4);
}
inline bool uses_utf16(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::PC_NTE) ||
(version == Version::PC_V2) ||
(version == Version::BB_V4);
}
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___
constexpr uint32_t SPECIFIC_VERSION_DC_V2_INDETERMINATE = 0x32000000; // 2___
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT
constexpr uint32_t SPECIFIC_VERSION_GC_V3_EU = 0x334F5030; // 3OP0
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_13 = 0x334F4A33; // 3OJ3
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_15 = 0x334F4A35; // 3OJ5
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_INDETERMINATE = 0x33534A00; // 3SJ_
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_JP = 0x33534A30; // 3SJ0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_EU = 0x33535030; // 3SP0
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__
constexpr uint32_t SPECIFIC_VERSION_BB_V4_INDETERMINATE = 0x35000000; // 5___
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
bool specific_version_is_indeterminate(uint32_t specific_version);
bool specific_version_is_dc(uint32_t specific_version);
bool specific_version_is_gc(uint32_t specific_version);
bool specific_version_is_xb(uint32_t specific_version);
bool specific_version_is_bb(uint32_t specific_version);
enum class ServerBehavior {
PC_CONSOLE_DETECT = 0,
LOGIN_SERVER,
LOBBY_SERVER,
PATCH_SERVER_PC,
PATCH_SERVER_BB,
PROXY_SERVER,
};
const char* login_port_name_for_version(Version v);
const char* lobby_port_name_for_version(Version v);
const char* proxy_port_name_for_version(Version v);
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
const char* file_path_token_for_version(Version version);
+319 -319
View File
@@ -1,319 +1,319 @@
#include "WordSelectTable.hh"
#include <inttypes.h>
#include <string>
#include <vector>
#include "Compression.hh"
using namespace std;
template <typename RetT, typename ReadT>
static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) {
vector<RetT> ret;
auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
while (!entries_r.eof()) {
ret.emplace_back(entries_r.get<ReadT>());
}
return ret;
}
template <typename RetT, typename ReadT, typename OffsetT>
static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) {
vector<vector<RetT>> ret;
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
while (!pointers_r.eof()) {
uint32_t sub_offset = pointers_r.get<OffsetT>();
uint32_t sub_count = pointers_r.get<OffsetT>();
ret.emplace_back(read_direct_table<RetT, ReadT>(base_r, sub_offset, sub_count));
}
return ret;
}
template <bool IsBigEndian>
struct NonWindowsRootT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T strings_table;
U32T table1;
U32T table2;
U32T token_id_to_string_id_table;
U32T table4;
U32T article_types_table;
U32T table6;
} __packed__;
using NonWindowsRoot = NonWindowsRootT<false>;
using NonWindowsRootBE = NonWindowsRootT<true>;
check_struct_size(NonWindowsRoot, 0x1C);
check_struct_size(NonWindowsRootBE, 0x1C);
struct PCV2Root {
le_uint32_t unknown_a1;
le_uint32_t unknown_a2;
le_uint32_t table1;
le_uint32_t table2;
le_uint32_t token_id_to_string_id_table;
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __packed_ws__(PCV2Root, 0x20);
struct BBRoot {
le_uint32_t table1;
le_uint32_t table2;
le_uint32_t token_id_to_string_id_table;
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __packed_ws__(BBRoot, 0x18);
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
StringReader r(data);
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
{
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
while (!string_offset_r.eof()) {
string raw_s = r.pget_cstr(string_offset_r.template get<U32T>());
this->strings.emplace_back(use_sjis ? tt_sega_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
}
}
// this->table1 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table1, Table1Count);
// this->table2 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table2, Table2Count);
this->token_id_to_string_id = read_direct_table<size_t, U16T>(r, root.token_id_to_string_id_table, TokenCount);
// this->table4 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table4, Table4Count);
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
// this->table6 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table6, Table6Count);
}
template <typename RootT, size_t TokenCount>
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
if (!unitxt_collection) {
throw runtime_error("a unitxt collection is required");
}
StringReader r(data);
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
const auto& root = r.pget<RootT>(root_offset);
this->strings = *unitxt_collection;
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
this->token_id_to_string_id = read_direct_table<size_t, le_uint16_t>(r, root.token_id_to_string_id_table, TokenCount);
// this->table4 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table4, Table4Count);
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
// this->table6 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table6, Table6Count);
}
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
switch (version) {
case Version::DC_NTE: {
if (data.size() < 4) {
throw runtime_error("data is too small");
}
string decrypted = data.substr(0, data.size() - 4);
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
PSOV2Encryption crypt(seed);
crypt.decrypt(decrypted);
this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis);
break;
}
case Version::DC_V1_11_2000_PROTOTYPE:
this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::DC_V1:
case Version::DC_V2:
this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::PC_NTE:
case Version::PC_V2:
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break;
case Version::GC_NTE:
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_EP3_NTE:
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_V3:
case Version::GC_EP3:
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::XB_V3:
this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::BB_V4:
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break;
default:
throw runtime_error("unsupported word select data version");
}
}
const string& WordSelectSet::string_for_token(uint16_t token_id) const {
return this->strings.at(this->token_id_to_string_id.at(token_id));
}
void WordSelectSet::print(FILE* stream) const {
fprintf(stream, "strings:\n");
for (size_t z = 0; z < this->strings.size(); z++) {
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
}
fprintf(stream, "token_id_to_string_id:\n");
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) {
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
}
}
WordSelectTable::WordSelectTable(
const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws,
const vector<vector<string>>& name_alias_lists) {
unordered_map<string, string> name_to_canonical_name;
for (const auto& alias_list : name_alias_lists) {
if (alias_list.size() < 2) {
continue;
}
auto it = alias_list.begin();
auto canonical_name = *it;
for (it++; it != alias_list.end(); it++) {
name_to_canonical_name.emplace(*it, canonical_name);
}
}
vector<shared_ptr<Token>> dynamic_tokens;
{
for (size_t z = 0; z < 12; z++) {
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z);
this->name_to_token.emplace(token->canonical_name, token);
}
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = "__BLANK__";
this->name_to_token.emplace(token->canonical_name, token);
}
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws,
&pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_ws,
&gc_ep3_nte_ws, &gc_ep3_ws, &xb_v3_ws, &bb_v4_ws};
for (size_t s_version = 0; s_version < ws_sets.size(); s_version++) {
Version version = static_cast<Version>(static_cast<size_t>(Version::DC_NTE) + s_version);
const auto& ws_set = *ws_sets[s_version];
auto& index = this->tokens_by_version.at(s_version);
index.reserve(ws_set.num_tokens());
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
const string& str = ws_set.string_for_token(token_id);
string canonical_name;
try {
canonical_name = name_to_canonical_name.at(str);
} catch (const out_of_range&) {
canonical_name = str;
}
auto token_it = this->name_to_token.find(canonical_name);
if (token_it == this->name_to_token.end()) {
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first;
token_it->second->canonical_name = std::move(canonical_name);
}
token_it->second->slot_for_version(version) = token_id;
index.emplace_back(token_it->second);
}
size_t dynamic_token_base_id = ws_set.num_tokens();
for (size_t z = 0; z < dynamic_tokens.size(); z++) {
auto& token = dynamic_tokens[z];
token->slot_for_version(version) = dynamic_token_base_id + z;
index.emplace_back(token);
}
}
}
void WordSelectTable::print(FILE* stream) const {
fprintf(stream, "DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
for (const auto& it : this->name_to_token) {
const auto& token = it.second;
for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " ");
} else {
fprintf(stream, "%04hX ", token->values_by_version[z]);
}
}
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str());
}
}
void WordSelectTable::print_index(FILE* stream, Version v) const {
fprintf(stream, " DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
const auto& index = this->tokens_for_version(v);
for (size_t token_id = 0; token_id < index.size(); token_id++) {
const auto& token = index[token_id];
fprintf(stream, "%04zX => ", token_id);
for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " ");
} else {
fprintf(stream, "%04hX ", token->values_by_version[z]);
}
}
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str());
}
}
WordSelectMessage WordSelectTable::translate(
const WordSelectMessage& msg,
Version from_version,
Version to_version) const {
const auto& index = this->tokens_for_version(from_version);
WordSelectMessage ret;
for (size_t z = 0; z < ret.tokens.size(); z++) {
if (msg.tokens[z] == 0xFFFF) {
ret.tokens[z] = 0xFFFF;
} else {
const auto& token = index.at(msg.tokens[z]);
if (!token) {
throw runtime_error(string_printf("token %04hX does not exist in the index", msg.tokens[z].load()));
}
ret.tokens[z] = token->slot_for_version(to_version);
if (ret.tokens[z] == 0xFFFF) {
throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load()));
}
}
}
ret.num_tokens = msg.num_tokens;
ret.target_type = msg.target_type;
ret.numeric_parameter = msg.numeric_parameter;
ret.unknown_a4 = msg.unknown_a4;
return ret;
}
WordSelectTable::Token::Token() {
for (size_t z = 0; z < this->values_by_version.size(); z++) {
this->values_by_version[z] = 0xFFFF;
}
}
#include "WordSelectTable.hh"
#include <inttypes.h>
#include <string>
#include <vector>
#include "Compression.hh"
using namespace std;
template <typename RetT, typename ReadT>
static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) {
vector<RetT> ret;
auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
while (!entries_r.eof()) {
ret.emplace_back(entries_r.get<ReadT>());
}
return ret;
}
template <typename RetT, typename ReadT, typename OffsetT>
static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) {
vector<vector<RetT>> ret;
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
while (!pointers_r.eof()) {
uint32_t sub_offset = pointers_r.get<OffsetT>();
uint32_t sub_count = pointers_r.get<OffsetT>();
ret.emplace_back(read_direct_table<RetT, ReadT>(base_r, sub_offset, sub_count));
}
return ret;
}
template <bool IsBigEndian>
struct NonWindowsRootT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T strings_table;
U32T table1;
U32T table2;
U32T token_id_to_string_id_table;
U32T table4;
U32T article_types_table;
U32T table6;
} __packed__;
using NonWindowsRoot = NonWindowsRootT<false>;
using NonWindowsRootBE = NonWindowsRootT<true>;
check_struct_size(NonWindowsRoot, 0x1C);
check_struct_size(NonWindowsRootBE, 0x1C);
struct PCV2Root {
le_uint32_t unknown_a1;
le_uint32_t unknown_a2;
le_uint32_t table1;
le_uint32_t table2;
le_uint32_t token_id_to_string_id_table;
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __packed_ws__(PCV2Root, 0x20);
struct BBRoot {
le_uint32_t table1;
le_uint32_t table2;
le_uint32_t token_id_to_string_id_table;
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __packed_ws__(BBRoot, 0x18);
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
StringReader r(data);
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
{
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
while (!string_offset_r.eof()) {
string raw_s = r.pget_cstr(string_offset_r.template get<U32T>());
this->strings.emplace_back(use_sjis ? tt_sega_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
}
}
// this->table1 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table1, Table1Count);
// this->table2 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table2, Table2Count);
this->token_id_to_string_id = read_direct_table<size_t, U16T>(r, root.token_id_to_string_id_table, TokenCount);
// this->table4 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table4, Table4Count);
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
// this->table6 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table6, Table6Count);
}
template <typename RootT, size_t TokenCount>
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
if (!unitxt_collection) {
throw runtime_error("a unitxt collection is required");
}
StringReader r(data);
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
const auto& root = r.pget<RootT>(root_offset);
this->strings = *unitxt_collection;
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
this->token_id_to_string_id = read_direct_table<size_t, le_uint16_t>(r, root.token_id_to_string_id_table, TokenCount);
// this->table4 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table4, Table4Count);
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
// this->table6 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table6, Table6Count);
}
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
switch (version) {
case Version::DC_NTE: {
if (data.size() < 4) {
throw runtime_error("data is too small");
}
string decrypted = data.substr(0, data.size() - 4);
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
PSOV2Encryption crypt(seed);
crypt.decrypt(decrypted);
this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis);
break;
}
case Version::DC_V1_11_2000_PROTOTYPE:
this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::DC_V1:
case Version::DC_V2:
this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::PC_NTE:
case Version::PC_V2:
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break;
case Version::GC_NTE:
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_EP3_NTE:
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_V3:
case Version::GC_EP3:
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::XB_V3:
this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break;
case Version::BB_V4:
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break;
default:
throw runtime_error("unsupported word select data version");
}
}
const string& WordSelectSet::string_for_token(uint16_t token_id) const {
return this->strings.at(this->token_id_to_string_id.at(token_id));
}
void WordSelectSet::print(FILE* stream) const {
fprintf(stream, "strings:\n");
for (size_t z = 0; z < this->strings.size(); z++) {
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
}
fprintf(stream, "token_id_to_string_id:\n");
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) {
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
}
}
WordSelectTable::WordSelectTable(
const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws,
const vector<vector<string>>& name_alias_lists) {
unordered_map<string, string> name_to_canonical_name;
for (const auto& alias_list : name_alias_lists) {
if (alias_list.size() < 2) {
continue;
}
auto it = alias_list.begin();
auto canonical_name = *it;
for (it++; it != alias_list.end(); it++) {
name_to_canonical_name.emplace(*it, canonical_name);
}
}
vector<shared_ptr<Token>> dynamic_tokens;
{
for (size_t z = 0; z < 12; z++) {
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z);
this->name_to_token.emplace(token->canonical_name, token);
}
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = "__BLANK__";
this->name_to_token.emplace(token->canonical_name, token);
}
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws,
&pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_ws,
&gc_ep3_nte_ws, &gc_ep3_ws, &xb_v3_ws, &bb_v4_ws};
for (size_t s_version = 0; s_version < ws_sets.size(); s_version++) {
Version version = static_cast<Version>(static_cast<size_t>(Version::DC_NTE) + s_version);
const auto& ws_set = *ws_sets[s_version];
auto& index = this->tokens_by_version.at(s_version);
index.reserve(ws_set.num_tokens());
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
const string& str = ws_set.string_for_token(token_id);
string canonical_name;
try {
canonical_name = name_to_canonical_name.at(str);
} catch (const out_of_range&) {
canonical_name = str;
}
auto token_it = this->name_to_token.find(canonical_name);
if (token_it == this->name_to_token.end()) {
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first;
token_it->second->canonical_name = std::move(canonical_name);
}
token_it->second->slot_for_version(version) = token_id;
index.emplace_back(token_it->second);
}
size_t dynamic_token_base_id = ws_set.num_tokens();
for (size_t z = 0; z < dynamic_tokens.size(); z++) {
auto& token = dynamic_tokens[z];
token->slot_for_version(version) = dynamic_token_base_id + z;
index.emplace_back(token);
}
}
}
void WordSelectTable::print(FILE* stream) const {
fprintf(stream, "DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
for (const auto& it : this->name_to_token) {
const auto& token = it.second;
for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " ");
} else {
fprintf(stream, "%04hX ", token->values_by_version[z]);
}
}
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str());
}
}
void WordSelectTable::print_index(FILE* stream, Version v) const {
fprintf(stream, " DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
const auto& index = this->tokens_for_version(v);
for (size_t token_id = 0; token_id < index.size(); token_id++) {
const auto& token = index[token_id];
fprintf(stream, "%04zX => ", token_id);
for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " ");
} else {
fprintf(stream, "%04hX ", token->values_by_version[z]);
}
}
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str());
}
}
WordSelectMessage WordSelectTable::translate(
const WordSelectMessage& msg,
Version from_version,
Version to_version) const {
const auto& index = this->tokens_for_version(from_version);
WordSelectMessage ret;
for (size_t z = 0; z < ret.tokens.size(); z++) {
if (msg.tokens[z] == 0xFFFF) {
ret.tokens[z] = 0xFFFF;
} else {
const auto& token = index.at(msg.tokens[z]);
if (!token) {
throw runtime_error(string_printf("token %04hX does not exist in the index", msg.tokens[z].load()));
}
ret.tokens[z] = token->slot_for_version(to_version);
if (ret.tokens[z] == 0xFFFF) {
throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load()));
}
}
}
ret.num_tokens = msg.num_tokens;
ret.target_type = msg.target_type;
ret.numeric_parameter = msg.numeric_parameter;
ret.unknown_a4 = msg.unknown_a4;
return ret;
}
WordSelectTable::Token::Token() {
for (size_t z = 0; z < this->values_by_version.size(); z++) {
this->values_by_version[z] = 0xFFFF;
}
}
+110 -110
View File
@@ -1,110 +1,110 @@
#pragma once
#include <inttypes.h>
#include <string>
#include <vector>
#include "CommandFormats.hh"
#include "QuestScript.hh"
class WordSelectSet {
public:
WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis);
~WordSelectSet() = default;
inline size_t num_strings() const {
return this->strings.size();
}
inline size_t num_tokens() const {
return this->token_id_to_string_id.size();
}
const std::string& string_for_token(uint16_t token_id) const;
void print(FILE* stream) const;
protected:
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void parse_non_windows_t(const std::string& data, bool use_sjis);
template <typename RootT, size_t TokenCount>
void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection);
std::vector<std::string> strings;
std::vector<size_t> token_id_to_string_id;
// Note: PC NTE and PC have exactly the same parameters
// => DC NTE DC112000 DCv1 DCv2 PCNTE/PC GC NTE GC XB Ep3 NTE Ep3 USA BB
// root: => 000074DC 000072A4 0000755C 0000755C 00002B50 0000AB04 0000BCAC 0000B620 0000B648 0000B914 0000B5FC
// u32 ???: => 00002A9C
// TODO
// u32 ???: => 00002B14
// TODO
// u32 strings_table: => 00006338 0000612C 000063C0 000063C0 (unitxt) 00009208 00009C9C 00009C34 00009C5C 00009904 (unitxt)
// u32 string_offset[COUNT]: => 469 45E 467 467 (unitxt) 63F 804 67B 67C 804 (unitxt)
// char string[...\0]
// u32 table1: => 00000B90 00000B54 00000D3C 00000D3C 00001018 0000100C 000012F0 000012F0 000012F0 000011D0 000012F0
// {u32 offset, u32 count}[COUNT]: => 94 122 93 93 F9 F9 126 126 126 17F 126
// u16[count]
// u32 table2: => 00001178 00001108 00001300 00001300 000019D8 000019CC 00001EE8 00001EE8 00001EE8 00001DC8 00001EE8
// {u32 offset, u32 count}[COUNT]: => 7 7 7 7 7 7 13 13 13 13 13
// u16[count]
// u32 token_id_to_string_id_table => 000011B0 00001140 00001338 00001338 00001A10 00001A04 00001F80 00001F80 00001F80 00001E60 00001F80
// u16[COUNT] string_id_for_token_id => 466 44B 457 457 645 693 68C 68C 68C 68C 68C
// u32 table4: => 00001A5C 00001B08 00001D1C 00001D1C 000027D0 000027C4 00002DCC 00002DCC 00002DCC 00002CAC 00002DCC
// (non-NTE) {u32 offset, u32 count}[COUNT]: => 2 2 2 2 2 2 2 2 2 2
// u16[count]
// (NTE) u16[COUNT] => E1
// u32 article_types_table: => 00001C1E 00001B18 00001D2C 00001D2C 000027E0 000027D4 00002DDC 00002DDC 00002DDC 00002CBC 00002DDC
// u8[COUNT] article_types => 1C8 166 166 166 266 266 28A 28A 28A 28A 266
// u32 table6: => 00001E28 00001CBC 00001ED0 00001ED0 00002A84 00002A78 000030A4 000030A4 000030A4 00002F84 00003080
// {u32 offset, u32 count}[3]:
// u16[count]
};
class WordSelectTable {
public:
WordSelectTable(
const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws,
const std::vector<std::vector<std::string>>& name_alias_lists);
void print(FILE* stream) const;
void print_index(FILE* stream, Version v) const;
WordSelectMessage translate(
const WordSelectMessage& msg,
Version from_version,
Version to_version) const;
private:
struct Token {
std::array<uint16_t, 12> values_by_version;
std::string canonical_name;
Token();
inline uint16_t& slot_for_version(Version version) {
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
inline uint16_t slot_for_version(Version version) const {
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
};
std::map<std::string, std::shared_ptr<Token>> name_to_token;
std::array<std::vector<std::shared_ptr<Token>>, 12> tokens_by_version;
inline const std::vector<std::shared_ptr<Token>>& tokens_for_version(Version version) const {
return this->tokens_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
};
#pragma once
#include <inttypes.h>
#include <string>
#include <vector>
#include "CommandFormats.hh"
#include "QuestScript.hh"
class WordSelectSet {
public:
WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis);
~WordSelectSet() = default;
inline size_t num_strings() const {
return this->strings.size();
}
inline size_t num_tokens() const {
return this->token_id_to_string_id.size();
}
const std::string& string_for_token(uint16_t token_id) const;
void print(FILE* stream) const;
protected:
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void parse_non_windows_t(const std::string& data, bool use_sjis);
template <typename RootT, size_t TokenCount>
void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection);
std::vector<std::string> strings;
std::vector<size_t> token_id_to_string_id;
// Note: PC NTE and PC have exactly the same parameters
// => DC NTE DC112000 DCv1 DCv2 PCNTE/PC GC NTE GC XB Ep3 NTE Ep3 USA BB
// root: => 000074DC 000072A4 0000755C 0000755C 00002B50 0000AB04 0000BCAC 0000B620 0000B648 0000B914 0000B5FC
// u32 ???: => 00002A9C
// TODO
// u32 ???: => 00002B14
// TODO
// u32 strings_table: => 00006338 0000612C 000063C0 000063C0 (unitxt) 00009208 00009C9C 00009C34 00009C5C 00009904 (unitxt)
// u32 string_offset[COUNT]: => 469 45E 467 467 (unitxt) 63F 804 67B 67C 804 (unitxt)
// char string[...\0]
// u32 table1: => 00000B90 00000B54 00000D3C 00000D3C 00001018 0000100C 000012F0 000012F0 000012F0 000011D0 000012F0
// {u32 offset, u32 count}[COUNT]: => 94 122 93 93 F9 F9 126 126 126 17F 126
// u16[count]
// u32 table2: => 00001178 00001108 00001300 00001300 000019D8 000019CC 00001EE8 00001EE8 00001EE8 00001DC8 00001EE8
// {u32 offset, u32 count}[COUNT]: => 7 7 7 7 7 7 13 13 13 13 13
// u16[count]
// u32 token_id_to_string_id_table => 000011B0 00001140 00001338 00001338 00001A10 00001A04 00001F80 00001F80 00001F80 00001E60 00001F80
// u16[COUNT] string_id_for_token_id => 466 44B 457 457 645 693 68C 68C 68C 68C 68C
// u32 table4: => 00001A5C 00001B08 00001D1C 00001D1C 000027D0 000027C4 00002DCC 00002DCC 00002DCC 00002CAC 00002DCC
// (non-NTE) {u32 offset, u32 count}[COUNT]: => 2 2 2 2 2 2 2 2 2 2
// u16[count]
// (NTE) u16[COUNT] => E1
// u32 article_types_table: => 00001C1E 00001B18 00001D2C 00001D2C 000027E0 000027D4 00002DDC 00002DDC 00002DDC 00002CBC 00002DDC
// u8[COUNT] article_types => 1C8 166 166 166 266 266 28A 28A 28A 28A 266
// u32 table6: => 00001E28 00001CBC 00001ED0 00001ED0 00002A84 00002A78 000030A4 000030A4 000030A4 00002F84 00003080
// {u32 offset, u32 count}[3]:
// u16[count]
};
class WordSelectTable {
public:
WordSelectTable(
const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws,
const std::vector<std::vector<std::string>>& name_alias_lists);
void print(FILE* stream) const;
void print_index(FILE* stream, Version v) const;
WordSelectMessage translate(
const WordSelectMessage& msg,
Version from_version,
Version to_version) const;
private:
struct Token {
std::array<uint16_t, 12> values_by_version;
std::string canonical_name;
Token();
inline uint16_t& slot_for_version(Version version) {
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
inline uint16_t slot_for_version(Version version) const {
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
};
std::map<std::string, std::shared_ptr<Token>> name_to_token;
std::array<std::vector<std::shared_ptr<Token>>, 12> tokens_by_version;
inline const std::vector<std::shared_ptr<Token>>& tokens_for_version(Version version) const {
return this->tokens_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
}
};