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
@@ -1,96 +1,94 @@
########################################################### ###########################################################
NPC: Coren Tsu - The Wanderer NPC: Coren Tsu - The Wanderer
AREAS: Pioneer 2 AREAS: Pioneer 2
Translations by: apexseals (discord: apexseals) Translations by: apexseals (discord: apexseals)
Proofing & Debugging by: nolrinale (github.com/nolrinale) Proofing & Debugging by: nolrinale (github.com/nolrinale)
########################################################### ###########################################################
presentation: presentation:
I am Coren Tsu, a wandering merchant, I am Coren Tsu, a wandering merchant,
you could say. you could say.
Please take some time to look at Please take some time to look at
the rare and wonderous goods the rare and wonderous goods
I have been collecting. I have been collecting.
If you spend a little meseta, If you spend a little meseta,
you could win a wonderful prize. you could win a wonderful prize.
Well? Wanna try? Well? Wanna try?
You may win, You may win,
you may lose. you may lose.
But if you don't win, But if you don't win,
don't take it out on me. don't take it out on me.
That's just the way That's just the way
gambling is, yes? gambling is, yes?
Well then, how much Well then, how much
meseta do you want to pay? meseta do you want to pay?
As long as you pay me, As long as you pay me,
I'll give you a great service. I'll give you a great service.
Huh? Huh?
That's too bad... That's too bad...
Well, these kind of things usually Well, these kind of things usually
have a chance to lose money. have a chance to lose money.
Let's keep this discreet. Let's keep this discreet.
If you feel up to it, talk to me again. If you feel up to it, talk to me again.
It seems you have It seems you have
too many items. too many items.
First, go and First, go and
organize your items, organize your items,
Then speak to me again. Then speak to me again.
What? What?
You said you'd try, You said you'd try,
then you said no. then you said no.
People like that People like that
fail at everything. fail at everything.
What the...? What the...?
You don't have the You don't have the
meseta to pay me? meseta to pay me?
I won't work with such I won't work with such
cold hearted people. cold hearted people.
Alright, let's do it. Alright, let's do it.
You better pray You better pray
for something good... for something good...
Look here! Look here!
Take it! Take it!
Even if you had bad luck, Even if you had bad luck,
something good will come out of it. something good will come out of it.
You'll win someday! You'll win someday!
In case you want to try again, In case you want to try again,
come back to me once more. come back to me once more.
File diff suppressed because it is too large Load Diff
+1003 -1003
View File
File diff suppressed because it is too large Load Diff
+263 -263
View File
@@ -1,263 +1,263 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <shared_mutex> #include <shared_mutex>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "Text.hh" #include "Text.hh"
class LicenseIndex; class LicenseIndex;
struct DCNTELicense { struct DCNTELicense {
std::string serial_number; std::string serial_number;
std::string access_key; std::string access_key;
static std::shared_ptr<DCNTELicense> from_json(const JSON& json); static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
JSON json() const; JSON json() const;
}; };
struct V1V2License { struct V1V2License {
uint32_t serial_number = 0; uint32_t serial_number = 0;
std::string access_key; std::string access_key;
static std::shared_ptr<V1V2License> from_json(const JSON& json); static std::shared_ptr<V1V2License> from_json(const JSON& json);
JSON json() const; JSON json() const;
}; };
struct GCLicense { struct GCLicense {
uint32_t serial_number = 0; uint32_t serial_number = 0;
std::string access_key; std::string access_key;
std::string password; std::string password;
static std::shared_ptr<GCLicense> from_json(const JSON& json); static std::shared_ptr<GCLicense> from_json(const JSON& json);
JSON json() const; JSON json() const;
}; };
struct XBLicense { struct XBLicense {
std::string gamertag; std::string gamertag;
uint64_t user_id = 0; uint64_t user_id = 0;
uint64_t account_id = 0; uint64_t account_id = 0;
static std::shared_ptr<XBLicense> from_json(const JSON& json); static std::shared_ptr<XBLicense> from_json(const JSON& json);
JSON json() const; JSON json() const;
}; };
struct BBLicense { struct BBLicense {
std::string username; std::string username;
std::string password; std::string password;
static std::shared_ptr<BBLicense> from_json(const JSON& json); static std::shared_ptr<BBLicense> from_json(const JSON& json);
JSON json() const; JSON json() const;
}; };
struct Account { struct Account {
enum class Flag : uint32_t { enum class Flag : uint32_t {
// clang-format off // clang-format off
KICK_USER = 0x00000001, KICK_USER = 0x00000001,
BAN_USER = 0x00000002, BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004, SILENCE_USER = 0x00000004,
CHANGE_EVENT = 0x00000010, CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020, ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040, FREE_JOIN_GAMES = 0x00000040,
DEBUG = 0x01000000, DEBUG = 0x01000000,
CHEAT_ANYWHERE = 0x02000000, CHEAT_ANYWHERE = 0x02000000,
DISABLE_QUEST_REQUIREMENTS = 0x04000000, DISABLE_QUEST_REQUIREMENTS = 0x04000000,
ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000, ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000,
MODERATOR = 0x00000007, MODERATOR = 0x00000007,
ADMINISTRATOR = 0x000000FF, ADMINISTRATOR = 0x000000FF,
ROOT = 0x7FFFFFFF, ROOT = 0x7FFFFFFF,
IS_SHARED_ACCOUNT = 0x80000000, IS_SHARED_ACCOUNT = 0x80000000,
// NOTE: When adding or changing license flags, don't forget to change the // NOTE: When adding or changing license flags, don't forget to change the
// documentation in the shell's help text. // documentation in the shell's help text.
UNUSED_BITS = 0x70FFFF00, UNUSED_BITS = 0x70FFFF00,
// clang-format on // clang-format on
}; };
// account_id is also the account's guild card number // account_id is also the account's guild card number
uint32_t account_id = 0; uint32_t account_id = 0;
uint32_t flags = 0; uint32_t flags = 0;
uint64_t ban_end_time = 0; // 0 = not banned uint64_t ban_end_time = 0; // 0 = not banned
std::string last_player_name; std::string last_player_name;
std::string auto_reply_message; std::string auto_reply_message;
uint32_t ep3_current_meseta = 0; uint32_t ep3_current_meseta = 0;
uint32_t ep3_total_meseta_earned = 0; uint32_t ep3_total_meseta_earned = 0;
uint32_t bb_team_id = 0; uint32_t bb_team_id = 0;
bool is_temporary = false; // If true, isn't saved to disk bool is_temporary = false; // If true, isn't saved to disk
std::unordered_set<std::string> auto_patches_enabled; std::unordered_set<std::string> auto_patches_enabled;
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses; 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>> dc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_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<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<XBLicense>> xb_licenses;
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses; std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
Account() = default; Account() = default;
explicit Account(const JSON& json); explicit Account(const JSON& json);
virtual ~Account() = default; virtual ~Account() = default;
JSON json() const; JSON json() const;
virtual void save() const; virtual void save() const;
virtual void delete_file() const; virtual void delete_file() const;
[[nodiscard]] inline bool check_flag(Flag flag) const { [[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->flags & static_cast<uint32_t>(flag)); return !!(this->flags & static_cast<uint32_t>(flag));
} }
inline void set_flag(Flag flag) { inline void set_flag(Flag flag) {
this->flags |= static_cast<uint32_t>(flag); this->flags |= static_cast<uint32_t>(flag);
} }
inline void clear_flag(Flag flag) { inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint32_t>(flag)); this->flags &= (~static_cast<uint32_t>(flag));
} }
inline void toggle_flag(Flag flag) { inline void toggle_flag(Flag flag) {
this->flags ^= static_cast<uint32_t>(flag); this->flags ^= static_cast<uint32_t>(flag);
} }
inline void replace_all_flags(Flag mask) { inline void replace_all_flags(Flag mask) {
this->flags = static_cast<uint32_t>(mask); this->flags = static_cast<uint32_t>(mask);
} }
void print(FILE* stream) const; void print(FILE* stream) const;
}; };
struct Login { struct Login {
bool account_was_created = false; bool account_was_created = false;
// This field will never be null // This field will never be null
std::shared_ptr<Account> account; std::shared_ptr<Account> account;
// Exactly one of the following will be non-null, representing the license // Exactly one of the following will be non-null, representing the license
// that the client logged in with // that the client logged in with
std::shared_ptr<DCNTELicense> dc_nte_license; std::shared_ptr<DCNTELicense> dc_nte_license;
std::shared_ptr<V1V2License> dc_license; std::shared_ptr<V1V2License> dc_license;
std::shared_ptr<V1V2License> pc_license; std::shared_ptr<V1V2License> pc_license;
std::shared_ptr<GCLicense> gc_license; std::shared_ptr<GCLicense> gc_license;
std::shared_ptr<XBLicense> xb_license; std::shared_ptr<XBLicense> xb_license;
std::shared_ptr<BBLicense> bb_license; std::shared_ptr<BBLicense> bb_license;
}; };
class AccountIndex { class AccountIndex {
public: public:
class no_username : public std::invalid_argument { class no_username : public std::invalid_argument {
public: public:
no_username() : invalid_argument("serial number is zero or username is missing") {} no_username() : invalid_argument("serial number is zero or username is missing") {}
}; };
class incorrect_password : public std::invalid_argument { class incorrect_password : public std::invalid_argument {
public: public:
incorrect_password() : invalid_argument("incorrect password") {} incorrect_password() : invalid_argument("incorrect password") {}
}; };
class incorrect_access_key : public std::invalid_argument { class incorrect_access_key : public std::invalid_argument {
public: public:
incorrect_access_key() : invalid_argument("incorrect access key") {} incorrect_access_key() : invalid_argument("incorrect access key") {}
}; };
class missing_account : public std::invalid_argument { class missing_account : public std::invalid_argument {
public: public:
missing_account() : invalid_argument("missing account") {} missing_account() : invalid_argument("missing account") {}
}; };
explicit AccountIndex(bool force_all_temporary); explicit AccountIndex(bool force_all_temporary);
virtual ~AccountIndex() = default; virtual ~AccountIndex() = default;
std::shared_ptr<Account> create_account(bool is_temporary) const; std::shared_ptr<Account> create_account(bool is_temporary) const;
size_t count() const; size_t count() const;
std::vector<std::shared_ptr<Account>> all() const; std::vector<std::shared_ptr<Account>> all() const;
void add(std::shared_ptr<Account> a); void add(std::shared_ptr<Account> a);
void remove(uint32_t serial_number); void remove(uint32_t serial_number);
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license); 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_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_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_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_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 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_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_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_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_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_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username); 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<Account> from_account_id(uint32_t account_id) const;
std::shared_ptr<Login> from_dc_nte_credentials( std::shared_ptr<Login> from_dc_nte_credentials(
const std::string& serial_number, const std::string& serial_number,
const std::string& access_key, const std::string& access_key,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_dc_credentials( std::shared_ptr<Login> from_dc_credentials(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string& character_name, const std::string& character_name,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_pc_nte_credentials( std::shared_ptr<Login> from_pc_nte_credentials(
uint32_t guild_card_number, uint32_t guild_card_number,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_pc_credentials( std::shared_ptr<Login> from_pc_credentials(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string& character_name, const std::string& character_name,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_gc_credentials( std::shared_ptr<Login> from_gc_credentials(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string* password, const std::string* password,
const std::string& character_name, const std::string& character_name,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_xb_credentials( std::shared_ptr<Login> from_xb_credentials(
const std::string& gamertag, const std::string& gamertag,
uint64_t user_id, uint64_t user_id,
uint64_t account_id, uint64_t account_id,
bool allow_create); bool allow_create);
std::shared_ptr<Login> from_bb_credentials( std::shared_ptr<Login> from_bb_credentials(
const std::string& username, const std::string& username,
const std::string* password, const std::string* password,
bool allow_create); bool allow_create);
std::shared_ptr<Account> create_temporary_account_for_shared_account( std::shared_ptr<Account> create_temporary_account_for_shared_account(
std::shared_ptr<const Account> src_a, const std::string& variation_data) const; std::shared_ptr<const Account> src_a, const std::string& variation_data) const;
protected: protected:
bool force_all_temporary; bool force_all_temporary;
// This class must be thread-safe because it's used by both the patch server // This class must be thread-safe because it's used by both the patch server
// and game server threads // and game server threads
mutable std::shared_mutex lock; mutable std::shared_mutex lock;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_account_id; 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<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_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_pc_serial_number;
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_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_xb_gamertag;
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username; std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
void add_locked(std::shared_ptr<Account> a); void add_locked(std::shared_ptr<Account> a);
std::shared_ptr<Login> from_dc_nte_credentials_locked( std::shared_ptr<Login> from_dc_nte_credentials_locked(
const std::string& serial_number, const std::string& serial_number,
const std::string& access_key); const std::string& access_key);
std::shared_ptr<Login> from_dc_credentials_locked( std::shared_ptr<Login> from_dc_credentials_locked(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string& character_name); const std::string& character_name);
std::shared_ptr<Login> from_pc_credentials_locked( std::shared_ptr<Login> from_pc_credentials_locked(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string& character_name); const std::string& character_name);
std::shared_ptr<Login> from_gc_credentials_locked( std::shared_ptr<Login> from_gc_credentials_locked(
uint32_t serial_number, uint32_t serial_number,
const std::string& access_key, const std::string& access_key,
const std::string* password, const std::string* password,
const std::string& character_name); const std::string& character_name);
std::shared_ptr<Login> from_xb_credentials_locked( std::shared_ptr<Login> from_xb_credentials_locked(
const std::string& gamertag, const std::string& gamertag,
uint64_t user_id, uint64_t user_id,
uint64_t account_id); uint64_t account_id);
std::shared_ptr<Login> from_bb_credentials_locked( std::shared_ptr<Login> from_bb_credentials_locked(
const std::string& username, const std::string& username,
const std::string* password); const std::string* password);
}; };
+83 -83
View File
@@ -1,83 +1,83 @@
#include "BattleParamsIndex.hh" #include "BattleParamsIndex.hh"
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
using namespace std; using namespace std;
void BattleParamsIndex::Table::print(FILE* stream) const { void BattleParamsIndex::Table::print(FILE* stream) const {
auto print_entry = +[](FILE* stream, const PlayerStats& e) { auto print_entry = +[](FILE* stream, const PlayerStats& e) {
fprintf(stream, fprintf(stream,
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32, "%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
e.char_stats.atp.load(), e.char_stats.atp.load(),
e.char_stats.mst.load(), e.char_stats.mst.load(),
e.char_stats.evp.load(), e.char_stats.evp.load(),
e.char_stats.hp.load(), e.char_stats.hp.load(),
e.char_stats.dfp.load(), e.char_stats.dfp.load(),
e.char_stats.ata.load(), e.char_stats.ata.load(),
e.char_stats.lck.load(), e.char_stats.lck.load(),
e.esp.load(), e.esp.load(),
e.experience.load(), e.experience.load(),
e.meseta.load()); e.meseta.load());
}; };
for (size_t diff = 0; diff < 4; diff++) { for (size_t diff = 0; diff < 4; diff++) {
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n", fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
abbreviation_for_difficulty(diff)); abbreviation_for_difficulty(diff));
for (size_t z = 0; z < 0x60; z++) { for (size_t z = 0; z < 0x60; z++) {
fprintf(stream, " %02zX ", z); fprintf(stream, " %02zX ", z);
print_entry(stream, this->stats[diff][z]); print_entry(stream, this->stats[diff][z]);
fputc('\n', stream); fputc('\n', stream);
} }
} }
} }
BattleParamsIndex::BattleParamsIndex( BattleParamsIndex::BattleParamsIndex(
shared_ptr<const string> data_on_ep1, shared_ptr<const string> data_on_ep1,
shared_ptr<const string> data_on_ep2, shared_ptr<const string> data_on_ep2,
shared_ptr<const string> data_on_ep4, shared_ptr<const string> data_on_ep4,
shared_ptr<const string> data_off_ep1, shared_ptr<const string> data_off_ep1,
shared_ptr<const string> data_off_ep2, shared_ptr<const string> data_off_ep2,
shared_ptr<const string> data_off_ep4) { shared_ptr<const string> data_off_ep4) {
this->files[0][0].data = data_on_ep1; this->files[0][0].data = data_on_ep1;
this->files[0][1].data = data_on_ep2; this->files[0][1].data = data_on_ep2;
this->files[0][2].data = data_on_ep4; this->files[0][2].data = data_on_ep4;
this->files[1][0].data = data_off_ep1; this->files[1][0].data = data_off_ep1;
this->files[1][1].data = data_off_ep2; this->files[1][1].data = data_off_ep2;
this->files[1][2].data = data_off_ep4; this->files[1][2].data = data_off_ep4;
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) { for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
for (uint8_t episode = 0; episode < 3; episode++) { for (uint8_t episode = 0; episode < 3; episode++) {
auto& file = this->files[is_solo][episode]; auto& file = this->files[is_solo][episode];
if (file.data->size() < sizeof(Table)) { if (file.data->size() < sizeof(Table)) {
throw runtime_error(string_printf( throw runtime_error(string_printf(
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)", "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)); sizeof(Table), file.data->size(), is_solo, episode));
} }
file.table = reinterpret_cast<const Table*>(file.data->data()); file.table = reinterpret_cast<const Table*>(file.data->data());
} }
} }
} }
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const { const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
uint8_t ep_index; uint8_t ep_index;
switch (episode) { switch (episode) {
case Episode::EP1: case Episode::EP1:
ep_index = 0; ep_index = 0;
break; break;
case Episode::EP2: case Episode::EP2:
ep_index = 1; ep_index = 1;
break; break;
case Episode::EP4: case Episode::EP4:
ep_index = 2; ep_index = 2;
break; break;
default: default:
throw invalid_argument("invalid episode"); throw invalid_argument("invalid episode");
} }
return *this->files[!!solo][ep_index].table; return *this->files[!!solo][ep_index].table;
} }
+100 -100
View File
@@ -1,100 +1,100 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <array> #include <array>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <random> #include <random>
#include <string> #include <string>
#include <vector> #include <vector>
#include "EnemyType.hh" #include "EnemyType.hh"
#include "LevelTable.hh" #include "LevelTable.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
class BattleParamsIndex { class BattleParamsIndex {
public: public:
// These files are little-endian, even on PSO GC. // These files are little-endian, even on PSO GC.
struct AttackData { struct AttackData {
/* 00 */ le_int16_t unknown_a1; /* 00 */ le_int16_t unknown_a1;
/* 02 */ le_int16_t atp; /* 02 */ le_int16_t atp;
/* 04 */ le_int16_t ata_bonus; /* 04 */ le_int16_t ata_bonus;
/* 06 */ le_uint16_t unknown_a4; /* 06 */ le_uint16_t unknown_a4;
/* 08 */ le_float distance_x; /* 08 */ le_float distance_x;
/* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused) /* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused)
/* 10 */ le_float distance_y; /* 10 */ le_float distance_y;
/* 14 */ le_uint16_t unknown_a8; /* 14 */ le_uint16_t unknown_a8;
/* 16 */ le_uint16_t unknown_a9; /* 16 */ le_uint16_t unknown_a9;
/* 18 */ le_uint16_t unknown_a10; /* 18 */ le_uint16_t unknown_a10;
/* 1A */ le_uint16_t unknown_a11; /* 1A */ le_uint16_t unknown_a11;
/* 1C */ le_uint32_t unknown_a12; /* 1C */ le_uint32_t unknown_a12;
/* 20 */ le_uint32_t unknown_a13; /* 20 */ le_uint32_t unknown_a13;
/* 24 */ le_uint32_t unknown_a14; /* 24 */ le_uint32_t unknown_a14;
/* 28 */ le_uint32_t unknown_a15; /* 28 */ le_uint32_t unknown_a15;
/* 2C */ le_uint32_t unknown_a16; /* 2C */ le_uint32_t unknown_a16;
/* 30 */ /* 30 */
} __packed_ws__(AttackData, 0x30); } __packed_ws__(AttackData, 0x30);
struct ResistData { struct ResistData {
/* 00 */ le_int16_t evp_bonus; /* 00 */ le_int16_t evp_bonus;
/* 02 */ le_uint16_t efr; /* 02 */ le_uint16_t efr;
/* 04 */ le_uint16_t eic; /* 04 */ le_uint16_t eic;
/* 06 */ le_uint16_t eth; /* 06 */ le_uint16_t eth;
/* 08 */ le_uint16_t elt; /* 08 */ le_uint16_t elt;
/* 0A */ le_uint16_t edk; /* 0A */ le_uint16_t edk;
/* 0C */ le_uint32_t unknown_a6; /* 0C */ le_uint32_t unknown_a6;
/* 10 */ le_uint32_t unknown_a7; /* 10 */ le_uint32_t unknown_a7;
/* 14 */ le_uint32_t unknown_a8; /* 14 */ le_uint32_t unknown_a8;
/* 18 */ le_uint32_t unknown_a9; /* 18 */ le_uint32_t unknown_a9;
/* 1C */ le_int32_t dfp_bonus; /* 1C */ le_int32_t dfp_bonus;
/* 20 */ /* 20 */
} __packed_ws__(ResistData, 0x20); } __packed_ws__(ResistData, 0x20);
struct MovementData { struct MovementData {
/* 00 */ le_float idle_move_speed; /* 00 */ le_float idle_move_speed;
/* 04 */ le_float idle_animation_speed; /* 04 */ le_float idle_animation_speed;
/* 08 */ le_float move_speed; /* 08 */ le_float move_speed;
/* 0C */ le_float animation_speed; /* 0C */ le_float animation_speed;
/* 10 */ le_float unknown_a1; /* 10 */ le_float unknown_a1;
/* 14 */ le_float unknown_a2; /* 14 */ le_float unknown_a2;
/* 18 */ le_uint32_t unknown_a3; /* 18 */ le_uint32_t unknown_a3;
/* 1C */ le_uint32_t unknown_a4; /* 1C */ le_uint32_t unknown_a4;
/* 20 */ le_uint32_t unknown_a5; /* 20 */ le_uint32_t unknown_a5;
/* 24 */ le_uint32_t unknown_a6; /* 24 */ le_uint32_t unknown_a6;
/* 28 */ le_uint32_t unknown_a7; /* 28 */ le_uint32_t unknown_a7;
/* 2C */ le_uint32_t unknown_a8; /* 2C */ le_uint32_t unknown_a8;
/* 30 */ /* 30 */
} __packed_ws__(MovementData, 0x30); } __packed_ws__(MovementData, 0x30);
struct Table { struct Table {
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats; /* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data; /* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data; /* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data; /* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
/* F600 */ /* F600 */
void print(FILE* stream) const; void print(FILE* stream) const;
} __packed_ws__(Table, 0xF600); } __packed_ws__(Table, 0xF600);
BattleParamsIndex( BattleParamsIndex(
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat 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_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_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_ep1, // BattleParamEntry.dat
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.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 std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
const Table& get_table(bool solo, Episode episode) const; const Table& get_table(bool solo, Episode episode) const;
private: private:
struct File { struct File {
std::shared_ptr<const std::string> data; std::shared_ptr<const std::string> data;
const Table* table; const Table* table;
}; };
// Indexed as [online/offline][episode] // Indexed as [online/offline][episode]
std::array<std::array<File, 3>, 2> files; std::array<std::array<File, 3>, 2> files;
}; };
+188 -188
View File
@@ -1,188 +1,188 @@
#include "CatSession.hh" #include "CatSession.hh"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/bufferevent.h> #include <event2/bufferevent.h>
#include <event2/event.h> #include <event2/event.h>
#include <event2/listener.h> #include <event2/listener.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Random.hh> #include <phosg/Random.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ProxyCommands.hh" #include "ProxyCommands.hh"
#include "ReceiveCommands.hh" #include "ReceiveCommands.hh"
#include "ReceiveSubcommands.hh" #include "ReceiveSubcommands.hh"
#include "SendCommands.hh" #include "SendCommands.hh"
using namespace std; using namespace std;
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {} CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
CatSession::CatSession( CatSession::CatSession(
shared_ptr<struct event_base> base, shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote, const struct sockaddr_storage& remote,
Version version, Version version,
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file) shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level), : log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
base(base), base(base),
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free), 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"), channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
bb_key_file(bb_key_file) { bb_key_file(bb_key_file) {
if (remote.ss_family != AF_INET) { if (remote.ss_family != AF_INET) {
throw runtime_error("remote is not AF_INET"); throw runtime_error("remote is not AF_INET");
} }
string netloc_str = render_sockaddr_storage(remote); string netloc_str = render_sockaddr_storage(remote);
this->log.info("Connecting to %s", netloc_str.c_str()); this->log.info("Connecting to %s", netloc_str.c_str());
struct bufferevent* bev = bufferevent_socket_new( struct bufferevent* bev = bufferevent_socket_new(
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
if (!bev) { if (!bev) {
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR())); throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
} }
this->channel.set_bufferevent(bev, 0); this->channel.set_bufferevent(bev, 0);
if (bufferevent_socket_connect(this->channel.bev.get(), if (bufferevent_socket_connect(this->channel.bev.get(),
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) { reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR())); throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
} }
event_add(this->read_event.get(), nullptr); event_add(this->read_event.get(), nullptr);
this->poll.add(0, POLLIN); this->poll.add(0, POLLIN);
} }
void CatSession::execute_command(const std::string& command) { void CatSession::execute_command(const std::string& command) {
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES); string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size()); send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
} }
void CatSession::dispatch_on_channel_input( void CatSession::dispatch_on_channel_input(
Channel& ch, uint16_t command, uint32_t flag, std::string& data) { Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj); auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_input(command, flag, data); session->on_channel_input(command, flag, data);
} }
void CatSession::on_channel_input( void CatSession::on_channel_input(
uint16_t command, uint32_t flag, std::string& data) { uint16_t command, uint32_t flag, std::string& data) {
if (!uses_v4_encryption(this->channel.version)) { if (!uses_v4_encryption(this->channel.version)) {
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { 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); const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
if (uses_v3_encryption(this->channel.version)) { if (uses_v3_encryption(this->channel.version)) {
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key); this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_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 ")", this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load()); cmd.server_key.load(), cmd.client_key.load());
} else { // PC, DC, or patch server } else { // PC, DC, or patch server
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key); this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_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 ")", this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
cmd.server_key.load(), cmd.client_key.load()); cmd.server_key.load(), cmd.client_key.load());
} }
} }
} else { // BB } else { // BB
if (command == 0x03 || command == 0x9B) { if (command == 0x03 || command == 0x9B) {
if (!this->bb_key_file) { if (!this->bb_key_file) {
throw runtime_error("BB encryption requires a key file"); throw runtime_error("BB encryption requires a key file");
} }
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF); 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_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->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
this->log.info("Enabled BB encryption"); this->log.info("Enabled BB encryption");
} }
} }
// TODO: Use the iovec form of print_data here instead of // TODO: Use the iovec form of print_data here instead of
// prepend_command_header (which copies the string) // prepend_command_header (which copies the string)
string full_cmd = prepend_command_header( string full_cmd = prepend_command_header(
this->channel.version, this->channel.crypt_in.get(), command, flag, data); 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); print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
} }
void CatSession::dispatch_on_channel_error(Channel& ch, short events) { void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
auto* session = reinterpret_cast<CatSession*>(ch.context_obj); auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
session->on_channel_error(events); session->on_channel_error(events);
} }
void CatSession::on_channel_error(short events) { void CatSession::on_channel_error(short events) {
if (events & BEV_EVENT_CONNECTED) { if (events & BEV_EVENT_CONNECTED) {
this->log.info("Channel connected"); this->log.info("Channel connected");
} }
if (events & BEV_EVENT_ERROR) { if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
this->log.warning("Error %d (%s) in unlinked client stream", err, this->log.warning("Error %d (%s) in unlinked client stream", err,
evutil_socket_error_to_string(err)); evutil_socket_error_to_string(err));
} }
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) { if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
this->log.info("Session endpoint has disconnected"); this->log.info("Session endpoint has disconnected");
this->channel.disconnect(); this->channel.disconnect();
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
} }
} }
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) { void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
reinterpret_cast<CatSession*>(ctx)->read_stdin(); reinterpret_cast<CatSession*>(ctx)->read_stdin();
} }
void CatSession::read_stdin() { void CatSession::read_stdin() {
bool any_command_read = false; bool any_command_read = false;
for (;;) { for (;;) {
auto poll_result = this->poll.poll(); auto poll_result = this->poll.poll();
short fd_events = 0; short fd_events = 0;
try { try {
fd_events = poll_result.at(0); fd_events = poll_result.at(0);
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
if (!(fd_events & POLLIN)) { if (!(fd_events & POLLIN)) {
break; break;
} }
string command(2048, '\0'); string command(2048, '\0');
if (!fgets(command.data(), command.size(), stdin)) { if (!fgets(command.data(), command.size(), stdin)) {
if (!any_command_read) { if (!any_command_read) {
// ctrl+d probably; we should exit // ctrl+d probably; we should exit
fputc('\n', stderr); fputc('\n', stderr);
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
return; return;
} else { } else {
break; // probably not EOF; just no more commands for now break; // probably not EOF; just no more commands for now
} }
} }
// trim the extra data off the string // trim the extra data off the string
size_t len = strlen(command.c_str()); size_t len = strlen(command.c_str());
if (len == 0) { if (len == 0) {
break; break;
} }
if (command[len - 1] == '\n') { if (command[len - 1] == '\n') {
len--; len--;
} }
command.resize(len); command.resize(len);
any_command_read = true; any_command_read = true;
try { try {
execute_command(command); execute_command(command);
} catch (const exit_shell&) { } catch (const exit_shell&) {
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
return; return;
} catch (const exception& e) { } catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what()); fprintf(stderr, "FAILED: %s\n", e.what());
} }
} }
} }
+54 -54
View File
@@ -1,54 +1,54 @@
#pragma once #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ServerState.hh" #include "ServerState.hh"
class CatSession { class CatSession {
public: public:
CatSession( CatSession(
std::shared_ptr<struct event_base> base, std::shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote, const struct sockaddr_storage& remote,
Version version, Version version,
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file); std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
CatSession(const CatSession&) = delete; CatSession(const CatSession&) = delete;
CatSession(CatSession&&) = delete; CatSession(CatSession&&) = delete;
CatSession& operator=(const CatSession&) = delete; CatSession& operator=(const CatSession&) = delete;
CatSession& operator=(CatSession&&) = delete; CatSession& operator=(CatSession&&) = delete;
virtual ~CatSession() = default; virtual ~CatSession() = default;
protected: protected:
PrefixedLogger log; PrefixedLogger log;
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> read_event; std::unique_ptr<struct event, void (*)(struct event*)> read_event;
Poll poll; Poll poll;
Channel channel; Channel channel;
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file; std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
class exit_shell : public std::runtime_error { class exit_shell : public std::runtime_error {
public: public:
exit_shell(); exit_shell();
~exit_shell() = default; ~exit_shell() = default;
}; };
virtual void execute_command(const std::string& command); virtual void execute_command(const std::string& command);
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx); 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_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void dispatch_on_channel_error(Channel& ch, short events); 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_input(uint16_t command, uint32_t flag, std::string& msg);
void on_channel_error(short events); void on_channel_error(short events);
void read_stdin(); void read_stdin();
}; };
+421 -421
View File
@@ -1,421 +1,421 @@
#include "Channel.hh" #include "Channel.hh"
#include <errno.h> #include <errno.h>
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/bufferevent.h> #include <event2/bufferevent.h>
#include <event2/event.h> #include <event2/event.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include "Loggers.hh" #include "Loggers.hh"
#include "Version.hh" #include "Version.hh"
using namespace std; using namespace std;
extern bool use_terminal_colors; extern bool use_terminal_colors;
static void flush_and_free_bufferevent(struct bufferevent* bev) { static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED); bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev); bufferevent_free(bev);
} }
Channel::Channel( Channel::Channel(
Version version, Version version,
uint8_t language, uint8_t language,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const string& name, const string& name,
TerminalFormat terminal_send_color, TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color) TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent), : bev(nullptr, flush_and_free_bufferevent),
virtual_network_id(0), virtual_network_id(0),
version(version), version(version),
language(language), language(language),
name(name), name(name),
terminal_send_color(terminal_send_color), terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color), terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received), on_command_received(on_command_received),
on_error(on_error), on_error(on_error),
context_obj(context_obj) { context_obj(context_obj) {
} }
Channel::Channel( Channel::Channel(
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
Version version, Version version,
uint8_t language, uint8_t language,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const string& name, const string& name,
TerminalFormat terminal_send_color, TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color) TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent), : bev(nullptr, flush_and_free_bufferevent),
version(version), version(version),
language(language), language(language),
name(name), name(name),
terminal_send_color(terminal_send_color), terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color), terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received), on_command_received(on_command_received),
on_error(on_error), on_error(on_error),
context_obj(context_obj) { context_obj(context_obj) {
this->set_bufferevent(bev, virtual_network_id); this->set_bufferevent(bev, virtual_network_id);
} }
void Channel::replace_with( void Channel::replace_with(
Channel&& other, Channel&& other,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const std::string& name) { const std::string& name) {
this->set_bufferevent(other.bev.release(), other.virtual_network_id); this->set_bufferevent(other.bev.release(), other.virtual_network_id);
this->local_addr = other.local_addr; this->local_addr = other.local_addr;
this->remote_addr = other.remote_addr; this->remote_addr = other.remote_addr;
this->version = other.version; this->version = other.version;
this->language = other.language; this->language = other.language;
this->crypt_in = other.crypt_in; this->crypt_in = other.crypt_in;
this->crypt_out = other.crypt_out; this->crypt_out = other.crypt_out;
this->name = name; this->name = name;
this->terminal_send_color = other.terminal_send_color; this->terminal_send_color = other.terminal_send_color;
this->terminal_recv_color = other.terminal_recv_color; this->terminal_recv_color = other.terminal_recv_color;
this->on_command_received = on_command_received; this->on_command_received = on_command_received;
this->on_error = on_error; this->on_error = on_error;
this->context_obj = context_obj; this->context_obj = context_obj;
other.disconnect(); // Clears crypts, addrs, etc. other.disconnect(); // Clears crypts, addrs, etc.
} }
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) { void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
this->bev.reset(bev); this->bev.reset(bev);
this->virtual_network_id = virtual_network_id; this->virtual_network_id = virtual_network_id;
if (this->bev.get()) { if (this->bev.get()) {
int fd = bufferevent_getfd(this->bev.get()); int fd = bufferevent_getfd(this->bev.get());
if (fd < 0) { if (fd < 0) {
memset(&this->local_addr, 0, sizeof(this->local_addr)); memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr)); memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else { } else {
get_socket_addresses(fd, &this->local_addr, &this->remote_addr); 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_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE); bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
} else { } else {
memset(&this->local_addr, 0, sizeof(this->local_addr)); memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr)); memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} }
} }
void Channel::disconnect() { void Channel::disconnect() {
if (this->bev.get()) { if (this->bev.get()) {
// If the output buffer is not empty, move the bufferevent into the draining // 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. // pool instead of disconnecting it, to make sure all the data gets sent.
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get()); struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
if (evbuffer_get_length(out_buffer) == 0) { if (evbuffer_get_length(out_buffer) == 0) {
this->bev.reset(); // Destructor flushes and frees the bufferevent this->bev.reset(); // Destructor flushes and frees the bufferevent
} else { } else {
// The callbacks will free it when all the data is sent or the client // The callbacks will free it when all the data is sent or the client
// disconnects // disconnects
auto on_output = +[](struct bufferevent* bev, void*) -> void { auto on_output = +[](struct bufferevent* bev, void*) -> void {
flush_and_free_bufferevent(bev); flush_and_free_bufferevent(bev);
}; };
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void { auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
if (events & BEV_EVENT_ERROR) { if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
channel_exceptions_log.warning( channel_exceptions_log.warning(
"Disconnecting channel caused error %d (%s)", err, "Disconnecting channel caused error %d (%s)", err,
evutil_socket_error_to_string(err)); evutil_socket_error_to_string(err));
} }
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED); bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev); bufferevent_free(bev);
} }
}; };
struct bufferevent* bev = this->bev.release(); struct bufferevent* bev = this->bev.release();
bufferevent_setcb(bev, nullptr, on_output, on_error, bev); bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
bufferevent_disable(bev, EV_READ); bufferevent_disable(bev, EV_READ);
} }
} }
memset(&this->local_addr, 0, sizeof(this->local_addr)); memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr)); memset(&this->remote_addr, 0, sizeof(this->remote_addr));
this->virtual_network_id = false; this->virtual_network_id = false;
this->crypt_in.reset(); this->crypt_in.reset();
this->crypt_out.reset(); this->crypt_out.reset();
} }
Channel::Message Channel::recv() { Channel::Message Channel::recv() {
struct evbuffer* buf = bufferevent_get_input(this->bev.get()); struct evbuffer* buf = bufferevent_get_input(this->bev.get());
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4; size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
PSOCommandHeader header; PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) { if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
throw out_of_range("no command available"); throw out_of_range("no command available");
} }
if (this->crypt_in.get()) { if (this->crypt_in.get()) {
this->crypt_in->decrypt(&header, header_size, false); this->crypt_in->decrypt(&header, header_size, false);
} }
size_t command_logical_size = header.size(version); size_t command_logical_size = header.size(version);
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this // 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 reflected in the size field. This logic does not occur if encryption
// is not yet enabled. // is not yet enabled.
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4)) size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
? ((command_logical_size + 7) & ~7) ? ((command_logical_size + 7) & ~7)
: command_logical_size; : command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) { if (evbuffer_get_length(buf) < command_physical_size) {
throw out_of_range("no command available"); throw out_of_range("no command available");
} }
// If we get here, then there is a full command in the buffer. Some encryption // 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 // algorithms' advancement depends on the decrypted data, so we have to
// actually decrypt the header again (with advance=true) to keep them in a // actually decrypt the header again (with advance=true) to keep them in a
// consistent state. // consistent state.
string header_data(header_size, '\0'); string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) { 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"); throw logic_error("enough bytes available, but could not remove them");
} }
if (this->crypt_in.get()) { if (this->crypt_in.get()) {
this->crypt_in->decrypt(header_data.data(), header_data.size()); this->crypt_in->decrypt(header_data.data(), header_data.size());
} }
string command_data(command_physical_size - header_size, '\0'); 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())) { 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"); throw logic_error("enough bytes available, but could not remove them");
} }
if (this->crypt_in.get()) { if (this->crypt_in.get()) {
// Some versions of PSO DC can send commands whose sizes are not a multiple // 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 // 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, // decrypting (the extra cipher bytes are lost). To emulate this behavior,
// we have to round up the size for DC commands here. // we have to round up the size for DC commands here.
size_t orig_size = command_data.size(); size_t orig_size = command_data.size();
command_data.resize((orig_size + 3) & (~3), 0); command_data.resize((orig_size + 3) & (~3), 0);
this->crypt_in->decrypt(command_data.data(), command_data.size()); this->crypt_in->decrypt(command_data.data(), command_data.size());
command_data.resize(orig_size); command_data.resize(orig_size);
} }
command_data.resize(command_logical_size - header_size); command_data.resize(command_logical_size - header_size);
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) { if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) { if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END); print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
} }
if (version == Version::BB_V4) { if (version == Version::BB_V4) {
command_data_log.info( command_data_log.info(
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")", "Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(), this->name.c_str(),
header.command(this->version), header.command(this->version),
header.flag(this->version)); header.flag(this->version));
} else { } else {
command_data_log.info( command_data_log.info(
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")", "Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(), this->name.c_str(),
name_for_enum(this->version), name_for_enum(this->version),
header.command(this->version), header.command(this->version),
header.flag(this->version)); header.flag(this->version));
} }
vector<struct iovec> iovs; vector<struct iovec> iovs;
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()}); 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()}); 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); 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) { if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END); print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
} }
} }
return { return {
.command = header.command(this->version), .command = header.command(this->version),
.flag = header.flag(this->version), .flag = header.flag(this->version),
.data = std::move(command_data), .data = std::move(command_data),
}; };
} }
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) { void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
this->send(cmd, flag, nullptr, 0, 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) { void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
if (!this->connected()) { if (!this->connected()) {
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data"); channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
return; return;
} }
size_t size = 0; size_t size = 0;
for (const auto& b : blocks) { for (const auto& b : blocks) {
size += b.second; size += b.second;
} }
string send_data; string send_data;
size_t logical_size; size_t logical_size;
size_t send_data_size = 0; size_t send_data_size = 0;
switch (this->version) { switch (this->version) {
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
case Version::XB_V3: { case Version::XB_V3: {
PSOCommandHeaderDCV3 header; PSOCommandHeaderDCV3 header;
if (this->crypt_out.get() && if (this->crypt_out.get() &&
(this->version != Version::DC_NTE) && (this->version != Version::DC_NTE) &&
(this->version != Version::DC_V1_11_2000_PROTOTYPE) && (this->version != Version::DC_V1_11_2000_PROTOTYPE) &&
(this->version != Version::DC_V1)) { (this->version != Version::DC_V1)) {
send_data_size = (sizeof(header) + size + 3) & ~3; send_data_size = (sizeof(header) + size + 3) & ~3;
} else { } else {
send_data_size = (sizeof(header) + size); send_data_size = (sizeof(header) + size);
} }
logical_size = send_data_size; logical_size = send_data_size;
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
header.size = send_data_size; header.size = send_data_size;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header)); send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break; break;
} }
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: { case Version::PC_V2: {
PSOCommandHeaderPC header; PSOCommandHeaderPC header;
if (this->crypt_out.get()) { if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3; send_data_size = (sizeof(header) + size + 3) & ~3;
} else { } else {
send_data_size = (sizeof(header) + size); send_data_size = (sizeof(header) + size);
} }
logical_size = send_data_size; logical_size = send_data_size;
header.size = send_data_size; header.size = send_data_size;
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header)); send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break; break;
} }
case Version::BB_V4: { case Version::BB_V4: {
// BB has an annoying behavior here: command lengths must be multiples of // 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 // 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 // field is not divisible by 8, 4 extra bytes are sent anyway. This
// behavior only applies when encryption is enabled - any commands sent // behavior only applies when encryption is enabled - any commands sent
// before encryption is enabled have no size restrictions (except they // before encryption is enabled have no size restrictions (except they
// must include a full header and must fit in the client's receive // must include a full header and must fit in the client's receive
// buffer), and no implicit extra bytes are sent. // buffer), and no implicit extra bytes are sent.
PSOCommandHeaderBB header; PSOCommandHeaderBB header;
if (this->crypt_out.get()) { if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 7) & ~7; send_data_size = (sizeof(header) + size + 7) & ~7;
} else { } else {
send_data_size = (sizeof(header) + size); send_data_size = (sizeof(header) + size);
} }
logical_size = (sizeof(header) + size + 3) & ~3; logical_size = (sizeof(header) + size + 3) & ~3;
header.size = logical_size; header.size = logical_size;
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header)); send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break; break;
} }
default: default:
throw logic_error("unimplemented game version in send_command"); throw logic_error("unimplemented game version in send_command");
} }
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00 // All versions of PSO I've seen (so far) have a receive buffer 0x7C00
// bytes in size // bytes in size
if (send_data_size > 0x7C00) { if (send_data_size > 0x7C00) {
throw runtime_error("outbound command too large"); throw runtime_error("outbound command too large");
} }
send_data.reserve(send_data_size); send_data.reserve(send_data_size);
for (const auto& b : blocks) { for (const auto& b : blocks) {
send_data.append(reinterpret_cast<const char*>(b.first), b.second); send_data.append(reinterpret_cast<const char*>(b.first), b.second);
} }
send_data.resize(send_data_size, '\0'); send_data.resize(send_data_size, '\0');
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) { 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) { if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END); print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
} }
if (version == Version::BB_V4) { if (version == Version::BB_V4) {
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")", command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(), cmd, flag); this->name.c_str(), cmd, flag);
} else { } else {
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")", command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(), name_for_enum(version), cmd, flag); 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); 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) { if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END); print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
} }
} }
if (this->crypt_out.get()) { if (this->crypt_out.get()) {
this->crypt_out->encrypt(send_data.data(), send_data.size()); this->crypt_out->encrypt(send_data.data(), send_data.size());
} }
struct evbuffer* buf = bufferevent_get_output(this->bev.get()); struct evbuffer* buf = bufferevent_get_output(this->bev.get());
evbuffer_add(buf, send_data.data(), send_data.size()); 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) { 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); this->send(cmd, flag, {make_pair(data, size)}, silent);
} }
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) { void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
this->send(cmd, flag, data.data(), data.size(), silent); this->send(cmd, flag, data.data(), data.size(), silent);
} }
void Channel::send(const void* data, size_t size, bool silent) { void Channel::send(const void* data, size_t size, bool silent) {
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4; size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data); const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send( this->send(
header->command(this->version), header->command(this->version),
header->flag(this->version), header->flag(this->version),
reinterpret_cast<const uint8_t*>(data) + header_size, reinterpret_cast<const uint8_t*>(data) + header_size,
size - header_size, size - header_size,
silent); silent);
} }
void Channel::send(const string& data, bool silent) { void Channel::send(const string& data, bool silent) {
return this->send(data.data(), data.size(), silent); return this->send(data.data(), data.size(), silent);
} }
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) { void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx); Channel* ch = reinterpret_cast<Channel*>(ctx);
// The client can be disconnected during on_command_received, so we have to // The client can be disconnected during on_command_received, so we have to
// make sure ch->bev is valid every time before calling recv() // make sure ch->bev is valid every time before calling recv()
while (ch->bev.get()) { while (ch->bev.get()) {
Message msg; Message msg;
try { try {
msg = ch->recv(); msg = ch->recv();
} catch (const out_of_range&) { } catch (const out_of_range&) {
break; break;
} catch (const exception& e) { } catch (const exception& e) {
channel_exceptions_log.warning("Error receiving on channel: %s", e.what()); channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
ch->on_error(*ch, BEV_EVENT_ERROR); ch->on_error(*ch, BEV_EVENT_ERROR);
break; break;
} }
if (ch->on_command_received) { if (ch->on_command_received) {
ch->on_command_received(*ch, msg.command, msg.flag, msg.data); ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
} }
} }
} }
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) { void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx); Channel* ch = reinterpret_cast<Channel*>(ctx);
if (ch->on_error) { if (ch->on_error) {
ch->on_error(*ch, events); ch->on_error(*ch, events);
} else { } else {
ch->disconnect(); ch->disconnect();
} }
} }
+103 -103
View File
@@ -1,103 +1,103 @@
#pragma once #pragma once
#include <netinet/in.h> #include <netinet/in.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "Version.hh" #include "Version.hh"
struct Channel { struct Channel {
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev; std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
struct sockaddr_storage local_addr; struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr; struct sockaddr_storage remote_addr;
uint64_t virtual_network_id; // 0 = normal TCP connection uint64_t virtual_network_id; // 0 = normal TCP connection
Version version; Version version;
uint8_t language; uint8_t language;
std::shared_ptr<PSOEncryption> crypt_in; std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out; std::shared_ptr<PSOEncryption> crypt_out;
std::string name; std::string name;
TerminalFormat terminal_send_color; TerminalFormat terminal_send_color;
TerminalFormat terminal_recv_color; TerminalFormat terminal_recv_color;
struct Message { struct Message {
uint16_t command; uint16_t command;
uint32_t flag; uint32_t flag;
std::string data; std::string data;
}; };
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&); typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
typedef void (*on_error_t)(Channel&, short); typedef void (*on_error_t)(Channel&, short);
on_command_received_t on_command_received; on_command_received_t on_command_received;
on_error_t on_error; on_error_t on_error;
void* context_obj; void* context_obj;
// Creates an unconnected channel // Creates an unconnected channel
Channel( Channel(
Version version, Version version,
uint8_t language, uint8_t language,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const std::string& name, const std::string& name,
TerminalFormat terminal_send_color = TerminalFormat::END, TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END); TerminalFormat terminal_recv_color = TerminalFormat::END);
// Creates a connected channel // Creates a connected channel
Channel( Channel(
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
Version version, Version version,
uint8_t language, uint8_t language,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const std::string& name = "", const std::string& name = "",
TerminalFormat terminal_send_color = TerminalFormat::END, TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END); TerminalFormat terminal_recv_color = TerminalFormat::END);
Channel(const Channel& other) = delete; Channel(const Channel& other) = delete;
Channel(Channel&& other) = delete; Channel(Channel&& other) = delete;
Channel& operator=(const Channel& other) = delete; Channel& operator=(const Channel& other) = delete;
Channel& operator=(Channel&& other) = delete; Channel& operator=(Channel&& other) = delete;
void replace_with( void replace_with(
Channel&& other, Channel&& other,
on_command_received_t on_command_received, on_command_received_t on_command_received,
on_error_t on_error, on_error_t on_error,
void* context_obj, void* context_obj,
const std::string& name = ""); const std::string& name = "");
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id); void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
inline bool connected() const { inline bool connected() const {
return this->bev.get() != nullptr; return this->bev.get() != nullptr;
} }
void disconnect(); void disconnect();
// Receives a message. Throws std::out_of_range if no messages are available. // Receives a message. Throws std::out_of_range if no messages are available.
Message recv(); Message recv();
// Sends a message with an automatically-constructed header. // 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 = 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 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::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); void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false);
template <typename CmdT> template <typename CmdT>
requires(!std::is_pointer_v<CmdT>) requires(!std::is_pointer_v<CmdT>)
void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) { void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) {
this->send(cmd, flag, &data, sizeof(data), silent); this->send(cmd, flag, &data, sizeof(data), silent);
} }
// Sends a message with a pre-existing header (as the first few bytes in the // Sends a message with a pre-existing header (as the first few bytes in the
// data) // data)
void send(const void* data, size_t size, bool silent = false); void send(const void* data, size_t size, bool silent = false);
void send(const std::string& data, bool silent = false); void send(const std::string& data, bool silent = false);
private: private:
static void dispatch_on_input(struct bufferevent*, void* ctx); static void dispatch_on_input(struct bufferevent*, void* ctx);
static void dispatch_on_error(struct bufferevent*, short events, 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include "Client.hh" #include "Client.hh"
#include "Lobby.hh" #include "Lobby.hh"
#include "ProxyServer.hh" #include "ProxyServer.hh"
#include "ServerState.hh" #include "ServerState.hh"
void on_chat_command(std::shared_ptr<Client> c, const std::string& text); 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); 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 "ChoiceSearch.hh"
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <string.h>
#include "Client.hh" #include "Client.hh"
using namespace std; using namespace std;
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
ChoiceSearchCategory{ ChoiceSearchCategory{
.id = 0x0001, .id = 0x0001,
.name = "Level", .name = "Level",
.choices = { .choices = {
{0x0000, "Any"}, {0x0000, "Any"},
{0x0001, "Own level +/- 5"}, {0x0001, "Own level +/- 5"},
{0x0002, "Level 1-10"}, {0x0002, "Level 1-10"},
{0x0003, "Level 11-20"}, {0x0003, "Level 11-20"},
{0x0004, "Level 21-40"}, {0x0004, "Level 21-40"},
{0x0005, "Level 41-60"}, {0x0005, "Level 41-60"},
{0x0006, "Level 61-80"}, {0x0006, "Level 61-80"},
{0x0007, "Level 81-100"}, {0x0007, "Level 81-100"},
{0x0008, "Level 101-120"}, {0x0008, "Level 101-120"},
{0x0009, "Level 121-160"}, {0x0009, "Level 121-160"},
{0x000A, "Level 161-200"}, {0x000A, "Level 161-200"},
}, },
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool { .client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) { if (choice_id == 0x0000) {
return true; return true;
} }
uint32_t target_level = target_c->character()->disp.stats.level + 1; uint32_t target_level = target_c->character()->disp.stats.level + 1;
switch (choice_id) { switch (choice_id) {
case 0x0001: case 0x0001:
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5); return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
case 0x0002: case 0x0002:
return (target_level <= 10); return (target_level <= 10);
case 0x0003: case 0x0003:
return (target_level > 10) && (target_level <= 20); return (target_level > 10) && (target_level <= 20);
case 0x0004: case 0x0004:
return (target_level > 20) && (target_level <= 40); return (target_level > 20) && (target_level <= 40);
case 0x0005: case 0x0005:
return (target_level > 40) && (target_level <= 60); return (target_level > 40) && (target_level <= 60);
case 0x0006: case 0x0006:
return (target_level > 60) && (target_level <= 80); return (target_level > 60) && (target_level <= 80);
case 0x0007: case 0x0007:
return (target_level > 80) && (target_level <= 100); return (target_level > 80) && (target_level <= 100);
case 0x0008: case 0x0008:
return (target_level > 100) && (target_level <= 120); return (target_level > 100) && (target_level <= 120);
case 0x0009: case 0x0009:
return (target_level > 120) && (target_level <= 160); return (target_level > 120) && (target_level <= 160);
case 0x000A: case 0x000A:
return (target_level > 160) && (target_level <= 200); return (target_level > 160) && (target_level <= 200);
} }
return false; return false;
}, },
}, },
ChoiceSearchCategory{ ChoiceSearchCategory{
.id = 0x0002, .id = 0x0002,
.name = "Class", .name = "Class",
.choices = { .choices = {
{0x0000, "Any"}, {0x0000, "Any"},
{0x0010, "Hunter"}, {0x0010, "Hunter"},
{0x0001, "HUmar"}, {0x0001, "HUmar"},
{0x0002, "HUnewearl"}, {0x0002, "HUnewearl"},
{0x0003, "HUcast"}, {0x0003, "HUcast"},
{0x000A, "HUcaseal"}, {0x000A, "HUcaseal"},
{0x0011, "Ranger"}, {0x0011, "Ranger"},
{0x0004, "RAmar"}, {0x0004, "RAmar"},
{0x000C, "RAmarl"}, {0x000C, "RAmarl"},
{0x0005, "RAcast"}, {0x0005, "RAcast"},
{0x0006, "RAcaseal"}, {0x0006, "RAcaseal"},
{0x0012, "Force"}, {0x0012, "Force"},
{0x000B, "FOmar"}, {0x000B, "FOmar"},
{0x0007, "FOmarl"}, {0x0007, "FOmarl"},
{0x0008, "FOnewm"}, {0x0008, "FOnewm"},
{0x0009, "FOnewearl"}, {0x0009, "FOnewearl"},
}, },
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool { .client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
switch (choice_id) { switch (choice_id) {
case 0x0000: case 0x0000:
return true; return true;
case 0x0010: case 0x0010:
return target_c->character()->disp.visual.class_flags & 0x20; return target_c->character()->disp.visual.class_flags & 0x20;
case 0x0011: case 0x0011:
return target_c->character()->disp.visual.class_flags & 0x40; return target_c->character()->disp.visual.class_flags & 0x40;
case 0x0012: case 0x0012:
return target_c->character()->disp.visual.class_flags & 0x80; return target_c->character()->disp.visual.class_flags & 0x80;
default: default:
return ((choice_id - 1) == target_c->character()->disp.visual.char_class); return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
} }
}, },
}, },
ChoiceSearchCategory{ ChoiceSearchCategory{
.id = 0x0003, .id = 0x0003,
.name = "Platform", .name = "Platform",
.choices = { .choices = {
{0x0000, "Any"}, {0x0000, "Any"},
{0x0001, "DC betas"}, {0x0001, "DC betas"},
{0x0002, "DC V1"}, {0x0002, "DC V1"},
{0x0003, "DC V2 / PC"}, {0x0003, "DC V2 / PC"},
{0x0004, "GC / Xbox Episodes 1&2"}, {0x0004, "GC / Xbox Episodes 1&2"},
{0x0005, "GC Episode 3"}, {0x0005, "GC Episode 3"},
{0x0006, "BB"}, {0x0006, "BB"},
}, },
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool { .client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
if (choice_id == 0x0000) { if (choice_id == 0x0000) {
return true; return true;
} }
switch (target_c->version()) { switch (target_c->version()) {
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
return (choice_id == 0x0001); return (choice_id == 0x0001);
case Version::DC_V1: case Version::DC_V1:
return (choice_id == 0x0002); return (choice_id == 0x0002);
case Version::DC_V2: case Version::DC_V2:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return (choice_id == 0x0003); return (choice_id == 0x0003);
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::XB_V3: case Version::XB_V3:
return (choice_id == 0x0004); return (choice_id == 0x0004);
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return (choice_id == 0x0005); return (choice_id == 0x0005);
case Version::BB_V4: case Version::BB_V4:
return (choice_id == 0x0006); return (choice_id == 0x0006);
default: default:
return false; return false;
} }
}, },
}, },
ChoiceSearchCategory{ ChoiceSearchCategory{
.id = 0x0204, .id = 0x0204,
.name = "Game mode", .name = "Game mode",
.choices = { .choices = {
{0x0000, "Any"}, {0x0000, "Any"},
{0x0001, "Normal"}, {0x0001, "Normal"},
{0x0002, "Hard"}, {0x0002, "Hard"},
{0x0003, "Very Hard"}, {0x0003, "Very Hard"},
{0x0004, "Ultimate"}, {0x0004, "Ultimate"},
{0x0005, "Battle"}, {0x0005, "Battle"},
{0x0006, "Challenge"}, {0x0006, "Challenge"},
}, },
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool { .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); 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); return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
}, },
}, },
}); });
+64 -64
View File
@@ -1,64 +1,64 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Text.hh" #include "Text.hh"
class Client; class Client;
template <bool IsBigEndian> template <bool IsBigEndian>
struct ChoiceSearchConfigT { struct ChoiceSearchConfigT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type; 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 U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3 U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
struct Entry { struct Entry {
U16T parent_choice_id = 0; U16T parent_choice_id = 0;
U16T choice_id = 0; U16T choice_id = 0;
} __packed_ws__(Entry, 4); } __packed_ws__(Entry, 4);
parray<Entry, 5> entries; parray<Entry, 5> entries;
int32_t get_setting(uint16_t parent_choice_id) const { int32_t get_setting(uint16_t parent_choice_id) const {
for (size_t z = 0; z < this->entries.size(); z++) { for (size_t z = 0; z < this->entries.size(); z++) {
if (this->entries[z].parent_choice_id == parent_choice_id) { if (this->entries[z].parent_choice_id == parent_choice_id) {
return this->entries[z].choice_id; return this->entries[z].choice_id;
} }
} }
return -1; return -1;
} }
operator ChoiceSearchConfigT<!IsBigEndian>() const { operator ChoiceSearchConfigT<!IsBigEndian>() const {
ChoiceSearchConfigT<!IsBigEndian> ret; ChoiceSearchConfigT<!IsBigEndian> ret;
ret.disabled = this->disabled.load(); ret.disabled = this->disabled.load();
for (size_t z = 0; z < this->entries.size(); z++) { for (size_t z = 0; z < this->entries.size(); z++) {
auto& ret_e = ret.entries[z]; auto& ret_e = ret.entries[z];
const auto& this_e = this->entries[z]; const auto& this_e = this->entries[z];
ret_e.parent_choice_id = this_e.parent_choice_id.load(); ret_e.parent_choice_id = this_e.parent_choice_id.load();
ret_e.choice_id = this_e.choice_id.load(); ret_e.choice_id = this_e.choice_id.load();
} }
return ret; return ret;
} }
} __packed__; } __packed__;
using ChoiceSearchConfig = ChoiceSearchConfigT<false>; using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>; using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
check_struct_size(ChoiceSearchConfig, 0x18); check_struct_size(ChoiceSearchConfig, 0x18);
check_struct_size(ChoiceSearchConfigBE, 0x18); check_struct_size(ChoiceSearchConfigBE, 0x18);
struct ChoiceSearchCategory { struct ChoiceSearchCategory {
struct Choice { struct Choice {
uint16_t id; uint16_t id;
const char* name; const char* name;
}; };
uint16_t id; uint16_t id;
const char* name; const char* name;
std::vector<Choice> choices; 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; 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; 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 #pragma once
#include <netinet/in.h> #include <netinet/in.h>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include "Account.hh" #include "Account.hh"
#include "Channel.hh" #include "Channel.hh"
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh" #include "Episode3/BattleRecord.hh"
#include "Episode3/Tournament.hh" #include "Episode3/Tournament.hh"
#include "FileContentsCache.hh" #include "FileContentsCache.hh"
#include "FunctionCompiler.hh" #include "FunctionCompiler.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "PatchFileIndex.hh" #include "PatchFileIndex.hh"
#include "Quest.hh" #include "Quest.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
#include "Text.hh" #include "Text.hh"
extern const uint64_t CLIENT_CONFIG_MAGIC; extern const uint64_t CLIENT_CONFIG_MAGIC;
class Server; class Server;
struct Lobby; struct Lobby;
class Parsed6x70Data; class Parsed6x70Data;
class Client : public std::enable_shared_from_this<Client> { class Client : public std::enable_shared_from_this<Client> {
public: public:
enum class Flag : uint64_t { enum class Flag : uint64_t {
// clang-format off // clang-format off
// This mask specifies which flags are sent to the client // 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 // 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 // in the high bits) but that would require re-recording or manually
// rewriting all the tests // rewriting all the tests
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB, CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
// Version-related flags // Version-related flags
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002, CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
NO_D6_AFTER_LOBBY = 0x0000000000000100, NO_D6_AFTER_LOBBY = 0x0000000000000100,
NO_D6 = 0x0000000000000200, NO_D6 = 0x0000000000000200,
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400, FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
// Flags describing the behavior for send_function_call // Flags describing the behavior for send_function_call
NO_SEND_FUNCTION_CALL = 0x0000000000001000, NO_SEND_FUNCTION_CALL = 0x0000000000001000,
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000, ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000, SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000, SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000, USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
// State flags // State flags
LOADING = 0x0000000000100000, // Server-side only LOADING = 0x0000000000100000, // Server-side only
LOADING_QUEST = 0x0000000000200000, // Server-side only LOADING_QUEST = 0x0000000000200000, // Server-side only
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only
LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only
IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only
AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only
SAVE_ENABLED = 0x0000000004000000, SAVE_ENABLED = 0x0000000004000000,
HAS_EP3_CARD_DEFS = 0x0000000008000000, HAS_EP3_CARD_DEFS = 0x0000000008000000,
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000, HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000, USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
HAS_GUILD_CARD_NUMBER = 0x0000000040000000, HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
HAS_AUTO_PATCHES = 0x0000004000000000, HAS_AUTO_PATCHES = 0x0000004000000000,
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // 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_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000, SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
SWITCH_ASSIST_ENABLED = 0x0000000100000000, SWITCH_ASSIST_ENABLED = 0x0000000100000000,
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000, IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
// Cheat mode and option flags // Cheat mode and option flags
INFINITE_HP_ENABLED = 0x0000000200000000, INFINITE_HP_ENABLED = 0x0000000200000000,
INFINITE_TP_ENABLED = 0x0000000400000000, INFINITE_TP_ENABLED = 0x0000000400000000,
DEBUG_ENABLED = 0x0000000800000000, DEBUG_ENABLED = 0x0000000800000000,
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000, ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000, ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
// Proxy option flags // Proxy option flags
PROXY_SAVE_FILES = 0x0000001000000000, PROXY_SAVE_FILES = 0x0000001000000000,
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000, PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000, PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000, PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000, PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000, PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000, PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000, PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
PROXY_RED_NAME_ENABLED = 0x0000200000000000, PROXY_RED_NAME_ENABLED = 0x0000200000000000,
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000, PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000, PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000, PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
// clang-format on // clang-format on
}; };
enum class ItemDropNotificationMode { enum class ItemDropNotificationMode {
NOTHING = 0, NOTHING = 0,
RARES_ONLY = 1, RARES_ONLY = 1,
ALL_ITEMS = 2, ALL_ITEMS = 2,
ALL_ITEMS_INCLUDING_MESETA = 3, ALL_ITEMS_INCLUDING_MESETA = 3,
}; };
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED); static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
struct Config { struct Config {
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
uint32_t specific_version = 0; uint32_t specific_version = 0;
int32_t override_random_seed = 0; int32_t override_random_seed = 0;
uint8_t override_section_id = 0xFF; // FF = no override uint8_t override_section_id = 0xFF; // FF = no override
uint8_t override_lobby_event = 0xFF; // FF = no override uint8_t override_lobby_event = 0xFF; // FF = no override
uint8_t override_lobby_number = 0x80; // 80 = no override uint8_t override_lobby_number = 0x80; // 80 = no override
uint32_t proxy_destination_address = 0; uint32_t proxy_destination_address = 0;
uint16_t proxy_destination_port = 0; uint16_t proxy_destination_port = 0;
Config() = default; Config() = default;
bool operator==(const Config& other) const = default; bool operator==(const Config& other) const = default;
bool operator!=(const Config& other) const = default; bool operator!=(const Config& other) const = default;
bool should_update_vs(const Config& other) const; bool should_update_vs(const Config& other) const;
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) { [[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
return !!(enabled_flags & static_cast<uint64_t>(flag)); return !!(enabled_flags & static_cast<uint64_t>(flag));
} }
[[nodiscard]] inline bool check_flag(Flag flag) const { [[nodiscard]] inline bool check_flag(Flag flag) const {
return this->check_flag(this->enabled_flags, flag); return this->check_flag(this->enabled_flags, flag);
} }
inline void set_flag(Flag flag) { inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint64_t>(flag); this->enabled_flags |= static_cast<uint64_t>(flag);
} }
inline void clear_flag(Flag flag) { inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint64_t>(flag)); this->enabled_flags &= (~static_cast<uint64_t>(flag));
} }
inline void toggle_flag(Flag flag) { inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint64_t>(flag); this->enabled_flags ^= static_cast<uint64_t>(flag);
} }
void set_flags_for_version(Version version, int64_t sub_version); void set_flags_for_version(Version version, int64_t sub_version);
ItemDropNotificationMode get_drop_notification_mode() const; ItemDropNotificationMode get_drop_notification_mode() const;
void set_drop_notification_mode(ItemDropNotificationMode new_mode); void set_drop_notification_mode(ItemDropNotificationMode new_mode);
template <size_t Bytes> template <size_t Bytes>
void parse_from(const parray<uint8_t, Bytes>& data) { void parse_from(const parray<uint8_t, Bytes>& data) {
StringReader r(data.data(), data.size()); StringReader r(data.data(), data.size());
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) { if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
throw std::invalid_argument("config signature is incorrect"); throw std::invalid_argument("config signature is incorrect");
} }
this->specific_version = r.get_u32l(); this->specific_version = r.get_u32l();
this->enabled_flags = r.get_u64l(); this->enabled_flags = r.get_u64l();
this->override_random_seed = r.get_u32l(); this->override_random_seed = r.get_u32l();
this->proxy_destination_address = r.get_u32b(); this->proxy_destination_address = r.get_u32b();
this->proxy_destination_port = r.get_u16l(); this->proxy_destination_port = r.get_u16l();
this->override_section_id = r.get_u8(); this->override_section_id = r.get_u8();
this->override_lobby_event = r.get_u8(); this->override_lobby_event = r.get_u8();
this->override_lobby_number = r.get_u8(); this->override_lobby_number = r.get_u8();
} }
template <size_t Bytes> template <size_t Bytes>
void serialize_into(parray<uint8_t, Bytes>& data) const { void serialize_into(parray<uint8_t, Bytes>& data) const {
StringWriter w; StringWriter w;
w.put_u32l(CLIENT_CONFIG_MAGIC); w.put_u32l(CLIENT_CONFIG_MAGIC);
w.put_u32l(this->specific_version); w.put_u32l(this->specific_version);
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK)); w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
w.put_u32l(this->override_random_seed); w.put_u32l(this->override_random_seed);
w.put_u32b(this->proxy_destination_address); w.put_u32b(this->proxy_destination_address);
w.put_u16l(this->proxy_destination_port); w.put_u16l(this->proxy_destination_port);
w.put_u8(this->override_section_id); w.put_u8(this->override_section_id);
w.put_u8(this->override_lobby_event); w.put_u8(this->override_lobby_event);
w.put_u8(this->override_lobby_number); w.put_u8(this->override_lobby_number);
const auto& s = w.str(); const auto& s = w.str();
for (size_t z = 0; z < s.size(); z++) { for (size_t z = 0; z < s.size(); z++) {
data[z] = s[z]; data[z] = s[z];
} }
data.clear_after(s.size(), 0xFF); data.clear_after(s.size(), 0xFF);
} }
}; };
std::weak_ptr<Server> server; std::weak_ptr<Server> server;
uint64_t id; uint64_t id;
PrefixedLogger log; PrefixedLogger log;
std::shared_ptr<Login> login; std::shared_ptr<Login> login;
// Network // Network
Channel channel; Channel channel;
struct sockaddr_storage next_connection_addr; struct sockaddr_storage next_connection_addr;
ServerBehavior server_behavior; ServerBehavior server_behavior;
bool should_disconnect; bool should_disconnect;
bool should_send_to_lobby_server; bool should_send_to_lobby_server;
bool should_send_to_proxy_server; bool should_send_to_proxy_server;
std::unordered_map<std::string, std::function<void()>> disconnect_hooks; std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
std::shared_ptr<XBNetworkLocation> xb_netloc; std::shared_ptr<XBNetworkLocation> xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a; parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint8_t bb_connection_phase; uint8_t bb_connection_phase;
uint64_t ping_start_time; uint64_t ping_start_time;
// Lobby/positioning // Lobby/positioning
Config config; Config config;
Config synced_config; Config synced_config;
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations; std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
int32_t sub_version; int32_t sub_version;
float x; float x;
float z; float z;
uint32_t floor; uint32_t floor;
std::weak_ptr<Lobby> lobby; std::weak_ptr<Lobby> lobby;
uint8_t lobby_client_id; uint8_t lobby_client_id;
uint8_t lobby_arrow_color; uint8_t lobby_arrow_color;
int64_t preferred_lobby_id; // <0 = no preference 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*)> save_game_data_event;
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event; std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event; std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
int16_t card_battle_table_number; int16_t card_battle_table_number;
uint16_t card_battle_table_seat_number; uint16_t card_battle_table_seat_number;
uint16_t card_battle_table_seat_state; uint16_t card_battle_table_seat_state;
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team; std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record; std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
std::shared_ptr<const Menu> last_menu_sent; std::shared_ptr<const Menu> last_menu_sent;
uint32_t last_game_info_requested; uint32_t last_game_info_requested;
struct JoinCommand { struct JoinCommand {
uint16_t command; uint16_t command;
uint32_t flag; uint32_t flag;
std::string data; std::string data;
}; };
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue; std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
// Character / game data // Character / game data
struct PendingItemTrade { struct PendingItemTrade {
uint8_t other_client_id; uint8_t other_client_id;
bool confirmed; // true if client has sent a D2 command bool confirmed; // true if client has sent a D2 command
std::vector<ItemData> items; std::vector<ItemData> items;
}; };
struct PendingCardTrade { struct PendingCardTrade {
uint8_t other_client_id; uint8_t other_client_id;
bool confirmed; // true if client has sent an EE D2 command bool confirmed; // true if client has sent an EE D2 command
std::vector<std::pair<uint32_t, uint32_t>> card_to_count; std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
}; };
bool should_update_play_time; bool should_update_play_time;
std::unordered_set<uint32_t> blocked_senders; std::unordered_set<uint32_t> blocked_senders;
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp; std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
std::shared_ptr<Parsed6x70Data> last_reported_6x70; std::shared_ptr<Parsed6x70Data> last_reported_6x70;
// These are null unless the client is within the trade sequence (D0-D4 or EE commands) // 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<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade; std::unique_ptr<PendingCardTrade> pending_card_trade;
uint32_t telepipe_lobby_id; uint32_t telepipe_lobby_id;
G_SetTelepipeState_6x68 telepipe_state; G_SetTelepipeState_6x68 telepipe_state;
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3 std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
int8_t bb_character_index; int8_t bb_character_index;
ItemData bb_identify_result; ItemData bb_identify_result;
std::array<std::vector<ItemData>, 3> bb_shop_contents; std::array<std::vector<ItemData>, 3> bb_shop_contents;
// Miscellaneous (used by chat commands) // Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give uint32_t next_exp_value; // next EXP value to give
RecentSwitchFlags recent_switch_flags; // used for switch assist RecentSwitchFlags recent_switch_flags; // used for switch assist
bool can_chat; bool can_chat;
struct PendingCharacterExport { struct PendingCharacterExport {
std::shared_ptr<const Account> dest_account; std::shared_ptr<const Account> dest_account;
ssize_t character_index = -1; ssize_t character_index = -1;
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
}; };
std::unique_ptr<PendingCharacterExport> pending_character_export; std::unique_ptr<PendingCharacterExport> pending_character_export;
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue; std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
// File loading state // File loading state
uint32_t dol_base_addr; uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::File> loading_dol_file; std::shared_ptr<DOLFileIndex::File> loading_dol_file;
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files; std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
Client( Client(
std::shared_ptr<Server> server, std::shared_ptr<Server> server,
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
Version version, Version version,
ServerBehavior server_behavior); ServerBehavior server_behavior);
~Client(); ~Client();
void update_channel_name(); void update_channel_name();
void reschedule_save_game_data_event(); void reschedule_save_game_data_event();
void reschedule_ping_and_timeout_events(); void reschedule_ping_and_timeout_events();
inline Version version() const { inline Version version() const {
return this->channel.version; return this->channel.version;
} }
inline uint8_t language() const { inline uint8_t language() const {
return this->channel.language; return this->channel.language;
} }
void convert_account_to_temporary_if_nte(); void convert_account_to_temporary_if_nte();
void sync_config(); void sync_config();
std::shared_ptr<ServerState> require_server_state() const; std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<Lobby> require_lobby() const; std::shared_ptr<Lobby> require_lobby() const;
std::shared_ptr<const TeamIndex::Team> team() const; std::shared_ptr<const TeamIndex::Team> team() const;
bool evaluate_quest_availability_expression( bool evaluate_quest_availability_expression(
std::shared_ptr<const IntegralExpression> expr, std::shared_ptr<const IntegralExpression> expr,
std::shared_ptr<const Lobby> game, std::shared_ptr<const Lobby> game,
uint8_t event, uint8_t event,
uint8_t difficulty, uint8_t difficulty,
size_t num_players, size_t num_players,
bool v1_present) const; bool v1_present) const;
bool can_see_quest( bool can_see_quest(
std::shared_ptr<const Quest> q, std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game, std::shared_ptr<const Lobby> game,
uint8_t event, uint8_t event,
uint8_t difficulty, uint8_t difficulty,
size_t num_players, size_t num_players,
bool v1_present) const; bool v1_present) const;
bool can_play_quest( bool can_play_quest(
std::shared_ptr<const Quest> q, std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game, std::shared_ptr<const Lobby> game,
uint8_t event, uint8_t event,
uint8_t difficulty, uint8_t difficulty,
size_t num_players, size_t num_players,
bool v1_present) const; bool v1_present) const;
bool can_use_chat_commands() const; bool can_use_chat_commands() const;
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx); static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
void save_game_data(); void save_game_data();
static void dispatch_send_ping(evutil_socket_t, short, void* ctx); static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
void send_ping(); void send_ping();
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx); static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout(); void idle_timeout();
void suspend_timeouts(); void suspend_timeouts();
const std::string& get_bb_username() const; const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username); 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_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); void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
inline void delete_overlay() { inline void delete_overlay() {
this->overlay_character_data.reset(); this->overlay_character_data.reset();
} }
inline bool has_overlay() const { inline bool has_overlay() const {
return this->overlay_character_data.get() != nullptr; return this->overlay_character_data.get() != nullptr;
} }
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders); 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<PSOBBBaseSystemFile> system_file(bool allow_load = true);
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = 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<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const; 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 PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const; std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
void create_character_file( void create_character_file(
uint32_t guild_card_number, uint32_t guild_card_number,
uint8_t language, uint8_t language,
const PlayerDispDataBBPreview& preview, const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table); std::shared_ptr<const LevelTable> level_table);
std::string system_filename() const; std::string system_filename() const;
static std::string character_filename(const std::string& bb_username, int8_t index); 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); static std::string backup_character_filename(uint32_t account_id, size_t index);
std::string character_filename(int8_t index = -1) const; std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const; std::string guild_card_filename() const;
std::string shared_bank_filename() const; std::string shared_bank_filename() const;
std::string legacy_player_filename() const; std::string legacy_player_filename() const;
std::string legacy_account_filename() const; std::string legacy_account_filename() const;
void save_all(); void save_all();
void save_system_file() const; void save_system_file() const;
static void save_character_file( static void save_character_file(
const std::string& filename, const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys, std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character); std::shared_ptr<const PSOBBCharacterFile> character);
// Note: This function is not const because it updates the player's play time. // Note: This function is not const because it updates the player's play time.
void save_character_file(); void save_character_file();
void save_guild_card_file() const; void save_guild_card_file() const;
void load_backup_character(uint32_t account_id, size_t index); void load_backup_character(uint32_t account_id, size_t index);
void save_and_unload_character(); void save_and_unload_character();
PlayerBank200& current_bank(); PlayerBank200& current_bank();
std::shared_ptr<PSOBBCharacterFile> current_bank_character(); std::shared_ptr<PSOBBCharacterFile> current_bank_character();
bool use_shared_bank(); // Returns true if the bank exists; false if it was created 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_character_bank(int8_t bb_character_index);
void use_default_bank(); void use_default_bank();
void print_inventory(FILE* stream) const; void print_inventory(FILE* stream) const;
void print_bank(FILE* stream) const; void print_bank(FILE* stream) const;
private: private:
// The overlay character data is used in battle and challenge modes, when // 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 // character data is temporarily replaced in-game. In other play modes and in
// lobbies, overlay_character_data is null. // lobbies, overlay_character_data is null.
std::shared_ptr<PSOBBBaseSystemFile> system_data; std::shared_ptr<PSOBBBaseSystemFile> system_data;
std::shared_ptr<PSOBBCharacterFile> overlay_character_data; std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data; std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data; std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank200> external_bank; std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character; std::shared_ptr<PSOBBCharacterFile> external_bank_character;
int8_t external_bank_character_index; int8_t external_bank_character_index;
uint64_t last_play_time_update; uint64_t last_play_time_update;
void save_and_clear_external_bank(); void save_and_clear_external_bank();
void load_all_files(); void load_all_files();
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data); 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 #pragma once
#include <stddef.h> #include <stddef.h>
#include <array> #include <array>
#include <deque> #include <deque>
#include <functional> #include <functional>
#include <phosg/Tools.hh> #include <phosg/Tools.hh>
#include <string> #include <string>
#include "Text.hh" #include "Text.hh"
enum class CompressPhase { enum class CompressPhase {
INDEX = 0, INDEX = 0,
CONSTRUCT_PATHS, CONSTRUCT_PATHS,
BACKTRACE_OPTIMAL_PATH, BACKTRACE_OPTIMAL_PATH,
GENERATE_RESULT, GENERATE_RESULT,
}; };
template <> template <>
const char* name_for_enum<CompressPhase>(CompressPhase v); 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; typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// PRS compression // PRS compression
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Use this class if you need to compress from multiple input buffers, or need // 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 // to compress multiple chunks and don't want to copy their contents
// unnecessarily. (For most common use cases, use prs_compress, below, instead.) // 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 // 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. // call .close() and use the returned string as the compressed result.
class PRSCompressor { class PRSCompressor {
public: public:
// compression_level specifies how aggressively to search for alternate paths: // compression_level specifies how aggressively to search for alternate paths:
// -1: Don't perform any compression at all, but produce output that can be // -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 // understood by prs_decompress. The output will be about 9/8 the size
// of the input. // of the input.
// 0: Greedily search for the longest backreference at every point. Don't // 0: Greedily search for the longest backreference at every point. Don't
// consider any alternate paths. Generally offers a good balance between // consider any alternate paths. Generally offers a good balance between
// speed and output size. // speed and output size.
// 1: Consider two paths at each point when a backreference is found: using // 1: Consider two paths at each point when a backreference is found: using
// the backreference or ignoring it. // the backreference or ignoring it.
// 2+: Consider further chains of paths at each point. Using values 2 or // 2+: Consider further chains of paths at each point. Using values 2 or
// greater for compression_level generally yields diminishing returns. // greater for compression_level generally yields diminishing returns.
explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr); explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr);
~PRSCompressor() = default; ~PRSCompressor() = default;
// Adds more input data to be compressed, which logically comes after all // Adds more input data to be compressed, which logically comes after all
// previous data provided via add() calls. Cannot be called after close() is // previous data provided via add() calls. Cannot be called after close() is
// called. // called.
void add(const void* data, size_t size); void add(const void* data, size_t size);
void add(const std::string& data); void add(const std::string& data);
// Ends compression and returns the complete compressed result. It's OK to // Ends compression and returns the complete compressed result. It's OK to
// std::move() from the returned string reference. // std::move() from the returned string reference.
std::string& close(); std::string& close();
// Returns the total number of bytes passed to add() calls so far. // Returns the total number of bytes passed to add() calls so far.
inline size_t input_size() const { inline size_t input_size() const {
return this->input_bytes; return this->input_bytes;
} }
private: private:
template <size_t Size> template <size_t Size>
struct WrappedLog { struct WrappedLog {
parray<uint8_t, Size> data; parray<uint8_t, Size> data;
WrappedLog() : data(0) {} WrappedLog() : data(0) {}
~WrappedLog() = default; ~WrappedLog() = default;
inline uint8_t at(size_t offset) const { inline uint8_t at(size_t offset) const {
return this->data[offset % this->data.size()]; return this->data[offset % this->data.size()];
} }
inline uint8_t& at(size_t offset) { inline uint8_t& at(size_t offset) {
return this->data[offset % this->data.size()]; return this->data[offset % this->data.size()];
} }
}; };
template <size_t Size> template <size_t Size>
struct IndexedLog : WrappedLog<Size> { struct IndexedLog : WrappedLog<Size> {
size_t offset; size_t offset;
size_t size; size_t size;
std::array<std::deque<size_t>, 0x100> index; std::array<std::deque<size_t>, 0x100> index;
IndexedLog() IndexedLog()
: WrappedLog<Size>(), : WrappedLog<Size>(),
offset(0), offset(0),
size(0) {} size(0) {}
~IndexedLog() = default; ~IndexedLog() = default;
inline size_t end_offset() const { inline size_t end_offset() const {
return this->offset + this->size; return this->offset + this->size;
} }
void push_back(uint8_t v) { void push_back(uint8_t v) {
if (this->size == Size) { if (this->size == Size) {
this->pop_front(); this->pop_front();
} }
size_t write_offset = this->offset + this->size; size_t write_offset = this->offset + this->size;
this->at(write_offset) = v; this->at(write_offset) = v;
this->index[v].push_back(write_offset); this->index[v].push_back(write_offset);
this->size++; this->size++;
} }
uint8_t pop_back() { uint8_t pop_back() {
if (!this->size) { if (!this->size) {
throw std::logic_error("pop_back called on empty IndexedLog"); throw std::logic_error("pop_back called on empty IndexedLog");
} }
this->size--; this->size--;
size_t offset = this->offset + this->size; size_t offset = this->offset + this->size;
uint8_t v = this->at(offset); uint8_t v = this->at(offset);
this->index[v].pop_back(); this->index[v].pop_back();
return v; return v;
} }
uint8_t pop_front() { uint8_t pop_front() {
uint8_t v = this->at(this->offset); uint8_t v = this->at(this->offset);
this->index[v].pop_front(); this->index[v].pop_front();
this->offset++; this->offset++;
this->size--; this->size--;
return v; return v;
} }
const std::deque<size_t>& find(uint8_t v) { const std::deque<size_t>& find(uint8_t v) {
return this->index[v]; return this->index[v];
} }
}; };
void add_byte(uint8_t v); void add_byte(uint8_t v);
void advance(); void advance();
void move_forward_data_to_reverse_log(size_t size); void move_forward_data_to_reverse_log(size_t size);
void advance_literal(); void advance_literal();
void advance_short_copy(ssize_t offset, size_t size); void advance_short_copy(ssize_t offset, size_t size);
void advance_long_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 advance_extended_copy(ssize_t offset, size_t size);
void write_control(bool z); void write_control(bool z);
void flush_control(); void flush_control();
ssize_t compression_level; ssize_t compression_level;
ProgressCallback progress_fn; ProgressCallback progress_fn;
bool closed; bool closed;
size_t control_byte_offset; size_t control_byte_offset;
uint16_t pending_control_bits; uint16_t pending_control_bits;
size_t input_bytes; size_t input_bytes;
WrappedLog<0x101> forward_log; WrappedLog<0x101> forward_log;
IndexedLog<0x2000> reverse_log; IndexedLog<0x2000> reverse_log;
StringWriter output; StringWriter output;
}; };
// These functions use PRSCompressor to compress a buffer of data. This is // These functions use PRSCompressor to compress a buffer of data. This is
// essentially a shortcut for constructing a PRSCompressor, calling .add() on // essentially a shortcut for constructing a PRSCompressor, calling .add() on
// it once, then calling .close(). // it once, then calling .close().
std::string prs_compress( std::string prs_compress(
const void* vdata, const void* vdata,
size_t size, size_t size,
ssize_t compression_level = 0, ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr); ProgressCallback progress_fn = nullptr);
std::string prs_compress( std::string prs_compress(
const std::string& data, const std::string& data,
ssize_t compression_level = 0, ssize_t compression_level = 0,
ProgressCallback progress_fn = nullptr); ProgressCallback progress_fn = nullptr);
// A faster form of prs_compress that doesn't have a tunable compression level. // A faster form of prs_compress that doesn't have a tunable compression level.
std::string prs_compress_indexed( std::string prs_compress_indexed(
const void* vdata, const void* vdata,
size_t size, size_t size,
ProgressCallback progress_fn = nullptr); ProgressCallback progress_fn = nullptr);
std::string prs_compress_indexed( std::string prs_compress_indexed(
const std::string& data, const std::string& data,
ProgressCallback progress_fn = nullptr); ProgressCallback progress_fn = nullptr);
// Compresses data using PRS to the smallest possible output size. This function // Compresses data using PRS to the smallest possible output size. This function
// is slow, but produces results significantly smaller than even Sega's original // is slow, but produces results significantly smaller than even Sega's original
// compressor. // compressor.
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr); 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); 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 // Compresses data using PRS to the LARGEST possible output size. There is no
// practical use for this function except for amusement. // practical use for this function except for amusement.
std::string prs_compress_pessimal(const void* vdata, size_t size); std::string prs_compress_pessimal(const void* vdata, size_t size);
// Decompresses PRS-compressed data. // Decompresses PRS-compressed data.
struct PRSDecompressResult { struct PRSDecompressResult {
std::string data; std::string data;
size_t input_bytes_used; 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 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); 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 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); 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 // Returns the decompressed size of PRS-compressed data, without actually
// decompressing it. // 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 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); 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. // 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 void* data, size_t size);
void prs_disassemble(FILE* stream, const std::string& data); void prs_disassemble(FILE* stream, const std::string& data);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// BC0 compression // BC0 compression
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Compresses data using the BC0 algorithm. Like with PRS, the optimal variant // Compresses data using the BC0 algorithm. Like with PRS, the optimal variant
// is slow, but produces the smallest possible output. // is slow, but produces the smallest possible output.
std::string bc0_compress_optimal( std::string bc0_compress_optimal(
const void* in_data_v, const void* in_data_v,
size_t in_size, size_t in_size,
ProgressCallback progress_fn = nullptr); ProgressCallback progress_fn = nullptr);
std::string bc0_compress(const std::string& data, 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); 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 // Encodes data in a BC0-compatible format without compression (similar to using
// compression_level=-1 with prs_compress). // compression_level=-1 with prs_compress).
std::string bc0_encode(const void* in_data_v, size_t in_size); std::string bc0_encode(const void* in_data_v, size_t in_size);
// Decompresses BC0-compressed data. // Decompresses BC0-compressed data.
std::string bc0_decompress(const std::string& data); std::string bc0_decompress(const std::string& data);
std::string bc0_decompress(const void* data, size_t size); std::string bc0_decompress(const void* data, size_t size);
// Prints the command stream from a BC0-compressed buffer. // Prints the command stream from a BC0-compressed buffer.
void bc0_disassemble(FILE* stream, const std::string& data); void bc0_disassemble(FILE* stream, const std::string& data);
void bc0_disassemble(FILE* stream, const void* data, size_t size); void bc0_disassemble(FILE* stream, const void* data, size_t size);
+121 -121
View File
@@ -1,121 +1,121 @@
#include "DNSServer.hh" #include "DNSServer.hh"
#include <netinet/in.h> #include <netinet/in.h>
#include <poll.h> #include <poll.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Loggers.hh" #include "Loggers.hh"
#include "NetworkAddresses.hh" #include "NetworkAddresses.hh"
using namespace std; using namespace std;
DNSServer::DNSServer( DNSServer::DNSServer(
shared_ptr<struct event_base> base, shared_ptr<struct event_base> base,
uint32_t local_connect_address, uint32_t local_connect_address,
uint32_t external_connect_address, uint32_t external_connect_address,
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
: base(base), : base(base),
local_connect_address(local_connect_address), local_connect_address(local_connect_address),
external_connect_address(external_connect_address), external_connect_address(external_connect_address),
banned_ipv4_ranges(banned_ipv4_ranges) {} banned_ipv4_ranges(banned_ipv4_ranges) {}
DNSServer::~DNSServer() { DNSServer::~DNSServer() {
for (const auto& it : this->fd_to_receive_event) { for (const auto& it : this->fd_to_receive_event) {
close(it.first); close(it.first);
} }
} }
void DNSServer::listen(const std::string& socket_path) { void DNSServer::listen(const std::string& socket_path) {
this->add_socket(::listen(socket_path, 0, 0)); this->add_socket(::listen(socket_path, 0, 0));
} }
void DNSServer::listen(const std::string& addr, int port) { void DNSServer::listen(const std::string& addr, int port) {
this->add_socket(::listen(addr, port, 0)); this->add_socket(::listen(addr, port, 0));
} }
void DNSServer::listen(int port) { void DNSServer::listen(int port) {
this->add_socket(::listen("", port, 0)); this->add_socket(::listen("", port, 0));
} }
void DNSServer::add_socket(int fd) { void DNSServer::add_socket(int fd) {
unique_ptr<struct event, void (*)(struct event*)> e( 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_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
event_free); event_free);
event_add(e.get(), nullptr); event_add(e.get(), nullptr);
this->fd_to_receive_event.emplace(fd, std::move(e)); this->fd_to_receive_event.emplace(fd, std::move(e));
} }
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd, void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
short events, void* ctx) { short events, void* ctx) {
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events); reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
} }
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) { string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
if (size < 0x0C) { if (size < 0x0C) {
throw invalid_argument("query too small"); throw invalid_argument("query too small");
} }
const char* data = reinterpret_cast<const char*>(vdata); const char* data = reinterpret_cast<const char*>(vdata);
size_t name_len = strlen(&data[12]) + 1; size_t name_len = strlen(&data[12]) + 1;
be_uint32_t be_resolved_address = resolved_address; be_uint32_t be_resolved_address = resolved_address;
string response; string response;
response.append(data, 2); response.append(data, 2);
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10); response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
response.append(&data[12], name_len); 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("\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); response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
return response; return response;
} }
string DNSServer::response_for_query( string DNSServer::response_for_query(
const string& query, uint32_t resolved_address) { const string& query, uint32_t resolved_address) {
return DNSServer::response_for_query(query.data(), query.size(), resolved_address); return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
} }
void DNSServer::on_receive_message(int fd, short) { void DNSServer::on_receive_message(int fd, short) {
for (;;) { for (;;) {
struct sockaddr_storage remote; struct sockaddr_storage remote;
socklen_t remote_size = sizeof(sockaddr_in); socklen_t remote_size = sizeof(sockaddr_in);
memset(&remote, 0, remote_size); memset(&remote, 0, remote_size);
string input(2048, 0); string input(2048, 0);
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(), ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
0, reinterpret_cast<sockaddr*>(&remote), &remote_size); 0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
if (bytes < 0) { if (bytes < 0) {
if (errno != EAGAIN) { if (errno != EAGAIN) {
dns_server_log.error("input error %d", errno); dns_server_log.error("input error %d", errno);
throw runtime_error("cannot read from udp socket"); throw runtime_error("cannot read from udp socket");
} }
break; break;
} else if (bytes == 0) { } else if (bytes == 0) {
break; break;
} else if (bytes < 0x0C) { } else if (bytes < 0x0C) {
dns_server_log.warning("input query too small"); dns_server_log.warning("input query too small");
print_data(stderr, input.data(), bytes); print_data(stderr, input.data(), bytes);
} else if (!this->banned_ipv4_ranges->check(remote)) { } else if (!this->banned_ipv4_ranges->check(remote)) {
input.resize(bytes); input.resize(bytes);
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote); const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr); uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
uint32_t connect_address = is_local_address(remote_address) uint32_t connect_address = is_local_address(remote_address)
? this->local_connect_address ? this->local_connect_address
: this->external_connect_address; : this->external_connect_address;
string response = this->response_for_query(input, connect_address); string response = this->response_for_query(input, connect_address);
sendto(fd, response.data(), response.size(), 0, sendto(fd, response.data(), response.size(), 0,
reinterpret_cast<const sockaddr*>(&remote), remote_size); reinterpret_cast<const sockaddr*>(&remote), remote_size);
} }
} }
} }
+44 -44
View File
@@ -1,44 +1,44 @@
#pragma once #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include "IPV4RangeSet.hh" #include "IPV4RangeSet.hh"
class DNSServer { class DNSServer {
public: public:
DNSServer( DNSServer(
std::shared_ptr<struct event_base> base, std::shared_ptr<struct event_base> base,
uint32_t local_connect_address, uint32_t local_connect_address,
uint32_t external_connect_address, uint32_t external_connect_address,
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges); std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
DNSServer(const DNSServer&) = delete; DNSServer(const DNSServer&) = delete;
DNSServer(DNSServer&&) = delete; DNSServer(DNSServer&&) = delete;
virtual ~DNSServer(); virtual ~DNSServer();
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) { inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
this->banned_ipv4_ranges = banned_ipv4_ranges; this->banned_ipv4_ranges = banned_ipv4_ranges;
} }
void listen(const std::string& socket_path); void listen(const std::string& socket_path);
void listen(const std::string& addr, int port); void listen(const std::string& addr, int port);
void listen(int port); void listen(int port);
void add_socket(int fd); 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 void* vdata, size_t size, uint32_t resolved_address);
static std::string response_for_query(const std::string& query, uint32_t resolved_address); static std::string response_for_query(const std::string& query, uint32_t resolved_address);
private: private:
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event; std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
uint32_t local_connect_address; uint32_t local_connect_address;
uint32_t external_connect_address; uint32_t external_connect_address;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges; std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx); static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
void on_receive_message(int fd, short event); 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 #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <phosg/Tools.hh> #include <phosg/Tools.hh>
#include "StaticGameData.hh" #include "StaticGameData.hh"
enum class EnemyType { enum class EnemyType {
UNKNOWN = -1, UNKNOWN = -1,
NONE = 0, NONE = 0,
NON_ENEMY_NPC, NON_ENEMY_NPC,
AL_RAPPY, AL_RAPPY,
ASTARK, ASTARK,
BA_BOOTA, BA_BOOTA,
BARBA_RAY, BARBA_RAY,
BARBAROUS_WOLF, BARBAROUS_WOLF,
BEE_L, BEE_L,
BEE_R, BEE_R,
BOOMA, BOOMA,
BOOTA, BOOTA,
BULCLAW, BULCLAW,
BULK, BULK,
CANADINE, CANADINE,
CANADINE_GROUP, CANADINE_GROUP,
CANANE, CANANE,
CHAOS_BRINGER, CHAOS_BRINGER,
CHAOS_SORCERER, CHAOS_SORCERER,
CLAW, CLAW,
DARK_BELRA, DARK_BELRA,
DARK_FALZ_1, DARK_FALZ_1,
DARK_FALZ_2, DARK_FALZ_2,
DARK_FALZ_3, DARK_FALZ_3,
DARK_GUNNER, DARK_GUNNER,
DARVANT, DARVANT,
DARVANT_ULTIMATE, DARVANT_ULTIMATE,
DE_ROL_LE, DE_ROL_LE,
DE_ROL_LE_BODY, DE_ROL_LE_BODY,
DE_ROL_LE_MINE, DE_ROL_LE_MINE,
DEATH_GUNNER, DEATH_GUNNER,
DEL_LILY, DEL_LILY,
DEL_RAPPY, DEL_RAPPY,
DEL_RAPPY_ALT, DEL_RAPPY_ALT,
DELBITER, DELBITER,
DELDEPTH, DELDEPTH,
DELSABER, DELSABER,
DIMENIAN, DIMENIAN,
DOLMDARL, DOLMDARL,
DOLMOLM, DOLMOLM,
DORPHON, DORPHON,
DORPHON_ECLAIR, DORPHON_ECLAIR,
DRAGON, DRAGON,
DUBCHIC, DUBCHIC,
DUBWITCH, // Has no entry in battle params DUBWITCH, // Has no entry in battle params
EGG_RAPPY, EGG_RAPPY,
EPSIGUARD, EPSIGUARD,
EPSILON, EPSILON,
EVIL_SHARK, EVIL_SHARK,
GAEL, GAEL,
GAL_GRYPHON, GAL_GRYPHON,
GARANZ, GARANZ,
GEE, GEE,
GI_GUE, GI_GUE,
GIBBLES, GIBBLES,
GIGOBOOMA, GIGOBOOMA,
GILLCHIC, GILLCHIC,
GIRTABLULU, GIRTABLULU,
GOBOOMA, GOBOOMA,
GOL_DRAGON, GOL_DRAGON,
GORAN, GORAN,
GORAN_DETONATOR, GORAN_DETONATOR,
GRASS_ASSASSIN, GRASS_ASSASSIN,
GUIL_SHARK, GUIL_SHARK,
HALLO_RAPPY, HALLO_RAPPY,
HIDOOM, HIDOOM,
HILDEBEAR, HILDEBEAR,
HILDEBLUE, HILDEBLUE,
ILL_GILL, ILL_GILL,
KONDRIEU, KONDRIEU,
LA_DIMENIAN, LA_DIMENIAN,
LOVE_RAPPY, LOVE_RAPPY,
MERICAROL, MERICAROL,
MERICUS, MERICUS,
MERIKLE, MERIKLE,
MERILLIA, MERILLIA,
MERILTAS, MERILTAS,
MERISSA_A, MERISSA_A,
MERISSA_AA, MERISSA_AA,
MIGIUM, MIGIUM,
MONEST, MONEST,
MORFOS, MORFOS,
MOTHMANT, MOTHMANT,
NANO_DRAGON, NANO_DRAGON,
NAR_LILY, NAR_LILY,
OLGA_FLOW_1, OLGA_FLOW_1,
OLGA_FLOW_2, OLGA_FLOW_2,
PAL_SHARK, PAL_SHARK,
PAN_ARMS, PAN_ARMS,
PAZUZU, PAZUZU,
PAZUZU_ALT, PAZUZU_ALT,
PIG_RAY, PIG_RAY,
POFUILLY_SLIME, POFUILLY_SLIME,
POUILLY_SLIME, POUILLY_SLIME,
POISON_LILY, POISON_LILY,
PYRO_GORAN, PYRO_GORAN,
RAG_RAPPY, RAG_RAPPY,
RECOBOX, RECOBOX,
RECON, RECON,
SAINT_MILLION, SAINT_MILLION,
SAINT_RAPPY, SAINT_RAPPY,
SAND_RAPPY, SAND_RAPPY,
SAND_RAPPY_ALT, SAND_RAPPY_ALT,
SATELLITE_LIZARD, SATELLITE_LIZARD,
SATELLITE_LIZARD_ALT, SATELLITE_LIZARD_ALT,
SAVAGE_WOLF, SAVAGE_WOLF,
SHAMBERTIN, SHAMBERTIN,
SINOW_BEAT, SINOW_BEAT,
SINOW_BERILL, SINOW_BERILL,
SINOW_GOLD, SINOW_GOLD,
SINOW_SPIGELL, SINOW_SPIGELL,
SINOW_ZELE, SINOW_ZELE,
SINOW_ZOA, SINOW_ZOA,
SO_DIMENIAN, SO_DIMENIAN,
UL_GIBBON, UL_GIBBON,
VOL_OPT_1, VOL_OPT_1,
VOL_OPT_2, VOL_OPT_2,
VOL_OPT_AMP, VOL_OPT_AMP,
VOL_OPT_CORE, VOL_OPT_CORE,
VOL_OPT_MONITOR, VOL_OPT_MONITOR,
VOL_OPT_PILLAR, VOL_OPT_PILLAR,
YOWIE, YOWIE,
YOWIE_ALT, YOWIE_ALT,
ZE_BOOTA, ZE_BOOTA,
ZOL_GIBBON, ZOL_GIBBON,
ZU, ZU,
ZU_ALT, ZU_ALT,
MAX_ENEMY_TYPE, MAX_ENEMY_TYPE,
}; };
template <> template <>
const char* name_for_enum<EnemyType>(EnemyType type); const char* name_for_enum<EnemyType>(EnemyType type);
template <> template <>
EnemyType enum_for_name<EnemyType>(const char* name); EnemyType enum_for_name<EnemyType>(const char* name);
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type); 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 battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
uint8_t rare_table_index_for_enemy_type(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); const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
bool enemy_type_is_rare(EnemyType type); 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 #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "Menu.hh" #include "Menu.hh"
bool function_compiler_available(); bool function_compiler_available();
void set_function_compiler_available(bool is_available); void set_function_compiler_available(bool is_available);
// TODO: Support x86 and SH4 function calls in the future. Currently we only // 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. // support PPC32 because I haven't written an appropriate x86 assembler yet.
struct CompiledFunctionCode { struct CompiledFunctionCode {
enum class Architecture { enum class Architecture {
UNKNOWN = 0, UNKNOWN = 0,
POWERPC, // GC POWERPC, // GC
X86, // PC, XB, BB X86, // PC, XB, BB
SH4, // Dreamcast SH4, // Dreamcast
}; };
Architecture arch; Architecture arch;
std::string code; std::string code;
std::vector<uint16_t> relocation_deltas; std::vector<uint16_t> relocation_deltas;
std::unordered_map<std::string, uint32_t> label_offsets; std::unordered_map<std::string, uint32_t> label_offsets;
uint32_t entrypoint_offset_offset; uint32_t entrypoint_offset_offset;
std::string source_path; // Path to source file from newserv root std::string source_path; // Path to source file from newserv root
std::string short_name; // Based on filename std::string short_name; // Based on filename
std::string long_name; // From .meta name directive std::string long_name; // From .meta name directive
std::string description; // From .meta description directive std::string description; // From .meta description directive
uint8_t index; // 0 = unused (not registered in index_to_function) uint8_t index; // 0 = unused (not registered in index_to_function)
uint32_t menu_item_id; uint32_t menu_item_id;
bool hide_from_patches_menu; bool hide_from_patches_menu;
uint32_t specific_version; uint32_t specific_version;
bool is_big_endian() const; bool is_big_endian() const;
template <typename FooterT> template <typename FooterT>
std::string generate_client_command_t( std::string generate_client_command_t(
const std::unordered_map<std::string, uint32_t>& label_writes, const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data = nullptr, const void* suffix_data = nullptr,
size_t suffix_size = 0, size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const; uint32_t override_relocations_offset = 0) const;
std::string generate_client_command( std::string generate_client_command(
const std::unordered_map<std::string, uint32_t>& label_writes = {}, const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr, const void* suffix_data = nullptr,
size_t suffix_size = 0, size_t suffix_size = 0,
uint32_t override_relocations_offset = 0) const; uint32_t override_relocations_offset = 0) const;
}; };
const char* name_for_architecture(CompiledFunctionCode::Architecture arch); const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
std::shared_ptr<CompiledFunctionCode> compile_function_code( std::shared_ptr<CompiledFunctionCode> compile_function_code(
CompiledFunctionCode::Architecture arch, CompiledFunctionCode::Architecture arch,
const std::string& directory, const std::string& directory,
const std::string& name, const std::string& name,
const std::string& text); const std::string& text);
struct FunctionCodeIndex { struct FunctionCodeIndex {
FunctionCodeIndex() = default; FunctionCodeIndex() = default;
explicit FunctionCodeIndex(const std::string& directory); explicit FunctionCodeIndex(const std::string& directory);
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function; 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<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; 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 // 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::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_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; 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; 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; std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
}; };
struct DOLFileIndex { struct DOLFileIndex {
struct File { struct File {
uint32_t menu_item_id; uint32_t menu_item_id;
std::string name; std::string name;
std::string data; std::string data;
bool is_compressed; bool is_compressed;
}; };
std::vector<std::shared_ptr<File>> item_id_to_file; std::vector<std::shared_ptr<File>> item_id_to_file;
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file; std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
std::shared_ptr<const Menu> menu; std::shared_ptr<const Menu> menu;
DOLFileIndex() = default; DOLFileIndex() = default;
explicit DOLFileIndex(const std::string& directory); explicit DOLFileIndex(const std::string& directory);
inline bool empty() const { inline bool empty() const {
return this->name_to_file.empty() && this->item_id_to_file.empty(); return this->name_to_file.empty() && this->item_id_to_file.empty();
} }
}; };
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum); 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 #pragma once
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/event.h> #include <event2/event.h>
#include <event2/http.h> #include <event2/http.h>
#include <stdlib.h> #include <stdlib.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include "ProxyServer.hh" #include "ProxyServer.hh"
#include "ServerState.hh" #include "ServerState.hh"
class HTTPServer { class HTTPServer {
public: public:
HTTPServer(std::shared_ptr<ServerState> state); HTTPServer(std::shared_ptr<ServerState> state);
HTTPServer(const HTTPServer&) = delete; HTTPServer(const HTTPServer&) = delete;
HTTPServer(HTTPServer&&) = delete; HTTPServer(HTTPServer&&) = delete;
HTTPServer& operator=(const HTTPServer&) = delete; HTTPServer& operator=(const HTTPServer&) = delete;
HTTPServer& operator=(HTTPServer&&) = delete; HTTPServer& operator=(HTTPServer&&) = delete;
virtual ~HTTPServer() = default; virtual ~HTTPServer() = default;
void listen(const std::string& socket_path); void listen(const std::string& socket_path);
void listen(const std::string& addr, int port); void listen(const std::string& addr, int port);
void listen(int port); void listen(int port);
void add_socket(int fd); void add_socket(int fd);
void schedule_stop(); void schedule_stop();
void wait_for_stop(); void wait_for_stop();
protected: protected:
class http_error : public std::runtime_error { class http_error : public std::runtime_error {
public: public:
http_error(int code, const std::string& what); http_error(int code, const std::string& what);
int code; int code;
}; };
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::shared_ptr<struct evhttp> http; std::shared_ptr<struct evhttp> http;
std::thread th; std::thread th;
void thread_fn(); void thread_fn();
static void dispatch_handle_request(struct evhttp_request* req, void* ctx); static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
void handle_request(struct evhttp_request* req); void handle_request(struct evhttp_request* req);
static const std::unordered_map<int, const char*> explanation_for_response_code; 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, struct evbuffer* b);
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...); 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_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 std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
static const std::string& get_url_param( static const std::string& get_url_param(
const std::unordered_multimap<std::string, std::string>& params, const std::unordered_multimap<std::string, std::string>& params,
const std::string& key, const std::string& key,
const std::string* _default = nullptr); const std::string* _default = nullptr);
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q); 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_client_config_json_st(const Client::Config& config);
static JSON generate_account_json_st(std::shared_ptr<const Account> a); 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_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_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); 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_game_server_clients_json() const;
JSON generate_proxy_server_clients_json() const; JSON generate_proxy_server_clients_json() const;
JSON generate_server_info_json() const; JSON generate_server_info_json() const;
JSON generate_lobbies_json() const; JSON generate_lobbies_json() const;
JSON generate_summary_json() const; JSON generate_summary_json() const;
JSON generate_all_json() const; JSON generate_all_json() const;
JSON generate_ep3_cards_json(bool trial) const; JSON generate_ep3_cards_json(bool trial) const;
JSON generate_common_tables_json() const; JSON generate_common_tables_json() const;
JSON generate_rare_tables_json() const; JSON generate_rare_tables_json() const;
JSON generate_rare_table_json(const std::string& table_name) const; JSON generate_rare_table_json(const std::string& table_name) const;
}; };
+73 -73
View File
@@ -1,73 +1,73 @@
#include "IPV4RangeSet.hh" #include "IPV4RangeSet.hh"
#include <arpa/inet.h> #include <arpa/inet.h>
using namespace std; using namespace std;
IPV4RangeSet::IPV4RangeSet(const JSON& json) { IPV4RangeSet::IPV4RangeSet(const JSON& json) {
for (const auto& it : json.as_list()) { for (const auto& it : json.as_list()) {
// String should be of the form a.b.c.d or a.b.c.d/e // String should be of the form a.b.c.d or a.b.c.d/e
auto tokens = split(it->as_string(), '/'); auto tokens = split(it->as_string(), '/');
size_t mask_bits; size_t mask_bits;
if (tokens.size() == 1) { if (tokens.size() == 1) {
mask_bits = 32; mask_bits = 32;
} else if (tokens.size() == 2) { } else if (tokens.size() == 2) {
mask_bits = stoul(tokens[1], nullptr, 10); mask_bits = stoul(tokens[1], nullptr, 10);
if (mask_bits > 32) { if (mask_bits > 32) {
throw runtime_error("invalid IPv4 address range"); throw runtime_error("invalid IPv4 address range");
} }
} else { } else {
throw runtime_error("invalid IPv4 address range"); throw runtime_error("invalid IPv4 address range");
} }
auto addr_tokens = split(tokens[0], '.'); auto addr_tokens = split(tokens[0], '.');
if (addr_tokens.size() != 4) { if (addr_tokens.size() != 4) {
throw runtime_error("invalid IPv4 address"); throw runtime_error("invalid IPv4 address");
} }
uint32_t addr = 0; uint32_t addr = 0;
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
size_t end_pos = 0; size_t end_pos = 0;
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10); size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) { if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
throw runtime_error("invalid IPv4 address"); throw runtime_error("invalid IPv4 address");
} }
addr = (addr << 8) | new_byte; addr = (addr << 8) | new_byte;
} }
addr &= (0xFFFFFFFF << (32 - mask_bits)); addr &= (0xFFFFFFFF << (32 - mask_bits));
this->ranges.emplace(addr, mask_bits); this->ranges.emplace(addr, mask_bits);
} }
} }
JSON IPV4RangeSet::json() const { JSON IPV4RangeSet::json() const {
auto ret = JSON::list(); auto ret = JSON::list();
for (const auto& it : this->ranges) { for (const auto& it : this->ranges) {
uint32_t addr = it.first; uint32_t addr = it.first;
uint8_t mask_bits = it.second; uint8_t mask_bits = it.second;
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu", ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
static_cast<uint8_t>((addr >> 24) & 0xFF), static_cast<uint8_t>((addr >> 24) & 0xFF),
static_cast<uint8_t>((addr >> 16) & 0xFF), static_cast<uint8_t>((addr >> 16) & 0xFF),
static_cast<uint8_t>((addr >> 8) & 0xFF), static_cast<uint8_t>((addr >> 8) & 0xFF),
static_cast<uint8_t>(addr & 0xFF), static_cast<uint8_t>(addr & 0xFF),
mask_bits)); mask_bits));
} }
return ret; return ret;
} }
bool IPV4RangeSet::check(uint32_t addr) const { bool IPV4RangeSet::check(uint32_t addr) const {
auto it = this->ranges.upper_bound(addr); auto it = this->ranges.upper_bound(addr);
if (it == this->ranges.begin()) { if (it == this->ranges.begin()) {
return false; // addr is before any range return false; // addr is before any range
} }
const auto& range = *(--it); const auto& range = *(--it);
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0); return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
} }
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const { bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
if (ss.ss_family != AF_INET) { if (ss.ss_family != AF_INET) {
return false; return false;
} }
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss); const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
return this->check(ntohl(sin->sin_addr.s_addr)); return this->check(ntohl(sin->sin_addr.s_addr));
} }
+18 -18
View File
@@ -1,18 +1,18 @@
#pragma once #pragma once
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <set> #include <set>
class IPV4RangeSet { class IPV4RangeSet {
public: public:
IPV4RangeSet() = default; IPV4RangeSet() = default;
explicit IPV4RangeSet(const JSON& json); explicit IPV4RangeSet(const JSON& json);
JSON json() const; JSON json() const;
bool check(uint32_t addr) const; bool check(uint32_t addr) const;
bool check(const struct sockaddr_storage& ss) const; bool check(const struct sockaddr_storage& ss) const;
protected: protected:
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits} std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
}; };
+455 -455
View File
@@ -1,455 +1,455 @@
#include "IntegralExpression.hh" #include "IntegralExpression.hh"
#include <algorithm> #include <algorithm>
#include <mutex> #include <mutex>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Hash.hh> #include <phosg/Hash.hh>
#include <phosg/Random.hh> #include <phosg/Random.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <phosg/Tools.hh> #include <phosg/Tools.hh>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "Compression.hh" #include "Compression.hh"
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
#include "SaveFileFormats.hh" #include "SaveFileFormats.hh"
#include "Text.hh" #include "Text.hh"
using namespace std; using namespace std;
IntegralExpression::IntegralExpression(const string& text) IntegralExpression::IntegralExpression(const string& text)
: root(this->parse_expr(text)) {} : root(this->parse_expr(text)) {}
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode( IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right) Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
: type(type), : type(type),
left(std::move(left)), left(std::move(left)),
right(std::move(right)) {} right(std::move(right)) {}
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const { bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
try { try {
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other); 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; return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const { int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) { switch (this->type) {
case Type::LOGICAL_OR: case Type::LOGICAL_OR:
return this->left->evaluate(env) || this->right->evaluate(env); return this->left->evaluate(env) || this->right->evaluate(env);
case Type::LOGICAL_AND: case Type::LOGICAL_AND:
return this->left->evaluate(env) && this->right->evaluate(env); return this->left->evaluate(env) && this->right->evaluate(env);
case Type::BITWISE_OR: case Type::BITWISE_OR:
return this->left->evaluate(env) | this->right->evaluate(env); return this->left->evaluate(env) | this->right->evaluate(env);
case Type::BITWISE_AND: case Type::BITWISE_AND:
return this->left->evaluate(env) & this->right->evaluate(env); return this->left->evaluate(env) & this->right->evaluate(env);
case Type::BITWISE_XOR: case Type::BITWISE_XOR:
return this->left->evaluate(env) ^ this->right->evaluate(env); return this->left->evaluate(env) ^ this->right->evaluate(env);
case Type::LEFT_SHIFT: case Type::LEFT_SHIFT:
return this->left->evaluate(env) << this->right->evaluate(env); return this->left->evaluate(env) << this->right->evaluate(env);
case Type::RIGHT_SHIFT: case Type::RIGHT_SHIFT:
return this->left->evaluate(env) >> this->right->evaluate(env); return this->left->evaluate(env) >> this->right->evaluate(env);
case Type::LESS_THAN: case Type::LESS_THAN:
return this->left->evaluate(env) < this->right->evaluate(env); return this->left->evaluate(env) < this->right->evaluate(env);
case Type::GREATER_THAN: case Type::GREATER_THAN:
return this->left->evaluate(env) > this->right->evaluate(env); return this->left->evaluate(env) > this->right->evaluate(env);
case Type::LESS_OR_EQUAL: case Type::LESS_OR_EQUAL:
return this->left->evaluate(env) <= this->right->evaluate(env); return this->left->evaluate(env) <= this->right->evaluate(env);
case Type::GREATER_OR_EQUAL: case Type::GREATER_OR_EQUAL:
return this->left->evaluate(env) >= this->right->evaluate(env); return this->left->evaluate(env) >= this->right->evaluate(env);
case Type::EQUAL: case Type::EQUAL:
return this->left->evaluate(env) == this->right->evaluate(env); return this->left->evaluate(env) == this->right->evaluate(env);
case Type::NOT_EQUAL: case Type::NOT_EQUAL:
return this->left->evaluate(env) != this->right->evaluate(env); return this->left->evaluate(env) != this->right->evaluate(env);
case Type::ADD: case Type::ADD:
return this->left->evaluate(env) + this->right->evaluate(env); return this->left->evaluate(env) + this->right->evaluate(env);
case Type::SUBTRACT: case Type::SUBTRACT:
return this->left->evaluate(env) - this->right->evaluate(env); return this->left->evaluate(env) - this->right->evaluate(env);
case Type::MULTIPLY: case Type::MULTIPLY:
return this->left->evaluate(env) * this->right->evaluate(env); return this->left->evaluate(env) * this->right->evaluate(env);
case Type::DIVIDE: case Type::DIVIDE:
return this->left->evaluate(env) / this->right->evaluate(env); return this->left->evaluate(env) / this->right->evaluate(env);
case Type::MODULUS: case Type::MODULUS:
return this->left->evaluate(env) % this->right->evaluate(env); return this->left->evaluate(env) % this->right->evaluate(env);
default: default:
throw logic_error("invalid binary operator type"); throw logic_error("invalid binary operator type");
} }
} }
string IntegralExpression::BinaryOperatorNode::str() const { string IntegralExpression::BinaryOperatorNode::str() const {
switch (this->type) { switch (this->type) {
case Type::LOGICAL_OR: case Type::LOGICAL_OR:
return "(" + this->left->str() + ") || (" + this->right->str() + ")"; return "(" + this->left->str() + ") || (" + this->right->str() + ")";
case Type::LOGICAL_AND: case Type::LOGICAL_AND:
return "(" + this->left->str() + ") && (" + this->right->str() + ")"; return "(" + this->left->str() + ") && (" + this->right->str() + ")";
case Type::BITWISE_OR: case Type::BITWISE_OR:
return "(" + this->left->str() + ") | (" + this->right->str() + ")"; return "(" + this->left->str() + ") | (" + this->right->str() + ")";
case Type::BITWISE_AND: case Type::BITWISE_AND:
return "(" + this->left->str() + ") & (" + this->right->str() + ")"; return "(" + this->left->str() + ") & (" + this->right->str() + ")";
case Type::BITWISE_XOR: case Type::BITWISE_XOR:
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")"; return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
case Type::LEFT_SHIFT: case Type::LEFT_SHIFT:
return "(" + this->left->str() + ") << (" + this->right->str() + ")"; return "(" + this->left->str() + ") << (" + this->right->str() + ")";
case Type::RIGHT_SHIFT: case Type::RIGHT_SHIFT:
return "(" + this->left->str() + ") >> (" + this->right->str() + ")"; return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
case Type::LESS_THAN: case Type::LESS_THAN:
return "(" + this->left->str() + ") < (" + this->right->str() + ")"; return "(" + this->left->str() + ") < (" + this->right->str() + ")";
case Type::GREATER_THAN: case Type::GREATER_THAN:
return "(" + this->left->str() + ") > (" + this->right->str() + ")"; return "(" + this->left->str() + ") > (" + this->right->str() + ")";
case Type::LESS_OR_EQUAL: case Type::LESS_OR_EQUAL:
return "(" + this->left->str() + ") <= (" + this->right->str() + ")"; return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
case Type::GREATER_OR_EQUAL: case Type::GREATER_OR_EQUAL:
return "(" + this->left->str() + ") >= (" + this->right->str() + ")"; return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
case Type::EQUAL: case Type::EQUAL:
return "(" + this->left->str() + ") == (" + this->right->str() + ")"; return "(" + this->left->str() + ") == (" + this->right->str() + ")";
case Type::NOT_EQUAL: case Type::NOT_EQUAL:
return "(" + this->left->str() + ") != (" + this->right->str() + ")"; return "(" + this->left->str() + ") != (" + this->right->str() + ")";
case Type::ADD: case Type::ADD:
return "(" + this->left->str() + ") + (" + this->right->str() + ")"; return "(" + this->left->str() + ") + (" + this->right->str() + ")";
case Type::SUBTRACT: case Type::SUBTRACT:
return "(" + this->left->str() + ") - (" + this->right->str() + ")"; return "(" + this->left->str() + ") - (" + this->right->str() + ")";
case Type::MULTIPLY: case Type::MULTIPLY:
return "(" + this->left->str() + ") * (" + this->right->str() + ")"; return "(" + this->left->str() + ") * (" + this->right->str() + ")";
case Type::DIVIDE: case Type::DIVIDE:
return "(" + this->left->str() + ") / (" + this->right->str() + ")"; return "(" + this->left->str() + ") / (" + this->right->str() + ")";
case Type::MODULUS: case Type::MODULUS:
return "(" + this->left->str() + ") % (" + this->right->str() + ")"; return "(" + this->left->str() + ") % (" + this->right->str() + ")";
default: default:
throw logic_error("invalid binary operator type"); throw logic_error("invalid binary operator type");
} }
} }
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub) IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
: type(type), : type(type),
sub(std::move(sub)) {} sub(std::move(sub)) {}
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const { bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
try { try {
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other); const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
return other_un.type == this->type && *other_un.sub == *this->sub; return other_un.type == this->type && *other_un.sub == *this->sub;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const { int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) { switch (this->type) {
case Type::LOGICAL_NOT: case Type::LOGICAL_NOT:
return !this->sub->evaluate(env); return !this->sub->evaluate(env);
case Type::BITWISE_NOT: case Type::BITWISE_NOT:
return ~this->sub->evaluate(env); return ~this->sub->evaluate(env);
case Type::NEGATIVE: case Type::NEGATIVE:
return -this->sub->evaluate(env); return -this->sub->evaluate(env);
default: default:
throw logic_error("invalid unary operator type"); throw logic_error("invalid unary operator type");
} }
} }
string IntegralExpression::UnaryOperatorNode::str() const { string IntegralExpression::UnaryOperatorNode::str() const {
switch (this->type) { switch (this->type) {
case Type::LOGICAL_NOT: case Type::LOGICAL_NOT:
return "!(" + this->sub->str() + ")"; return "!(" + this->sub->str() + ")";
case Type::BITWISE_NOT: case Type::BITWISE_NOT:
return "~(" + this->sub->str() + ")"; return "~(" + this->sub->str() + ")";
case Type::NEGATIVE: case Type::NEGATIVE:
return "-(" + this->sub->str() + ")"; return "-(" + this->sub->str() + ")";
default: default:
throw logic_error("invalid unary operator type"); throw logic_error("invalid unary operator type");
} }
} }
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index) IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
: flag_index(flag_index) {} : flag_index(flag_index) {}
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const { bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
try { try {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other); const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index; return other_flag.flag_index == this->flag_index;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
if (!env.flags) { if (!env.flags) {
throw runtime_error("quest flags not available"); throw runtime_error("quest flags not available");
} }
return env.flags->get(this->flag_index) ? 1 : 0; return env.flags->get(this->flag_index) ? 1 : 0;
} }
string IntegralExpression::FlagLookupNode::str() const { string IntegralExpression::FlagLookupNode::str() const {
return string_printf("F_%04hX", this->flag_index); return string_printf("F_%04hX", this->flag_index);
} }
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode( IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
Episode episode, uint8_t stage_index) Episode episode, uint8_t stage_index)
: episode(episode), : episode(episode),
stage_index(stage_index) {} stage_index(stage_index) {}
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const { bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
try { try {
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other); const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index; return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
if (!env.challenge_records) { if (!env.challenge_records) {
throw runtime_error("challenge records not available"); throw runtime_error("challenge records not available");
} }
if (this->episode == Episode::EP1) { if (this->episode == Episode::EP1) {
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value(); return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
} else if (this->episode == Episode::EP2) { } else if (this->episode == Episode::EP2) {
return env.challenge_records->times_ep2_online.at(this->stage_index).has_value(); return env.challenge_records->times_ep2_online.at(this->stage_index).has_value();
} }
return false; return false;
} }
string IntegralExpression::ChallengeCompletionLookupNode::str() const { string IntegralExpression::ChallengeCompletionLookupNode::str() const {
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1)); 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) IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
: reward_name(reward_name) {} : reward_name(reward_name) {}
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const { bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
try { try {
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other); const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
return other_team_reward.reward_name == this->reward_name; return other_team_reward.reward_name == this->reward_name;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0; return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
} }
string IntegralExpression::TeamRewardLookupNode::str() const { string IntegralExpression::TeamRewardLookupNode::str() const {
return "T_" + this->reward_name; return "T_" + this->reward_name;
} }
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {} IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const { bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr; return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
} }
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
return env.num_players; return env.num_players;
} }
string IntegralExpression::NumPlayersLookupNode::str() const { string IntegralExpression::NumPlayersLookupNode::str() const {
return "V_NumPlayers"; return "V_NumPlayers";
} }
IntegralExpression::EventLookupNode::EventLookupNode() {} IntegralExpression::EventLookupNode::EventLookupNode() {}
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const { bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
return dynamic_cast<const EventLookupNode*>(&other) != nullptr; return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
} }
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
return env.event; return env.event;
} }
string IntegralExpression::EventLookupNode::str() const { string IntegralExpression::EventLookupNode::str() const {
return "V_Event"; return "V_Event";
} }
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {} IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const { bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr; return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
} }
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const { int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
return env.v1_present ? 1 : 0; return env.v1_present ? 1 : 0;
} }
string IntegralExpression::V1PresenceLookupNode::str() const { string IntegralExpression::V1PresenceLookupNode::str() const {
return "V_V1Present"; return "V_V1Present";
} }
IntegralExpression::ConstantNode::ConstantNode(int64_t value) IntegralExpression::ConstantNode::ConstantNode(int64_t value)
: value(value) {} : value(value) {}
bool IntegralExpression::ConstantNode::operator==(const Node& other) const { bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
try { try {
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other); const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
return other_const.value == this->value; return other_const.value == this->value;
} catch (const bad_cast&) { } catch (const bad_cast&) {
return false; return false;
} }
} }
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const { int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
return this->value; return this->value;
} }
string IntegralExpression::ConstantNode::str() const { string IntegralExpression::ConstantNode::str() const {
return string_printf("%" PRId64, this->value); return string_printf("%" PRId64, this->value);
} }
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) { unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
// Strip off spaces and fully-enclosing parentheses // Strip off spaces and fully-enclosing parentheses
for (;;) { for (;;) {
size_t starting_size = text.size(); size_t starting_size = text.size();
while (text.at(0) == ' ') { while (text.at(0) == ' ') {
text = text.substr(1); text = text.substr(1);
} }
while (text.at(text.size() - 1) == ' ') { while (text.at(text.size() - 1) == ' ') {
text = text.substr(0, text.size() - 1); text = text.substr(0, text.size() - 1);
} }
if (text.at(0) == '(' && text.at(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 // 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 // text could be like "(a) && (b)". Instead, we ignore the first and last
// characters, and don't strip anything if the internal parentheses are // characters, and don't strip anything if the internal parentheses are
// unbalanced. // unbalanced.
size_t paren_level = 1; size_t paren_level = 1;
for (size_t z = 1; z < text.size() - 1; z++) { for (size_t z = 1; z < text.size() - 1; z++) {
if (text[z] == '(') { if (text[z] == '(') {
paren_level++; paren_level++;
} else if (text[z] == ')') { } else if (text[z] == ')') {
paren_level--; paren_level--;
if (paren_level == 0) { if (paren_level == 0) {
break; break;
} }
} }
} }
if (paren_level > 0) { if (paren_level > 0) {
text = text.substr(1, text.size() - 2); text = text.substr(1, text.size() - 2);
} }
} }
if (text.size() == starting_size) { if (text.size() == starting_size) {
break; break;
} }
} }
if (text.empty()) { if (text.empty()) {
throw runtime_error("invalid expression"); throw runtime_error("invalid expression");
} }
// Check for binary operators at the root level // Check for binary operators at the root level
using BinType = BinaryOperatorNode::Type; using BinType = BinaryOperatorNode::Type;
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = { 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::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}}, {{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}}, {{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::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::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
{{make_pair("&", BinType::BITWISE_AND)}}, {{make_pair("&", BinType::BITWISE_AND)}},
{{make_pair("^", BinType::BITWISE_XOR)}}, {{make_pair("^", BinType::BITWISE_XOR)}},
{{make_pair("|", BinType::BITWISE_OR)}}, {{make_pair("|", BinType::BITWISE_OR)}},
{{make_pair("&&", BinType::LOGICAL_AND)}}, {{make_pair("&&", BinType::LOGICAL_AND)}},
{{make_pair("||", BinType::LOGICAL_OR)}}, {{make_pair("||", BinType::LOGICAL_OR)}},
}; };
for (const auto& operators : binary_operator_levels) { for (const auto& operators : binary_operator_levels) {
size_t paren_level = 0; size_t paren_level = 0;
for (size_t z = 0; z < text.size() - 1; z++) { for (size_t z = 0; z < text.size() - 1; z++) {
if (text[z] == '(') { if (text[z] == '(') {
paren_level++; paren_level++;
continue; continue;
} else if (text[z] == ')') { } else if (text[z] == ')') {
paren_level--; paren_level--;
continue; continue;
} }
if (!paren_level) { if (!paren_level) {
for (const auto& oper : operators) { for (const auto& oper : operators) {
// Awful hack (because I'm too lazy to add a tokenization step): if // Awful hack (because I'm too lazy to add a tokenization step): if
// the operator is followed or preceded by another copy of itself, // the operator is followed or preceded by another copy of itself,
// don't match it (this prevents us from matching & when the token is // don't match it (this prevents us from matching & when the token is
// actually &&) // actually &&)
if ((text.size() > z + oper.first.size()) && if ((text.size() > z + oper.first.size()) &&
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) && ((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) == 0) &&
(text.compare(z + oper.first.size(), 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 left = IntegralExpression::parse_expr(text.substr(0, z));
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size())); auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right)); return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
} }
} }
} }
} }
} }
// Check for unary operators // Check for unary operators
if (text[0] == '!') { if (text[0] == '!') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT, return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
IntegralExpression::parse_expr(text.substr(1))); IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '~') { } else if (text[0] == '~') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT, return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
IntegralExpression::parse_expr(text.substr(1))); IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '-') { } else if (text[0] == '-') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE, return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
IntegralExpression::parse_expr(text.substr(1))); IntegralExpression::parse_expr(text.substr(1)));
} }
// Check for env lookups // Check for env lookups
if (text.starts_with("F_")) { if (text.starts_with("F_")) {
char* endptr = nullptr; char* endptr = nullptr;
uint64_t flag = strtoul(text.data() + 2, &endptr, 16); uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
if (endptr != text.data() + text.size()) { if (endptr != text.data() + text.size()) {
throw runtime_error("invalid flag lookup token"); throw runtime_error("invalid flag lookup token");
} }
if (flag >= 0x400) { if (flag >= 0x400) {
throw runtime_error("invalid flag index"); throw runtime_error("invalid flag index");
} }
return make_unique<FlagLookupNode>(flag); return make_unique<FlagLookupNode>(flag);
} }
if (text.starts_with("CC_")) { if (text.starts_with("CC_")) {
Episode episode; Episode episode;
if (text.starts_with("CC_Ep1_")) { if (text.starts_with("CC_Ep1_")) {
episode = Episode::EP1; episode = Episode::EP1;
} else if (text.starts_with("CC_Ep2_")) { } else if (text.starts_with("CC_Ep2_")) {
episode = Episode::EP2; episode = Episode::EP2;
} else { } else {
throw runtime_error("invalid challenge episode"); throw runtime_error("invalid challenge episode");
} }
char* endptr = nullptr; char* endptr = nullptr;
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1; uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
if (endptr != text.data() + text.size()) { if (endptr != text.data() + text.size()) {
throw runtime_error("invalid challenge completion lookup token"); throw runtime_error("invalid challenge completion lookup token");
} }
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) { if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
throw runtime_error("invalid challenge stage index"); throw runtime_error("invalid challenge stage index");
} }
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index); return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
} }
if (text.starts_with("T_")) { if (text.starts_with("T_")) {
return make_unique<TeamRewardLookupNode>(string(text.substr(2))); return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
} }
if (text == "V_NumPlayers") { if (text == "V_NumPlayers") {
return make_unique<NumPlayersLookupNode>(); return make_unique<NumPlayersLookupNode>();
} }
if (text == "V_Event") { if (text == "V_Event") {
return make_unique<EventLookupNode>(); return make_unique<EventLookupNode>();
} }
if (text == "V_V1Present") { if (text == "V_V1Present") {
return make_unique<V1PresenceLookupNode>(); return make_unique<V1PresenceLookupNode>();
} }
// Check for constants // Check for constants
if (text == "true") { if (text == "true") {
return make_unique<ConstantNode>(1); return make_unique<ConstantNode>(1);
} }
if (text == "false") { if (text == "false") {
return make_unique<ConstantNode>(0); return make_unique<ConstantNode>(0);
} }
try { try {
size_t endpos; size_t endpos;
int64_t v = stoll(string(text), &endpos, 0); int64_t v = stoll(string(text), &endpos, 0);
if (endpos == text.size()) { if (endpos == text.size()) {
return make_unique<ConstantNode>(v); return make_unique<ConstantNode>(v);
} }
} catch (const exception&) { } catch (const exception&) {
} }
throw runtime_error("unparseable expression"); throw runtime_error("unparseable expression");
} }
+188 -188
View File
@@ -1,188 +1,188 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "PlayerSubordinates.hh" #include "PlayerSubordinates.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
class IntegralExpression { class IntegralExpression {
public: public:
struct Env { struct Env {
const QuestFlagsForDifficulty* flags; const QuestFlagsForDifficulty* flags;
const PlayerRecordsChallengeBB* challenge_records; const PlayerRecordsChallengeBB* challenge_records;
std::shared_ptr<const TeamIndex::Team> team; std::shared_ptr<const TeamIndex::Team> team;
size_t num_players; size_t num_players;
uint8_t event; uint8_t event;
bool v1_present; bool v1_present;
}; };
IntegralExpression(const std::string& text); IntegralExpression(const std::string& text);
~IntegralExpression() = default; ~IntegralExpression() = default;
inline bool operator==(const IntegralExpression& other) const { inline bool operator==(const IntegralExpression& other) const {
return this->root->operator==(*other.root); return this->root->operator==(*other.root);
} }
inline bool operator!=(const IntegralExpression& other) const { inline bool operator!=(const IntegralExpression& other) const {
return !this->operator==(other); return !this->operator==(other);
} }
inline int64_t evaluate(const Env& env) const { inline int64_t evaluate(const Env& env) const {
return this->root->evaluate(env); return this->root->evaluate(env);
} }
inline std::string str() const { inline std::string str() const {
return this->root->str(); return this->root->str();
} }
protected: protected:
class Node { class Node {
public: public:
virtual ~Node() = default; virtual ~Node() = default;
virtual bool operator==(const Node& other) const = 0; virtual bool operator==(const Node& other) const = 0;
inline bool operator!=(const Node& other) const { inline bool operator!=(const Node& other) const {
return !this->operator==(other); return !this->operator==(other);
} }
virtual int64_t evaluate(const Env& env) const = 0; virtual int64_t evaluate(const Env& env) const = 0;
virtual std::string str() const = 0; virtual std::string str() const = 0;
protected: protected:
Node() = default; Node() = default;
}; };
class BinaryOperatorNode : public Node { class BinaryOperatorNode : public Node {
public: public:
enum class Type { enum class Type {
LOGICAL_OR = 0, LOGICAL_OR = 0,
LOGICAL_AND, LOGICAL_AND,
BITWISE_OR, BITWISE_OR,
BITWISE_AND, BITWISE_AND,
BITWISE_XOR, BITWISE_XOR,
LEFT_SHIFT, LEFT_SHIFT,
RIGHT_SHIFT, RIGHT_SHIFT,
LESS_THAN, LESS_THAN,
GREATER_THAN, GREATER_THAN,
LESS_OR_EQUAL, LESS_OR_EQUAL,
GREATER_OR_EQUAL, GREATER_OR_EQUAL,
EQUAL, EQUAL,
NOT_EQUAL, NOT_EQUAL,
ADD, ADD,
SUBTRACT, SUBTRACT,
MULTIPLY, MULTIPLY,
DIVIDE, DIVIDE,
MODULUS, MODULUS,
}; };
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right); BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
virtual ~BinaryOperatorNode() = default; virtual ~BinaryOperatorNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
Type type; Type type;
std::unique_ptr<const Node> left; std::unique_ptr<const Node> left;
std::unique_ptr<const Node> right; std::unique_ptr<const Node> right;
}; };
class UnaryOperatorNode : public Node { class UnaryOperatorNode : public Node {
public: public:
enum class Type { enum class Type {
LOGICAL_NOT = 0, LOGICAL_NOT = 0,
BITWISE_NOT, BITWISE_NOT,
NEGATIVE, NEGATIVE,
}; };
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub); UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
virtual ~UnaryOperatorNode() = default; virtual ~UnaryOperatorNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
Type type; Type type;
std::unique_ptr<const Node> sub; std::unique_ptr<const Node> sub;
}; };
class FlagLookupNode : public Node { class FlagLookupNode : public Node {
public: public:
FlagLookupNode(uint16_t flag_index); FlagLookupNode(uint16_t flag_index);
virtual ~FlagLookupNode() = default; virtual ~FlagLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
uint16_t flag_index; uint16_t flag_index;
}; };
class ChallengeCompletionLookupNode : public Node { class ChallengeCompletionLookupNode : public Node {
public: public:
ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index); ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index);
virtual ~ChallengeCompletionLookupNode() = default; virtual ~ChallengeCompletionLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
Episode episode; Episode episode;
uint8_t stage_index; uint8_t stage_index;
}; };
class TeamRewardLookupNode : public Node { class TeamRewardLookupNode : public Node {
public: public:
TeamRewardLookupNode(const std::string& reward_name); TeamRewardLookupNode(const std::string& reward_name);
virtual ~TeamRewardLookupNode() = default; virtual ~TeamRewardLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
std::string reward_name; std::string reward_name;
}; };
class NumPlayersLookupNode : public Node { class NumPlayersLookupNode : public Node {
public: public:
NumPlayersLookupNode(); NumPlayersLookupNode();
virtual ~NumPlayersLookupNode() = default; virtual ~NumPlayersLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
}; };
class EventLookupNode : public Node { class EventLookupNode : public Node {
public: public:
EventLookupNode(); EventLookupNode();
virtual ~EventLookupNode() = default; virtual ~EventLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
}; };
class V1PresenceLookupNode : public Node { class V1PresenceLookupNode : public Node {
public: public:
V1PresenceLookupNode(); V1PresenceLookupNode();
virtual ~V1PresenceLookupNode() = default; virtual ~V1PresenceLookupNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
}; };
class ConstantNode : public Node { class ConstantNode : public Node {
public: public:
ConstantNode(int64_t value); ConstantNode(int64_t value);
virtual ~ConstantNode() = default; virtual ~ConstantNode() = default;
virtual bool operator==(const Node& other) const; virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const; virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const; virtual std::string str() const;
protected: protected:
int64_t value; int64_t value;
}; };
std::unique_ptr<const Node> parse_expr(std::string_view text); std::unique_ptr<const Node> parse_expr(std::string_view text);
std::unique_ptr<const Node> root; 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include <memory> #include <memory>
#include <random> #include <random>
#include "Client.hh" #include "Client.hh"
#include "ItemData.hh" #include "ItemData.hh"
#include "ItemParameterTable.hh" #include "ItemParameterTable.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "ServerState.hh" #include "ServerState.hh"
#include "StaticGameData.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_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 player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
void apply_mag_feed_result( void apply_mag_feed_result(
ItemData& mag_item, ItemData& mag_item,
const ItemData& fed_item, const ItemData& fed_item,
std::shared_ptr<const ItemParameterTable> item_parameter_table, std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const MagEvolutionTable> mag_evolution_table, std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
uint8_t char_class, uint8_t char_class,
uint8_t section_id, uint8_t section_id,
bool version_has_rare_mags); bool version_has_rare_mags);
+190 -190
View File
@@ -1,190 +1,190 @@
#include "LevelTable.hh" #include "LevelTable.hh"
#include <string.h> #include <string.h>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include "Compression.hh" #include "Compression.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
using namespace std; using namespace std;
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const { void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 0; stats.level = 0;
stats.experience = 0; stats.experience = 0;
stats.char_stats = this->base_stats_for_class(char_class); 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 { void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
for (; stats.level < level; stats.level++) { for (; stats.level < level; stats.level++) {
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1); 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 // 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 // don't have max_stat handy so we just allow them to be unbounded
stats.char_stats.atp += level_stats.atp; stats.char_stats.atp += level_stats.atp;
stats.char_stats.mst += level_stats.mst; stats.char_stats.mst += level_stats.mst;
stats.char_stats.evp += level_stats.evp; stats.char_stats.evp += level_stats.evp;
stats.char_stats.hp += level_stats.hp; stats.char_stats.hp += level_stats.hp;
stats.char_stats.dfp += level_stats.dfp; stats.char_stats.dfp += level_stats.dfp;
stats.char_stats.ata += level_stats.ata; stats.char_stats.ata += level_stats.ata;
// Note: It is not a bug that lck is ignored here; the original code // Note: It is not a bug that lck is ignored here; the original code
// ignores it too. // ignores it too.
stats.experience = level_stats.experience; stats.experience = level_stats.experience;
} }
} }
LevelTableV2::LevelTableV2(const string& data, bool compressed) { LevelTableV2::LevelTableV2(const string& data, bool compressed) {
struct Offsets { struct Offsets {
// TODO: The overall format of this file on V2 has much more data than we // 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: // actually use. What's known of the structure so far:
le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200] le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200]
le_uint32_t unknown_a1; // -> float[6] le_uint32_t unknown_a1; // -> float[6]
le_uint32_t max_stats; // -> PlayerStats[9] le_uint32_t max_stats; // -> PlayerStats[9]
le_uint32_t level_100_stats; // -> Level100Entry[9] le_uint32_t level_100_stats; // -> Level100Entry[9]
le_uint32_t base_stats; // -> u32[9] -> CharacterStats le_uint32_t base_stats; // -> u32[9] -> CharacterStats
le_uint32_t unknown_a2; // -> (0x120 zero bytes) le_uint32_t unknown_a2; // -> (0x120 zero bytes)
le_uint32_t attack_data; // -> AttackData[9] le_uint32_t attack_data; // -> AttackData[9]
le_uint32_t unknown_a4; // -> (0x14-byte struct)[9] le_uint32_t unknown_a4; // -> (0x14-byte struct)[9]
le_uint32_t unknown_a5; // -> float[9] le_uint32_t unknown_a5; // -> float[9]
le_uint32_t unknown_a6; // -> (0x30 bytes) le_uint32_t unknown_a6; // -> (0x30 bytes)
le_uint32_t unknown_a7; // -> (0x2D bytes) le_uint32_t unknown_a7; // -> (0x2D bytes)
le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D] le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D]
le_uint32_t unknown_a9; // -> (0x90 bytes) le_uint32_t unknown_a9; // -> (0x90 bytes)
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C] le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes) le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F] le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
} __packed_ws__(Offsets, 0x40); } __packed_ws__(Offsets, 0x40);
StringReader r; StringReader r;
string decompressed_data; string decompressed_data;
if (compressed) { if (compressed) {
decompressed_data = prs_decompress(data); decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data); r = StringReader(decompressed_data);
} else { } else {
r = StringReader(data); r = StringReader(data);
} }
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10)); 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& 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); 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++) { 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]); const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) { for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[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->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->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]); 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 { const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class); return this->base_stats.at(char_class);
} }
const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const { const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
return this->level_100_stats.at(char_class); return this->level_100_stats.at(char_class);
} }
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const { const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
return this->max_stats.at(char_class); return this->max_stats.at(char_class);
} }
const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const { const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level); return this->level_deltas.at(char_class).at(level);
} }
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) { LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
StringReader r; StringReader r;
string decompressed_data; string decompressed_data;
if (encrypted) { if (encrypted) {
auto decrypted = decrypt_pr2_data<true>(data); auto decrypted = decrypt_pr2_data<true>(data);
decompressed_data = prs_decompress(decrypted.compressed_data); decompressed_data = prs_decompress(decrypted.compressed_data);
if (decompressed_data.size() != decrypted.decompressed_size) { if (decompressed_data.size() != decrypted.decompressed_size) {
throw runtime_error("decompressed data size does not match expected size"); throw runtime_error("decompressed data size does not match expected size");
} }
r = StringReader(decompressed_data); r = StringReader(decompressed_data);
} else { } else {
r = StringReader(data); r = StringReader(data);
} }
// The GC format is very simple (but everything is big-endian): // The GC format is very simple (but everything is big-endian):
// root: // root:
// u32 offset: // u32 offset:
// u32[12] offsets: // u32[12] offsets:
// LevelStatsDeltaBE[200] level_deltas // LevelStatsDeltaBE[200] level_deltas
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10))); 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++) { for (size_t char_class = 0; char_class < 12; char_class++) {
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]); const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
for (size_t level = 0; level < 200; level++) { for (size_t level = 0; level < 200; level++) {
const auto& src_delta = src_deltas[level]; const auto& src_delta = src_deltas[level];
auto& dest_delta = this->level_deltas[char_class][level]; auto& dest_delta = this->level_deltas[char_class][level];
dest_delta.atp = src_delta.atp; dest_delta.atp = src_delta.atp;
dest_delta.mst = src_delta.mst; dest_delta.mst = src_delta.mst;
dest_delta.evp = src_delta.evp; dest_delta.evp = src_delta.evp;
dest_delta.hp = src_delta.hp; dest_delta.hp = src_delta.hp;
dest_delta.dfp = src_delta.dfp; dest_delta.dfp = src_delta.dfp;
dest_delta.ata = src_delta.ata; dest_delta.ata = src_delta.ata;
dest_delta.lck = src_delta.lck; dest_delta.lck = src_delta.lck;
dest_delta.tp = src_delta.tp; dest_delta.tp = src_delta.tp;
dest_delta.experience = src_delta.experience.load(); dest_delta.experience = src_delta.experience.load();
} }
} }
} }
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const { const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
static const array<CharacterStats, 12> data = { static const array<CharacterStats, 12> data = {
// ATP MST EVP HP DFP ATA LCK // ATP MST EVP HP DFP ATA LCK
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A}, CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A}, CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A}, CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A}, CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A}, CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A},
CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A}, CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A}, CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A}, CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A},
CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A}, CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A},
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A}, CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A}, CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A}, CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
}; };
return data.at(char_class); return data.at(char_class);
} }
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const { const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level); return this->level_deltas.at(char_class).at(level);
} }
LevelTableV4::LevelTableV4(const string& data, bool compressed) { LevelTableV4::LevelTableV4(const string& data, bool compressed) {
struct Offsets { struct Offsets {
le_uint32_t base_stats; // -> u32[12] -> CharacterStats le_uint32_t base_stats; // -> u32[12] -> CharacterStats
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200] le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
} __packed_ws__(Offsets, 8); } __packed_ws__(Offsets, 8);
StringReader r; StringReader r;
string decompressed_data; string decompressed_data;
if (compressed) { if (compressed) {
decompressed_data = prs_decompress(data); decompressed_data = prs_decompress(data);
r = StringReader(decompressed_data); r = StringReader(decompressed_data);
} else { } else {
r = StringReader(data); r = StringReader(data);
} }
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10)); 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& 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); 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++) { 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]); const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
for (size_t level = 0; level < 200; level++) { for (size_t level = 0; level < 200; level++) {
this->level_deltas[char_class][level] = src_level_deltas[level]; this->level_deltas[char_class][level] = src_level_deltas[level];
} }
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]); 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 { const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
return this->base_stats.at(char_class); return this->base_stats.at(char_class);
} }
const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const { const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
return this->level_deltas.at(char_class).at(level); return this->level_deltas.at(char_class).at(level);
} }
+173 -173
View File
@@ -1,173 +1,173 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <array> #include <array>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <string> #include <string>
#include "Text.hh" #include "Text.hh"
class LevelTable; class LevelTable;
template <bool IsBigEndian> template <bool IsBigEndian>
struct CharacterStatsT { struct CharacterStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type; using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ U16T atp = 0; /* 00 */ U16T atp = 0;
/* 02 */ U16T mst = 0; /* 02 */ U16T mst = 0;
/* 04 */ U16T evp = 0; /* 04 */ U16T evp = 0;
/* 06 */ U16T hp = 0; /* 06 */ U16T hp = 0;
/* 08 */ U16T dfp = 0; /* 08 */ U16T dfp = 0;
/* 0A */ U16T ata = 0; /* 0A */ U16T ata = 0;
/* 0C */ U16T lck = 0; /* 0C */ U16T lck = 0;
/* 0E */ /* 0E */
operator CharacterStatsT<!IsBigEndian>() const { operator CharacterStatsT<!IsBigEndian>() const {
CharacterStatsT<!IsBigEndian> ret; CharacterStatsT<!IsBigEndian> ret;
ret.atp = this->atp.load(); ret.atp = this->atp.load();
ret.mst = this->mst.load(); ret.mst = this->mst.load();
ret.evp = this->evp.load(); ret.evp = this->evp.load();
ret.hp = this->hp.load(); ret.hp = this->hp.load();
ret.dfp = this->dfp.load(); ret.dfp = this->dfp.load();
ret.ata = this->ata.load(); ret.ata = this->ata.load();
ret.lck = this->lck.load(); ret.lck = this->lck.load();
return ret; return ret;
} }
} __packed__; } __packed__;
using CharacterStats = CharacterStatsT<false>; using CharacterStats = CharacterStatsT<false>;
using CharacterStatsBE = CharacterStatsT<true>; using CharacterStatsBE = CharacterStatsT<true>;
check_struct_size(CharacterStats, 0x0E); check_struct_size(CharacterStats, 0x0E);
check_struct_size(CharacterStatsBE, 0x0E); check_struct_size(CharacterStatsBE, 0x0E);
template <bool IsBigEndian> template <bool IsBigEndian>
struct PlayerStatsT { struct PlayerStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type; 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 U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type; using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
/* 00 */ CharacterStatsT<IsBigEndian> char_stats; /* 00 */ CharacterStatsT<IsBigEndian> char_stats;
/* 0E */ U16T esp = 0; /* 0E */ U16T esp = 0;
/* 10 */ F32T height = 0.0; /* 10 */ F32T height = 0.0;
/* 14 */ F32T unknown_a3 = 0.0; /* 14 */ F32T unknown_a3 = 0.0;
/* 18 */ U32T level = 0; /* 18 */ U32T level = 0;
/* 1C */ U32T experience = 0; /* 1C */ U32T experience = 0;
/* 20 */ U32T meseta = 0; /* 20 */ U32T meseta = 0;
/* 24 */ /* 24 */
operator PlayerStatsT<!IsBigEndian>() const { operator PlayerStatsT<!IsBigEndian>() const {
PlayerStatsT<!IsBigEndian> ret; PlayerStatsT<!IsBigEndian> ret;
ret.char_stats = this->char_stats; ret.char_stats = this->char_stats;
ret.esp = this->esp.load(); ret.esp = this->esp.load();
ret.height = this->height.load(); ret.height = this->height.load();
ret.unknown_a3 = this->unknown_a3.load(); ret.unknown_a3 = this->unknown_a3.load();
ret.level = this->level.load(); ret.level = this->level.load();
ret.experience = this->experience.load(); ret.experience = this->experience.load();
ret.meseta = this->meseta.load(); ret.meseta = this->meseta.load();
return ret; return ret;
} }
} __packed__; } __packed__;
using PlayerStats = PlayerStatsT<false>; using PlayerStats = PlayerStatsT<false>;
using PlayerStatsBE = PlayerStatsT<true>; using PlayerStatsBE = PlayerStatsT<true>;
check_struct_size(PlayerStats, 0x24); check_struct_size(PlayerStats, 0x24);
check_struct_size(PlayerStatsBE, 0x24); check_struct_size(PlayerStatsBE, 0x24);
template <bool IsBigEndian> template <bool IsBigEndian>
struct LevelStatsDeltaT { struct LevelStatsDeltaT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type; using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ uint8_t atp; /* 00 */ uint8_t atp;
/* 01 */ uint8_t mst; /* 01 */ uint8_t mst;
/* 02 */ uint8_t evp; /* 02 */ uint8_t evp;
/* 03 */ uint8_t hp; /* 03 */ uint8_t hp;
/* 04 */ uint8_t dfp; /* 04 */ uint8_t dfp;
/* 05 */ uint8_t ata; /* 05 */ uint8_t ata;
/* 06 */ uint8_t lck; /* 06 */ uint8_t lck;
/* 07 */ uint8_t tp; /* 07 */ uint8_t tp;
/* 08 */ U32T experience; /* 08 */ U32T experience;
/* 0C */ /* 0C */
void apply(CharacterStats& ps) const { void apply(CharacterStats& ps) const {
ps.ata += this->ata; ps.ata += this->ata;
ps.atp += this->atp; ps.atp += this->atp;
ps.dfp += this->dfp; ps.dfp += this->dfp;
ps.evp += this->evp; ps.evp += this->evp;
ps.hp += this->hp; ps.hp += this->hp;
ps.mst += this->mst; ps.mst += this->mst;
ps.lck += this->lck; ps.lck += this->lck;
} }
} __packed__; } __packed__;
using LevelStatsDelta = LevelStatsDeltaT<false>; using LevelStatsDelta = LevelStatsDeltaT<false>;
using LevelStatsDeltaBE = LevelStatsDeltaT<true>; using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
check_struct_size(LevelStatsDelta, 0x0C); check_struct_size(LevelStatsDelta, 0x0C);
check_struct_size(LevelStatsDeltaBE, 0x0C); check_struct_size(LevelStatsDeltaBE, 0x0C);
class LevelTable { class LevelTable {
// This is the base class for all the LevelTable implementations. The public // This is the base class for all the LevelTable implementations. The public
// interface here only defines functions that the server needs to handle // interface here only defines functions that the server needs to handle
// requests, but some subclasses implement more functionality. See the // requests, but some subclasses implement more functionality. See the
// comments and Offsets structures inside the subclasses' constructor // comments and Offsets structures inside the subclasses' constructor
// implementations for more details on the file formats. // implementations for more details on the file formats.
public: public:
virtual ~LevelTable() = default; virtual ~LevelTable() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0; 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; 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 reset_to_base(PlayerStats& stats, uint8_t char_class) const;
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const; void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
protected: protected:
LevelTable() = default; LevelTable() = default;
}; };
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC) class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
public: public:
struct Level100Entry { struct Level100Entry {
/* 00 */ CharacterStats char_stats; /* 00 */ CharacterStats char_stats;
/* 0E */ le_uint16_t unknown_a1 = 0; /* 0E */ le_uint16_t unknown_a1 = 0;
/* 10 */ le_float height = 0.0; /* 10 */ le_float height = 0.0;
/* 14 */ le_float unknown_a3 = 0.0; /* 14 */ le_float unknown_a3 = 0.0;
/* 18 */ le_uint32_t level = 0; /* 18 */ le_uint32_t level = 0;
/* 1C */ /* 1C */
} __packed_ws__(Level100Entry, 0x1C); } __packed_ws__(Level100Entry, 0x1C);
LevelTableV2(const std::string& data, bool compressed); LevelTableV2(const std::string& data, bool compressed);
virtual ~LevelTableV2() = default; virtual ~LevelTableV2() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const; 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 Level100Entry& level_100_stats_for_class(uint8_t char_class) const;
const PlayerStats& max_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; virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private: private:
std::array<CharacterStats, 9> base_stats; std::array<CharacterStats, 9> base_stats;
std::array<Level100Entry, 9> level_100_stats; std::array<Level100Entry, 9> level_100_stats;
std::array<PlayerStats, 9> max_stats; std::array<PlayerStats, 9> max_stats;
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas; std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
}; };
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC) class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
public: public:
LevelTableV3BE(const std::string& data, bool encrypted); LevelTableV3BE(const std::string& data, bool encrypted);
virtual ~LevelTableV3BE() = default; virtual ~LevelTableV3BE() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const; 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; virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private: private:
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas; std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
}; };
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB) class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
public: public:
LevelTableV4(const std::string& data, bool compressed); LevelTableV4(const std::string& data, bool compressed);
virtual ~LevelTableV4() = default; virtual ~LevelTableV4() = default;
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const; 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; virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
private: private:
std::array<CharacterStats, 12> base_stats; std::array<CharacterStats, 12> base_stats;
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas; 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 #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <inttypes.h> #include <inttypes.h>
#include <array> #include <array>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <random> #include <random>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "Client.hh" #include "Client.hh"
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh" #include "Episode3/BattleRecord.hh"
#include "Episode3/Server.hh" #include "Episode3/Server.hh"
#include "ItemCreator.hh" #include "ItemCreator.hh"
#include "Map.hh" #include "Map.hh"
#include "Quest.hh" #include "Quest.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
struct ServerState; struct ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> { struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem { struct FloorItem {
ItemData data; ItemData data;
float x; float x;
float z; float z;
uint64_t drop_number; uint64_t drop_number;
// The low 12 bits of flags are visibility flags, specifying which clients // 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, // 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.) // but the game has fields for 12 players so we do too.)
// The 13th bit (0x1000) specifies whether a rare item notification should // 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 // be sent to all players when the item is picked up. This has no effect for
// non-rare items. // non-rare items.
uint16_t flags; uint16_t flags;
bool visible_to_client(uint8_t client_id) const; bool visible_to_client(uint8_t client_id) const;
}; };
struct FloorItemManager { struct FloorItemManager {
PrefixedLogger log; PrefixedLogger log;
uint64_t next_drop_number; uint64_t next_drop_number;
// It's important that this is a map and not an unordered_map. See the // It's important that this is a map and not an unordered_map. See the
// comment in send_game_item_state for more details. // comment in send_game_item_state for more details.
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id 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; std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
FloorItemManager(uint32_t lobby_id, uint8_t floor); FloorItemManager(uint32_t lobby_id, uint8_t floor);
~FloorItemManager() = default; ~FloorItemManager() = default;
bool exists(uint32_t item_id) const; bool exists(uint32_t item_id) const;
std::shared_ptr<FloorItem> find(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(const ItemData& item, float x, float z, uint16_t flags);
void add(std::shared_ptr<FloorItem> fi); void add(std::shared_ptr<FloorItem> fi);
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id); std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
std::unordered_set<std::shared_ptr<FloorItem>> evict(); std::unordered_set<std::shared_ptr<FloorItem>> evict();
void clear_inaccessible(uint16_t remaining_clients_mask); void clear_inaccessible(uint16_t remaining_clients_mask);
void clear_private(); void clear_private();
void clear(); void clear();
uint32_t reassign_all_item_ids(uint32_t next_item_id); uint32_t reassign_all_item_ids(uint32_t next_item_id);
}; };
enum class Flag { enum class Flag {
// clang-format off // clang-format off
GAME = 0x00000001, GAME = 0x00000001,
PERSISTENT = 0x00000002, PERSISTENT = 0x00000002,
// Flags used only for games // Flags used only for games
CHEATS_ENABLED = 0x00000100, CHEATS_ENABLED = 0x00000100,
QUEST_SELECTION_IN_PROGRESS = 0x00000200, QUEST_SELECTION_IN_PROGRESS = 0x00000200,
QUEST_IN_PROGRESS = 0x00000400, QUEST_IN_PROGRESS = 0x00000400,
BATTLE_IN_PROGRESS = 0x00000800, BATTLE_IN_PROGRESS = 0x00000800,
JOINABLE_QUEST_IN_PROGRESS = 0x00001000, JOINABLE_QUEST_IN_PROGRESS = 0x00001000,
IS_CLIENT_CUSTOMIZATION = 0x00002000, IS_CLIENT_CUSTOMIZATION = 0x00002000,
IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also
SPECTATORS_FORBIDDEN = 0x00008000, SPECTATORS_FORBIDDEN = 0x00008000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000, START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000,
CANNOT_CHANGE_CHEAT_MODE = 0x00020000, CANNOT_CHANGE_CHEAT_MODE = 0x00020000,
USE_CREATOR_SECTION_ID = 0x00040000, USE_CREATOR_SECTION_ID = 0x00040000,
// Flags used only for lobbies // Flags used only for lobbies
PUBLIC = 0x01000000, PUBLIC = 0x01000000,
DEFAULT = 0x02000000, DEFAULT = 0x02000000,
IS_OVERFLOW = 0x08000000, IS_OVERFLOW = 0x08000000,
// clang-format on // clang-format on
}; };
enum class DropMode { enum class DropMode {
DISABLED = 0, DISABLED = 0,
CLIENT = 1, // Not allowed for BB games CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2, SERVER_SHARED = 2,
SERVER_PRIVATE = 3, SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4, SERVER_DUPLICATE = 4,
}; };
std::weak_ptr<ServerState> std::weak_ptr<ServerState>
server_state; server_state;
PrefixedLogger log; PrefixedLogger log;
uint32_t lobby_id; uint32_t lobby_id;
uint32_t min_level; uint32_t min_level;
uint32_t max_level; uint32_t max_level;
// Game state // Game state
std::array<uint32_t, 12> next_item_id_for_client; std::array<uint32_t, 12> next_item_id_for_client;
uint32_t next_game_item_id; uint32_t next_game_item_id;
std::vector<FloorItemManager> floor_item_managers; std::vector<FloorItemManager> floor_item_managers;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates; std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
std::shared_ptr<Map> map; std::shared_ptr<Map> map;
parray<le_uint32_t, 0x20> variations; 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_flags_known; // If null, ALL quest flags are known
std::unique_ptr<QuestFlags> quest_flag_values; std::unique_ptr<QuestFlags> quest_flag_values;
std::unique_ptr<SwitchFlags> switch_flags; std::unique_ptr<SwitchFlags> switch_flags;
// Game config // Game config
Version base_version; Version base_version;
// Bits in allowed_versions specify who is allowed to join this game. The // 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 // bits are indexed as (1 << version), where version is a value from the
// Version enum. // Version enum.
uint16_t allowed_versions; uint16_t allowed_versions;
uint8_t creator_section_id; uint8_t creator_section_id;
uint8_t override_section_id; uint8_t override_section_id;
Episode episode; Episode episode;
GameMode mode; GameMode mode;
uint8_t difficulty; // 0-3 uint8_t difficulty; // 0-3
uint16_t base_exp_multiplier; uint16_t base_exp_multiplier;
float exp_share_multiplier; float exp_share_multiplier;
float challenge_exp_multiplier; float challenge_exp_multiplier;
std::string password; std::string password;
std::string name; std::string name;
// This seed is also sent to the client for rare enemy generation // This seed is also sent to the client for rare enemy generation
uint32_t random_seed; uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt; std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
uint8_t allowed_drop_modes; uint8_t allowed_drop_modes;
DropMode drop_mode; DropMode drop_mode;
std::shared_ptr<ItemCreator> item_creator; std::shared_ptr<ItemCreator> item_creator;
struct ChallengeParameters { struct ChallengeParameters {
uint8_t stage_number = 0; uint8_t stage_number = 0;
uint32_t rank_color = 0xFFFFFFFF; uint32_t rank_color = 0xFFFFFFFF;
std::string rank_text; std::string rank_text;
struct RankThreshold { struct RankThreshold {
uint32_t bitmask = 0; uint32_t bitmask = 0;
uint32_t seconds = 0; uint32_t seconds = 0;
}; };
std::array<RankThreshold, 3> rank_thresholds; std::array<RankThreshold, 3> rank_thresholds;
}; };
std::shared_ptr<ChallengeParameters> challenge_params; std::shared_ptr<ChallengeParameters> challenge_params;
// Ep3 stuff // Ep3 stuff
// There are three kinds of Episode 3 games. All of these types have episode // 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. // 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. // 1. Primary games. These are the lobbies where battles may take place.
// 2. Watcher games. These lobbies receive all the battle and chat commands // 2. Watcher games. These lobbies receive all the battle and chat commands
// from a primary game. (This the implementation of spectator teams.) // from a primary game. (This the implementation of spectator teams.)
// 3. Replay games. These lobbies replay a sequence of battle commands and // 3. Replay games. These lobbies replay a sequence of battle commands and
// chat commands from a previous primary game. // chat commands from a previous primary game.
// Types 2 and 3 may be distinguished by the presence of the battle_record // 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 // field - in replay games, it will be present; in watcher games it will be
// absent. // absent.
std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games
std::weak_ptr<Lobby> watched_lobby; // Only used in watcher 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::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::BattleRecord> battle_record; // Not used in watcher games
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
std::shared_ptr<Episode3::Tournament::Match> tournament_match; std::shared_ptr<Episode3::Tournament::Match> tournament_match;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values; std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
// Lobby stuff // Lobby stuff
uint8_t event; uint8_t event;
uint8_t block; uint8_t block;
uint8_t leader_id; uint8_t leader_id;
uint8_t max_clients; uint8_t max_clients;
uint32_t enabled_flags; uint32_t enabled_flags;
std::shared_ptr<const Quest> quest; std::shared_ptr<const Quest> quest;
std::array<std::shared_ptr<Client>, 12> clients; std::array<std::shared_ptr<Client>, 12> clients;
// Keys in this map are client_id // Keys in this map are client_id
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add; 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 // This is only used when the PERSISTENT flag is set and idle_timeout_usecs
// is not zero // is not zero
uint64_t idle_timeout_usecs; uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event; std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game); Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
Lobby(const Lobby&) = delete; Lobby(const Lobby&) = delete;
Lobby(Lobby&&) = delete; Lobby(Lobby&&) = delete;
~Lobby(); ~Lobby();
Lobby& operator=(const Lobby&) = delete; Lobby& operator=(const Lobby&) = delete;
Lobby& operator=(Lobby&&) = delete; Lobby& operator=(Lobby&&) = delete;
void reset_next_item_ids(); void reset_next_item_ids();
[[nodiscard]] inline bool check_flag(Flag flag) const { [[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->enabled_flags & static_cast<uint32_t>(flag)); return !!(this->enabled_flags & static_cast<uint32_t>(flag));
} }
inline void set_flag(Flag flag) { inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint32_t>(flag); this->enabled_flags |= static_cast<uint32_t>(flag);
} }
inline void clear_flag(Flag flag) { inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint32_t>(flag)); this->enabled_flags &= (~static_cast<uint32_t>(flag));
} }
inline void toggle_flag(Flag flag) { inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint32_t>(flag); this->enabled_flags ^= static_cast<uint32_t>(flag);
} }
std::shared_ptr<ServerState> require_server_state() const; std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<ChallengeParameters> require_challenge_params() const; std::shared_ptr<ChallengeParameters> require_challenge_params() const;
void set_drop_mode(DropMode new_mode); void set_drop_mode(DropMode new_mode);
void create_item_creator(); void create_item_creator();
void change_section_id(); void change_section_id();
uint8_t effective_section_id() const; uint8_t effective_section_id() const;
static std::shared_ptr<Map> load_maps( static std::shared_ptr<Map> load_maps(
Version version, Version version,
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint32_t lobby_id, uint32_t lobby_id,
std::shared_ptr<const Map::RareEnemyRates> rare_rates, std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed, uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
std::shared_ptr<const std::string> quest_dat_contents_decompressed); std::shared_ptr<const std::string> quest_dat_contents_decompressed);
static std::shared_ptr<Map> load_maps( static std::shared_ptr<Map> load_maps(
Version version, Version version,
Episode episode, Episode episode,
GameMode mode, GameMode mode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint32_t lobby_id, uint32_t lobby_id,
std::shared_ptr<const SetDataTableBase> sdt, std::shared_ptr<const SetDataTableBase> sdt,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data, std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates, std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed, uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const parray<le_uint32_t, 0x20>& variations, const parray<le_uint32_t, 0x20>& variations,
const PrefixedLogger* log = nullptr); const PrefixedLogger* log = nullptr);
static std::shared_ptr<Map> load_maps( static std::shared_ptr<Map> load_maps(
const std::vector<std::string>& enemy_filenames, const std::vector<std::string>& enemy_filenames,
const std::vector<std::string>& object_filenames, const std::vector<std::string>& object_filenames,
const std::vector<std::string>& event_filenames, const std::vector<std::string>& event_filenames,
Version version, Version version,
Episode episode, Episode episode,
GameMode mode, GameMode mode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint32_t lobby_id, uint32_t lobby_id,
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data, std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
std::shared_ptr<const Map::RareEnemyRates> rare_rates, std::shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed, uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const PrefixedLogger* log = nullptr); const PrefixedLogger* log = nullptr);
void load_maps(); void load_maps();
void create_ep3_server(); void create_ep3_server();
[[nodiscard]] inline bool is_game() const { [[nodiscard]] inline bool is_game() const {
return this->check_flag(Flag::GAME); return this->check_flag(Flag::GAME);
} }
[[nodiscard]] inline bool is_ep3() const { [[nodiscard]] inline bool is_ep3() const {
return this->episode == Episode::EP3; return this->episode == Episode::EP3;
} }
[[nodiscard]] inline bool version_is_allowed(Version v) const { [[nodiscard]] inline bool version_is_allowed(Version v) const {
return this->allowed_versions & (1 << static_cast<size_t>(v)); return this->allowed_versions & (1 << static_cast<size_t>(v));
} }
inline void allow_version(Version v) { inline void allow_version(Version v) {
this->allowed_versions |= (1 << static_cast<size_t>(v)); this->allowed_versions |= (1 << static_cast<size_t>(v));
} }
void reassign_leader_on_client_departure(size_t leaving_client_id); void reassign_leader_on_client_departure(size_t leaving_client_id);
size_t count_clients() const; size_t count_clients() const;
bool any_v1_clients_present() const; bool any_v1_clients_present() const;
bool any_client_loading() const; bool any_client_loading() const;
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1); void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
void remove_client(std::shared_ptr<Client> c); void remove_client(std::shared_ptr<Client> c);
void move_client_to_lobby( void move_client_to_lobby(
std::shared_ptr<Lobby> dest_lobby, std::shared_ptr<Lobby> dest_lobby,
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
ssize_t required_client_id = -1); ssize_t required_client_id = -1);
std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0); std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0);
enum class JoinError { enum class JoinError {
ALLOWED = 0, ALLOWED = 0,
FULL, FULL,
VERSION_CONFLICT, VERSION_CONFLICT,
QUEST_SELECTION_IN_PROGRESS, QUEST_SELECTION_IN_PROGRESS,
QUEST_IN_PROGRESS, QUEST_IN_PROGRESS,
BATTLE_IN_PROGRESS, BATTLE_IN_PROGRESS,
LOADING, LOADING,
SOLO, SOLO,
INCORRECT_PASSWORD, INCORRECT_PASSWORD,
LEVEL_TOO_LOW, LEVEL_TOO_LOW,
LEVEL_TOO_HIGH, LEVEL_TOO_HIGH,
NO_ACCESS_TO_QUEST, NO_ACCESS_TO_QUEST,
}; };
JoinError join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const; 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; 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; 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, const ItemData& item, float x, float z, uint16_t flags);
void add_item(uint8_t floor, std::shared_ptr<FloorItem>); void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
void evict_items_from_floor(uint8_t floor); 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); 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); uint32_t generate_item_id(uint8_t client_id);
void on_item_id_generated_externally(uint32_t item_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); void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
QuestIndex::IncludeCondition quest_include_condition() const; QuestIndex::IncludeCondition quest_include_condition() const;
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() 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 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); static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
}; };
template <> template <>
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name); Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
template <> template <>
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value); const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
+48 -48
View File
@@ -1,48 +1,48 @@
#include "Loggers.hh" #include "Loggers.hh"
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
using namespace std; using namespace std;
PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT); PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT);
PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT); PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT);
PrefixedLogger client_log("", LogLevel::USE_DEFAULT); PrefixedLogger client_log("", LogLevel::USE_DEFAULT);
PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT); PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT);
PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT); PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT); PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT); PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT); PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT); PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT); PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT); PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT); PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT); PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT); PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT); PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
static void set_log_level_from_json( static void set_log_level_from_json(
PrefixedLogger& log, const JSON& d, const char* json_key) { PrefixedLogger& log, const JSON& d, const char* json_key) {
try { try {
string name = toupper(d.at(json_key).as_string()); string name = toupper(d.at(json_key).as_string());
log.min_level = enum_for_name<LogLevel>(name.c_str()); log.min_level = enum_for_name<LogLevel>(name.c_str());
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
} }
void set_log_levels_from_json(const JSON& json) { void set_log_levels_from_json(const JSON& json) {
set_log_level_from_json(ax_messages_log, json, "AXMessages"); 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(channel_exceptions_log, json, "ChannelExceptions");
set_log_level_from_json(client_log, json, "Clients"); set_log_level_from_json(client_log, json, "Clients");
set_log_level_from_json(command_data_log, json, "CommandData"); set_log_level_from_json(command_data_log, json, "CommandData");
set_log_level_from_json(config_log, json, "Config"); set_log_level_from_json(config_log, json, "Config");
set_log_level_from_json(dns_server_log, json, "DNSServer"); 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(function_compiler_log, json, "FunctionCompiler");
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator"); 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(lobby_log, json, "Lobbies");
set_log_level_from_json(patch_index_log, json, "PatchFileIndex"); 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(player_data_log, json, "PlayerData");
set_log_level_from_json(proxy_server_log, json, "ProxyServer"); set_log_level_from_json(proxy_server_log, json, "ProxyServer");
set_log_level_from_json(replay_log, json, "Replay"); set_log_level_from_json(replay_log, json, "Replay");
set_log_level_from_json(server_log, json, "GameServer"); set_log_level_from_json(server_log, json, "GameServer");
set_log_level_from_json(static_game_data_log, json, "StaticGameData"); set_log_level_from_json(static_game_data_log, json, "StaticGameData");
} }
+22 -22
View File
@@ -1,22 +1,22 @@
#pragma once #pragma once
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
extern PrefixedLogger ax_messages_log; extern PrefixedLogger ax_messages_log;
extern PrefixedLogger channel_exceptions_log; extern PrefixedLogger channel_exceptions_log;
extern PrefixedLogger client_log; extern PrefixedLogger client_log;
extern PrefixedLogger command_data_log; extern PrefixedLogger command_data_log;
extern PrefixedLogger config_log; extern PrefixedLogger config_log;
extern PrefixedLogger dns_server_log; extern PrefixedLogger dns_server_log;
extern PrefixedLogger function_compiler_log; extern PrefixedLogger function_compiler_log;
extern PrefixedLogger ip_stack_simulator_log; extern PrefixedLogger ip_stack_simulator_log;
extern PrefixedLogger lobby_log; extern PrefixedLogger lobby_log;
extern PrefixedLogger patch_index_log; extern PrefixedLogger patch_index_log;
extern PrefixedLogger player_data_log; extern PrefixedLogger player_data_log;
extern PrefixedLogger proxy_server_log; extern PrefixedLogger proxy_server_log;
extern PrefixedLogger replay_log; extern PrefixedLogger replay_log;
extern PrefixedLogger server_log; extern PrefixedLogger server_log;
extern PrefixedLogger static_game_data_log; extern PrefixedLogger static_game_data_log;
void set_log_levels_from_json(const JSON& json); 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 #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <random> #include <random>
#include <string> #include <string>
#include <vector> #include <vector>
#include "BattleParamsIndex.hh" #include "BattleParamsIndex.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
struct Map { struct Map {
static const char* name_for_object_type(uint16_t type); static const char* name_for_object_type(uint16_t type);
struct SectionHeader { struct SectionHeader {
enum class Type { enum class Type {
END = 0, END = 0,
OBJECTS = 1, OBJECTS = 1,
ENEMIES = 2, ENEMIES = 2,
WAVE_EVENTS = 3, WAVE_EVENTS = 3,
RANDOM_ENEMY_LOCATIONS = 4, RANDOM_ENEMY_LOCATIONS = 4,
RANDOM_ENEMY_DEFINITIONS = 5, RANDOM_ENEMY_DEFINITIONS = 5,
}; };
le_uint32_t le_type; le_uint32_t le_type;
le_uint32_t section_size; // Includes this header le_uint32_t section_size; // Includes this header
le_uint32_t floor; le_uint32_t floor;
le_uint32_t data_size; le_uint32_t data_size;
inline Type type() const { inline Type type() const {
return static_cast<Type>(this->le_type.load()); return static_cast<Type>(this->le_type.load());
} }
} __packed_ws__(SectionHeader, 0x10); } __packed_ws__(SectionHeader, 0x10);
struct ObjectEntry { // Section type 1 (OBJECTS) struct ObjectEntry { // Section type 1 (OBJECTS)
/* 00 */ le_uint16_t base_type; /* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags; /* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index; /* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t unknown_a2; /* 06 */ le_uint16_t unknown_a2;
/* 08 */ le_uint16_t entity_id; // == index + 0x4000 /* 08 */ le_uint16_t entity_id; // == index + 0x4000
/* 0A */ le_uint16_t group; /* 0A */ le_uint16_t group;
/* 0C */ le_uint16_t section; /* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t unknown_a3; /* 0E */ le_uint16_t unknown_a3;
/* 10 */ le_float x; /* 10 */ le_float x;
/* 14 */ le_float y; /* 14 */ le_float y;
/* 18 */ le_float z; /* 18 */ le_float z;
/* 1C */ le_uint32_t x_angle; /* 1C */ le_uint32_t x_angle;
/* 20 */ le_uint32_t y_angle; /* 20 */ le_uint32_t y_angle;
/* 24 */ le_uint32_t z_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 /* 28 */ le_float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
/* 2C */ le_float param2; /* 2C */ le_float param2;
/* 30 */ le_float param3; /* 30 */ le_float param3;
/* 34 */ le_uint32_t param4; /* 34 */ le_uint32_t param4;
/* 38 */ le_uint32_t param5; /* 38 */ le_uint32_t param5;
/* 3C */ le_uint32_t param6; /* 3C */ le_uint32_t param6;
/* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server /* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 44 */ /* 44 */
std::string str() const; std::string str() const;
} __packed_ws__(ObjectEntry, 0x44); } __packed_ws__(ObjectEntry, 0x44);
struct EnemyEntry { // Section type 2 (ENEMIES) struct EnemyEntry { // Section type 2 (ENEMIES)
/* 00 */ le_uint16_t base_type; /* 00 */ le_uint16_t base_type;
/* 02 */ le_uint16_t flags; /* 02 */ le_uint16_t flags;
/* 04 */ le_uint16_t index; /* 04 */ le_uint16_t index;
/* 06 */ le_uint16_t num_children; /* 06 */ le_uint16_t num_children;
/* 08 */ le_uint16_t floor; /* 08 */ le_uint16_t floor;
/* 0A */ le_uint16_t entity_id; // == index + 0x1000 /* 0A */ le_uint16_t entity_id; // == index + 0x1000
/* 0C */ le_uint16_t section; /* 0C */ le_uint16_t section;
/* 0E */ le_uint16_t wave_number; /* 0E */ le_uint16_t wave_number;
/* 10 */ le_uint16_t wave_number2; /* 10 */ le_uint16_t wave_number2;
/* 12 */ le_uint16_t unknown_a1; /* 12 */ le_uint16_t unknown_a1;
/* 14 */ le_float x; /* 14 */ le_float x;
/* 18 */ le_float y; /* 18 */ le_float y;
/* 1C */ le_float z; /* 1C */ le_float z;
/* 20 */ le_uint32_t x_angle; /* 20 */ le_uint32_t x_angle;
/* 24 */ le_uint32_t y_angle; /* 24 */ le_uint32_t y_angle;
/* 28 */ le_uint32_t z_angle; /* 28 */ le_uint32_t z_angle;
/* 2C */ le_float fparam1; /* 2C */ le_float fparam1;
/* 30 */ le_float fparam2; /* 30 */ le_float fparam2;
/* 34 */ le_float fparam3; /* 34 */ le_float fparam3;
/* 38 */ le_float fparam4; /* 38 */ le_float fparam4;
/* 3C */ le_float fparam5; /* 3C */ le_float fparam5;
/* 40 */ le_uint16_t uparam1; /* 40 */ le_uint16_t uparam1;
/* 42 */ le_uint16_t uparam2; /* 42 */ le_uint16_t uparam2;
/* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server /* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
/* 48 */ /* 48 */
std::string str() const; std::string str() const;
} __packed_ws__(EnemyEntry, 0x48); } __packed_ws__(EnemyEntry, 0x48);
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS) struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
/* 00 */ le_uint32_t action_stream_offset; /* 00 */ le_uint32_t action_stream_offset;
/* 04 */ le_uint32_t entries_offset; /* 04 */ le_uint32_t entries_offset;
/* 08 */ le_uint32_t entry_count; /* 08 */ le_uint32_t entry_count;
/* 0C */ be_uint32_t format; // 0 or 'evt2' /* 0C */ be_uint32_t format; // 0 or 'evt2'
/* 10 */ /* 10 */
} __packed_ws__(EventsSectionHeader, 0x10); } __packed_ws__(EventsSectionHeader, 0x10);
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0 struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
/* 00 */ le_uint32_t event_id; /* 00 */ le_uint32_t event_id;
// Bits in flags: // Bits in flags:
// 0004 = is active // 0004 = is active
// 0008 = post-wave actions have been run // 0008 = post-wave actions have been run
// 0010 = all enemies killed // 0010 = all enemies killed
/* 04 */ le_uint16_t flags; /* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type; /* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section; /* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number; /* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint32_t delay; /* 0C */ le_uint32_t delay;
/* 10 */ le_uint32_t action_stream_offset; /* 10 */ le_uint32_t action_stream_offset;
/* 14 */ /* 14 */
} __packed_ws__(Event1Entry, 0x14); } __packed_ws__(Event1Entry, 0x14);
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2' struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
/* 00 */ le_uint32_t event_id; /* 00 */ le_uint32_t event_id;
/* 04 */ le_uint16_t flags; /* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t event_type; /* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section; /* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number; /* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint16_t min_delay; /* 0C */ le_uint16_t min_delay;
/* 0E */ le_uint16_t max_delay; /* 0E */ le_uint16_t max_delay;
/* 10 */ uint8_t min_enemies; /* 10 */ uint8_t min_enemies;
/* 11 */ uint8_t max_enemies; /* 11 */ uint8_t max_enemies;
/* 12 */ le_uint16_t max_waves; /* 12 */ le_uint16_t max_waves;
/* 14 */ le_uint32_t action_stream_offset; /* 14 */ le_uint32_t action_stream_offset;
/* 18 */ /* 18 */
} __packed_ws__(Event2Entry, 0x18); } __packed_ws__(Event2Entry, 0x18);
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS) struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct /* 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 /* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
/* 08 */ le_uint32_t num_sections; /* 08 */ le_uint32_t num_sections;
/* 0C */ /* 0C */
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C); } __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS) struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint16_t section; /* 00 */ le_uint16_t section;
/* 02 */ le_uint16_t count; /* 02 */ le_uint16_t count;
/* 04 */ le_uint32_t offset; /* 04 */ le_uint32_t offset;
/* 08 */ /* 08 */
} __packed_ws__(RandomEnemyLocationSection, 8); } __packed_ws__(RandomEnemyLocationSection, 8);
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS) struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_float x; /* 00 */ le_float x;
/* 04 */ le_float y; /* 04 */ le_float y;
/* 08 */ le_float z; /* 08 */ le_float z;
/* 0C */ le_uint32_t x_angle; /* 0C */ le_uint32_t x_angle;
/* 10 */ le_uint32_t y_angle; /* 10 */ le_uint32_t y_angle;
/* 14 */ le_uint32_t z_angle; /* 14 */ le_uint32_t z_angle;
/* 18 */ uint16_t unknown_a9; /* 18 */ uint16_t unknown_a9;
/* 1A */ uint16_t unknown_a10; /* 1A */ uint16_t unknown_a10;
/* 1C */ /* 1C */
} __packed_ws__(RandomEnemyLocationEntry, 0x1C); } __packed_ws__(RandomEnemyLocationEntry, 0x1C);
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct /* 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 /* 04 */ le_uint32_t weight_entries_offset; // Offset to RandomEnemyDefinitionWeights structs, from start of this struct
/* 08 */ le_uint32_t entry_count; /* 08 */ le_uint32_t entry_count;
/* 0C */ le_uint32_t weight_entry_count; /* 0C */ le_uint32_t weight_entry_count;
/* 10 */ /* 10 */
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10); } __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
// All fields through entry_num map to the corresponding fields in // All fields through entry_num map to the corresponding fields in
// EnemyEntry. Note that the order of the uparam fields is switched! // EnemyEntry. Note that the order of the uparam fields is switched!
/* 00 */ le_float fparam1; /* 00 */ le_float fparam1;
/* 04 */ le_float fparam2; /* 04 */ le_float fparam2;
/* 08 */ le_float fparam3; /* 08 */ le_float fparam3;
/* 0C */ le_float fparam4; /* 0C */ le_float fparam4;
/* 10 */ le_float fparam5; /* 10 */ le_float fparam5;
/* 14 */ le_uint16_t uparam2; /* 14 */ le_uint16_t uparam2;
/* 16 */ le_uint16_t uparam1; /* 16 */ le_uint16_t uparam1;
/* 18 */ le_uint32_t entry_num; /* 18 */ le_uint32_t entry_num;
/* 1C */ le_uint16_t min_children; /* 1C */ le_uint16_t min_children;
/* 1E */ le_uint16_t max_children; /* 1E */ le_uint16_t max_children;
/* 20 */ /* 20 */
} __packed_ws__(RandomEnemyDefinition, 0x20); } __packed_ws__(RandomEnemyDefinition, 0x20);
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ uint8_t base_type_index; /* 00 */ uint8_t base_type_index;
/* 01 */ uint8_t definition_entry_num; /* 01 */ uint8_t definition_entry_num;
/* 02 */ uint8_t weight; /* 02 */ uint8_t weight;
/* 03 */ uint8_t unknown_a4; /* 03 */ uint8_t unknown_a4;
/* 04 */ /* 04 */
} __packed_ws__(RandomEnemyWeight, 4); } __packed_ws__(RandomEnemyWeight, 4);
struct RareEnemyRates { struct RareEnemyRates {
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
uint32_t nar_lily; // POISON_LILY -> NAR_LILY uint32_t nar_lily; // POISON_LILY -> NAR_LILY
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants) uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate); RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
explicit RareEnemyRates(const JSON& json); explicit RareEnemyRates(const JSON& json);
JSON json() const; JSON json() const;
}; };
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES; static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES; static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
struct Object { struct Object {
// TODO: Add more fields in here if we ever care about them. Currently we // TODO: Add more fields in here if we ever care about them. Currently we
// only care about boxes with fixed item drops. // only care about boxes with fixed item drops.
size_t source_index; size_t source_index;
uint8_t floor; uint8_t floor;
uint16_t object_id; uint16_t object_id;
uint16_t base_type; uint16_t base_type;
uint16_t section; uint16_t section;
uint16_t group; uint16_t group;
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6 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 float param3; // If == 0, the item should be varied by difficulty and area
uint32_t param4; uint32_t param4;
uint32_t param5; uint32_t param5;
uint32_t param6; uint32_t param6;
uint16_t game_flags; uint16_t game_flags;
// Technically set_flags shouldn't be part of the Object struct, but all // 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. // object entries always generate exactly one object, so we store it here.
uint16_t set_flags; uint16_t set_flags;
bool item_drop_checked; bool item_drop_checked;
std::string str() const; std::string str() const;
}; };
struct Enemy { struct Enemy {
enum Flag { enum Flag {
LAST_HIT_MASK = 0x03, LAST_HIT_MASK = 0x03,
EXP_GIVEN = 0x04, EXP_GIVEN = 0x04,
ITEM_DROPPED = 0x08, ITEM_DROPPED = 0x08,
ALL_HITS_MASK_FIRST = 0x10, ALL_HITS_MASK_FIRST = 0x10,
ALL_HITS_MASK = 0xF0, ALL_HITS_MASK = 0xF0,
}; };
size_t source_index; size_t source_index;
size_t set_index; size_t set_index;
uint16_t enemy_id; uint16_t enemy_id;
uint16_t total_damage; uint16_t total_damage;
uint32_t game_flags; // From 6x0A uint32_t game_flags; // From 6x0A
uint16_t section; uint16_t section;
uint16_t wave_number; uint16_t wave_number;
EnemyType type; EnemyType type;
uint8_t floor; uint8_t floor;
uint8_t server_flags; uint8_t server_flags;
Enemy( Enemy(
uint16_t enemy_id, uint16_t enemy_id,
size_t source_index, size_t source_index,
size_t set_index, size_t set_index,
uint8_t floor, uint8_t floor,
uint16_t section, uint16_t section,
uint16_t wave_number, uint16_t wave_number,
EnemyType type); EnemyType type);
std::string str() const; std::string str() const;
inline bool ever_hit_by_client_id(uint8_t client_id) const { inline bool ever_hit_by_client_id(uint8_t client_id) const {
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id); return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
} }
inline bool last_hit_by_client_id(uint8_t client_id) const { inline bool last_hit_by_client_id(uint8_t client_id) const {
return (this->server_flags & Flag::LAST_HIT_MASK) == client_id; return (this->server_flags & Flag::LAST_HIT_MASK) == client_id;
} }
inline void set_last_hit_by_client_id(uint8_t 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); this->server_flags = (this->server_flags & (~Flag::LAST_HIT_MASK)) | (Flag::ALL_HITS_MASK_FIRST << client_id) | (client_id & 3);
} }
}; };
struct Event { struct Event {
uint32_t event_id; uint32_t event_id;
uint16_t flags; uint16_t flags;
uint16_t section; uint16_t section;
uint16_t wave_number; uint16_t wave_number;
uint8_t floor; uint8_t floor;
uint32_t action_stream_offset; uint32_t action_stream_offset;
std::vector<size_t> enemy_indexes; std::vector<size_t> enemy_indexes;
std::string str() const; std::string str() const;
}; };
struct DATParserRandomState { struct DATParserRandomState {
PSOV2Encryption random; PSOV2Encryption random;
PSOV2Encryption location_table_random; PSOV2Encryption location_table_random;
std::array<uint32_t, 0x20> location_index_table; std::array<uint32_t, 0x20> location_index_table;
uint32_t location_indexes_populated; uint32_t location_indexes_populated;
uint32_t location_indexes_used; uint32_t location_indexes_used;
uint32_t location_entries_base_offset; uint32_t location_entries_base_offset;
DATParserRandomState(uint32_t rare_seed); DATParserRandomState(uint32_t rare_seed);
size_t rand_int_biased(size_t min_v, size_t max_v); size_t rand_int_biased(size_t min_v, size_t max_v);
uint32_t next_location_index(); uint32_t next_location_index();
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section); 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(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
~Map() = default; ~Map() = default;
void clear(); void clear();
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size); 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); bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
void add_enemy( void add_enemy(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint8_t floor, uint8_t floor,
size_t index, size_t index,
const EnemyEntry& e, const EnemyEntry& e,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES); std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_enemies_from_map_data( void add_enemies_from_map_data(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint8_t floor, uint8_t floor,
const void* data, const void* data,
size_t size, size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES); std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_random_enemies_from_map_data( void add_random_enemies_from_map_data(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
uint8_t floor, uint8_t floor,
StringReader wave_events_r, StringReader wave_events_r,
StringReader random_enemy_locations_r, StringReader random_enemy_locations_r,
StringReader random_enemy_definitions_r, StringReader random_enemy_definitions_r,
std::shared_ptr<DATParserRandomState> random_state, std::shared_ptr<DATParserRandomState> random_state,
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES); std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_event( void add_event(
uint32_t event_id, uint32_t event_id,
uint16_t flags, uint16_t flags,
uint8_t floor, uint8_t floor,
uint16_t section, uint16_t section,
uint16_t wave_number, uint16_t wave_number,
uint32_t action_stream_offset); uint32_t action_stream_offset);
std::vector<Event*> get_events(uint8_t floor, uint32_t event_id); 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; 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); void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
struct DATSectionsForFloor { struct DATSectionsForFloor {
uint32_t objects = 0xFFFFFFFF; uint32_t objects = 0xFFFFFFFF;
uint32_t enemies = 0xFFFFFFFF; uint32_t enemies = 0xFFFFFFFF;
uint32_t wave_events = 0xFFFFFFFF; uint32_t wave_events = 0xFFFFFFFF;
uint32_t random_enemy_locations = 0xFFFFFFFF; uint32_t random_enemy_locations = 0xFFFFFFFF;
uint32_t random_enemy_definitions = 0xFFFFFFFF; uint32_t random_enemy_definitions = 0xFFFFFFFF;
}; };
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size); static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
void add_entities_from_quest_data( void add_entities_from_quest_data(
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t event, uint8_t event,
const void* data, const void* data,
size_t size, size_t size,
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES); std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
const Enemy& find_enemy(uint8_t floor, EnemyType type) const; const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
Enemy& find_enemy(uint8_t floor, EnemyType type); 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<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<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, uint16_t section, uint16_t wave_number);
std::vector<Event*> get_events(uint8_t floor); 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_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_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_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); static std::string disassemble_quest_data(const void* data, size_t size);
PrefixedLogger log; PrefixedLogger log;
Version version; Version version;
uint32_t rare_seed; uint32_t rare_seed;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt; std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::vector<Object> objects; std::vector<Object> objects;
std::vector<Enemy> enemies; std::vector<Enemy> enemies;
std::vector<uint16_t> enemy_set_flags; std::vector<uint16_t> enemy_set_flags;
std::vector<size_t> rare_enemy_indexes; std::vector<size_t> rare_enemy_indexes;
std::vector<Event> events; std::vector<Event> events;
std::string event_action_stream; std::string event_action_stream;
std::multimap<uint64_t, size_t> floor_and_event_id_to_index; 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_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_enemy_index;
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index; std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
}; };
class SetDataTableBase { class SetDataTableBase {
public: public:
virtual ~SetDataTableBase() = default; virtual ~SetDataTableBase() = default;
parray<le_uint32_t, 0x20> generate_variations( parray<le_uint32_t, 0x20> generate_variations(
Episode episode, Episode episode,
bool is_solo, bool is_solo,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const; 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_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; 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 { enum class FilenameType {
OBJECTS = 0, OBJECTS = 0,
ENEMIES, ENEMIES,
EVENTS, EVENTS,
}; };
virtual std::string map_filename_for_variation( virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const = 0; 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( std::vector<std::string> map_filenames_for_variations(
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, FilenameType type) const; 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; uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
protected: protected:
explicit SetDataTableBase(Version version); explicit SetDataTableBase(Version version);
Version version; Version version;
}; };
class SetDataTable : public SetDataTableBase { class SetDataTable : public SetDataTableBase {
public: public:
struct SetEntry { struct SetEntry {
std::string object_list_basename; std::string object_list_basename;
std::string enemy_and_event_list_basename; std::string enemy_and_event_list_basename;
std::string area_setup_filename; std::string area_setup_filename;
}; };
SetDataTable(Version version, const std::string& data); SetDataTable(Version version, const std::string& data);
virtual ~SetDataTable() = default; 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_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::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( virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const; uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
std::string str() const; std::string str() const;
private: private:
template <bool IsBigEndian> template <bool IsBigEndian>
void load_table_t(const std::string& data); void load_table_t(const std::string& data);
// Indexes are [floor][variation1][variation2] // Indexes are [floor][variation1][variation2]
// floor is cumulative per episode, so Ep2 starts at floor=18. // floor is cumulative per episode, so Ep2 starts at floor=18.
std::vector<std::vector<std::vector<SetEntry>>> entries; std::vector<std::vector<std::vector<SetEntry>>> entries;
}; };
class SetDataTableDCNTE : public SetDataTableBase { class SetDataTableDCNTE : public SetDataTableBase {
public: public:
SetDataTableDCNTE(); SetDataTableDCNTE();
virtual ~SetDataTableDCNTE() = default; 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_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::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( virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const; uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private: private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES; static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
}; };
class SetDataTableDC112000 : public SetDataTableBase { class SetDataTableDC112000 : public SetDataTableBase {
public: public:
SetDataTableDC112000(); SetDataTableDC112000();
virtual ~SetDataTableDC112000() = default; 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_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::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( virtual std::string map_filename_for_variation(
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const; uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
private: private:
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES; static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
}; };
void generate_variations_deprecated( void generate_variations_deprecated(
parray<le_uint32_t, 0x20>& variations, parray<le_uint32_t, 0x20>& variations,
std::shared_ptr<PSOLFGEncryption> random, std::shared_ptr<PSOLFGEncryption> random,
Version version, Version version,
Episode episode, Episode episode,
bool is_solo); bool is_solo);
parray<le_uint32_t, 0x20> variation_maxes_deprecated(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); 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( 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); 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( std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
const parray<le_uint32_t, 0x20>& variations, const parray<le_uint32_t, 0x20>& variations,
Version version, Version version,
Episode episode, Episode episode,
GameMode mode, GameMode mode,
bool is_enemies); bool is_enemies);
+149 -149
View File
@@ -1,149 +1,149 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
// Note: These aren't enums because neither enum nor enum class does what we // 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 // 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 // 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 // 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. // casting values all over the place, so we can't use enum classes either.
namespace MenuID { namespace MenuID {
constexpr uint32_t MAIN = 0x11000011; constexpr uint32_t MAIN = 0x11000011;
constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111; constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111;
constexpr uint32_t INFORMATION = 0x22000022; constexpr uint32_t INFORMATION = 0x22000022;
constexpr uint32_t LOBBY = 0x33000033; constexpr uint32_t LOBBY = 0x33000033;
constexpr uint32_t GAME = 0x44000044; constexpr uint32_t GAME = 0x44000044;
constexpr uint32_t QUEST_EP1 = 0x55010155; constexpr uint32_t QUEST_EP1 = 0x55010155;
constexpr uint32_t QUEST_EP2 = 0x55020255; constexpr uint32_t QUEST_EP2 = 0x55020255;
// See the decsription of the A2 command in CommandFormats.hh for why these // See the decsription of the A2 command in CommandFormats.hh for why these
// menu IDs don't fit the rest of the pattern. // menu IDs don't fit the rest of the pattern.
constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001; constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001;
constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002; constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002;
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077; constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
constexpr uint32_t PROGRAMS = 0x88000088; constexpr uint32_t PROGRAMS = 0x88000088;
constexpr uint32_t PATCHES = 0x99000099; constexpr uint32_t PATCHES = 0x99000099;
constexpr uint32_t PATCH_SWITCHES = 0x99010199; constexpr uint32_t PATCH_SWITCHES = 0x99010199;
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA; constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
constexpr uint32_t TOURNAMENTS = 0xBB0000BB; constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB; constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC; constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC;
} // namespace MenuID } // namespace MenuID
namespace MainMenuItemID { namespace MainMenuItemID {
constexpr uint32_t GO_TO_LOBBY = 0x11222211; constexpr uint32_t GO_TO_LOBBY = 0x11222211;
constexpr uint32_t INFORMATION = 0x11333311; constexpr uint32_t INFORMATION = 0x11333311;
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411; constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511; constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
constexpr uint32_t PATCHES = 0x11666611; constexpr uint32_t PATCHES = 0x11666611;
constexpr uint32_t PATCH_SWITCHES = 0x11676711; constexpr uint32_t PATCH_SWITCHES = 0x11676711;
constexpr uint32_t PROGRAMS = 0x11777711; constexpr uint32_t PROGRAMS = 0x11777711;
constexpr uint32_t DISCONNECT = 0x11888811; constexpr uint32_t DISCONNECT = 0x11888811;
constexpr uint32_t CLEAR_LICENSE = 0x11999911; constexpr uint32_t CLEAR_LICENSE = 0x11999911;
} // namespace MainMenuItemID } // namespace MainMenuItemID
namespace ClearLicenseConfirmationMenuItemID { namespace ClearLicenseConfirmationMenuItemID {
constexpr uint32_t CANCEL = 0x01010101; constexpr uint32_t CANCEL = 0x01010101;
constexpr uint32_t CLEAR_LICENSE = 0x02020202; constexpr uint32_t CLEAR_LICENSE = 0x02020202;
} // namespace ClearLicenseConfirmationMenuItemID } // namespace ClearLicenseConfirmationMenuItemID
namespace InformationMenuItemID { namespace InformationMenuItemID {
constexpr uint32_t GO_BACK = 0x22FFFF22; constexpr uint32_t GO_BACK = 0x22FFFF22;
} }
namespace ProxyDestinationsMenuItemID { namespace ProxyDestinationsMenuItemID {
constexpr uint32_t GO_BACK = 0x77FFFF77; constexpr uint32_t GO_BACK = 0x77FFFF77;
constexpr uint32_t OPTIONS = 0x77EEEE77; constexpr uint32_t OPTIONS = 0x77EEEE77;
} // namespace ProxyDestinationsMenuItemID } // namespace ProxyDestinationsMenuItemID
namespace ProgramsMenuItemID { namespace ProgramsMenuItemID {
constexpr uint32_t GO_BACK = 0x88FFFF88; constexpr uint32_t GO_BACK = 0x88FFFF88;
} }
namespace PatchesMenuItemID { namespace PatchesMenuItemID {
constexpr uint32_t GO_BACK = 0x99FFFF99; constexpr uint32_t GO_BACK = 0x99FFFF99;
} }
namespace ProxyOptionsMenuItemID { namespace ProxyOptionsMenuItemID {
constexpr uint32_t GO_BACK = 0xAAFFFFAA; constexpr uint32_t GO_BACK = 0xAAFFFFAA;
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA; constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA; constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA; constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA; constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
constexpr uint32_t INFINITE_HP = 0xAA0505AA; constexpr uint32_t INFINITE_HP = 0xAA0505AA;
constexpr uint32_t INFINITE_TP = 0xAA0606AA; constexpr uint32_t INFINITE_TP = 0xAA0606AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA; constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA; constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA; constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA; constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
constexpr uint32_t RED_NAME = 0xAA0B0BAA; constexpr uint32_t RED_NAME = 0xAA0B0BAA;
constexpr uint32_t BLANK_NAME = 0xAA0C0CAA; constexpr uint32_t BLANK_NAME = 0xAA0C0CAA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA; constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA;
constexpr uint32_t SKIP_CARD = 0xAA0E0EAA; constexpr uint32_t SKIP_CARD = 0xAA0E0EAA;
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA; constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA;
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA; constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA;
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA; constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA;
} // namespace ProxyOptionsMenuItemID } // namespace ProxyOptionsMenuItemID
namespace TeamRewardMenuItemID { namespace TeamRewardMenuItemID {
constexpr uint32_t TEAM_FLAG = 0x01010101; constexpr uint32_t TEAM_FLAG = 0x01010101;
constexpr uint32_t DRESSING_ROOM = 0x02020202; constexpr uint32_t DRESSING_ROOM = 0x02020202;
constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303; constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303;
constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404; constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404;
constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505; constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505;
constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606; constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606;
// constexpr uint32_t POINT_OF_DISASTER = ...; // constexpr uint32_t POINT_OF_DISASTER = ...;
// constexpr uint32_t TOYS_TWILIGHT = ...; // constexpr uint32_t TOYS_TWILIGHT = ...;
// constexpr uint32_t COMMANDER_BLADE = ...; // constexpr uint32_t COMMANDER_BLADE = ...;
// constexpr uint32_t UNION_GUARD = ...; // constexpr uint32_t UNION_GUARD = ...;
// constexpr uint32_t TEAM_POINTS_500 = ...; // constexpr uint32_t TEAM_POINTS_500 = ...;
// constexpr uint32_t TEAM_POINTS_1000 = ...; // constexpr uint32_t TEAM_POINTS_1000 = ...;
// constexpr uint32_t TEAM_POINTS_5000 = ...; // constexpr uint32_t TEAM_POINTS_5000 = ...;
// constexpr uint32_t TEAM_POINTS_10000 = ...; // constexpr uint32_t TEAM_POINTS_10000 = ...;
} // namespace TeamRewardMenuItemID } // namespace TeamRewardMenuItemID
struct MenuItem { struct MenuItem {
enum Flag { enum Flag {
// For menu items to be visible on DC NTE, they must not have either of the // 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.) // following two flags. (The INVISIBLE_ON_GC_NTE flag behaves similarly.)
INVISIBLE_ON_DC_PROTOS = 0x001, INVISIBLE_ON_DC_PROTOS = 0x001,
INVISIBLE_ON_DC = 0x002, INVISIBLE_ON_DC = 0x002,
INVISIBLE_ON_PC_NTE = 0x004, INVISIBLE_ON_PC_NTE = 0x004,
INVISIBLE_ON_PC = 0x008, INVISIBLE_ON_PC = 0x008,
INVISIBLE_ON_GC_NTE = 0x010, INVISIBLE_ON_GC_NTE = 0x010,
INVISIBLE_ON_GC = 0x020, INVISIBLE_ON_GC = 0x020,
INVISIBLE_ON_XB = 0x040, INVISIBLE_ON_XB = 0x040,
INVISIBLE_ON_BB = 0x080, INVISIBLE_ON_BB = 0x080,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB, 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, 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, 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, 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, BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
REQUIRES_MESSAGE_BOXES = 0x100, REQUIRES_MESSAGE_BOXES = 0x100,
REQUIRES_SEND_FUNCTION_CALL = 0x200, REQUIRES_SEND_FUNCTION_CALL = 0x200,
REQUIRES_SAVE_DISABLED = 0x400, REQUIRES_SAVE_DISABLED = 0x400,
INVISIBLE_IN_INFO_MENU = 0x800, INVISIBLE_IN_INFO_MENU = 0x800,
}; };
uint32_t item_id; uint32_t item_id;
std::string name; std::string name;
std::string description; std::string description;
std::function<std::string()> get_description; std::function<std::string()> get_description;
uint32_t flags; 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, 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); MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
}; };
struct Menu { struct Menu {
uint32_t menu_id; uint32_t menu_id;
std::string name; std::string name;
std::vector<MenuItem> items; std::vector<MenuItem> items;
Menu() = delete; Menu() = delete;
Menu(uint32_t menu_id, const std::string& name); Menu(uint32_t menu_id, const std::string& name);
}; };
+104 -104
View File
@@ -1,104 +1,104 @@
#include "NetworkAddresses.hh" #include "NetworkAddresses.hh"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h> #include <errno.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <stdexcept> #include <stdexcept>
using namespace std; using namespace std;
uint32_t resolve_address(const char* address) { uint32_t resolve_address(const char* address) {
struct addrinfo* res0; struct addrinfo* res0;
if (getaddrinfo(address, nullptr, nullptr, &res0)) { if (getaddrinfo(address, nullptr, nullptr, &res0)) {
auto e = string_for_error(errno); auto e = string_for_error(errno);
throw runtime_error(string_printf( throw runtime_error(string_printf(
"can\'t resolve hostname %s: %s", address, e.c_str())); "can\'t resolve hostname %s: %s", address, e.c_str()));
} }
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo); std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
struct addrinfo* res4 = nullptr; struct addrinfo* res4 = nullptr;
for (struct addrinfo* res = res0; res; res = res->ai_next) { for (struct addrinfo* res = res0; res; res = res->ai_next) {
if (res->ai_family == AF_INET) { if (res->ai_family == AF_INET) {
res4 = res; res4 = res;
} }
} }
if (!res4) { if (!res4) {
throw runtime_error(string_printf( throw runtime_error(string_printf(
"can\'t resolve hostname %s: no usable data", address)); "can\'t resolve hostname %s: no usable data", address));
} }
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr; struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
return ntohl(res_sin->sin_addr.s_addr); return ntohl(res_sin->sin_addr.s_addr);
} }
map<string, uint32_t> get_local_addresses() { map<string, uint32_t> get_local_addresses() {
struct ifaddrs* ifa_raw; struct ifaddrs* ifa_raw;
if (getifaddrs(&ifa_raw)) { if (getifaddrs(&ifa_raw)) {
auto s = string_for_error(errno); auto s = string_for_error(errno);
throw runtime_error(string_printf("failed to get interface addresses: %s", s.c_str())); 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); unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
map<string, uint32_t> ret; map<string, uint32_t> ret;
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) { for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
if (!i->ifa_addr) { if (!i->ifa_addr) {
continue; continue;
} }
auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr); auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
if (sin->sin_family != AF_INET) { if (sin->sin_family != AF_INET) {
continue; continue;
} }
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr)); ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
} }
return ret; return ret;
} }
bool is_local_address(uint32_t addr) { bool is_local_address(uint32_t addr) {
uint8_t net = (addr >> 24) & 0xFF; uint8_t net = (addr >> 24) & 0xFF;
return ((net == 127) || (net == 172) || (net == 10) || (net == 192)); return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
} }
bool is_local_address(const sockaddr_storage& daddr) { bool is_local_address(const sockaddr_storage& daddr) {
if (daddr.ss_family != AF_INET) { if (daddr.ss_family != AF_INET) {
return false; return false;
} }
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr); const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
return is_local_address(ntohl(sin->sin_addr.s_addr)); return is_local_address(ntohl(sin->sin_addr.s_addr));
} }
string string_for_address(uint32_t address) { string string_for_address(uint32_t address) {
return string_printf("%hhu.%hhu.%hhu.%hhu", return string_printf("%hhu.%hhu.%hhu.%hhu",
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16), static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address)); static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
} }
uint32_t address_for_string(const char* address) { uint32_t address_for_string(const char* address) {
return ntohl(inet_addr(address)); return ntohl(inet_addr(address));
} }
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port) { 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 // It seems the address part of the number is fixed-width, but the port is
// not. Why did they do it this way? // not. Why did they do it this way?
if (port & 0xF000) { if (port & 0xF000) {
return (static_cast<uint64_t>(addr) << 16) | port; return (static_cast<uint64_t>(addr) << 16) | port;
} else if (port & 0x0F00) { } else if (port & 0x0F00) {
return (static_cast<uint64_t>(addr) << 12) | port; return (static_cast<uint64_t>(addr) << 12) | port;
} else if (port & 0x00F0) { } else if (port & 0x00F0) {
return (static_cast<uint64_t>(addr) << 8) | port; return (static_cast<uint64_t>(addr) << 8) | port;
} else { } else {
return (static_cast<uint64_t>(addr) << 4) | port; return (static_cast<uint64_t>(addr) << 4) | port;
} }
} }
+21 -21
View File
@@ -1,21 +1,21 @@
#pragma once #pragma once
#include <netinet/in.h> #include <netinet/in.h>
#include <stdint.h> #include <stdint.h>
#include <map> #include <map>
#include <string> #include <string>
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because // PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
// it's easier // it's easier
uint32_t resolve_address(const char* address); uint32_t resolve_address(const char* address);
std::map<std::string, uint32_t> get_local_addresses(); std::map<std::string, uint32_t> get_local_addresses();
uint32_t get_connected_address(int fd); uint32_t get_connected_address(int fd);
bool is_local_address(uint32_t daddr); bool is_local_address(uint32_t daddr);
bool is_local_address(const sockaddr_storage& daddr); bool is_local_address(const sockaddr_storage& daddr);
std::string string_for_address(uint32_t address); std::string string_for_address(uint32_t address);
uint32_t address_for_string(const char* address); uint32_t address_for_string(const char* address);
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port); 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 #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <stddef.h> #include <stddef.h>
#include <memory> #include <memory>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Random.hh> #include <phosg/Random.hh>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Compression.hh" #include "Compression.hh"
#include "Text.hh" #include "Text.hh"
class PSOEncryption { class PSOEncryption {
public: public:
enum class Type { enum class Type {
V2 = 0, V2 = 0,
V3, V3,
BB, BB,
JSD0, JSD0,
}; };
virtual ~PSOEncryption() = default; virtual ~PSOEncryption() = default;
virtual void encrypt(void* data, size_t size, bool advance = true) = 0; virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
virtual void decrypt(void* data, size_t size, bool advance = true); virtual void decrypt(void* data, size_t size, bool advance = true);
inline void encrypt(std::string& data, bool advance = true) { inline void encrypt(std::string& data, bool advance = true) {
this->encrypt(data.data(), data.size(), advance); this->encrypt(data.data(), data.size(), advance);
} }
inline void decrypt(std::string& data, bool advance = true) { inline void decrypt(std::string& data, bool advance = true) {
this->decrypt(data.data(), data.size(), advance); this->decrypt(data.data(), data.size(), advance);
} }
virtual Type type() const = 0; virtual Type type() const = 0;
protected: protected:
PSOEncryption() = default; PSOEncryption() = default;
}; };
class PSOLFGEncryption : public PSOEncryption { class PSOLFGEncryption : public PSOEncryption {
public: public:
virtual void encrypt(void* data, size_t size, bool advance = true); 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_big_endian(void* data, size_t size, bool advance = true);
void encrypt_minus(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_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); void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
template <bool IsBigEndian> template <bool IsBigEndian>
void encrypt_t(void* data, size_t size, bool advance = true); void encrypt_t(void* data, size_t size, bool advance = true);
template <bool IsBigEndian> template <bool IsBigEndian>
void encrypt_minus_t(void* data, size_t size, bool advance = true); void encrypt_minus_t(void* data, size_t size, bool advance = true);
uint32_t next(bool advance = true); uint32_t next(bool advance = true);
inline uint32_t seed() const { inline uint32_t seed() const {
return this->initial_seed; return this->initial_seed;
} }
uint32_t absolute_offset() const { uint32_t absolute_offset() const {
return (this->cycles * this->end_offset) + this->offset; return (this->cycles * this->end_offset) + this->offset;
} }
protected: protected:
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset); PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
virtual void update_stream() = 0; virtual void update_stream() = 0;
std::vector<uint32_t> stream; std::vector<uint32_t> stream;
size_t offset; size_t offset;
size_t end_offset; size_t end_offset;
uint32_t initial_seed; uint32_t initial_seed;
size_t cycles; size_t cycles;
}; };
class PSOV2Encryption : public PSOLFGEncryption { class PSOV2Encryption : public PSOLFGEncryption {
public: public:
explicit PSOV2Encryption(uint32_t seed); explicit PSOV2Encryption(uint32_t seed);
virtual Type type() const; virtual Type type() const;
protected: protected:
virtual void update_stream(); virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 0x38; static constexpr size_t STREAM_LENGTH = 0x38;
}; };
class PSOV3Encryption : public PSOLFGEncryption { class PSOV3Encryption : public PSOLFGEncryption {
public: public:
explicit PSOV3Encryption(uint32_t key); explicit PSOV3Encryption(uint32_t key);
virtual Type type() const; virtual Type type() const;
protected: protected:
virtual void update_stream(); virtual void update_stream();
static constexpr size_t STREAM_LENGTH = 521; static constexpr size_t STREAM_LENGTH = 521;
}; };
class PSOBBEncryption : public PSOEncryption { class PSOBBEncryption : public PSOEncryption {
public: public:
enum Subtype : uint8_t { enum Subtype : uint8_t {
STANDARD = 0x00, STANDARD = 0x00,
MOCB1 = 0x01, MOCB1 = 0x01,
JSD1 = 0x02, JSD1 = 0x02,
TFS1 = 0x03, TFS1 = 0x03,
}; };
struct KeyFile { struct KeyFile {
// initial_keys are actually a stream of uint32_ts, but we treat them as // initial_keys are actually a stream of uint32_ts, but we treat them as
// bytes for code simplicity // bytes for code simplicity
union InitialKeys { union InitialKeys {
uint8_t jsd1_stream_offset; uint8_t jsd1_stream_offset;
parray<uint8_t, 0x48> as8; parray<uint8_t, 0x48> as8;
parray<le_uint32_t, 0x12> as32; parray<le_uint32_t, 0x12> as32;
InitialKeys() : as32() {} InitialKeys() : as32() {}
InitialKeys(const InitialKeys& other) : as32(other.as32) {} InitialKeys(const InitialKeys& other) : as32(other.as32) {}
} __packed_ws__(InitialKeys, 0x48); } __packed_ws__(InitialKeys, 0x48);
union PrivateKeys { union PrivateKeys {
parray<uint8_t, 0x1000> as8; parray<uint8_t, 0x1000> as8;
parray<le_uint32_t, 0x400> as32; parray<le_uint32_t, 0x400> as32;
PrivateKeys() : as32() {} PrivateKeys() : as32() {}
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {} PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
} __packed_ws__(PrivateKeys, 0x1000); } __packed_ws__(PrivateKeys, 0x1000);
InitialKeys initial_keys; InitialKeys initial_keys;
PrivateKeys private_keys; PrivateKeys private_keys;
// This field only really needs to be one byte, but annoyingly, some // This field only really needs to be one byte, but annoyingly, some
// compilers pad this structure to a longer alignment, presumably because // compilers pad this structure to a longer alignment, presumably because
// the unions above contain structures with 32-bit alignment. To prevent // the unions above contain structures with 32-bit alignment. To prevent
// this structure's size from not matching the .nsk files' sizes, we use // this structure's size from not matching the .nsk files' sizes, we use
// an unnecessarily large size for this field. // an unnecessarily large size for this field.
le_uint64_t subtype; le_uint64_t subtype;
} __packed_ws__(KeyFile, 0x1050); } __packed_ws__(KeyFile, 0x1050);
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size); PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true); virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const; virtual Type type() const;
protected: protected:
KeyFile state; KeyFile state;
void tfs1_scramble(uint32_t* out1, uint32_t* out2) const; void tfs1_scramble(uint32_t* out1, uint32_t* out2) const;
void apply_seed(const void* original_seed, size_t seed_size); void apply_seed(const void* original_seed, size_t seed_size);
}; };
// The following classes provide support for automatically detecting which type // The following classes provide support for automatically detecting which type
// of encryption a client is using based on their initial response to the server // of encryption a client is using based on their initial response to the server
class PSOV2OrV3DetectorEncryption : public PSOEncryption { class PSOV2OrV3DetectorEncryption : public PSOEncryption {
public: public:
PSOV2OrV3DetectorEncryption( PSOV2OrV3DetectorEncryption(
uint32_t key, uint32_t key,
const std::unordered_set<uint32_t>& v2_matches, const std::unordered_set<uint32_t>& v2_matches,
const std::unordered_set<uint32_t>& v3_matches); const std::unordered_set<uint32_t>& v3_matches);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const; virtual Type type() const;
protected: protected:
uint32_t key; uint32_t key;
const std::unordered_set<uint32_t>& v2_matches; const std::unordered_set<uint32_t>& v2_matches;
const std::unordered_set<uint32_t>& v3_matches; const std::unordered_set<uint32_t>& v3_matches;
std::unique_ptr<PSOEncryption> active_crypt; std::unique_ptr<PSOEncryption> active_crypt;
}; };
class PSOV2OrV3ImitatorEncryption : public PSOEncryption { class PSOV2OrV3ImitatorEncryption : public PSOEncryption {
public: public:
PSOV2OrV3ImitatorEncryption( PSOV2OrV3ImitatorEncryption(
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt); uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual Type type() const; virtual Type type() const;
protected: protected:
uint32_t key; uint32_t key;
std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt; std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt;
std::shared_ptr<PSOEncryption> active_crypt; std::shared_ptr<PSOEncryption> active_crypt;
}; };
// The following classes provide support for multiple PSOBB private keys, and // 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 ability to automatically detect which key the client is using based on
// the first 8 bytes they send // the first 8 bytes they send
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption { class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
public: public:
PSOBBMultiKeyDetectorEncryption( PSOBBMultiKeyDetectorEncryption(
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys, const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const std::unordered_set<std::string>& expected_first_data, const std::unordered_set<std::string>& expected_first_data,
const void* seed, const void* seed,
size_t seed_size); size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(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 { inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
return this->active_key; return this->active_key;
} }
inline const std::string& get_seed() const { inline const std::string& get_seed() const {
return this->seed; return this->seed;
} }
virtual Type type() const; virtual Type type() const;
protected: protected:
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys; std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key; std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
std::shared_ptr<PSOBBEncryption> active_crypt; std::shared_ptr<PSOBBEncryption> active_crypt;
const std::unordered_set<std::string>& expected_first_data; const std::unordered_set<std::string>& expected_first_data;
std::string seed; std::string seed;
}; };
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption { class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
public: public:
PSOBBMultiKeyImitatorEncryption( PSOBBMultiKeyImitatorEncryption(
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt, std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
const void* seed, const void* seed,
size_t seed_size, size_t seed_size,
bool jsd1_use_detector_seed); bool jsd1_use_detector_seed);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true); virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const; virtual Type type() const;
protected: protected:
std::shared_ptr<PSOBBEncryption> ensure_crypt(); std::shared_ptr<PSOBBEncryption> ensure_crypt();
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt; std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<PSOBBEncryption> active_crypt; std::shared_ptr<PSOBBEncryption> active_crypt;
std::string seed; std::string seed;
bool jsd1_use_detector_seed; bool jsd1_use_detector_seed;
}; };
class JSD0Encryption : public PSOEncryption { class JSD0Encryption : public PSOEncryption {
public: public:
JSD0Encryption(const void* seed, size_t seed_size); JSD0Encryption(const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true); virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true); virtual void decrypt(void* data, size_t size, bool advance = true);
virtual Type type() const = 0; virtual Type type() const = 0;
private: private:
uint8_t key; uint8_t key;
}; };
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis); void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
uint32_t encrypt_challenge_time(uint16_t value); uint32_t encrypt_challenge_time(uint16_t value);
uint16_t decrypt_challenge_time(uint32_t value); uint16_t decrypt_challenge_time(uint32_t value);
template <bool IsBigEndian> template <bool IsBigEndian>
class ChallengeTimeT { class ChallengeTimeT {
private: private:
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type; using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T value; U32T value;
public: public:
ChallengeTimeT() = default; ChallengeTimeT() = default;
ChallengeTimeT(uint16_t v) { ChallengeTimeT(uint16_t v) {
this->encode(v); this->encode(v);
} }
ChallengeTimeT(const ChallengeTimeT& other) = default; ChallengeTimeT(const ChallengeTimeT& other) = default;
ChallengeTimeT(ChallengeTimeT&& other) = default; ChallengeTimeT(ChallengeTimeT&& other) = default;
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default; ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default; ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
bool has_value() const { bool has_value() const {
return this->value != 0; return this->value != 0;
} }
uint32_t load_raw() const { uint32_t load_raw() const {
return this->value; return this->value;
} }
void store_raw(uint32_t value) { void store_raw(uint32_t value) {
this->value = value; this->value = value;
} }
uint16_t decode() const { uint16_t decode() const {
return decrypt_challenge_time(this->value); return decrypt_challenge_time(this->value);
} }
void encode(uint16_t v) { void encode(uint16_t v) {
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v); this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
} }
operator ChallengeTimeT<!IsBigEndian>() const { operator ChallengeTimeT<!IsBigEndian>() const {
ChallengeTimeT<!IsBigEndian> ret; ChallengeTimeT<!IsBigEndian> ret;
ret.store_raw(this->value); ret.store_raw(this->value);
return ret; return ret;
} }
} __packed__; } __packed__;
using ChallengeTime = ChallengeTimeT<false>; using ChallengeTime = ChallengeTimeT<false>;
using ChallengeTimeBE = ChallengeTimeT<true>; using ChallengeTimeBE = ChallengeTimeT<true>;
check_struct_size(ChallengeTime, 4); check_struct_size(ChallengeTime, 4);
check_struct_size(ChallengeTimeBE, 4); check_struct_size(ChallengeTimeBE, 4);
std::string decrypt_v2_registry_value(const void* data, size_t size); std::string decrypt_v2_registry_value(const void* data, size_t size);
struct DecryptedPR2 { struct DecryptedPR2 {
std::string compressed_data; std::string compressed_data;
size_t decompressed_size; size_t decompressed_size;
}; };
template <bool IsBigEndian> template <bool IsBigEndian>
DecryptedPR2 decrypt_pr2_data(const std::string& data) { DecryptedPR2 decrypt_pr2_data(const std::string& data) {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>; using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
if (data.size() < 8) { if (data.size() < 8) {
throw std::runtime_error("not enough data for PR2 header"); throw std::runtime_error("not enough data for PR2 header");
} }
StringReader r(data); StringReader r(data);
DecryptedPR2 ret = { DecryptedPR2 ret = {
.compressed_data = data.substr(8), .compressed_data = data.substr(8),
.decompressed_size = r.get<U32T>()}; .decompressed_size = r.get<U32T>()};
PSOV2Encryption crypt(r.get<U32T>()); PSOV2Encryption crypt(r.get<U32T>());
if (IsBigEndian) { if (IsBigEndian) {
crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size()); crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size());
} else { } else {
crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size()); crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size());
} }
return ret; return ret;
} }
template <bool IsBigEndian> template <bool IsBigEndian>
std::string decrypt_and_decompress_pr2_data(const std::string& data) { std::string decrypt_and_decompress_pr2_data(const std::string& data) {
auto decrypted = decrypt_pr2_data<IsBigEndian>(data); auto decrypted = decrypt_pr2_data<IsBigEndian>(data);
std::string decompressed = prs_decompress(decrypted.compressed_data); std::string decompressed = prs_decompress(decrypted.compressed_data);
if (decompressed.size() != decrypted.decompressed_size) { if (decompressed.size() != decrypted.decompressed_size) {
throw std::runtime_error("decompressed size does not match expected size"); throw std::runtime_error("decompressed size does not match expected size");
} }
return decompressed; return decompressed;
} }
template <bool IsBigEndian> template <bool IsBigEndian>
std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, uint32_t seed) { 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>; using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
StringWriter w; StringWriter w;
w.put<U32T>(decompressed_size); w.put<U32T>(decompressed_size);
w.put<U32T>(seed); w.put<U32T>(seed);
w.write(data); w.write(data);
std::string ret = std::move(w.str()); std::string ret = std::move(w.str());
PSOV2Encryption crypt(seed); PSOV2Encryption crypt(seed);
if (IsBigEndian) { if (IsBigEndian) {
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8); crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
} else { } else {
crypt.decrypt(ret.data() + 8, ret.size() - 8); crypt.decrypt(ret.data() + 8, ret.size() - 8);
} }
return ret; return ret;
} }
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) { inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
return random_crypt ? random_crypt->next() : random_object<uint32_t>(); return random_crypt ? random_crypt->next() : random_object<uint32_t>();
} }
+270 -270
View File
@@ -1,270 +1,270 @@
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include <event2/buffer.h> #include <event2/buffer.h>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <stdexcept> #include <stdexcept>
#include "Text.hh" #include "Text.hh"
using namespace std; using namespace std;
extern bool use_terminal_colors; extern bool use_terminal_colors;
PSOCommandHeader::PSOCommandHeader() { PSOCommandHeader::PSOCommandHeader() {
this->bb.size = 0; this->bb.size = 0;
this->bb.command = 0; this->bb.command = 0;
this->bb.flag = 0; this->bb.flag = 0;
} }
uint16_t PSOCommandHeader::command(Version version) const { uint16_t PSOCommandHeader::command(Version version) const {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return this->pc.command; return this->pc.command;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
return this->dc.command; return this->dc.command;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return this->gc.command; return this->gc.command;
case Version::XB_V3: case Version::XB_V3:
return this->xb.command; return this->xb.command;
case Version::BB_V4: case Version::BB_V4:
return this->bb.command; return this->bb.command;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
void PSOCommandHeader::set_command(Version version, uint16_t command) { void PSOCommandHeader::set_command(Version version, uint16_t command) {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
this->pc.command = command; this->pc.command = command;
break; break;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
this->dc.command = command; this->dc.command = command;
break; break;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
this->gc.command = command; this->gc.command = command;
break; break;
case Version::XB_V3: case Version::XB_V3:
this->xb.command = command; this->xb.command = command;
break; break;
case Version::BB_V4: case Version::BB_V4:
this->bb.command = command; this->bb.command = command;
break; break;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
uint16_t PSOCommandHeader::size(Version version) const { uint16_t PSOCommandHeader::size(Version version) const {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return this->pc.size; return this->pc.size;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
return this->dc.size; return this->dc.size;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return this->gc.size; return this->gc.size;
case Version::XB_V3: case Version::XB_V3:
return this->xb.size; return this->xb.size;
case Version::BB_V4: case Version::BB_V4:
return this->bb.size; return this->bb.size;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
void PSOCommandHeader::set_size(Version version, uint32_t size) { void PSOCommandHeader::set_size(Version version, uint32_t size) {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
this->pc.size = size; this->pc.size = size;
break; break;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
this->dc.size = size; this->dc.size = size;
break; break;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
this->gc.size = size; this->gc.size = size;
break; break;
case Version::XB_V3: case Version::XB_V3:
this->xb.size = size; this->xb.size = size;
break; break;
case Version::BB_V4: case Version::BB_V4:
this->bb.size = size; this->bb.size = size;
break; break;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
uint32_t PSOCommandHeader::flag(Version version) const { uint32_t PSOCommandHeader::flag(Version version) const {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return this->pc.flag; return this->pc.flag;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
return this->dc.flag; return this->dc.flag;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return this->gc.flag; return this->gc.flag;
case Version::XB_V3: case Version::XB_V3:
return this->xb.flag; return this->xb.flag;
case Version::BB_V4: case Version::BB_V4:
return this->bb.flag; return this->bb.flag;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
void PSOCommandHeader::set_flag(Version version, uint32_t flag) { void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
this->pc.flag = flag; this->pc.flag = flag;
break; break;
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
this->dc.flag = flag; this->dc.flag = flag;
break; break;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
this->gc.flag = flag; this->gc.flag = flag;
break; break;
case Version::XB_V3: case Version::XB_V3:
this->xb.flag = flag; this->xb.flag = flag;
break; break;
case Version::BB_V4: case Version::BB_V4:
this->bb.flag = flag; this->bb.flag = flag;
break; break;
default: default:
throw logic_error("unknown game version"); throw logic_error("unknown game version");
} }
} }
void check_size_v(size_t size, size_t min_size, size_t max_size) { void check_size_v(size_t size, size_t min_size, size_t max_size) {
if (size < min_size) { if (size < min_size) {
throw std::runtime_error(string_printf( throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)", "command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size)); min_size, size));
} }
if (max_size < min_size) { if (max_size < min_size) {
max_size = min_size; max_size = min_size;
} }
if (size > max_size) { if (size > max_size) {
throw std::runtime_error(string_printf( throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)", "command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size)); max_size, size));
} }
} }
std::string prepend_command_header( std::string prepend_command_header(
Version version, Version version,
bool encryption_enabled, bool encryption_enabled,
uint16_t cmd, uint16_t cmd,
uint32_t flag, uint32_t flag,
const std::string& data) { const std::string& data) {
StringWriter ret; StringWriter ret;
switch (version) { switch (version) {
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
case Version::XB_V3: { case Version::XB_V3: {
PSOCommandHeaderDCV3 header; PSOCommandHeaderDCV3 header;
if (encryption_enabled) { if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3; header.size = (sizeof(header) + data.size() + 3) & ~3;
} else { } else {
header.size = (sizeof(header) + data.size()); header.size = (sizeof(header) + data.size());
} }
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
ret.put(header); ret.put(header);
break; break;
} }
case Version::PC_PATCH: case Version::PC_PATCH:
case Version::BB_PATCH: case Version::BB_PATCH:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: { case Version::PC_V2: {
PSOCommandHeaderPC header; PSOCommandHeaderPC header;
if (encryption_enabled) { if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3; header.size = (sizeof(header) + data.size() + 3) & ~3;
} else { } else {
header.size = (sizeof(header) + data.size()); header.size = (sizeof(header) + data.size());
} }
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
ret.put(header); ret.put(header);
break; break;
} }
case Version::BB_V4: { case Version::BB_V4: {
PSOCommandHeaderBB header; PSOCommandHeaderBB header;
if (encryption_enabled) { if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3; header.size = (sizeof(header) + data.size() + 3) & ~3;
} else { } else {
header.size = (sizeof(header) + data.size()); header.size = (sizeof(header) + data.size());
} }
header.command = cmd; header.command = cmd;
header.flag = flag; header.flag = flag;
ret.put(header); ret.put(header);
break; break;
} }
default: default:
throw logic_error("unimplemented game version in prepend_command_header"); throw logic_error("unimplemented game version in prepend_command_header");
} }
ret.write(data); ret.write(data);
return std::move(ret.str()); return std::move(ret.str());
} }
+130 -130
View File
@@ -1,130 +1,130 @@
#pragma once #pragma once
#include <event2/bufferevent.h> #include <event2/bufferevent.h>
#include <inttypes.h> #include <inttypes.h>
#include <functional> #include <functional>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "Version.hh" #include "Version.hh"
struct PSOCommandHeaderPC { struct PSOCommandHeaderPC {
le_uint16_t size; le_uint16_t size;
uint8_t command; uint8_t command;
uint8_t flag; uint8_t flag;
} __packed_ws__(PSOCommandHeaderPC, 4); } __packed_ws__(PSOCommandHeaderPC, 4);
struct PSOCommandHeaderDCV3 { struct PSOCommandHeaderDCV3 {
uint8_t command; uint8_t command;
uint8_t flag; uint8_t flag;
le_uint16_t size; le_uint16_t size;
} __packed_ws__(PSOCommandHeaderDCV3, 4); } __packed_ws__(PSOCommandHeaderDCV3, 4);
struct PSOCommandHeaderBB { struct PSOCommandHeaderBB {
le_uint16_t size; le_uint16_t size;
le_uint16_t command; le_uint16_t command;
le_uint32_t flag; le_uint32_t flag;
} __packed_ws__(PSOCommandHeaderBB, 8); } __packed_ws__(PSOCommandHeaderBB, 8);
union PSOCommandHeader { union PSOCommandHeader {
PSOCommandHeaderDCV3 dc; PSOCommandHeaderDCV3 dc;
PSOCommandHeaderPC pc; PSOCommandHeaderPC pc;
PSOCommandHeaderDCV3 gc; PSOCommandHeaderDCV3 gc;
PSOCommandHeaderDCV3 xb; PSOCommandHeaderDCV3 xb;
PSOCommandHeaderBB bb; PSOCommandHeaderBB bb;
uint16_t command(Version version) const; uint16_t command(Version version) const;
void set_command(Version version, uint16_t command); void set_command(Version version, uint16_t command);
uint16_t size(Version version) const; uint16_t size(Version version) const;
void set_size(Version version, uint32_t size); void set_size(Version version, uint32_t size);
uint32_t flag(Version version) const; uint32_t flag(Version version) const;
void set_flag(Version version, uint32_t flag); void set_flag(Version version, uint32_t flag);
static inline size_t header_size(Version version) { static inline size_t header_size(Version version) {
return (version == Version::BB_V4) ? 8 : 4; return (version == Version::BB_V4) ? 8 : 4;
} }
PSOCommandHeader(); PSOCommandHeader();
} __packed_ws__(PSOCommandHeader, 8); } __packed_ws__(PSOCommandHeader, 8);
// This function is used in a lot of places to check received command sizes and // This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type // cast them to the appropriate type
template <typename RetT, typename PtrT> template <typename RetT, typename PtrT>
RetT& check_size_generic( RetT& check_size_generic(
PtrT data, PtrT data,
size_t size, size_t size,
size_t min_size, size_t min_size,
size_t max_size) { size_t max_size) {
if (size < min_size) { if (size < min_size) {
throw std::runtime_error(string_printf( throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)", "command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size)); min_size, size));
} }
if (size > max_size) { if (size > max_size) {
throw std::runtime_error(string_printf( throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)", "command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size)); max_size, size));
} }
return *reinterpret_cast<RetT*>(data); return *reinterpret_cast<RetT*>(data);
} }
template <typename T> template <typename T>
const T& check_size_t(const std::string& data, size_t min_size, size_t max_size) { 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); return check_size_generic<const T, const void*>(data.data(), data.size(), min_size, max_size);
} }
template <typename T> template <typename T>
const T& check_size_t(const std::string& data, size_t max_size) { 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); return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), max_size);
} }
template <typename T> template <typename T>
const T& check_size_t(const std::string& data) { 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)); return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), sizeof(T));
} }
template <typename T> template <typename T>
T& check_size_t(std::string& data, size_t min_size, size_t max_size) { 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); return check_size_generic<T, void*>(data.data(), data.size(), min_size, max_size);
} }
template <typename T> template <typename T>
T& check_size_t(std::string& data, size_t max_size) { 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); return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), max_size);
} }
template <typename T> template <typename T>
T& check_size_t(std::string& data) { T& check_size_t(std::string& data) {
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T)); return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T));
} }
template <typename T> template <typename T>
const T& check_size_t(const void* data, size_t size, size_t min_size, size_t max_size) { 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); return check_size_generic<const T, const void*>(data, size, min_size, max_size);
} }
template <typename T> template <typename T>
const T& check_size_t(const void* data, size_t size, size_t max_size) { 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); return check_size_generic<const T, const void*>(data, size, sizeof(T), max_size);
} }
template <typename T> template <typename T>
const T& check_size_t(const void* data, size_t size) { 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)); return check_size_generic<const T, const void*>(data, size, sizeof(T), sizeof(T));
} }
template <typename T> template <typename T>
T& check_size_t(void* data, size_t size, size_t min_size, size_t max_size) { 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); return check_size_generic<T, void*>(data, size, min_size, max_size);
} }
template <typename T> template <typename T>
T& check_size_t(void* data, size_t size, size_t max_size) { 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); return check_size_generic<T, void*>(data, size, sizeof(T), max_size);
} }
template <typename T> template <typename T>
T& check_size_t(void* data, size_t size) { T& check_size_t(void* data, size_t size) {
return check_size_generic<T, void*>(data, size, sizeof(T), sizeof(T)); 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); void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
std::string prepend_command_header( std::string prepend_command_header(
Version version, Version version,
bool encryption_enabled, bool encryption_enabled,
uint16_t cmd, uint16_t cmd,
uint32_t flag, uint32_t flag,
const std::string& data); const std::string& data);
+162 -162
View File
@@ -1,162 +1,162 @@
#include "PatchFileIndex.hh" #include "PatchFileIndex.hh"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <functional> #include <functional>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Hash.hh> #include <phosg/Hash.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <stdexcept> #include <stdexcept>
#include "Loggers.hh" #include "Loggers.hh"
using namespace std; using namespace std;
PatchFileIndex::File::File(PatchFileIndex* index) PatchFileIndex::File::File(PatchFileIndex* index)
: index(index), : index(index),
crc32(0), crc32(0),
size(0) {} size(0) {}
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() { std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
if (!this->loaded_data) { if (!this->loaded_data) {
string relative_path = join(this->path_directories, "/") + "/" + this->name; string relative_path = join(this->path_directories, "/") + "/" + this->name;
string full_path = this->index->root_dir + "/" + relative_path; string full_path = this->index->root_dir + "/" + relative_path;
patch_index_log.info("Loading data for %s", relative_path.c_str()); patch_index_log.info("Loading data for %s", relative_path.c_str());
this->loaded_data = make_shared<string>(load_file(full_path)); this->loaded_data = make_shared<string>(load_file(full_path));
this->size = this->loaded_data->size(); this->size = this->loaded_data->size();
} }
return this->loaded_data; return this->loaded_data;
} }
PatchFileIndex::PatchFileIndex(const string& root_dir) PatchFileIndex::PatchFileIndex(const string& root_dir)
: root_dir(root_dir) { : root_dir(root_dir) {
string metadata_cache_filename = root_dir + "/.metadata-cache.json"; string metadata_cache_filename = root_dir + "/.metadata-cache.json";
JSON metadata_cache_json; JSON metadata_cache_json;
try { try {
string metadata_text = load_file(metadata_cache_filename); string metadata_text = load_file(metadata_cache_filename);
metadata_cache_json = JSON::parse(metadata_text); metadata_cache_json = JSON::parse(metadata_text);
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str()); patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
} catch (const exception& e) { } catch (const exception& e) {
metadata_cache_json = JSON::dict(); metadata_cache_json = JSON::dict();
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what()); 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 // 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) // cache if no files were changed at all (which should usually be the case)
bool should_write_metadata_cache = false; bool should_write_metadata_cache = false;
JSON new_metadata_cache_json = JSON::dict(); JSON new_metadata_cache_json = JSON::dict();
vector<string> path_directories; vector<string> path_directories;
function<void(const string&)> collect_dir = [&](const string& dir) -> void { function<void(const string&)> collect_dir = [&](const string& dir) -> void {
path_directories.emplace_back(dir); path_directories.emplace_back(dir);
string relative_dirs = join(path_directories, "/"); string relative_dirs = join(path_directories, "/");
string full_dir_path = root_dir + '/' + relative_dirs; string full_dir_path = root_dir + '/' + relative_dirs;
patch_index_log.info("Listing directory %s", full_dir_path.c_str()); patch_index_log.info("Listing directory %s", full_dir_path.c_str());
for (const auto& item : list_directory(full_dir_path)) { for (const auto& item : list_directory(full_dir_path)) {
// Skip invisible files (e.g. .DS_Store on macOS) // Skip invisible files (e.g. .DS_Store on macOS)
if (starts_with(item, ".")) { if (starts_with(item, ".")) {
continue; continue;
} }
string relative_item_path = relative_dirs + '/' + item; string relative_item_path = relative_dirs + '/' + item;
string full_item_path = root_dir + '/' + relative_item_path; string full_item_path = root_dir + '/' + relative_item_path;
if (isdir(full_item_path)) { if (isdir(full_item_path)) {
collect_dir(item); collect_dir(item);
} else if (isfile(full_item_path)) { } else if (isfile(full_item_path)) {
auto st = stat(full_item_path); auto st = stat(full_item_path);
auto f = make_shared<File>(this); auto f = make_shared<File>(this);
f->path_directories = path_directories; f->path_directories = path_directories;
f->name = item; f->name = item;
string compute_crc32s_message; // If not empty, should compute crc32s string compute_crc32s_message; // If not empty, should compute crc32s
JSON cache_item_json; JSON cache_item_json;
try { try {
cache_item_json = metadata_cache_json.at(relative_item_path); cache_item_json = metadata_cache_json.at(relative_item_path);
uint64_t cached_size = cache_item_json.get_int(0); uint64_t cached_size = cache_item_json.get_int(0);
uint64_t cached_mtime = cache_item_json.get_int(1); uint64_t cached_mtime = cache_item_json.get_int(1);
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) { if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
throw runtime_error("file has been modified"); throw runtime_error("file has been modified");
} }
if (static_cast<uint64_t>(st.st_size) != cached_size) { if (static_cast<uint64_t>(st.st_size) != cached_size) {
throw runtime_error("file size has changed"); throw runtime_error("file size has changed");
} }
f->size = cached_size; f->size = cached_size;
f->crc32 = cache_item_json.get_int(2); f->crc32 = cache_item_json.get_int(2);
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) { for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int()); f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
} }
} catch (const exception& e) { } catch (const exception& e) {
compute_crc32s_message = e.what(); compute_crc32s_message = e.what();
} }
if (!compute_crc32s_message.empty()) { if (!compute_crc32s_message.empty()) {
auto data = f->load_data(); // Sets f->size auto data = f->load_data(); // Sets f->size
f->crc32 = crc32(data->data(), f->size); f->crc32 = crc32(data->data(), f->size);
for (size_t x = 0; x < data->size(); x += 0x4000) { for (size_t x = 0; x < data->size(); x += 0x4000) {
size_t chunk_bytes = min<size_t>(f->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)); f->chunk_crcs.emplace_back(::crc32(data->data() + x, chunk_bytes));
} }
// File was modified or cache item was missing; make a new cache item // File was modified or cache item was missing; make a new cache item
auto chunk_crcs_item = JSON::list(); auto chunk_crcs_item = JSON::list();
for (uint32_t chunk_crc : f->chunk_crcs) { for (uint32_t chunk_crc : f->chunk_crcs) {
chunk_crcs_item.emplace_back(chunk_crc); chunk_crcs_item.emplace_back(chunk_crc);
} }
new_metadata_cache_json.emplace( new_metadata_cache_json.emplace(
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)})); relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
should_write_metadata_cache = true; should_write_metadata_cache = true;
} else { } else {
// File was not modified and cache item was valid; just use the // File was not modified and cache item was valid; just use the
// existing cache item // existing cache item
new_metadata_cache_json.emplace( new_metadata_cache_json.emplace(
relative_item_path, std::move(cache_item_json)); relative_item_path, std::move(cache_item_json));
} }
this->files_by_patch_order.emplace_back(f); this->files_by_patch_order.emplace_back(f);
this->files_by_name.emplace(relative_item_path, f); this->files_by_name.emplace(relative_item_path, f);
if (compute_crc32s_message.empty()) { if (compute_crc32s_message.empty()) {
patch_index_log.info( patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)", "Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32); full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
} else { } else {
patch_index_log.info( patch_index_log.info(
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])", "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()); full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
} }
} }
} }
path_directories.pop_back(); path_directories.pop_back();
}; };
collect_dir("."); collect_dir(".");
if (should_write_metadata_cache) { if (should_write_metadata_cache) {
try { try {
save_file(metadata_cache_filename, new_metadata_cache_json.serialize()); 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()); patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
} catch (const exception& e) { } catch (const exception& e) {
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what()); patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
} }
} else { } else {
patch_index_log.info("No files were modified; skipping metadata cache update"); patch_index_log.info("No files were modified; skipping metadata cache update");
} }
} }
const vector<shared_ptr<PatchFileIndex::File>>& const vector<shared_ptr<PatchFileIndex::File>>&
PatchFileIndex::all_files() const { PatchFileIndex::all_files() const {
return this->files_by_patch_order; return this->files_by_patch_order;
} }
shared_ptr<PatchFileIndex::File> PatchFileIndex::get( shared_ptr<PatchFileIndex::File> PatchFileIndex::get(
const string& filename) const { const string& filename) const {
return this->files_by_name.at(filename); return this->files_by_name.at(filename);
} }
+52 -52
View File
@@ -1,52 +1,52 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
struct PatchFileIndex { struct PatchFileIndex {
explicit PatchFileIndex(const std::string& root_dir); explicit PatchFileIndex(const std::string& root_dir);
struct File { struct File {
PatchFileIndex* index; PatchFileIndex* index;
std::vector<std::string> path_directories; std::vector<std::string> path_directories;
std::string name; std::string name;
std::shared_ptr<const std::string> loaded_data; std::shared_ptr<const std::string> loaded_data;
std::vector<uint32_t> chunk_crcs; std::vector<uint32_t> chunk_crcs;
uint32_t crc32; uint32_t crc32;
uint32_t size; uint32_t size;
explicit File(PatchFileIndex* index); explicit File(PatchFileIndex* index);
std::shared_ptr<const std::string> load_data(); std::shared_ptr<const std::string> load_data();
}; };
const std::vector<std::shared_ptr<File>>& all_files() const; const std::vector<std::shared_ptr<File>>& all_files() const;
std::shared_ptr<File> get(const std::string& filename) const; std::shared_ptr<File> get(const std::string& filename) const;
private: private:
std::vector<std::shared_ptr<File>> files_by_patch_order; std::vector<std::shared_ptr<File>> files_by_patch_order;
std::unordered_map<std::string, std::shared_ptr<File>> files_by_name; std::unordered_map<std::string, std::shared_ptr<File>> files_by_name;
std::string root_dir; std::string root_dir;
}; };
struct PatchFileChecksumRequest { struct PatchFileChecksumRequest {
std::shared_ptr<PatchFileIndex::File> file; std::shared_ptr<PatchFileIndex::File> file;
uint32_t crc32; uint32_t crc32;
uint32_t size; uint32_t size;
bool response_received; bool response_received;
explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file) explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file)
: file(file), : file(file),
crc32(0), crc32(0),
size(0), size(0),
response_received(false) {} response_received(false) {}
inline bool needs_update() const { inline bool needs_update() const {
return !this->response_received || return !this->response_received ||
(this->crc32 != this->file->crc32) || (this->crc32 != this->file->crc32) ||
(this->size != this->file->size); (this->size != this->file->size);
} }
}; };
+478 -478
View File
@@ -1,478 +1,478 @@
#include "PatchServer.hh" #include "PatchServer.hh"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/bufferevent.h> #include <event2/bufferevent.h>
#include <event2/event.h> #include <event2/event.h>
#include <event2/listener.h> #include <event2/listener.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include "EventUtils.hh" #include "EventUtils.hh"
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ReceiveCommands.hh" #include "ReceiveCommands.hh"
using namespace std; using namespace std;
static atomic<uint64_t> next_id(1); static atomic<uint64_t> next_id(1);
PatchServer::Client::Client( PatchServer::Client::Client(
shared_ptr<PatchServer> server, shared_ptr<PatchServer> server,
struct bufferevent* bev, struct bufferevent* bev,
Version version, Version version,
uint64_t idle_timeout_usecs, uint64_t idle_timeout_usecs,
bool hide_data_from_logs) bool hide_data_from_logs)
: server(server), : server(server),
id(next_id++), id(next_id++),
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level), 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), 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_usecs(idle_timeout_usecs),
idle_timeout_event( idle_timeout_event(
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this), event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
event_free) { event_free) {
this->reschedule_timeout_event(); this->reschedule_timeout_event();
// Don't print data sent to patch clients to the logs. The patch server // 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 // protocol is fully understood and data logs for patch clients are generally
// more annoying than helpful at this point. // more annoying than helpful at this point.
if (hide_data_from_logs) { if (hide_data_from_logs) {
this->channel.terminal_recv_color = TerminalFormat::END; this->channel.terminal_recv_color = TerminalFormat::END;
this->channel.terminal_send_color = TerminalFormat::END; this->channel.terminal_send_color = TerminalFormat::END;
} }
this->log.info("Created"); this->log.info("Created");
} }
void PatchServer::Client::reschedule_timeout_event() { void PatchServer::Client::reschedule_timeout_event() {
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs); struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &idle_tv); event_add(this->idle_timeout_event.get(), &idle_tv);
} }
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) { void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Client*>(ctx)->idle_timeout(); reinterpret_cast<Client*>(ctx)->idle_timeout();
} }
void PatchServer::Client::idle_timeout() { void PatchServer::Client::idle_timeout() {
this->log.info("Idle timeout expired"); this->log.info("Idle timeout expired");
auto s = this->server.lock(); auto s = this->server.lock();
if (s) { if (s) {
auto c = this->shared_from_this(); auto c = this->shared_from_this();
s->disconnect_client(c); s->disconnect_client(c);
} else { } else {
this->channel.disconnect(); this->channel.disconnect();
this->log.info("Server is deleted; cannot disconnect client"); this->log.info("Server is deleted; cannot disconnect client");
} }
} }
void PatchServer::send_server_init(shared_ptr<Client> c) const { void PatchServer::send_server_init(shared_ptr<Client> c) const {
uint32_t server_key = random_object<uint32_t>(); uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>(); uint32_t client_key = random_object<uint32_t>();
S_ServerInit_Patch_02 cmd; S_ServerInit_Patch_02 cmd;
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001"); cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
cmd.server_key = server_key; cmd.server_key = server_key;
cmd.client_key = client_key; cmd.client_key = client_key;
c->channel.send(0x02, 0x00, cmd); c->channel.send(0x02, 0x00, cmd);
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key); c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key); c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
} }
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const { void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
StringWriter w; StringWriter w;
try { try {
if (c->version() == Version::PC_PATCH) { if (c->version() == Version::PC_PATCH) {
w.write(tt_encode_marked_optional(text, c->channel.language, true)); w.write(tt_encode_marked_optional(text, c->channel.language, true));
} else if (c->version() == Version::BB_PATCH) { } else if (c->version() == Version::BB_PATCH) {
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true)); w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
} else { } else {
throw logic_error("non-patch client on patch server"); throw logic_error("non-patch client on patch server");
} }
} catch (const runtime_error& e) { } catch (const runtime_error& e) {
log_warning("Failed to encode message for patch message box command: %s", e.what()); log_warning("Failed to encode message for patch message box command: %s", e.what());
return; return;
} }
w.put_u16(0); w.put_u16(0);
while (w.str().size() & 3) { while (w.str().size() & 3) {
w.put_u8(0); w.put_u8(0);
} }
c->channel.send(0x13, 0x00, w.str()); c->channel.send(0x13, 0x00, w.str());
} }
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const { void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
S_EnterDirectory_Patch_09 cmd = {{dir, 1}}; S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
c->channel.send(0x09, 0x00, cmd); c->channel.send(0x09, 0x00, cmd);
} }
void PatchServer::on_02(shared_ptr<Client> c, string& data) { void PatchServer::on_02(shared_ptr<Client> c, string& data) {
check_size_v(data.size(), 0); check_size_v(data.size(), 0);
c->channel.send(0x04, 0x00); // This requests the user's login information c->channel.send(0x04, 0x00); // This requests the user's login information
} }
void PatchServer::change_to_directory( void PatchServer::change_to_directory(
shared_ptr<Client> c, shared_ptr<Client> c,
vector<string>& client_path_directories, vector<string>& client_path_directories,
const vector<string>& file_path_directories) const { const vector<string>& file_path_directories) const {
// First, exit all leaf directories that don't match the desired path // First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() && while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) || ((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) { (client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
c->channel.send(0x0A, 0x00); c->channel.send(0x0A, 0x00);
client_path_directories.pop_back(); client_path_directories.pop_back();
} }
// At this point, client_path_directories should be a prefix of // At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly) // file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) { if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories"); throw logic_error("did not exit all necessary directories");
} }
for (size_t x = 0; x < client_path_directories.size(); x++) { for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) { if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path"); throw logic_error("intermediate path is not a prefix of final path");
} }
} }
// Second, enter all necessary leaf directories // Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) { while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()]; const string& dir = file_path_directories[client_path_directories.size()];
this->send_enter_directory(c, dir); this->send_enter_directory(c, dir);
client_path_directories.emplace_back(dir); client_path_directories.emplace_back(dir);
} }
} }
void PatchServer::on_04(shared_ptr<Client> c, string& data) { void PatchServer::on_04(shared_ptr<Client> c, string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data); const auto& cmd = check_size_t<C_Login_Patch_04>(data);
string username = cmd.username.decode(); string username = cmd.username.decode();
string password = cmd.password.decode(); string password = cmd.password.decode();
// There are 3 cases here: // There are 3 cases here:
// - No login information at all: just proceed without checking credentials // - No login information at all: just proceed without checking credentials
// - Username: check that account exists if allow_unregistered_users is off // - Username: check that account exists if allow_unregistered_users is off
// - Username and password: call verify_bb // - Username and password: call verify_bb
if (!username.empty() && !password.empty()) { if (!username.empty() && !password.empty()) {
try { try {
this->config->account_index->from_bb_credentials(username, &password, false); this->config->account_index->from_bb_credentials(username, &password, false);
} catch (const AccountIndex::incorrect_password& e) { } catch (const AccountIndex::incorrect_password& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c); this->disconnect_client(c);
return; return;
} catch (const AccountIndex::missing_account& e) { } catch (const AccountIndex::missing_account& e) {
if (!this->config->allow_unregistered_users) { if (!this->config->allow_unregistered_users) {
this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c); this->disconnect_client(c);
return; return;
} }
} }
} else if (!username.empty() && !this->config->allow_unregistered_users) { } else if (!username.empty() && !this->config->allow_unregistered_users) {
try { try {
this->config->account_index->from_bb_credentials(username, nullptr, false); this->config->account_index->from_bb_credentials(username, nullptr, false);
} catch (const AccountIndex::missing_account& e) { } catch (const AccountIndex::missing_account& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c); this->disconnect_client(c);
return; return;
} }
} }
if (!this->config->message.empty()) { if (!this->config->message.empty()) {
this->send_message_box(c, this->config->message.c_str()); this->send_message_box(c, this->config->message.c_str());
} }
const auto& index = this->config->patch_file_index; const auto& index = this->config->patch_file_index;
if (index.get()) { if (index.get()) {
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories; vector<string> path_directories;
for (const auto& file : index->all_files()) { for (const auto& file : index->all_files()) {
this->change_to_directory(c, path_directories, file->path_directories); this->change_to_directory(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}}; S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
c->channel.send(0x0C, 0x00, req); c->channel.send(0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file); c->patch_file_checksum_requests.emplace_back(file);
} }
this->change_to_directory(c, path_directories, {}); this->change_to_directory(c, path_directories, {});
c->channel.send(0x0D, 0x00); // End of checksum requests c->channel.send(0x0D, 0x00); // End of checksum requests
} else { } else {
// No patch index present: just do something that will satisfy the client // No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files // without actually checking or downloading any files
this->send_enter_directory(c, "."); this->send_enter_directory(c, ".");
this->send_enter_directory(c, "data"); this->send_enter_directory(c, "data");
this->send_enter_directory(c, "scene"); this->send_enter_directory(c, "scene");
c->channel.send(0x0A, 0x00); c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00); c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00); c->channel.send(0x0A, 0x00);
c->channel.send(0x12, 0x00); c->channel.send(0x12, 0x00);
} }
} }
void PatchServer::on_0F(shared_ptr<Client> c, string& data) { void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data); auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id); auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum; req.crc32 = cmd.checksum;
req.size = cmd.size; req.size = cmd.size;
req.response_received = true; req.response_received = true;
} }
void PatchServer::on_10(shared_ptr<Client> c, string&) { void PatchServer::on_10(shared_ptr<Client> c, string&) {
S_StartFileDownloads_Patch_11 start_cmd = {0, 0}; S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) { for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) { if (!req.response_received) {
throw runtime_error("client did not respond to checksum request"); throw runtime_error("client did not respond to checksum request");
} }
if (req.needs_update()) { if (req.needs_update()) {
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")", 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); req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
start_cmd.total_bytes += req.file->size; start_cmd.total_bytes += req.file->size;
start_cmd.num_files++; start_cmd.num_files++;
} else { } else {
c->log.info("File %s is up to date", req.file->name.c_str()); c->log.info("File %s is up to date", req.file->name.c_str());
} }
} }
if (start_cmd.num_files) { if (start_cmd.num_files) {
c->channel.send(0x11, 0x00, start_cmd); c->channel.send(0x11, 0x00, start_cmd);
vector<string> path_directories; vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) { for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) { if (req.needs_update()) {
this->change_to_directory(c, path_directories, req.file->path_directories); 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}}; S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
c->channel.send(0x06, 0x00, open_cmd); c->channel.send(0x06, 0x00, open_cmd);
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) { for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
auto data = req.file->load_data(); auto data = req.file->load_data();
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000); size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
vector<pair<const void*, size_t>> blocks; vector<pair<const void*, size_t>> blocks;
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size}; 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(&cmd_header, sizeof(cmd_header));
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size); blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
c->channel.send(0x07, 0x00, blocks); c->channel.send(0x07, 0x00, blocks);
} }
S_CloseCurrentFile_Patch_08 close_cmd = {0}; S_CloseCurrentFile_Patch_08 close_cmd = {0};
c->channel.send(0x08, 0x00, close_cmd); c->channel.send(0x08, 0x00, close_cmd);
} }
} }
this->change_to_directory(c, path_directories, {}); this->change_to_directory(c, path_directories, {});
} }
c->channel.send(0x12, 0x00); c->channel.send(0x12, 0x00);
} }
void PatchServer::disconnect_client(shared_ptr<Client> c) { void PatchServer::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) { if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, 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) { } else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id); server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else { } else {
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id); server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
} }
this->channel_to_client.erase(&c->channel); this->channel_to_client.erase(&c->channel);
c->channel.disconnect(); c->channel.disconnect();
// We can't just let c be destroyed here, since disconnect_client can be // 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 // 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 // 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 // callback after the current event. This will also call the client's
// disconnect hooks (if any). // disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c)); this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients(); this->enqueue_destroy_clients();
} }
void PatchServer::enqueue_destroy_clients() { void PatchServer::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0); auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv); event_add(this->destroy_clients_ev.get(), &tv);
} }
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) { void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear(); reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
} }
void PatchServer::dispatch_on_listen_accept( void PatchServer::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd, struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) { struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen); reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
} }
void PatchServer::dispatch_on_listen_error( void PatchServer::dispatch_on_listen_error(
struct evconnlistener* listener, void* ctx) { struct evconnlistener* listener, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener); reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
} }
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr; struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr); get_socket_addresses(fd, nullptr, &remote_addr);
if (this->config->banned_ipv4_ranges->check(remote_addr)) { if (this->config->banned_ipv4_ranges->check(remote_addr)) {
close(fd); close(fd);
return; return;
} }
int listen_fd = evconnlistener_get_fd(listener); int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket; ListeningSocket* listening_socket;
try { try {
listening_socket = &this->listening_sockets.at(listen_fd); listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) { } catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd); server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
close(fd); close(fd);
return; return;
} }
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>( auto c = make_shared<Client>(
this->shared_from_this(), this->shared_from_this(),
bev, bev,
listening_socket->version, listening_socket->version,
this->config->idle_timeout_usecs, this->config->idle_timeout_usecs,
this->config->hide_data_from_logs); this->config->hide_data_from_logs);
c->channel.on_command_received = PatchServer::on_client_input; c->channel.on_command_received = PatchServer::on_client_input;
c->channel.on_error = PatchServer::on_client_error; c->channel.on_error = PatchServer::on_client_error;
c->channel.context_obj = this; c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c); this->channel_to_client.emplace(&c->channel, c);
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)", 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()); c->id, fd, listen_fd, listening_socket->addr_str.c_str());
this->send_server_init(c); this->send_server_init(c);
} }
void PatchServer::on_listen_error(struct evconnlistener* listener) { void PatchServer::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)", server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err)); evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
} }
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) { void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj); PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch); shared_ptr<Client> c = server->channel_to_client.at(&ch);
try { try {
switch (command) { switch (command) {
case 0x02: case 0x02:
server->on_02(c, data); server->on_02(c, data);
break; break;
case 0x04: case 0x04:
server->on_04(c, data); server->on_04(c, data);
break; break;
case 0x0F: case 0x0F:
server->on_0F(c, data); server->on_0F(c, data);
break; break;
case 0x10: case 0x10:
server->on_10(c, data); server->on_10(c, data);
break; break;
default: default:
throw runtime_error("invalid command"); throw runtime_error("invalid command");
} }
} catch (const exception& e) { } catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what()); server_log.warning("Error processing client command: %s", e.what());
} }
} }
void PatchServer::on_client_error(Channel& ch, short events) { void PatchServer::on_client_error(Channel& ch, short events) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj); PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch); shared_ptr<Client> c = server->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) { if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err)); server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
} }
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c); server->disconnect_client(c);
} }
} }
PatchServer::PatchServer(shared_ptr<const Config> config) PatchServer::PatchServer(shared_ptr<const Config> config)
: config(config) { : config(config) {
if (config->shared_base) { if (config->shared_base) {
this->base = config->shared_base; this->base = config->shared_base;
this->base_is_shared = true; this->base_is_shared = true;
} else { } else {
this->base.reset(event_base_new(), event_base_free); this->base.reset(event_base_new(), event_base_free);
this->base_is_shared = false; this->base_is_shared = false;
} }
this->destroy_clients_ev.reset( this->destroy_clients_ev.reset(
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free); event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
if (!this->base_is_shared) { if (!this->base_is_shared) {
this->th = thread(&PatchServer::thread_fn, this); this->th = thread(&PatchServer::thread_fn, this);
} }
} }
void PatchServer::schedule_stop() { void PatchServer::schedule_stop() {
if (!this->base_is_shared) { if (!this->base_is_shared) {
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
} }
} }
void PatchServer::wait_for_stop() { void PatchServer::wait_for_stop() {
if (!this->base_is_shared) { if (!this->base_is_shared) {
this->th.join(); this->th.join();
} }
} }
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) { void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
int fd = ::listen(socket_path, 0, SOMAXCONN); 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()); 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); this->add_socket(addr_str, fd, version);
} }
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) { void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
if (port == 0) { if (port == 0) {
this->listen(addr_str, addr, version); this->listen(addr_str, addr, version);
} else { } else {
int fd = ::listen(addr, port, SOMAXCONN); int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port); 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()); 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); this->add_socket(addr_str, fd, version);
} }
} }
void PatchServer::listen(const std::string& addr_str, int port, Version version) { void PatchServer::listen(const std::string& addr_str, int port, Version version) {
this->listen(addr_str, "", port, version); this->listen(addr_str, "", port, version);
} }
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version) PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
: addr_str(addr_str), : addr_str(addr_str),
fd(fd), fd(fd),
version(version), version(version),
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd), listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) { evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error); 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) { 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)); this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
} }
void PatchServer::thread_fn() { void PatchServer::thread_fn() {
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY); event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
} }
void PatchServer::set_config(std::shared_ptr<const Config> config) { void PatchServer::set_config(std::shared_ptr<const Config> config) {
if (this->base_is_shared) { if (this->base_is_shared) {
this->config = config; this->config = config;
} else { } else {
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() { forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
s->config = config; s->config = config;
}); });
} }
} }
+131 -131
View File
@@ -1,131 +1,131 @@
#pragma once #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <stdint.h> #include <stdint.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "Account.hh" #include "Account.hh"
#include "Channel.hh" #include "Channel.hh"
#include "IPV4RangeSet.hh" #include "IPV4RangeSet.hh"
#include "PatchFileIndex.hh" #include "PatchFileIndex.hh"
#include "Version.hh" #include "Version.hh"
class PatchServer : public std::enable_shared_from_this<PatchServer> { class PatchServer : public std::enable_shared_from_this<PatchServer> {
public: public:
struct Config { struct Config {
bool allow_unregistered_users; bool allow_unregistered_users;
bool hide_data_from_logs; bool hide_data_from_logs;
uint64_t idle_timeout_usecs; uint64_t idle_timeout_usecs;
std::string message; std::string message;
std::shared_ptr<AccountIndex> account_index; std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<const PatchFileIndex> patch_file_index; std::shared_ptr<const PatchFileIndex> patch_file_index;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges; std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<struct event_base> shared_base; std::shared_ptr<struct event_base> shared_base;
}; };
PatchServer() = delete; PatchServer() = delete;
explicit PatchServer(std::shared_ptr<const Config> config); explicit PatchServer(std::shared_ptr<const Config> config);
PatchServer(const PatchServer&) = delete; PatchServer(const PatchServer&) = delete;
PatchServer(PatchServer&&) = delete; PatchServer(PatchServer&&) = delete;
PatchServer& operator=(const PatchServer&) = delete; PatchServer& operator=(const PatchServer&) = delete;
PatchServer& operator=(PatchServer&&) = delete; PatchServer& operator=(PatchServer&&) = delete;
virtual ~PatchServer() = default; virtual ~PatchServer() = default;
void schedule_stop(); void schedule_stop();
void wait_for_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& 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, const std::string& addr, int port, Version version);
void listen(const std::string& addr_str, 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 add_socket(const std::string& addr_str, int fd, Version version);
void set_config(std::shared_ptr<const Config> config); void set_config(std::shared_ptr<const Config> config);
private: private:
class Client : public std::enable_shared_from_this<Client> { class Client : public std::enable_shared_from_this<Client> {
public: public:
std::weak_ptr<PatchServer> server; std::weak_ptr<PatchServer> server;
uint64_t id; uint64_t id;
PrefixedLogger log; PrefixedLogger log;
Channel channel; Channel channel;
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests; std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
uint64_t idle_timeout_usecs; uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event; std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Client( Client(
std::shared_ptr<PatchServer> server, std::shared_ptr<PatchServer> server,
struct bufferevent* bev, struct bufferevent* bev,
Version version, Version version,
uint64_t idle_timeout_usecs, uint64_t idle_timeout_usecs,
bool hide_data_from_logs); bool hide_data_from_logs);
~Client() = default; ~Client() = default;
void reschedule_timeout_event(); void reschedule_timeout_event();
inline Version version() const { inline Version version() const {
return this->channel.version; return this->channel.version;
} }
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx); static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout(); void idle_timeout();
const std::string& get_bb_username() const; const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username); void set_bb_username(const std::string& bb_username);
}; };
struct ListeningSocket { struct ListeningSocket {
std::string addr_str; std::string addr_str;
int fd; int fd;
Version version; Version version;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener; std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version); ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
}; };
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
bool base_is_shared; bool base_is_shared;
std::shared_ptr<const Config> config; std::shared_ptr<const Config> config;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy; std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<struct event> destroy_clients_ev; std::shared_ptr<struct event> destroy_clients_ev;
std::unordered_map<int, ListeningSocket> listening_sockets; std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client; std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::thread th; std::thread th;
void send_server_init(std::shared_ptr<Client> c) const; 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_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 send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
void change_to_directory( void change_to_directory(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::vector<std::string>& client_path_directories, std::vector<std::string>& client_path_directories,
const std::vector<std::string>& file_path_directories) const; const std::vector<std::string>& file_path_directories) const;
void on_02(std::shared_ptr<Client> c, std::string& data); void on_02(std::shared_ptr<Client> c, std::string& data);
void on_04(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_0F(std::shared_ptr<Client> c, std::string& data);
void on_10(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 disconnect_client(std::shared_ptr<Client> c);
void enqueue_destroy_clients(); void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx); static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
static void dispatch_on_listen_accept(struct evconnlistener* listener, static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, 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_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener); 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_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events); static void on_client_error(Channel& ch, short events);
void thread_fn(); void thread_fn();
}; };
+119 -119
View File
@@ -1,119 +1,119 @@
#include "PlayerFilesManager.hh" #include "PlayerFilesManager.hh"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <wchar.h> #include <wchar.h>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Hash.hh> #include <phosg/Hash.hh>
#include <stdexcept> #include <stdexcept>
#include "FileContentsCache.hh" #include "FileContentsCache.hh"
#include "ItemData.hh" #include "ItemData.hh"
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
using namespace std; using namespace std;
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base) PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
: base(base), : base(base),
clear_expired_files_event( clear_expired_files_event(
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this), event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
event_free) { event_free) {
auto tv = usecs_to_timeval(30 * 1000 * 1000); auto tv = usecs_to_timeval(30 * 1000 * 1000);
event_add(this->clear_expired_files_event.get(), &tv); event_add(this->clear_expired_files_event.get(), &tv);
} }
template <typename KeyT, typename ValueT> template <typename KeyT, typename ValueT>
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) { size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
size_t ret = 0; size_t ret = 0;
for (auto it = m.begin(); it != m.end();) { for (auto it = m.begin(); it != m.end();) {
if (it->second.use_count() <= 1) { if (it->second.use_count() <= 1) {
it = m.erase(it); it = m.erase(it);
ret++; ret++;
} else { } else {
it++; it++;
} }
} }
return ret; return ret;
} }
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) { std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
try { try {
return this->loaded_system_files.at(filename); return this->loaded_system_files.at(filename);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) { std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
try { try {
return this->loaded_character_files.at(filename); return this->loaded_character_files.at(filename);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) { std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
try { try {
return this->loaded_guild_card_files.at(filename); return this->loaded_guild_card_files.at(filename);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) { std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) {
try { try {
return this->loaded_bank_files.at(filename); return this->loaded_bank_files.at(filename);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) { void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
if (!this->loaded_system_files.emplace(filename, file).second) { if (!this->loaded_system_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename); throw runtime_error("Guild Card file already loaded: " + filename);
} }
} }
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) { void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
if (!this->loaded_character_files.emplace(filename, file).second) { if (!this->loaded_character_files.emplace(filename, file).second) {
throw runtime_error("character file already loaded: " + filename); throw runtime_error("character file already loaded: " + filename);
} }
} }
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) { void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
if (!this->loaded_guild_card_files.emplace(filename, file).second) { if (!this->loaded_guild_card_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded: " + filename); throw runtime_error("Guild Card file already loaded: " + filename);
} }
} }
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) { void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) { if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename); throw runtime_error("bank file already loaded: " + filename);
} }
} }
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) { void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx); auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
size_t num_deleted = erase_unused(self->loaded_system_files); size_t num_deleted = erase_unused(self->loaded_system_files);
if (num_deleted) { if (num_deleted) {
player_data_log.info("Cleared %zu expired system file(s)", num_deleted); player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
} }
num_deleted = erase_unused(self->loaded_character_files); num_deleted = erase_unused(self->loaded_character_files);
if (num_deleted) { if (num_deleted) {
player_data_log.info("Cleared %zu expired character file(s)", num_deleted); player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
} }
num_deleted = erase_unused(self->loaded_guild_card_files); num_deleted = erase_unused(self->loaded_guild_card_files);
if (num_deleted) { if (num_deleted) {
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted); player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
} }
num_deleted = erase_unused(self->loaded_bank_files); num_deleted = erase_unused(self->loaded_bank_files);
if (num_deleted) { if (num_deleted) {
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted); player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
} }
} }
+47 -47
View File
@@ -1,47 +1,47 @@
#pragma once #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <inttypes.h> #include <inttypes.h>
#include <stddef.h> #include <stddef.h>
#include <array> #include <array>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "Episode3/DataIndexes.hh" #include "Episode3/DataIndexes.hh"
#include "ItemCreator.hh" #include "ItemCreator.hh"
#include "ItemNameIndex.hh" #include "ItemNameIndex.hh"
#include "LevelTable.hh" #include "LevelTable.hh"
#include "PlayerSubordinates.hh" #include "PlayerSubordinates.hh"
#include "SaveFileFormats.hh" #include "SaveFileFormats.hh"
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
class PlayerFilesManager { class PlayerFilesManager {
public: public:
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base); explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
~PlayerFilesManager() = default; ~PlayerFilesManager() = default;
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename); std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
std::shared_ptr<PSOBBCharacterFile> get_character(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<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
std::shared_ptr<PlayerBank200> get_bank(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_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> 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_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file); void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
private: private:
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event; 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<PSOBBBaseSystemFile>> loaded_system_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_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<PSOBBGuildCardFile>> loaded_guild_card_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_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); 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
#include "ProxyServer.hh" #include "ProxyServer.hh"
#include "ServerState.hh" #include "ServerState.hh"
void on_proxy_command( void on_proxy_command(
std::shared_ptr<ProxyServer::LinkedSession> ses, std::shared_ptr<ProxyServer::LinkedSession> ses,
bool from_server, bool from_server,
uint16_t command, uint16_t command,
uint32_t flag, uint32_t flag,
std::string& data); 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 #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <deque> #include <deque>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ServerState.hh" #include "ServerState.hh"
class ProxyServer : public std::enable_shared_from_this<ProxyServer> { class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
public: public:
ProxyServer() = delete; ProxyServer() = delete;
ProxyServer(const ProxyServer&) = delete; ProxyServer(const ProxyServer&) = delete;
ProxyServer(ProxyServer&&) = delete; ProxyServer(ProxyServer&&) = delete;
ProxyServer( ProxyServer(
std::shared_ptr<struct event_base> base, std::shared_ptr<struct event_base> base,
std::shared_ptr<ServerState> state); std::shared_ptr<ServerState> state);
virtual ~ProxyServer() = default; virtual ~ProxyServer() = default;
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr); 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); void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
struct LinkedSession : std::enable_shared_from_this<LinkedSession> { struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
std::weak_ptr<ProxyServer> server; std::weak_ptr<ProxyServer> server;
uint64_t id; uint64_t id;
PrefixedLogger log; PrefixedLogger log;
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event; std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
std::shared_ptr<Login> login; std::shared_ptr<Login> login;
Channel client_channel; Channel client_channel;
Channel server_channel; Channel server_channel;
uint16_t local_port; uint16_t local_port;
struct sockaddr_storage next_destination; struct sockaddr_storage next_destination;
enum class DisconnectAction { enum class DisconnectAction {
LONG_TIMEOUT = 0, LONG_TIMEOUT = 0,
MEDIUM_TIMEOUT, MEDIUM_TIMEOUT,
SHORT_TIMEOUT, SHORT_TIMEOUT,
CLOSE_IMMEDIATELY, CLOSE_IMMEDIATELY,
}; };
DisconnectAction disconnect_action; DisconnectAction disconnect_action;
uint8_t prev_server_command_bytes[6]; uint8_t prev_server_command_bytes[6];
uint32_t remote_ip_crc; uint32_t remote_ip_crc;
bool enable_remote_ip_crc_patch; bool enable_remote_ip_crc_patch;
uint32_t sub_version; uint32_t sub_version;
std::string character_name; std::string character_name;
std::string hardware_id; // Only used for DC sessions std::string hardware_id; // Only used for DC sessions
std::string login_command_bb; std::string login_command_bb;
XBNetworkLocation xb_netloc; XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a; parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint32_t challenge_rank_color_override; uint32_t challenge_rank_color_override;
std::string challenge_rank_title_override; std::string challenge_rank_title_override;
int64_t remote_guild_card_number; int64_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data; parray<uint8_t, 0x20> remote_client_config_data;
Client::Config config; Client::Config config;
// A null handler in here means to forward the response to the remote server // 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; 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 RecentSwitchFlags recent_switch_flags; // used for switch assist
ItemData next_drop_item; ItemData next_drop_item;
uint32_t next_item_id; uint32_t next_item_id;
enum class DropMode { enum class DropMode {
DISABLED = 0, DISABLED = 0,
PASSTHROUGH, PASSTHROUGH,
INTERCEPT, INTERCEPT,
}; };
DropMode drop_mode; DropMode drop_mode;
std::shared_ptr<std::string> quest_dat_data; std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator; std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<Map> map; std::shared_ptr<Map> map;
struct LobbyPlayer { struct LobbyPlayer {
uint32_t guild_card_number = 0; uint32_t guild_card_number = 0;
uint64_t xb_user_id = 0; uint64_t xb_user_id = 0;
std::string name; std::string name;
uint8_t language = 0; uint8_t language = 0;
uint8_t section_id = 0; uint8_t section_id = 0;
uint8_t char_class = 0; uint8_t char_class = 0;
}; };
std::vector<LobbyPlayer> lobby_players; std::vector<LobbyPlayer> lobby_players;
size_t lobby_client_id; size_t lobby_client_id;
size_t leader_client_id; size_t leader_client_id;
uint16_t floor; uint16_t floor;
float x; float x;
float z; float z;
bool is_in_game; bool is_in_game;
bool is_in_quest; bool is_in_quest;
uint8_t lobby_event; uint8_t lobby_event;
uint8_t lobby_difficulty; uint8_t lobby_difficulty;
uint8_t lobby_section_id; uint8_t lobby_section_id;
GameMode lobby_mode; GameMode lobby_mode;
Episode lobby_episode; Episode lobby_episode;
uint32_t lobby_random_seed; uint32_t lobby_random_seed;
uint64_t client_ping_start_time = 0; uint64_t client_ping_start_time = 0;
uint64_t server_ping_start_time = 0; uint64_t server_ping_start_time = 0;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt; std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
struct SavingFile { struct SavingFile {
std::string basename; std::string basename;
std::string output_filename; std::string output_filename;
bool is_download; bool is_download;
size_t remaining_bytes; size_t remaining_bytes;
std::deque<std::string> blocks; std::deque<std::string> blocks;
SavingFile( SavingFile(
const std::string& basename, const std::string& basename,
const std::string& output_filename, const std::string& output_filename,
size_t remaining_bytes, size_t remaining_bytes,
bool is_download); bool is_download);
}; };
std::unordered_map<std::string, SavingFile> saving_files; std::unordered_map<std::string, SavingFile> saving_files;
// TODO: This first constructor should be private // TODO: This first constructor should be private
LinkedSession( LinkedSession(
std::shared_ptr<ProxyServer> server, std::shared_ptr<ProxyServer> server,
uint64_t id, uint64_t id,
uint16_t local_port, uint16_t local_port,
Version version); Version version);
LinkedSession( LinkedSession(
std::shared_ptr<ProxyServer> server, std::shared_ptr<ProxyServer> server,
uint16_t local_port, uint16_t local_port,
Version version, Version version,
std::shared_ptr<Login> login, std::shared_ptr<Login> login,
const Client::Config& config); const Client::Config& config);
LinkedSession( LinkedSession(
std::shared_ptr<ProxyServer> server, std::shared_ptr<ProxyServer> server,
uint16_t local_port, uint16_t local_port,
Version version, Version version,
std::shared_ptr<Login> login, std::shared_ptr<Login> login,
const struct sockaddr_storage& next_destination); const struct sockaddr_storage& next_destination);
LinkedSession( LinkedSession(
std::shared_ptr<ProxyServer> server, std::shared_ptr<ProxyServer> server,
uint64_t id, uint64_t id,
uint16_t local_port, uint16_t local_port,
Version version, Version version,
const struct sockaddr_storage& next_destination); const struct sockaddr_storage& next_destination);
std::shared_ptr<ProxyServer> require_server() const; std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const; std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const { inline Version version() const {
return this->client_channel.version; return this->client_channel.version;
} }
inline uint8_t language() const { inline uint8_t language() const {
return this->client_channel.language; return this->client_channel.language;
} }
void set_version(Version v); void set_version(Version v);
void resume( void resume(
Channel&& client_channel, Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt, std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version, uint32_t sub_version,
const std::string& character_name, const std::string& character_name,
const std::string& hardware_id, const std::string& hardware_id,
const XBNetworkLocation& xb_netloc, const XBNetworkLocation& xb_netloc,
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a); const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
void resume( void resume(
Channel&& client_channel, Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt, std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
std::string&& login_command_bb); std::string&& login_command_bb);
void resume(Channel&& client_channel); void resume(Channel&& client_channel);
void resume_inner( void resume_inner(
Channel&& client_channel, Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt); std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
void connect(); void connect();
static uint64_t timeout_for_disconnect_action(DisconnectAction action); static uint64_t timeout_for_disconnect_action(DisconnectAction action);
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx); 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_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
static void on_error(Channel& ch, short events); static void on_error(Channel& ch, short events);
void on_timeout(); void on_timeout();
void update_channel_names(); void update_channel_names();
void clear_lobby_players(size_t num_slots); void clear_lobby_players(size_t num_slots);
void set_drop_mode(DropMode new_mode); void set_drop_mode(DropMode new_mode);
void send_to_game_server(const char* error_message = nullptr); void send_to_game_server(const char* error_message = nullptr);
void disconnect(); void disconnect();
bool is_connected() const; bool is_connected() const;
}; };
std::shared_ptr<LinkedSession> get_session() const; std::shared_ptr<LinkedSession> get_session() const;
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) 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; const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
std::shared_ptr<LinkedSession> create_logged_in_session( std::shared_ptr<LinkedSession> create_logged_in_session(
std::shared_ptr<Login> login, std::shared_ptr<Login> login,
uint16_t local_port, uint16_t local_port,
Version version, Version version,
const Client::Config& config); const Client::Config& config);
void delete_session(uint64_t id); void delete_session(uint64_t id);
size_t num_sessions() const; size_t num_sessions() const;
size_t delete_disconnected_sessions(); size_t delete_disconnected_sessions();
private: private:
struct ListeningSocket { struct ListeningSocket {
ProxyServer* server; ProxyServer* server;
PrefixedLogger log; PrefixedLogger log;
uint16_t port; uint16_t port;
scoped_fd fd; scoped_fd fd;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener; std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
Version version; Version version;
struct sockaddr_storage default_destination; struct sockaddr_storage default_destination;
ListeningSocket( ListeningSocket(
ProxyServer* server, ProxyServer* server,
const std::string& addr, const std::string& addr,
uint16_t port, uint16_t port,
Version version, Version version,
const struct sockaddr_storage* default_destination); const struct sockaddr_storage* default_destination);
static void dispatch_on_listen_accept(struct evconnlistener* listener, static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx); static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(int fd); void on_listen_accept(int fd);
void on_listen_error(); void on_listen_error();
}; };
struct UnlinkedSession { struct UnlinkedSession {
std::weak_ptr<ProxyServer> server; std::weak_ptr<ProxyServer> server;
uint64_t id; uint64_t id;
PrefixedLogger log; PrefixedLogger log;
Channel channel; Channel channel;
uint16_t local_port; uint16_t local_port;
struct sockaddr_storage next_destination; struct sockaddr_storage next_destination;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt; std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
// Temporary state used just before resuming a LinkedSession. These aren't // Temporary state used just before resuming a LinkedSession. These aren't
// just local variables inside on_input because XB requires two commands to // 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 // get started (9E and 9F), so we need to store this state somewhere between
// those commands. // those commands.
std::shared_ptr<Login> login; std::shared_ptr<Login> login;
uint32_t sub_version = 0; uint32_t sub_version = 0;
std::string character_name; std::string character_name;
Client::Config config; Client::Config config;
std::string login_command_bb; std::string login_command_bb;
std::string hardware_id; std::string hardware_id;
XBNetworkLocation xb_netloc; XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a; parray<le_uint32_t, 3> xb_9E_unknown_a1a;
UnlinkedSession( UnlinkedSession(
std::shared_ptr<ProxyServer> server, std::shared_ptr<ProxyServer> server,
uint64_t id, uint64_t id,
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
uint16_t port, uint16_t port,
Version version); Version version);
std::shared_ptr<ProxyServer> require_server() const; std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const; std::shared_ptr<ServerState> require_server_state() const;
inline Version version() const { inline Version version() const {
return this->channel.version; return this->channel.version;
} }
void receive_and_process_commands(); void receive_and_process_commands();
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg); static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void on_error(Channel& ch, short events); static void on_error(Channel& ch, short events);
}; };
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_sessions_ev; std::shared_ptr<struct event> destroy_sessions_ev;
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
std::map<int, std::shared_ptr<ListeningSocket>> listeners; std::map<int, std::shared_ptr<ListeningSocket>> listeners;
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session; 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_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session; std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
uint64_t next_unlinked_session_id; uint64_t next_unlinked_session_id;
uint64_t next_logged_out_session_id; uint64_t next_logged_out_session_id;
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx); static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
void destroy_sessions(); void destroy_sessions();
void on_client_connect( void on_client_connect(
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
uint16_t listen_port, uint16_t listen_port,
Version version, Version version,
const struct sockaddr_storage* default_destination); const struct sockaddr_storage* default_destination);
static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000; static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000;
static constexpr uint64_t MIN_LINKED_LOGGED_OUT_SESSION_ID = 0x1000000000000000; 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "IntegralExpression.hh" #include "IntegralExpression.hh"
#include "PlayerSubordinates.hh" #include "PlayerSubordinates.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
enum class QuestFileFormat { enum class QuestFileFormat {
BIN_DAT = 0, BIN_DAT = 0,
BIN_DAT_UNCOMPRESSED, BIN_DAT_UNCOMPRESSED,
BIN_DAT_GCI, BIN_DAT_GCI,
BIN_DAT_VMS, BIN_DAT_VMS,
BIN_DAT_DLQ, BIN_DAT_DLQ,
QST, QST,
}; };
enum class QuestMenuType { enum class QuestMenuType {
NORMAL = 0, NORMAL = 0,
BATTLE = 1, BATTLE = 1,
CHALLENGE = 2, CHALLENGE = 2,
SOLO = 3, SOLO = 3,
GOVERNMENT = 4, GOVERNMENT = 4,
DOWNLOAD = 5, DOWNLOAD = 5,
EP3_DOWNLOAD = 6, EP3_DOWNLOAD = 6,
// 7 can't be used as a menu type (it enables the per-episode filter) // 7 can't be used as a menu type (it enables the per-episode filter)
}; };
struct QuestCategoryIndex { struct QuestCategoryIndex {
struct Category { struct Category {
uint32_t category_id; uint32_t category_id;
uint16_t enabled_flags; uint16_t enabled_flags;
std::string directory_name; std::string directory_name;
std::string name; std::string name;
std::string description; std::string description;
explicit Category(uint32_t category_id, const JSON& json); explicit Category(uint32_t category_id, const JSON& json);
[[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const { [[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const {
return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type)); return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type));
} }
[[nodiscard]] inline bool enable_episode_filter() const { [[nodiscard]] inline bool enable_episode_filter() const {
return this->enabled_flags & 0x080; return this->enabled_flags & 0x080;
} }
[[nodiscard]] inline bool use_ep2_icon() const { [[nodiscard]] inline bool use_ep2_icon() const {
return this->enabled_flags & 0x100; return this->enabled_flags & 0x100;
} }
}; };
std::vector<std::shared_ptr<Category>> categories; std::vector<std::shared_ptr<Category>> categories;
explicit QuestCategoryIndex(const JSON& json); explicit QuestCategoryIndex(const JSON& json);
std::shared_ptr<const Category> at(uint32_t category_id) const; std::shared_ptr<const Category> at(uint32_t category_id) const;
}; };
struct VersionedQuest { struct VersionedQuest {
uint32_t quest_number; uint32_t quest_number;
uint32_t category_id; uint32_t category_id;
Episode episode; Episode episode;
bool allow_start_from_chat_command; bool allow_start_from_chat_command;
bool joinable; bool joinable;
int16_t lock_status_register; int16_t lock_status_register;
std::string name; std::string name;
Version version; Version version;
uint8_t language; uint8_t language;
bool is_dlq_encoded; bool is_dlq_encoded;
std::string short_description; std::string short_description;
std::string long_description; std::string long_description;
std::shared_ptr<const std::string> bin_contents; std::shared_ptr<const std::string> bin_contents;
std::shared_ptr<const std::string> dat_contents; std::shared_ptr<const std::string> dat_contents;
std::shared_ptr<const std::string> dat_contents_decompressed; std::shared_ptr<const std::string> dat_contents_decompressed;
std::shared_ptr<const std::string> pvr_contents; std::shared_ptr<const std::string> pvr_contents;
std::shared_ptr<const BattleRules> battle_rules; std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index; ssize_t challenge_template_index;
uint8_t description_flag; uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression; std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression; std::shared_ptr<const IntegralExpression> enabled_expression;
VersionedQuest( VersionedQuest(
uint32_t quest_number, uint32_t quest_number,
uint32_t category_id, uint32_t category_id,
Version version, Version version,
uint8_t language, uint8_t language,
std::shared_ptr<const std::string> bin_contents, std::shared_ptr<const std::string> bin_contents,
std::shared_ptr<const std::string> dat_contents, std::shared_ptr<const std::string> dat_contents,
std::shared_ptr<const std::string> pvr_contents, std::shared_ptr<const std::string> pvr_contents,
std::shared_ptr<const BattleRules> battle_rules = nullptr, std::shared_ptr<const BattleRules> battle_rules = nullptr,
ssize_t challenge_template_index = -1, ssize_t challenge_template_index = -1,
uint8_t description_flag = 0, uint8_t description_flag = 0,
std::shared_ptr<const IntegralExpression> available_expression = nullptr, std::shared_ptr<const IntegralExpression> available_expression = nullptr,
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr, std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
bool allow_start_from_chat_command = false, bool allow_start_from_chat_command = false,
bool force_joinable = false, bool force_joinable = false,
int16_t lock_status_register = -1); int16_t lock_status_register = -1);
std::string bin_filename() const; std::string bin_filename() const;
std::string dat_filename() const; std::string dat_filename() const;
std::string pvr_filename() const; std::string pvr_filename() const;
std::string xb_filename() const; std::string xb_filename() const;
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const; std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
std::string encode_qst() const; std::string encode_qst() const;
}; };
class Quest { class Quest {
public: public:
Quest() = delete; Quest() = delete;
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version); explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
Quest(const Quest&) = default; Quest(const Quest&) = default;
Quest(Quest&&) = default; Quest(Quest&&) = default;
Quest& operator=(const Quest&) = default; Quest& operator=(const Quest&) = default;
Quest& operator=(Quest&&) = default; Quest& operator=(Quest&&) = default;
void add_version(std::shared_ptr<const VersionedQuest> vq); void add_version(std::shared_ptr<const VersionedQuest> vq);
bool has_version(Version v, uint8_t language) const; bool has_version(Version v, uint8_t language) const;
bool has_version_any_language(Version v) const; bool has_version_any_language(Version v) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const; std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
static uint32_t versions_key(Version v, uint8_t language); static uint32_t versions_key(Version v, uint8_t language);
uint32_t quest_number; uint32_t quest_number;
uint32_t category_id; uint32_t category_id;
Episode episode; Episode episode;
bool allow_start_from_chat_command; bool allow_start_from_chat_command;
bool joinable; bool joinable;
int16_t lock_status_register; int16_t lock_status_register;
std::string name; std::string name;
std::shared_ptr<const BattleRules> battle_rules; std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index; ssize_t challenge_template_index;
uint8_t description_flag; uint8_t description_flag;
std::shared_ptr<const IntegralExpression> available_expression; std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression; std::shared_ptr<const IntegralExpression> enabled_expression;
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions; std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
}; };
struct QuestIndex { struct QuestIndex {
enum class IncludeState { enum class IncludeState {
HIDDEN = 0, HIDDEN = 0,
AVAILABLE, AVAILABLE,
DISABLED, DISABLED,
}; };
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>; using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
std::string directory; std::string directory;
std::shared_ptr<const QuestCategoryIndex> category_index; std::shared_ptr<const QuestCategoryIndex> category_index;
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number; std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
std::map<std::string, std::shared_ptr<Quest>> quests_by_name; 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; 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); 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(uint32_t quest_number) const;
std::shared_ptr<const Quest> get(const std::string& name) const; std::shared_ptr<const Quest> get(const std::string& name) const;
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories( std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
QuestMenuType menu_type, QuestMenuType menu_type,
Episode episode, Episode episode,
Version version, Version version,
IncludeCondition include_condition = nullptr) const; IncludeCondition include_condition = nullptr) const;
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter( std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
Episode episode, Episode episode,
Version version, Version version,
uint32_t category_id, uint32_t category_id,
IncludeCondition include_condition = nullptr, IncludeCondition include_condition = nullptr,
size_t limit = 0) const; size_t limit = 0) const;
}; };
std::string encode_download_quest_data( std::string encode_download_quest_data(
const std::string& compressed_data, const std::string& compressed_data,
size_t decompressed_size = 0, size_t decompressed_size = 0,
uint32_t encryption_seed = 0); uint32_t encryption_seed = 0);
std::string decode_gci_data( std::string decode_gci_data(
const std::string& data, const std::string& data,
ssize_t find_seed_num_threads = -1, ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1, int64_t known_seed = -1,
bool skip_checksum = false); bool skip_checksum = false);
std::string decode_vms_data( std::string decode_vms_data(
const std::string& data, const std::string& data,
ssize_t find_seed_num_threads = -1, ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1, int64_t known_seed = -1,
bool skip_checksum = false); bool skip_checksum = false);
std::string decode_dlq_data(const std::string& data); std::string decode_dlq_data(const std::string& data);
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data); std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data);
std::string encode_qst_file( std::string encode_qst_file(
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files, const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
const std::string& name, const std::string& name,
uint32_t quest_number, uint32_t quest_number,
const std::string& xb_filename, const std::string& xb_filename,
Version version, Version version,
bool is_dlq_encoded); 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include <array> #include <array>
#include <memory> #include <memory>
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <random> #include <random>
#include <string> #include <string>
#include "AFSArchive.hh" #include "AFSArchive.hh"
#include "GSLArchive.hh" #include "GSLArchive.hh"
#include "ItemNameIndex.hh" #include "ItemNameIndex.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
class RareItemSet { class RareItemSet {
public: public:
struct ExpandedDrop { struct ExpandedDrop {
uint32_t probability = 0; uint32_t probability = 0;
ItemData data; ItemData data;
std::string str() const; std::string str() const;
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const; std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
}; };
RareItemSet(); RareItemSet();
RareItemSet(const AFSArchive& afs, bool is_v1); RareItemSet(const AFSArchive& afs, bool is_v1);
RareItemSet(const GSLArchive& gsl, bool is_big_endian); RareItemSet(const GSLArchive& gsl, bool is_big_endian);
RareItemSet(const std::string& rel, 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(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
~RareItemSet() = default; ~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_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::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_afs(bool is_v1) const;
std::string serialize_gsl(bool big_endian) const; std::string serialize_gsl(bool big_endian) const;
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const; JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void multiply_all_rates(double factor); void multiply_all_rates(double factor);
void print_collection( void print_collection(
FILE* stream, FILE* stream,
GameMode mode, GameMode mode,
Episode episode, Episode episode,
uint8_t difficulty, uint8_t difficulty,
uint8_t section_id, uint8_t section_id,
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const; std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_all_collections(FILE* stream, 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: protected:
struct SpecCollection { struct SpecCollection {
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs; std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
std::vector<std::vector<ExpandedDrop>> box_area_to_specs; std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
}; };
struct ParsedRELData { struct ParsedRELData {
struct PackedDrop { struct PackedDrop {
uint8_t probability = 0; uint8_t probability = 0;
parray<uint8_t, 3> item_code; parray<uint8_t, 3> item_code;
PackedDrop() = default; PackedDrop() = default;
explicit PackedDrop(const ExpandedDrop&); explicit PackedDrop(const ExpandedDrop&);
ExpandedDrop expand() const; ExpandedDrop expand() const;
} __packed_ws__(PackedDrop, 4); } __packed_ws__(PackedDrop, 4);
template <bool IsBigEndian> template <bool IsBigEndian>
struct OffsetsT { struct OffsetsT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type; 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) /* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
/* 04 */ U32T box_count; // Usually 30 (0x1E) /* 04 */ U32T box_count; // Usually 30 (0x1E)
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E> /* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E> /* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
/* 10 */ /* 10 */
} __packed__; } __packed__;
using Offsets = OffsetsT<false>; using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>; using OffsetsBE = OffsetsT<true>;
check_struct_size(Offsets, 0x10); check_struct_size(Offsets, 0x10);
check_struct_size(OffsetsBE, 0x10); check_struct_size(OffsetsBE, 0x10);
struct BoxRare { struct BoxRare {
uint8_t area; uint8_t area;
ExpandedDrop drop; ExpandedDrop drop;
}; };
std::vector<ExpandedDrop> monster_rares; std::vector<ExpandedDrop> monster_rares;
std::vector<BoxRare> box_rares; std::vector<BoxRare> box_rares;
ParsedRELData() = default; ParsedRELData() = default;
ParsedRELData(StringReader r, bool big_endian, bool is_v1); ParsedRELData(StringReader r, bool big_endian, bool is_v1);
explicit ParsedRELData(const SpecCollection& collection); explicit ParsedRELData(const SpecCollection& collection);
std::string serialize(bool big_endian, bool is_v1) const; std::string serialize(bool big_endian, bool is_v1) const;
template <bool IsBigEndian> template <bool IsBigEndian>
void parse_t(StringReader r, bool is_v1); void parse_t(StringReader r, bool is_v1);
template <bool IsBigEndian> template <bool IsBigEndian>
std::string serialize_t(bool is_v1) const; std::string serialize_t(bool is_v1) const;
SpecCollection as_collection() const; SpecCollection as_collection() const;
}; };
std::unordered_map<uint16_t, SpecCollection> collections; std::unordered_map<uint16_t, SpecCollection> collections;
const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const; 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 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 uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
static uint32_t expand_rate(uint8_t pc); static uint32_t expand_rate(uint8_t pc);
static uint8_t compress_rate(uint32_t probability); 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 #pragma once
#include <memory> #include <memory>
#include <string> #include <string>
#include "Client.hh" #include "Client.hh"
#include "ServerState.hh" #include "ServerState.hh"
std::shared_ptr<Lobby> create_game_generic( std::shared_ptr<Lobby> create_game_generic(
std::shared_ptr<ServerState> s, std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
const std::string& name, const std::string& name,
const std::string& password = "", const std::string& password = "",
Episode episode = Episode::EP1, Episode episode = Episode::EP1,
GameMode mode = GameMode::NORMAL, GameMode mode = GameMode::NORMAL,
uint8_t difficulty = 0, uint8_t difficulty = 0,
bool allow_v1 = false, bool allow_v1 = false,
std::shared_ptr<Lobby> watched_lobby = nullptr, std::shared_ptr<Lobby> watched_lobby = nullptr,
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = 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 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_connect(std::shared_ptr<Client> c);
void on_disconnect(std::shared_ptr<Client> c); void on_disconnect(std::shared_ptr<Client> c);
void on_login_complete(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(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 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_login_server(std::shared_ptr<Client> c);
void send_client_to_lobby_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); 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 #pragma once
#include <stdint.h> #include <stdint.h>
#include "Client.hh" #include "Client.hh"
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "Lobby.hh" #include "Lobby.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ServerState.hh" #include "ServerState.hh"
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data); 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); bool subcommand_is_implemented(uint8_t which);
void send_item_notification_if_needed( void send_item_notification_if_needed(
std::shared_ptr<ServerState> s, std::shared_ptr<ServerState> s,
Channel& ch, Channel& ch,
const Client::Config& config, const Client::Config& config,
const ItemData& item, const ItemData& item,
bool is_from_rare_table); bool is_from_rare_table);
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size); G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
struct DropReconcileResult { struct DropReconcileResult {
uint8_t effective_rt_index; uint8_t effective_rt_index;
bool is_box; bool is_box;
bool should_drop; bool should_drop;
bool ignore_def; bool ignore_def;
}; };
DropReconcileResult reconcile_drop_request_with_map( DropReconcileResult reconcile_drop_request_with_map(
PrefixedLogger& log, PrefixedLogger& log,
Channel& client_channel, Channel& client_channel,
G_SpecializableItemDropRequest_6xA2& cmd, G_SpecializableItemDropRequest_6xA2& cmd,
Version version, Version version,
Episode episode, Episode episode,
const Client::Config& config, const Client::Config& config,
std::shared_ptr<Map> map, std::shared_ptr<Map> map,
bool mark_drop); bool mark_drop);
class Parsed6x70Data { class Parsed6x70Data {
public: public:
Version from_version; Version from_version;
bool from_client_customization; bool from_client_customization;
Version item_version; Version item_version;
G_SyncPlayerDispAndInventory_BaseDCNTE base; G_SyncPlayerDispAndInventory_BaseDCNTE base;
uint32_t unknown_a5_nte = 0; uint32_t unknown_a5_nte = 0;
uint32_t unknown_a6_nte = 0; uint32_t unknown_a6_nte = 0;
uint16_t bonus_hp_from_materials = 0; uint16_t bonus_hp_from_materials = 0;
uint16_t bonus_tp_from_materials = 0; uint16_t bonus_tp_from_materials = 0;
parray<uint8_t, 0x10> unknown_a5_112000; parray<uint8_t, 0x10> unknown_a5_112000;
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final; parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
uint32_t language = 0; uint32_t language = 0;
uint32_t player_tag = 0; uint32_t player_tag = 0;
uint32_t guild_card_number = 0; uint32_t guild_card_number = 0;
uint32_t unknown_a6 = 0; uint32_t unknown_a6 = 0;
uint32_t battle_team_number = 0; uint32_t battle_team_number = 0;
Telepipe6x70 telepipe; Telepipe6x70 telepipe;
uint32_t unknown_a8 = 0; uint32_t unknown_a8 = 0;
parray<uint8_t, 0x10> unknown_a9_nte_112000; parray<uint8_t, 0x10> unknown_a9_nte_112000;
G_Unknown_6x70_SubA1 unknown_a9_final; G_Unknown_6x70_SubA1 unknown_a9_final;
uint32_t area = 0; uint32_t area = 0;
uint32_t flags2 = 0; uint32_t flags2 = 0;
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
PlayerVisualConfig visual; PlayerVisualConfig visual;
std::string name; std::string name;
PlayerStats stats; PlayerStats stats;
uint32_t num_items = 0; uint32_t num_items = 0;
parray<PlayerInventoryItem, 0x1E> items; parray<PlayerInventoryItem, 0x1E> items;
uint32_t floor = 0; uint32_t floor = 0;
uint64_t xb_user_id = 0; uint64_t xb_user_id = 0;
uint32_t xb_unknown_a16 = 0; uint32_t xb_unknown_a16 = 0;
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd, const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
uint8_t language, uint8_t language,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd, const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_GC_6x70& cmd, const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_XB_6x70& cmd, const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BB_6x70& cmd, const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const; 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_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_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_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_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; G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
uint64_t default_xb_user_id() const; uint64_t default_xb_user_id() const;
void clear_v1_unused_item_fields(); void clear_v1_unused_item_fields();
void clear_dc_protos_unused_item_fields(); void clear_dc_protos_unused_item_fields();
protected: protected:
Parsed6x70Data( Parsed6x70Data(
const G_SyncPlayerDispAndInventory_BaseV1& base, const G_SyncPlayerDispAndInventory_BaseV1& base,
uint32_t guild_card_number, uint32_t guild_card_number,
Version from_version, Version from_version,
bool from_client_customization); bool from_client_customization);
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const; 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 #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <deque> #include <deque>
#include <memory> #include <memory>
#include <string> #include <string>
#include "Channel.hh" #include "Channel.hh"
#include "ServerState.hh" #include "ServerState.hh"
#include "Version.hh" #include "Version.hh"
class ReplaySession { class ReplaySession {
public: public:
ReplaySession( ReplaySession(
std::shared_ptr<struct event_base> base, std::shared_ptr<struct event_base> base,
FILE* input_log, FILE* input_log,
std::shared_ptr<ServerState> state, std::shared_ptr<ServerState> state,
bool require_basic_credentials); bool require_basic_credentials);
ReplaySession(const ReplaySession&) = delete; ReplaySession(const ReplaySession&) = delete;
ReplaySession(ReplaySession&&) = delete; ReplaySession(ReplaySession&&) = delete;
ReplaySession& operator=(const ReplaySession&) = delete; ReplaySession& operator=(const ReplaySession&) = delete;
ReplaySession& operator=(ReplaySession&&) = delete; ReplaySession& operator=(ReplaySession&&) = delete;
~ReplaySession() = default; ~ReplaySession() = default;
void start(); void start();
private: private:
struct Event { struct Event {
enum class Type { enum class Type {
CONNECT = 0, CONNECT = 0,
DISCONNECT, DISCONNECT,
SEND, SEND,
RECEIVE, RECEIVE,
}; };
Type type; Type type;
uint64_t client_id; uint64_t client_id;
std::string data; // Only used for SEND and RECEIVE std::string data; // Only used for SEND and RECEIVE
std::string mask; // Only used for RECEIVE std::string mask; // Only used for RECEIVE
bool allow_size_disparity; bool allow_size_disparity;
bool complete; bool complete;
size_t line_num; size_t line_num;
std::shared_ptr<Event> next_event; std::shared_ptr<Event> next_event;
Event(Type type, uint64_t client_id, size_t line_num); Event(Type type, uint64_t client_id, size_t line_num);
std::string str() const; std::string str() const;
}; };
struct Client { struct Client {
uint64_t id; uint64_t id;
uint16_t port; uint16_t port;
Version version; Version version;
Channel channel; Channel channel;
std::deque<std::shared_ptr<Event>> receive_events; std::deque<std::shared_ptr<Event>> receive_events;
std::shared_ptr<Event> disconnect_event; std::shared_ptr<Event> disconnect_event;
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version); Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
std::string str() const; std::string str() const;
}; };
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
bool require_basic_credentials; bool require_basic_credentials;
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients; std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client; std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::shared_ptr<Event> first_event; std::shared_ptr<Event> first_event;
std::shared_ptr<Event> last_event; std::shared_ptr<Event> last_event;
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> timeout_ev; std::shared_ptr<struct event> timeout_ev;
size_t commands_sent; size_t commands_sent;
size_t bytes_sent; size_t bytes_sent;
size_t commands_received; size_t commands_received;
size_t bytes_received; size_t bytes_received;
std::shared_ptr<ReplaySession::Event> create_event( std::shared_ptr<ReplaySession::Event> create_event(
Event::Type type, std::shared_ptr<Client> c, size_t line_num); Event::Type type, std::shared_ptr<Client> c, size_t line_num);
void update_timeout_event(); void update_timeout_event();
void apply_default_mask(std::shared_ptr<Event> ev); void apply_default_mask(std::shared_ptr<Event> ev);
void check_for_password(std::shared_ptr<const Event> ev) const; 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_timeout(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_command_received( static void dispatch_on_command_received(
Channel& ch, uint16_t command, uint32_t flag, std::string& data); Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void dispatch_on_error(Channel& ch, short events); static void dispatch_on_error(Channel& ch, short events);
void on_command_received( void on_command_received(
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data); 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 on_error(std::shared_ptr<Client> c, short events);
void execute_pending_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 #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <memory> #include <memory>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <unordered_set> #include <unordered_set>
#include "Client.hh" #include "Client.hh"
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "FunctionCompiler.hh" #include "FunctionCompiler.hh"
#include "Lobby.hh" #include "Lobby.hh"
#include "Menu.hh" #include "Menu.hh"
#include "Quest.hh" #include "Quest.hh"
#include "Server.hh" #include "Server.hh"
#include "Text.hh" #include "Text.hh"
extern const std::unordered_set<uint32_t> v2_crypt_initial_client_commands; 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<uint32_t> v3_crypt_initial_client_commands;
extern const std::unordered_set<std::string> bb_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; constexpr size_t GC_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
// TODO: Many of these functions should take a Channel& instead of a // TODO: Many of these functions should take a Channel& instead of a
// shared_ptr<Client>. Refactor functions appropriately. // shared_ptr<Client>. Refactor functions appropriately.
// Note: There are so many versions of this function for a few reasons: // 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 // - 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.) // command to one client, sometimes to everyone in a lobby, etc.)
// - For the const void* versions, the data and size arguments should not be // - 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 // 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 // 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*). // data in the calling function is changed from string to void*).
template <typename CmdT> template <typename CmdT>
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) { 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) { if (c->game_join_command_queue) {
c->log.info("Client not ready to receive game commands; adding to queue"); c->log.info("Client not ready to receive game commands; adding to queue");
auto& q_cmd = c->game_join_command_queue->emplace_back(); auto& q_cmd = c->game_join_command_queue->emplace_back();
q_cmd.command = command; q_cmd.command = command;
q_cmd.flag = flag; q_cmd.flag = flag;
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd // 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? // 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)); q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
} else { } else {
send_command(c, command, flag, &cmd, sizeof(cmd)); send_command(c, command, flag, &cmd, sizeof(cmd));
} }
} }
void send_command(std::shared_ptr<Client> c, uint16_t command, void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks); uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
void send_command(std::shared_ptr<Client> c, uint16_t command, void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const void* data, size_t size); uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<Client> c, uint16_t command, inline void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag) { uint32_t flag) {
send_command(c, command, flag, nullptr, 0); send_command(c, command, flag, nullptr, 0);
} }
void send_command_excluding_client(std::shared_ptr<Lobby> l, void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::shared_ptr<Client> c, uint16_t command, uint32_t flag,
const void* data, size_t size); const void* data, size_t size);
inline void send_command_excluding_client(std::shared_ptr<Lobby> l, inline void send_command_excluding_client(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, uint16_t command, uint32_t flag) { std::shared_ptr<Client> c, uint16_t command, uint32_t flag) {
send_command_excluding_client(l, c, command, flag, nullptr, 0); send_command_excluding_client(l, c, command, flag, nullptr, 0);
} }
void send_command_if_not_loading(std::shared_ptr<Lobby> l, void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const void* data, size_t size); uint16_t command, uint32_t flag, const void* data, size_t size);
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l, inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const std::string& data) { uint16_t command, uint32_t flag, const std::string& data) {
send_command_if_not_loading(l, command, flag, data.data(), data.size()); send_command_if_not_loading(l, command, flag, data.data(), data.size());
} }
template <typename StructT> template <typename StructT>
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l, inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
uint16_t command, uint32_t flag, const StructT& data) { uint16_t command, uint32_t flag, const StructT& data) {
send_command_if_not_loading(l, command, flag, &data, sizeof(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, void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag,
const void* data, size_t size); const void* data, size_t size);
inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) { inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) {
send_command(l, command, flag, nullptr, 0); send_command(l, command, flag, nullptr, 0);
} }
void send_command(std::shared_ptr<ServerState> s, uint16_t command, void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag, const void* data, size_t size); uint32_t flag, const void* data, size_t size);
inline void send_command(std::shared_ptr<ServerState> s, uint16_t command, inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag) { uint32_t flag) {
send_command(s, command, flag, nullptr, 0); send_command(s, command, flag, nullptr, 0);
} }
template <typename TargetT, typename StructT> template <typename TargetT, typename StructT>
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command, static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data) { uint32_t flag, const StructT& data) {
send_command(c, command, flag, &data, sizeof(data)); send_command(c, command, flag, &data, sizeof(data));
} }
template <typename TargetT> template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::string& data) { uint32_t flag, const std::string& data) {
send_command(c, command, flag, data.data(), data.size()); send_command(c, command, flag, data.data(), data.size());
} }
template <typename TargetT, typename StructT> template <typename TargetT, typename StructT>
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command, void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::vector<StructT>& data) { uint32_t flag, const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT)); send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
} }
template <typename StructT> template <typename StructT>
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag, void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) { const std::vector<StructT>& data) {
ch.send(command, flag, data.data(), data.size() * sizeof(StructT)); ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
} }
template <typename TargetT, typename StructT, typename EntryT> template <typename TargetT, typename StructT, typename EntryT>
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command, 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) { uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT)); std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()), all_data.append(reinterpret_cast<const char*>(array_data.data()),
array_data.size() * sizeof(EntryT)); array_data.size() * sizeof(EntryT));
send_command(c, command, flag, all_data.data(), all_data.size()); send_command(c, command, flag, all_data.data(), all_data.size());
} }
void send_command_with_header(Channel& c, const void* data, size_t size); void send_command_with_header(Channel& c, const void* data, size_t size);
enum SendServerInitFlag { enum SendServerInitFlag {
IS_INITIAL_CONNECTION = 0x01, IS_INITIAL_CONNECTION = 0x01,
USE_SECONDARY_MESSAGE = 0x02, USE_SECONDARY_MESSAGE = 0x02,
}; };
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4> S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console( prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags); uint32_t server_key, uint32_t client_key, uint8_t flags);
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4> S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
prepare_server_init_contents_bb( prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key, const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key, const parray<uint8_t, 0x30>& client_key,
uint8_t flags); uint8_t flags);
void send_server_init(std::shared_ptr<Client> c, 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 send_update_client_config(std::shared_ptr<Client> c, bool always_send);
void empty_function_call_response_handler(uint32_t, uint32_t); void empty_function_call_response_handler(uint32_t, uint32_t);
void send_quest_buffer_overflow(std::shared_ptr<Client> c); 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); void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
std::string prepare_send_function_call_data( std::string prepare_send_function_call_data(
std::shared_ptr<const CompiledFunctionCode> code, std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes, const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data, const void* suffix_data,
size_t suffix_size, size_t suffix_size,
uint32_t checksum_addr, uint32_t checksum_addr,
uint32_t checksum_size, uint32_t checksum_size,
uint32_t override_relocations_offset, uint32_t override_relocations_offset,
bool use_encrypted_format); bool use_encrypted_format);
void send_function_call( void send_function_call(
Channel& ch, Channel& ch,
const Client::Config& client_config, const Client::Config& client_config,
std::shared_ptr<const CompiledFunctionCode> code, std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {}, const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr, const void* suffix_data = nullptr,
size_t suffix_size = 0, size_t suffix_size = 0,
uint32_t checksum_addr = 0, uint32_t checksum_addr = 0,
uint32_t checksum_size = 0, uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 0); uint32_t override_relocations_offset = 0);
void send_function_call( void send_function_call(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const CompiledFunctionCode> code, std::shared_ptr<const CompiledFunctionCode> code,
const std::unordered_map<std::string, uint32_t>& label_writes = {}, const std::unordered_map<std::string, uint32_t>& label_writes = {},
const void* suffix_data = nullptr, const void* suffix_data = nullptr,
size_t suffix_size = 0, size_t suffix_size = 0,
uint32_t checksum_addr = 0, uint32_t checksum_addr = 0,
uint32_t checksum_size = 0, uint32_t checksum_size = 0,
uint32_t override_relocations_offset = 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); 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_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
void send_pc_console_split_reconnect( void send_pc_console_split_reconnect(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
uint32_t address, uint32_t address,
uint16_t pc_port, uint16_t pc_port,
uint16_t console_port); uint16_t console_port);
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error); 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_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_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_accept_client_checksum_bb(std::shared_ptr<Client> c);
void send_guild_card_header_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_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_index_bb(std::shared_ptr<Client> c);
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index); 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_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_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_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_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_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_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_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(std::shared_ptr<Client> c, const std::string& text);
void send_ship_info(Channel& ch, 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(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<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<Lobby> l, const std::string& text);
void send_text_message(std::shared_ptr<ServerState> s, 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_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<Client> c, const std::string& text, const std::string& scrolling);
void send_text_or_scrolling_message( 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::shared_ptr<Lobby> l, std::shared_ptr<Client> exclude_c, const std::string& text, const std::string& scrolling);
std::string prepare_chat_data( std::string prepare_chat_data(
Version version, Version version,
uint8_t language, uint8_t language,
uint8_t from_client_id, uint8_t from_client_id,
const std::string& from_name, const std::string& from_name,
const std::string& text, const std::string& text,
char private_flags); char private_flags);
void send_chat_message_from_client( void send_chat_message_from_client(
Channel& ch, Channel& ch,
const std::string& text, const std::string& text,
char private_flags); char private_flags);
void send_prepared_chat_message( void send_prepared_chat_message(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
uint32_t from_guild_card_number, uint32_t from_guild_card_number,
const std::string& prepared_data); const std::string& prepared_data);
void send_prepared_chat_message( void send_prepared_chat_message(
std::shared_ptr<Lobby> l, std::shared_ptr<Lobby> l,
uint32_t from_guild_card_number, uint32_t from_guild_card_number,
const std::string& prepared_data); const std::string& prepared_data);
void send_chat_message( void send_chat_message(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
uint32_t from_guild_card_number, uint32_t from_guild_card_number,
const std::string& from_name, const std::string& from_name,
const std::string& text, const std::string& text,
char private_flags); char private_flags);
void send_simple_mail( void send_simple_mail(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
uint32_t from_guild_card_number, uint32_t from_guild_card_number,
const std::string& from_name, const std::string& from_name,
const std::string& text); const std::string& text);
void send_simple_mail( void send_simple_mail(
std::shared_ptr<ServerState> s, std::shared_ptr<ServerState> s,
uint32_t from_guild_card_number, uint32_t from_guild_card_number,
const std::string& from_name, const std::string& from_name,
const std::string& text); const std::string& text);
template <typename TargetT> template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf( __attribute__((format(printf, 2, 3))) void send_text_message_printf(
TargetT& t, const char* format, ...) { TargetT& t, const char* format, ...) {
va_list va; va_list va;
va_start(va, format); va_start(va, format);
std::string buf = string_vprintf(format, va); std::string buf = string_vprintf(format, va);
va_end(va); va_end(va);
return send_text_message(t, buf.c_str()); return send_text_message(t, buf.c_str());
} }
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf( __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
std::shared_ptr<ServerState> s, const char* format, ...); std::shared_ptr<ServerState> s, const char* format, ...);
void send_info_board(std::shared_ptr<Client> c); void send_info_board(std::shared_ptr<Client> c);
void send_choice_search_choices(std::shared_ptr<Client> c); void send_choice_search_choices(std::shared_ptr<Client> c);
void send_card_search_result( void send_card_search_result(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<Client> result, std::shared_ptr<Client> result,
std::shared_ptr<Lobby> result_lobby); std::shared_ptr<Lobby> result_lobby);
void send_guild_card( void send_guild_card(
Channel& ch, Channel& ch,
uint32_t guild_card_number, uint32_t guild_card_number,
uint64_t xb_user_id, uint64_t xb_user_id,
const std::string& name, const std::string& name,
const std::string& team_name, const std::string& team_name,
const std::string& description, const std::string& description,
uint8_t language, uint8_t language,
uint8_t section_id, uint8_t section_id,
uint8_t char_class); uint8_t char_class);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source); 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_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
void send_game_menu( void send_game_menu(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
bool is_spectator_team_list, bool is_spectator_team_list,
bool is_tournament_game_list); bool is_tournament_game_list);
void send_quest_menu( void send_quest_menu(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests, const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
bool is_download_menu); bool is_download_menu);
void send_quest_categories_menu( void send_quest_categories_menu(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const QuestIndex> quest_index, std::shared_ptr<const QuestIndex> quest_index,
QuestMenuType menu_type, QuestMenuType menu_type,
Episode episode); Episode episode);
void send_lobby_list(std::shared_ptr<Client> c); 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_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_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_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_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_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_self_leave_notification(std::shared_ptr<Client> c);
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false); 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_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_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_arrow_update(std::shared_ptr<Lobby> l);
void send_unblock_join(std::shared_ptr<Client> c); void send_unblock_join(std::shared_ptr<Client> c);
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client); void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
enum PlayerStatsChange { enum PlayerStatsChange {
SUBTRACT_HP = 0, SUBTRACT_HP = 0,
SUBTRACT_TP = 1, SUBTRACT_TP = 1,
SUBTRACT_MESETA = 2, SUBTRACT_MESETA = 2,
ADD_HP = 3, ADD_HP = 3,
ADD_TP = 4, ADD_TP = 4,
}; };
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount); 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_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(std::shared_ptr<Client> c);
void send_remove_conditions(Channel& ch, uint16_t client_id); 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(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<Client> c, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Lobby> l, 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_ep3_change_music(Channel& ch, uint32_t song);
void send_revive_player(std::shared_ptr<Client> c); void send_revive_player(std::shared_ptr<Client> c);
void send_game_join_sync_command( 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); 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( 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); 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( void send_game_join_sync_command_compressed(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
const void* data, const void* data,
size_t size, size_t size,
size_t decompressed_size, size_t decompressed_size,
uint8_t dc_nte_sc, uint8_t dc_nte_sc,
uint8_t dc_11_2000_sc, uint8_t dc_11_2000_sc,
uint8_t sc); uint8_t sc);
void send_game_item_state(std::shared_ptr<Client> c); void send_game_item_state(std::shared_ptr<Client> c);
void send_game_enemy_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_object_state(std::shared_ptr<Client> c);
void send_game_set_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_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_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, 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); 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, 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); bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_stacked_item_to_channel( void send_drop_stacked_item_to_channel(
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z); std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
void send_drop_stacked_item_to_lobby( void send_drop_stacked_item_to_lobby(
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z); 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_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_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_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_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_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_item_identify_result(std::shared_ptr<Client> c);
void send_bank(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_shop(std::shared_ptr<Client> c, uint8_t shop_type);
void send_level_up(std::shared_ptr<Client> c); void send_level_up(std::shared_ptr<Client> c);
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount); void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
void send_set_exp_multiplier(std::shared_ptr<Lobby> l); 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_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(Channel& ch, uint16_t function_id);
void send_quest_function_call(std::shared_ptr<Client> c, 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_card_list_update(std::shared_ptr<Client> c);
void send_ep3_media_update( void send_ep3_media_update(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
uint32_t type, uint32_t type,
uint32_t which, uint32_t which,
const std::string& compressed_data); const std::string& compressed_data);
void send_ep3_rank_update(std::shared_ptr<Client> c); 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_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_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
void send_ep3_confirm_tournament_entry( void send_ep3_confirm_tournament_entry(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t); std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_tournament_list( void send_ep3_tournament_list(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
bool is_for_spectator_team_create); bool is_for_spectator_team_create);
void send_ep3_tournament_entry_list( void send_ep3_tournament_entry_list(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t, std::shared_ptr<const Episode3::Tournament> t,
bool is_for_spectator_team_create); bool is_for_spectator_team_create);
void send_ep3_tournament_info( void send_ep3_tournament_info(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t); std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c); 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_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
void send_ep3_tournament_details( void send_ep3_tournament_details(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t); std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_game_details( void send_ep3_game_details(
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l); std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_ep3_update_game_metadata(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_card_auction(std::shared_ptr<Lobby> l);
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l); void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
// Pass mask_key = 0 to unmask the command // Pass mask_key = 0 to unmask the command
void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key); void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key);
enum class QuestFileType { enum class QuestFileType {
ONLINE = 0, ONLINE = 0,
DOWNLOAD_WITHOUT_PVR, DOWNLOAD_WITHOUT_PVR,
DOWNLOAD_WITH_PVR, DOWNLOAD_WITH_PVR,
EPISODE_3, EPISODE_3,
GBA_DEMO, GBA_DEMO,
}; };
void send_open_quest_file( void send_open_quest_file(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
const std::string& quest_name, const std::string& quest_name,
const std::string& filename, const std::string& filename,
const std::string& xb_filename, const std::string& xb_filename,
uint32_t quest_number, uint32_t quest_number,
QuestFileType type, QuestFileType type,
std::shared_ptr<const std::string> contents); std::shared_ptr<const std::string> contents);
void send_quest_file_chunk( void send_quest_file_chunk(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
const std::string& filename, const std::string& filename,
size_t chunk_index, size_t chunk_index,
const void* data, const void* data,
size_t size, size_t size,
bool is_download_quest); bool is_download_quest);
bool send_quest_barrier_if_all_clients_ready(std::shared_ptr<Lobby> l); 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); 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_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<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<Lobby> l, uint8_t new_event);
void send_change_event(std::shared_ptr<ServerState> s, 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_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_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_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_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
void send_team_member_list(std::shared_ptr<Client> c); // 09EA void send_team_member_list(std::shared_ptr<Client> c); // 09EA
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA 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_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
+353 -353
View File
@@ -1,353 +1,353 @@
#include "Server.hh" #include "Server.hh"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <event2/buffer.h> #include <event2/buffer.h>
#include <event2/bufferevent.h> #include <event2/bufferevent.h>
#include <event2/event.h> #include <event2/event.h>
#include <event2/listener.h> #include <event2/listener.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Network.hh> #include <phosg/Network.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "ReceiveCommands.hh" #include "ReceiveCommands.hh"
using namespace std; using namespace std;
using namespace std::placeholders; using namespace std::placeholders;
void Server::disconnect_client(shared_ptr<Client> c) { void Server::disconnect_client(shared_ptr<Client> c) {
if (c->channel.virtual_network_id) { if (c->channel.virtual_network_id) {
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, 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) { } else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64, c->id); server_log.info("Client disconnected: C-%" PRIX64, c->id);
} else { } else {
server_log.info("Client C-%" PRIX64 " removed from game server", c->id); server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
} }
this->state->channel_to_client.erase(&c->channel); this->state->channel_to_client.erase(&c->channel);
c->channel.disconnect(); c->channel.disconnect();
try { try {
on_disconnect(c); on_disconnect(c);
} catch (const exception& e) { } catch (const exception& e) {
server_log.warning("Error during client disconnect cleanup: %s", e.what()); 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 // 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 // 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 // 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 // callback after the current event. This will also call the client's
// disconnect hooks (if any). // disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c)); this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients(); this->enqueue_destroy_clients();
} }
void Server::enqueue_destroy_clients() { void Server::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0); auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv); event_add(this->destroy_clients_ev.get(), &tv);
} }
void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) { void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Server*>(ctx)->destroy_clients(); reinterpret_cast<Server*>(ctx)->destroy_clients();
} }
void Server::destroy_clients() { void Server::destroy_clients() {
for (auto c_it = this->clients_to_destroy.begin(); for (auto c_it = this->clients_to_destroy.begin();
c_it != this->clients_to_destroy.end(); c_it != this->clients_to_destroy.end();
c_it = this->clients_to_destroy.erase(c_it)) { c_it = this->clients_to_destroy.erase(c_it)) {
auto c = *c_it; auto c = *c_it;
// Note: It's important to move the disconnect hooks out of the client here // 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 // because the hooks could modify c->disconnect_hooks while it's being
// iterated here, which would invalidate these iterators. // iterated here, which would invalidate these iterators.
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks); unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
for (auto h_it : hooks) { for (auto h_it : hooks) {
try { try {
h_it.second(); h_it.second();
} catch (const exception& e) { } catch (const exception& e) {
c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what()); c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what());
} }
} }
} }
} }
void Server::dispatch_on_listen_accept( void Server::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd, struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) { struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen); reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen);
} }
void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) { void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_listen_error(listener); reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
} }
void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
struct sockaddr_storage remote_addr; struct sockaddr_storage remote_addr;
get_socket_addresses(fd, nullptr, &remote_addr); get_socket_addresses(fd, nullptr, &remote_addr);
if (this->state->banned_ipv4_ranges->check(remote_addr)) { if (this->state->banned_ipv4_ranges->check(remote_addr)) {
close(fd); close(fd);
return; return;
} }
int listen_fd = evconnlistener_get_fd(listener); int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket; ListeningSocket* listening_socket;
try { try {
listening_socket = &this->listening_sockets.at(listen_fd); listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) { } catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client", server_log.warning("Can\'t determine version for socket %d; disconnecting client",
listen_fd); listen_fd);
close(fd); close(fd);
return; return;
} }
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); 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); 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_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error; c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this; c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c); this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)", server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str()); c->id, fd, listen_fd, listening_socket->addr_str.c_str());
try { try {
on_connect(c); on_connect(c);
} catch (const exception& e) { } catch (const exception& e) {
server_log.warning("Error during client initialization: %s", e.what()); server_log.warning("Error during client initialization: %s", e.what());
this->disconnect_client(c); this->disconnect_client(c);
} }
} }
void Server::connect_virtual_client( void Server::connect_virtual_client(
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
uint32_t address, uint32_t address,
uint16_t client_port, uint16_t client_port,
uint16_t server_port, uint16_t server_port,
Version version, Version version,
ServerBehavior initial_state) { ServerBehavior initial_state) {
auto c = make_shared<Client>(this->shared_from_this(), bev, virtual_network_id, version, 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_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error; c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this; c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c); this->state->channel_to_client.emplace(&c->channel, c);
server_log.info( server_log.info(
"Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI", "Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI",
c->id, c->id,
virtual_network_id, virtual_network_id,
server_port, server_port,
name_for_enum(version), name_for_enum(version),
name_for_enum(initial_state)); name_for_enum(initial_state));
// Manually set the remote address, since the bufferevent has no fd and the // Manually set the remote address, since the bufferevent has no fd and the
// Channel constructor can't figure out the virtual remote address // Channel constructor can't figure out the virtual remote address
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr); auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
remote_sin->sin_family = AF_INET; remote_sin->sin_family = AF_INET;
remote_sin->sin_addr.s_addr = htonl(address); remote_sin->sin_addr.s_addr = htonl(address);
remote_sin->sin_port = htons(client_port); remote_sin->sin_port = htons(client_port);
try { try {
on_connect(c); on_connect(c);
} catch (const exception& e) { } catch (const exception& e) {
server_log.error("Error during client initialization: %s", e.what()); server_log.error("Error during client initialization: %s", e.what());
this->disconnect_client(c); this->disconnect_client(c);
} }
} }
void Server::connect_virtual_client(shared_ptr<Client> c, Channel&& ch) { 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)); 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); this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client C-%" PRIX64 " added to game server", c->id); server_log.info("Client C-%" PRIX64 " added to game server", c->id);
} }
void Server::on_listen_error(struct evconnlistener* listener) { void Server::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)", server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err)); evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr); event_base_loopexit(this->base.get(), nullptr);
} }
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) { void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
Server* server = reinterpret_cast<Server*>(ch.context_obj); Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch); shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (c->should_disconnect) { if (c->should_disconnect) {
server->disconnect_client(c); server->disconnect_client(c);
} else { } else {
if (server->state->catch_handler_exceptions) { if (server->state->catch_handler_exceptions) {
try { try {
on_command(c, command, flag, data); on_command(c, command, flag, data);
} catch (const exception& e) { } catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what()); server_log.warning("Error processing client command: %s", e.what());
c->should_disconnect = true; c->should_disconnect = true;
} }
} else { } else {
on_command(c, command, flag, data); on_command(c, command, flag, data);
} }
if (c->should_disconnect) { if (c->should_disconnect) {
server->disconnect_client(c); server->disconnect_client(c);
} }
} }
} }
void Server::on_client_error(Channel& ch, short events) { void Server::on_client_error(Channel& ch, short events) {
Server* server = reinterpret_cast<Server*>(ch.context_obj); Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->state->channel_to_client.at(&ch); shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) { if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err, server_log.warning("Client caused error %d (%s)", err,
evutil_socket_error_to_string(err)); evutil_socket_error_to_string(err));
} }
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c); server->disconnect_client(c);
} }
} }
Server::Server( Server::Server(
shared_ptr<struct event_base> base, shared_ptr<struct event_base> base,
shared_ptr<ServerState> state) shared_ptr<ServerState> state)
: base(base), : base(base),
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free), destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
state(state) {} state(state) {}
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) { void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN); int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s", server_log.info("Listening on Unix socket %s on fd %d as %s",
socket_path.c_str(), fd, addr_str.c_str()); socket_path.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior); this->add_socket(addr_str, fd, version, behavior);
} }
void Server::listen( void Server::listen(
const std::string& addr_str, const std::string& addr_str,
const string& addr, const string& addr,
int port, int port,
Version version, Version version,
ServerBehavior behavior) { ServerBehavior behavior) {
if (port == 0) { if (port == 0) {
this->listen(addr_str, addr, version, behavior); this->listen(addr_str, addr, version, behavior);
} else { } else {
int fd = ::listen(addr, port, SOMAXCONN); int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port); string netloc_str = render_netloc(addr, port);
server_log.info("Listening on TCP interface %s on fd %d as %s", server_log.info("Listening on TCP interface %s on fd %d as %s",
netloc_str.c_str(), fd, addr_str.c_str()); netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version, behavior); this->add_socket(addr_str, fd, version, behavior);
} }
} }
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) { void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
this->listen(addr_str, "", port, version, behavior); this->listen(addr_str, "", port, version, behavior);
} }
Server::ListeningSocket::ListeningSocket( Server::ListeningSocket::ListeningSocket(
Server* s, const std::string& addr_str, Server* s, const std::string& addr_str,
int fd, Version version, ServerBehavior behavior) int fd, Version version, ServerBehavior behavior)
: addr_str(addr_str), : addr_str(addr_str),
fd(fd), fd(fd),
version(version), version(version),
behavior(behavior), behavior(behavior),
listener(evconnlistener_new( listener(evconnlistener_new(
s->base.get(), Server::dispatch_on_listen_accept, s, s->base.get(), Server::dispatch_on_listen_accept, s,
LEV_OPT_REUSEABLE, 0, this->fd), LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) { evconnlistener_free) {
evconnlistener_set_error_cb( evconnlistener_set_error_cb(
this->listener.get(), this->listener.get(),
Server::dispatch_on_listen_error); Server::dispatch_on_listen_error);
} }
void Server::add_socket( void Server::add_socket(
const std::string& addr_str, const std::string& addr_str,
int fd, int fd,
Version version, Version version,
ServerBehavior behavior) { ServerBehavior behavior) {
this->listening_sockets.emplace( this->listening_sockets.emplace(
piecewise_construct, forward_as_tuple(fd), piecewise_construct, forward_as_tuple(fd),
forward_as_tuple(this, addr_str, fd, version, behavior)); forward_as_tuple(this, addr_str, fd, version, behavior));
} }
shared_ptr<Client> Server::get_client() const { shared_ptr<Client> Server::get_client() const {
if (this->state->channel_to_client.empty()) { if (this->state->channel_to_client.empty()) {
throw runtime_error("no clients on game server"); throw runtime_error("no clients on game server");
} }
if (this->state->channel_to_client.size() > 1) { if (this->state->channel_to_client.size() > 1) {
throw runtime_error("multiple clients on game server"); throw runtime_error("multiple clients on game server");
} }
return this->state->channel_to_client.begin()->second; return this->state->channel_to_client.begin()->second;
} }
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const { vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
int64_t account_id_hex = -1; int64_t account_id_hex = -1;
int64_t account_id_dec = -1; int64_t account_id_dec = -1;
try { try {
account_id_dec = stoul(ident, nullptr, 10); account_id_dec = stoul(ident, nullptr, 10);
} catch (const invalid_argument&) { } catch (const invalid_argument&) {
} }
try { try {
account_id_hex = stoul(ident, nullptr, 16); account_id_hex = stoul(ident, nullptr, 16);
} catch (const invalid_argument&) { } catch (const invalid_argument&) {
} }
// TODO: It's kind of not great that we do a linear search here, but this is // 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. // only used in the shell, so it should be pretty rare.
vector<shared_ptr<Client>> results; vector<shared_ptr<Client>> results;
for (const auto& it : this->state->channel_to_client) { for (const auto& it : this->state->channel_to_client) {
auto c = it.second; auto c = it.second;
if (c->login && c->login->account->account_id == account_id_hex) { if (c->login && c->login->account->account_id == account_id_hex) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
if (c->login && c->login->account->account_id == account_id_dec) { if (c->login && c->login->account->account_id == account_id_dec) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) { if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) { if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
auto p = c->character(false, false); auto p = c->character(false, false);
if (p && p->disp.name.eq(ident, p->inventory.language)) { if (p && p->disp.name.eq(ident, p->inventory.language)) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
if (c->channel.name == ident) { if (c->channel.name == ident) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
if (starts_with(c->channel.name, ident + " ")) { if (starts_with(c->channel.name, ident + " ")) {
results.emplace_back(c); results.emplace_back(c);
continue; continue;
} }
} }
return results; return results;
} }
vector<shared_ptr<Client>> Server::all_clients() const { vector<shared_ptr<Client>> Server::all_clients() const {
vector<shared_ptr<Client>> ret; vector<shared_ptr<Client>> ret;
for (const auto& it : this->state->channel_to_client) { for (const auto& it : this->state->channel_to_client) {
ret.emplace_back(it.second); ret.emplace_back(it.second);
} }
return ret; return ret;
} }
shared_ptr<struct event_base> Server::get_base() const { shared_ptr<struct event_base> Server::get_base() const {
return this->base; return this->base;
} }
+82 -82
View File
@@ -1,82 +1,82 @@
#pragma once #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include "Client.hh" #include "Client.hh"
#include "ServerState.hh" #include "ServerState.hh"
class Server : public std::enable_shared_from_this<Server> { class Server : public std::enable_shared_from_this<Server> {
public: public:
Server() = delete; Server() = delete;
Server(const Server&) = delete; Server(const Server&) = delete;
Server(Server&&) = delete; Server(Server&&) = delete;
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state); Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
virtual ~Server() = default; 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& 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, 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 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 add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
void connect_virtual_client( void connect_virtual_client(
struct bufferevent* bev, struct bufferevent* bev,
uint64_t virtual_network_id, uint64_t virtual_network_id,
uint32_t address, uint32_t address,
uint16_t client_port, uint16_t client_port,
uint16_t server_port, uint16_t server_port,
Version version, Version version,
ServerBehavior initial_state); ServerBehavior initial_state);
void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch); void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch);
void disconnect_client(std::shared_ptr<Client> c); void disconnect_client(std::shared_ptr<Client> c);
std::shared_ptr<Client> get_client() const; 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>> get_clients_by_identifier(const std::string& ident) const;
std::vector<std::shared_ptr<Client>> all_clients() const; std::vector<std::shared_ptr<Client>> all_clients() const;
std::shared_ptr<struct event_base> get_base() const; std::shared_ptr<struct event_base> get_base() const;
inline std::shared_ptr<ServerState> get_state() const { inline std::shared_ptr<ServerState> get_state() const {
return this->state; return this->state;
} }
private: private:
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::shared_ptr<struct event> destroy_clients_ev; std::shared_ptr<struct event> destroy_clients_ev;
struct ListeningSocket { struct ListeningSocket {
std::string addr_str; std::string addr_str;
int fd; int fd;
Version version; Version version;
ServerBehavior behavior; ServerBehavior behavior;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener; std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket( ListeningSocket(
Server* s, Server* s,
const std::string& name, const std::string& name,
int fd, int fd,
Version version, Version version,
ServerBehavior behavior); ServerBehavior behavior);
}; };
std::unordered_map<int, ListeningSocket> listening_sockets; std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy; std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
void enqueue_destroy_clients(); void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx); static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
void destroy_clients(); void destroy_clients();
static void dispatch_on_listen_accept(struct evconnlistener* listener, static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, 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_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener); 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_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events); 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 #pragma once
#include <memory> #include <memory>
#include <string> #include <string>
#include <event2/event.h> #include <event2/event.h>
#include "ProxyServer.hh" #include "ProxyServer.hh"
#include "ServerState.hh" #include "ServerState.hh"
class ServerShell : public std::enable_shared_from_this<ServerShell> { class ServerShell : public std::enable_shared_from_this<ServerShell> {
public: public:
class exit_shell : public std::runtime_error { class exit_shell : public std::runtime_error {
public: public:
exit_shell(); exit_shell();
~exit_shell() = default; ~exit_shell() = default;
}; };
explicit ServerShell(std::shared_ptr<ServerState> state); explicit ServerShell(std::shared_ptr<ServerState> state);
ServerShell(const ServerShell&) = delete; ServerShell(const ServerShell&) = delete;
ServerShell(ServerShell&&) = delete; ServerShell(ServerShell&&) = delete;
ServerShell& operator=(const ServerShell&) = delete; ServerShell& operator=(const ServerShell&) = delete;
ServerShell& operator=(ServerShell&&) = delete; ServerShell& operator=(ServerShell&&) = delete;
~ServerShell(); ~ServerShell();
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name); std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
protected: protected:
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
std::thread th; std::thread th;
void thread_fn(); 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 #pragma once
#include <event2/event.h> #include <event2/event.h>
#include <atomic> #include <atomic>
#include <map> #include <map>
#include <memory> #include <memory>
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "Account.hh" #include "Account.hh"
#include "Client.hh" #include "Client.hh"
#include "CommonItemSet.hh" #include "CommonItemSet.hh"
#include "DNSServer.hh" #include "DNSServer.hh"
#include "Episode3/DataIndexes.hh" #include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh" #include "Episode3/Tournament.hh"
#include "EventUtils.hh" #include "EventUtils.hh"
#include "FunctionCompiler.hh" #include "FunctionCompiler.hh"
#include "GSLArchive.hh" #include "GSLArchive.hh"
#include "IPV4RangeSet.hh" #include "IPV4RangeSet.hh"
#include "ItemNameIndex.hh" #include "ItemNameIndex.hh"
#include "ItemParameterTable.hh" #include "ItemParameterTable.hh"
#include "LevelTable.hh" #include "LevelTable.hh"
#include "Lobby.hh" #include "Lobby.hh"
#include "Menu.hh" #include "Menu.hh"
#include "PatchServer.hh" #include "PatchServer.hh"
#include "PlayerFilesManager.hh" #include "PlayerFilesManager.hh"
#include "Quest.hh" #include "Quest.hh"
#include "TeamIndex.hh" #include "TeamIndex.hh"
#include "WordSelectTable.hh" #include "WordSelectTable.hh"
// Forward declarations due to reference cycles // Forward declarations due to reference cycles
class ProxyServer; class ProxyServer;
class Server; class Server;
class IPStackSimulator; class IPStackSimulator;
struct PortConfiguration { struct PortConfiguration {
std::string name; std::string name;
std::string addr; // Blank = listen on all interfaces (default) std::string addr; // Blank = listen on all interfaces (default)
uint16_t port; uint16_t port;
Version version; Version version;
ServerBehavior behavior; ServerBehavior behavior;
}; };
struct ServerState : public std::enable_shared_from_this<ServerState> { struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior { enum class RunShellBehavior {
DEFAULT = 0, DEFAULT = 0,
ALWAYS, ALWAYS,
NEVER, NEVER,
}; };
enum class BehaviorSwitch { enum class BehaviorSwitch {
OFF = 0, OFF = 0,
OFF_BY_DEFAULT, OFF_BY_DEFAULT,
ON_BY_DEFAULT, ON_BY_DEFAULT,
ON, ON,
}; };
static inline bool behavior_enabled(BehaviorSwitch b) { static inline bool behavior_enabled(BehaviorSwitch b) {
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON); return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
} }
static inline bool behavior_can_be_overridden(BehaviorSwitch b) { static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT); return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
} }
uint64_t creation_time; uint64_t creation_time;
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
std::string config_filename; std::string config_filename;
std::shared_ptr<const JSON> config_json; std::shared_ptr<const JSON> config_json;
bool is_replay = false; bool is_replay = false;
bool one_time_config_loaded = false; bool one_time_config_loaded = false;
bool default_lobbies_created = false; bool default_lobbies_created = false;
std::string name; std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config; 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::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username; std::string username;
std::string dns_server_addr; std::string dns_server_addr;
uint16_t dns_server_port = 0; uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses; std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses; std::vector<std::string> ppp_stack_addresses;
std::vector<std::string> ppp_raw_addresses; std::vector<std::string> ppp_raw_addresses;
std::vector<std::string> http_addresses; std::vector<std::string> http_addresses;
uint64_t client_ping_interval_usecs = 30000000; uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000; uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000; uint64_t patch_client_idle_timeout_usecs = 300000000;
bool ip_stack_debug = false; bool ip_stack_debug = false;
bool allow_unregistered_users = false; bool allow_unregistered_users = false;
bool allow_pc_nte = false; bool allow_pc_nte = false;
bool use_temp_accounts_for_prototypes = true; bool use_temp_accounts_for_prototypes = true;
bool allow_dc_pc_games = true; bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true; bool allow_gc_xb_games = true;
bool enable_chat_commands = true; bool enable_chat_commands = true;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors; std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000; uint32_t client_customization_name_color = 0x00000000;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F; 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_battle = 0x07;
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07; uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
uint8_t allowed_drop_modes_v3_normal = 0x1F; uint8_t allowed_drop_modes_v3_normal = 0x1F;
uint8_t allowed_drop_modes_v3_battle = 0x07; uint8_t allowed_drop_modes_v3_battle = 0x07;
uint8_t allowed_drop_modes_v3_challenge = 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_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle = 0x05; uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 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_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = 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_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = 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_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = 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_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = 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; 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_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3; std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4; 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 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; uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled = false; bool ep3_send_function_call_enabled = false;
bool enable_v3_v4_protected_subcommands = false; bool enable_v3_v4_protected_subcommands = false;
bool catch_handler_exceptions = true; bool catch_handler_exceptions = true;
bool ep3_infinite_meseta = false; 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_player_meseta_rewards = {400, 500, 600, 700, 800};
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500}; std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
uint32_t ep3_final_round_meseta_bonus = 300; uint32_t ep3_final_round_meseta_bonus = 300;
bool ep3_jukebox_is_free = false; bool ep3_jukebox_is_free = false;
uint32_t ep3_behavior_flags = 0; uint32_t ep3_behavior_flags = 0;
bool hide_download_commands = true; bool hide_download_commands = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT; RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT; BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
bool use_game_creator_section_id = false; bool use_game_creator_section_id = false;
bool default_rare_notifs_enabled_v1_v2 = false; bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = 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_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_v3;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4; 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_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_v3;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4; std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
bool notify_server_for_max_level_achieved = false; bool notify_server_for_max_level_achieved = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys; std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index; std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index; std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index; std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches; std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
std::shared_ptr<const DOLFileIndex> dol_file_index; 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;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial; std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index; std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_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_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_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_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 QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> default_quest_index; std::shared_ptr<const QuestIndex> default_quest_index;
std::shared_ptr<const QuestIndex> ep3_download_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_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3; std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4; std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params; std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl; std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets; 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_v2;
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4; std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
std::shared_ptr<const ArmorRandomSet> armor_random_set; std::shared_ptr<const ArmorRandomSet> armor_random_set;
std::shared_ptr<const ToolRandomSet> tool_random_set; std::shared_ptr<const ToolRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets; std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set; 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 ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_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 MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const TextIndex> text_index; std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes; std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table; 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;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult; 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;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult; 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::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge; 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] std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
struct QuestF960Result { struct QuestF960Result {
uint32_t meseta_cost = 0; uint32_t meseta_cost = 0;
uint32_t base_probability = 0; uint32_t base_probability = 0;
uint32_t probability_upgrade = 0; uint32_t probability_upgrade = 0;
std::array<std::vector<ItemData>, 7> results; std::array<std::vector<ItemData>, 7> results;
QuestF960Result() = default; QuestF960Result() = default;
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index); QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
}; };
// Indexed as [type][difficulty][random_choice] // Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results; 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<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<QuestF960Result> quest_F960_success_results; std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results; QuestF960Result quest_F960_failure_results;
std::vector<ItemData> secret_lottery_results; std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier = 1; uint16_t bb_global_exp_multiplier = 1;
float exp_share_multiplier = 0.5; float exp_share_multiplier = 0.5;
double server_global_drop_rate_multiplier = 1.0; double server_global_drop_rate_multiplier = 1.0;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index; std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
uint16_t ep3_card_auction_points = 0; uint16_t ep3_card_auction_points = 0;
uint16_t ep3_card_auction_min_size = 0; uint16_t ep3_card_auction_min_size = 0;
uint16_t ep3_card_auction_max_size = 0; uint16_t ep3_card_auction_max_size = 0;
struct CardAuctionPoolEntry { struct CardAuctionPoolEntry {
uint64_t probability; uint64_t probability;
uint16_t card_id; uint16_t card_id;
uint16_t min_price; uint16_t min_price;
}; };
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool; std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids; std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry { struct Ep3LobbyBannerEntry {
uint32_t type = 1; uint32_t type = 1;
uint32_t which; // See B9 documentation in CommandFormats.hh uint32_t which; // See B9 documentation in CommandFormats.hh
std::string data; std::string data;
}; };
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners; std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<AccountIndex> account_index; std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges; std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index; std::shared_ptr<TeamIndex> team_index;
JSON team_reward_defs_json; JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2; std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3; 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_v2;
std::shared_ptr<std::vector<std::string>> information_contents_v3; 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_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc; 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_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb; 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_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc; 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_gc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb; 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_patch;
std::pair<std::string, uint16_t> proxy_destination_bb; std::pair<std::string, uint16_t> proxy_destination_bb;
std::string welcome_message; std::string welcome_message;
std::string pc_patch_server_message; std::string pc_patch_server_message;
std::string bb_patch_server_message; std::string bb_patch_server_message;
std::shared_ptr<PlayerFilesManager> player_files_manager; std::shared_ptr<PlayerFilesManager> player_files_manager;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client; std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby; std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy; std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event; std::shared_ptr<struct event> destroy_lobbies_event;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders; std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order; std::vector<uint32_t> client_customization_public_lobby_search_order;
std::atomic<int32_t> next_lobby_id = 1; std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event = 0; uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song = -1; int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses; std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0; uint32_t local_address = 0;
uint32_t external_address = 0; uint32_t external_address = 0;
bool proxy_allow_save_files = true; bool proxy_allow_save_files = true;
bool proxy_enable_login_options = false; bool proxy_enable_login_options = false;
std::shared_ptr<IPStackSimulator> ip_stack_simulator; std::shared_ptr<IPStackSimulator> ip_stack_simulator;
std::shared_ptr<DNSServer> dns_server; std::shared_ptr<DNSServer> dns_server;
std::shared_ptr<ProxyServer> proxy_server; std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server; std::shared_ptr<Server> game_server;
std::shared_ptr<PatchServer> pc_patch_server; std::shared_ptr<PatchServer> pc_patch_server;
std::shared_ptr<PatchServer> bb_patch_server; std::shared_ptr<PatchServer> bb_patch_server;
explicit ServerState(const std::string& config_filename = ""); explicit ServerState(const std::string& config_filename = "");
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay); ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete; ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete; ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete; ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete; ServerState& operator=(ServerState&&) = delete;
void add_client_to_available_lobby(std::shared_ptr<Client> c); void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c); void remove_client_from_lobby(std::shared_ptr<Client> c);
bool change_client_lobby( bool change_client_lobby(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<Lobby> new_lobby, std::shared_ptr<Lobby> new_lobby,
bool send_join_notification = true, bool send_join_notification = true,
ssize_t required_client_id = -1); ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l, void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> joining_client); std::shared_ptr<Client> joining_client);
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id); std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
std::vector<std::shared_ptr<Lobby>> all_lobbies(); std::vector<std::shared_ptr<Lobby>> all_lobbies();
std::shared_ptr<Lobby> create_lobby(bool is_game); std::shared_ptr<Lobby> create_lobby(bool is_game);
void remove_lobby(std::shared_ptr<Lobby> l); void remove_lobby(std::shared_ptr<Lobby> l);
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id); void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
std::shared_ptr<Client> find_client( std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr, const std::string* identifier = nullptr,
uint64_t account_id = 0, uint64_t account_id = 0,
std::shared_ptr<Lobby> l = nullptr); std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const; 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> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_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; 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 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 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(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(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 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_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws 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; 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; 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; 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 { 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)); 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 { inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) { if (is_client_customization && this->client_customization_name_color) {
return 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; 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 { 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)); 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 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; 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; 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); void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file( std::shared_ptr<const std::string> load_bb_file(
const std::string& patch_index_filename, const std::string& patch_index_filename,
const std::string& gsl_filename = "", const std::string& gsl_filename = "",
const std::string& bb_directory_filename = "") const; 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(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::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::pair<std::string, uint16_t> parse_port_spec(const JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const; std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const;
template <typename T> template <typename T>
inline void call_on_event_thread(std::function<T()>&& fn) { inline void call_on_event_thread(std::function<T()>&& fn) {
return ::call_on_event_thread<T>(this->base, std::move(fn)); return ::call_on_event_thread<T>(this->base, std::move(fn));
} }
inline void forward_to_event_thread(std::function<void()>&& fn) { inline void forward_to_event_thread(std::function<void()>&& fn) {
::forward_to_event_thread(this->base, std::move(fn)); ::forward_to_event_thread(this->base, std::move(fn));
} }
inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) { inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) {
if (from_non_event_thread) { if (from_non_event_thread) {
::forward_to_event_thread(this->base, std::move(fn)); ::forward_to_event_thread(this->base, std::move(fn));
} else { } else {
fn(); fn();
} }
} }
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const; std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
void update_dependent_server_configs() const; void update_dependent_server_configs() const;
// The following functions may only be called from a non-event thread if they // 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 // take a from_non_event_thread argument; any function that does not have this
// argument must be called only from the event thread. // argument must be called only from the event thread.
void create_default_lobbies(); void create_default_lobbies();
void collect_network_addresses(); void collect_network_addresses();
void load_config_early(); void load_config_early();
void load_config_late(); void load_config_late();
void load_bb_private_keys(bool from_non_event_thread); void load_bb_private_keys(bool from_non_event_thread);
void load_accounts(bool from_non_event_thread); void load_accounts(bool from_non_event_thread);
void load_teams(bool from_non_event_thread); void load_teams(bool from_non_event_thread);
void load_patch_indexes(bool from_non_event_thread); void load_patch_indexes(bool from_non_event_thread);
void clear_map_file_caches(); void clear_map_file_caches();
void load_battle_params(bool from_non_event_thread); void load_battle_params(bool from_non_event_thread);
void load_level_tables(bool from_non_event_thread); void load_level_tables(bool from_non_event_thread);
void load_text_index(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<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt, std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits, std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const; std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes(bool from_non_event_thread); void load_item_name_indexes(bool from_non_event_thread);
void load_drop_tables(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_item_definitions(bool from_non_event_thread);
void load_set_data_tables(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_word_select_table(bool from_non_event_thread);
void load_ep3_cards(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_maps(bool from_non_event_thread);
void load_ep3_tournament_state(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 load_quest_index(bool from_non_event_thread);
void compile_functions(bool from_non_event_thread); void compile_functions(bool from_non_event_thread);
void load_dol_files(bool from_non_event_thread); void load_dol_files(bool from_non_event_thread);
void load_all(); void load_all();
void enqueue_destroy_lobbies(); void enqueue_destroy_lobbies();
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx); static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
void disconnect_all_banned_clients(); void disconnect_all_banned_clients();
std::string format_address_for_channel_name(const struct sockaddr_storage& remote_ss, uint64_t virtual_network); 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 "TeamIndex.hh"
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Image.hh> #include <phosg/Image.hh>
#include <phosg/Random.hh> #include <phosg/Random.hh>
#include "BattleParamsIndex.hh" #include "BattleParamsIndex.hh"
#include "GVMEncoder.hh" #include "GVMEncoder.hh"
#include "ItemData.hh" #include "ItemData.hh"
#include "Loggers.hh" #include "Loggers.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
using namespace std; using namespace std;
TeamIndex::Team::Member::Member(const JSON& json) TeamIndex::Team::Member::Member(const JSON& json)
: flags(json.get_int("Flags", 0)), : flags(json.get_int("Flags", 0)),
points(json.get_int("Points", 0)), points(json.get_int("Points", 0)),
name(json.get_string("Name", "")) { name(json.get_string("Name", "")) {
try { try {
this->account_id = json.get_int("AccountID"); this->account_id = json.get_int("AccountID");
} catch (const out_of_range&) { } catch (const out_of_range&) {
this->account_id = json.get_int("SerialNumber"); this->account_id = json.get_int("SerialNumber");
} }
} }
JSON TeamIndex::Team::Member::json() const { JSON TeamIndex::Team::Member::json() const {
return JSON::dict({ return JSON::dict({
{"AccountID", this->account_id}, {"AccountID", this->account_id},
{"Flags", this->flags}, {"Flags", this->flags},
{"Points", this->points}, {"Points", this->points},
{"Name", this->name}, {"Name", this->name},
}); });
} }
uint32_t TeamIndex::Team::Member::privilege_level() const { uint32_t TeamIndex::Team::Member::privilege_level() const {
if (this->check_flag(Member::Flag::IS_MASTER)) { if (this->check_flag(Member::Flag::IS_MASTER)) {
return 0x40; return 0x40;
} else if (this->check_flag(Member::Flag::IS_LEADER)) { } else if (this->check_flag(Member::Flag::IS_LEADER)) {
return 0x30; return 0x30;
} else { } else {
return 0x00; return 0x00;
} }
} }
TeamIndex::Team::Team(uint32_t team_id) : Team() { TeamIndex::Team::Team(uint32_t team_id) : Team() {
this->team_id = team_id; this->team_id = team_id;
} }
string TeamIndex::Team::json_filename() const { string TeamIndex::Team::json_filename() const {
return string_printf("system/teams/%08" PRIX32 ".json", this->team_id); return string_printf("system/teams/%08" PRIX32 ".json", this->team_id);
} }
string TeamIndex::Team::flag_filename() const { string TeamIndex::Team::flag_filename() const {
return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id); return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id);
} }
void TeamIndex::Team::load_config() { void TeamIndex::Team::load_config() {
auto json = JSON::parse(load_file(this->json_filename())); auto json = JSON::parse(load_file(this->json_filename()));
this->name = json.get_string("Name"); this->name = json.get_string("Name");
this->spent_points = json.get_int("SpentPoints"); this->spent_points = json.get_int("SpentPoints");
this->points = 0; this->points = 0;
for (const auto& member_it : json.get_list("Members")) { for (const auto& member_it : json.get_list("Members")) {
Member m(*member_it); Member m(*member_it);
this->points += m.points; this->points += m.points;
uint32_t account_id = m.account_id; uint32_t account_id = m.account_id;
if (m.check_flag(Member::Flag::IS_MASTER)) { if (m.check_flag(Member::Flag::IS_MASTER)) {
this->master_account_id = account_id; this->master_account_id = account_id;
} }
this->members.emplace(account_id, std::move(m)); this->members.emplace(account_id, std::move(m));
} }
try { try {
for (const auto& it : json.get_list("RewardKeys")) { for (const auto& it : json.get_list("RewardKeys")) {
this->reward_keys.emplace(it->as_string()); this->reward_keys.emplace(it->as_string());
} }
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
this->reward_flags = json.get_int("RewardFlags"); this->reward_flags = json.get_int("RewardFlags");
} }
void TeamIndex::Team::save_config() const { void TeamIndex::Team::save_config() const {
JSON members_json = JSON::list(); JSON members_json = JSON::list();
for (const auto& it : this->members) { for (const auto& it : this->members) {
members_json.emplace_back(it.second.json()); members_json.emplace_back(it.second.json());
} }
JSON reward_keys_json = JSON::list(); JSON reward_keys_json = JSON::list();
for (const auto& it : this->reward_keys) { for (const auto& it : this->reward_keys) {
reward_keys_json.emplace_back(it); reward_keys_json.emplace_back(it);
} }
JSON root = JSON::dict({ JSON root = JSON::dict({
{"Name", this->name}, {"Name", this->name},
{"SpentPoints", this->spent_points}, {"SpentPoints", this->spent_points},
{"Members", std::move(members_json)}, {"Members", std::move(members_json)},
{"RewardKeys", std::move(reward_keys_json)}, {"RewardKeys", std::move(reward_keys_json)},
{"RewardFlags", this->reward_flags}, {"RewardFlags", this->reward_flags},
}); });
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY)); save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
} }
void TeamIndex::Team::load_flag() { void TeamIndex::Team::load_flag() {
Image img(this->flag_filename()); Image img(this->flag_filename());
if (img.get_width() != 32 || img.get_height() != 32) { if (img.get_width() != 32 || img.get_height() != 32) {
throw runtime_error("incorrect flag image dimensions"); throw runtime_error("incorrect flag image dimensions");
} }
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>()); this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
for (size_t y = 0; y < 32; y++) { for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) { for (size_t x = 0; x < 32; x++) {
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y)); this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y));
} }
} }
} }
void TeamIndex::Team::save_flag() const { void TeamIndex::Team::save_flag() const {
if (!this->flag_data) { if (!this->flag_data) {
return; return;
} }
Image img(32, 32, true); Image img(32, 32, true);
for (size_t y = 0; y < 32; y++) { for (size_t y = 0; y < 32; y++) {
for (size_t x = 0; x < 32; x++) { 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.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x)));
} }
} }
img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP); img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP);
} }
void TeamIndex::Team::delete_files() const { void TeamIndex::Team::delete_files() const {
string json_filename = this->json_filename(); string json_filename = this->json_filename();
string flag_filename = this->flag_filename(); string flag_filename = this->flag_filename();
remove(json_filename.c_str()); remove(json_filename.c_str());
remove(flag_filename.c_str()); remove(flag_filename.c_str());
} }
PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const { PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const {
const auto& m = this->members.at(account_id); const auto& m = this->members.at(account_id);
PSOBBTeamMembership ret; PSOBBTeamMembership ret;
ret.team_master_guild_card_number = this->master_account_id; ret.team_master_guild_card_number = this->master_account_id;
ret.team_id = this->team_id; ret.team_id = this->team_id;
ret.unknown_a5 = 0; ret.unknown_a5 = 0;
ret.unknown_a6 = 0; ret.unknown_a6 = 0;
ret.privilege_level = m.privilege_level(); ret.privilege_level = m.privilege_level();
ret.unknown_a7 = 0; ret.unknown_a7 = 0;
ret.unknown_a8 = 0; ret.unknown_a8 = 0;
ret.unknown_a9 = 0; ret.unknown_a9 = 0;
ret.team_name.encode(this->name); ret.team_name.encode(this->name);
if (this->flag_data) { if (this->flag_data) {
ret.flag_data = *this->flag_data; ret.flag_data = *this->flag_data;
} else { } else {
ret.flag_data.clear(); ret.flag_data.clear();
} }
ret.reward_flags = this->reward_flags; ret.reward_flags = this->reward_flags;
return ret; return ret;
} }
bool TeamIndex::Team::has_reward(const string& key) const { bool TeamIndex::Team::has_reward(const string& key) const {
return this->reward_keys.count(key); return this->reward_keys.count(key);
} }
size_t TeamIndex::Team::num_members() const { size_t TeamIndex::Team::num_members() const {
return this->members.size(); return this->members.size();
} }
size_t TeamIndex::Team::num_leaders() const { size_t TeamIndex::Team::num_leaders() const {
size_t count = 0; size_t count = 0;
for (const auto& it : this->members) { for (const auto& it : this->members) {
if (it.second.check_flag(Member::Flag::IS_LEADER)) { if (it.second.check_flag(Member::Flag::IS_LEADER)) {
count++; count++;
} }
} }
return count; return count;
} }
size_t TeamIndex::Team::max_members() const { size_t TeamIndex::Team::max_members() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) { if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 100; return 100;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 70; return 70;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 40; return 40;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 20; return 20;
} else { } else {
return 10; return 10;
} }
} }
size_t TeamIndex::Team::max_leaders() const { size_t TeamIndex::Team::max_leaders() const {
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) { if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
return 10; return 10;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
return 8; return 8;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
return 5; return 5;
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) { } else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
return 3; return 3;
} else { } else {
return 2; return 2;
} }
} }
bool TeamIndex::Team::can_add_member() const { bool TeamIndex::Team::can_add_member() const {
return this->num_members() < this->max_members(); return this->num_members() < this->max_members();
} }
bool TeamIndex::Team::can_promote_leader() const { bool TeamIndex::Team::can_promote_leader() const {
return this->num_leaders() < this->max_leaders(); return this->num_leaders() < this->max_leaders();
} }
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json) TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
: menu_item_id(menu_item_id), : menu_item_id(menu_item_id),
key(def_json.get_string("Key")), key(def_json.get_string("Key")),
name(def_json.get_string("Name")), name(def_json.get_string("Name")),
description(def_json.get_string("Description")), description(def_json.get_string("Description")),
is_unique(def_json.get_bool("IsUnique", true)), is_unique(def_json.get_bool("IsUnique", true)),
team_points(def_json.get_int("Points")) { team_points(def_json.get_int("Points")) {
try { try {
for (const auto& it : def_json.get_list("PrerequisiteKeys")) { for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
this->prerequisite_keys.emplace(it->as_string()); this->prerequisite_keys.emplace(it->as_string());
} }
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
try { try {
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag")); this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
try { try {
this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem"))); this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem")));
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
} }
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json) TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
: directory(directory), : directory(directory),
next_team_id(1) { next_team_id(1) {
uint32_t reward_menu_item_id = 0; uint32_t reward_menu_item_id = 0;
for (const auto& it : reward_defs_json.as_list()) { for (const auto& it : reward_defs_json.as_list()) {
this->reward_defs.emplace_back(reward_menu_item_id++, *it); this->reward_defs.emplace_back(reward_menu_item_id++, *it);
} }
if (!isdir(this->directory)) { if (!isdir(this->directory)) {
mkdir(this->directory.c_str(), 0755); mkdir(this->directory.c_str(), 0755);
return; return;
} }
for (const auto& filename : list_directory(this->directory)) { for (const auto& filename : list_directory(this->directory)) {
string file_path = this->directory + "/" + filename; string file_path = this->directory + "/" + filename;
if (filename == "base.json") { if (filename == "base.json") {
auto json = JSON::parse(load_file(file_path)); auto json = JSON::parse(load_file(file_path));
this->next_team_id = json.get_int("NextTeamID"); this->next_team_id = json.get_int("NextTeamID");
} else if (ends_with(filename, ".json")) { } else if (ends_with(filename, ".json")) {
try { try {
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16); uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
auto team = make_shared<Team>(team_id); auto team = make_shared<Team>(team_id);
team->load_config(); team->load_config();
try { try {
team->load_flag(); team->load_flag();
} catch (const exception& e) { } catch (const exception& e) {
static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what()); static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what());
} }
this->add_to_indexes(team); 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()); 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) { } catch (const exception& e) {
static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what()); static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what());
} }
} }
} }
} }
size_t TeamIndex::count() const { size_t TeamIndex::count() const {
return this->id_to_team.size(); return this->id_to_team.size();
} }
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const { shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
try { try {
return this->id_to_team.at(team_id); return this->id_to_team.at(team_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const { shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
try { try {
return this->name_to_team.at(name); return this->name_to_team.at(name);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const { shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
try { try {
return this->account_id_to_team.at(account_id); return this->account_id_to_team.at(account_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
} }
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const { vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
vector<shared_ptr<const Team>> ret; vector<shared_ptr<const Team>> ret;
for (const auto& it : this->id_to_team) { for (const auto& it : this->id_to_team) {
ret.emplace_back(it.second); ret.emplace_back(it.second);
} }
return ret; return ret;
} }
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) { 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++); auto team = make_shared<Team>(this->next_team_id++);
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize()); save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
Team::Member m; Team::Member m;
m.account_id = master_account_id; m.account_id = master_account_id;
m.flags = 0; m.flags = 0;
m.points = 0; m.points = 0;
m.name = master_name; m.name = master_name;
m.set_flag(Team::Member::Flag::IS_MASTER); m.set_flag(Team::Member::Flag::IS_MASTER);
team->members.emplace(master_account_id, std::move(m)); team->members.emplace(master_account_id, std::move(m));
team->name = name; team->name = name;
team->save_config(); team->save_config();
this->add_to_indexes(team); this->add_to_indexes(team);
return team; return team;
} }
void TeamIndex::disband(uint32_t team_id) { void TeamIndex::disband(uint32_t team_id) {
auto team = this->id_to_team.at(team_id); auto team = this->id_to_team.at(team_id);
this->remove_from_indexes(team); this->remove_from_indexes(team);
team->delete_files(); team->delete_files();
} }
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) { void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
auto team = this->id_to_team.at(team_id); auto team = this->id_to_team.at(team_id);
if (!this->name_to_team.emplace(new_team_name, team).second) { if (!this->name_to_team.emplace(new_team_name, team).second) {
throw runtime_error("team name is already in use"); throw runtime_error("team name is already in use");
} }
this->name_to_team.erase(team->name); this->name_to_team.erase(team->name);
team->name = new_team_name; team->name = new_team_name;
team->save_config(); team->save_config();
} }
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) { void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
auto team = this->id_to_team.at(team_id); auto team = this->id_to_team.at(team_id);
if (!this->account_id_to_team.emplace(account_id, team).second) { if (!this->account_id_to_team.emplace(account_id, team).second) {
throw runtime_error("user is already in a different team"); throw runtime_error("user is already in a different team");
} }
Team::Member m; Team::Member m;
m.account_id = account_id; m.account_id = account_id;
m.flags = 0; m.flags = 0;
m.points = 0; m.points = 0;
m.name = name; m.name = name;
team->members.emplace(account_id, std::move(m)); team->members.emplace(account_id, std::move(m));
team->save_config(); team->save_config();
} }
void TeamIndex::remove_member(uint32_t account_id) { void TeamIndex::remove_member(uint32_t account_id) {
auto team_it = this->account_id_to_team.find(account_id); auto team_it = this->account_id_to_team.find(account_id);
if (team_it == this->account_id_to_team.end()) { if (team_it == this->account_id_to_team.end()) {
throw runtime_error("client is not in any team"); throw runtime_error("client is not in any team");
} }
auto team = std::move(team_it->second); auto team = std::move(team_it->second);
this->account_id_to_team.erase(team_it); this->account_id_to_team.erase(team_it);
team->members.erase(account_id); team->members.erase(account_id);
if (team->members.empty()) { if (team->members.empty()) {
this->disband(team->team_id); this->disband(team->team_id);
} else { } else {
team->save_config(); team->save_config();
} }
} }
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) { void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
auto team = this->account_id_to_team.at(account_id); auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id); auto& m = team->members.at(account_id);
m.name = name; m.name = name;
team->save_config(); team->save_config();
} }
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) { void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
auto team = this->account_id_to_team.at(account_id); auto team = this->account_id_to_team.at(account_id);
auto& m = team->members.at(account_id); auto& m = team->members.at(account_id);
m.points += points; m.points += points;
team->points += points; team->points += points;
team->save_config(); team->save_config();
} }
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) { 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); auto team = this->id_to_team.at(team_id);
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data)); team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
team->save_flag(); team->save_flag();
} }
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) { 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 team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id); auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID"); throw runtime_error("incorrect master account ID");
} }
auto& other_m = team->members.at(leader_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()) { if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
return false; return false;
} }
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER); other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config(); team->save_config();
return true; return true;
} }
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) { 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 team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id); auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID"); throw runtime_error("incorrect master account ID");
} }
auto& other_m = team->members.at(leader_account_id); auto& other_m = team->members.at(leader_account_id);
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) { if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
return false; return false;
} }
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER); other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
team->save_config(); team->save_config();
return true; return true;
} }
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) { 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 team = this->account_id_to_team.at(master_account_id);
auto& master_m = team->members.at(master_account_id); auto& master_m = team->members.at(master_account_id);
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
throw runtime_error("incorrect master account ID"); throw runtime_error("incorrect master account ID");
} }
auto& new_master_m = team->members.at(new_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.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER); master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
new_master_m.clear_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); new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
team->master_account_id = new_master_account_id; team->master_account_id = new_master_account_id;
team->save_config(); team->save_config();
} }
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) { 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); auto team = this->id_to_team.at(team_id);
if (team->spent_points + points > team->points) { if (team->spent_points + points > team->points) {
throw runtime_error("not enough points available"); throw runtime_error("not enough points available");
} }
team->reward_keys.emplace(key); team->reward_keys.emplace(key);
team->spent_points += points; team->spent_points += points;
if (reward_flag != Team::RewardFlag::NONE) { if (reward_flag != Team::RewardFlag::NONE) {
team->set_reward_flag(reward_flag); team->set_reward_flag(reward_flag);
} }
team->save_config(); team->save_config();
} }
void TeamIndex::add_to_indexes(shared_ptr<Team> team) { void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
if (!this->id_to_team.emplace(team->team_id, team).second) { if (!this->id_to_team.emplace(team->team_id, team).second) {
throw runtime_error("team ID is already in use"); throw runtime_error("team ID is already in use");
} }
if (!this->name_to_team.emplace(team->name, team).second) { if (!this->name_to_team.emplace(team->name, team).second) {
this->id_to_team.erase(team->team_id); this->id_to_team.erase(team->team_id);
throw runtime_error("team name is already in use"); throw runtime_error("team name is already in use");
} }
for (const auto& it : team->members) { for (const auto& it : team->members) {
if (!this->account_id_to_team.emplace(it.second.account_id, team).second) { 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", static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams",
it.second.account_id, it.second.account_id); it.second.account_id, it.second.account_id);
} }
} }
} }
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) { void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
this->id_to_team.erase(team->team_id); this->id_to_team.erase(team->team_id);
this->name_to_team.erase(team->name); this->name_to_team.erase(team->name);
for (const auto& it : team->members) { for (const auto& it : team->members) {
this->account_id_to_team.erase(it.second.account_id); this->account_id_to_team.erase(it.second.account_id);
} }
} }
+155 -155
View File
@@ -1,155 +1,155 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <array> #include <array>
#include <memory> #include <memory>
#include <phosg/JSON.hh> #include <phosg/JSON.hh>
#include <random> #include <random>
#include <string> #include <string>
#include "ItemNameIndex.hh" #include "ItemNameIndex.hh"
#include "SaveFileFormats.hh" #include "SaveFileFormats.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
class TeamIndex { class TeamIndex {
public: public:
struct Team { struct Team {
struct Member { struct Member {
enum class Flag { enum class Flag {
IS_MASTER = 0x01, IS_MASTER = 0x01,
IS_LEADER = 0x02, IS_LEADER = 0x02,
}; };
uint32_t account_id = 0; uint32_t account_id = 0;
uint8_t flags = 0; uint8_t flags = 0;
uint64_t points = 0; uint64_t points = 0;
std::string name; std::string name;
Member() = default; Member() = default;
explicit Member(const JSON& json); explicit Member(const JSON& json);
JSON json() const; JSON json() const;
[[nodiscard]] inline bool check_flag(Flag flag) const { [[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(static_cast<uint8_t>(flag) & this->flags); return !!(static_cast<uint8_t>(flag) & this->flags);
} }
inline void set_flag(Flag flag) { inline void set_flag(Flag flag) {
this->flags |= static_cast<uint8_t>(flag); this->flags |= static_cast<uint8_t>(flag);
} }
inline void clear_flag(Flag flag) { inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint8_t>(flag)); this->flags &= (~static_cast<uint8_t>(flag));
} }
uint32_t privilege_level() const; uint32_t privilege_level() const;
}; };
enum class RewardFlag { enum class RewardFlag {
// Only 0x00000001 and 0x00000002 are used by the client; the rest are // Only 0x00000001 and 0x00000002 are used by the client; the rest are
// free to be used however the server chooses. // free to be used however the server chooses.
NONE = 0x00000000, NONE = 0x00000000,
TEAM_FLAG = 0x00000001, TEAM_FLAG = 0x00000001,
DRESSING_ROOM = 0x00000002, DRESSING_ROOM = 0x00000002,
MEMBERS_20_LEADERS_3 = 0x00000004, MEMBERS_20_LEADERS_3 = 0x00000004,
MEMBERS_40_LEADERS_5 = 0x00000008, MEMBERS_40_LEADERS_5 = 0x00000008,
MEMBERS_70_LEADERS_8 = 0x00000010, MEMBERS_70_LEADERS_8 = 0x00000010,
MEMBERS_100_LEADERS_10 = 0x00000020, MEMBERS_100_LEADERS_10 = 0x00000020,
}; };
uint32_t team_id = 0; uint32_t team_id = 0;
uint32_t points = 0; uint32_t points = 0;
uint32_t spent_points = 0; uint32_t spent_points = 0;
std::string name; std::string name;
uint32_t master_account_id = 0; uint32_t master_account_id = 0;
std::unordered_map<uint32_t, Member> members; std::unordered_map<uint32_t, Member> members;
uint32_t reward_flags = 0; uint32_t reward_flags = 0;
std::unordered_set<std::string> reward_keys; std::unordered_set<std::string> reward_keys;
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data; std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
Team() = default; Team() = default;
explicit Team(uint32_t team_id); explicit Team(uint32_t team_id);
JSON json() const; JSON json() const;
std::string json_filename() const; std::string json_filename() const;
std::string flag_filename() const; std::string flag_filename() const;
void load_config(); void load_config();
void save_config() const; void save_config() const;
void load_flag(); void load_flag();
void save_flag() const; void save_flag() const;
void delete_files() const; void delete_files() const;
PSOBBTeamMembership membership_for_member(uint32_t account_id) const; PSOBBTeamMembership membership_for_member(uint32_t account_id) const;
[[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const { [[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const {
return !!(static_cast<uint8_t>(flag) & this->reward_flags); return !!(static_cast<uint8_t>(flag) & this->reward_flags);
} }
inline void set_reward_flag(RewardFlag flag) { inline void set_reward_flag(RewardFlag flag) {
this->reward_flags |= static_cast<uint8_t>(flag); this->reward_flags |= static_cast<uint8_t>(flag);
} }
inline void clear_reward_flag(RewardFlag flag) { inline void clear_reward_flag(RewardFlag flag) {
this->reward_flags &= (~static_cast<uint8_t>(flag)); this->reward_flags &= (~static_cast<uint8_t>(flag));
} }
[[nodiscard]] bool has_reward(const std::string& key) const; [[nodiscard]] bool has_reward(const std::string& key) const;
size_t num_members() const; size_t num_members() const;
size_t num_leaders() const; size_t num_leaders() const;
size_t max_members() const; size_t max_members() const;
size_t max_leaders() const; size_t max_leaders() const;
bool can_add_member() const; bool can_add_member() const;
bool can_promote_leader() const; bool can_promote_leader() const;
}; };
struct Reward { struct Reward {
uint32_t menu_item_id = 0; uint32_t menu_item_id = 0;
std::string key; std::string key;
std::string name; std::string name;
std::string description; std::string description;
std::unordered_set<std::string> prerequisite_keys; std::unordered_set<std::string> prerequisite_keys;
bool is_unique = true; bool is_unique = true;
uint32_t team_points = 0; uint32_t team_points = 0;
Team::RewardFlag reward_flag = Team::RewardFlag::NONE; Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
ItemData reward_item; ItemData reward_item;
Reward(uint32_t menu_item_id, const JSON& def_json); Reward(uint32_t menu_item_id, const JSON& def_json);
}; };
TeamIndex(const std::string& directory, const JSON& reward_defs_json); TeamIndex(const std::string& directory, const JSON& reward_defs_json);
~TeamIndex() = default; ~TeamIndex() = default;
inline const std::vector<Reward>& reward_definitions() const { inline const std::vector<Reward>& reward_definitions() const {
return this->reward_defs; return this->reward_defs;
} }
size_t count() const; size_t count() const;
std::shared_ptr<const Team> get_by_id(uint32_t team_id) 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_name(const std::string& name) const;
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) 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::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); 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 disband(uint32_t team_id);
void rename(uint32_t team_id, const std::string& new_name); 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 add_member(uint32_t team_id, uint32_t account_id, const std::string& name);
void remove_member(uint32_t account_id); void remove_member(uint32_t account_id);
void update_member_name(uint32_t account_id, const std::string& name); void update_member_name(uint32_t account_id, const std::string& name);
void add_member_points(uint32_t account_id, uint32_t points); 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); 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 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); 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 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); void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
protected: protected:
std::string directory; std::string directory;
uint32_t next_team_id; uint32_t next_team_id;
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team; 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<std::string, std::shared_ptr<Team>> name_to_team;
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team; std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
std::vector<Reward> reward_defs; std::vector<Reward> reward_defs;
void add_to_indexes(std::shared_ptr<Team> team); void add_to_indexes(std::shared_ptr<Team> team);
void remove_from_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 "Version.hh"
#include <strings.h> #include <strings.h>
#include <stdexcept> #include <stdexcept>
#include "Client.hh" #include "Client.hh"
using namespace std; using namespace std;
const char* login_port_name_for_version(Version v) { const char* login_port_name_for_version(Version v) {
switch (v) { switch (v) {
case Version::PC_PATCH: case Version::PC_PATCH:
return "pc-patch"; return "pc-patch";
case Version::BB_PATCH: case Version::BB_PATCH:
return "bb-patch"; return "bb-patch";
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return "console-login"; return "console-login";
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return "pc-login"; return "pc-login";
case Version::XB_V3: case Version::XB_V3:
return "xb-login"; return "xb-login";
case Version::BB_V4: case Version::BB_V4:
return "bb-init"; return "bb-init";
default: default:
throw runtime_error("unknown version"); throw runtime_error("unknown version");
} }
} }
const char* lobby_port_name_for_version(Version v) { const char* lobby_port_name_for_version(Version v) {
switch (v) { switch (v) {
case Version::PC_PATCH: case Version::PC_PATCH:
return "pc-patch"; return "pc-patch";
case Version::BB_PATCH: case Version::BB_PATCH:
return "bb-patch"; return "bb-patch";
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return "console-lobby"; return "console-lobby";
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return "pc-lobby"; return "pc-lobby";
case Version::XB_V3: case Version::XB_V3:
return "xb-lobby"; return "xb-lobby";
case Version::BB_V4: case Version::BB_V4:
return "bb-lobby"; return "bb-lobby";
default: default:
throw runtime_error("unknown version"); throw runtime_error("unknown version");
} }
} }
const char* proxy_port_name_for_version(Version v) { const char* proxy_port_name_for_version(Version v) {
switch (v) { switch (v) {
case Version::PC_PATCH: case Version::PC_PATCH:
return "pc-patch"; return "pc-patch";
case Version::BB_PATCH: case Version::BB_PATCH:
return "bb-patch"; return "bb-patch";
case Version::DC_NTE: case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
case Version::GC_NTE: case Version::GC_NTE:
return "dc-proxy"; return "dc-proxy";
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
return "gc-proxy"; return "gc-proxy";
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
return "pc-proxy"; return "pc-proxy";
case Version::XB_V3: case Version::XB_V3:
return "xb-proxy"; return "xb-proxy";
case Version::BB_V4: case Version::BB_V4:
return "bb-proxy"; return "bb-proxy";
default: default:
throw runtime_error("unknown version"); throw runtime_error("unknown version");
} }
} }
template <> template <>
const char* name_for_enum<Version>(Version v) { const char* name_for_enum<Version>(Version v) {
switch (v) { switch (v) {
case Version::PC_PATCH: case Version::PC_PATCH:
return "PC_PATCH"; return "PC_PATCH";
case Version::BB_PATCH: case Version::BB_PATCH:
return "BB_PATCH"; return "BB_PATCH";
case Version::DC_NTE: case Version::DC_NTE:
return "DC_NTE"; return "DC_NTE";
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
return "DC_V1_11_2000_PROTOTYPE"; return "DC_V1_11_2000_PROTOTYPE";
case Version::DC_V1: case Version::DC_V1:
return "DC_V1"; return "DC_V1";
case Version::DC_V2: case Version::DC_V2:
return "DC_V2"; return "DC_V2";
case Version::PC_NTE: case Version::PC_NTE:
return "PC_NTE"; return "PC_NTE";
case Version::PC_V2: case Version::PC_V2:
return "PC_V2"; return "PC_V2";
case Version::GC_NTE: case Version::GC_NTE:
return "GC_NTE"; return "GC_NTE";
case Version::GC_V3: case Version::GC_V3:
return "GC_V3"; return "GC_V3";
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
return "GC_EP3_NTE"; return "GC_EP3_NTE";
case Version::GC_EP3: case Version::GC_EP3:
return "GC_EP3"; return "GC_EP3";
case Version::XB_V3: case Version::XB_V3:
return "XB_V3"; return "XB_V3";
case Version::BB_V4: case Version::BB_V4:
return "BB_V4"; return "BB_V4";
default: default:
throw runtime_error("unknown version"); throw runtime_error("unknown version");
} }
} }
template <> template <>
Version enum_for_name<Version>(const char* name) { Version enum_for_name<Version>(const char* name) {
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) { if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
return Version::PC_PATCH; return Version::PC_PATCH;
} else if (!strcmp(name, "BB_PATCH")) { } else if (!strcmp(name, "BB_PATCH")) {
return Version::BB_PATCH; return Version::BB_PATCH;
} else if (!strcmp(name, "DC_NTE")) { } else if (!strcmp(name, "DC_NTE")) {
return Version::DC_NTE; return Version::DC_NTE;
} else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) { } else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) {
return Version::DC_V1_11_2000_PROTOTYPE; return Version::DC_V1_11_2000_PROTOTYPE;
} else if (!strcmp(name, "DC_V1")) { } else if (!strcmp(name, "DC_V1")) {
return Version::DC_V1; return Version::DC_V1;
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) { } else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
return Version::DC_V2; return Version::DC_V2;
} else if (!strcmp(name, "PC_NTE")) { } else if (!strcmp(name, "PC_NTE")) {
return Version::PC_NTE; return Version::PC_NTE;
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) { } else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
return Version::PC_V2; return Version::PC_V2;
} else if (!strcmp(name, "GC_NTE")) { } else if (!strcmp(name, "GC_NTE")) {
return Version::GC_NTE; return Version::GC_NTE;
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) { } else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
return Version::GC_V3; return Version::GC_V3;
} else if (!strcmp(name, "GC_EP3_NTE")) { } else if (!strcmp(name, "GC_EP3_NTE")) {
return Version::GC_EP3_NTE; return Version::GC_EP3_NTE;
} else if (!strcmp(name, "GC_EP3")) { } else if (!strcmp(name, "GC_EP3")) {
return Version::GC_EP3; return Version::GC_EP3;
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) { } else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
return Version::XB_V3; return Version::XB_V3;
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) { } else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
return Version::BB_V4; return Version::BB_V4;
} else { } else {
throw invalid_argument("incorrect version name"); throw invalid_argument("incorrect version name");
} }
} }
template <> template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) { const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
switch (behavior) { switch (behavior) {
case ServerBehavior::PC_CONSOLE_DETECT: case ServerBehavior::PC_CONSOLE_DETECT:
return "pc_console_detect"; return "pc_console_detect";
case ServerBehavior::LOGIN_SERVER: case ServerBehavior::LOGIN_SERVER:
return "login_server"; return "login_server";
case ServerBehavior::LOBBY_SERVER: case ServerBehavior::LOBBY_SERVER:
return "lobby_server"; return "lobby_server";
case ServerBehavior::PATCH_SERVER_PC: case ServerBehavior::PATCH_SERVER_PC:
return "patch_server_pc"; return "patch_server_pc";
case ServerBehavior::PATCH_SERVER_BB: case ServerBehavior::PATCH_SERVER_BB:
return "patch_server_bb"; return "patch_server_bb";
case ServerBehavior::PROXY_SERVER: case ServerBehavior::PROXY_SERVER:
return "proxy_server"; return "proxy_server";
} }
throw logic_error("invalid server behavior"); throw logic_error("invalid server behavior");
} }
template <> template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name) { ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
if (!strcasecmp(name, "pc_console_detect")) { if (!strcasecmp(name, "pc_console_detect")) {
return ServerBehavior::PC_CONSOLE_DETECT; return ServerBehavior::PC_CONSOLE_DETECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) { } else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) {
return ServerBehavior::LOGIN_SERVER; return ServerBehavior::LOGIN_SERVER;
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) { } else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
return ServerBehavior::LOBBY_SERVER; return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) { } else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
return ServerBehavior::PATCH_SERVER_PC; return ServerBehavior::PATCH_SERVER_PC;
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) { } else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
return ServerBehavior::PATCH_SERVER_BB; return ServerBehavior::PATCH_SERVER_BB;
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) { } else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
return ServerBehavior::PROXY_SERVER; return ServerBehavior::PROXY_SERVER;
} else { } else {
throw invalid_argument("incorrect server behavior name"); throw invalid_argument("incorrect server behavior name");
} }
} }
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) { 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 // For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all // to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call, // versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the // so for those versions we get the specific_version later by sending the
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call. // VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
switch (version) { switch (version) {
case Version::DC_NTE: case Version::DC_NTE:
return SPECIFIC_VERSION_DC_NTE; // 1OJ1 return SPECIFIC_VERSION_DC_NTE; // 1OJ1
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2 return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
case Version::DC_V1: case Version::DC_V1:
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip) return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
case Version::DC_V2: case Version::DC_V2:
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
case Version::PC_V2: case Version::PC_V2:
return SPECIFIC_VERSION_PC_V2; // 2OJW return SPECIFIC_VERSION_PC_V2; // 2OJW
case Version::GC_NTE: case Version::GC_NTE:
return SPECIFIC_VERSION_GC_NTE; // 3OJT return SPECIFIC_VERSION_GC_NTE; // 3OJT
case Version::GC_V3: case Version::GC_V3:
switch (sub_version) { switch (sub_version) {
case 0x32: // GC Ep1&2 EU 50Hz case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz case 0x33: // GC Ep1&2 EU 60Hz
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0 return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
case 0x36: // GC Ep1&2 US v1.2 (Plus) case 0x36: // GC Ep1&2 US v1.2 (Plus)
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2 return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
case 0x34: // GC Ep1&2 JP v1.3 case 0x34: // GC Ep1&2 JP v1.3
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3 return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
case 0x35: // GC Ep1&2 JP v1.4 (Plus) case 0x35: // GC Ep1&2 JP v1.4 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4 return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
case 0x39: // GC Ep1&2 JP v1.5 (Plus) case 0x39: // GC Ep1&2 JP v1.5 (Plus)
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5 return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
case -1: // Initial check (before sub_version recognition) 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 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 case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
default: default:
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
} }
throw logic_error("this should be impossible"); throw logic_error("this should be impossible");
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
case Version::GC_EP3: case Version::GC_EP3:
switch (sub_version) { switch (sub_version) {
case 0x41: // GC Ep3 US case 0x41: // GC Ep3 US
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0 return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
case 0x42: // GC Ep3 EU 50Hz case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz case 0x43: // GC Ep3 EU 60Hz
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0 return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
case -1: // Initial check (before sub_version recognition) case -1: // Initial check (before sub_version recognition)
case 0x40: // GC Ep3 trial and GC Ep3 JP case 0x40: // GC Ep3 trial and GC Ep3 JP
default: default:
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
} }
case Version::XB_V3: case Version::XB_V3:
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
case Version::BB_V4: case Version::BB_V4:
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
default: default:
return SPECIFIC_VERSION_INDETERMINATE; return SPECIFIC_VERSION_INDETERMINATE;
} }
} }
bool specific_version_is_indeterminate(uint32_t specific_version) { bool specific_version_is_indeterminate(uint32_t specific_version) {
return ((specific_version & 0x000000FF) == 0); return ((specific_version & 0x000000FF) == 0);
} }
bool specific_version_is_dc(uint32_t specific_version) { bool specific_version_is_dc(uint32_t specific_version) {
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC // All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
uint8_t major_version = specific_version >> 24; uint8_t major_version = specific_version >> 24;
if (major_version < 0x31 || major_version > 0x32) { if (major_version < 0x31 || major_version > 0x32) {
return false; return false;
} }
return (specific_version != 0x324F4A57); return (specific_version != 0x324F4A57);
} }
bool specific_version_is_gc(uint32_t specific_version) { 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] // GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
if ((specific_version & 0xFF000000) != 0x33000000) { if ((specific_version & 0xFF000000) != 0x33000000) {
return false; return false;
} }
char game = specific_version >> 16; char game = specific_version >> 16;
if ((game != 'O') && (game != 'S')) { if ((game != 'O') && (game != 'S')) {
return false; return false;
} }
char region = specific_version >> 8; char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) { if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false; return false;
} }
char revision = specific_version; char revision = specific_version;
return (isdigit(revision) || (revision == 'T')); return (isdigit(revision) || (revision == 'T'));
} }
bool specific_version_is_xb(uint32_t specific_version) { bool specific_version_is_xb(uint32_t specific_version) {
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU] // XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
if ((specific_version & 0xFFFF0000) != 0x344F0000) { if ((specific_version & 0xFFFF0000) != 0x344F0000) {
return false; return false;
} }
char region = specific_version >> 8; char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) { if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false; return false;
} }
char revision = specific_version; char revision = specific_version;
return ((revision == 'B') || (revision == 'D') || (revision == 'U')); return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
} }
bool specific_version_is_bb(uint32_t specific_version) { bool specific_version_is_bb(uint32_t specific_version) {
// BB specific_versions are 5XXX, where X is an encoding of the revision number // BB specific_versions are 5XXX, where X is an encoding of the revision number
return (specific_version & 0xFF000000) == 0x35000000; return (specific_version & 0xFF000000) == 0x35000000;
} }
const char* file_path_token_for_version(Version version) { const char* file_path_token_for_version(Version version) {
switch (version) { switch (version) {
case Version::PC_PATCH: case Version::PC_PATCH:
return "pc-patch"; return "pc-patch";
case Version::BB_PATCH: case Version::BB_PATCH:
return "bb-patch"; return "bb-patch";
case Version::DC_NTE: case Version::DC_NTE:
return "dc-nte"; return "dc-nte";
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
return "dc-11-2000"; return "dc-11-2000";
case Version::DC_V1: case Version::DC_V1:
return "dc-v1"; return "dc-v1";
case Version::DC_V2: case Version::DC_V2:
return "dc-v2"; return "dc-v2";
case Version::PC_NTE: case Version::PC_NTE:
return "pc-nte"; return "pc-nte";
case Version::PC_V2: case Version::PC_V2:
return "pc-v2"; return "pc-v2";
case Version::GC_NTE: case Version::GC_NTE:
return "gc-nte"; return "gc-nte";
case Version::GC_V3: case Version::GC_V3:
return "gc-v3"; return "gc-v3";
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
return "gc-ep3-nte"; return "gc-ep3-nte";
case Version::GC_EP3: case Version::GC_EP3:
return "gc-ep3"; return "gc-ep3";
case Version::XB_V3: case Version::XB_V3:
return "xb-v3"; return "xb-v3";
case Version::BB_V4: case Version::BB_V4:
return "bb-v4"; return "bb-v4";
default: default:
throw runtime_error("invalid game version"); throw runtime_error("invalid game version");
} }
} }
+189 -189
View File
@@ -1,189 +1,189 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <phosg/Types.hh> #include <phosg/Types.hh>
#include <string> #include <string>
#include <vector> #include <vector>
enum class Version { enum class Version {
PC_PATCH = 0, PC_PATCH = 0,
BB_PATCH = 1, BB_PATCH = 1,
DC_NTE = 2, DC_NTE = 2,
DC_V1_11_2000_PROTOTYPE = 3, DC_V1_11_2000_PROTOTYPE = 3,
DC_V1 = 4, DC_V1 = 4,
DC_V2 = 5, DC_V2 = 5,
PC_NTE = 6, PC_NTE = 6,
PC_V2 = 7, PC_V2 = 7,
GC_NTE = 8, GC_NTE = 8,
GC_V3 = 9, GC_V3 = 9,
GC_EP3_NTE = 10, GC_EP3_NTE = 10,
GC_EP3 = 11, GC_EP3 = 11,
XB_V3 = 12, XB_V3 = 12,
BB_V4 = 13, BB_V4 = 13,
UNKNOWN = 15, UNKNOWN = 15,
}; };
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1; 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_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS; 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"); static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don't forget to update VersionNameColors in config.json");
template <> template <>
const char* name_for_enum<Version>(Version v); const char* name_for_enum<Version>(Version v);
template <> template <>
Version enum_for_name<Version>(const char* name); Version enum_for_name<Version>(const char* name);
inline bool is_any_nte(Version version) { inline bool is_any_nte(Version version) {
return (version == Version::DC_NTE) || return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::PC_NTE) || (version == Version::PC_NTE) ||
(version == Version::GC_NTE) || (version == Version::GC_NTE) ||
(version == Version::GC_EP3_NTE); (version == Version::GC_EP3_NTE);
} }
inline bool is_patch(Version version) { inline bool is_patch(Version version) {
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH); return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
} }
inline bool is_pre_v1(Version version) { inline bool is_pre_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE); return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
} }
inline bool is_v1(Version version) { inline bool is_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1); return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
} }
inline bool is_v2(Version version) { inline bool is_v2(Version version) {
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE); 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) { inline bool is_v1_or_v2(Version version) {
return is_v1(version) || is_v2(version); return is_v1(version) || is_v2(version);
} }
inline bool is_v3(Version version) { inline bool is_v3(Version version) {
return (version == Version::GC_V3) || return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) || (version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) || (version == Version::GC_EP3) ||
(version == Version::XB_V3); (version == Version::XB_V3);
} }
inline bool is_v4(Version version) { inline bool is_v4(Version version) {
return (version == Version::BB_V4); return (version == Version::BB_V4);
} }
inline bool is_ep3(Version version) { inline bool is_ep3(Version version) {
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3); return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
} }
inline bool is_dc(Version version) { inline bool is_dc(Version version) {
return (version == Version::DC_NTE) || return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) || (version == Version::DC_V1) ||
(version == Version::DC_V2); (version == Version::DC_V2);
} }
inline bool is_gc(Version version) { inline bool is_gc(Version version) {
return (version == Version::GC_NTE) || return (version == Version::GC_NTE) ||
(version == Version::GC_V3) || (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) || (version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3); (version == Version::GC_EP3);
} }
inline bool is_sh4(Version version) { inline bool is_sh4(Version version) {
return (version == Version::DC_NTE) || return (version == Version::DC_NTE) ||
(version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1_11_2000_PROTOTYPE) ||
(version == Version::DC_V1) || (version == Version::DC_V1) ||
(version == Version::DC_V2); (version == Version::DC_V2);
} }
inline bool is_x86(Version version) { inline bool is_x86(Version version) {
return (version == Version::PC_PATCH) || return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) || (version == Version::BB_PATCH) ||
(version == Version::PC_NTE) || (version == Version::PC_NTE) ||
(version == Version::PC_V2) || (version == Version::PC_V2) ||
(version == Version::XB_V3) || (version == Version::XB_V3) ||
(version == Version::BB_V4); (version == Version::BB_V4);
} }
inline bool is_ppc(Version version) { inline bool is_ppc(Version version) {
return (version == Version::GC_NTE) || return (version == Version::GC_NTE) ||
(version == Version::GC_V3) || (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) || (version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3); (version == Version::GC_EP3);
} }
inline bool is_big_endian(Version version) { inline bool is_big_endian(Version version) {
return (version == Version::GC_NTE) || return (version == Version::GC_NTE) ||
(version == Version::GC_V3) || (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) || (version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3); (version == Version::GC_EP3);
} }
inline bool uses_v2_encryption(Version version) { inline bool uses_v2_encryption(Version version) {
return (version == Version::PC_PATCH) || return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) || (version == Version::BB_PATCH) ||
(version == Version::DC_NTE) || (version == Version::DC_NTE) ||
(version == Version::DC_V1) || (version == Version::DC_V1) ||
(version == Version::DC_V2) || (version == Version::DC_V2) ||
(version == Version::PC_NTE) || (version == Version::PC_NTE) ||
(version == Version::PC_V2) || (version == Version::PC_V2) ||
(version == Version::GC_NTE); (version == Version::GC_NTE);
} }
inline bool uses_v3_encryption(Version version) { inline bool uses_v3_encryption(Version version) {
return (version == Version::GC_V3) || return (version == Version::GC_V3) ||
(version == Version::GC_EP3_NTE) || (version == Version::GC_EP3_NTE) ||
(version == Version::GC_EP3) || (version == Version::GC_EP3) ||
(version == Version::XB_V3); (version == Version::XB_V3);
} }
inline bool uses_v4_encryption(Version version) { inline bool uses_v4_encryption(Version version) {
return (version == Version::BB_V4); return (version == Version::BB_V4);
} }
inline bool uses_utf16(Version version) { inline bool uses_utf16(Version version) {
return (version == Version::PC_PATCH) || return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) || (version == Version::BB_PATCH) ||
(version == Version::PC_NTE) || (version == Version::PC_NTE) ||
(version == Version::PC_V2) || (version == Version::PC_V2) ||
(version == Version::BB_V4); (version == Version::BB_V4);
} }
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1 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_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___ 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_DC_V2_INDETERMINATE = 0x32000000; // 2___
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT 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_EU = 0x334F5030; // 3OP0
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2 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_13 = 0x334F4A33; // 3OJ3
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4 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_JP_15 = 0x334F4A35; // 3OJ5
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__ 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_INDETERMINATE = 0x33534A00; // 3SJ_
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT 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_JP = 0x33534A30; // 3SJ0
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0 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_GC_EP3_EU = 0x33535030; // 3SP0
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__ 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_BB_V4_INDETERMINATE = 0x35000000; // 5___
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____ constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
uint32_t default_specific_version_for_version(Version version, int64_t sub_version); 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_indeterminate(uint32_t specific_version);
bool specific_version_is_dc(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_gc(uint32_t specific_version);
bool specific_version_is_xb(uint32_t specific_version); bool specific_version_is_xb(uint32_t specific_version);
bool specific_version_is_bb(uint32_t specific_version); bool specific_version_is_bb(uint32_t specific_version);
enum class ServerBehavior { enum class ServerBehavior {
PC_CONSOLE_DETECT = 0, PC_CONSOLE_DETECT = 0,
LOGIN_SERVER, LOGIN_SERVER,
LOBBY_SERVER, LOBBY_SERVER,
PATCH_SERVER_PC, PATCH_SERVER_PC,
PATCH_SERVER_BB, PATCH_SERVER_BB,
PROXY_SERVER, PROXY_SERVER,
}; };
const char* login_port_name_for_version(Version v); const char* login_port_name_for_version(Version v);
const char* lobby_port_name_for_version(Version v); const char* lobby_port_name_for_version(Version v);
const char* proxy_port_name_for_version(Version v); const char* proxy_port_name_for_version(Version v);
template <> template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior); const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
template <> template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name); ServerBehavior enum_for_name<ServerBehavior>(const char* name);
const char* file_path_token_for_version(Version version); const char* file_path_token_for_version(Version version);
+319 -319
View File
@@ -1,319 +1,319 @@
#include "WordSelectTable.hh" #include "WordSelectTable.hh"
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Compression.hh" #include "Compression.hh"
using namespace std; using namespace std;
template <typename RetT, typename ReadT> template <typename RetT, typename ReadT>
static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) { static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) {
vector<RetT> ret; vector<RetT> ret;
auto entries_r = base_r.sub(offset, count * sizeof(ReadT)); auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
while (!entries_r.eof()) { while (!entries_r.eof()) {
ret.emplace_back(entries_r.get<ReadT>()); ret.emplace_back(entries_r.get<ReadT>());
} }
return ret; return ret;
} }
template <typename RetT, typename ReadT, typename OffsetT> template <typename RetT, typename ReadT, typename OffsetT>
static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) { static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) {
vector<vector<RetT>> ret; vector<vector<RetT>> ret;
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count); auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
while (!pointers_r.eof()) { while (!pointers_r.eof()) {
uint32_t sub_offset = pointers_r.get<OffsetT>(); uint32_t sub_offset = pointers_r.get<OffsetT>();
uint32_t sub_count = 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)); ret.emplace_back(read_direct_table<RetT, ReadT>(base_r, sub_offset, sub_count));
} }
return ret; return ret;
} }
template <bool IsBigEndian> template <bool IsBigEndian>
struct NonWindowsRootT { struct NonWindowsRootT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type; using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T strings_table; U32T strings_table;
U32T table1; U32T table1;
U32T table2; U32T table2;
U32T token_id_to_string_id_table; U32T token_id_to_string_id_table;
U32T table4; U32T table4;
U32T article_types_table; U32T article_types_table;
U32T table6; U32T table6;
} __packed__; } __packed__;
using NonWindowsRoot = NonWindowsRootT<false>; using NonWindowsRoot = NonWindowsRootT<false>;
using NonWindowsRootBE = NonWindowsRootT<true>; using NonWindowsRootBE = NonWindowsRootT<true>;
check_struct_size(NonWindowsRoot, 0x1C); check_struct_size(NonWindowsRoot, 0x1C);
check_struct_size(NonWindowsRootBE, 0x1C); check_struct_size(NonWindowsRootBE, 0x1C);
struct PCV2Root { struct PCV2Root {
le_uint32_t unknown_a1; le_uint32_t unknown_a1;
le_uint32_t unknown_a2; le_uint32_t unknown_a2;
le_uint32_t table1; le_uint32_t table1;
le_uint32_t table2; le_uint32_t table2;
le_uint32_t token_id_to_string_id_table; le_uint32_t token_id_to_string_id_table;
le_uint32_t table4; le_uint32_t table4;
le_uint32_t article_types_table; le_uint32_t article_types_table;
le_uint32_t table6; le_uint32_t table6;
} __packed_ws__(PCV2Root, 0x20); } __packed_ws__(PCV2Root, 0x20);
struct BBRoot { struct BBRoot {
le_uint32_t table1; le_uint32_t table1;
le_uint32_t table2; le_uint32_t table2;
le_uint32_t token_id_to_string_id_table; le_uint32_t token_id_to_string_id_table;
le_uint32_t table4; le_uint32_t table4;
le_uint32_t article_types_table; le_uint32_t article_types_table;
le_uint32_t table6; le_uint32_t table6;
} __packed_ws__(BBRoot, 0x18); } __packed_ws__(BBRoot, 0x18);
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount> template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) { 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 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; using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
StringReader r(data); StringReader r(data);
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10); uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset); const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
{ {
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount); auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
while (!string_offset_r.eof()) { while (!string_offset_r.eof()) {
string raw_s = r.pget_cstr(string_offset_r.template get<U32T>()); 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->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->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->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->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->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->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); // this->table6 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table6, Table6Count);
} }
template <typename RootT, size_t TokenCount> template <typename RootT, size_t TokenCount>
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) { void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
if (!unitxt_collection) { if (!unitxt_collection) {
throw runtime_error("a unitxt collection is required"); throw runtime_error("a unitxt collection is required");
} }
StringReader r(data); StringReader r(data);
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10); uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
const auto& root = r.pget<RootT>(root_offset); const auto& root = r.pget<RootT>(root_offset);
this->strings = *unitxt_collection; this->strings = *unitxt_collection;
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count); // 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->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->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->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->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); // 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) { WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
switch (version) { switch (version) {
case Version::DC_NTE: { case Version::DC_NTE: {
if (data.size() < 4) { if (data.size() < 4) {
throw runtime_error("data is too small"); throw runtime_error("data is too small");
} }
string decrypted = data.substr(0, data.size() - 4); string decrypted = data.substr(0, data.size() - 4);
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4); uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
PSOV2Encryption crypt(seed); PSOV2Encryption crypt(seed);
crypt.decrypt(decrypted); crypt.decrypt(decrypted);
this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis); this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis);
break; break;
} }
case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1_11_2000_PROTOTYPE:
this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis); this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break; break;
case Version::DC_V1: case Version::DC_V1:
case Version::DC_V2: case Version::DC_V2:
this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis); this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break; break;
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection); this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break; break;
case Version::GC_NTE: case Version::GC_NTE:
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis); this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break; break;
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis); this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break; break;
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3: case Version::GC_EP3:
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis); this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break; break;
case Version::XB_V3: case Version::XB_V3:
this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis); this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
break; break;
case Version::BB_V4: case Version::BB_V4:
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection); this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
break; break;
default: default:
throw runtime_error("unsupported word select data version"); throw runtime_error("unsupported word select data version");
} }
} }
const string& WordSelectSet::string_for_token(uint16_t token_id) const { const string& WordSelectSet::string_for_token(uint16_t token_id) const {
return this->strings.at(this->token_id_to_string_id.at(token_id)); return this->strings.at(this->token_id_to_string_id.at(token_id));
} }
void WordSelectSet::print(FILE* stream) const { void WordSelectSet::print(FILE* stream) const {
fprintf(stream, "strings:\n"); fprintf(stream, "strings:\n");
for (size_t z = 0; z < this->strings.size(); z++) { for (size_t z = 0; z < this->strings.size(); z++) {
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str()); fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
} }
fprintf(stream, "token_id_to_string_id:\n"); fprintf(stream, "token_id_to_string_id:\n");
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) { 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()); fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
} }
} }
WordSelectTable::WordSelectTable( WordSelectTable::WordSelectTable(
const WordSelectSet& dc_nte_ws, const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws, const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws, const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws, const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws, const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws, const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws, const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws, const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws, const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws, const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws, const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws, const WordSelectSet& bb_v4_ws,
const vector<vector<string>>& name_alias_lists) { const vector<vector<string>>& name_alias_lists) {
unordered_map<string, string> name_to_canonical_name; unordered_map<string, string> name_to_canonical_name;
for (const auto& alias_list : name_alias_lists) { for (const auto& alias_list : name_alias_lists) {
if (alias_list.size() < 2) { if (alias_list.size() < 2) {
continue; continue;
} }
auto it = alias_list.begin(); auto it = alias_list.begin();
auto canonical_name = *it; auto canonical_name = *it;
for (it++; it != alias_list.end(); it++) { for (it++; it != alias_list.end(); it++) {
name_to_canonical_name.emplace(*it, canonical_name); name_to_canonical_name.emplace(*it, canonical_name);
} }
} }
vector<shared_ptr<Token>> dynamic_tokens; vector<shared_ptr<Token>> dynamic_tokens;
{ {
for (size_t z = 0; z < 12; z++) { for (size_t z = 0; z < 12; z++) {
auto& token = dynamic_tokens.emplace_back(make_shared<Token>()); auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z); token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z);
this->name_to_token.emplace(token->canonical_name, token); this->name_to_token.emplace(token->canonical_name, token);
} }
auto& token = dynamic_tokens.emplace_back(make_shared<Token>()); auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
token->canonical_name = "__BLANK__"; token->canonical_name = "__BLANK__";
this->name_to_token.emplace(token->canonical_name, token); this->name_to_token.emplace(token->canonical_name, token);
} }
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor"); static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = { array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws, &dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws,
&pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_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}; &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++) { 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); Version version = static_cast<Version>(static_cast<size_t>(Version::DC_NTE) + s_version);
const auto& ws_set = *ws_sets[s_version]; const auto& ws_set = *ws_sets[s_version];
auto& index = this->tokens_by_version.at(s_version); auto& index = this->tokens_by_version.at(s_version);
index.reserve(ws_set.num_tokens()); index.reserve(ws_set.num_tokens());
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) { for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
const string& str = ws_set.string_for_token(token_id); const string& str = ws_set.string_for_token(token_id);
string canonical_name; string canonical_name;
try { try {
canonical_name = name_to_canonical_name.at(str); canonical_name = name_to_canonical_name.at(str);
} catch (const out_of_range&) { } catch (const out_of_range&) {
canonical_name = str; canonical_name = str;
} }
auto token_it = this->name_to_token.find(canonical_name); auto token_it = this->name_to_token.find(canonical_name);
if (token_it == this->name_to_token.end()) { if (token_it == this->name_to_token.end()) {
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first; 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->canonical_name = std::move(canonical_name);
} }
token_it->second->slot_for_version(version) = token_id; token_it->second->slot_for_version(version) = token_id;
index.emplace_back(token_it->second); index.emplace_back(token_it->second);
} }
size_t dynamic_token_base_id = ws_set.num_tokens(); size_t dynamic_token_base_id = ws_set.num_tokens();
for (size_t z = 0; z < dynamic_tokens.size(); z++) { for (size_t z = 0; z < dynamic_tokens.size(); z++) {
auto& token = dynamic_tokens[z]; auto& token = dynamic_tokens[z];
token->slot_for_version(version) = dynamic_token_base_id + z; token->slot_for_version(version) = dynamic_token_base_id + z;
index.emplace_back(token); index.emplace_back(token);
} }
} }
} }
void WordSelectTable::print(FILE* stream) const { void WordSelectTable::print(FILE* stream) const {
fprintf(stream, "DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n"); 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) { for (const auto& it : this->name_to_token) {
const auto& token = it.second; const auto& token = it.second;
for (size_t z = 0; z < 12; z++) { for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) { if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " "); fprintf(stream, " ");
} else { } else {
fprintf(stream, "%04hX ", token->values_by_version[z]); fprintf(stream, "%04hX ", token->values_by_version[z]);
} }
} }
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY); string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str()); fprintf(stream, "%s\n", serialized.c_str());
} }
} }
void WordSelectTable::print_index(FILE* stream, Version v) const { 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"); 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); const auto& index = this->tokens_for_version(v);
for (size_t token_id = 0; token_id < index.size(); token_id++) { for (size_t token_id = 0; token_id < index.size(); token_id++) {
const auto& token = index[token_id]; const auto& token = index[token_id];
fprintf(stream, "%04zX => ", token_id); fprintf(stream, "%04zX => ", token_id);
for (size_t z = 0; z < 12; z++) { for (size_t z = 0; z < 12; z++) {
if (token->values_by_version[z] == 0xFFFF) { if (token->values_by_version[z] == 0xFFFF) {
fprintf(stream, " "); fprintf(stream, " ");
} else { } else {
fprintf(stream, "%04hX ", token->values_by_version[z]); fprintf(stream, "%04hX ", token->values_by_version[z]);
} }
} }
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY); string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
fprintf(stream, "%s\n", serialized.c_str()); fprintf(stream, "%s\n", serialized.c_str());
} }
} }
WordSelectMessage WordSelectTable::translate( WordSelectMessage WordSelectTable::translate(
const WordSelectMessage& msg, const WordSelectMessage& msg,
Version from_version, Version from_version,
Version to_version) const { Version to_version) const {
const auto& index = this->tokens_for_version(from_version); const auto& index = this->tokens_for_version(from_version);
WordSelectMessage ret; WordSelectMessage ret;
for (size_t z = 0; z < ret.tokens.size(); z++) { for (size_t z = 0; z < ret.tokens.size(); z++) {
if (msg.tokens[z] == 0xFFFF) { if (msg.tokens[z] == 0xFFFF) {
ret.tokens[z] = 0xFFFF; ret.tokens[z] = 0xFFFF;
} else { } else {
const auto& token = index.at(msg.tokens[z]); const auto& token = index.at(msg.tokens[z]);
if (!token) { if (!token) {
throw runtime_error(string_printf("token %04hX does not exist in the index", msg.tokens[z].load())); 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); ret.tokens[z] = token->slot_for_version(to_version);
if (ret.tokens[z] == 0xFFFF) { if (ret.tokens[z] == 0xFFFF) {
throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load())); throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load()));
} }
} }
} }
ret.num_tokens = msg.num_tokens; ret.num_tokens = msg.num_tokens;
ret.target_type = msg.target_type; ret.target_type = msg.target_type;
ret.numeric_parameter = msg.numeric_parameter; ret.numeric_parameter = msg.numeric_parameter;
ret.unknown_a4 = msg.unknown_a4; ret.unknown_a4 = msg.unknown_a4;
return ret; return ret;
} }
WordSelectTable::Token::Token() { WordSelectTable::Token::Token() {
for (size_t z = 0; z < this->values_by_version.size(); z++) { for (size_t z = 0; z < this->values_by_version.size(); z++) {
this->values_by_version[z] = 0xFFFF; this->values_by_version[z] = 0xFFFF;
} }
} }
+110 -110
View File
@@ -1,110 +1,110 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include "CommandFormats.hh" #include "CommandFormats.hh"
#include "QuestScript.hh" #include "QuestScript.hh"
class WordSelectSet { class WordSelectSet {
public: public:
WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis); WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis);
~WordSelectSet() = default; ~WordSelectSet() = default;
inline size_t num_strings() const { inline size_t num_strings() const {
return this->strings.size(); return this->strings.size();
} }
inline size_t num_tokens() const { inline size_t num_tokens() const {
return this->token_id_to_string_id.size(); return this->token_id_to_string_id.size();
} }
const std::string& string_for_token(uint16_t token_id) const; const std::string& string_for_token(uint16_t token_id) const;
void print(FILE* stream) const; void print(FILE* stream) const;
protected: protected:
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount> template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void parse_non_windows_t(const std::string& data, bool use_sjis); void parse_non_windows_t(const std::string& data, bool use_sjis);
template <typename RootT, size_t TokenCount> template <typename RootT, size_t TokenCount>
void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection); void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection);
std::vector<std::string> strings; std::vector<std::string> strings;
std::vector<size_t> token_id_to_string_id; std::vector<size_t> token_id_to_string_id;
// Note: PC NTE and PC have exactly the same parameters // 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 // => 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 // root: => 000074DC 000072A4 0000755C 0000755C 00002B50 0000AB04 0000BCAC 0000B620 0000B648 0000B914 0000B5FC
// u32 ???: => 00002A9C // u32 ???: => 00002A9C
// TODO // TODO
// u32 ???: => 00002B14 // u32 ???: => 00002B14
// TODO // TODO
// u32 strings_table: => 00006338 0000612C 000063C0 000063C0 (unitxt) 00009208 00009C9C 00009C34 00009C5C 00009904 (unitxt) // 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) // u32 string_offset[COUNT]: => 469 45E 467 467 (unitxt) 63F 804 67B 67C 804 (unitxt)
// char string[...\0] // char string[...\0]
// u32 table1: => 00000B90 00000B54 00000D3C 00000D3C 00001018 0000100C 000012F0 000012F0 000012F0 000011D0 000012F0 // 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 // {u32 offset, u32 count}[COUNT]: => 94 122 93 93 F9 F9 126 126 126 17F 126
// u16[count] // u16[count]
// u32 table2: => 00001178 00001108 00001300 00001300 000019D8 000019CC 00001EE8 00001EE8 00001EE8 00001DC8 00001EE8 // 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 // {u32 offset, u32 count}[COUNT]: => 7 7 7 7 7 7 13 13 13 13 13
// u16[count] // u16[count]
// u32 token_id_to_string_id_table => 000011B0 00001140 00001338 00001338 00001A10 00001A04 00001F80 00001F80 00001F80 00001E60 00001F80 // 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 // 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 // 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 // (non-NTE) {u32 offset, u32 count}[COUNT]: => 2 2 2 2 2 2 2 2 2 2
// u16[count] // u16[count]
// (NTE) u16[COUNT] => E1 // (NTE) u16[COUNT] => E1
// u32 article_types_table: => 00001C1E 00001B18 00001D2C 00001D2C 000027E0 000027D4 00002DDC 00002DDC 00002DDC 00002CBC 00002DDC // 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 // 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 table6: => 00001E28 00001CBC 00001ED0 00001ED0 00002A84 00002A78 000030A4 000030A4 000030A4 00002F84 00003080
// {u32 offset, u32 count}[3]: // {u32 offset, u32 count}[3]:
// u16[count] // u16[count]
}; };
class WordSelectTable { class WordSelectTable {
public: public:
WordSelectTable( WordSelectTable(
const WordSelectSet& dc_nte_ws, const WordSelectSet& dc_nte_ws,
const WordSelectSet& dc_112000_ws, const WordSelectSet& dc_112000_ws,
const WordSelectSet& dc_v1_ws, const WordSelectSet& dc_v1_ws,
const WordSelectSet& dc_v2_ws, const WordSelectSet& dc_v2_ws,
const WordSelectSet& pc_nte_ws, const WordSelectSet& pc_nte_ws,
const WordSelectSet& pc_v2_ws, const WordSelectSet& pc_v2_ws,
const WordSelectSet& gc_nte_ws, const WordSelectSet& gc_nte_ws,
const WordSelectSet& gc_v3_ws, const WordSelectSet& gc_v3_ws,
const WordSelectSet& gc_ep3_nte_ws, const WordSelectSet& gc_ep3_nte_ws,
const WordSelectSet& gc_ep3_ws, const WordSelectSet& gc_ep3_ws,
const WordSelectSet& xb_v3_ws, const WordSelectSet& xb_v3_ws,
const WordSelectSet& bb_v4_ws, const WordSelectSet& bb_v4_ws,
const std::vector<std::vector<std::string>>& name_alias_lists); const std::vector<std::vector<std::string>>& name_alias_lists);
void print(FILE* stream) const; void print(FILE* stream) const;
void print_index(FILE* stream, Version v) const; void print_index(FILE* stream, Version v) const;
WordSelectMessage translate( WordSelectMessage translate(
const WordSelectMessage& msg, const WordSelectMessage& msg,
Version from_version, Version from_version,
Version to_version) const; Version to_version) const;
private: private:
struct Token { struct Token {
std::array<uint16_t, 12> values_by_version; std::array<uint16_t, 12> values_by_version;
std::string canonical_name; std::string canonical_name;
Token(); Token();
inline uint16_t& slot_for_version(Version version) { 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)); 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 { 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)); 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::map<std::string, std::shared_ptr<Token>> name_to_token;
std::array<std::vector<std::shared_ptr<Token>>, 12> tokens_by_version; 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 { 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)); return this->tokens_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
} }
}; };
+1232 -1232
View File
File diff suppressed because it is too large Load Diff