convert all CRLF line endings to LF only
This commit is contained in:
@@ -1,96 +1,94 @@
|
||||
###########################################################
|
||||
|
||||
NPC: Coren Tsu - The Wanderer
|
||||
AREAS: Pioneer 2
|
||||
|
||||
Translations by: apexseals (discord: apexseals)
|
||||
Proofing & Debugging by: nolrinale (github.com/nolrinale)
|
||||
|
||||
###########################################################
|
||||
|
||||
presentation:
|
||||
|
||||
I am Coren Tsu, a wandering merchant,
|
||||
you could say.
|
||||
|
||||
Please take some time to look at
|
||||
the rare and wonderous goods
|
||||
I have been collecting.
|
||||
|
||||
If you spend a little meseta,
|
||||
you could win a wonderful prize.
|
||||
|
||||
Well? Wanna try?
|
||||
|
||||
|
||||
You may win,
|
||||
you may lose.
|
||||
|
||||
But if you don't win,
|
||||
don't take it out on me.
|
||||
|
||||
That's just the way
|
||||
gambling is, yes?
|
||||
|
||||
Well then, how much
|
||||
meseta do you want to pay?
|
||||
|
||||
As long as you pay me,
|
||||
I'll give you a great service.
|
||||
|
||||
|
||||
Huh?
|
||||
|
||||
That's too bad...
|
||||
|
||||
Well, these kind of things usually
|
||||
have a chance to lose money.
|
||||
|
||||
Let's keep this discreet.
|
||||
If you feel up to it, talk to me again.
|
||||
|
||||
|
||||
It seems you have
|
||||
too many items.
|
||||
|
||||
First, go and
|
||||
organize your items,
|
||||
|
||||
Then speak to me again.
|
||||
|
||||
What?
|
||||
|
||||
You said you'd try,
|
||||
then you said no.
|
||||
|
||||
People like that
|
||||
fail at everything.
|
||||
|
||||
|
||||
What the...?
|
||||
|
||||
You don't have the
|
||||
meseta to pay me?
|
||||
|
||||
I won't work with such
|
||||
cold hearted people.
|
||||
|
||||
|
||||
Alright, let's do it.
|
||||
|
||||
You better pray
|
||||
for something good...
|
||||
|
||||
|
||||
Look here!
|
||||
Take it!
|
||||
|
||||
Even if you had bad luck,
|
||||
something good will come out of it.
|
||||
|
||||
You'll win someday!
|
||||
|
||||
In case you want to try again,
|
||||
come back to me once more.
|
||||
|
||||
|
||||
###########################################################
|
||||
|
||||
NPC: Coren Tsu - The Wanderer
|
||||
AREAS: Pioneer 2
|
||||
|
||||
Translations by: apexseals (discord: apexseals)
|
||||
Proofing & Debugging by: nolrinale (github.com/nolrinale)
|
||||
|
||||
###########################################################
|
||||
|
||||
presentation:
|
||||
|
||||
I am Coren Tsu, a wandering merchant,
|
||||
you could say.
|
||||
|
||||
Please take some time to look at
|
||||
the rare and wonderous goods
|
||||
I have been collecting.
|
||||
|
||||
If you spend a little meseta,
|
||||
you could win a wonderful prize.
|
||||
|
||||
Well? Wanna try?
|
||||
|
||||
|
||||
You may win,
|
||||
you may lose.
|
||||
|
||||
But if you don't win,
|
||||
don't take it out on me.
|
||||
|
||||
That's just the way
|
||||
gambling is, yes?
|
||||
|
||||
Well then, how much
|
||||
meseta do you want to pay?
|
||||
|
||||
As long as you pay me,
|
||||
I'll give you a great service.
|
||||
|
||||
|
||||
Huh?
|
||||
|
||||
That's too bad...
|
||||
|
||||
Well, these kind of things usually
|
||||
have a chance to lose money.
|
||||
|
||||
Let's keep this discreet.
|
||||
If you feel up to it, talk to me again.
|
||||
|
||||
|
||||
It seems you have
|
||||
too many items.
|
||||
|
||||
First, go and
|
||||
organize your items,
|
||||
|
||||
Then speak to me again.
|
||||
|
||||
What?
|
||||
|
||||
You said you'd try,
|
||||
then you said no.
|
||||
|
||||
People like that
|
||||
fail at everything.
|
||||
|
||||
|
||||
What the...?
|
||||
|
||||
You don't have the
|
||||
meseta to pay me?
|
||||
|
||||
I won't work with such
|
||||
cold hearted people.
|
||||
|
||||
|
||||
Alright, let's do it.
|
||||
|
||||
You better pray
|
||||
for something good...
|
||||
|
||||
|
||||
Look here!
|
||||
Take it!
|
||||
|
||||
Even if you had bad luck,
|
||||
something good will come out of it.
|
||||
|
||||
You'll win someday!
|
||||
|
||||
In case you want to try again,
|
||||
come back to me once more.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1003
-1003
File diff suppressed because it is too large
Load Diff
+263
-263
@@ -1,263 +1,263 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class LicenseIndex;
|
||||
|
||||
struct DCNTELicense {
|
||||
std::string serial_number;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct V1V2License {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<V1V2License> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct GCLicense {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<GCLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct XBLicense {
|
||||
std::string gamertag;
|
||||
uint64_t user_id = 0;
|
||||
uint64_t account_id = 0;
|
||||
|
||||
static std::shared_ptr<XBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct BBLicense {
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<BBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct Account {
|
||||
enum class Flag : uint32_t {
|
||||
// clang-format off
|
||||
KICK_USER = 0x00000001,
|
||||
BAN_USER = 0x00000002,
|
||||
SILENCE_USER = 0x00000004,
|
||||
CHANGE_EVENT = 0x00000010,
|
||||
ANNOUNCE = 0x00000020,
|
||||
FREE_JOIN_GAMES = 0x00000040,
|
||||
DEBUG = 0x01000000,
|
||||
CHEAT_ANYWHERE = 0x02000000,
|
||||
DISABLE_QUEST_REQUIREMENTS = 0x04000000,
|
||||
ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000,
|
||||
MODERATOR = 0x00000007,
|
||||
ADMINISTRATOR = 0x000000FF,
|
||||
ROOT = 0x7FFFFFFF,
|
||||
IS_SHARED_ACCOUNT = 0x80000000,
|
||||
// NOTE: When adding or changing license flags, don't forget to change the
|
||||
// documentation in the shell's help text.
|
||||
UNUSED_BITS = 0x70FFFF00,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// account_id is also the account's guild card number
|
||||
uint32_t account_id = 0;
|
||||
|
||||
uint32_t flags = 0;
|
||||
uint64_t ban_end_time = 0; // 0 = not banned
|
||||
std::string last_player_name;
|
||||
std::string auto_reply_message;
|
||||
|
||||
uint32_t ep3_current_meseta = 0;
|
||||
uint32_t ep3_total_meseta_earned = 0;
|
||||
|
||||
uint32_t bb_team_id = 0;
|
||||
bool is_temporary = false; // If true, isn't saved to disk
|
||||
|
||||
std::unordered_set<std::string> auto_patches_enabled;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<GCLicense>> gc_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<XBLicense>> xb_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
|
||||
|
||||
Account() = default;
|
||||
explicit Account(const JSON& json);
|
||||
virtual ~Account() = default;
|
||||
|
||||
JSON json() const;
|
||||
virtual void save() const;
|
||||
virtual void delete_file() const;
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(this->flags & static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->flags |= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->flags &= (~static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->flags ^= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void replace_all_flags(Flag mask) {
|
||||
this->flags = static_cast<uint32_t>(mask);
|
||||
}
|
||||
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
struct Login {
|
||||
bool account_was_created = false;
|
||||
// This field will never be null
|
||||
std::shared_ptr<Account> account;
|
||||
// Exactly one of the following will be non-null, representing the license
|
||||
// that the client logged in with
|
||||
std::shared_ptr<DCNTELicense> dc_nte_license;
|
||||
std::shared_ptr<V1V2License> dc_license;
|
||||
std::shared_ptr<V1V2License> pc_license;
|
||||
std::shared_ptr<GCLicense> gc_license;
|
||||
std::shared_ptr<XBLicense> xb_license;
|
||||
std::shared_ptr<BBLicense> bb_license;
|
||||
};
|
||||
|
||||
class AccountIndex {
|
||||
public:
|
||||
class no_username : public std::invalid_argument {
|
||||
public:
|
||||
no_username() : invalid_argument("serial number is zero or username is missing") {}
|
||||
};
|
||||
class incorrect_password : public std::invalid_argument {
|
||||
public:
|
||||
incorrect_password() : invalid_argument("incorrect password") {}
|
||||
};
|
||||
class incorrect_access_key : public std::invalid_argument {
|
||||
public:
|
||||
incorrect_access_key() : invalid_argument("incorrect access key") {}
|
||||
};
|
||||
class missing_account : public std::invalid_argument {
|
||||
public:
|
||||
missing_account() : invalid_argument("missing account") {}
|
||||
};
|
||||
|
||||
explicit AccountIndex(bool force_all_temporary);
|
||||
virtual ~AccountIndex() = default;
|
||||
|
||||
std::shared_ptr<Account> create_account(bool is_temporary) const;
|
||||
|
||||
size_t count() const;
|
||||
std::vector<std::shared_ptr<Account>> all() const;
|
||||
|
||||
void add(std::shared_ptr<Account> a);
|
||||
void remove(uint32_t serial_number);
|
||||
|
||||
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license);
|
||||
void add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license);
|
||||
void add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license);
|
||||
void add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license);
|
||||
void remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number);
|
||||
void remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
|
||||
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username);
|
||||
|
||||
std::shared_ptr<Account> from_account_id(uint32_t account_id) const;
|
||||
std::shared_ptr<Login> from_dc_nte_credentials(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_dc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_nte_credentials(
|
||||
uint32_t guild_card_number,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_gc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_xb_credentials(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_bb_credentials(
|
||||
const std::string& username,
|
||||
const std::string* password,
|
||||
bool allow_create);
|
||||
|
||||
std::shared_ptr<Account> create_temporary_account_for_shared_account(
|
||||
std::shared_ptr<const Account> src_a, const std::string& variation_data) const;
|
||||
|
||||
protected:
|
||||
bool force_all_temporary;
|
||||
|
||||
// This class must be thread-safe because it's used by both the patch server
|
||||
// and game server threads
|
||||
mutable std::shared_mutex lock;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_account_id;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_dc_nte_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_dc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_pc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_serial_number;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_xb_gamertag;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
|
||||
|
||||
void add_locked(std::shared_ptr<Account> a);
|
||||
|
||||
std::shared_ptr<Login> from_dc_nte_credentials_locked(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key);
|
||||
std::shared_ptr<Login> from_dc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_pc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_gc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_xb_credentials_locked(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id);
|
||||
std::shared_ptr<Login> from_bb_credentials_locked(
|
||||
const std::string& username,
|
||||
const std::string* password);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class LicenseIndex;
|
||||
|
||||
struct DCNTELicense {
|
||||
std::string serial_number;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct V1V2License {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<V1V2License> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct GCLicense {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<GCLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct XBLicense {
|
||||
std::string gamertag;
|
||||
uint64_t user_id = 0;
|
||||
uint64_t account_id = 0;
|
||||
|
||||
static std::shared_ptr<XBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct BBLicense {
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<BBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct Account {
|
||||
enum class Flag : uint32_t {
|
||||
// clang-format off
|
||||
KICK_USER = 0x00000001,
|
||||
BAN_USER = 0x00000002,
|
||||
SILENCE_USER = 0x00000004,
|
||||
CHANGE_EVENT = 0x00000010,
|
||||
ANNOUNCE = 0x00000020,
|
||||
FREE_JOIN_GAMES = 0x00000040,
|
||||
DEBUG = 0x01000000,
|
||||
CHEAT_ANYWHERE = 0x02000000,
|
||||
DISABLE_QUEST_REQUIREMENTS = 0x04000000,
|
||||
ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000,
|
||||
MODERATOR = 0x00000007,
|
||||
ADMINISTRATOR = 0x000000FF,
|
||||
ROOT = 0x7FFFFFFF,
|
||||
IS_SHARED_ACCOUNT = 0x80000000,
|
||||
// NOTE: When adding or changing license flags, don't forget to change the
|
||||
// documentation in the shell's help text.
|
||||
UNUSED_BITS = 0x70FFFF00,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// account_id is also the account's guild card number
|
||||
uint32_t account_id = 0;
|
||||
|
||||
uint32_t flags = 0;
|
||||
uint64_t ban_end_time = 0; // 0 = not banned
|
||||
std::string last_player_name;
|
||||
std::string auto_reply_message;
|
||||
|
||||
uint32_t ep3_current_meseta = 0;
|
||||
uint32_t ep3_total_meseta_earned = 0;
|
||||
|
||||
uint32_t bb_team_id = 0;
|
||||
bool is_temporary = false; // If true, isn't saved to disk
|
||||
|
||||
std::unordered_set<std::string> auto_patches_enabled;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<GCLicense>> gc_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<XBLicense>> xb_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
|
||||
|
||||
Account() = default;
|
||||
explicit Account(const JSON& json);
|
||||
virtual ~Account() = default;
|
||||
|
||||
JSON json() const;
|
||||
virtual void save() const;
|
||||
virtual void delete_file() const;
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(this->flags & static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->flags |= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->flags &= (~static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->flags ^= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void replace_all_flags(Flag mask) {
|
||||
this->flags = static_cast<uint32_t>(mask);
|
||||
}
|
||||
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
struct Login {
|
||||
bool account_was_created = false;
|
||||
// This field will never be null
|
||||
std::shared_ptr<Account> account;
|
||||
// Exactly one of the following will be non-null, representing the license
|
||||
// that the client logged in with
|
||||
std::shared_ptr<DCNTELicense> dc_nte_license;
|
||||
std::shared_ptr<V1V2License> dc_license;
|
||||
std::shared_ptr<V1V2License> pc_license;
|
||||
std::shared_ptr<GCLicense> gc_license;
|
||||
std::shared_ptr<XBLicense> xb_license;
|
||||
std::shared_ptr<BBLicense> bb_license;
|
||||
};
|
||||
|
||||
class AccountIndex {
|
||||
public:
|
||||
class no_username : public std::invalid_argument {
|
||||
public:
|
||||
no_username() : invalid_argument("serial number is zero or username is missing") {}
|
||||
};
|
||||
class incorrect_password : public std::invalid_argument {
|
||||
public:
|
||||
incorrect_password() : invalid_argument("incorrect password") {}
|
||||
};
|
||||
class incorrect_access_key : public std::invalid_argument {
|
||||
public:
|
||||
incorrect_access_key() : invalid_argument("incorrect access key") {}
|
||||
};
|
||||
class missing_account : public std::invalid_argument {
|
||||
public:
|
||||
missing_account() : invalid_argument("missing account") {}
|
||||
};
|
||||
|
||||
explicit AccountIndex(bool force_all_temporary);
|
||||
virtual ~AccountIndex() = default;
|
||||
|
||||
std::shared_ptr<Account> create_account(bool is_temporary) const;
|
||||
|
||||
size_t count() const;
|
||||
std::vector<std::shared_ptr<Account>> all() const;
|
||||
|
||||
void add(std::shared_ptr<Account> a);
|
||||
void remove(uint32_t serial_number);
|
||||
|
||||
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license);
|
||||
void add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license);
|
||||
void add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license);
|
||||
void add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license);
|
||||
void remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number);
|
||||
void remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
|
||||
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username);
|
||||
|
||||
std::shared_ptr<Account> from_account_id(uint32_t account_id) const;
|
||||
std::shared_ptr<Login> from_dc_nte_credentials(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_dc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_nte_credentials(
|
||||
uint32_t guild_card_number,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_gc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_xb_credentials(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_bb_credentials(
|
||||
const std::string& username,
|
||||
const std::string* password,
|
||||
bool allow_create);
|
||||
|
||||
std::shared_ptr<Account> create_temporary_account_for_shared_account(
|
||||
std::shared_ptr<const Account> src_a, const std::string& variation_data) const;
|
||||
|
||||
protected:
|
||||
bool force_all_temporary;
|
||||
|
||||
// This class must be thread-safe because it's used by both the patch server
|
||||
// and game server threads
|
||||
mutable std::shared_mutex lock;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_account_id;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_dc_nte_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_dc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_pc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_serial_number;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_xb_gamertag;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
|
||||
|
||||
void add_locked(std::shared_ptr<Account> a);
|
||||
|
||||
std::shared_ptr<Login> from_dc_nte_credentials_locked(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key);
|
||||
std::shared_ptr<Login> from_dc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_pc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_gc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_xb_credentials_locked(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id);
|
||||
std::shared_ptr<Login> from_bb_credentials_locked(
|
||||
const std::string& username,
|
||||
const std::string* password);
|
||||
};
|
||||
|
||||
+83
-83
@@ -1,83 +1,83 @@
|
||||
#include "BattleParamsIndex.hh"
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream) const {
|
||||
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
|
||||
fprintf(stream,
|
||||
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
|
||||
e.char_stats.atp.load(),
|
||||
e.char_stats.mst.load(),
|
||||
e.char_stats.evp.load(),
|
||||
e.char_stats.hp.load(),
|
||||
e.char_stats.dfp.load(),
|
||||
e.char_stats.ata.load(),
|
||||
e.char_stats.lck.load(),
|
||||
e.esp.load(),
|
||||
e.experience.load(),
|
||||
e.meseta.load());
|
||||
};
|
||||
|
||||
for (size_t diff = 0; diff < 4; diff++) {
|
||||
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
|
||||
abbreviation_for_difficulty(diff));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
fprintf(stream, " %02zX ", z);
|
||||
print_entry(stream, this->stats[diff][z]);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BattleParamsIndex::BattleParamsIndex(
|
||||
shared_ptr<const string> data_on_ep1,
|
||||
shared_ptr<const string> data_on_ep2,
|
||||
shared_ptr<const string> data_on_ep4,
|
||||
shared_ptr<const string> data_off_ep1,
|
||||
shared_ptr<const string> data_off_ep2,
|
||||
shared_ptr<const string> data_off_ep4) {
|
||||
this->files[0][0].data = data_on_ep1;
|
||||
this->files[0][1].data = data_on_ep2;
|
||||
this->files[0][2].data = data_on_ep4;
|
||||
this->files[1][0].data = data_off_ep1;
|
||||
this->files[1][1].data = data_off_ep2;
|
||||
this->files[1][2].data = data_off_ep4;
|
||||
|
||||
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
|
||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||
auto& file = this->files[is_solo][episode];
|
||||
if (file.data->size() < sizeof(Table)) {
|
||||
throw runtime_error(string_printf(
|
||||
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
|
||||
sizeof(Table), file.data->size(), is_solo, episode));
|
||||
}
|
||||
file.table = reinterpret_cast<const Table*>(file.data->data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
uint8_t ep_index;
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
ep_index = 0;
|
||||
break;
|
||||
case Episode::EP2:
|
||||
ep_index = 1;
|
||||
break;
|
||||
case Episode::EP4:
|
||||
ep_index = 2;
|
||||
break;
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
}
|
||||
|
||||
return *this->files[!!solo][ep_index].table;
|
||||
}
|
||||
#include "BattleParamsIndex.hh"
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream) const {
|
||||
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
|
||||
fprintf(stream,
|
||||
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
|
||||
e.char_stats.atp.load(),
|
||||
e.char_stats.mst.load(),
|
||||
e.char_stats.evp.load(),
|
||||
e.char_stats.hp.load(),
|
||||
e.char_stats.dfp.load(),
|
||||
e.char_stats.ata.load(),
|
||||
e.char_stats.lck.load(),
|
||||
e.esp.load(),
|
||||
e.experience.load(),
|
||||
e.meseta.load());
|
||||
};
|
||||
|
||||
for (size_t diff = 0; diff < 4; diff++) {
|
||||
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
|
||||
abbreviation_for_difficulty(diff));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
fprintf(stream, " %02zX ", z);
|
||||
print_entry(stream, this->stats[diff][z]);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BattleParamsIndex::BattleParamsIndex(
|
||||
shared_ptr<const string> data_on_ep1,
|
||||
shared_ptr<const string> data_on_ep2,
|
||||
shared_ptr<const string> data_on_ep4,
|
||||
shared_ptr<const string> data_off_ep1,
|
||||
shared_ptr<const string> data_off_ep2,
|
||||
shared_ptr<const string> data_off_ep4) {
|
||||
this->files[0][0].data = data_on_ep1;
|
||||
this->files[0][1].data = data_on_ep2;
|
||||
this->files[0][2].data = data_on_ep4;
|
||||
this->files[1][0].data = data_off_ep1;
|
||||
this->files[1][1].data = data_off_ep2;
|
||||
this->files[1][2].data = data_off_ep4;
|
||||
|
||||
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
|
||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||
auto& file = this->files[is_solo][episode];
|
||||
if (file.data->size() < sizeof(Table)) {
|
||||
throw runtime_error(string_printf(
|
||||
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
|
||||
sizeof(Table), file.data->size(), is_solo, episode));
|
||||
}
|
||||
file.table = reinterpret_cast<const Table*>(file.data->data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
uint8_t ep_index;
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
ep_index = 0;
|
||||
break;
|
||||
case Episode::EP2:
|
||||
ep_index = 1;
|
||||
break;
|
||||
case Episode::EP4:
|
||||
ep_index = 2;
|
||||
break;
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
}
|
||||
|
||||
return *this->files[!!solo][ep_index].table;
|
||||
}
|
||||
|
||||
+100
-100
@@ -1,100 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class BattleParamsIndex {
|
||||
public:
|
||||
// These files are little-endian, even on PSO GC.
|
||||
|
||||
struct AttackData {
|
||||
/* 00 */ le_int16_t unknown_a1;
|
||||
/* 02 */ le_int16_t atp;
|
||||
/* 04 */ le_int16_t ata_bonus;
|
||||
/* 06 */ le_uint16_t unknown_a4;
|
||||
/* 08 */ le_float distance_x;
|
||||
/* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused)
|
||||
/* 10 */ le_float distance_y;
|
||||
/* 14 */ le_uint16_t unknown_a8;
|
||||
/* 16 */ le_uint16_t unknown_a9;
|
||||
/* 18 */ le_uint16_t unknown_a10;
|
||||
/* 1A */ le_uint16_t unknown_a11;
|
||||
/* 1C */ le_uint32_t unknown_a12;
|
||||
/* 20 */ le_uint32_t unknown_a13;
|
||||
/* 24 */ le_uint32_t unknown_a14;
|
||||
/* 28 */ le_uint32_t unknown_a15;
|
||||
/* 2C */ le_uint32_t unknown_a16;
|
||||
/* 30 */
|
||||
} __packed_ws__(AttackData, 0x30);
|
||||
|
||||
struct ResistData {
|
||||
/* 00 */ le_int16_t evp_bonus;
|
||||
/* 02 */ le_uint16_t efr;
|
||||
/* 04 */ le_uint16_t eic;
|
||||
/* 06 */ le_uint16_t eth;
|
||||
/* 08 */ le_uint16_t elt;
|
||||
/* 0A */ le_uint16_t edk;
|
||||
/* 0C */ le_uint32_t unknown_a6;
|
||||
/* 10 */ le_uint32_t unknown_a7;
|
||||
/* 14 */ le_uint32_t unknown_a8;
|
||||
/* 18 */ le_uint32_t unknown_a9;
|
||||
/* 1C */ le_int32_t dfp_bonus;
|
||||
/* 20 */
|
||||
} __packed_ws__(ResistData, 0x20);
|
||||
|
||||
struct MovementData {
|
||||
/* 00 */ le_float idle_move_speed;
|
||||
/* 04 */ le_float idle_animation_speed;
|
||||
/* 08 */ le_float move_speed;
|
||||
/* 0C */ le_float animation_speed;
|
||||
/* 10 */ le_float unknown_a1;
|
||||
/* 14 */ le_float unknown_a2;
|
||||
/* 18 */ le_uint32_t unknown_a3;
|
||||
/* 1C */ le_uint32_t unknown_a4;
|
||||
/* 20 */ le_uint32_t unknown_a5;
|
||||
/* 24 */ le_uint32_t unknown_a6;
|
||||
/* 28 */ le_uint32_t unknown_a7;
|
||||
/* 2C */ le_uint32_t unknown_a8;
|
||||
/* 30 */
|
||||
} __packed_ws__(MovementData, 0x30);
|
||||
|
||||
struct Table {
|
||||
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
|
||||
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
|
||||
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
|
||||
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
|
||||
/* F600 */
|
||||
|
||||
void print(FILE* stream) const;
|
||||
} __packed_ws__(Table, 0xF600);
|
||||
|
||||
BattleParamsIndex(
|
||||
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep2, // BattleParamEntry_lab_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep4, // BattleParamEntry_ep4_on.dat
|
||||
std::shared_ptr<const std::string> data_off_ep1, // BattleParamEntry.dat
|
||||
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
|
||||
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
|
||||
|
||||
const Table& get_table(bool solo, Episode episode) const;
|
||||
|
||||
private:
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table;
|
||||
};
|
||||
|
||||
// Indexed as [online/offline][episode]
|
||||
std::array<std::array<File, 3>, 2> files;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class BattleParamsIndex {
|
||||
public:
|
||||
// These files are little-endian, even on PSO GC.
|
||||
|
||||
struct AttackData {
|
||||
/* 00 */ le_int16_t unknown_a1;
|
||||
/* 02 */ le_int16_t atp;
|
||||
/* 04 */ le_int16_t ata_bonus;
|
||||
/* 06 */ le_uint16_t unknown_a4;
|
||||
/* 08 */ le_float distance_x;
|
||||
/* 0C */ le_uint32_t angle_x; // Out of 0x10000 (high 16 bits are unused)
|
||||
/* 10 */ le_float distance_y;
|
||||
/* 14 */ le_uint16_t unknown_a8;
|
||||
/* 16 */ le_uint16_t unknown_a9;
|
||||
/* 18 */ le_uint16_t unknown_a10;
|
||||
/* 1A */ le_uint16_t unknown_a11;
|
||||
/* 1C */ le_uint32_t unknown_a12;
|
||||
/* 20 */ le_uint32_t unknown_a13;
|
||||
/* 24 */ le_uint32_t unknown_a14;
|
||||
/* 28 */ le_uint32_t unknown_a15;
|
||||
/* 2C */ le_uint32_t unknown_a16;
|
||||
/* 30 */
|
||||
} __packed_ws__(AttackData, 0x30);
|
||||
|
||||
struct ResistData {
|
||||
/* 00 */ le_int16_t evp_bonus;
|
||||
/* 02 */ le_uint16_t efr;
|
||||
/* 04 */ le_uint16_t eic;
|
||||
/* 06 */ le_uint16_t eth;
|
||||
/* 08 */ le_uint16_t elt;
|
||||
/* 0A */ le_uint16_t edk;
|
||||
/* 0C */ le_uint32_t unknown_a6;
|
||||
/* 10 */ le_uint32_t unknown_a7;
|
||||
/* 14 */ le_uint32_t unknown_a8;
|
||||
/* 18 */ le_uint32_t unknown_a9;
|
||||
/* 1C */ le_int32_t dfp_bonus;
|
||||
/* 20 */
|
||||
} __packed_ws__(ResistData, 0x20);
|
||||
|
||||
struct MovementData {
|
||||
/* 00 */ le_float idle_move_speed;
|
||||
/* 04 */ le_float idle_animation_speed;
|
||||
/* 08 */ le_float move_speed;
|
||||
/* 0C */ le_float animation_speed;
|
||||
/* 10 */ le_float unknown_a1;
|
||||
/* 14 */ le_float unknown_a2;
|
||||
/* 18 */ le_uint32_t unknown_a3;
|
||||
/* 1C */ le_uint32_t unknown_a4;
|
||||
/* 20 */ le_uint32_t unknown_a5;
|
||||
/* 24 */ le_uint32_t unknown_a6;
|
||||
/* 28 */ le_uint32_t unknown_a7;
|
||||
/* 2C */ le_uint32_t unknown_a8;
|
||||
/* 30 */
|
||||
} __packed_ws__(MovementData, 0x30);
|
||||
|
||||
struct Table {
|
||||
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
|
||||
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
|
||||
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
|
||||
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
|
||||
/* F600 */
|
||||
|
||||
void print(FILE* stream) const;
|
||||
} __packed_ws__(Table, 0xF600);
|
||||
|
||||
BattleParamsIndex(
|
||||
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep2, // BattleParamEntry_lab_on.dat
|
||||
std::shared_ptr<const std::string> data_on_ep4, // BattleParamEntry_ep4_on.dat
|
||||
std::shared_ptr<const std::string> data_off_ep1, // BattleParamEntry.dat
|
||||
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
|
||||
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
|
||||
|
||||
const Table& get_table(bool solo, Episode episode) const;
|
||||
|
||||
private:
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table;
|
||||
};
|
||||
|
||||
// Indexed as [online/offline][episode]
|
||||
std::array<std::array<File, 3>, 2> files;
|
||||
};
|
||||
|
||||
+188
-188
@@ -1,188 +1,188 @@
|
||||
#include "CatSession.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
|
||||
|
||||
CatSession::CatSession(
|
||||
shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
|
||||
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
|
||||
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
|
||||
bb_key_file(bb_key_file) {
|
||||
|
||||
if (remote.ss_family != AF_INET) {
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
|
||||
event_add(this->read_event.get(), nullptr);
|
||||
this->poll.add(0, POLLIN);
|
||||
}
|
||||
|
||||
void CatSession::execute_command(const std::string& command) {
|
||||
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
|
||||
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_input(
|
||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_input(command, flag, data);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_input(
|
||||
uint16_t command, uint32_t flag, std::string& data) {
|
||||
if (!uses_v4_encryption(this->channel.version)) {
|
||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else { // PC, DC, or patch server
|
||||
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
}
|
||||
}
|
||||
} else { // BB
|
||||
if (command == 0x03 || command == 0x9B) {
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
|
||||
this->channel.crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||
this->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||
this->log.info("Enabled BB encryption");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(
|
||||
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_error(events);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_error(short events) {
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
this->log.info("Channel connected");
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log.info("Session endpoint has disconnected");
|
||||
this->channel.disconnect();
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<CatSession*>(ctx)->read_stdin();
|
||||
}
|
||||
|
||||
void CatSession::read_stdin() {
|
||||
bool any_command_read = false;
|
||||
for (;;) {
|
||||
auto poll_result = this->poll.poll();
|
||||
short fd_events = 0;
|
||||
try {
|
||||
fd_events = poll_result.at(0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (!(fd_events & POLLIN)) {
|
||||
break;
|
||||
}
|
||||
|
||||
string command(2048, '\0');
|
||||
if (!fgets(command.data(), command.size(), stdin)) {
|
||||
if (!any_command_read) {
|
||||
// ctrl+d probably; we should exit
|
||||
fputc('\n', stderr);
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} else {
|
||||
break; // probably not EOF; just no more commands for now
|
||||
}
|
||||
}
|
||||
|
||||
// trim the extra data off the string
|
||||
size_t len = strlen(command.c_str());
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
if (command[len - 1] == '\n') {
|
||||
len--;
|
||||
}
|
||||
command.resize(len);
|
||||
any_command_read = true;
|
||||
|
||||
try {
|
||||
execute_command(command);
|
||||
} catch (const exit_shell&) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} catch (const exception& e) {
|
||||
fprintf(stderr, "FAILED: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "CatSession.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
|
||||
|
||||
CatSession::CatSession(
|
||||
shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
|
||||
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
|
||||
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
|
||||
bb_key_file(bb_key_file) {
|
||||
|
||||
if (remote.ss_family != AF_INET) {
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
|
||||
event_add(this->read_event.get(), nullptr);
|
||||
this->poll.add(0, POLLIN);
|
||||
}
|
||||
|
||||
void CatSession::execute_command(const std::string& command) {
|
||||
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
|
||||
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_input(
|
||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_input(command, flag, data);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_input(
|
||||
uint16_t command, uint32_t flag, std::string& data) {
|
||||
if (!uses_v4_encryption(this->channel.version)) {
|
||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else { // PC, DC, or patch server
|
||||
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
}
|
||||
}
|
||||
} else { // BB
|
||||
if (command == 0x03 || command == 0x9B) {
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
|
||||
this->channel.crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||
this->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||
this->log.info("Enabled BB encryption");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(
|
||||
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_error(events);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_error(short events) {
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
this->log.info("Channel connected");
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log.info("Session endpoint has disconnected");
|
||||
this->channel.disconnect();
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<CatSession*>(ctx)->read_stdin();
|
||||
}
|
||||
|
||||
void CatSession::read_stdin() {
|
||||
bool any_command_read = false;
|
||||
for (;;) {
|
||||
auto poll_result = this->poll.poll();
|
||||
short fd_events = 0;
|
||||
try {
|
||||
fd_events = poll_result.at(0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (!(fd_events & POLLIN)) {
|
||||
break;
|
||||
}
|
||||
|
||||
string command(2048, '\0');
|
||||
if (!fgets(command.data(), command.size(), stdin)) {
|
||||
if (!any_command_read) {
|
||||
// ctrl+d probably; we should exit
|
||||
fputc('\n', stderr);
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} else {
|
||||
break; // probably not EOF; just no more commands for now
|
||||
}
|
||||
}
|
||||
|
||||
// trim the extra data off the string
|
||||
size_t len = strlen(command.c_str());
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
if (command[len - 1] == '\n') {
|
||||
len--;
|
||||
}
|
||||
command.resize(len);
|
||||
any_command_read = true;
|
||||
|
||||
try {
|
||||
execute_command(command);
|
||||
} catch (const exit_shell&) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} catch (const exception& e) {
|
||||
fprintf(stderr, "FAILED: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+54
-54
@@ -1,54 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class CatSession {
|
||||
public:
|
||||
CatSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
|
||||
CatSession(const CatSession&) = delete;
|
||||
CatSession(CatSession&&) = delete;
|
||||
CatSession& operator=(const CatSession&) = delete;
|
||||
CatSession& operator=(CatSession&&) = delete;
|
||||
virtual ~CatSession() = default;
|
||||
|
||||
protected:
|
||||
PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
||||
Poll poll;
|
||||
|
||||
Channel channel;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
|
||||
class exit_shell : public std::runtime_error {
|
||||
public:
|
||||
exit_shell();
|
||||
~exit_shell() = default;
|
||||
};
|
||||
|
||||
virtual void execute_command(const std::string& command);
|
||||
|
||||
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx);
|
||||
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||
void on_channel_error(short events);
|
||||
void read_stdin();
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class CatSession {
|
||||
public:
|
||||
CatSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
|
||||
CatSession(const CatSession&) = delete;
|
||||
CatSession(CatSession&&) = delete;
|
||||
CatSession& operator=(const CatSession&) = delete;
|
||||
CatSession& operator=(CatSession&&) = delete;
|
||||
virtual ~CatSession() = default;
|
||||
|
||||
protected:
|
||||
PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
||||
Poll poll;
|
||||
|
||||
Channel channel;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
|
||||
class exit_shell : public std::runtime_error {
|
||||
public:
|
||||
exit_shell();
|
||||
~exit_shell() = default;
|
||||
};
|
||||
|
||||
virtual void execute_command(const std::string& command);
|
||||
|
||||
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx);
|
||||
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||
void on_channel_error(short events);
|
||||
void read_stdin();
|
||||
};
|
||||
|
||||
+421
-421
@@ -1,421 +1,421 @@
|
||||
#include "Channel.hh"
|
||||
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
static void flush_and_free_bufferevent(struct bufferevent* bev) {
|
||||
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
virtual_network_id(0),
|
||||
version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
this->set_bufferevent(bev, virtual_network_id);
|
||||
}
|
||||
|
||||
void Channel::replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name) {
|
||||
this->set_bufferevent(other.bev.release(), other.virtual_network_id);
|
||||
this->local_addr = other.local_addr;
|
||||
this->remote_addr = other.remote_addr;
|
||||
this->version = other.version;
|
||||
this->language = other.language;
|
||||
this->crypt_in = other.crypt_in;
|
||||
this->crypt_out = other.crypt_out;
|
||||
this->name = name;
|
||||
this->terminal_send_color = other.terminal_send_color;
|
||||
this->terminal_recv_color = other.terminal_recv_color;
|
||||
this->on_command_received = on_command_received;
|
||||
this->on_error = on_error;
|
||||
this->context_obj = context_obj;
|
||||
other.disconnect(); // Clears crypts, addrs, etc.
|
||||
}
|
||||
|
||||
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
|
||||
this->bev.reset(bev);
|
||||
this->virtual_network_id = virtual_network_id;
|
||||
|
||||
if (this->bev.get()) {
|
||||
int fd = bufferevent_getfd(this->bev.get());
|
||||
if (fd < 0) {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
} else {
|
||||
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
|
||||
}
|
||||
|
||||
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
|
||||
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
|
||||
|
||||
} else {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::disconnect() {
|
||||
if (this->bev.get()) {
|
||||
// If the output buffer is not empty, move the bufferevent into the draining
|
||||
// pool instead of disconnecting it, to make sure all the data gets sent.
|
||||
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
|
||||
if (evbuffer_get_length(out_buffer) == 0) {
|
||||
this->bev.reset(); // Destructor flushes and frees the bufferevent
|
||||
} else {
|
||||
// The callbacks will free it when all the data is sent or the client
|
||||
// disconnects
|
||||
|
||||
auto on_output = +[](struct bufferevent* bev, void*) -> void {
|
||||
flush_and_free_bufferevent(bev);
|
||||
};
|
||||
|
||||
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
channel_exceptions_log.warning(
|
||||
"Disconnecting channel caused error %d (%s)", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
};
|
||||
|
||||
struct bufferevent* bev = this->bev.release();
|
||||
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
|
||||
bufferevent_disable(bev, EV_READ);
|
||||
}
|
||||
}
|
||||
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
this->virtual_network_id = false;
|
||||
this->crypt_in.reset();
|
||||
this->crypt_out.reset();
|
||||
}
|
||||
|
||||
Channel::Message Channel::recv() {
|
||||
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
|
||||
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
PSOCommandHeader header;
|
||||
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(&header, header_size, false);
|
||||
}
|
||||
|
||||
size_t command_logical_size = header.size(version);
|
||||
|
||||
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
|
||||
// is not reflected in the size field. This logic does not occur if encryption
|
||||
// is not yet enabled.
|
||||
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
|
||||
? ((command_logical_size + 7) & ~7)
|
||||
: command_logical_size;
|
||||
if (evbuffer_get_length(buf) < command_physical_size) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
// If we get here, then there is a full command in the buffer. Some encryption
|
||||
// algorithms' advancement depends on the decrypted data, so we have to
|
||||
// actually decrypt the header again (with advance=true) to keep them in a
|
||||
// consistent state.
|
||||
|
||||
string header_data(header_size, '\0');
|
||||
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(header_data.data(), header_data.size());
|
||||
}
|
||||
|
||||
string command_data(command_physical_size - header_size, '\0');
|
||||
if (evbuffer_remove(buf, command_data.data(), command_data.size()) < static_cast<ssize_t>(command_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
// Some versions of PSO DC can send commands whose sizes are not a multiple
|
||||
// of 4, but the server is expected to always use a multiple of 4 bytes when
|
||||
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
|
||||
// we have to round up the size for DC commands here.
|
||||
size_t orig_size = command_data.size();
|
||||
command_data.resize((orig_size + 3) & (~3), 0);
|
||||
this->crypt_in->decrypt(command_data.data(), command_data.size());
|
||||
command_data.resize(orig_size);
|
||||
}
|
||||
command_data.resize(command_logical_size - header_size);
|
||||
|
||||
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
}
|
||||
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
} else {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
name_for_enum(this->version),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
}
|
||||
|
||||
vector<struct iovec> iovs;
|
||||
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
|
||||
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
|
||||
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.command = header.command(this->version),
|
||||
.flag = header.flag(this->version),
|
||||
.data = std::move(command_data),
|
||||
};
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
|
||||
this->send(cmd, flag, nullptr, 0, silent);
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
|
||||
if (!this->connected()) {
|
||||
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
for (const auto& b : blocks) {
|
||||
size += b.second;
|
||||
}
|
||||
|
||||
string send_data;
|
||||
size_t logical_size;
|
||||
size_t send_data_size = 0;
|
||||
switch (this->version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
case Version::XB_V3: {
|
||||
PSOCommandHeaderDCV3 header;
|
||||
if (this->crypt_out.get() &&
|
||||
(this->version != Version::DC_NTE) &&
|
||||
(this->version != Version::DC_V1_11_2000_PROTOTYPE) &&
|
||||
(this->version != Version::DC_V1)) {
|
||||
send_data_size = (sizeof(header) + size + 3) & ~3;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = send_data_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
header.size = send_data_size;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2: {
|
||||
PSOCommandHeaderPC header;
|
||||
if (this->crypt_out.get()) {
|
||||
send_data_size = (sizeof(header) + size + 3) & ~3;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = send_data_size;
|
||||
header.size = send_data_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
case Version::BB_V4: {
|
||||
// BB has an annoying behavior here: command lengths must be multiples of
|
||||
// 4, but the actual data length must be a multiple of 8. If the size
|
||||
// field is not divisible by 8, 4 extra bytes are sent anyway. This
|
||||
// behavior only applies when encryption is enabled - any commands sent
|
||||
// before encryption is enabled have no size restrictions (except they
|
||||
// must include a full header and must fit in the client's receive
|
||||
// buffer), and no implicit extra bytes are sent.
|
||||
PSOCommandHeaderBB header;
|
||||
if (this->crypt_out.get()) {
|
||||
send_data_size = (sizeof(header) + size + 7) & ~7;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = (sizeof(header) + size + 3) & ~3;
|
||||
header.size = logical_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in send_command");
|
||||
}
|
||||
|
||||
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00
|
||||
// bytes in size
|
||||
if (send_data_size > 0x7C00) {
|
||||
throw runtime_error("outbound command too large");
|
||||
}
|
||||
|
||||
send_data.reserve(send_data_size);
|
||||
for (const auto& b : blocks) {
|
||||
send_data.append(reinterpret_cast<const char*>(b.first), b.second);
|
||||
}
|
||||
send_data.resize(send_data_size, '\0');
|
||||
|
||||
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(), cmd, flag);
|
||||
} else {
|
||||
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(), name_for_enum(version), cmd, flag);
|
||||
}
|
||||
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->crypt_out.get()) {
|
||||
this->crypt_out->encrypt(send_data.data(), send_data.size());
|
||||
}
|
||||
|
||||
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
|
||||
evbuffer_add(buf, send_data.data(), send_data.size());
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
||||
this->send(cmd, flag, {make_pair(data, size)}, silent);
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
|
||||
this->send(cmd, flag, data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
void Channel::send(const void* data, size_t size, bool silent) {
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
|
||||
this->send(
|
||||
header->command(this->version),
|
||||
header->flag(this->version),
|
||||
reinterpret_cast<const uint8_t*>(data) + header_size,
|
||||
size - header_size,
|
||||
silent);
|
||||
}
|
||||
|
||||
void Channel::send(const string& data, bool silent) {
|
||||
return this->send(data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
// The client can be disconnected during on_command_received, so we have to
|
||||
// make sure ch->bev is valid every time before calling recv()
|
||||
while (ch->bev.get()) {
|
||||
Message msg;
|
||||
try {
|
||||
msg = ch->recv();
|
||||
} catch (const out_of_range&) {
|
||||
break;
|
||||
} catch (const exception& e) {
|
||||
channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
|
||||
ch->on_error(*ch, BEV_EVENT_ERROR);
|
||||
break;
|
||||
}
|
||||
if (ch->on_command_received) {
|
||||
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
if (ch->on_error) {
|
||||
ch->on_error(*ch, events);
|
||||
} else {
|
||||
ch->disconnect();
|
||||
}
|
||||
}
|
||||
#include "Channel.hh"
|
||||
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
static void flush_and_free_bufferevent(struct bufferevent* bev) {
|
||||
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
virtual_network_id(0),
|
||||
version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
this->set_bufferevent(bev, virtual_network_id);
|
||||
}
|
||||
|
||||
void Channel::replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name) {
|
||||
this->set_bufferevent(other.bev.release(), other.virtual_network_id);
|
||||
this->local_addr = other.local_addr;
|
||||
this->remote_addr = other.remote_addr;
|
||||
this->version = other.version;
|
||||
this->language = other.language;
|
||||
this->crypt_in = other.crypt_in;
|
||||
this->crypt_out = other.crypt_out;
|
||||
this->name = name;
|
||||
this->terminal_send_color = other.terminal_send_color;
|
||||
this->terminal_recv_color = other.terminal_recv_color;
|
||||
this->on_command_received = on_command_received;
|
||||
this->on_error = on_error;
|
||||
this->context_obj = context_obj;
|
||||
other.disconnect(); // Clears crypts, addrs, etc.
|
||||
}
|
||||
|
||||
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
|
||||
this->bev.reset(bev);
|
||||
this->virtual_network_id = virtual_network_id;
|
||||
|
||||
if (this->bev.get()) {
|
||||
int fd = bufferevent_getfd(this->bev.get());
|
||||
if (fd < 0) {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
} else {
|
||||
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
|
||||
}
|
||||
|
||||
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
|
||||
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
|
||||
|
||||
} else {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::disconnect() {
|
||||
if (this->bev.get()) {
|
||||
// If the output buffer is not empty, move the bufferevent into the draining
|
||||
// pool instead of disconnecting it, to make sure all the data gets sent.
|
||||
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
|
||||
if (evbuffer_get_length(out_buffer) == 0) {
|
||||
this->bev.reset(); // Destructor flushes and frees the bufferevent
|
||||
} else {
|
||||
// The callbacks will free it when all the data is sent or the client
|
||||
// disconnects
|
||||
|
||||
auto on_output = +[](struct bufferevent* bev, void*) -> void {
|
||||
flush_and_free_bufferevent(bev);
|
||||
};
|
||||
|
||||
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
channel_exceptions_log.warning(
|
||||
"Disconnecting channel caused error %d (%s)", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
};
|
||||
|
||||
struct bufferevent* bev = this->bev.release();
|
||||
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
|
||||
bufferevent_disable(bev, EV_READ);
|
||||
}
|
||||
}
|
||||
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
this->virtual_network_id = false;
|
||||
this->crypt_in.reset();
|
||||
this->crypt_out.reset();
|
||||
}
|
||||
|
||||
Channel::Message Channel::recv() {
|
||||
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
|
||||
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
PSOCommandHeader header;
|
||||
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(&header, header_size, false);
|
||||
}
|
||||
|
||||
size_t command_logical_size = header.size(version);
|
||||
|
||||
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
|
||||
// is not reflected in the size field. This logic does not occur if encryption
|
||||
// is not yet enabled.
|
||||
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
|
||||
? ((command_logical_size + 7) & ~7)
|
||||
: command_logical_size;
|
||||
if (evbuffer_get_length(buf) < command_physical_size) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
// If we get here, then there is a full command in the buffer. Some encryption
|
||||
// algorithms' advancement depends on the decrypted data, so we have to
|
||||
// actually decrypt the header again (with advance=true) to keep them in a
|
||||
// consistent state.
|
||||
|
||||
string header_data(header_size, '\0');
|
||||
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(header_data.data(), header_data.size());
|
||||
}
|
||||
|
||||
string command_data(command_physical_size - header_size, '\0');
|
||||
if (evbuffer_remove(buf, command_data.data(), command_data.size()) < static_cast<ssize_t>(command_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
// Some versions of PSO DC can send commands whose sizes are not a multiple
|
||||
// of 4, but the server is expected to always use a multiple of 4 bytes when
|
||||
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
|
||||
// we have to round up the size for DC commands here.
|
||||
size_t orig_size = command_data.size();
|
||||
command_data.resize((orig_size + 3) & (~3), 0);
|
||||
this->crypt_in->decrypt(command_data.data(), command_data.size());
|
||||
command_data.resize(orig_size);
|
||||
}
|
||||
command_data.resize(command_logical_size - header_size);
|
||||
|
||||
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
}
|
||||
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
} else {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
name_for_enum(this->version),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
}
|
||||
|
||||
vector<struct iovec> iovs;
|
||||
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
|
||||
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
|
||||
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.command = header.command(this->version),
|
||||
.flag = header.flag(this->version),
|
||||
.data = std::move(command_data),
|
||||
};
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
|
||||
this->send(cmd, flag, nullptr, 0, silent);
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
|
||||
if (!this->connected()) {
|
||||
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
for (const auto& b : blocks) {
|
||||
size += b.second;
|
||||
}
|
||||
|
||||
string send_data;
|
||||
size_t logical_size;
|
||||
size_t send_data_size = 0;
|
||||
switch (this->version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
case Version::XB_V3: {
|
||||
PSOCommandHeaderDCV3 header;
|
||||
if (this->crypt_out.get() &&
|
||||
(this->version != Version::DC_NTE) &&
|
||||
(this->version != Version::DC_V1_11_2000_PROTOTYPE) &&
|
||||
(this->version != Version::DC_V1)) {
|
||||
send_data_size = (sizeof(header) + size + 3) & ~3;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = send_data_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
header.size = send_data_size;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2: {
|
||||
PSOCommandHeaderPC header;
|
||||
if (this->crypt_out.get()) {
|
||||
send_data_size = (sizeof(header) + size + 3) & ~3;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = send_data_size;
|
||||
header.size = send_data_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
case Version::BB_V4: {
|
||||
// BB has an annoying behavior here: command lengths must be multiples of
|
||||
// 4, but the actual data length must be a multiple of 8. If the size
|
||||
// field is not divisible by 8, 4 extra bytes are sent anyway. This
|
||||
// behavior only applies when encryption is enabled - any commands sent
|
||||
// before encryption is enabled have no size restrictions (except they
|
||||
// must include a full header and must fit in the client's receive
|
||||
// buffer), and no implicit extra bytes are sent.
|
||||
PSOCommandHeaderBB header;
|
||||
if (this->crypt_out.get()) {
|
||||
send_data_size = (sizeof(header) + size + 7) & ~7;
|
||||
} else {
|
||||
send_data_size = (sizeof(header) + size);
|
||||
}
|
||||
logical_size = (sizeof(header) + size + 3) & ~3;
|
||||
header.size = logical_size;
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in send_command");
|
||||
}
|
||||
|
||||
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00
|
||||
// bytes in size
|
||||
if (send_data_size > 0x7C00) {
|
||||
throw runtime_error("outbound command too large");
|
||||
}
|
||||
|
||||
send_data.reserve(send_data_size);
|
||||
for (const auto& b : blocks) {
|
||||
send_data.append(reinterpret_cast<const char*>(b.first), b.second);
|
||||
}
|
||||
send_data.resize(send_data_size, '\0');
|
||||
|
||||
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(), cmd, flag);
|
||||
} else {
|
||||
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(), name_for_enum(version), cmd, flag);
|
||||
}
|
||||
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->crypt_out.get()) {
|
||||
this->crypt_out->encrypt(send_data.data(), send_data.size());
|
||||
}
|
||||
|
||||
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
|
||||
evbuffer_add(buf, send_data.data(), send_data.size());
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
||||
this->send(cmd, flag, {make_pair(data, size)}, silent);
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
|
||||
this->send(cmd, flag, data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
void Channel::send(const void* data, size_t size, bool silent) {
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
|
||||
this->send(
|
||||
header->command(this->version),
|
||||
header->flag(this->version),
|
||||
reinterpret_cast<const uint8_t*>(data) + header_size,
|
||||
size - header_size,
|
||||
silent);
|
||||
}
|
||||
|
||||
void Channel::send(const string& data, bool silent) {
|
||||
return this->send(data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
// The client can be disconnected during on_command_received, so we have to
|
||||
// make sure ch->bev is valid every time before calling recv()
|
||||
while (ch->bev.get()) {
|
||||
Message msg;
|
||||
try {
|
||||
msg = ch->recv();
|
||||
} catch (const out_of_range&) {
|
||||
break;
|
||||
} catch (const exception& e) {
|
||||
channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
|
||||
ch->on_error(*ch, BEV_EVENT_ERROR);
|
||||
break;
|
||||
}
|
||||
if (ch->on_command_received) {
|
||||
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
if (ch->on_error) {
|
||||
ch->on_error(*ch, events);
|
||||
} else {
|
||||
ch->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
+103
-103
@@ -1,103 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
struct Channel {
|
||||
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
|
||||
struct sockaddr_storage local_addr;
|
||||
struct sockaddr_storage remote_addr;
|
||||
uint64_t virtual_network_id; // 0 = normal TCP connection
|
||||
|
||||
Version version;
|
||||
uint8_t language;
|
||||
std::shared_ptr<PSOEncryption> crypt_in;
|
||||
std::shared_ptr<PSOEncryption> crypt_out;
|
||||
|
||||
std::string name;
|
||||
TerminalFormat terminal_send_color;
|
||||
TerminalFormat terminal_recv_color;
|
||||
|
||||
struct Message {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
|
||||
typedef void (*on_error_t)(Channel&, short);
|
||||
|
||||
on_command_received_t on_command_received;
|
||||
on_error_t on_error;
|
||||
void* context_obj;
|
||||
|
||||
// Creates an unconnected channel
|
||||
Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name,
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
// Creates a connected channel
|
||||
Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "",
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
Channel(const Channel& other) = delete;
|
||||
Channel(Channel&& other) = delete;
|
||||
Channel& operator=(const Channel& other) = delete;
|
||||
Channel& operator=(Channel&& other) = delete;
|
||||
|
||||
void replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "");
|
||||
|
||||
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
|
||||
|
||||
inline bool connected() const {
|
||||
return this->bev.get() != nullptr;
|
||||
}
|
||||
void disconnect();
|
||||
|
||||
// Receives a message. Throws std::out_of_range if no messages are available.
|
||||
Message recv();
|
||||
|
||||
// Sends a message with an automatically-constructed header.
|
||||
void send(uint16_t cmd, uint32_t flag = 0, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false);
|
||||
template <typename CmdT>
|
||||
requires(!std::is_pointer_v<CmdT>)
|
||||
void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) {
|
||||
this->send(cmd, flag, &data, sizeof(data), silent);
|
||||
}
|
||||
|
||||
// Sends a message with a pre-existing header (as the first few bytes in the
|
||||
// data)
|
||||
void send(const void* data, size_t size, bool silent = false);
|
||||
void send(const std::string& data, bool silent = false);
|
||||
|
||||
private:
|
||||
static void dispatch_on_input(struct bufferevent*, void* ctx);
|
||||
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
struct Channel {
|
||||
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
|
||||
struct sockaddr_storage local_addr;
|
||||
struct sockaddr_storage remote_addr;
|
||||
uint64_t virtual_network_id; // 0 = normal TCP connection
|
||||
|
||||
Version version;
|
||||
uint8_t language;
|
||||
std::shared_ptr<PSOEncryption> crypt_in;
|
||||
std::shared_ptr<PSOEncryption> crypt_out;
|
||||
|
||||
std::string name;
|
||||
TerminalFormat terminal_send_color;
|
||||
TerminalFormat terminal_recv_color;
|
||||
|
||||
struct Message {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
|
||||
typedef void (*on_error_t)(Channel&, short);
|
||||
|
||||
on_command_received_t on_command_received;
|
||||
on_error_t on_error;
|
||||
void* context_obj;
|
||||
|
||||
// Creates an unconnected channel
|
||||
Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name,
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
// Creates a connected channel
|
||||
Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "",
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
Channel(const Channel& other) = delete;
|
||||
Channel(Channel&& other) = delete;
|
||||
Channel& operator=(const Channel& other) = delete;
|
||||
Channel& operator=(Channel&& other) = delete;
|
||||
|
||||
void replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "");
|
||||
|
||||
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
|
||||
|
||||
inline bool connected() const {
|
||||
return this->bev.get() != nullptr;
|
||||
}
|
||||
void disconnect();
|
||||
|
||||
// Receives a message. Throws std::out_of_range if no messages are available.
|
||||
Message recv();
|
||||
|
||||
// Sends a message with an automatically-constructed header.
|
||||
void send(uint16_t cmd, uint32_t flag = 0, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent = false);
|
||||
void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false);
|
||||
template <typename CmdT>
|
||||
requires(!std::is_pointer_v<CmdT>)
|
||||
void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) {
|
||||
this->send(cmd, flag, &data, sizeof(data), silent);
|
||||
}
|
||||
|
||||
// Sends a message with a pre-existing header (as the first few bytes in the
|
||||
// data)
|
||||
void send(const void* data, size_t size, bool silent = false);
|
||||
void send(const std::string& data, bool silent = false);
|
||||
|
||||
private:
|
||||
static void dispatch_on_input(struct bufferevent*, void* ctx);
|
||||
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
|
||||
};
|
||||
|
||||
+2603
-2603
File diff suppressed because it is too large
Load Diff
+14
-14
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_chat_command(std::shared_ptr<Client> c, const std::string& text);
|
||||
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text);
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_chat_command(std::shared_ptr<Client> c, const std::string& text);
|
||||
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text);
|
||||
|
||||
+150
-150
@@ -1,150 +1,150 @@
|
||||
#include "ChoiceSearch.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0001,
|
||||
.name = "Level",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "Own level +/- 5"},
|
||||
{0x0002, "Level 1-10"},
|
||||
{0x0003, "Level 11-20"},
|
||||
{0x0004, "Level 21-40"},
|
||||
{0x0005, "Level 41-60"},
|
||||
{0x0006, "Level 61-80"},
|
||||
{0x0007, "Level 81-100"},
|
||||
{0x0008, "Level 101-120"},
|
||||
{0x0009, "Level 121-160"},
|
||||
{0x000A, "Level 161-200"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
uint32_t target_level = target_c->character()->disp.stats.level + 1;
|
||||
switch (choice_id) {
|
||||
case 0x0001:
|
||||
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
|
||||
case 0x0002:
|
||||
return (target_level <= 10);
|
||||
case 0x0003:
|
||||
return (target_level > 10) && (target_level <= 20);
|
||||
case 0x0004:
|
||||
return (target_level > 20) && (target_level <= 40);
|
||||
case 0x0005:
|
||||
return (target_level > 40) && (target_level <= 60);
|
||||
case 0x0006:
|
||||
return (target_level > 60) && (target_level <= 80);
|
||||
case 0x0007:
|
||||
return (target_level > 80) && (target_level <= 100);
|
||||
case 0x0008:
|
||||
return (target_level > 100) && (target_level <= 120);
|
||||
case 0x0009:
|
||||
return (target_level > 120) && (target_level <= 160);
|
||||
case 0x000A:
|
||||
return (target_level > 160) && (target_level <= 200);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0002,
|
||||
.name = "Class",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0010, "Hunter"},
|
||||
{0x0001, "HUmar"},
|
||||
{0x0002, "HUnewearl"},
|
||||
{0x0003, "HUcast"},
|
||||
{0x000A, "HUcaseal"},
|
||||
{0x0011, "Ranger"},
|
||||
{0x0004, "RAmar"},
|
||||
{0x000C, "RAmarl"},
|
||||
{0x0005, "RAcast"},
|
||||
{0x0006, "RAcaseal"},
|
||||
{0x0012, "Force"},
|
||||
{0x000B, "FOmar"},
|
||||
{0x0007, "FOmarl"},
|
||||
{0x0008, "FOnewm"},
|
||||
{0x0009, "FOnewearl"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
switch (choice_id) {
|
||||
case 0x0000:
|
||||
return true;
|
||||
case 0x0010:
|
||||
return target_c->character()->disp.visual.class_flags & 0x20;
|
||||
case 0x0011:
|
||||
return target_c->character()->disp.visual.class_flags & 0x40;
|
||||
case 0x0012:
|
||||
return target_c->character()->disp.visual.class_flags & 0x80;
|
||||
default:
|
||||
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
|
||||
}
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0003,
|
||||
.name = "Platform",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "DC betas"},
|
||||
{0x0002, "DC V1"},
|
||||
{0x0003, "DC V2 / PC"},
|
||||
{0x0004, "GC / Xbox Episodes 1&2"},
|
||||
{0x0005, "GC Episode 3"},
|
||||
{0x0006, "BB"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
switch (target_c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return (choice_id == 0x0001);
|
||||
case Version::DC_V1:
|
||||
return (choice_id == 0x0002);
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return (choice_id == 0x0003);
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
return (choice_id == 0x0004);
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return (choice_id == 0x0005);
|
||||
case Version::BB_V4:
|
||||
return (choice_id == 0x0006);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0204,
|
||||
.name = "Game mode",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "Normal"},
|
||||
{0x0002, "Hard"},
|
||||
{0x0003, "Very Hard"},
|
||||
{0x0004, "Ultimate"},
|
||||
{0x0005, "Battle"},
|
||||
{0x0006, "Challenge"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
|
||||
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
||||
},
|
||||
},
|
||||
});
|
||||
#include "ChoiceSearch.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0001,
|
||||
.name = "Level",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "Own level +/- 5"},
|
||||
{0x0002, "Level 1-10"},
|
||||
{0x0003, "Level 11-20"},
|
||||
{0x0004, "Level 21-40"},
|
||||
{0x0005, "Level 41-60"},
|
||||
{0x0006, "Level 61-80"},
|
||||
{0x0007, "Level 81-100"},
|
||||
{0x0008, "Level 101-120"},
|
||||
{0x0009, "Level 121-160"},
|
||||
{0x000A, "Level 161-200"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
uint32_t target_level = target_c->character()->disp.stats.level + 1;
|
||||
switch (choice_id) {
|
||||
case 0x0001:
|
||||
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
|
||||
case 0x0002:
|
||||
return (target_level <= 10);
|
||||
case 0x0003:
|
||||
return (target_level > 10) && (target_level <= 20);
|
||||
case 0x0004:
|
||||
return (target_level > 20) && (target_level <= 40);
|
||||
case 0x0005:
|
||||
return (target_level > 40) && (target_level <= 60);
|
||||
case 0x0006:
|
||||
return (target_level > 60) && (target_level <= 80);
|
||||
case 0x0007:
|
||||
return (target_level > 80) && (target_level <= 100);
|
||||
case 0x0008:
|
||||
return (target_level > 100) && (target_level <= 120);
|
||||
case 0x0009:
|
||||
return (target_level > 120) && (target_level <= 160);
|
||||
case 0x000A:
|
||||
return (target_level > 160) && (target_level <= 200);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0002,
|
||||
.name = "Class",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0010, "Hunter"},
|
||||
{0x0001, "HUmar"},
|
||||
{0x0002, "HUnewearl"},
|
||||
{0x0003, "HUcast"},
|
||||
{0x000A, "HUcaseal"},
|
||||
{0x0011, "Ranger"},
|
||||
{0x0004, "RAmar"},
|
||||
{0x000C, "RAmarl"},
|
||||
{0x0005, "RAcast"},
|
||||
{0x0006, "RAcaseal"},
|
||||
{0x0012, "Force"},
|
||||
{0x000B, "FOmar"},
|
||||
{0x0007, "FOmarl"},
|
||||
{0x0008, "FOnewm"},
|
||||
{0x0009, "FOnewearl"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
switch (choice_id) {
|
||||
case 0x0000:
|
||||
return true;
|
||||
case 0x0010:
|
||||
return target_c->character()->disp.visual.class_flags & 0x20;
|
||||
case 0x0011:
|
||||
return target_c->character()->disp.visual.class_flags & 0x40;
|
||||
case 0x0012:
|
||||
return target_c->character()->disp.visual.class_flags & 0x80;
|
||||
default:
|
||||
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
|
||||
}
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0003,
|
||||
.name = "Platform",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "DC betas"},
|
||||
{0x0002, "DC V1"},
|
||||
{0x0003, "DC V2 / PC"},
|
||||
{0x0004, "GC / Xbox Episodes 1&2"},
|
||||
{0x0005, "GC Episode 3"},
|
||||
{0x0006, "BB"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
switch (target_c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return (choice_id == 0x0001);
|
||||
case Version::DC_V1:
|
||||
return (choice_id == 0x0002);
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return (choice_id == 0x0003);
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
return (choice_id == 0x0004);
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return (choice_id == 0x0005);
|
||||
case Version::BB_V4:
|
||||
return (choice_id == 0x0006);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0204,
|
||||
.name = "Game mode",
|
||||
.choices = {
|
||||
{0x0000, "Any"},
|
||||
{0x0001, "Normal"},
|
||||
{0x0002, "Hard"},
|
||||
{0x0003, "Very Hard"},
|
||||
{0x0004, "Ultimate"},
|
||||
{0x0005, "Battle"},
|
||||
{0x0006, "Challenge"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
|
||||
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+64
-64
@@ -1,64 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class Client;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct ChoiceSearchConfigT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
||||
struct Entry {
|
||||
U16T parent_choice_id = 0;
|
||||
U16T choice_id = 0;
|
||||
} __packed_ws__(Entry, 4);
|
||||
parray<Entry, 5> entries;
|
||||
|
||||
int32_t get_setting(uint16_t parent_choice_id) const {
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
if (this->entries[z].parent_choice_id == parent_choice_id) {
|
||||
return this->entries[z].choice_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
operator ChoiceSearchConfigT<!IsBigEndian>() const {
|
||||
ChoiceSearchConfigT<!IsBigEndian> ret;
|
||||
ret.disabled = this->disabled.load();
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
auto& ret_e = ret.entries[z];
|
||||
const auto& this_e = this->entries[z];
|
||||
ret_e.parent_choice_id = this_e.parent_choice_id.load();
|
||||
ret_e.choice_id = this_e.choice_id.load();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
|
||||
using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
|
||||
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
|
||||
check_struct_size(ChoiceSearchConfig, 0x18);
|
||||
check_struct_size(ChoiceSearchConfigBE, 0x18);
|
||||
|
||||
struct ChoiceSearchCategory {
|
||||
struct Choice {
|
||||
uint16_t id;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
uint16_t id;
|
||||
const char* name;
|
||||
std::vector<Choice> choices;
|
||||
std::function<bool(std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id)> client_matches;
|
||||
};
|
||||
|
||||
extern const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES;
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class Client;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct ChoiceSearchConfigT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
||||
struct Entry {
|
||||
U16T parent_choice_id = 0;
|
||||
U16T choice_id = 0;
|
||||
} __packed_ws__(Entry, 4);
|
||||
parray<Entry, 5> entries;
|
||||
|
||||
int32_t get_setting(uint16_t parent_choice_id) const {
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
if (this->entries[z].parent_choice_id == parent_choice_id) {
|
||||
return this->entries[z].choice_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
operator ChoiceSearchConfigT<!IsBigEndian>() const {
|
||||
ChoiceSearchConfigT<!IsBigEndian> ret;
|
||||
ret.disabled = this->disabled.load();
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
auto& ret_e = ret.entries[z];
|
||||
const auto& this_e = this->entries[z];
|
||||
ret_e.parent_choice_id = this_e.parent_choice_id.load();
|
||||
ret_e.choice_id = this_e.choice_id.load();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
|
||||
using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
|
||||
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
|
||||
check_struct_size(ChoiceSearchConfig, 0x18);
|
||||
check_struct_size(ChoiceSearchConfigBE, 0x18);
|
||||
|
||||
struct ChoiceSearchCategory {
|
||||
struct Choice {
|
||||
uint16_t id;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
uint16_t id;
|
||||
const char* name;
|
||||
std::vector<Choice> choices;
|
||||
std::function<bool(std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id)> client_matches;
|
||||
};
|
||||
|
||||
extern const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES;
|
||||
|
||||
+1120
-1120
File diff suppressed because it is too large
Load Diff
+412
-412
@@ -1,412 +1,412 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Channel.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "Quest.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "TeamIndex.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
class Server;
|
||||
struct Lobby;
|
||||
class Parsed6x70Data;
|
||||
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
enum class Flag : uint64_t {
|
||||
// clang-format off
|
||||
|
||||
// This mask specifies which flags are sent to the client
|
||||
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
|
||||
// in the high bits) but that would require re-recording or manually
|
||||
// rewriting all the tests
|
||||
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
|
||||
|
||||
// Version-related flags
|
||||
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
|
||||
NO_D6_AFTER_LOBBY = 0x0000000000000100,
|
||||
NO_D6 = 0x0000000000000200,
|
||||
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
|
||||
|
||||
// Flags describing the behavior for send_function_call
|
||||
NO_SEND_FUNCTION_CALL = 0x0000000000001000,
|
||||
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
|
||||
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
|
||||
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
|
||||
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
|
||||
|
||||
// State flags
|
||||
LOADING = 0x0000000000100000, // Server-side only
|
||||
LOADING_QUEST = 0x0000000000200000, // Server-side only
|
||||
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only
|
||||
LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only
|
||||
IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only
|
||||
AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only
|
||||
SAVE_ENABLED = 0x0000000004000000,
|
||||
HAS_EP3_CARD_DEFS = 0x0000000008000000,
|
||||
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
|
||||
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
|
||||
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
|
||||
HAS_AUTO_PATCHES = 0x0000004000000000,
|
||||
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
|
||||
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
||||
|
||||
// Cheat mode and option flags
|
||||
INFINITE_HP_ENABLED = 0x0000000200000000,
|
||||
INFINITE_TP_ENABLED = 0x0000000400000000,
|
||||
DEBUG_ENABLED = 0x0000000800000000,
|
||||
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
|
||||
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
|
||||
|
||||
// Proxy option flags
|
||||
PROXY_SAVE_FILES = 0x0000001000000000,
|
||||
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
|
||||
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
|
||||
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
|
||||
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
|
||||
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
|
||||
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
|
||||
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
|
||||
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
|
||||
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
|
||||
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
|
||||
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class ItemDropNotificationMode {
|
||||
NOTHING = 0,
|
||||
RARES_ONLY = 1,
|
||||
ALL_ITEMS = 2,
|
||||
ALL_ITEMS_INCLUDING_MESETA = 3,
|
||||
};
|
||||
|
||||
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||
|
||||
struct Config {
|
||||
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
|
||||
uint32_t specific_version = 0;
|
||||
int32_t override_random_seed = 0;
|
||||
uint8_t override_section_id = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_event = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||
uint32_t proxy_destination_address = 0;
|
||||
uint16_t proxy_destination_port = 0;
|
||||
|
||||
Config() = default;
|
||||
|
||||
bool operator==(const Config& other) const = default;
|
||||
bool operator!=(const Config& other) const = default;
|
||||
|
||||
bool should_update_vs(const Config& other) const;
|
||||
|
||||
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
|
||||
return !!(enabled_flags & static_cast<uint64_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return this->check_flag(this->enabled_flags, flag);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint64_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint64_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint64_t>(flag);
|
||||
}
|
||||
|
||||
void set_flags_for_version(Version version, int64_t sub_version);
|
||||
|
||||
ItemDropNotificationMode get_drop_notification_mode() const;
|
||||
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
|
||||
|
||||
template <size_t Bytes>
|
||||
void parse_from(const parray<uint8_t, Bytes>& data) {
|
||||
StringReader r(data.data(), data.size());
|
||||
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
|
||||
throw std::invalid_argument("config signature is incorrect");
|
||||
}
|
||||
this->specific_version = r.get_u32l();
|
||||
this->enabled_flags = r.get_u64l();
|
||||
this->override_random_seed = r.get_u32l();
|
||||
this->proxy_destination_address = r.get_u32b();
|
||||
this->proxy_destination_port = r.get_u16l();
|
||||
this->override_section_id = r.get_u8();
|
||||
this->override_lobby_event = r.get_u8();
|
||||
this->override_lobby_number = r.get_u8();
|
||||
}
|
||||
|
||||
template <size_t Bytes>
|
||||
void serialize_into(parray<uint8_t, Bytes>& data) const {
|
||||
StringWriter w;
|
||||
w.put_u32l(CLIENT_CONFIG_MAGIC);
|
||||
w.put_u32l(this->specific_version);
|
||||
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
|
||||
w.put_u32l(this->override_random_seed);
|
||||
w.put_u32b(this->proxy_destination_address);
|
||||
w.put_u16l(this->proxy_destination_port);
|
||||
w.put_u8(this->override_section_id);
|
||||
w.put_u8(this->override_lobby_event);
|
||||
w.put_u8(this->override_lobby_number);
|
||||
|
||||
const auto& s = w.str();
|
||||
for (size_t z = 0; z < s.size(); z++) {
|
||||
data[z] = s[z];
|
||||
}
|
||||
data.clear_after(s.size(), 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
std::weak_ptr<Server> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
// Network
|
||||
Channel channel;
|
||||
struct sockaddr_storage next_connection_addr;
|
||||
ServerBehavior server_behavior;
|
||||
bool should_disconnect;
|
||||
bool should_send_to_lobby_server;
|
||||
bool should_send_to_proxy_server;
|
||||
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
|
||||
std::shared_ptr<XBNetworkLocation> xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
uint8_t bb_connection_phase;
|
||||
uint64_t ping_start_time;
|
||||
|
||||
// Lobby/positioning
|
||||
Config config;
|
||||
Config synced_config;
|
||||
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
|
||||
int32_t sub_version;
|
||||
float x;
|
||||
float z;
|
||||
uint32_t floor;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
uint8_t lobby_arrow_color;
|
||||
int64_t preferred_lobby_id; // <0 = no preference
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
int16_t card_battle_table_number;
|
||||
uint16_t card_battle_table_seat_number;
|
||||
uint16_t card_battle_table_seat_state;
|
||||
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
|
||||
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
|
||||
std::shared_ptr<const Menu> last_menu_sent;
|
||||
uint32_t last_game_info_requested;
|
||||
struct JoinCommand {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
std::string data;
|
||||
};
|
||||
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
|
||||
|
||||
// Character / game data
|
||||
struct PendingItemTrade {
|
||||
uint8_t other_client_id;
|
||||
bool confirmed; // true if client has sent a D2 command
|
||||
std::vector<ItemData> items;
|
||||
};
|
||||
struct PendingCardTrade {
|
||||
uint8_t other_client_id;
|
||||
bool confirmed; // true if client has sent an EE D2 command
|
||||
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
|
||||
};
|
||||
bool should_update_play_time;
|
||||
std::unordered_set<uint32_t> blocked_senders;
|
||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
||||
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
||||
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||
uint32_t telepipe_lobby_id;
|
||||
G_SetTelepipeState_6x68 telepipe_state;
|
||||
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
|
||||
int8_t bb_character_index;
|
||||
ItemData bb_identify_result;
|
||||
std::array<std::vector<ItemData>, 3> bb_shop_contents;
|
||||
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
bool can_chat;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const Account> dest_account;
|
||||
ssize_t character_index = -1;
|
||||
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
|
||||
};
|
||||
std::unique_ptr<PendingCharacterExport> pending_character_export;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
|
||||
// File loading state
|
||||
uint32_t dol_base_addr;
|
||||
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
ServerBehavior server_behavior);
|
||||
~Client();
|
||||
|
||||
void update_channel_name();
|
||||
|
||||
void reschedule_save_game_data_event();
|
||||
void reschedule_ping_and_timeout_events();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->channel.language;
|
||||
}
|
||||
|
||||
void convert_account_to_temporary_if_nte();
|
||||
|
||||
void sync_config();
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<Lobby> require_lobby() const;
|
||||
|
||||
std::shared_ptr<const TeamIndex::Team> team() const;
|
||||
|
||||
bool evaluate_quest_availability_expression(
|
||||
std::shared_ptr<const IntegralExpression> expr,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_see_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_play_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
|
||||
bool can_use_chat_commands() const;
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
void save_game_data();
|
||||
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
|
||||
void send_ping();
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
|
||||
void suspend_timeouts();
|
||||
|
||||
const std::string& get_bb_username() const;
|
||||
void set_bb_username(const std::string& bb_username);
|
||||
|
||||
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
||||
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
||||
inline void delete_overlay() {
|
||||
this->overlay_character_data.reset();
|
||||
}
|
||||
inline bool has_overlay() const {
|
||||
return this->overlay_character_data.get() != nullptr;
|
||||
}
|
||||
|
||||
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
|
||||
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
|
||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const;
|
||||
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
|
||||
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
|
||||
|
||||
void create_character_file(
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
std::shared_ptr<const LevelTable> level_table);
|
||||
|
||||
std::string system_filename() const;
|
||||
static std::string character_filename(const std::string& bb_username, int8_t index);
|
||||
static std::string backup_character_filename(uint32_t account_id, size_t index);
|
||||
std::string character_filename(int8_t index = -1) const;
|
||||
std::string guild_card_filename() const;
|
||||
std::string shared_bank_filename() const;
|
||||
|
||||
std::string legacy_player_filename() const;
|
||||
std::string legacy_account_filename() const;
|
||||
|
||||
void save_all();
|
||||
void save_system_file() const;
|
||||
static void save_character_file(
|
||||
const std::string& filename,
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> sys,
|
||||
std::shared_ptr<const PSOBBCharacterFile> character);
|
||||
// Note: This function is not const because it updates the player's play time.
|
||||
void save_character_file();
|
||||
void save_guild_card_file() const;
|
||||
|
||||
void load_backup_character(uint32_t account_id, size_t index);
|
||||
void save_and_unload_character();
|
||||
|
||||
PlayerBank200& current_bank();
|
||||
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
|
||||
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
||||
void use_character_bank(int8_t bb_character_index);
|
||||
void use_default_bank();
|
||||
|
||||
void print_inventory(FILE* stream) const;
|
||||
void print_bank(FILE* stream) const;
|
||||
|
||||
private:
|
||||
// The overlay character data is used in battle and challenge modes, when
|
||||
// character data is temporarily replaced in-game. In other play modes and in
|
||||
// lobbies, overlay_character_data is null.
|
||||
std::shared_ptr<PSOBBBaseSystemFile> system_data;
|
||||
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
|
||||
std::shared_ptr<PSOBBCharacterFile> character_data;
|
||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
|
||||
std::shared_ptr<PlayerBank200> external_bank;
|
||||
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
|
||||
int8_t external_bank_character_index;
|
||||
uint64_t last_play_time_update;
|
||||
|
||||
void save_and_clear_external_bank();
|
||||
|
||||
void load_all_files();
|
||||
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Channel.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "Quest.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "TeamIndex.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
class Server;
|
||||
struct Lobby;
|
||||
class Parsed6x70Data;
|
||||
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
enum class Flag : uint64_t {
|
||||
// clang-format off
|
||||
|
||||
// This mask specifies which flags are sent to the client
|
||||
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
|
||||
// in the high bits) but that would require re-recording or manually
|
||||
// rewriting all the tests
|
||||
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
|
||||
|
||||
// Version-related flags
|
||||
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
|
||||
NO_D6_AFTER_LOBBY = 0x0000000000000100,
|
||||
NO_D6 = 0x0000000000000200,
|
||||
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
|
||||
|
||||
// Flags describing the behavior for send_function_call
|
||||
NO_SEND_FUNCTION_CALL = 0x0000000000001000,
|
||||
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
|
||||
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
|
||||
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
|
||||
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
|
||||
|
||||
// State flags
|
||||
LOADING = 0x0000000000100000, // Server-side only
|
||||
LOADING_QUEST = 0x0000000000200000, // Server-side only
|
||||
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, // Server-side only
|
||||
LOADING_TOURNAMENT = 0x0000000000800000, // Server-side only
|
||||
IN_INFORMATION_MENU = 0x0000000001000000, // Server-side only
|
||||
AT_WELCOME_MESSAGE = 0x0000000002000000, // Server-side only
|
||||
SAVE_ENABLED = 0x0000000004000000,
|
||||
HAS_EP3_CARD_DEFS = 0x0000000008000000,
|
||||
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
|
||||
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
|
||||
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
|
||||
HAS_AUTO_PATCHES = 0x0000004000000000,
|
||||
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
|
||||
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
||||
|
||||
// Cheat mode and option flags
|
||||
INFINITE_HP_ENABLED = 0x0000000200000000,
|
||||
INFINITE_TP_ENABLED = 0x0000000400000000,
|
||||
DEBUG_ENABLED = 0x0000000800000000,
|
||||
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
|
||||
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
|
||||
|
||||
// Proxy option flags
|
||||
PROXY_SAVE_FILES = 0x0000001000000000,
|
||||
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
|
||||
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
|
||||
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
|
||||
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
|
||||
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
|
||||
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
|
||||
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
|
||||
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
|
||||
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
|
||||
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
|
||||
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class ItemDropNotificationMode {
|
||||
NOTHING = 0,
|
||||
RARES_ONLY = 1,
|
||||
ALL_ITEMS = 2,
|
||||
ALL_ITEMS_INCLUDING_MESETA = 3,
|
||||
};
|
||||
|
||||
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||
|
||||
struct Config {
|
||||
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
|
||||
uint32_t specific_version = 0;
|
||||
int32_t override_random_seed = 0;
|
||||
uint8_t override_section_id = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_event = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||
uint32_t proxy_destination_address = 0;
|
||||
uint16_t proxy_destination_port = 0;
|
||||
|
||||
Config() = default;
|
||||
|
||||
bool operator==(const Config& other) const = default;
|
||||
bool operator!=(const Config& other) const = default;
|
||||
|
||||
bool should_update_vs(const Config& other) const;
|
||||
|
||||
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
|
||||
return !!(enabled_flags & static_cast<uint64_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return this->check_flag(this->enabled_flags, flag);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint64_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint64_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint64_t>(flag);
|
||||
}
|
||||
|
||||
void set_flags_for_version(Version version, int64_t sub_version);
|
||||
|
||||
ItemDropNotificationMode get_drop_notification_mode() const;
|
||||
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
|
||||
|
||||
template <size_t Bytes>
|
||||
void parse_from(const parray<uint8_t, Bytes>& data) {
|
||||
StringReader r(data.data(), data.size());
|
||||
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
|
||||
throw std::invalid_argument("config signature is incorrect");
|
||||
}
|
||||
this->specific_version = r.get_u32l();
|
||||
this->enabled_flags = r.get_u64l();
|
||||
this->override_random_seed = r.get_u32l();
|
||||
this->proxy_destination_address = r.get_u32b();
|
||||
this->proxy_destination_port = r.get_u16l();
|
||||
this->override_section_id = r.get_u8();
|
||||
this->override_lobby_event = r.get_u8();
|
||||
this->override_lobby_number = r.get_u8();
|
||||
}
|
||||
|
||||
template <size_t Bytes>
|
||||
void serialize_into(parray<uint8_t, Bytes>& data) const {
|
||||
StringWriter w;
|
||||
w.put_u32l(CLIENT_CONFIG_MAGIC);
|
||||
w.put_u32l(this->specific_version);
|
||||
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
|
||||
w.put_u32l(this->override_random_seed);
|
||||
w.put_u32b(this->proxy_destination_address);
|
||||
w.put_u16l(this->proxy_destination_port);
|
||||
w.put_u8(this->override_section_id);
|
||||
w.put_u8(this->override_lobby_event);
|
||||
w.put_u8(this->override_lobby_number);
|
||||
|
||||
const auto& s = w.str();
|
||||
for (size_t z = 0; z < s.size(); z++) {
|
||||
data[z] = s[z];
|
||||
}
|
||||
data.clear_after(s.size(), 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
std::weak_ptr<Server> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
// Network
|
||||
Channel channel;
|
||||
struct sockaddr_storage next_connection_addr;
|
||||
ServerBehavior server_behavior;
|
||||
bool should_disconnect;
|
||||
bool should_send_to_lobby_server;
|
||||
bool should_send_to_proxy_server;
|
||||
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
|
||||
std::shared_ptr<XBNetworkLocation> xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
uint8_t bb_connection_phase;
|
||||
uint64_t ping_start_time;
|
||||
|
||||
// Lobby/positioning
|
||||
Config config;
|
||||
Config synced_config;
|
||||
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
|
||||
int32_t sub_version;
|
||||
float x;
|
||||
float z;
|
||||
uint32_t floor;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
uint8_t lobby_arrow_color;
|
||||
int64_t preferred_lobby_id; // <0 = no preference
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
int16_t card_battle_table_number;
|
||||
uint16_t card_battle_table_seat_number;
|
||||
uint16_t card_battle_table_seat_state;
|
||||
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
|
||||
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
|
||||
std::shared_ptr<const Menu> last_menu_sent;
|
||||
uint32_t last_game_info_requested;
|
||||
struct JoinCommand {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
std::string data;
|
||||
};
|
||||
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
|
||||
|
||||
// Character / game data
|
||||
struct PendingItemTrade {
|
||||
uint8_t other_client_id;
|
||||
bool confirmed; // true if client has sent a D2 command
|
||||
std::vector<ItemData> items;
|
||||
};
|
||||
struct PendingCardTrade {
|
||||
uint8_t other_client_id;
|
||||
bool confirmed; // true if client has sent an EE D2 command
|
||||
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
|
||||
};
|
||||
bool should_update_play_time;
|
||||
std::unordered_set<uint32_t> blocked_senders;
|
||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
||||
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
||||
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||
uint32_t telepipe_lobby_id;
|
||||
G_SetTelepipeState_6x68 telepipe_state;
|
||||
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
|
||||
int8_t bb_character_index;
|
||||
ItemData bb_identify_result;
|
||||
std::array<std::vector<ItemData>, 3> bb_shop_contents;
|
||||
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
bool can_chat;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const Account> dest_account;
|
||||
ssize_t character_index = -1;
|
||||
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
|
||||
};
|
||||
std::unique_ptr<PendingCharacterExport> pending_character_export;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
|
||||
// File loading state
|
||||
uint32_t dol_base_addr;
|
||||
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
ServerBehavior server_behavior);
|
||||
~Client();
|
||||
|
||||
void update_channel_name();
|
||||
|
||||
void reschedule_save_game_data_event();
|
||||
void reschedule_ping_and_timeout_events();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->channel.language;
|
||||
}
|
||||
|
||||
void convert_account_to_temporary_if_nte();
|
||||
|
||||
void sync_config();
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<Lobby> require_lobby() const;
|
||||
|
||||
std::shared_ptr<const TeamIndex::Team> team() const;
|
||||
|
||||
bool evaluate_quest_availability_expression(
|
||||
std::shared_ptr<const IntegralExpression> expr,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_see_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
bool can_play_quest(
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const;
|
||||
|
||||
bool can_use_chat_commands() const;
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
void save_game_data();
|
||||
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
|
||||
void send_ping();
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
|
||||
void suspend_timeouts();
|
||||
|
||||
const std::string& get_bb_username() const;
|
||||
void set_bb_username(const std::string& bb_username);
|
||||
|
||||
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
||||
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
||||
inline void delete_overlay() {
|
||||
this->overlay_character_data.reset();
|
||||
}
|
||||
inline bool has_overlay() const {
|
||||
return this->overlay_character_data.get() != nullptr;
|
||||
}
|
||||
|
||||
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
|
||||
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
|
||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const;
|
||||
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
|
||||
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
|
||||
|
||||
void create_character_file(
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
std::shared_ptr<const LevelTable> level_table);
|
||||
|
||||
std::string system_filename() const;
|
||||
static std::string character_filename(const std::string& bb_username, int8_t index);
|
||||
static std::string backup_character_filename(uint32_t account_id, size_t index);
|
||||
std::string character_filename(int8_t index = -1) const;
|
||||
std::string guild_card_filename() const;
|
||||
std::string shared_bank_filename() const;
|
||||
|
||||
std::string legacy_player_filename() const;
|
||||
std::string legacy_account_filename() const;
|
||||
|
||||
void save_all();
|
||||
void save_system_file() const;
|
||||
static void save_character_file(
|
||||
const std::string& filename,
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> sys,
|
||||
std::shared_ptr<const PSOBBCharacterFile> character);
|
||||
// Note: This function is not const because it updates the player's play time.
|
||||
void save_character_file();
|
||||
void save_guild_card_file() const;
|
||||
|
||||
void load_backup_character(uint32_t account_id, size_t index);
|
||||
void save_and_unload_character();
|
||||
|
||||
PlayerBank200& current_bank();
|
||||
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
|
||||
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
||||
void use_character_bank(int8_t bb_character_index);
|
||||
void use_default_bank();
|
||||
|
||||
void print_inventory(FILE* stream) const;
|
||||
void print_bank(FILE* stream) const;
|
||||
|
||||
private:
|
||||
// The overlay character data is used in battle and challenge modes, when
|
||||
// character data is temporarily replaced in-game. In other play modes and in
|
||||
// lobbies, overlay_character_data is null.
|
||||
std::shared_ptr<PSOBBBaseSystemFile> system_data;
|
||||
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
|
||||
std::shared_ptr<PSOBBCharacterFile> character_data;
|
||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
|
||||
std::shared_ptr<PlayerBank200> external_bank;
|
||||
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
|
||||
int8_t external_bank_character_index;
|
||||
uint64_t last_play_time_update;
|
||||
|
||||
void save_and_clear_external_bank();
|
||||
|
||||
void load_all_files();
|
||||
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
|
||||
};
|
||||
|
||||
+7233
-7233
File diff suppressed because it is too large
Load Diff
+1357
-1357
File diff suppressed because it is too large
Load Diff
+226
-226
@@ -1,226 +1,226 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <phosg/Tools.hh>
|
||||
#include <string>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
enum class CompressPhase {
|
||||
INDEX = 0,
|
||||
CONSTRUCT_PATHS,
|
||||
BACKTRACE_OPTIMAL_PATH,
|
||||
GENERATE_RESULT,
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<CompressPhase>(CompressPhase v);
|
||||
|
||||
typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// PRS compression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Use this class if you need to compress from multiple input buffers, or need
|
||||
// to compress multiple chunks and don't want to copy their contents
|
||||
// unnecessarily. (For most common use cases, use prs_compress, below, instead.)
|
||||
// To use this class, instantiate it, then call .add() one or more times, then
|
||||
// call .close() and use the returned string as the compressed result.
|
||||
class PRSCompressor {
|
||||
public:
|
||||
// compression_level specifies how aggressively to search for alternate paths:
|
||||
// -1: Don't perform any compression at all, but produce output that can be
|
||||
// understood by prs_decompress. The output will be about 9/8 the size
|
||||
// of the input.
|
||||
// 0: Greedily search for the longest backreference at every point. Don't
|
||||
// consider any alternate paths. Generally offers a good balance between
|
||||
// speed and output size.
|
||||
// 1: Consider two paths at each point when a backreference is found: using
|
||||
// the backreference or ignoring it.
|
||||
// 2+: Consider further chains of paths at each point. Using values 2 or
|
||||
// greater for compression_level generally yields diminishing returns.
|
||||
explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr);
|
||||
~PRSCompressor() = default;
|
||||
|
||||
// Adds more input data to be compressed, which logically comes after all
|
||||
// previous data provided via add() calls. Cannot be called after close() is
|
||||
// called.
|
||||
void add(const void* data, size_t size);
|
||||
void add(const std::string& data);
|
||||
|
||||
// Ends compression and returns the complete compressed result. It's OK to
|
||||
// std::move() from the returned string reference.
|
||||
std::string& close();
|
||||
|
||||
// Returns the total number of bytes passed to add() calls so far.
|
||||
inline size_t input_size() const {
|
||||
return this->input_bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
template <size_t Size>
|
||||
struct WrappedLog {
|
||||
parray<uint8_t, Size> data;
|
||||
|
||||
WrappedLog() : data(0) {}
|
||||
~WrappedLog() = default;
|
||||
|
||||
inline uint8_t at(size_t offset) const {
|
||||
return this->data[offset % this->data.size()];
|
||||
}
|
||||
inline uint8_t& at(size_t offset) {
|
||||
return this->data[offset % this->data.size()];
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t Size>
|
||||
struct IndexedLog : WrappedLog<Size> {
|
||||
size_t offset;
|
||||
size_t size;
|
||||
std::array<std::deque<size_t>, 0x100> index;
|
||||
|
||||
IndexedLog()
|
||||
: WrappedLog<Size>(),
|
||||
offset(0),
|
||||
size(0) {}
|
||||
~IndexedLog() = default;
|
||||
|
||||
inline size_t end_offset() const {
|
||||
return this->offset + this->size;
|
||||
}
|
||||
|
||||
void push_back(uint8_t v) {
|
||||
if (this->size == Size) {
|
||||
this->pop_front();
|
||||
}
|
||||
size_t write_offset = this->offset + this->size;
|
||||
this->at(write_offset) = v;
|
||||
this->index[v].push_back(write_offset);
|
||||
this->size++;
|
||||
}
|
||||
uint8_t pop_back() {
|
||||
if (!this->size) {
|
||||
throw std::logic_error("pop_back called on empty IndexedLog");
|
||||
}
|
||||
this->size--;
|
||||
size_t offset = this->offset + this->size;
|
||||
uint8_t v = this->at(offset);
|
||||
this->index[v].pop_back();
|
||||
return v;
|
||||
}
|
||||
uint8_t pop_front() {
|
||||
uint8_t v = this->at(this->offset);
|
||||
this->index[v].pop_front();
|
||||
this->offset++;
|
||||
this->size--;
|
||||
return v;
|
||||
}
|
||||
const std::deque<size_t>& find(uint8_t v) {
|
||||
return this->index[v];
|
||||
}
|
||||
};
|
||||
|
||||
void add_byte(uint8_t v);
|
||||
void advance();
|
||||
void move_forward_data_to_reverse_log(size_t size);
|
||||
void advance_literal();
|
||||
void advance_short_copy(ssize_t offset, size_t size);
|
||||
void advance_long_copy(ssize_t offset, size_t size);
|
||||
void advance_extended_copy(ssize_t offset, size_t size);
|
||||
void write_control(bool z);
|
||||
void flush_control();
|
||||
|
||||
ssize_t compression_level;
|
||||
ProgressCallback progress_fn;
|
||||
bool closed;
|
||||
|
||||
size_t control_byte_offset;
|
||||
uint16_t pending_control_bits;
|
||||
|
||||
size_t input_bytes;
|
||||
WrappedLog<0x101> forward_log;
|
||||
IndexedLog<0x2000> reverse_log;
|
||||
|
||||
StringWriter output;
|
||||
};
|
||||
|
||||
// These functions use PRSCompressor to compress a buffer of data. This is
|
||||
// essentially a shortcut for constructing a PRSCompressor, calling .add() on
|
||||
// it once, then calling .close().
|
||||
std::string prs_compress(
|
||||
const void* vdata,
|
||||
size_t size,
|
||||
ssize_t compression_level = 0,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress(
|
||||
const std::string& data,
|
||||
ssize_t compression_level = 0,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// A faster form of prs_compress that doesn't have a tunable compression level.
|
||||
std::string prs_compress_indexed(
|
||||
const void* vdata,
|
||||
size_t size,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress_indexed(
|
||||
const std::string& data,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Compresses data using PRS to the smallest possible output size. This function
|
||||
// is slow, but produces results significantly smaller than even Sega's original
|
||||
// compressor.
|
||||
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Compresses data using PRS to the LARGEST possible output size. There is no
|
||||
// practical use for this function except for amusement.
|
||||
std::string prs_compress_pessimal(const void* vdata, size_t size);
|
||||
|
||||
// Decompresses PRS-compressed data.
|
||||
struct PRSDecompressResult {
|
||||
std::string data;
|
||||
size_t input_bytes_used;
|
||||
};
|
||||
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
std::string prs_decompress(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
|
||||
// Returns the decompressed size of PRS-compressed data, without actually
|
||||
// decompressing it.
|
||||
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
|
||||
// Prints the command stream from a PRS-compressed buffer.
|
||||
void prs_disassemble(FILE* stream, const void* data, size_t size);
|
||||
void prs_disassemble(FILE* stream, const std::string& data);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// BC0 compression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Compresses data using the BC0 algorithm. Like with PRS, the optimal variant
|
||||
// is slow, but produces the smallest possible output.
|
||||
std::string bc0_compress_optimal(
|
||||
const void* in_data_v,
|
||||
size_t in_size,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn = nullptr);
|
||||
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Encodes data in a BC0-compatible format without compression (similar to using
|
||||
// compression_level=-1 with prs_compress).
|
||||
std::string bc0_encode(const void* in_data_v, size_t in_size);
|
||||
|
||||
// Decompresses BC0-compressed data.
|
||||
std::string bc0_decompress(const std::string& data);
|
||||
std::string bc0_decompress(const void* data, size_t size);
|
||||
|
||||
// Prints the command stream from a BC0-compressed buffer.
|
||||
void bc0_disassemble(FILE* stream, const std::string& data);
|
||||
void bc0_disassemble(FILE* stream, const void* data, size_t size);
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <phosg/Tools.hh>
|
||||
#include <string>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
enum class CompressPhase {
|
||||
INDEX = 0,
|
||||
CONSTRUCT_PATHS,
|
||||
BACKTRACE_OPTIMAL_PATH,
|
||||
GENERATE_RESULT,
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<CompressPhase>(CompressPhase v);
|
||||
|
||||
typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// PRS compression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Use this class if you need to compress from multiple input buffers, or need
|
||||
// to compress multiple chunks and don't want to copy their contents
|
||||
// unnecessarily. (For most common use cases, use prs_compress, below, instead.)
|
||||
// To use this class, instantiate it, then call .add() one or more times, then
|
||||
// call .close() and use the returned string as the compressed result.
|
||||
class PRSCompressor {
|
||||
public:
|
||||
// compression_level specifies how aggressively to search for alternate paths:
|
||||
// -1: Don't perform any compression at all, but produce output that can be
|
||||
// understood by prs_decompress. The output will be about 9/8 the size
|
||||
// of the input.
|
||||
// 0: Greedily search for the longest backreference at every point. Don't
|
||||
// consider any alternate paths. Generally offers a good balance between
|
||||
// speed and output size.
|
||||
// 1: Consider two paths at each point when a backreference is found: using
|
||||
// the backreference or ignoring it.
|
||||
// 2+: Consider further chains of paths at each point. Using values 2 or
|
||||
// greater for compression_level generally yields diminishing returns.
|
||||
explicit PRSCompressor(ssize_t compression_level = 0, ProgressCallback progress_fn = nullptr);
|
||||
~PRSCompressor() = default;
|
||||
|
||||
// Adds more input data to be compressed, which logically comes after all
|
||||
// previous data provided via add() calls. Cannot be called after close() is
|
||||
// called.
|
||||
void add(const void* data, size_t size);
|
||||
void add(const std::string& data);
|
||||
|
||||
// Ends compression and returns the complete compressed result. It's OK to
|
||||
// std::move() from the returned string reference.
|
||||
std::string& close();
|
||||
|
||||
// Returns the total number of bytes passed to add() calls so far.
|
||||
inline size_t input_size() const {
|
||||
return this->input_bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
template <size_t Size>
|
||||
struct WrappedLog {
|
||||
parray<uint8_t, Size> data;
|
||||
|
||||
WrappedLog() : data(0) {}
|
||||
~WrappedLog() = default;
|
||||
|
||||
inline uint8_t at(size_t offset) const {
|
||||
return this->data[offset % this->data.size()];
|
||||
}
|
||||
inline uint8_t& at(size_t offset) {
|
||||
return this->data[offset % this->data.size()];
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t Size>
|
||||
struct IndexedLog : WrappedLog<Size> {
|
||||
size_t offset;
|
||||
size_t size;
|
||||
std::array<std::deque<size_t>, 0x100> index;
|
||||
|
||||
IndexedLog()
|
||||
: WrappedLog<Size>(),
|
||||
offset(0),
|
||||
size(0) {}
|
||||
~IndexedLog() = default;
|
||||
|
||||
inline size_t end_offset() const {
|
||||
return this->offset + this->size;
|
||||
}
|
||||
|
||||
void push_back(uint8_t v) {
|
||||
if (this->size == Size) {
|
||||
this->pop_front();
|
||||
}
|
||||
size_t write_offset = this->offset + this->size;
|
||||
this->at(write_offset) = v;
|
||||
this->index[v].push_back(write_offset);
|
||||
this->size++;
|
||||
}
|
||||
uint8_t pop_back() {
|
||||
if (!this->size) {
|
||||
throw std::logic_error("pop_back called on empty IndexedLog");
|
||||
}
|
||||
this->size--;
|
||||
size_t offset = this->offset + this->size;
|
||||
uint8_t v = this->at(offset);
|
||||
this->index[v].pop_back();
|
||||
return v;
|
||||
}
|
||||
uint8_t pop_front() {
|
||||
uint8_t v = this->at(this->offset);
|
||||
this->index[v].pop_front();
|
||||
this->offset++;
|
||||
this->size--;
|
||||
return v;
|
||||
}
|
||||
const std::deque<size_t>& find(uint8_t v) {
|
||||
return this->index[v];
|
||||
}
|
||||
};
|
||||
|
||||
void add_byte(uint8_t v);
|
||||
void advance();
|
||||
void move_forward_data_to_reverse_log(size_t size);
|
||||
void advance_literal();
|
||||
void advance_short_copy(ssize_t offset, size_t size);
|
||||
void advance_long_copy(ssize_t offset, size_t size);
|
||||
void advance_extended_copy(ssize_t offset, size_t size);
|
||||
void write_control(bool z);
|
||||
void flush_control();
|
||||
|
||||
ssize_t compression_level;
|
||||
ProgressCallback progress_fn;
|
||||
bool closed;
|
||||
|
||||
size_t control_byte_offset;
|
||||
uint16_t pending_control_bits;
|
||||
|
||||
size_t input_bytes;
|
||||
WrappedLog<0x101> forward_log;
|
||||
IndexedLog<0x2000> reverse_log;
|
||||
|
||||
StringWriter output;
|
||||
};
|
||||
|
||||
// These functions use PRSCompressor to compress a buffer of data. This is
|
||||
// essentially a shortcut for constructing a PRSCompressor, calling .add() on
|
||||
// it once, then calling .close().
|
||||
std::string prs_compress(
|
||||
const void* vdata,
|
||||
size_t size,
|
||||
ssize_t compression_level = 0,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress(
|
||||
const std::string& data,
|
||||
ssize_t compression_level = 0,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// A faster form of prs_compress that doesn't have a tunable compression level.
|
||||
std::string prs_compress_indexed(
|
||||
const void* vdata,
|
||||
size_t size,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress_indexed(
|
||||
const std::string& data,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Compresses data using PRS to the smallest possible output size. This function
|
||||
// is slow, but produces results significantly smaller than even Sega's original
|
||||
// compressor.
|
||||
std::string prs_compress_optimal(const void* vdata, size_t size, ProgressCallback progress_fn = nullptr);
|
||||
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Compresses data using PRS to the LARGEST possible output size. There is no
|
||||
// practical use for this function except for amusement.
|
||||
std::string prs_compress_pessimal(const void* vdata, size_t size);
|
||||
|
||||
// Decompresses PRS-compressed data.
|
||||
struct PRSDecompressResult {
|
||||
std::string data;
|
||||
size_t input_bytes_used;
|
||||
};
|
||||
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
std::string prs_decompress(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
|
||||
// Returns the decompressed size of PRS-compressed data, without actually
|
||||
// decompressing it.
|
||||
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
|
||||
|
||||
// Prints the command stream from a PRS-compressed buffer.
|
||||
void prs_disassemble(FILE* stream, const void* data, size_t size);
|
||||
void prs_disassemble(FILE* stream, const std::string& data);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// BC0 compression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Compresses data using the BC0 algorithm. Like with PRS, the optimal variant
|
||||
// is slow, but produces the smallest possible output.
|
||||
std::string bc0_compress_optimal(
|
||||
const void* in_data_v,
|
||||
size_t in_size,
|
||||
ProgressCallback progress_fn = nullptr);
|
||||
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn = nullptr);
|
||||
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn = nullptr);
|
||||
|
||||
// Encodes data in a BC0-compatible format without compression (similar to using
|
||||
// compression_level=-1 with prs_compress).
|
||||
std::string bc0_encode(const void* in_data_v, size_t in_size);
|
||||
|
||||
// Decompresses BC0-compressed data.
|
||||
std::string bc0_decompress(const std::string& data);
|
||||
std::string bc0_decompress(const void* data, size_t size);
|
||||
|
||||
// Prints the command stream from a BC0-compressed buffer.
|
||||
void bc0_disassemble(FILE* stream, const std::string& data);
|
||||
void bc0_disassemble(FILE* stream, const void* data, size_t size);
|
||||
|
||||
+121
-121
@@ -1,121 +1,121 @@
|
||||
#include "DNSServer.hh"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DNSServer::DNSServer(
|
||||
shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
|
||||
: base(base),
|
||||
local_connect_address(local_connect_address),
|
||||
external_connect_address(external_connect_address),
|
||||
banned_ipv4_ranges(banned_ipv4_ranges) {}
|
||||
|
||||
DNSServer::~DNSServer() {
|
||||
for (const auto& it : this->fd_to_receive_event) {
|
||||
close(it.first);
|
||||
}
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& socket_path) {
|
||||
this->add_socket(::listen(socket_path, 0, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
this->add_socket(::listen(addr, port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(int port) {
|
||||
this->add_socket(::listen("", port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::add_socket(int fd) {
|
||||
unique_ptr<struct event, void (*)(struct event*)> e(
|
||||
event_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
|
||||
event_free);
|
||||
event_add(e.get(), nullptr);
|
||||
this->fd_to_receive_event.emplace(fd, std::move(e));
|
||||
}
|
||||
|
||||
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
|
||||
short events, void* ctx) {
|
||||
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
||||
if (size < 0x0C) {
|
||||
throw invalid_argument("query too small");
|
||||
}
|
||||
|
||||
const char* data = reinterpret_cast<const char*>(vdata);
|
||||
size_t name_len = strlen(&data[12]) + 1;
|
||||
|
||||
be_uint32_t be_resolved_address = resolved_address;
|
||||
|
||||
string response;
|
||||
response.append(data, 2);
|
||||
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
||||
response.append(&data[12], name_len);
|
||||
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
|
||||
response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
|
||||
return response;
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(
|
||||
const string& query, uint32_t resolved_address) {
|
||||
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
||||
}
|
||||
|
||||
void DNSServer::on_receive_message(int fd, short) {
|
||||
for (;;) {
|
||||
struct sockaddr_storage remote;
|
||||
socklen_t remote_size = sizeof(sockaddr_in);
|
||||
memset(&remote, 0, remote_size);
|
||||
|
||||
string input(2048, 0);
|
||||
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
|
||||
0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
|
||||
|
||||
if (bytes < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
dns_server_log.error("input error %d", errno);
|
||||
throw runtime_error("cannot read from udp socket");
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (bytes == 0) {
|
||||
break;
|
||||
|
||||
} else if (bytes < 0x0C) {
|
||||
dns_server_log.warning("input query too small");
|
||||
print_data(stderr, input.data(), bytes);
|
||||
|
||||
} else if (!this->banned_ipv4_ranges->check(remote)) {
|
||||
input.resize(bytes);
|
||||
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
|
||||
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
|
||||
uint32_t connect_address = is_local_address(remote_address)
|
||||
? this->local_connect_address
|
||||
: this->external_connect_address;
|
||||
string response = this->response_for_query(input, connect_address);
|
||||
sendto(fd, response.data(), response.size(), 0,
|
||||
reinterpret_cast<const sockaddr*>(&remote), remote_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "DNSServer.hh"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DNSServer::DNSServer(
|
||||
shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
|
||||
: base(base),
|
||||
local_connect_address(local_connect_address),
|
||||
external_connect_address(external_connect_address),
|
||||
banned_ipv4_ranges(banned_ipv4_ranges) {}
|
||||
|
||||
DNSServer::~DNSServer() {
|
||||
for (const auto& it : this->fd_to_receive_event) {
|
||||
close(it.first);
|
||||
}
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& socket_path) {
|
||||
this->add_socket(::listen(socket_path, 0, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
this->add_socket(::listen(addr, port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(int port) {
|
||||
this->add_socket(::listen("", port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::add_socket(int fd) {
|
||||
unique_ptr<struct event, void (*)(struct event*)> e(
|
||||
event_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
|
||||
event_free);
|
||||
event_add(e.get(), nullptr);
|
||||
this->fd_to_receive_event.emplace(fd, std::move(e));
|
||||
}
|
||||
|
||||
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
|
||||
short events, void* ctx) {
|
||||
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
||||
if (size < 0x0C) {
|
||||
throw invalid_argument("query too small");
|
||||
}
|
||||
|
||||
const char* data = reinterpret_cast<const char*>(vdata);
|
||||
size_t name_len = strlen(&data[12]) + 1;
|
||||
|
||||
be_uint32_t be_resolved_address = resolved_address;
|
||||
|
||||
string response;
|
||||
response.append(data, 2);
|
||||
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
||||
response.append(&data[12], name_len);
|
||||
response.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
|
||||
response.append(reinterpret_cast<const char*>(&be_resolved_address), 4);
|
||||
return response;
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(
|
||||
const string& query, uint32_t resolved_address) {
|
||||
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
||||
}
|
||||
|
||||
void DNSServer::on_receive_message(int fd, short) {
|
||||
for (;;) {
|
||||
struct sockaddr_storage remote;
|
||||
socklen_t remote_size = sizeof(sockaddr_in);
|
||||
memset(&remote, 0, remote_size);
|
||||
|
||||
string input(2048, 0);
|
||||
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
|
||||
0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
|
||||
|
||||
if (bytes < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
dns_server_log.error("input error %d", errno);
|
||||
throw runtime_error("cannot read from udp socket");
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (bytes == 0) {
|
||||
break;
|
||||
|
||||
} else if (bytes < 0x0C) {
|
||||
dns_server_log.warning("input query too small");
|
||||
print_data(stderr, input.data(), bytes);
|
||||
|
||||
} else if (!this->banned_ipv4_ranges->check(remote)) {
|
||||
input.resize(bytes);
|
||||
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
|
||||
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
|
||||
uint32_t connect_address = is_local_address(remote_address)
|
||||
? this->local_connect_address
|
||||
: this->external_connect_address;
|
||||
string response = this->response_for_query(input, connect_address);
|
||||
sendto(fd, response.data(), response.size(), 0,
|
||||
reinterpret_cast<const sockaddr*>(&remote), remote_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-44
@@ -1,44 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
DNSServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
|
||||
DNSServer(const DNSServer&) = delete;
|
||||
DNSServer(DNSServer&&) = delete;
|
||||
virtual ~DNSServer();
|
||||
|
||||
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
|
||||
this->banned_ipv4_ranges = banned_ipv4_ranges;
|
||||
}
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address);
|
||||
static std::string response_for_query(const std::string& query, uint32_t resolved_address);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
|
||||
uint32_t local_connect_address;
|
||||
uint32_t external_connect_address;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
|
||||
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
|
||||
void on_receive_message(int fd, short event);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
DNSServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
|
||||
DNSServer(const DNSServer&) = delete;
|
||||
DNSServer(DNSServer&&) = delete;
|
||||
virtual ~DNSServer();
|
||||
|
||||
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
|
||||
this->banned_ipv4_ranges = banned_ipv4_ranges;
|
||||
}
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address);
|
||||
static std::string response_for_query(const std::string& query, uint32_t resolved_address);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
|
||||
uint32_t local_connect_address;
|
||||
uint32_t external_connect_address;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
|
||||
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
|
||||
void on_receive_message(int fd, short event);
|
||||
};
|
||||
|
||||
+1120
-1120
File diff suppressed because it is too large
Load Diff
+149
-149
@@ -1,149 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/Tools.hh>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
enum class EnemyType {
|
||||
UNKNOWN = -1,
|
||||
NONE = 0,
|
||||
NON_ENEMY_NPC,
|
||||
AL_RAPPY,
|
||||
ASTARK,
|
||||
BA_BOOTA,
|
||||
BARBA_RAY,
|
||||
BARBAROUS_WOLF,
|
||||
BEE_L,
|
||||
BEE_R,
|
||||
BOOMA,
|
||||
BOOTA,
|
||||
BULCLAW,
|
||||
BULK,
|
||||
CANADINE,
|
||||
CANADINE_GROUP,
|
||||
CANANE,
|
||||
CHAOS_BRINGER,
|
||||
CHAOS_SORCERER,
|
||||
CLAW,
|
||||
DARK_BELRA,
|
||||
DARK_FALZ_1,
|
||||
DARK_FALZ_2,
|
||||
DARK_FALZ_3,
|
||||
DARK_GUNNER,
|
||||
DARVANT,
|
||||
DARVANT_ULTIMATE,
|
||||
DE_ROL_LE,
|
||||
DE_ROL_LE_BODY,
|
||||
DE_ROL_LE_MINE,
|
||||
DEATH_GUNNER,
|
||||
DEL_LILY,
|
||||
DEL_RAPPY,
|
||||
DEL_RAPPY_ALT,
|
||||
DELBITER,
|
||||
DELDEPTH,
|
||||
DELSABER,
|
||||
DIMENIAN,
|
||||
DOLMDARL,
|
||||
DOLMOLM,
|
||||
DORPHON,
|
||||
DORPHON_ECLAIR,
|
||||
DRAGON,
|
||||
DUBCHIC,
|
||||
DUBWITCH, // Has no entry in battle params
|
||||
EGG_RAPPY,
|
||||
EPSIGUARD,
|
||||
EPSILON,
|
||||
EVIL_SHARK,
|
||||
GAEL,
|
||||
GAL_GRYPHON,
|
||||
GARANZ,
|
||||
GEE,
|
||||
GI_GUE,
|
||||
GIBBLES,
|
||||
GIGOBOOMA,
|
||||
GILLCHIC,
|
||||
GIRTABLULU,
|
||||
GOBOOMA,
|
||||
GOL_DRAGON,
|
||||
GORAN,
|
||||
GORAN_DETONATOR,
|
||||
GRASS_ASSASSIN,
|
||||
GUIL_SHARK,
|
||||
HALLO_RAPPY,
|
||||
HIDOOM,
|
||||
HILDEBEAR,
|
||||
HILDEBLUE,
|
||||
ILL_GILL,
|
||||
KONDRIEU,
|
||||
LA_DIMENIAN,
|
||||
LOVE_RAPPY,
|
||||
MERICAROL,
|
||||
MERICUS,
|
||||
MERIKLE,
|
||||
MERILLIA,
|
||||
MERILTAS,
|
||||
MERISSA_A,
|
||||
MERISSA_AA,
|
||||
MIGIUM,
|
||||
MONEST,
|
||||
MORFOS,
|
||||
MOTHMANT,
|
||||
NANO_DRAGON,
|
||||
NAR_LILY,
|
||||
OLGA_FLOW_1,
|
||||
OLGA_FLOW_2,
|
||||
PAL_SHARK,
|
||||
PAN_ARMS,
|
||||
PAZUZU,
|
||||
PAZUZU_ALT,
|
||||
PIG_RAY,
|
||||
POFUILLY_SLIME,
|
||||
POUILLY_SLIME,
|
||||
POISON_LILY,
|
||||
PYRO_GORAN,
|
||||
RAG_RAPPY,
|
||||
RECOBOX,
|
||||
RECON,
|
||||
SAINT_MILLION,
|
||||
SAINT_RAPPY,
|
||||
SAND_RAPPY,
|
||||
SAND_RAPPY_ALT,
|
||||
SATELLITE_LIZARD,
|
||||
SATELLITE_LIZARD_ALT,
|
||||
SAVAGE_WOLF,
|
||||
SHAMBERTIN,
|
||||
SINOW_BEAT,
|
||||
SINOW_BERILL,
|
||||
SINOW_GOLD,
|
||||
SINOW_SPIGELL,
|
||||
SINOW_ZELE,
|
||||
SINOW_ZOA,
|
||||
SO_DIMENIAN,
|
||||
UL_GIBBON,
|
||||
VOL_OPT_1,
|
||||
VOL_OPT_2,
|
||||
VOL_OPT_AMP,
|
||||
VOL_OPT_CORE,
|
||||
VOL_OPT_MONITOR,
|
||||
VOL_OPT_PILLAR,
|
||||
YOWIE,
|
||||
YOWIE_ALT,
|
||||
ZE_BOOTA,
|
||||
ZOL_GIBBON,
|
||||
ZU,
|
||||
ZU_ALT,
|
||||
MAX_ENEMY_TYPE,
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<EnemyType>(EnemyType type);
|
||||
template <>
|
||||
EnemyType enum_for_name<EnemyType>(const char* name);
|
||||
|
||||
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type);
|
||||
uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
|
||||
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
bool enemy_type_is_rare(EnemyType type);
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/Tools.hh>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
enum class EnemyType {
|
||||
UNKNOWN = -1,
|
||||
NONE = 0,
|
||||
NON_ENEMY_NPC,
|
||||
AL_RAPPY,
|
||||
ASTARK,
|
||||
BA_BOOTA,
|
||||
BARBA_RAY,
|
||||
BARBAROUS_WOLF,
|
||||
BEE_L,
|
||||
BEE_R,
|
||||
BOOMA,
|
||||
BOOTA,
|
||||
BULCLAW,
|
||||
BULK,
|
||||
CANADINE,
|
||||
CANADINE_GROUP,
|
||||
CANANE,
|
||||
CHAOS_BRINGER,
|
||||
CHAOS_SORCERER,
|
||||
CLAW,
|
||||
DARK_BELRA,
|
||||
DARK_FALZ_1,
|
||||
DARK_FALZ_2,
|
||||
DARK_FALZ_3,
|
||||
DARK_GUNNER,
|
||||
DARVANT,
|
||||
DARVANT_ULTIMATE,
|
||||
DE_ROL_LE,
|
||||
DE_ROL_LE_BODY,
|
||||
DE_ROL_LE_MINE,
|
||||
DEATH_GUNNER,
|
||||
DEL_LILY,
|
||||
DEL_RAPPY,
|
||||
DEL_RAPPY_ALT,
|
||||
DELBITER,
|
||||
DELDEPTH,
|
||||
DELSABER,
|
||||
DIMENIAN,
|
||||
DOLMDARL,
|
||||
DOLMOLM,
|
||||
DORPHON,
|
||||
DORPHON_ECLAIR,
|
||||
DRAGON,
|
||||
DUBCHIC,
|
||||
DUBWITCH, // Has no entry in battle params
|
||||
EGG_RAPPY,
|
||||
EPSIGUARD,
|
||||
EPSILON,
|
||||
EVIL_SHARK,
|
||||
GAEL,
|
||||
GAL_GRYPHON,
|
||||
GARANZ,
|
||||
GEE,
|
||||
GI_GUE,
|
||||
GIBBLES,
|
||||
GIGOBOOMA,
|
||||
GILLCHIC,
|
||||
GIRTABLULU,
|
||||
GOBOOMA,
|
||||
GOL_DRAGON,
|
||||
GORAN,
|
||||
GORAN_DETONATOR,
|
||||
GRASS_ASSASSIN,
|
||||
GUIL_SHARK,
|
||||
HALLO_RAPPY,
|
||||
HIDOOM,
|
||||
HILDEBEAR,
|
||||
HILDEBLUE,
|
||||
ILL_GILL,
|
||||
KONDRIEU,
|
||||
LA_DIMENIAN,
|
||||
LOVE_RAPPY,
|
||||
MERICAROL,
|
||||
MERICUS,
|
||||
MERIKLE,
|
||||
MERILLIA,
|
||||
MERILTAS,
|
||||
MERISSA_A,
|
||||
MERISSA_AA,
|
||||
MIGIUM,
|
||||
MONEST,
|
||||
MORFOS,
|
||||
MOTHMANT,
|
||||
NANO_DRAGON,
|
||||
NAR_LILY,
|
||||
OLGA_FLOW_1,
|
||||
OLGA_FLOW_2,
|
||||
PAL_SHARK,
|
||||
PAN_ARMS,
|
||||
PAZUZU,
|
||||
PAZUZU_ALT,
|
||||
PIG_RAY,
|
||||
POFUILLY_SLIME,
|
||||
POUILLY_SLIME,
|
||||
POISON_LILY,
|
||||
PYRO_GORAN,
|
||||
RAG_RAPPY,
|
||||
RECOBOX,
|
||||
RECON,
|
||||
SAINT_MILLION,
|
||||
SAINT_RAPPY,
|
||||
SAND_RAPPY,
|
||||
SAND_RAPPY_ALT,
|
||||
SATELLITE_LIZARD,
|
||||
SATELLITE_LIZARD_ALT,
|
||||
SAVAGE_WOLF,
|
||||
SHAMBERTIN,
|
||||
SINOW_BEAT,
|
||||
SINOW_BERILL,
|
||||
SINOW_GOLD,
|
||||
SINOW_SPIGELL,
|
||||
SINOW_ZELE,
|
||||
SINOW_ZOA,
|
||||
SO_DIMENIAN,
|
||||
UL_GIBBON,
|
||||
VOL_OPT_1,
|
||||
VOL_OPT_2,
|
||||
VOL_OPT_AMP,
|
||||
VOL_OPT_CORE,
|
||||
VOL_OPT_MONITOR,
|
||||
VOL_OPT_PILLAR,
|
||||
YOWIE,
|
||||
YOWIE_ALT,
|
||||
ZE_BOOTA,
|
||||
ZOL_GIBBON,
|
||||
ZU,
|
||||
ZU_ALT,
|
||||
MAX_ENEMY_TYPE,
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<EnemyType>(EnemyType type);
|
||||
template <>
|
||||
EnemyType enum_for_name<EnemyType>(const char* name);
|
||||
|
||||
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type);
|
||||
uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
|
||||
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
bool enemy_type_is_rare(EnemyType type);
|
||||
|
||||
+528
-528
File diff suppressed because it is too large
Load Diff
+101
-101
@@ -1,101 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
bool function_compiler_available();
|
||||
void set_function_compiler_available(bool is_available);
|
||||
|
||||
// TODO: Support x86 and SH4 function calls in the future. Currently we only
|
||||
// support PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
struct CompiledFunctionCode {
|
||||
enum class Architecture {
|
||||
UNKNOWN = 0,
|
||||
POWERPC, // GC
|
||||
X86, // PC, XB, BB
|
||||
SH4, // Dreamcast
|
||||
};
|
||||
Architecture arch;
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset;
|
||||
std::string source_path; // Path to source file from newserv root
|
||||
std::string short_name; // Based on filename
|
||||
std::string long_name; // From .meta name directive
|
||||
std::string description; // From .meta description directive
|
||||
uint8_t index; // 0 = unused (not registered in index_to_function)
|
||||
uint32_t menu_item_id;
|
||||
bool hide_from_patches_menu;
|
||||
uint32_t specific_version;
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
template <typename FooterT>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
};
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const std::string& directory,
|
||||
const std::string& name,
|
||||
const std::string& text);
|
||||
|
||||
struct FunctionCodeIndex {
|
||||
FunctionCodeIndex() = default;
|
||||
explicit FunctionCodeIndex(const std::string& directory);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
|
||||
std::unordered_map<uint8_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
|
||||
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
|
||||
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
|
||||
|
||||
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
|
||||
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
|
||||
};
|
||||
|
||||
struct DOLFileIndex {
|
||||
struct File {
|
||||
uint32_t menu_item_id;
|
||||
std::string name;
|
||||
std::string data;
|
||||
bool is_compressed;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<File>> item_id_to_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
|
||||
std::shared_ptr<const Menu> menu;
|
||||
|
||||
DOLFileIndex() = default;
|
||||
explicit DOLFileIndex(const std::string& directory);
|
||||
|
||||
inline bool empty() const {
|
||||
return this->name_to_file.empty() && this->item_id_to_file.empty();
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
bool function_compiler_available();
|
||||
void set_function_compiler_available(bool is_available);
|
||||
|
||||
// TODO: Support x86 and SH4 function calls in the future. Currently we only
|
||||
// support PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
struct CompiledFunctionCode {
|
||||
enum class Architecture {
|
||||
UNKNOWN = 0,
|
||||
POWERPC, // GC
|
||||
X86, // PC, XB, BB
|
||||
SH4, // Dreamcast
|
||||
};
|
||||
Architecture arch;
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset;
|
||||
std::string source_path; // Path to source file from newserv root
|
||||
std::string short_name; // Based on filename
|
||||
std::string long_name; // From .meta name directive
|
||||
std::string description; // From .meta description directive
|
||||
uint8_t index; // 0 = unused (not registered in index_to_function)
|
||||
uint32_t menu_item_id;
|
||||
bool hide_from_patches_menu;
|
||||
uint32_t specific_version;
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
template <typename FooterT>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t override_relocations_offset = 0) const;
|
||||
};
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const std::string& directory,
|
||||
const std::string& name,
|
||||
const std::string& text);
|
||||
|
||||
struct FunctionCodeIndex {
|
||||
FunctionCodeIndex() = default;
|
||||
explicit FunctionCodeIndex(const std::string& directory);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
|
||||
std::unordered_map<uint8_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
|
||||
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
|
||||
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
|
||||
|
||||
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
|
||||
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
|
||||
bool patch_menu_empty(uint32_t specific_version) const;
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
|
||||
};
|
||||
|
||||
struct DOLFileIndex {
|
||||
struct File {
|
||||
uint32_t menu_item_id;
|
||||
std::string name;
|
||||
std::string data;
|
||||
bool is_compressed;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<File>> item_id_to_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
|
||||
std::shared_ptr<const Menu> menu;
|
||||
|
||||
DOLFileIndex() = default;
|
||||
explicit DOLFileIndex(const std::string& directory);
|
||||
|
||||
inline bool empty() const {
|
||||
return this->name_to_file.empty() && this->item_id_to_file.empty();
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum);
|
||||
|
||||
+1031
-1031
File diff suppressed because it is too large
Load Diff
+76
-76
@@ -1,76 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class HTTPServer {
|
||||
public:
|
||||
HTTPServer(std::shared_ptr<ServerState> state);
|
||||
HTTPServer(const HTTPServer&) = delete;
|
||||
HTTPServer(HTTPServer&&) = delete;
|
||||
HTTPServer& operator=(const HTTPServer&) = delete;
|
||||
HTTPServer& operator=(HTTPServer&&) = delete;
|
||||
virtual ~HTTPServer() = default;
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
protected:
|
||||
class http_error : public std::runtime_error {
|
||||
public:
|
||||
http_error(int code, const std::string& what);
|
||||
int code;
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct evhttp> http;
|
||||
std::thread th;
|
||||
|
||||
void thread_fn();
|
||||
|
||||
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
|
||||
void handle_request(struct evhttp_request* req);
|
||||
|
||||
static const std::unordered_map<int, const char*> explanation_for_response_code;
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
|
||||
|
||||
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
|
||||
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
|
||||
static const std::string& get_url_param(
|
||||
const std::unordered_multimap<std::string, std::string>& params,
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
JSON generate_game_server_clients_json() const;
|
||||
JSON generate_proxy_server_clients_json() const;
|
||||
JSON generate_server_info_json() const;
|
||||
JSON generate_lobbies_json() const;
|
||||
JSON generate_summary_json() const;
|
||||
JSON generate_all_json() const;
|
||||
|
||||
JSON generate_ep3_cards_json(bool trial) const;
|
||||
JSON generate_common_tables_json() const;
|
||||
JSON generate_rare_tables_json() const;
|
||||
JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class HTTPServer {
|
||||
public:
|
||||
HTTPServer(std::shared_ptr<ServerState> state);
|
||||
HTTPServer(const HTTPServer&) = delete;
|
||||
HTTPServer(HTTPServer&&) = delete;
|
||||
HTTPServer& operator=(const HTTPServer&) = delete;
|
||||
HTTPServer& operator=(HTTPServer&&) = delete;
|
||||
virtual ~HTTPServer() = default;
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
protected:
|
||||
class http_error : public std::runtime_error {
|
||||
public:
|
||||
http_error(int code, const std::string& what);
|
||||
int code;
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct evhttp> http;
|
||||
std::thread th;
|
||||
|
||||
void thread_fn();
|
||||
|
||||
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
|
||||
void handle_request(struct evhttp_request* req);
|
||||
|
||||
static const std::unordered_map<int, const char*> explanation_for_response_code;
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
|
||||
|
||||
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
|
||||
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
|
||||
static const std::string& get_url_param(
|
||||
const std::unordered_multimap<std::string, std::string>& params,
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
JSON generate_game_server_clients_json() const;
|
||||
JSON generate_proxy_server_clients_json() const;
|
||||
JSON generate_server_info_json() const;
|
||||
JSON generate_lobbies_json() const;
|
||||
JSON generate_summary_json() const;
|
||||
JSON generate_all_json() const;
|
||||
|
||||
JSON generate_ep3_cards_json(bool trial) const;
|
||||
JSON generate_common_tables_json() const;
|
||||
JSON generate_rare_tables_json() const;
|
||||
JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
};
|
||||
|
||||
+73
-73
@@ -1,73 +1,73 @@
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPV4RangeSet::IPV4RangeSet(const JSON& json) {
|
||||
for (const auto& it : json.as_list()) {
|
||||
// String should be of the form a.b.c.d or a.b.c.d/e
|
||||
auto tokens = split(it->as_string(), '/');
|
||||
|
||||
size_t mask_bits;
|
||||
if (tokens.size() == 1) {
|
||||
mask_bits = 32;
|
||||
} else if (tokens.size() == 2) {
|
||||
mask_bits = stoul(tokens[1], nullptr, 10);
|
||||
if (mask_bits > 32) {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
|
||||
auto addr_tokens = split(tokens[0], '.');
|
||||
if (addr_tokens.size() != 4) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
}
|
||||
uint32_t addr = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
size_t end_pos = 0;
|
||||
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
|
||||
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
}
|
||||
addr = (addr << 8) | new_byte;
|
||||
}
|
||||
addr &= (0xFFFFFFFF << (32 - mask_bits));
|
||||
|
||||
this->ranges.emplace(addr, mask_bits);
|
||||
}
|
||||
}
|
||||
|
||||
JSON IPV4RangeSet::json() const {
|
||||
auto ret = JSON::list();
|
||||
for (const auto& it : this->ranges) {
|
||||
uint32_t addr = it.first;
|
||||
uint8_t mask_bits = it.second;
|
||||
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
|
||||
static_cast<uint8_t>((addr >> 24) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 16) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 8) & 0xFF),
|
||||
static_cast<uint8_t>(addr & 0xFF),
|
||||
mask_bits));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IPV4RangeSet::check(uint32_t addr) const {
|
||||
auto it = this->ranges.upper_bound(addr);
|
||||
if (it == this->ranges.begin()) {
|
||||
return false; // addr is before any range
|
||||
}
|
||||
const auto& range = *(--it);
|
||||
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
|
||||
}
|
||||
|
||||
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
|
||||
if (ss.ss_family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
|
||||
return this->check(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPV4RangeSet::IPV4RangeSet(const JSON& json) {
|
||||
for (const auto& it : json.as_list()) {
|
||||
// String should be of the form a.b.c.d or a.b.c.d/e
|
||||
auto tokens = split(it->as_string(), '/');
|
||||
|
||||
size_t mask_bits;
|
||||
if (tokens.size() == 1) {
|
||||
mask_bits = 32;
|
||||
} else if (tokens.size() == 2) {
|
||||
mask_bits = stoul(tokens[1], nullptr, 10);
|
||||
if (mask_bits > 32) {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
|
||||
auto addr_tokens = split(tokens[0], '.');
|
||||
if (addr_tokens.size() != 4) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
}
|
||||
uint32_t addr = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
size_t end_pos = 0;
|
||||
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
|
||||
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
}
|
||||
addr = (addr << 8) | new_byte;
|
||||
}
|
||||
addr &= (0xFFFFFFFF << (32 - mask_bits));
|
||||
|
||||
this->ranges.emplace(addr, mask_bits);
|
||||
}
|
||||
}
|
||||
|
||||
JSON IPV4RangeSet::json() const {
|
||||
auto ret = JSON::list();
|
||||
for (const auto& it : this->ranges) {
|
||||
uint32_t addr = it.first;
|
||||
uint8_t mask_bits = it.second;
|
||||
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
|
||||
static_cast<uint8_t>((addr >> 24) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 16) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 8) & 0xFF),
|
||||
static_cast<uint8_t>(addr & 0xFF),
|
||||
mask_bits));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IPV4RangeSet::check(uint32_t addr) const {
|
||||
auto it = this->ranges.upper_bound(addr);
|
||||
if (it == this->ranges.begin()) {
|
||||
return false; // addr is before any range
|
||||
}
|
||||
const auto& range = *(--it);
|
||||
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
|
||||
}
|
||||
|
||||
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
|
||||
if (ss.ss_family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
|
||||
return this->check(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
+18
-18
@@ -1,18 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
|
||||
class IPV4RangeSet {
|
||||
public:
|
||||
IPV4RangeSet() = default;
|
||||
explicit IPV4RangeSet(const JSON& json);
|
||||
|
||||
JSON json() const;
|
||||
|
||||
bool check(uint32_t addr) const;
|
||||
bool check(const struct sockaddr_storage& ss) const;
|
||||
|
||||
protected:
|
||||
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
|
||||
class IPV4RangeSet {
|
||||
public:
|
||||
IPV4RangeSet() = default;
|
||||
explicit IPV4RangeSet(const JSON& json);
|
||||
|
||||
JSON json() const;
|
||||
|
||||
bool check(uint32_t addr) const;
|
||||
bool check(const struct sockaddr_storage& ss) const;
|
||||
|
||||
protected:
|
||||
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
|
||||
};
|
||||
|
||||
+455
-455
@@ -1,455 +1,455 @@
|
||||
#include "IntegralExpression.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Tools.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
IntegralExpression::IntegralExpression(const string& text)
|
||||
: root(this->parse_expr(text)) {}
|
||||
|
||||
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
|
||||
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||
: type(type),
|
||||
left(std::move(left)),
|
||||
right(std::move(right)) {}
|
||||
|
||||
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
||||
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return this->left->evaluate(env) || this->right->evaluate(env);
|
||||
case Type::LOGICAL_AND:
|
||||
return this->left->evaluate(env) && this->right->evaluate(env);
|
||||
case Type::BITWISE_OR:
|
||||
return this->left->evaluate(env) | this->right->evaluate(env);
|
||||
case Type::BITWISE_AND:
|
||||
return this->left->evaluate(env) & this->right->evaluate(env);
|
||||
case Type::BITWISE_XOR:
|
||||
return this->left->evaluate(env) ^ this->right->evaluate(env);
|
||||
case Type::LEFT_SHIFT:
|
||||
return this->left->evaluate(env) << this->right->evaluate(env);
|
||||
case Type::RIGHT_SHIFT:
|
||||
return this->left->evaluate(env) >> this->right->evaluate(env);
|
||||
case Type::LESS_THAN:
|
||||
return this->left->evaluate(env) < this->right->evaluate(env);
|
||||
case Type::GREATER_THAN:
|
||||
return this->left->evaluate(env) > this->right->evaluate(env);
|
||||
case Type::LESS_OR_EQUAL:
|
||||
return this->left->evaluate(env) <= this->right->evaluate(env);
|
||||
case Type::GREATER_OR_EQUAL:
|
||||
return this->left->evaluate(env) >= this->right->evaluate(env);
|
||||
case Type::EQUAL:
|
||||
return this->left->evaluate(env) == this->right->evaluate(env);
|
||||
case Type::NOT_EQUAL:
|
||||
return this->left->evaluate(env) != this->right->evaluate(env);
|
||||
case Type::ADD:
|
||||
return this->left->evaluate(env) + this->right->evaluate(env);
|
||||
case Type::SUBTRACT:
|
||||
return this->left->evaluate(env) - this->right->evaluate(env);
|
||||
case Type::MULTIPLY:
|
||||
return this->left->evaluate(env) * this->right->evaluate(env);
|
||||
case Type::DIVIDE:
|
||||
return this->left->evaluate(env) / this->right->evaluate(env);
|
||||
case Type::MODULUS:
|
||||
return this->left->evaluate(env) % this->right->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::BinaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||
case Type::LOGICAL_AND:
|
||||
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
|
||||
case Type::BITWISE_OR:
|
||||
return "(" + this->left->str() + ") | (" + this->right->str() + ")";
|
||||
case Type::BITWISE_AND:
|
||||
return "(" + this->left->str() + ") & (" + this->right->str() + ")";
|
||||
case Type::BITWISE_XOR:
|
||||
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
|
||||
case Type::LEFT_SHIFT:
|
||||
return "(" + this->left->str() + ") << (" + this->right->str() + ")";
|
||||
case Type::RIGHT_SHIFT:
|
||||
return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
|
||||
case Type::LESS_THAN:
|
||||
return "(" + this->left->str() + ") < (" + this->right->str() + ")";
|
||||
case Type::GREATER_THAN:
|
||||
return "(" + this->left->str() + ") > (" + this->right->str() + ")";
|
||||
case Type::LESS_OR_EQUAL:
|
||||
return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
|
||||
case Type::GREATER_OR_EQUAL:
|
||||
return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
|
||||
case Type::EQUAL:
|
||||
return "(" + this->left->str() + ") == (" + this->right->str() + ")";
|
||||
case Type::NOT_EQUAL:
|
||||
return "(" + this->left->str() + ") != (" + this->right->str() + ")";
|
||||
case Type::ADD:
|
||||
return "(" + this->left->str() + ") + (" + this->right->str() + ")";
|
||||
case Type::SUBTRACT:
|
||||
return "(" + this->left->str() + ") - (" + this->right->str() + ")";
|
||||
case Type::MULTIPLY:
|
||||
return "(" + this->left->str() + ") * (" + this->right->str() + ")";
|
||||
case Type::DIVIDE:
|
||||
return "(" + this->left->str() + ") / (" + this->right->str() + ")";
|
||||
case Type::MODULUS:
|
||||
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
||||
: type(type),
|
||||
sub(std::move(sub)) {}
|
||||
|
||||
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
return !this->sub->evaluate(env);
|
||||
case Type::BITWISE_NOT:
|
||||
return ~this->sub->evaluate(env);
|
||||
case Type::NEGATIVE:
|
||||
return -this->sub->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
return "!(" + this->sub->str() + ")";
|
||||
case Type::BITWISE_NOT:
|
||||
return "~(" + this->sub->str() + ")";
|
||||
case Type::NEGATIVE:
|
||||
return "-(" + this->sub->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
||||
: flag_index(flag_index) {}
|
||||
|
||||
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
||||
return other_flag.flag_index == this->flag_index;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.flags) {
|
||||
throw runtime_error("quest flags not available");
|
||||
}
|
||||
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::FlagLookupNode::str() const {
|
||||
return string_printf("F_%04hX", this->flag_index);
|
||||
}
|
||||
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
|
||||
Episode episode, uint8_t stage_index)
|
||||
: episode(episode),
|
||||
stage_index(stage_index) {}
|
||||
|
||||
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
|
||||
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.challenge_records) {
|
||||
throw runtime_error("challenge records not available");
|
||||
}
|
||||
if (this->episode == Episode::EP1) {
|
||||
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
|
||||
} else if (this->episode == Episode::EP2) {
|
||||
return env.challenge_records->times_ep2_online.at(this->stage_index).has_value();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||
: reward_name(reward_name) {}
|
||||
|
||||
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
||||
return other_team_reward.reward_name == this->reward_name;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
|
||||
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::TeamRewardLookupNode::str() const {
|
||||
return "T_" + this->reward_name;
|
||||
}
|
||||
|
||||
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
|
||||
|
||||
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
|
||||
return env.num_players;
|
||||
}
|
||||
|
||||
string IntegralExpression::NumPlayersLookupNode::str() const {
|
||||
return "V_NumPlayers";
|
||||
}
|
||||
|
||||
IntegralExpression::EventLookupNode::EventLookupNode() {}
|
||||
|
||||
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
|
||||
return env.event;
|
||||
}
|
||||
|
||||
string IntegralExpression::EventLookupNode::str() const {
|
||||
return "V_Event";
|
||||
}
|
||||
|
||||
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
|
||||
|
||||
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
|
||||
return env.v1_present ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::V1PresenceLookupNode::str() const {
|
||||
return "V_V1Present";
|
||||
}
|
||||
|
||||
IntegralExpression::ConstantNode::ConstantNode(int64_t value)
|
||||
: value(value) {}
|
||||
|
||||
bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
||||
return other_const.value == this->value;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
||||
return this->value;
|
||||
}
|
||||
|
||||
string IntegralExpression::ConstantNode::str() const {
|
||||
return string_printf("%" PRId64, this->value);
|
||||
}
|
||||
|
||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
||||
// Strip off spaces and fully-enclosing parentheses
|
||||
for (;;) {
|
||||
size_t starting_size = text.size();
|
||||
while (text.at(0) == ' ') {
|
||||
text = text.substr(1);
|
||||
}
|
||||
while (text.at(text.size() - 1) == ' ') {
|
||||
text = text.substr(0, text.size() - 1);
|
||||
}
|
||||
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
|
||||
// It doesn't suffice to just check the first ant last characters, since
|
||||
// text could be like "(a) && (b)". Instead, we ignore the first and last
|
||||
// characters, and don't strip anything if the internal parentheses are
|
||||
// unbalanced.
|
||||
size_t paren_level = 1;
|
||||
for (size_t z = 1; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
if (paren_level == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paren_level > 0) {
|
||||
text = text.substr(1, text.size() - 2);
|
||||
}
|
||||
}
|
||||
if (text.size() == starting_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text.empty()) {
|
||||
throw runtime_error("invalid expression");
|
||||
}
|
||||
|
||||
// Check for binary operators at the root level
|
||||
using BinType = BinaryOperatorNode::Type;
|
||||
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{make_pair("||", BinType::LOGICAL_OR)}},
|
||||
};
|
||||
for (const auto& operators : binary_operator_levels) {
|
||||
size_t paren_level = 0;
|
||||
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
continue;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
continue;
|
||||
}
|
||||
if (!paren_level) {
|
||||
for (const auto& oper : operators) {
|
||||
// Awful hack (because I'm too lazy to add a tokenization step): if
|
||||
// the operator is followed or preceded by another copy of itself,
|
||||
// don't match it (this prevents us from matching & when the token is
|
||||
// actually &&)
|
||||
if ((text.size() > z + oper.first.size()) &&
|
||||
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
|
||||
(text.compare(z, oper.first.size(), oper.first) == 0) &&
|
||||
(text.compare(z + oper.first.size(), oper.first.size(), oper.first) != 0)) {
|
||||
auto left = IntegralExpression::parse_expr(text.substr(0, z));
|
||||
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
|
||||
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unary operators
|
||||
if (text[0] == '!') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
} else if (text[0] == '~') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
} else if (text[0] == '-') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
}
|
||||
|
||||
// Check for env lookups
|
||||
if (text.starts_with("F_")) {
|
||||
char* endptr = nullptr;
|
||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid flag lookup token");
|
||||
}
|
||||
if (flag >= 0x400) {
|
||||
throw runtime_error("invalid flag index");
|
||||
}
|
||||
return make_unique<FlagLookupNode>(flag);
|
||||
}
|
||||
if (text.starts_with("CC_")) {
|
||||
Episode episode;
|
||||
if (text.starts_with("CC_Ep1_")) {
|
||||
episode = Episode::EP1;
|
||||
} else if (text.starts_with("CC_Ep2_")) {
|
||||
episode = Episode::EP2;
|
||||
} else {
|
||||
throw runtime_error("invalid challenge episode");
|
||||
}
|
||||
char* endptr = nullptr;
|
||||
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid challenge completion lookup token");
|
||||
}
|
||||
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
|
||||
throw runtime_error("invalid challenge stage index");
|
||||
}
|
||||
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
||||
}
|
||||
if (text.starts_with("T_")) {
|
||||
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
||||
}
|
||||
if (text == "V_NumPlayers") {
|
||||
return make_unique<NumPlayersLookupNode>();
|
||||
}
|
||||
if (text == "V_Event") {
|
||||
return make_unique<EventLookupNode>();
|
||||
}
|
||||
if (text == "V_V1Present") {
|
||||
return make_unique<V1PresenceLookupNode>();
|
||||
}
|
||||
|
||||
// Check for constants
|
||||
if (text == "true") {
|
||||
return make_unique<ConstantNode>(1);
|
||||
}
|
||||
if (text == "false") {
|
||||
return make_unique<ConstantNode>(0);
|
||||
}
|
||||
try {
|
||||
size_t endpos;
|
||||
int64_t v = stoll(string(text), &endpos, 0);
|
||||
if (endpos == text.size()) {
|
||||
return make_unique<ConstantNode>(v);
|
||||
}
|
||||
} catch (const exception&) {
|
||||
}
|
||||
throw runtime_error("unparseable expression");
|
||||
}
|
||||
#include "IntegralExpression.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Tools.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
IntegralExpression::IntegralExpression(const string& text)
|
||||
: root(this->parse_expr(text)) {}
|
||||
|
||||
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
|
||||
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||
: type(type),
|
||||
left(std::move(left)),
|
||||
right(std::move(right)) {}
|
||||
|
||||
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
||||
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return this->left->evaluate(env) || this->right->evaluate(env);
|
||||
case Type::LOGICAL_AND:
|
||||
return this->left->evaluate(env) && this->right->evaluate(env);
|
||||
case Type::BITWISE_OR:
|
||||
return this->left->evaluate(env) | this->right->evaluate(env);
|
||||
case Type::BITWISE_AND:
|
||||
return this->left->evaluate(env) & this->right->evaluate(env);
|
||||
case Type::BITWISE_XOR:
|
||||
return this->left->evaluate(env) ^ this->right->evaluate(env);
|
||||
case Type::LEFT_SHIFT:
|
||||
return this->left->evaluate(env) << this->right->evaluate(env);
|
||||
case Type::RIGHT_SHIFT:
|
||||
return this->left->evaluate(env) >> this->right->evaluate(env);
|
||||
case Type::LESS_THAN:
|
||||
return this->left->evaluate(env) < this->right->evaluate(env);
|
||||
case Type::GREATER_THAN:
|
||||
return this->left->evaluate(env) > this->right->evaluate(env);
|
||||
case Type::LESS_OR_EQUAL:
|
||||
return this->left->evaluate(env) <= this->right->evaluate(env);
|
||||
case Type::GREATER_OR_EQUAL:
|
||||
return this->left->evaluate(env) >= this->right->evaluate(env);
|
||||
case Type::EQUAL:
|
||||
return this->left->evaluate(env) == this->right->evaluate(env);
|
||||
case Type::NOT_EQUAL:
|
||||
return this->left->evaluate(env) != this->right->evaluate(env);
|
||||
case Type::ADD:
|
||||
return this->left->evaluate(env) + this->right->evaluate(env);
|
||||
case Type::SUBTRACT:
|
||||
return this->left->evaluate(env) - this->right->evaluate(env);
|
||||
case Type::MULTIPLY:
|
||||
return this->left->evaluate(env) * this->right->evaluate(env);
|
||||
case Type::DIVIDE:
|
||||
return this->left->evaluate(env) / this->right->evaluate(env);
|
||||
case Type::MODULUS:
|
||||
return this->left->evaluate(env) % this->right->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::BinaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||
case Type::LOGICAL_AND:
|
||||
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
|
||||
case Type::BITWISE_OR:
|
||||
return "(" + this->left->str() + ") | (" + this->right->str() + ")";
|
||||
case Type::BITWISE_AND:
|
||||
return "(" + this->left->str() + ") & (" + this->right->str() + ")";
|
||||
case Type::BITWISE_XOR:
|
||||
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
|
||||
case Type::LEFT_SHIFT:
|
||||
return "(" + this->left->str() + ") << (" + this->right->str() + ")";
|
||||
case Type::RIGHT_SHIFT:
|
||||
return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
|
||||
case Type::LESS_THAN:
|
||||
return "(" + this->left->str() + ") < (" + this->right->str() + ")";
|
||||
case Type::GREATER_THAN:
|
||||
return "(" + this->left->str() + ") > (" + this->right->str() + ")";
|
||||
case Type::LESS_OR_EQUAL:
|
||||
return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
|
||||
case Type::GREATER_OR_EQUAL:
|
||||
return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
|
||||
case Type::EQUAL:
|
||||
return "(" + this->left->str() + ") == (" + this->right->str() + ")";
|
||||
case Type::NOT_EQUAL:
|
||||
return "(" + this->left->str() + ") != (" + this->right->str() + ")";
|
||||
case Type::ADD:
|
||||
return "(" + this->left->str() + ") + (" + this->right->str() + ")";
|
||||
case Type::SUBTRACT:
|
||||
return "(" + this->left->str() + ") - (" + this->right->str() + ")";
|
||||
case Type::MULTIPLY:
|
||||
return "(" + this->left->str() + ") * (" + this->right->str() + ")";
|
||||
case Type::DIVIDE:
|
||||
return "(" + this->left->str() + ") / (" + this->right->str() + ")";
|
||||
case Type::MODULUS:
|
||||
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
||||
: type(type),
|
||||
sub(std::move(sub)) {}
|
||||
|
||||
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
return !this->sub->evaluate(env);
|
||||
case Type::BITWISE_NOT:
|
||||
return ~this->sub->evaluate(env);
|
||||
case Type::NEGATIVE:
|
||||
return -this->sub->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
return "!(" + this->sub->str() + ")";
|
||||
case Type::BITWISE_NOT:
|
||||
return "~(" + this->sub->str() + ")";
|
||||
case Type::NEGATIVE:
|
||||
return "-(" + this->sub->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
||||
: flag_index(flag_index) {}
|
||||
|
||||
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
||||
return other_flag.flag_index == this->flag_index;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.flags) {
|
||||
throw runtime_error("quest flags not available");
|
||||
}
|
||||
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::FlagLookupNode::str() const {
|
||||
return string_printf("F_%04hX", this->flag_index);
|
||||
}
|
||||
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
|
||||
Episode episode, uint8_t stage_index)
|
||||
: episode(episode),
|
||||
stage_index(stage_index) {}
|
||||
|
||||
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
|
||||
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.challenge_records) {
|
||||
throw runtime_error("challenge records not available");
|
||||
}
|
||||
if (this->episode == Episode::EP1) {
|
||||
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
|
||||
} else if (this->episode == Episode::EP2) {
|
||||
return env.challenge_records->times_ep2_online.at(this->stage_index).has_value();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||
: reward_name(reward_name) {}
|
||||
|
||||
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
||||
return other_team_reward.reward_name == this->reward_name;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
|
||||
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::TeamRewardLookupNode::str() const {
|
||||
return "T_" + this->reward_name;
|
||||
}
|
||||
|
||||
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
|
||||
|
||||
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
|
||||
return env.num_players;
|
||||
}
|
||||
|
||||
string IntegralExpression::NumPlayersLookupNode::str() const {
|
||||
return "V_NumPlayers";
|
||||
}
|
||||
|
||||
IntegralExpression::EventLookupNode::EventLookupNode() {}
|
||||
|
||||
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
|
||||
return env.event;
|
||||
}
|
||||
|
||||
string IntegralExpression::EventLookupNode::str() const {
|
||||
return "V_Event";
|
||||
}
|
||||
|
||||
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
|
||||
|
||||
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
|
||||
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
|
||||
return env.v1_present ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::V1PresenceLookupNode::str() const {
|
||||
return "V_V1Present";
|
||||
}
|
||||
|
||||
IntegralExpression::ConstantNode::ConstantNode(int64_t value)
|
||||
: value(value) {}
|
||||
|
||||
bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
||||
return other_const.value == this->value;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
||||
return this->value;
|
||||
}
|
||||
|
||||
string IntegralExpression::ConstantNode::str() const {
|
||||
return string_printf("%" PRId64, this->value);
|
||||
}
|
||||
|
||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
||||
// Strip off spaces and fully-enclosing parentheses
|
||||
for (;;) {
|
||||
size_t starting_size = text.size();
|
||||
while (text.at(0) == ' ') {
|
||||
text = text.substr(1);
|
||||
}
|
||||
while (text.at(text.size() - 1) == ' ') {
|
||||
text = text.substr(0, text.size() - 1);
|
||||
}
|
||||
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
|
||||
// It doesn't suffice to just check the first ant last characters, since
|
||||
// text could be like "(a) && (b)". Instead, we ignore the first and last
|
||||
// characters, and don't strip anything if the internal parentheses are
|
||||
// unbalanced.
|
||||
size_t paren_level = 1;
|
||||
for (size_t z = 1; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
if (paren_level == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paren_level > 0) {
|
||||
text = text.substr(1, text.size() - 2);
|
||||
}
|
||||
}
|
||||
if (text.size() == starting_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (text.empty()) {
|
||||
throw runtime_error("invalid expression");
|
||||
}
|
||||
|
||||
// Check for binary operators at the root level
|
||||
using BinType = BinaryOperatorNode::Type;
|
||||
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{make_pair("||", BinType::LOGICAL_OR)}},
|
||||
};
|
||||
for (const auto& operators : binary_operator_levels) {
|
||||
size_t paren_level = 0;
|
||||
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
continue;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
continue;
|
||||
}
|
||||
if (!paren_level) {
|
||||
for (const auto& oper : operators) {
|
||||
// Awful hack (because I'm too lazy to add a tokenization step): if
|
||||
// the operator is followed or preceded by another copy of itself,
|
||||
// don't match it (this prevents us from matching & when the token is
|
||||
// actually &&)
|
||||
if ((text.size() > z + oper.first.size()) &&
|
||||
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
|
||||
(text.compare(z, oper.first.size(), oper.first) == 0) &&
|
||||
(text.compare(z + oper.first.size(), oper.first.size(), oper.first) != 0)) {
|
||||
auto left = IntegralExpression::parse_expr(text.substr(0, z));
|
||||
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
|
||||
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unary operators
|
||||
if (text[0] == '!') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
} else if (text[0] == '~') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
} else if (text[0] == '-') {
|
||||
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
|
||||
IntegralExpression::parse_expr(text.substr(1)));
|
||||
}
|
||||
|
||||
// Check for env lookups
|
||||
if (text.starts_with("F_")) {
|
||||
char* endptr = nullptr;
|
||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid flag lookup token");
|
||||
}
|
||||
if (flag >= 0x400) {
|
||||
throw runtime_error("invalid flag index");
|
||||
}
|
||||
return make_unique<FlagLookupNode>(flag);
|
||||
}
|
||||
if (text.starts_with("CC_")) {
|
||||
Episode episode;
|
||||
if (text.starts_with("CC_Ep1_")) {
|
||||
episode = Episode::EP1;
|
||||
} else if (text.starts_with("CC_Ep2_")) {
|
||||
episode = Episode::EP2;
|
||||
} else {
|
||||
throw runtime_error("invalid challenge episode");
|
||||
}
|
||||
char* endptr = nullptr;
|
||||
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid challenge completion lookup token");
|
||||
}
|
||||
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
|
||||
throw runtime_error("invalid challenge stage index");
|
||||
}
|
||||
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
||||
}
|
||||
if (text.starts_with("T_")) {
|
||||
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
||||
}
|
||||
if (text == "V_NumPlayers") {
|
||||
return make_unique<NumPlayersLookupNode>();
|
||||
}
|
||||
if (text == "V_Event") {
|
||||
return make_unique<EventLookupNode>();
|
||||
}
|
||||
if (text == "V_V1Present") {
|
||||
return make_unique<V1PresenceLookupNode>();
|
||||
}
|
||||
|
||||
// Check for constants
|
||||
if (text == "true") {
|
||||
return make_unique<ConstantNode>(1);
|
||||
}
|
||||
if (text == "false") {
|
||||
return make_unique<ConstantNode>(0);
|
||||
}
|
||||
try {
|
||||
size_t endpos;
|
||||
int64_t v = stoll(string(text), &endpos, 0);
|
||||
if (endpos == text.size()) {
|
||||
return make_unique<ConstantNode>(v);
|
||||
}
|
||||
} catch (const exception&) {
|
||||
}
|
||||
throw runtime_error("unparseable expression");
|
||||
}
|
||||
|
||||
+188
-188
@@ -1,188 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
class IntegralExpression {
|
||||
public:
|
||||
struct Env {
|
||||
const QuestFlagsForDifficulty* flags;
|
||||
const PlayerRecordsChallengeBB* challenge_records;
|
||||
std::shared_ptr<const TeamIndex::Team> team;
|
||||
size_t num_players;
|
||||
uint8_t event;
|
||||
bool v1_present;
|
||||
};
|
||||
|
||||
IntegralExpression(const std::string& text);
|
||||
~IntegralExpression() = default;
|
||||
inline bool operator==(const IntegralExpression& other) const {
|
||||
return this->root->operator==(*other.root);
|
||||
}
|
||||
inline bool operator!=(const IntegralExpression& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
inline int64_t evaluate(const Env& env) const {
|
||||
return this->root->evaluate(env);
|
||||
}
|
||||
inline std::string str() const {
|
||||
return this->root->str();
|
||||
}
|
||||
|
||||
protected:
|
||||
class Node {
|
||||
public:
|
||||
virtual ~Node() = default;
|
||||
virtual bool operator==(const Node& other) const = 0;
|
||||
inline bool operator!=(const Node& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
virtual int64_t evaluate(const Env& env) const = 0;
|
||||
virtual std::string str() const = 0;
|
||||
|
||||
protected:
|
||||
Node() = default;
|
||||
};
|
||||
|
||||
class BinaryOperatorNode : public Node {
|
||||
public:
|
||||
enum class Type {
|
||||
LOGICAL_OR = 0,
|
||||
LOGICAL_AND,
|
||||
BITWISE_OR,
|
||||
BITWISE_AND,
|
||||
BITWISE_XOR,
|
||||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT,
|
||||
LESS_THAN,
|
||||
GREATER_THAN,
|
||||
LESS_OR_EQUAL,
|
||||
GREATER_OR_EQUAL,
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
MODULUS,
|
||||
};
|
||||
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||
virtual ~BinaryOperatorNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> left;
|
||||
std::unique_ptr<const Node> right;
|
||||
};
|
||||
|
||||
class UnaryOperatorNode : public Node {
|
||||
public:
|
||||
enum class Type {
|
||||
LOGICAL_NOT = 0,
|
||||
BITWISE_NOT,
|
||||
NEGATIVE,
|
||||
};
|
||||
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
|
||||
virtual ~UnaryOperatorNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> sub;
|
||||
};
|
||||
|
||||
class FlagLookupNode : public Node {
|
||||
public:
|
||||
FlagLookupNode(uint16_t flag_index);
|
||||
virtual ~FlagLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
uint16_t flag_index;
|
||||
};
|
||||
|
||||
class ChallengeCompletionLookupNode : public Node {
|
||||
public:
|
||||
ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index);
|
||||
virtual ~ChallengeCompletionLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Episode episode;
|
||||
uint8_t stage_index;
|
||||
};
|
||||
|
||||
class TeamRewardLookupNode : public Node {
|
||||
public:
|
||||
TeamRewardLookupNode(const std::string& reward_name);
|
||||
virtual ~TeamRewardLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
std::string reward_name;
|
||||
};
|
||||
|
||||
class NumPlayersLookupNode : public Node {
|
||||
public:
|
||||
NumPlayersLookupNode();
|
||||
virtual ~NumPlayersLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class EventLookupNode : public Node {
|
||||
public:
|
||||
EventLookupNode();
|
||||
virtual ~EventLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class V1PresenceLookupNode : public Node {
|
||||
public:
|
||||
V1PresenceLookupNode();
|
||||
virtual ~V1PresenceLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class ConstantNode : public Node {
|
||||
public:
|
||||
ConstantNode(int64_t value);
|
||||
virtual ~ConstantNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
int64_t value;
|
||||
};
|
||||
|
||||
std::unique_ptr<const Node> parse_expr(std::string_view text);
|
||||
|
||||
std::unique_ptr<const Node> root;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
class IntegralExpression {
|
||||
public:
|
||||
struct Env {
|
||||
const QuestFlagsForDifficulty* flags;
|
||||
const PlayerRecordsChallengeBB* challenge_records;
|
||||
std::shared_ptr<const TeamIndex::Team> team;
|
||||
size_t num_players;
|
||||
uint8_t event;
|
||||
bool v1_present;
|
||||
};
|
||||
|
||||
IntegralExpression(const std::string& text);
|
||||
~IntegralExpression() = default;
|
||||
inline bool operator==(const IntegralExpression& other) const {
|
||||
return this->root->operator==(*other.root);
|
||||
}
|
||||
inline bool operator!=(const IntegralExpression& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
inline int64_t evaluate(const Env& env) const {
|
||||
return this->root->evaluate(env);
|
||||
}
|
||||
inline std::string str() const {
|
||||
return this->root->str();
|
||||
}
|
||||
|
||||
protected:
|
||||
class Node {
|
||||
public:
|
||||
virtual ~Node() = default;
|
||||
virtual bool operator==(const Node& other) const = 0;
|
||||
inline bool operator!=(const Node& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
virtual int64_t evaluate(const Env& env) const = 0;
|
||||
virtual std::string str() const = 0;
|
||||
|
||||
protected:
|
||||
Node() = default;
|
||||
};
|
||||
|
||||
class BinaryOperatorNode : public Node {
|
||||
public:
|
||||
enum class Type {
|
||||
LOGICAL_OR = 0,
|
||||
LOGICAL_AND,
|
||||
BITWISE_OR,
|
||||
BITWISE_AND,
|
||||
BITWISE_XOR,
|
||||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT,
|
||||
LESS_THAN,
|
||||
GREATER_THAN,
|
||||
LESS_OR_EQUAL,
|
||||
GREATER_OR_EQUAL,
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
MODULUS,
|
||||
};
|
||||
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||
virtual ~BinaryOperatorNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> left;
|
||||
std::unique_ptr<const Node> right;
|
||||
};
|
||||
|
||||
class UnaryOperatorNode : public Node {
|
||||
public:
|
||||
enum class Type {
|
||||
LOGICAL_NOT = 0,
|
||||
BITWISE_NOT,
|
||||
NEGATIVE,
|
||||
};
|
||||
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
|
||||
virtual ~UnaryOperatorNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> sub;
|
||||
};
|
||||
|
||||
class FlagLookupNode : public Node {
|
||||
public:
|
||||
FlagLookupNode(uint16_t flag_index);
|
||||
virtual ~FlagLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
uint16_t flag_index;
|
||||
};
|
||||
|
||||
class ChallengeCompletionLookupNode : public Node {
|
||||
public:
|
||||
ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index);
|
||||
virtual ~ChallengeCompletionLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Episode episode;
|
||||
uint8_t stage_index;
|
||||
};
|
||||
|
||||
class TeamRewardLookupNode : public Node {
|
||||
public:
|
||||
TeamRewardLookupNode(const std::string& reward_name);
|
||||
virtual ~TeamRewardLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
std::string reward_name;
|
||||
};
|
||||
|
||||
class NumPlayersLookupNode : public Node {
|
||||
public:
|
||||
NumPlayersLookupNode();
|
||||
virtual ~NumPlayersLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class EventLookupNode : public Node {
|
||||
public:
|
||||
EventLookupNode();
|
||||
virtual ~EventLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class V1PresenceLookupNode : public Node {
|
||||
public:
|
||||
V1PresenceLookupNode();
|
||||
virtual ~V1PresenceLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
};
|
||||
|
||||
class ConstantNode : public Node {
|
||||
public:
|
||||
ConstantNode(int64_t value);
|
||||
virtual ~ConstantNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
int64_t value;
|
||||
};
|
||||
|
||||
std::unique_ptr<const Node> parse_expr(std::string_view text);
|
||||
|
||||
std::unique_ptr<const Node> root;
|
||||
};
|
||||
|
||||
+513
-513
File diff suppressed because it is too large
Load Diff
+25
-25
@@ -1,25 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
void apply_mag_feed_result(
|
||||
ItemData& mag_item,
|
||||
const ItemData& fed_item,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||
uint8_t char_class,
|
||||
uint8_t section_id,
|
||||
bool version_has_rare_mags);
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
void apply_mag_feed_result(
|
||||
ItemData& mag_item,
|
||||
const ItemData& fed_item,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||
uint8_t char_class,
|
||||
uint8_t section_id,
|
||||
bool version_has_rare_mags);
|
||||
|
||||
+190
-190
@@ -1,190 +1,190 @@
|
||||
#include "LevelTable.hh"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||
stats.level = 0;
|
||||
stats.experience = 0;
|
||||
stats.char_stats = this->base_stats_for_class(char_class);
|
||||
}
|
||||
|
||||
void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
|
||||
for (; stats.level < level; stats.level++) {
|
||||
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1);
|
||||
// The original code clamps the resulting stat values to [0, max_stat]; we
|
||||
// don't have max_stat handy so we just allow them to be unbounded
|
||||
stats.char_stats.atp += level_stats.atp;
|
||||
stats.char_stats.mst += level_stats.mst;
|
||||
stats.char_stats.evp += level_stats.evp;
|
||||
stats.char_stats.hp += level_stats.hp;
|
||||
stats.char_stats.dfp += level_stats.dfp;
|
||||
stats.char_stats.ata += level_stats.ata;
|
||||
// Note: It is not a bug that lck is ignored here; the original code
|
||||
// ignores it too.
|
||||
stats.experience = level_stats.experience;
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
// TODO: The overall format of this file on V2 has much more data than we
|
||||
// actually use. What's known of the structure so far:
|
||||
le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200]
|
||||
le_uint32_t unknown_a1; // -> float[6]
|
||||
le_uint32_t max_stats; // -> PlayerStats[9]
|
||||
le_uint32_t level_100_stats; // -> Level100Entry[9]
|
||||
le_uint32_t base_stats; // -> u32[9] -> CharacterStats
|
||||
le_uint32_t unknown_a2; // -> (0x120 zero bytes)
|
||||
le_uint32_t attack_data; // -> AttackData[9]
|
||||
le_uint32_t unknown_a4; // -> (0x14-byte struct)[9]
|
||||
le_uint32_t unknown_a5; // -> float[9]
|
||||
le_uint32_t unknown_a6; // -> (0x30 bytes)
|
||||
le_uint32_t unknown_a7; // -> (0x2D bytes)
|
||||
le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D]
|
||||
le_uint32_t unknown_a9; // -> (0x90 bytes)
|
||||
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
|
||||
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
|
||||
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
|
||||
} __packed_ws__(Offsets, 0x40);
|
||||
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
|
||||
this->level_100_stats[char_class] = r.pget<Level100Entry>(offsets.level_100_stats + char_class * sizeof(Level100Entry));
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
|
||||
return this->level_100_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
|
||||
return this->max_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (encrypted) {
|
||||
auto decrypted = decrypt_pr2_data<true>(data);
|
||||
decompressed_data = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed_data.size() != decrypted.decompressed_size) {
|
||||
throw runtime_error("decompressed data size does not match expected size");
|
||||
}
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
// The GC format is very simple (but everything is big-endian):
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
const auto& src_delta = src_deltas[level];
|
||||
auto& dest_delta = this->level_deltas[char_class][level];
|
||||
dest_delta.atp = src_delta.atp;
|
||||
dest_delta.mst = src_delta.mst;
|
||||
dest_delta.evp = src_delta.evp;
|
||||
dest_delta.hp = src_delta.hp;
|
||||
dest_delta.dfp = src_delta.dfp;
|
||||
dest_delta.ata = src_delta.ata;
|
||||
dest_delta.lck = src_delta.lck;
|
||||
dest_delta.tp = src_delta.tp;
|
||||
dest_delta.experience = src_delta.experience.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
|
||||
static const array<CharacterStats, 12> data = {
|
||||
// ATP MST EVP HP DFP ATA LCK
|
||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
|
||||
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
|
||||
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
|
||||
CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A},
|
||||
CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A},
|
||||
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
|
||||
CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A},
|
||||
CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A},
|
||||
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
|
||||
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
|
||||
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
|
||||
};
|
||||
return data.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
|
||||
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
|
||||
} __packed_ws__(Offsets, 8);
|
||||
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
#include "LevelTable.hh"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||
stats.level = 0;
|
||||
stats.experience = 0;
|
||||
stats.char_stats = this->base_stats_for_class(char_class);
|
||||
}
|
||||
|
||||
void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
|
||||
for (; stats.level < level; stats.level++) {
|
||||
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1);
|
||||
// The original code clamps the resulting stat values to [0, max_stat]; we
|
||||
// don't have max_stat handy so we just allow them to be unbounded
|
||||
stats.char_stats.atp += level_stats.atp;
|
||||
stats.char_stats.mst += level_stats.mst;
|
||||
stats.char_stats.evp += level_stats.evp;
|
||||
stats.char_stats.hp += level_stats.hp;
|
||||
stats.char_stats.dfp += level_stats.dfp;
|
||||
stats.char_stats.ata += level_stats.ata;
|
||||
// Note: It is not a bug that lck is ignored here; the original code
|
||||
// ignores it too.
|
||||
stats.experience = level_stats.experience;
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
// TODO: The overall format of this file on V2 has much more data than we
|
||||
// actually use. What's known of the structure so far:
|
||||
le_uint32_t level_deltas; // -> u32[9] -> LevelStatsDelta[200]
|
||||
le_uint32_t unknown_a1; // -> float[6]
|
||||
le_uint32_t max_stats; // -> PlayerStats[9]
|
||||
le_uint32_t level_100_stats; // -> Level100Entry[9]
|
||||
le_uint32_t base_stats; // -> u32[9] -> CharacterStats
|
||||
le_uint32_t unknown_a2; // -> (0x120 zero bytes)
|
||||
le_uint32_t attack_data; // -> AttackData[9]
|
||||
le_uint32_t unknown_a4; // -> (0x14-byte struct)[9]
|
||||
le_uint32_t unknown_a5; // -> float[9]
|
||||
le_uint32_t unknown_a6; // -> (0x30 bytes)
|
||||
le_uint32_t unknown_a7; // -> (0x2D bytes)
|
||||
le_uint32_t unknown_a8; // -> u32[3] -> float[0x2D]
|
||||
le_uint32_t unknown_a9; // -> (0x90 bytes)
|
||||
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
|
||||
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
|
||||
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
|
||||
} __packed_ws__(Offsets, 0x40);
|
||||
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
|
||||
this->level_100_stats[char_class] = r.pget<Level100Entry>(offsets.level_100_stats + char_class * sizeof(Level100Entry));
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelTableV2::Level100Entry& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
|
||||
return this->level_100_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
|
||||
return this->max_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (encrypted) {
|
||||
auto decrypted = decrypt_pr2_data<true>(data);
|
||||
decompressed_data = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed_data.size() != decrypted.decompressed_size) {
|
||||
throw runtime_error("decompressed data size does not match expected size");
|
||||
}
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
// The GC format is very simple (but everything is big-endian):
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
const auto& src_delta = src_deltas[level];
|
||||
auto& dest_delta = this->level_deltas[char_class][level];
|
||||
dest_delta.atp = src_delta.atp;
|
||||
dest_delta.mst = src_delta.mst;
|
||||
dest_delta.evp = src_delta.evp;
|
||||
dest_delta.hp = src_delta.hp;
|
||||
dest_delta.dfp = src_delta.dfp;
|
||||
dest_delta.ata = src_delta.ata;
|
||||
dest_delta.lck = src_delta.lck;
|
||||
dest_delta.tp = src_delta.tp;
|
||||
dest_delta.experience = src_delta.experience.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
|
||||
static const array<CharacterStats, 12> data = {
|
||||
// ATP MST EVP HP DFP ATA LCK
|
||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
|
||||
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
|
||||
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
|
||||
CharacterStats{0x0019, 0x0000, 0x001F, 0x0012, 0x0012, 0x002D, 0x000A},
|
||||
CharacterStats{0x0014, 0x0000, 0x001F, 0x0011, 0x0017, 0x002D, 0x000A},
|
||||
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
|
||||
CharacterStats{0x000D, 0x003C, 0x0032, 0x0013, 0x0007, 0x000C, 0x000A},
|
||||
CharacterStats{0x000A, 0x003A, 0x0035, 0x0013, 0x000D, 0x000A, 0x000A},
|
||||
CharacterStats{0x0023, 0x0000, 0x0023, 0x0016, 0x0012, 0x0023, 0x000A},
|
||||
CharacterStats{0x000D, 0x0035, 0x0023, 0x0014, 0x000A, 0x000F, 0x000A},
|
||||
CharacterStats{0x0012, 0x0014, 0x0024, 0x0010, 0x000D, 0x0028, 0x000A},
|
||||
};
|
||||
return data.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
|
||||
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
|
||||
} __packed_ws__(Offsets, 8);
|
||||
|
||||
StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = StringReader(decompressed_data);
|
||||
} else {
|
||||
r = StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_level_deltas = r.pget<parray<LevelStatsDelta, 200>>(level_deltas_offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV4::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
+173
-173
@@ -1,173 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class LevelTable;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct CharacterStatsT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
|
||||
/* 00 */ U16T atp = 0;
|
||||
/* 02 */ U16T mst = 0;
|
||||
/* 04 */ U16T evp = 0;
|
||||
/* 06 */ U16T hp = 0;
|
||||
/* 08 */ U16T dfp = 0;
|
||||
/* 0A */ U16T ata = 0;
|
||||
/* 0C */ U16T lck = 0;
|
||||
/* 0E */
|
||||
|
||||
operator CharacterStatsT<!IsBigEndian>() const {
|
||||
CharacterStatsT<!IsBigEndian> ret;
|
||||
ret.atp = this->atp.load();
|
||||
ret.mst = this->mst.load();
|
||||
ret.evp = this->evp.load();
|
||||
ret.hp = this->hp.load();
|
||||
ret.dfp = this->dfp.load();
|
||||
ret.ata = this->ata.load();
|
||||
ret.lck = this->lck.load();
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using CharacterStats = CharacterStatsT<false>;
|
||||
using CharacterStatsBE = CharacterStatsT<true>;
|
||||
check_struct_size(CharacterStats, 0x0E);
|
||||
check_struct_size(CharacterStatsBE, 0x0E);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct PlayerStatsT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
|
||||
|
||||
/* 00 */ CharacterStatsT<IsBigEndian> char_stats;
|
||||
/* 0E */ U16T esp = 0;
|
||||
/* 10 */ F32T height = 0.0;
|
||||
/* 14 */ F32T unknown_a3 = 0.0;
|
||||
/* 18 */ U32T level = 0;
|
||||
/* 1C */ U32T experience = 0;
|
||||
/* 20 */ U32T meseta = 0;
|
||||
/* 24 */
|
||||
|
||||
operator PlayerStatsT<!IsBigEndian>() const {
|
||||
PlayerStatsT<!IsBigEndian> ret;
|
||||
ret.char_stats = this->char_stats;
|
||||
ret.esp = this->esp.load();
|
||||
ret.height = this->height.load();
|
||||
ret.unknown_a3 = this->unknown_a3.load();
|
||||
ret.level = this->level.load();
|
||||
ret.experience = this->experience.load();
|
||||
ret.meseta = this->meseta.load();
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using PlayerStats = PlayerStatsT<false>;
|
||||
using PlayerStatsBE = PlayerStatsT<true>;
|
||||
check_struct_size(PlayerStats, 0x24);
|
||||
check_struct_size(PlayerStatsBE, 0x24);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct LevelStatsDeltaT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
/* 00 */ uint8_t atp;
|
||||
/* 01 */ uint8_t mst;
|
||||
/* 02 */ uint8_t evp;
|
||||
/* 03 */ uint8_t hp;
|
||||
/* 04 */ uint8_t dfp;
|
||||
/* 05 */ uint8_t ata;
|
||||
/* 06 */ uint8_t lck;
|
||||
/* 07 */ uint8_t tp;
|
||||
/* 08 */ U32T experience;
|
||||
/* 0C */
|
||||
|
||||
void apply(CharacterStats& ps) const {
|
||||
ps.ata += this->ata;
|
||||
ps.atp += this->atp;
|
||||
ps.dfp += this->dfp;
|
||||
ps.evp += this->evp;
|
||||
ps.hp += this->hp;
|
||||
ps.mst += this->mst;
|
||||
ps.lck += this->lck;
|
||||
}
|
||||
} __packed__;
|
||||
using LevelStatsDelta = LevelStatsDeltaT<false>;
|
||||
using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
|
||||
check_struct_size(LevelStatsDelta, 0x0C);
|
||||
check_struct_size(LevelStatsDeltaBE, 0x0C);
|
||||
|
||||
class LevelTable {
|
||||
// This is the base class for all the LevelTable implementations. The public
|
||||
// interface here only defines functions that the server needs to handle
|
||||
// requests, but some subclasses implement more functionality. See the
|
||||
// comments and Offsets structures inside the subclasses' constructor
|
||||
// implementations for more details on the file formats.
|
||||
public:
|
||||
virtual ~LevelTable() = default;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
|
||||
|
||||
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
|
||||
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
|
||||
|
||||
protected:
|
||||
LevelTable() = default;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
public:
|
||||
struct Level100Entry {
|
||||
/* 00 */ CharacterStats char_stats;
|
||||
/* 0E */ le_uint16_t unknown_a1 = 0;
|
||||
/* 10 */ le_float height = 0.0;
|
||||
/* 14 */ le_float unknown_a3 = 0.0;
|
||||
/* 18 */ le_uint32_t level = 0;
|
||||
/* 1C */
|
||||
} __packed_ws__(Level100Entry, 0x1C);
|
||||
|
||||
LevelTableV2(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV2() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
const Level100Entry& level_100_stats_for_class(uint8_t char_class) const;
|
||||
const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<CharacterStats, 9> base_stats;
|
||||
std::array<Level100Entry, 9> level_100_stats;
|
||||
std::array<PlayerStats, 9> max_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
|
||||
public:
|
||||
LevelTableV3BE(const std::string& data, bool encrypted);
|
||||
virtual ~LevelTableV3BE() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
|
||||
public:
|
||||
LevelTableV4(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV4() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<CharacterStats, 12> base_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class LevelTable;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct CharacterStatsT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
|
||||
/* 00 */ U16T atp = 0;
|
||||
/* 02 */ U16T mst = 0;
|
||||
/* 04 */ U16T evp = 0;
|
||||
/* 06 */ U16T hp = 0;
|
||||
/* 08 */ U16T dfp = 0;
|
||||
/* 0A */ U16T ata = 0;
|
||||
/* 0C */ U16T lck = 0;
|
||||
/* 0E */
|
||||
|
||||
operator CharacterStatsT<!IsBigEndian>() const {
|
||||
CharacterStatsT<!IsBigEndian> ret;
|
||||
ret.atp = this->atp.load();
|
||||
ret.mst = this->mst.load();
|
||||
ret.evp = this->evp.load();
|
||||
ret.hp = this->hp.load();
|
||||
ret.dfp = this->dfp.load();
|
||||
ret.ata = this->ata.load();
|
||||
ret.lck = this->lck.load();
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using CharacterStats = CharacterStatsT<false>;
|
||||
using CharacterStatsBE = CharacterStatsT<true>;
|
||||
check_struct_size(CharacterStats, 0x0E);
|
||||
check_struct_size(CharacterStatsBE, 0x0E);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct PlayerStatsT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
|
||||
|
||||
/* 00 */ CharacterStatsT<IsBigEndian> char_stats;
|
||||
/* 0E */ U16T esp = 0;
|
||||
/* 10 */ F32T height = 0.0;
|
||||
/* 14 */ F32T unknown_a3 = 0.0;
|
||||
/* 18 */ U32T level = 0;
|
||||
/* 1C */ U32T experience = 0;
|
||||
/* 20 */ U32T meseta = 0;
|
||||
/* 24 */
|
||||
|
||||
operator PlayerStatsT<!IsBigEndian>() const {
|
||||
PlayerStatsT<!IsBigEndian> ret;
|
||||
ret.char_stats = this->char_stats;
|
||||
ret.esp = this->esp.load();
|
||||
ret.height = this->height.load();
|
||||
ret.unknown_a3 = this->unknown_a3.load();
|
||||
ret.level = this->level.load();
|
||||
ret.experience = this->experience.load();
|
||||
ret.meseta = this->meseta.load();
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using PlayerStats = PlayerStatsT<false>;
|
||||
using PlayerStatsBE = PlayerStatsT<true>;
|
||||
check_struct_size(PlayerStats, 0x24);
|
||||
check_struct_size(PlayerStatsBE, 0x24);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct LevelStatsDeltaT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
/* 00 */ uint8_t atp;
|
||||
/* 01 */ uint8_t mst;
|
||||
/* 02 */ uint8_t evp;
|
||||
/* 03 */ uint8_t hp;
|
||||
/* 04 */ uint8_t dfp;
|
||||
/* 05 */ uint8_t ata;
|
||||
/* 06 */ uint8_t lck;
|
||||
/* 07 */ uint8_t tp;
|
||||
/* 08 */ U32T experience;
|
||||
/* 0C */
|
||||
|
||||
void apply(CharacterStats& ps) const {
|
||||
ps.ata += this->ata;
|
||||
ps.atp += this->atp;
|
||||
ps.dfp += this->dfp;
|
||||
ps.evp += this->evp;
|
||||
ps.hp += this->hp;
|
||||
ps.mst += this->mst;
|
||||
ps.lck += this->lck;
|
||||
}
|
||||
} __packed__;
|
||||
using LevelStatsDelta = LevelStatsDeltaT<false>;
|
||||
using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
|
||||
check_struct_size(LevelStatsDelta, 0x0C);
|
||||
check_struct_size(LevelStatsDeltaBE, 0x0C);
|
||||
|
||||
class LevelTable {
|
||||
// This is the base class for all the LevelTable implementations. The public
|
||||
// interface here only defines functions that the server needs to handle
|
||||
// requests, but some subclasses implement more functionality. See the
|
||||
// comments and Offsets structures inside the subclasses' constructor
|
||||
// implementations for more details on the file formats.
|
||||
public:
|
||||
virtual ~LevelTable() = default;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
|
||||
|
||||
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
|
||||
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
|
||||
|
||||
protected:
|
||||
LevelTable() = default;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
public:
|
||||
struct Level100Entry {
|
||||
/* 00 */ CharacterStats char_stats;
|
||||
/* 0E */ le_uint16_t unknown_a1 = 0;
|
||||
/* 10 */ le_float height = 0.0;
|
||||
/* 14 */ le_float unknown_a3 = 0.0;
|
||||
/* 18 */ le_uint32_t level = 0;
|
||||
/* 1C */
|
||||
} __packed_ws__(Level100Entry, 0x1C);
|
||||
|
||||
LevelTableV2(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV2() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
const Level100Entry& level_100_stats_for_class(uint8_t char_class) const;
|
||||
const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<CharacterStats, 9> base_stats;
|
||||
std::array<Level100Entry, 9> level_100_stats;
|
||||
std::array<PlayerStats, 9> max_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
|
||||
public:
|
||||
LevelTableV3BE(const std::string& data, bool encrypted);
|
||||
virtual ~LevelTableV3BE() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
|
||||
public:
|
||||
LevelTableV4(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV4() = default;
|
||||
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::array<CharacterStats, 12> base_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
+1066
-1066
File diff suppressed because it is too large
Load Diff
+322
-322
@@ -1,322 +1,322 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Server.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "Map.hh"
|
||||
#include "Quest.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
struct ServerState;
|
||||
|
||||
struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
struct FloorItem {
|
||||
ItemData data;
|
||||
float x;
|
||||
float z;
|
||||
uint64_t drop_number;
|
||||
// The low 12 bits of flags are visibility flags, specifying which clients
|
||||
// can see the item. (In practice, only the lowest 4 of these bits are used,
|
||||
// but the game has fields for 12 players so we do too.)
|
||||
// The 13th bit (0x1000) specifies whether a rare item notification should
|
||||
// be sent to all players when the item is picked up. This has no effect for
|
||||
// non-rare items.
|
||||
uint16_t flags;
|
||||
|
||||
bool visible_to_client(uint8_t client_id) const;
|
||||
};
|
||||
struct FloorItemManager {
|
||||
PrefixedLogger log;
|
||||
uint64_t next_drop_number;
|
||||
// It's important that this is a map and not an unordered_map. See the
|
||||
// comment in send_game_item_state for more details.
|
||||
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
|
||||
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
|
||||
|
||||
FloorItemManager(uint32_t lobby_id, uint8_t floor);
|
||||
~FloorItemManager() = default;
|
||||
|
||||
bool exists(uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
|
||||
void add(const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add(std::shared_ptr<FloorItem> fi);
|
||||
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
|
||||
std::unordered_set<std::shared_ptr<FloorItem>> evict();
|
||||
void clear_inaccessible(uint16_t remaining_clients_mask);
|
||||
void clear_private();
|
||||
void clear();
|
||||
uint32_t reassign_all_item_ids(uint32_t next_item_id);
|
||||
};
|
||||
enum class Flag {
|
||||
// clang-format off
|
||||
GAME = 0x00000001,
|
||||
PERSISTENT = 0x00000002,
|
||||
// Flags used only for games
|
||||
CHEATS_ENABLED = 0x00000100,
|
||||
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
|
||||
QUEST_IN_PROGRESS = 0x00000400,
|
||||
BATTLE_IN_PROGRESS = 0x00000800,
|
||||
JOINABLE_QUEST_IN_PROGRESS = 0x00001000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x00002000,
|
||||
IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also
|
||||
SPECTATORS_FORBIDDEN = 0x00008000,
|
||||
START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000,
|
||||
CANNOT_CHANGE_CHEAT_MODE = 0x00020000,
|
||||
USE_CREATOR_SECTION_ID = 0x00040000,
|
||||
// Flags used only for lobbies
|
||||
PUBLIC = 0x01000000,
|
||||
DEFAULT = 0x02000000,
|
||||
IS_OVERFLOW = 0x08000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
CLIENT = 1, // Not allowed for BB games
|
||||
SERVER_SHARED = 2,
|
||||
SERVER_PRIVATE = 3,
|
||||
SERVER_DUPLICATE = 4,
|
||||
};
|
||||
|
||||
std::weak_ptr<ServerState>
|
||||
server_state;
|
||||
PrefixedLogger log;
|
||||
|
||||
uint32_t lobby_id;
|
||||
|
||||
uint32_t min_level;
|
||||
uint32_t max_level;
|
||||
|
||||
// Game state
|
||||
std::array<uint32_t, 12> next_item_id_for_client;
|
||||
uint32_t next_game_item_id;
|
||||
std::vector<FloorItemManager> floor_item_managers;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<Map> map;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
// Bits in allowed_versions specify who is allowed to join this game. The
|
||||
// bits are indexed as (1 << version), where version is a value from the
|
||||
// Version enum.
|
||||
uint16_t allowed_versions;
|
||||
uint8_t creator_section_id;
|
||||
uint8_t override_section_id;
|
||||
Episode episode;
|
||||
GameMode mode;
|
||||
uint8_t difficulty; // 0-3
|
||||
uint16_t base_exp_multiplier;
|
||||
float exp_share_multiplier;
|
||||
float challenge_exp_multiplier;
|
||||
std::string password;
|
||||
std::string name;
|
||||
// This seed is also sent to the client for rare enemy generation
|
||||
uint32_t random_seed;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
uint8_t allowed_drop_modes;
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
|
||||
struct ChallengeParameters {
|
||||
uint8_t stage_number = 0;
|
||||
uint32_t rank_color = 0xFFFFFFFF;
|
||||
std::string rank_text;
|
||||
struct RankThreshold {
|
||||
uint32_t bitmask = 0;
|
||||
uint32_t seconds = 0;
|
||||
};
|
||||
std::array<RankThreshold, 3> rank_thresholds;
|
||||
};
|
||||
std::shared_ptr<ChallengeParameters> challenge_params;
|
||||
|
||||
// Ep3 stuff
|
||||
// There are three kinds of Episode 3 games. All of these types have episode
|
||||
// set to EP3; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
|
||||
// 1. Primary games. These are the lobbies where battles may take place.
|
||||
// 2. Watcher games. These lobbies receive all the battle and chat commands
|
||||
// from a primary game. (This the implementation of spectator teams.)
|
||||
// 3. Replay games. These lobbies replay a sequence of battle commands and
|
||||
// chat commands from a previous primary game.
|
||||
// Types 2 and 3 may be distinguished by the presence of the battle_record
|
||||
// field - in replay games, it will be present; in watcher games it will be
|
||||
// absent.
|
||||
std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games
|
||||
std::weak_ptr<Lobby> watched_lobby; // Only used in watcher games
|
||||
std::unordered_set<std::shared_ptr<Lobby>> watcher_lobbies; // Only used in primary games
|
||||
std::shared_ptr<Episode3::BattleRecord> battle_record; // Not used in watcher games
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
|
||||
std::shared_ptr<Episode3::Tournament::Match> tournament_match;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
|
||||
|
||||
// Lobby stuff
|
||||
uint8_t event;
|
||||
uint8_t block;
|
||||
uint8_t leader_id;
|
||||
uint8_t max_clients;
|
||||
uint32_t enabled_flags;
|
||||
std::shared_ptr<const Quest> quest;
|
||||
std::array<std::shared_ptr<Client>, 12> clients;
|
||||
// Keys in this map are client_id
|
||||
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
|
||||
|
||||
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
|
||||
// is not zero
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
|
||||
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
|
||||
Lobby(const Lobby&) = delete;
|
||||
Lobby(Lobby&&) = delete;
|
||||
~Lobby();
|
||||
Lobby& operator=(const Lobby&) = delete;
|
||||
Lobby& operator=(Lobby&&) = delete;
|
||||
|
||||
void reset_next_item_ids();
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(this->enabled_flags & static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint32_t>(flag);
|
||||
}
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
void create_item_creator();
|
||||
void change_section_id();
|
||||
uint8_t effective_section_id() const;
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const SetDataTableBase> sdt,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
const std::vector<std::string>& enemy_filenames,
|
||||
const std::vector<std::string>& object_filenames,
|
||||
const std::vector<std::string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
void load_maps();
|
||||
void create_ep3_server();
|
||||
|
||||
[[nodiscard]] inline bool is_game() const {
|
||||
return this->check_flag(Flag::GAME);
|
||||
}
|
||||
[[nodiscard]] inline bool is_ep3() const {
|
||||
return this->episode == Episode::EP3;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool version_is_allowed(Version v) const {
|
||||
return this->allowed_versions & (1 << static_cast<size_t>(v));
|
||||
}
|
||||
inline void allow_version(Version v) {
|
||||
this->allowed_versions |= (1 << static_cast<size_t>(v));
|
||||
}
|
||||
|
||||
void reassign_leader_on_client_departure(size_t leaving_client_id);
|
||||
size_t count_clients() const;
|
||||
bool any_v1_clients_present() const;
|
||||
bool any_client_loading() const;
|
||||
|
||||
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
|
||||
void remove_client(std::shared_ptr<Client> c);
|
||||
|
||||
void move_client_to_lobby(
|
||||
std::shared_ptr<Lobby> dest_lobby,
|
||||
std::shared_ptr<Client> c,
|
||||
ssize_t required_client_id = -1);
|
||||
|
||||
std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0);
|
||||
|
||||
enum class JoinError {
|
||||
ALLOWED = 0,
|
||||
FULL,
|
||||
VERSION_CONFLICT,
|
||||
QUEST_SELECTION_IN_PROGRESS,
|
||||
QUEST_IN_PROGRESS,
|
||||
BATTLE_IN_PROGRESS,
|
||||
LOADING,
|
||||
SOLO,
|
||||
INCORRECT_PASSWORD,
|
||||
LEVEL_TOO_LOW,
|
||||
LEVEL_TOO_HIGH,
|
||||
NO_ACCESS_TO_QUEST,
|
||||
};
|
||||
JoinError join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const;
|
||||
|
||||
bool item_exists(uint8_t floor, uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
|
||||
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
|
||||
void evict_items_from_floor(uint8_t floor);
|
||||
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
|
||||
|
||||
uint32_t generate_item_id(uint8_t client_id);
|
||||
void on_item_id_generated_externally(uint32_t item_id);
|
||||
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
|
||||
|
||||
QuestIndex::IncludeCondition quest_include_condition() const;
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
|
||||
|
||||
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
|
||||
};
|
||||
|
||||
template <>
|
||||
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Server.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "Map.hh"
|
||||
#include "Quest.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
struct ServerState;
|
||||
|
||||
struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
struct FloorItem {
|
||||
ItemData data;
|
||||
float x;
|
||||
float z;
|
||||
uint64_t drop_number;
|
||||
// The low 12 bits of flags are visibility flags, specifying which clients
|
||||
// can see the item. (In practice, only the lowest 4 of these bits are used,
|
||||
// but the game has fields for 12 players so we do too.)
|
||||
// The 13th bit (0x1000) specifies whether a rare item notification should
|
||||
// be sent to all players when the item is picked up. This has no effect for
|
||||
// non-rare items.
|
||||
uint16_t flags;
|
||||
|
||||
bool visible_to_client(uint8_t client_id) const;
|
||||
};
|
||||
struct FloorItemManager {
|
||||
PrefixedLogger log;
|
||||
uint64_t next_drop_number;
|
||||
// It's important that this is a map and not an unordered_map. See the
|
||||
// comment in send_game_item_state for more details.
|
||||
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
|
||||
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
|
||||
|
||||
FloorItemManager(uint32_t lobby_id, uint8_t floor);
|
||||
~FloorItemManager() = default;
|
||||
|
||||
bool exists(uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
|
||||
void add(const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add(std::shared_ptr<FloorItem> fi);
|
||||
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
|
||||
std::unordered_set<std::shared_ptr<FloorItem>> evict();
|
||||
void clear_inaccessible(uint16_t remaining_clients_mask);
|
||||
void clear_private();
|
||||
void clear();
|
||||
uint32_t reassign_all_item_ids(uint32_t next_item_id);
|
||||
};
|
||||
enum class Flag {
|
||||
// clang-format off
|
||||
GAME = 0x00000001,
|
||||
PERSISTENT = 0x00000002,
|
||||
// Flags used only for games
|
||||
CHEATS_ENABLED = 0x00000100,
|
||||
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
|
||||
QUEST_IN_PROGRESS = 0x00000400,
|
||||
BATTLE_IN_PROGRESS = 0x00000800,
|
||||
JOINABLE_QUEST_IN_PROGRESS = 0x00001000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x00002000,
|
||||
IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also
|
||||
SPECTATORS_FORBIDDEN = 0x00008000,
|
||||
START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000,
|
||||
CANNOT_CHANGE_CHEAT_MODE = 0x00020000,
|
||||
USE_CREATOR_SECTION_ID = 0x00040000,
|
||||
// Flags used only for lobbies
|
||||
PUBLIC = 0x01000000,
|
||||
DEFAULT = 0x02000000,
|
||||
IS_OVERFLOW = 0x08000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
CLIENT = 1, // Not allowed for BB games
|
||||
SERVER_SHARED = 2,
|
||||
SERVER_PRIVATE = 3,
|
||||
SERVER_DUPLICATE = 4,
|
||||
};
|
||||
|
||||
std::weak_ptr<ServerState>
|
||||
server_state;
|
||||
PrefixedLogger log;
|
||||
|
||||
uint32_t lobby_id;
|
||||
|
||||
uint32_t min_level;
|
||||
uint32_t max_level;
|
||||
|
||||
// Game state
|
||||
std::array<uint32_t, 12> next_item_id_for_client;
|
||||
uint32_t next_game_item_id;
|
||||
std::vector<FloorItemManager> floor_item_managers;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<Map> map;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
// Bits in allowed_versions specify who is allowed to join this game. The
|
||||
// bits are indexed as (1 << version), where version is a value from the
|
||||
// Version enum.
|
||||
uint16_t allowed_versions;
|
||||
uint8_t creator_section_id;
|
||||
uint8_t override_section_id;
|
||||
Episode episode;
|
||||
GameMode mode;
|
||||
uint8_t difficulty; // 0-3
|
||||
uint16_t base_exp_multiplier;
|
||||
float exp_share_multiplier;
|
||||
float challenge_exp_multiplier;
|
||||
std::string password;
|
||||
std::string name;
|
||||
// This seed is also sent to the client for rare enemy generation
|
||||
uint32_t random_seed;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
uint8_t allowed_drop_modes;
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
|
||||
struct ChallengeParameters {
|
||||
uint8_t stage_number = 0;
|
||||
uint32_t rank_color = 0xFFFFFFFF;
|
||||
std::string rank_text;
|
||||
struct RankThreshold {
|
||||
uint32_t bitmask = 0;
|
||||
uint32_t seconds = 0;
|
||||
};
|
||||
std::array<RankThreshold, 3> rank_thresholds;
|
||||
};
|
||||
std::shared_ptr<ChallengeParameters> challenge_params;
|
||||
|
||||
// Ep3 stuff
|
||||
// There are three kinds of Episode 3 games. All of these types have episode
|
||||
// set to EP3; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
|
||||
// 1. Primary games. These are the lobbies where battles may take place.
|
||||
// 2. Watcher games. These lobbies receive all the battle and chat commands
|
||||
// from a primary game. (This the implementation of spectator teams.)
|
||||
// 3. Replay games. These lobbies replay a sequence of battle commands and
|
||||
// chat commands from a previous primary game.
|
||||
// Types 2 and 3 may be distinguished by the presence of the battle_record
|
||||
// field - in replay games, it will be present; in watcher games it will be
|
||||
// absent.
|
||||
std::shared_ptr<Episode3::Server> ep3_server; // Only used in primary games
|
||||
std::weak_ptr<Lobby> watched_lobby; // Only used in watcher games
|
||||
std::unordered_set<std::shared_ptr<Lobby>> watcher_lobbies; // Only used in primary games
|
||||
std::shared_ptr<Episode3::BattleRecord> battle_record; // Not used in watcher games
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
|
||||
std::shared_ptr<Episode3::Tournament::Match> tournament_match;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
|
||||
|
||||
// Lobby stuff
|
||||
uint8_t event;
|
||||
uint8_t block;
|
||||
uint8_t leader_id;
|
||||
uint8_t max_clients;
|
||||
uint32_t enabled_flags;
|
||||
std::shared_ptr<const Quest> quest;
|
||||
std::array<std::shared_ptr<Client>, 12> clients;
|
||||
// Keys in this map are client_id
|
||||
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
|
||||
|
||||
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
|
||||
// is not zero
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
|
||||
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
|
||||
Lobby(const Lobby&) = delete;
|
||||
Lobby(Lobby&&) = delete;
|
||||
~Lobby();
|
||||
Lobby& operator=(const Lobby&) = delete;
|
||||
Lobby& operator=(Lobby&&) = delete;
|
||||
|
||||
void reset_next_item_ids();
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(this->enabled_flags & static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint32_t>(flag);
|
||||
}
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
void create_item_creator();
|
||||
void change_section_id();
|
||||
uint8_t effective_section_id() const;
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const SetDataTableBase> sdt,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
const std::vector<std::string>& enemy_filenames,
|
||||
const std::vector<std::string>& object_filenames,
|
||||
const std::vector<std::string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const PrefixedLogger* log = nullptr);
|
||||
void load_maps();
|
||||
void create_ep3_server();
|
||||
|
||||
[[nodiscard]] inline bool is_game() const {
|
||||
return this->check_flag(Flag::GAME);
|
||||
}
|
||||
[[nodiscard]] inline bool is_ep3() const {
|
||||
return this->episode == Episode::EP3;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool version_is_allowed(Version v) const {
|
||||
return this->allowed_versions & (1 << static_cast<size_t>(v));
|
||||
}
|
||||
inline void allow_version(Version v) {
|
||||
this->allowed_versions |= (1 << static_cast<size_t>(v));
|
||||
}
|
||||
|
||||
void reassign_leader_on_client_departure(size_t leaving_client_id);
|
||||
size_t count_clients() const;
|
||||
bool any_v1_clients_present() const;
|
||||
bool any_client_loading() const;
|
||||
|
||||
void add_client(std::shared_ptr<Client> c, ssize_t required_client_id = -1);
|
||||
void remove_client(std::shared_ptr<Client> c);
|
||||
|
||||
void move_client_to_lobby(
|
||||
std::shared_ptr<Lobby> dest_lobby,
|
||||
std::shared_ptr<Client> c,
|
||||
ssize_t required_client_id = -1);
|
||||
|
||||
std::shared_ptr<Client> find_client(const std::string* identifier = nullptr, uint64_t account_id = 0);
|
||||
|
||||
enum class JoinError {
|
||||
ALLOWED = 0,
|
||||
FULL,
|
||||
VERSION_CONFLICT,
|
||||
QUEST_SELECTION_IN_PROGRESS,
|
||||
QUEST_IN_PROGRESS,
|
||||
BATTLE_IN_PROGRESS,
|
||||
LOADING,
|
||||
SOLO,
|
||||
INCORRECT_PASSWORD,
|
||||
LEVEL_TOO_LOW,
|
||||
LEVEL_TOO_HIGH,
|
||||
NO_ACCESS_TO_QUEST,
|
||||
};
|
||||
JoinError join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const;
|
||||
|
||||
bool item_exists(uint8_t floor, uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
|
||||
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
|
||||
void evict_items_from_floor(uint8_t floor);
|
||||
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
|
||||
|
||||
uint32_t generate_item_id(uint8_t client_id);
|
||||
void on_item_id_generated_externally(uint32_t item_id);
|
||||
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
|
||||
|
||||
QuestIndex::IncludeCondition quest_include_condition() const;
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
|
||||
|
||||
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
|
||||
};
|
||||
|
||||
template <>
|
||||
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
|
||||
|
||||
+48
-48
@@ -1,48 +1,48 @@
|
||||
#include "Loggers.hh"
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger client_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
|
||||
|
||||
static void set_log_level_from_json(
|
||||
PrefixedLogger& log, const JSON& d, const char* json_key) {
|
||||
try {
|
||||
string name = toupper(d.at(json_key).as_string());
|
||||
log.min_level = enum_for_name<LogLevel>(name.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
void set_log_levels_from_json(const JSON& json) {
|
||||
set_log_level_from_json(ax_messages_log, json, "AXMessages");
|
||||
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
|
||||
set_log_level_from_json(client_log, json, "Clients");
|
||||
set_log_level_from_json(command_data_log, json, "CommandData");
|
||||
set_log_level_from_json(config_log, json, "Config");
|
||||
set_log_level_from_json(dns_server_log, json, "DNSServer");
|
||||
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
|
||||
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
|
||||
set_log_level_from_json(lobby_log, json, "Lobbies");
|
||||
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
|
||||
set_log_level_from_json(player_data_log, json, "PlayerData");
|
||||
set_log_level_from_json(proxy_server_log, json, "ProxyServer");
|
||||
set_log_level_from_json(replay_log, json, "Replay");
|
||||
set_log_level_from_json(server_log, json, "GameServer");
|
||||
set_log_level_from_json(static_game_data_log, json, "StaticGameData");
|
||||
}
|
||||
#include "Loggers.hh"
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
PrefixedLogger ax_messages_log("[$ax message] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger channel_exceptions_log("[Channel] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger client_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger command_data_log("[Commands] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger proxy_server_log("[ProxyServer] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
|
||||
|
||||
static void set_log_level_from_json(
|
||||
PrefixedLogger& log, const JSON& d, const char* json_key) {
|
||||
try {
|
||||
string name = toupper(d.at(json_key).as_string());
|
||||
log.min_level = enum_for_name<LogLevel>(name.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
void set_log_levels_from_json(const JSON& json) {
|
||||
set_log_level_from_json(ax_messages_log, json, "AXMessages");
|
||||
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
|
||||
set_log_level_from_json(client_log, json, "Clients");
|
||||
set_log_level_from_json(command_data_log, json, "CommandData");
|
||||
set_log_level_from_json(config_log, json, "Config");
|
||||
set_log_level_from_json(dns_server_log, json, "DNSServer");
|
||||
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
|
||||
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
|
||||
set_log_level_from_json(lobby_log, json, "Lobbies");
|
||||
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
|
||||
set_log_level_from_json(player_data_log, json, "PlayerData");
|
||||
set_log_level_from_json(proxy_server_log, json, "ProxyServer");
|
||||
set_log_level_from_json(replay_log, json, "Replay");
|
||||
set_log_level_from_json(server_log, json, "GameServer");
|
||||
set_log_level_from_json(static_game_data_log, json, "StaticGameData");
|
||||
}
|
||||
|
||||
+22
-22
@@ -1,22 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
extern PrefixedLogger ax_messages_log;
|
||||
extern PrefixedLogger channel_exceptions_log;
|
||||
extern PrefixedLogger client_log;
|
||||
extern PrefixedLogger command_data_log;
|
||||
extern PrefixedLogger config_log;
|
||||
extern PrefixedLogger dns_server_log;
|
||||
extern PrefixedLogger function_compiler_log;
|
||||
extern PrefixedLogger ip_stack_simulator_log;
|
||||
extern PrefixedLogger lobby_log;
|
||||
extern PrefixedLogger patch_index_log;
|
||||
extern PrefixedLogger player_data_log;
|
||||
extern PrefixedLogger proxy_server_log;
|
||||
extern PrefixedLogger replay_log;
|
||||
extern PrefixedLogger server_log;
|
||||
extern PrefixedLogger static_game_data_log;
|
||||
|
||||
void set_log_levels_from_json(const JSON& json);
|
||||
#pragma once
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
extern PrefixedLogger ax_messages_log;
|
||||
extern PrefixedLogger channel_exceptions_log;
|
||||
extern PrefixedLogger client_log;
|
||||
extern PrefixedLogger command_data_log;
|
||||
extern PrefixedLogger config_log;
|
||||
extern PrefixedLogger dns_server_log;
|
||||
extern PrefixedLogger function_compiler_log;
|
||||
extern PrefixedLogger ip_stack_simulator_log;
|
||||
extern PrefixedLogger lobby_log;
|
||||
extern PrefixedLogger patch_index_log;
|
||||
extern PrefixedLogger player_data_log;
|
||||
extern PrefixedLogger proxy_server_log;
|
||||
extern PrefixedLogger replay_log;
|
||||
extern PrefixedLogger server_log;
|
||||
extern PrefixedLogger static_game_data_log;
|
||||
|
||||
void set_log_levels_from_json(const JSON& json);
|
||||
|
||||
+2820
-2820
File diff suppressed because it is too large
Load Diff
+2567
-2567
File diff suppressed because it is too large
Load Diff
+494
-494
@@ -1,494 +1,494 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
struct Map {
|
||||
static const char* name_for_object_type(uint16_t type);
|
||||
|
||||
struct SectionHeader {
|
||||
enum class Type {
|
||||
END = 0,
|
||||
OBJECTS = 1,
|
||||
ENEMIES = 2,
|
||||
WAVE_EVENTS = 3,
|
||||
RANDOM_ENEMY_LOCATIONS = 4,
|
||||
RANDOM_ENEMY_DEFINITIONS = 5,
|
||||
};
|
||||
le_uint32_t le_type;
|
||||
le_uint32_t section_size; // Includes this header
|
||||
le_uint32_t floor;
|
||||
le_uint32_t data_size;
|
||||
|
||||
inline Type type() const {
|
||||
return static_cast<Type>(this->le_type.load());
|
||||
}
|
||||
} __packed_ws__(SectionHeader, 0x10);
|
||||
|
||||
struct ObjectEntry { // Section type 1 (OBJECTS)
|
||||
/* 00 */ le_uint16_t base_type;
|
||||
/* 02 */ le_uint16_t flags;
|
||||
/* 04 */ le_uint16_t index;
|
||||
/* 06 */ le_uint16_t unknown_a2;
|
||||
/* 08 */ le_uint16_t entity_id; // == index + 0x4000
|
||||
/* 0A */ le_uint16_t group;
|
||||
/* 0C */ le_uint16_t section;
|
||||
/* 0E */ le_uint16_t unknown_a3;
|
||||
/* 10 */ le_float x;
|
||||
/* 14 */ le_float y;
|
||||
/* 18 */ le_float z;
|
||||
/* 1C */ le_uint32_t x_angle;
|
||||
/* 20 */ le_uint32_t y_angle;
|
||||
/* 24 */ le_uint32_t z_angle;
|
||||
/* 28 */ le_float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
/* 2C */ le_float param2;
|
||||
/* 30 */ le_float param3;
|
||||
/* 34 */ le_uint32_t param4;
|
||||
/* 38 */ le_uint32_t param5;
|
||||
/* 3C */ le_uint32_t param6;
|
||||
/* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
|
||||
/* 44 */
|
||||
|
||||
std::string str() const;
|
||||
} __packed_ws__(ObjectEntry, 0x44);
|
||||
|
||||
struct EnemyEntry { // Section type 2 (ENEMIES)
|
||||
/* 00 */ le_uint16_t base_type;
|
||||
/* 02 */ le_uint16_t flags;
|
||||
/* 04 */ le_uint16_t index;
|
||||
/* 06 */ le_uint16_t num_children;
|
||||
/* 08 */ le_uint16_t floor;
|
||||
/* 0A */ le_uint16_t entity_id; // == index + 0x1000
|
||||
/* 0C */ le_uint16_t section;
|
||||
/* 0E */ le_uint16_t wave_number;
|
||||
/* 10 */ le_uint16_t wave_number2;
|
||||
/* 12 */ le_uint16_t unknown_a1;
|
||||
/* 14 */ le_float x;
|
||||
/* 18 */ le_float y;
|
||||
/* 1C */ le_float z;
|
||||
/* 20 */ le_uint32_t x_angle;
|
||||
/* 24 */ le_uint32_t y_angle;
|
||||
/* 28 */ le_uint32_t z_angle;
|
||||
/* 2C */ le_float fparam1;
|
||||
/* 30 */ le_float fparam2;
|
||||
/* 34 */ le_float fparam3;
|
||||
/* 38 */ le_float fparam4;
|
||||
/* 3C */ le_float fparam5;
|
||||
/* 40 */ le_uint16_t uparam1;
|
||||
/* 42 */ le_uint16_t uparam2;
|
||||
/* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
|
||||
/* 48 */
|
||||
|
||||
std::string str() const;
|
||||
} __packed_ws__(EnemyEntry, 0x48);
|
||||
|
||||
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
|
||||
/* 00 */ le_uint32_t action_stream_offset;
|
||||
/* 04 */ le_uint32_t entries_offset;
|
||||
/* 08 */ le_uint32_t entry_count;
|
||||
/* 0C */ be_uint32_t format; // 0 or 'evt2'
|
||||
/* 10 */
|
||||
} __packed_ws__(EventsSectionHeader, 0x10);
|
||||
|
||||
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
// Bits in flags:
|
||||
// 0004 = is active
|
||||
// 0008 = post-wave actions have been run
|
||||
// 0010 = all enemies killed
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
/* 0A */ le_uint16_t wave_number;
|
||||
/* 0C */ le_uint32_t delay;
|
||||
/* 10 */ le_uint32_t action_stream_offset;
|
||||
/* 14 */
|
||||
} __packed_ws__(Event1Entry, 0x14);
|
||||
|
||||
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
/* 0A */ le_uint16_t wave_number;
|
||||
/* 0C */ le_uint16_t min_delay;
|
||||
/* 0E */ le_uint16_t max_delay;
|
||||
/* 10 */ uint8_t min_enemies;
|
||||
/* 11 */ uint8_t max_enemies;
|
||||
/* 12 */ le_uint16_t max_waves;
|
||||
/* 14 */ le_uint32_t action_stream_offset;
|
||||
/* 18 */
|
||||
} __packed_ws__(Event2Entry, 0x18);
|
||||
|
||||
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct
|
||||
/* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
|
||||
/* 08 */ le_uint32_t num_sections;
|
||||
/* 0C */
|
||||
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
|
||||
|
||||
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_uint16_t section;
|
||||
/* 02 */ le_uint16_t count;
|
||||
/* 04 */ le_uint32_t offset;
|
||||
/* 08 */
|
||||
} __packed_ws__(RandomEnemyLocationSection, 8);
|
||||
|
||||
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_float x;
|
||||
/* 04 */ le_float y;
|
||||
/* 08 */ le_float z;
|
||||
/* 0C */ le_uint32_t x_angle;
|
||||
/* 10 */ le_uint32_t y_angle;
|
||||
/* 14 */ le_uint32_t z_angle;
|
||||
/* 18 */ uint16_t unknown_a9;
|
||||
/* 1A */ uint16_t unknown_a10;
|
||||
/* 1C */
|
||||
} __packed_ws__(RandomEnemyLocationEntry, 0x1C);
|
||||
|
||||
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct
|
||||
/* 04 */ le_uint32_t weight_entries_offset; // Offset to RandomEnemyDefinitionWeights structs, from start of this struct
|
||||
/* 08 */ le_uint32_t entry_count;
|
||||
/* 0C */ le_uint32_t weight_entry_count;
|
||||
/* 10 */
|
||||
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
|
||||
|
||||
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
// All fields through entry_num map to the corresponding fields in
|
||||
// EnemyEntry. Note that the order of the uparam fields is switched!
|
||||
/* 00 */ le_float fparam1;
|
||||
/* 04 */ le_float fparam2;
|
||||
/* 08 */ le_float fparam3;
|
||||
/* 0C */ le_float fparam4;
|
||||
/* 10 */ le_float fparam5;
|
||||
/* 14 */ le_uint16_t uparam2;
|
||||
/* 16 */ le_uint16_t uparam1;
|
||||
/* 18 */ le_uint32_t entry_num;
|
||||
/* 1C */ le_uint16_t min_children;
|
||||
/* 1E */ le_uint16_t max_children;
|
||||
/* 20 */
|
||||
} __packed_ws__(RandomEnemyDefinition, 0x20);
|
||||
|
||||
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
/* 00 */ uint8_t base_type_index;
|
||||
/* 01 */ uint8_t definition_entry_num;
|
||||
/* 02 */ uint8_t weight;
|
||||
/* 03 */ uint8_t unknown_a4;
|
||||
/* 04 */
|
||||
} __packed_ws__(RandomEnemyWeight, 4);
|
||||
|
||||
struct RareEnemyRates {
|
||||
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
|
||||
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
|
||||
uint32_t nar_lily; // POISON_LILY -> NAR_LILY
|
||||
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
|
||||
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
|
||||
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
|
||||
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
|
||||
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
|
||||
|
||||
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
|
||||
explicit RareEnemyRates(const JSON& json);
|
||||
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
|
||||
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
|
||||
|
||||
struct Object {
|
||||
// TODO: Add more fields in here if we ever care about them. Currently we
|
||||
// only care about boxes with fixed item drops.
|
||||
size_t source_index;
|
||||
uint8_t floor;
|
||||
uint16_t object_id;
|
||||
uint16_t base_type;
|
||||
uint16_t section;
|
||||
uint16_t group;
|
||||
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
float param3; // If == 0, the item should be varied by difficulty and area
|
||||
uint32_t param4;
|
||||
uint32_t param5;
|
||||
uint32_t param6;
|
||||
uint16_t game_flags;
|
||||
// Technically set_flags shouldn't be part of the Object struct, but all
|
||||
// object entries always generate exactly one object, so we store it here.
|
||||
uint16_t set_flags;
|
||||
bool item_drop_checked;
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct Enemy {
|
||||
enum Flag {
|
||||
LAST_HIT_MASK = 0x03,
|
||||
EXP_GIVEN = 0x04,
|
||||
ITEM_DROPPED = 0x08,
|
||||
ALL_HITS_MASK_FIRST = 0x10,
|
||||
ALL_HITS_MASK = 0xF0,
|
||||
};
|
||||
size_t source_index;
|
||||
size_t set_index;
|
||||
uint16_t enemy_id;
|
||||
uint16_t total_damage;
|
||||
uint32_t game_flags; // From 6x0A
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
EnemyType type;
|
||||
uint8_t floor;
|
||||
uint8_t server_flags;
|
||||
|
||||
Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type);
|
||||
|
||||
std::string str() const;
|
||||
|
||||
inline bool ever_hit_by_client_id(uint8_t client_id) const {
|
||||
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
|
||||
}
|
||||
inline bool last_hit_by_client_id(uint8_t client_id) const {
|
||||
return (this->server_flags & Flag::LAST_HIT_MASK) == client_id;
|
||||
}
|
||||
inline void set_last_hit_by_client_id(uint8_t client_id) {
|
||||
this->server_flags = (this->server_flags & (~Flag::LAST_HIT_MASK)) | (Flag::ALL_HITS_MASK_FIRST << client_id) | (client_id & 3);
|
||||
}
|
||||
};
|
||||
|
||||
struct Event {
|
||||
uint32_t event_id;
|
||||
uint16_t flags;
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
uint8_t floor;
|
||||
uint32_t action_stream_offset;
|
||||
std::vector<size_t> enemy_indexes;
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
PSOV2Encryption location_table_random;
|
||||
std::array<uint32_t, 0x20> location_index_table;
|
||||
uint32_t location_indexes_populated;
|
||||
uint32_t location_indexes_used;
|
||||
uint32_t location_entries_base_offset;
|
||||
|
||||
DATParserRandomState(uint32_t rare_seed);
|
||||
size_t rand_int_biased(size_t min_v, size_t max_v);
|
||||
uint32_t next_location_index();
|
||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
||||
};
|
||||
|
||||
Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
~Map() = default;
|
||||
|
||||
void clear();
|
||||
|
||||
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
|
||||
bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
|
||||
void add_enemy(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
size_t index,
|
||||
const EnemyEntry& e,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
void add_enemies_from_map_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
const void* data,
|
||||
size_t size,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
void add_random_enemies_from_map_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
StringReader wave_events_r,
|
||||
StringReader random_enemy_locations_r,
|
||||
StringReader random_enemy_definitions_r,
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
|
||||
void add_event(
|
||||
uint32_t event_id,
|
||||
uint16_t flags,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
uint32_t action_stream_offset);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint32_t event_id);
|
||||
std::vector<const Event*> get_events(uint8_t floor, uint32_t event_id) const;
|
||||
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
|
||||
struct DATSectionsForFloor {
|
||||
uint32_t objects = 0xFFFFFFFF;
|
||||
uint32_t enemies = 0xFFFFFFFF;
|
||||
uint32_t wave_events = 0xFFFFFFFF;
|
||||
uint32_t random_enemy_locations = 0xFFFFFFFF;
|
||||
uint32_t random_enemy_definitions = 0xFFFFFFFF;
|
||||
};
|
||||
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
|
||||
|
||||
void add_entities_from_quest_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
const void* data,
|
||||
size_t size,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
|
||||
|
||||
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
|
||||
Enemy& find_enemy(uint8_t floor, EnemyType type);
|
||||
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor);
|
||||
|
||||
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
|
||||
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
|
||||
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
Version version;
|
||||
uint32_t rare_seed;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::vector<Object> objects;
|
||||
std::vector<Enemy> enemies;
|
||||
std::vector<uint16_t> enemy_set_flags;
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
std::vector<Event> events;
|
||||
std::string event_action_stream;
|
||||
std::multimap<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
|
||||
};
|
||||
|
||||
class SetDataTableBase {
|
||||
public:
|
||||
virtual ~SetDataTableBase() = default;
|
||||
|
||||
parray<le_uint32_t, 0x20> generate_variations(
|
||||
Episode episode,
|
||||
bool is_solo,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
||||
|
||||
enum class FilenameType {
|
||||
OBJECTS = 0,
|
||||
ENEMIES,
|
||||
EVENTS,
|
||||
};
|
||||
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const = 0;
|
||||
std::vector<std::string> map_filenames_for_variations(
|
||||
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
|
||||
|
||||
protected:
|
||||
explicit SetDataTableBase(Version version);
|
||||
|
||||
Version version;
|
||||
};
|
||||
|
||||
class SetDataTable : public SetDataTableBase {
|
||||
public:
|
||||
struct SetEntry {
|
||||
std::string object_list_basename;
|
||||
std::string enemy_and_event_list_basename;
|
||||
std::string area_setup_filename;
|
||||
};
|
||||
|
||||
SetDataTable(Version version, const std::string& data);
|
||||
virtual ~SetDataTable() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void load_table_t(const std::string& data);
|
||||
|
||||
// Indexes are [floor][variation1][variation2]
|
||||
// floor is cumulative per episode, so Ep2 starts at floor=18.
|
||||
std::vector<std::vector<std::vector<SetEntry>>> entries;
|
||||
};
|
||||
|
||||
class SetDataTableDCNTE : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDCNTE();
|
||||
virtual ~SetDataTableDCNTE() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
|
||||
};
|
||||
|
||||
class SetDataTableDC112000 : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDC112000();
|
||||
virtual ~SetDataTableDC112000() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
|
||||
};
|
||||
|
||||
void generate_variations_deprecated(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
std::shared_ptr<PSOLFGEncryption> random,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo);
|
||||
|
||||
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo);
|
||||
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo);
|
||||
|
||||
std::vector<std::string> map_filenames_for_variation_deprecated(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Version version, Episode episode, GameMode mode, bool is_enemies);
|
||||
std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool is_enemies);
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
struct Map {
|
||||
static const char* name_for_object_type(uint16_t type);
|
||||
|
||||
struct SectionHeader {
|
||||
enum class Type {
|
||||
END = 0,
|
||||
OBJECTS = 1,
|
||||
ENEMIES = 2,
|
||||
WAVE_EVENTS = 3,
|
||||
RANDOM_ENEMY_LOCATIONS = 4,
|
||||
RANDOM_ENEMY_DEFINITIONS = 5,
|
||||
};
|
||||
le_uint32_t le_type;
|
||||
le_uint32_t section_size; // Includes this header
|
||||
le_uint32_t floor;
|
||||
le_uint32_t data_size;
|
||||
|
||||
inline Type type() const {
|
||||
return static_cast<Type>(this->le_type.load());
|
||||
}
|
||||
} __packed_ws__(SectionHeader, 0x10);
|
||||
|
||||
struct ObjectEntry { // Section type 1 (OBJECTS)
|
||||
/* 00 */ le_uint16_t base_type;
|
||||
/* 02 */ le_uint16_t flags;
|
||||
/* 04 */ le_uint16_t index;
|
||||
/* 06 */ le_uint16_t unknown_a2;
|
||||
/* 08 */ le_uint16_t entity_id; // == index + 0x4000
|
||||
/* 0A */ le_uint16_t group;
|
||||
/* 0C */ le_uint16_t section;
|
||||
/* 0E */ le_uint16_t unknown_a3;
|
||||
/* 10 */ le_float x;
|
||||
/* 14 */ le_float y;
|
||||
/* 18 */ le_float z;
|
||||
/* 1C */ le_uint32_t x_angle;
|
||||
/* 20 */ le_uint32_t y_angle;
|
||||
/* 24 */ le_uint32_t z_angle;
|
||||
/* 28 */ le_float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
/* 2C */ le_float param2;
|
||||
/* 30 */ le_float param3;
|
||||
/* 34 */ le_uint32_t param4;
|
||||
/* 38 */ le_uint32_t param5;
|
||||
/* 3C */ le_uint32_t param6;
|
||||
/* 40 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
|
||||
/* 44 */
|
||||
|
||||
std::string str() const;
|
||||
} __packed_ws__(ObjectEntry, 0x44);
|
||||
|
||||
struct EnemyEntry { // Section type 2 (ENEMIES)
|
||||
/* 00 */ le_uint16_t base_type;
|
||||
/* 02 */ le_uint16_t flags;
|
||||
/* 04 */ le_uint16_t index;
|
||||
/* 06 */ le_uint16_t num_children;
|
||||
/* 08 */ le_uint16_t floor;
|
||||
/* 0A */ le_uint16_t entity_id; // == index + 0x1000
|
||||
/* 0C */ le_uint16_t section;
|
||||
/* 0E */ le_uint16_t wave_number;
|
||||
/* 10 */ le_uint16_t wave_number2;
|
||||
/* 12 */ le_uint16_t unknown_a1;
|
||||
/* 14 */ le_float x;
|
||||
/* 18 */ le_float y;
|
||||
/* 1C */ le_float z;
|
||||
/* 20 */ le_uint32_t x_angle;
|
||||
/* 24 */ le_uint32_t y_angle;
|
||||
/* 28 */ le_uint32_t z_angle;
|
||||
/* 2C */ le_float fparam1;
|
||||
/* 30 */ le_float fparam2;
|
||||
/* 34 */ le_float fparam3;
|
||||
/* 38 */ le_float fparam4;
|
||||
/* 3C */ le_float fparam5;
|
||||
/* 40 */ le_uint16_t uparam1;
|
||||
/* 42 */ le_uint16_t uparam2;
|
||||
/* 44 */ le_uint32_t unused; // Reserved for pointer in client's memory; unused by server
|
||||
/* 48 */
|
||||
|
||||
std::string str() const;
|
||||
} __packed_ws__(EnemyEntry, 0x48);
|
||||
|
||||
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
|
||||
/* 00 */ le_uint32_t action_stream_offset;
|
||||
/* 04 */ le_uint32_t entries_offset;
|
||||
/* 08 */ le_uint32_t entry_count;
|
||||
/* 0C */ be_uint32_t format; // 0 or 'evt2'
|
||||
/* 10 */
|
||||
} __packed_ws__(EventsSectionHeader, 0x10);
|
||||
|
||||
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
// Bits in flags:
|
||||
// 0004 = is active
|
||||
// 0008 = post-wave actions have been run
|
||||
// 0010 = all enemies killed
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
/* 0A */ le_uint16_t wave_number;
|
||||
/* 0C */ le_uint32_t delay;
|
||||
/* 10 */ le_uint32_t action_stream_offset;
|
||||
/* 14 */
|
||||
} __packed_ws__(Event1Entry, 0x14);
|
||||
|
||||
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
|
||||
/* 00 */ le_uint32_t event_id;
|
||||
/* 04 */ le_uint16_t flags;
|
||||
/* 06 */ le_uint16_t event_type;
|
||||
/* 08 */ le_uint16_t section;
|
||||
/* 0A */ le_uint16_t wave_number;
|
||||
/* 0C */ le_uint16_t min_delay;
|
||||
/* 0E */ le_uint16_t max_delay;
|
||||
/* 10 */ uint8_t min_enemies;
|
||||
/* 11 */ uint8_t max_enemies;
|
||||
/* 12 */ le_uint16_t max_waves;
|
||||
/* 14 */ le_uint32_t action_stream_offset;
|
||||
/* 18 */
|
||||
} __packed_ws__(Event2Entry, 0x18);
|
||||
|
||||
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct
|
||||
/* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
|
||||
/* 08 */ le_uint32_t num_sections;
|
||||
/* 0C */
|
||||
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
|
||||
|
||||
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_uint16_t section;
|
||||
/* 02 */ le_uint16_t count;
|
||||
/* 04 */ le_uint32_t offset;
|
||||
/* 08 */
|
||||
} __packed_ws__(RandomEnemyLocationSection, 8);
|
||||
|
||||
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
|
||||
/* 00 */ le_float x;
|
||||
/* 04 */ le_float y;
|
||||
/* 08 */ le_float z;
|
||||
/* 0C */ le_uint32_t x_angle;
|
||||
/* 10 */ le_uint32_t y_angle;
|
||||
/* 14 */ le_uint32_t z_angle;
|
||||
/* 18 */ uint16_t unknown_a9;
|
||||
/* 1A */ uint16_t unknown_a10;
|
||||
/* 1C */
|
||||
} __packed_ws__(RandomEnemyLocationEntry, 0x1C);
|
||||
|
||||
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct
|
||||
/* 04 */ le_uint32_t weight_entries_offset; // Offset to RandomEnemyDefinitionWeights structs, from start of this struct
|
||||
/* 08 */ le_uint32_t entry_count;
|
||||
/* 0C */ le_uint32_t weight_entry_count;
|
||||
/* 10 */
|
||||
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
|
||||
|
||||
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
// All fields through entry_num map to the corresponding fields in
|
||||
// EnemyEntry. Note that the order of the uparam fields is switched!
|
||||
/* 00 */ le_float fparam1;
|
||||
/* 04 */ le_float fparam2;
|
||||
/* 08 */ le_float fparam3;
|
||||
/* 0C */ le_float fparam4;
|
||||
/* 10 */ le_float fparam5;
|
||||
/* 14 */ le_uint16_t uparam2;
|
||||
/* 16 */ le_uint16_t uparam1;
|
||||
/* 18 */ le_uint32_t entry_num;
|
||||
/* 1C */ le_uint16_t min_children;
|
||||
/* 1E */ le_uint16_t max_children;
|
||||
/* 20 */
|
||||
} __packed_ws__(RandomEnemyDefinition, 0x20);
|
||||
|
||||
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
|
||||
/* 00 */ uint8_t base_type_index;
|
||||
/* 01 */ uint8_t definition_entry_num;
|
||||
/* 02 */ uint8_t weight;
|
||||
/* 03 */ uint8_t unknown_a4;
|
||||
/* 04 */
|
||||
} __packed_ws__(RandomEnemyWeight, 4);
|
||||
|
||||
struct RareEnemyRates {
|
||||
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
|
||||
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
|
||||
uint32_t nar_lily; // POISON_LILY -> NAR_LILY
|
||||
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
|
||||
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
|
||||
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
|
||||
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
|
||||
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
|
||||
|
||||
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
|
||||
explicit RareEnemyRates(const JSON& json);
|
||||
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
|
||||
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
|
||||
|
||||
struct Object {
|
||||
// TODO: Add more fields in here if we ever care about them. Currently we
|
||||
// only care about boxes with fixed item drops.
|
||||
size_t source_index;
|
||||
uint8_t floor;
|
||||
uint16_t object_id;
|
||||
uint16_t base_type;
|
||||
uint16_t section;
|
||||
uint16_t group;
|
||||
float param1; // If <= 0, this is a specialized box, and the specialization is in param4/5/6
|
||||
float param3; // If == 0, the item should be varied by difficulty and area
|
||||
uint32_t param4;
|
||||
uint32_t param5;
|
||||
uint32_t param6;
|
||||
uint16_t game_flags;
|
||||
// Technically set_flags shouldn't be part of the Object struct, but all
|
||||
// object entries always generate exactly one object, so we store it here.
|
||||
uint16_t set_flags;
|
||||
bool item_drop_checked;
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct Enemy {
|
||||
enum Flag {
|
||||
LAST_HIT_MASK = 0x03,
|
||||
EXP_GIVEN = 0x04,
|
||||
ITEM_DROPPED = 0x08,
|
||||
ALL_HITS_MASK_FIRST = 0x10,
|
||||
ALL_HITS_MASK = 0xF0,
|
||||
};
|
||||
size_t source_index;
|
||||
size_t set_index;
|
||||
uint16_t enemy_id;
|
||||
uint16_t total_damage;
|
||||
uint32_t game_flags; // From 6x0A
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
EnemyType type;
|
||||
uint8_t floor;
|
||||
uint8_t server_flags;
|
||||
|
||||
Enemy(
|
||||
uint16_t enemy_id,
|
||||
size_t source_index,
|
||||
size_t set_index,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
EnemyType type);
|
||||
|
||||
std::string str() const;
|
||||
|
||||
inline bool ever_hit_by_client_id(uint8_t client_id) const {
|
||||
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
|
||||
}
|
||||
inline bool last_hit_by_client_id(uint8_t client_id) const {
|
||||
return (this->server_flags & Flag::LAST_HIT_MASK) == client_id;
|
||||
}
|
||||
inline void set_last_hit_by_client_id(uint8_t client_id) {
|
||||
this->server_flags = (this->server_flags & (~Flag::LAST_HIT_MASK)) | (Flag::ALL_HITS_MASK_FIRST << client_id) | (client_id & 3);
|
||||
}
|
||||
};
|
||||
|
||||
struct Event {
|
||||
uint32_t event_id;
|
||||
uint16_t flags;
|
||||
uint16_t section;
|
||||
uint16_t wave_number;
|
||||
uint8_t floor;
|
||||
uint32_t action_stream_offset;
|
||||
std::vector<size_t> enemy_indexes;
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct DATParserRandomState {
|
||||
PSOV2Encryption random;
|
||||
PSOV2Encryption location_table_random;
|
||||
std::array<uint32_t, 0x20> location_index_table;
|
||||
uint32_t location_indexes_populated;
|
||||
uint32_t location_indexes_used;
|
||||
uint32_t location_entries_base_offset;
|
||||
|
||||
DATParserRandomState(uint32_t rare_seed);
|
||||
size_t rand_int_biased(size_t min_v, size_t max_v);
|
||||
uint32_t next_location_index();
|
||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
||||
};
|
||||
|
||||
Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
~Map() = default;
|
||||
|
||||
void clear();
|
||||
|
||||
void add_objects_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
|
||||
bool check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate);
|
||||
void add_enemy(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
size_t index,
|
||||
const EnemyEntry& e,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
void add_enemies_from_map_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
const void* data,
|
||||
size_t size,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
void add_random_enemies_from_map_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint8_t floor,
|
||||
StringReader wave_events_r,
|
||||
StringReader random_enemy_locations_r,
|
||||
StringReader random_enemy_definitions_r,
|
||||
std::shared_ptr<DATParserRandomState> random_state,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
|
||||
|
||||
void add_event(
|
||||
uint32_t event_id,
|
||||
uint16_t flags,
|
||||
uint8_t floor,
|
||||
uint16_t section,
|
||||
uint16_t wave_number,
|
||||
uint32_t action_stream_offset);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint32_t event_id);
|
||||
std::vector<const Event*> get_events(uint8_t floor, uint32_t event_id) const;
|
||||
void add_events_from_map_data(uint8_t floor, const void* data, size_t size);
|
||||
|
||||
struct DATSectionsForFloor {
|
||||
uint32_t objects = 0xFFFFFFFF;
|
||||
uint32_t enemies = 0xFFFFFFFF;
|
||||
uint32_t wave_events = 0xFFFFFFFF;
|
||||
uint32_t random_enemy_locations = 0xFFFFFFFF;
|
||||
uint32_t random_enemy_definitions = 0xFFFFFFFF;
|
||||
};
|
||||
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
|
||||
|
||||
void add_entities_from_quest_data(
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
const void* data,
|
||||
size_t size,
|
||||
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
|
||||
|
||||
const Enemy& find_enemy(uint8_t floor, EnemyType type) const;
|
||||
Enemy& find_enemy(uint8_t floor, EnemyType type);
|
||||
std::vector<Object*> get_objects(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Enemy*> get_enemies(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor, uint16_t section, uint16_t wave_number);
|
||||
std::vector<Event*> get_events(uint8_t floor);
|
||||
|
||||
static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr);
|
||||
static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr);
|
||||
static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF);
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
Version version;
|
||||
uint32_t rare_seed;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::vector<Object> objects;
|
||||
std::vector<Enemy> enemies;
|
||||
std::vector<uint16_t> enemy_set_flags;
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
std::vector<Event> events;
|
||||
std::string event_action_stream;
|
||||
std::multimap<uint64_t, size_t> floor_and_event_id_to_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_group_to_object_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_enemy_index;
|
||||
std::unordered_multimap<uint64_t, size_t> floor_section_and_wave_number_to_event_index;
|
||||
};
|
||||
|
||||
class SetDataTableBase {
|
||||
public:
|
||||
virtual ~SetDataTableBase() = default;
|
||||
|
||||
parray<le_uint32_t, 0x20> generate_variations(
|
||||
Episode episode,
|
||||
bool is_solo,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
||||
|
||||
enum class FilenameType {
|
||||
OBJECTS = 0,
|
||||
ENEMIES,
|
||||
EVENTS,
|
||||
};
|
||||
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const = 0;
|
||||
std::vector<std::string> map_filenames_for_variations(
|
||||
const parray<le_uint32_t, 0x20>& variations, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
uint8_t default_area_for_floor(Episode episode, uint8_t floor) const;
|
||||
|
||||
protected:
|
||||
explicit SetDataTableBase(Version version);
|
||||
|
||||
Version version;
|
||||
};
|
||||
|
||||
class SetDataTable : public SetDataTableBase {
|
||||
public:
|
||||
struct SetEntry {
|
||||
std::string object_list_basename;
|
||||
std::string enemy_and_event_list_basename;
|
||||
std::string area_setup_filename;
|
||||
};
|
||||
|
||||
SetDataTable(Version version, const std::string& data);
|
||||
virtual ~SetDataTable() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void load_table_t(const std::string& data);
|
||||
|
||||
// Indexes are [floor][variation1][variation2]
|
||||
// floor is cumulative per episode, so Ep2 starts at floor=18.
|
||||
std::vector<std::vector<std::vector<SetEntry>>> entries;
|
||||
};
|
||||
|
||||
class SetDataTableDCNTE : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDCNTE();
|
||||
virtual ~SetDataTableDCNTE() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
|
||||
};
|
||||
|
||||
class SetDataTableDC112000 : public SetDataTableBase {
|
||||
public:
|
||||
SetDataTableDC112000();
|
||||
virtual ~SetDataTableDC112000() = default;
|
||||
|
||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const;
|
||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const;
|
||||
virtual std::string map_filename_for_variation(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Episode episode, GameMode mode, FilenameType type) const;
|
||||
|
||||
private:
|
||||
static const std::array<std::vector<std::vector<std::string>>, 0x12> NAMES;
|
||||
};
|
||||
|
||||
void generate_variations_deprecated(
|
||||
parray<le_uint32_t, 0x20>& variations,
|
||||
std::shared_ptr<PSOLFGEncryption> random,
|
||||
Version version,
|
||||
Episode episode,
|
||||
bool is_solo);
|
||||
|
||||
parray<le_uint32_t, 0x20> variation_maxes_deprecated(Version version, Episode episode, bool is_solo);
|
||||
bool next_variation_deprecated(parray<le_uint32_t, 0x20>& variations, Version version, Episode episode, bool is_solo);
|
||||
|
||||
std::vector<std::string> map_filenames_for_variation_deprecated(
|
||||
uint8_t floor, uint32_t var1, uint32_t var2, Version version, Episode episode, GameMode mode, bool is_enemies);
|
||||
std::vector<std::vector<std::string>> map_filenames_for_variations_deprecated(
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
bool is_enemies);
|
||||
|
||||
+149
-149
@@ -1,149 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Note: These aren't enums because neither enum nor enum class does what we
|
||||
// want. Specifically, we need GO_BACK to be valid in multiple enums (and enums
|
||||
// aren't namespaced unless they're enum classes), so we can't use enums. But we
|
||||
// also want to be able to use non-enum values in switch statements without
|
||||
// casting values all over the place, so we can't use enum classes either.
|
||||
|
||||
namespace MenuID {
|
||||
constexpr uint32_t MAIN = 0x11000011;
|
||||
constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111;
|
||||
constexpr uint32_t INFORMATION = 0x22000022;
|
||||
constexpr uint32_t LOBBY = 0x33000033;
|
||||
constexpr uint32_t GAME = 0x44000044;
|
||||
constexpr uint32_t QUEST_EP1 = 0x55010155;
|
||||
constexpr uint32_t QUEST_EP2 = 0x55020255;
|
||||
// See the decsription of the A2 command in CommandFormats.hh for why these
|
||||
// menu IDs don't fit the rest of the pattern.
|
||||
constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001;
|
||||
constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
|
||||
constexpr uint32_t PROGRAMS = 0x88000088;
|
||||
constexpr uint32_t PATCHES = 0x99000099;
|
||||
constexpr uint32_t PATCH_SWITCHES = 0x99010199;
|
||||
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
|
||||
constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
|
||||
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
|
||||
constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC;
|
||||
} // namespace MenuID
|
||||
|
||||
namespace MainMenuItemID {
|
||||
constexpr uint32_t GO_TO_LOBBY = 0x11222211;
|
||||
constexpr uint32_t INFORMATION = 0x11333311;
|
||||
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
|
||||
constexpr uint32_t PATCHES = 0x11666611;
|
||||
constexpr uint32_t PATCH_SWITCHES = 0x11676711;
|
||||
constexpr uint32_t PROGRAMS = 0x11777711;
|
||||
constexpr uint32_t DISCONNECT = 0x11888811;
|
||||
constexpr uint32_t CLEAR_LICENSE = 0x11999911;
|
||||
} // namespace MainMenuItemID
|
||||
|
||||
namespace ClearLicenseConfirmationMenuItemID {
|
||||
constexpr uint32_t CANCEL = 0x01010101;
|
||||
constexpr uint32_t CLEAR_LICENSE = 0x02020202;
|
||||
} // namespace ClearLicenseConfirmationMenuItemID
|
||||
|
||||
namespace InformationMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x22FFFF22;
|
||||
}
|
||||
|
||||
namespace ProxyDestinationsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x77FFFF77;
|
||||
constexpr uint32_t OPTIONS = 0x77EEEE77;
|
||||
} // namespace ProxyDestinationsMenuItemID
|
||||
|
||||
namespace ProgramsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x88FFFF88;
|
||||
}
|
||||
|
||||
namespace PatchesMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x99FFFF99;
|
||||
}
|
||||
|
||||
namespace ProxyOptionsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0xAAFFFFAA;
|
||||
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
|
||||
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
|
||||
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
|
||||
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
|
||||
constexpr uint32_t INFINITE_HP = 0xAA0505AA;
|
||||
constexpr uint32_t INFINITE_TP = 0xAA0606AA;
|
||||
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
|
||||
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
|
||||
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
|
||||
constexpr uint32_t RED_NAME = 0xAA0B0BAA;
|
||||
constexpr uint32_t BLANK_NAME = 0xAA0C0CAA;
|
||||
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA;
|
||||
constexpr uint32_t SKIP_CARD = 0xAA0E0EAA;
|
||||
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA;
|
||||
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA;
|
||||
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA;
|
||||
} // namespace ProxyOptionsMenuItemID
|
||||
|
||||
namespace TeamRewardMenuItemID {
|
||||
constexpr uint32_t TEAM_FLAG = 0x01010101;
|
||||
constexpr uint32_t DRESSING_ROOM = 0x02020202;
|
||||
constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303;
|
||||
constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404;
|
||||
constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505;
|
||||
constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606;
|
||||
// constexpr uint32_t POINT_OF_DISASTER = ...;
|
||||
// constexpr uint32_t TOYS_TWILIGHT = ...;
|
||||
// constexpr uint32_t COMMANDER_BLADE = ...;
|
||||
// constexpr uint32_t UNION_GUARD = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_500 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_1000 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_5000 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_10000 = ...;
|
||||
} // namespace TeamRewardMenuItemID
|
||||
|
||||
struct MenuItem {
|
||||
enum Flag {
|
||||
// For menu items to be visible on DC NTE, they must not have either of the
|
||||
// following two flags. (The INVISIBLE_ON_GC_NTE flag behaves similarly.)
|
||||
INVISIBLE_ON_DC_PROTOS = 0x001,
|
||||
INVISIBLE_ON_DC = 0x002,
|
||||
INVISIBLE_ON_PC_NTE = 0x004,
|
||||
INVISIBLE_ON_PC = 0x008,
|
||||
INVISIBLE_ON_GC_NTE = 0x010,
|
||||
INVISIBLE_ON_GC = 0x020,
|
||||
INVISIBLE_ON_XB = 0x040,
|
||||
INVISIBLE_ON_BB = 0x080,
|
||||
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
XB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
|
||||
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
|
||||
REQUIRES_MESSAGE_BOXES = 0x100,
|
||||
REQUIRES_SEND_FUNCTION_CALL = 0x200,
|
||||
REQUIRES_SAVE_DISABLED = 0x400,
|
||||
INVISIBLE_IN_INFO_MENU = 0x800,
|
||||
};
|
||||
|
||||
uint32_t item_id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<std::string()> get_description;
|
||||
uint32_t flags;
|
||||
|
||||
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
|
||||
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
|
||||
};
|
||||
|
||||
struct Menu {
|
||||
uint32_t menu_id;
|
||||
std::string name;
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
Menu() = delete;
|
||||
Menu(uint32_t menu_id, const std::string& name);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Note: These aren't enums because neither enum nor enum class does what we
|
||||
// want. Specifically, we need GO_BACK to be valid in multiple enums (and enums
|
||||
// aren't namespaced unless they're enum classes), so we can't use enums. But we
|
||||
// also want to be able to use non-enum values in switch statements without
|
||||
// casting values all over the place, so we can't use enum classes either.
|
||||
|
||||
namespace MenuID {
|
||||
constexpr uint32_t MAIN = 0x11000011;
|
||||
constexpr uint32_t CLEAR_LICENSE_CONFIRMATION = 0x11111111;
|
||||
constexpr uint32_t INFORMATION = 0x22000022;
|
||||
constexpr uint32_t LOBBY = 0x33000033;
|
||||
constexpr uint32_t GAME = 0x44000044;
|
||||
constexpr uint32_t QUEST_EP1 = 0x55010155;
|
||||
constexpr uint32_t QUEST_EP2 = 0x55020255;
|
||||
// See the decsription of the A2 command in CommandFormats.hh for why these
|
||||
// menu IDs don't fit the rest of the pattern.
|
||||
constexpr uint32_t QUEST_CATEGORIES_EP1 = 0x01000001;
|
||||
constexpr uint32_t QUEST_CATEGORIES_EP2 = 0x02000002;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
|
||||
constexpr uint32_t PROGRAMS = 0x88000088;
|
||||
constexpr uint32_t PATCHES = 0x99000099;
|
||||
constexpr uint32_t PATCH_SWITCHES = 0x99010199;
|
||||
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
|
||||
constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
|
||||
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
|
||||
constexpr uint32_t TOURNAMENT_ENTRIES = 0xCC0000CC;
|
||||
} // namespace MenuID
|
||||
|
||||
namespace MainMenuItemID {
|
||||
constexpr uint32_t GO_TO_LOBBY = 0x11222211;
|
||||
constexpr uint32_t INFORMATION = 0x11333311;
|
||||
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
|
||||
constexpr uint32_t PATCHES = 0x11666611;
|
||||
constexpr uint32_t PATCH_SWITCHES = 0x11676711;
|
||||
constexpr uint32_t PROGRAMS = 0x11777711;
|
||||
constexpr uint32_t DISCONNECT = 0x11888811;
|
||||
constexpr uint32_t CLEAR_LICENSE = 0x11999911;
|
||||
} // namespace MainMenuItemID
|
||||
|
||||
namespace ClearLicenseConfirmationMenuItemID {
|
||||
constexpr uint32_t CANCEL = 0x01010101;
|
||||
constexpr uint32_t CLEAR_LICENSE = 0x02020202;
|
||||
} // namespace ClearLicenseConfirmationMenuItemID
|
||||
|
||||
namespace InformationMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x22FFFF22;
|
||||
}
|
||||
|
||||
namespace ProxyDestinationsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x77FFFF77;
|
||||
constexpr uint32_t OPTIONS = 0x77EEEE77;
|
||||
} // namespace ProxyDestinationsMenuItemID
|
||||
|
||||
namespace ProgramsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x88FFFF88;
|
||||
}
|
||||
|
||||
namespace PatchesMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x99FFFF99;
|
||||
}
|
||||
|
||||
namespace ProxyOptionsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0xAAFFFFAA;
|
||||
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
|
||||
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
|
||||
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
|
||||
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
|
||||
constexpr uint32_t INFINITE_HP = 0xAA0505AA;
|
||||
constexpr uint32_t INFINITE_TP = 0xAA0606AA;
|
||||
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
|
||||
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
|
||||
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
|
||||
constexpr uint32_t RED_NAME = 0xAA0B0BAA;
|
||||
constexpr uint32_t BLANK_NAME = 0xAA0C0CAA;
|
||||
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0D0DAA;
|
||||
constexpr uint32_t SKIP_CARD = 0xAA0E0EAA;
|
||||
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0F0FAA;
|
||||
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1010AA;
|
||||
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1111AA;
|
||||
} // namespace ProxyOptionsMenuItemID
|
||||
|
||||
namespace TeamRewardMenuItemID {
|
||||
constexpr uint32_t TEAM_FLAG = 0x01010101;
|
||||
constexpr uint32_t DRESSING_ROOM = 0x02020202;
|
||||
constexpr uint32_t MEMBERS_20_LEADERS_3 = 0x03030303;
|
||||
constexpr uint32_t MEMBERS_40_LEADERS_5 = 0x04040404;
|
||||
constexpr uint32_t MEMBERS_70_LEADERS_8 = 0x05050505;
|
||||
constexpr uint32_t MEMBERS_100_LEADERS_10 = 0x06060606;
|
||||
// constexpr uint32_t POINT_OF_DISASTER = ...;
|
||||
// constexpr uint32_t TOYS_TWILIGHT = ...;
|
||||
// constexpr uint32_t COMMANDER_BLADE = ...;
|
||||
// constexpr uint32_t UNION_GUARD = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_500 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_1000 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_5000 = ...;
|
||||
// constexpr uint32_t TEAM_POINTS_10000 = ...;
|
||||
} // namespace TeamRewardMenuItemID
|
||||
|
||||
struct MenuItem {
|
||||
enum Flag {
|
||||
// For menu items to be visible on DC NTE, they must not have either of the
|
||||
// following two flags. (The INVISIBLE_ON_GC_NTE flag behaves similarly.)
|
||||
INVISIBLE_ON_DC_PROTOS = 0x001,
|
||||
INVISIBLE_ON_DC = 0x002,
|
||||
INVISIBLE_ON_PC_NTE = 0x004,
|
||||
INVISIBLE_ON_PC = 0x008,
|
||||
INVISIBLE_ON_GC_NTE = 0x010,
|
||||
INVISIBLE_ON_GC = 0x020,
|
||||
INVISIBLE_ON_XB = 0x040,
|
||||
INVISIBLE_ON_BB = 0x080,
|
||||
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
|
||||
XB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
|
||||
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
|
||||
REQUIRES_MESSAGE_BOXES = 0x100,
|
||||
REQUIRES_SEND_FUNCTION_CALL = 0x200,
|
||||
REQUIRES_SAVE_DISABLED = 0x400,
|
||||
INVISIBLE_IN_INFO_MENU = 0x800,
|
||||
};
|
||||
|
||||
uint32_t item_id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<std::string()> get_description;
|
||||
uint32_t flags;
|
||||
|
||||
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
|
||||
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
|
||||
};
|
||||
|
||||
struct Menu {
|
||||
uint32_t menu_id;
|
||||
std::string name;
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
Menu() = delete;
|
||||
Menu(uint32_t menu_id, const std::string& name);
|
||||
};
|
||||
|
||||
+104
-104
@@ -1,104 +1,104 @@
|
||||
#include "NetworkAddresses.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t resolve_address(const char* address) {
|
||||
struct addrinfo* res0;
|
||||
if (getaddrinfo(address, nullptr, nullptr, &res0)) {
|
||||
auto e = string_for_error(errno);
|
||||
throw runtime_error(string_printf(
|
||||
"can\'t resolve hostname %s: %s", address, e.c_str()));
|
||||
}
|
||||
|
||||
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
|
||||
struct addrinfo* res4 = nullptr;
|
||||
for (struct addrinfo* res = res0; res; res = res->ai_next) {
|
||||
if (res->ai_family == AF_INET) {
|
||||
res4 = res;
|
||||
}
|
||||
}
|
||||
if (!res4) {
|
||||
throw runtime_error(string_printf(
|
||||
"can\'t resolve hostname %s: no usable data", address));
|
||||
}
|
||||
|
||||
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
|
||||
return ntohl(res_sin->sin_addr.s_addr);
|
||||
}
|
||||
|
||||
map<string, uint32_t> get_local_addresses() {
|
||||
struct ifaddrs* ifa_raw;
|
||||
if (getifaddrs(&ifa_raw)) {
|
||||
auto s = string_for_error(errno);
|
||||
throw runtime_error(string_printf("failed to get interface addresses: %s", s.c_str()));
|
||||
}
|
||||
|
||||
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||
|
||||
map<string, uint32_t> ret;
|
||||
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
|
||||
if (sin->sin_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_local_address(uint32_t addr) {
|
||||
uint8_t net = (addr >> 24) & 0xFF;
|
||||
return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
|
||||
}
|
||||
|
||||
bool is_local_address(const sockaddr_storage& daddr) {
|
||||
if (daddr.ss_family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
|
||||
return is_local_address(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
string string_for_address(uint32_t address) {
|
||||
return string_printf("%hhu.%hhu.%hhu.%hhu",
|
||||
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
||||
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
||||
}
|
||||
|
||||
uint32_t address_for_string(const char* address) {
|
||||
return ntohl(inet_addr(address));
|
||||
}
|
||||
|
||||
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port) {
|
||||
// It seems the address part of the number is fixed-width, but the port is
|
||||
// not. Why did they do it this way?
|
||||
if (port & 0xF000) {
|
||||
return (static_cast<uint64_t>(addr) << 16) | port;
|
||||
} else if (port & 0x0F00) {
|
||||
return (static_cast<uint64_t>(addr) << 12) | port;
|
||||
} else if (port & 0x00F0) {
|
||||
return (static_cast<uint64_t>(addr) << 8) | port;
|
||||
} else {
|
||||
return (static_cast<uint64_t>(addr) << 4) | port;
|
||||
}
|
||||
}
|
||||
#include "NetworkAddresses.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t resolve_address(const char* address) {
|
||||
struct addrinfo* res0;
|
||||
if (getaddrinfo(address, nullptr, nullptr, &res0)) {
|
||||
auto e = string_for_error(errno);
|
||||
throw runtime_error(string_printf(
|
||||
"can\'t resolve hostname %s: %s", address, e.c_str()));
|
||||
}
|
||||
|
||||
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
|
||||
struct addrinfo* res4 = nullptr;
|
||||
for (struct addrinfo* res = res0; res; res = res->ai_next) {
|
||||
if (res->ai_family == AF_INET) {
|
||||
res4 = res;
|
||||
}
|
||||
}
|
||||
if (!res4) {
|
||||
throw runtime_error(string_printf(
|
||||
"can\'t resolve hostname %s: no usable data", address));
|
||||
}
|
||||
|
||||
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
|
||||
return ntohl(res_sin->sin_addr.s_addr);
|
||||
}
|
||||
|
||||
map<string, uint32_t> get_local_addresses() {
|
||||
struct ifaddrs* ifa_raw;
|
||||
if (getifaddrs(&ifa_raw)) {
|
||||
auto s = string_for_error(errno);
|
||||
throw runtime_error(string_printf("failed to get interface addresses: %s", s.c_str()));
|
||||
}
|
||||
|
||||
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||
|
||||
map<string, uint32_t> ret;
|
||||
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* sin = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
|
||||
if (sin->sin_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_local_address(uint32_t addr) {
|
||||
uint8_t net = (addr >> 24) & 0xFF;
|
||||
return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
|
||||
}
|
||||
|
||||
bool is_local_address(const sockaddr_storage& daddr) {
|
||||
if (daddr.ss_family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
|
||||
return is_local_address(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
string string_for_address(uint32_t address) {
|
||||
return string_printf("%hhu.%hhu.%hhu.%hhu",
|
||||
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
||||
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
||||
}
|
||||
|
||||
uint32_t address_for_string(const char* address) {
|
||||
return ntohl(inet_addr(address));
|
||||
}
|
||||
|
||||
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port) {
|
||||
// It seems the address part of the number is fixed-width, but the port is
|
||||
// not. Why did they do it this way?
|
||||
if (port & 0xF000) {
|
||||
return (static_cast<uint64_t>(addr) << 16) | port;
|
||||
} else if (port & 0x0F00) {
|
||||
return (static_cast<uint64_t>(addr) << 12) | port;
|
||||
} else if (port & 0x00F0) {
|
||||
return (static_cast<uint64_t>(addr) << 8) | port;
|
||||
} else {
|
||||
return (static_cast<uint64_t>(addr) << 4) | port;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-21
@@ -1,21 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
|
||||
// it's easier
|
||||
|
||||
uint32_t resolve_address(const char* address);
|
||||
std::map<std::string, uint32_t> get_local_addresses();
|
||||
uint32_t get_connected_address(int fd);
|
||||
bool is_local_address(uint32_t daddr);
|
||||
bool is_local_address(const sockaddr_storage& daddr);
|
||||
|
||||
std::string string_for_address(uint32_t address);
|
||||
uint32_t address_for_string(const char* address);
|
||||
|
||||
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port);
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
|
||||
// it's easier
|
||||
|
||||
uint32_t resolve_address(const char* address);
|
||||
std::map<std::string, uint32_t> get_local_addresses();
|
||||
uint32_t get_connected_address(int fd);
|
||||
bool is_local_address(uint32_t daddr);
|
||||
bool is_local_address(const sockaddr_storage& daddr);
|
||||
|
||||
std::string string_for_address(uint32_t address);
|
||||
uint32_t address_for_string(const char* address);
|
||||
|
||||
uint64_t devolution_phone_number_for_netloc(uint32_t addr, uint16_t port);
|
||||
|
||||
+914
-914
File diff suppressed because it is too large
Load Diff
+359
-359
@@ -1,359 +1,359 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class PSOEncryption {
|
||||
public:
|
||||
enum class Type {
|
||||
V2 = 0,
|
||||
V3,
|
||||
BB,
|
||||
JSD0,
|
||||
};
|
||||
|
||||
virtual ~PSOEncryption() = default;
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
inline void encrypt(std::string& data, bool advance = true) {
|
||||
this->encrypt(data.data(), data.size(), advance);
|
||||
}
|
||||
inline void decrypt(std::string& data, bool advance = true) {
|
||||
this->decrypt(data.data(), data.size(), advance);
|
||||
}
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
protected:
|
||||
PSOEncryption() = default;
|
||||
};
|
||||
|
||||
class PSOLFGEncryption : public PSOEncryption {
|
||||
public:
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian(void* data, size_t size, bool advance = true);
|
||||
void encrypt_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void encrypt_t(void* data, size_t size, bool advance = true);
|
||||
template <bool IsBigEndian>
|
||||
void encrypt_minus_t(void* data, size_t size, bool advance = true);
|
||||
|
||||
uint32_t next(bool advance = true);
|
||||
|
||||
inline uint32_t seed() const {
|
||||
return this->initial_seed;
|
||||
}
|
||||
uint32_t absolute_offset() const {
|
||||
return (this->cycles * this->end_offset) + this->offset;
|
||||
}
|
||||
|
||||
protected:
|
||||
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
|
||||
|
||||
virtual void update_stream() = 0;
|
||||
|
||||
std::vector<uint32_t> stream;
|
||||
size_t offset;
|
||||
size_t end_offset;
|
||||
uint32_t initial_seed;
|
||||
size_t cycles;
|
||||
};
|
||||
|
||||
class PSOV2Encryption : public PSOLFGEncryption {
|
||||
public:
|
||||
explicit PSOV2Encryption(uint32_t seed);
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
virtual void update_stream();
|
||||
|
||||
static constexpr size_t STREAM_LENGTH = 0x38;
|
||||
};
|
||||
|
||||
class PSOV3Encryption : public PSOLFGEncryption {
|
||||
public:
|
||||
explicit PSOV3Encryption(uint32_t key);
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
virtual void update_stream();
|
||||
|
||||
static constexpr size_t STREAM_LENGTH = 521;
|
||||
};
|
||||
|
||||
class PSOBBEncryption : public PSOEncryption {
|
||||
public:
|
||||
enum Subtype : uint8_t {
|
||||
STANDARD = 0x00,
|
||||
MOCB1 = 0x01,
|
||||
JSD1 = 0x02,
|
||||
TFS1 = 0x03,
|
||||
};
|
||||
|
||||
struct KeyFile {
|
||||
// initial_keys are actually a stream of uint32_ts, but we treat them as
|
||||
// bytes for code simplicity
|
||||
union InitialKeys {
|
||||
uint8_t jsd1_stream_offset;
|
||||
parray<uint8_t, 0x48> as8;
|
||||
parray<le_uint32_t, 0x12> as32;
|
||||
InitialKeys() : as32() {}
|
||||
InitialKeys(const InitialKeys& other) : as32(other.as32) {}
|
||||
} __packed_ws__(InitialKeys, 0x48);
|
||||
union PrivateKeys {
|
||||
parray<uint8_t, 0x1000> as8;
|
||||
parray<le_uint32_t, 0x400> as32;
|
||||
PrivateKeys() : as32() {}
|
||||
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
|
||||
} __packed_ws__(PrivateKeys, 0x1000);
|
||||
InitialKeys initial_keys;
|
||||
PrivateKeys private_keys;
|
||||
// This field only really needs to be one byte, but annoyingly, some
|
||||
// compilers pad this structure to a longer alignment, presumably because
|
||||
// the unions above contain structures with 32-bit alignment. To prevent
|
||||
// this structure's size from not matching the .nsk files' sizes, we use
|
||||
// an unnecessarily large size for this field.
|
||||
le_uint64_t subtype;
|
||||
} __packed_ws__(KeyFile, 0x1050);
|
||||
|
||||
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
KeyFile state;
|
||||
|
||||
void tfs1_scramble(uint32_t* out1, uint32_t* out2) const;
|
||||
void apply_seed(const void* original_seed, size_t seed_size);
|
||||
};
|
||||
|
||||
// The following classes provide support for automatically detecting which type
|
||||
// of encryption a client is using based on their initial response to the server
|
||||
|
||||
class PSOV2OrV3DetectorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOV2OrV3DetectorEncryption(
|
||||
uint32_t key,
|
||||
const std::unordered_set<uint32_t>& v2_matches,
|
||||
const std::unordered_set<uint32_t>& v3_matches);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
uint32_t key;
|
||||
const std::unordered_set<uint32_t>& v2_matches;
|
||||
const std::unordered_set<uint32_t>& v3_matches;
|
||||
std::unique_ptr<PSOEncryption> active_crypt;
|
||||
};
|
||||
|
||||
class PSOV2OrV3ImitatorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOV2OrV3ImitatorEncryption(
|
||||
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
uint32_t key;
|
||||
std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt;
|
||||
std::shared_ptr<PSOEncryption> active_crypt;
|
||||
};
|
||||
|
||||
// The following classes provide support for multiple PSOBB private keys, and
|
||||
// the ability to automatically detect which key the client is using based on
|
||||
// the first 8 bytes they send
|
||||
|
||||
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyDetectorEncryption(
|
||||
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||
const std::unordered_set<std::string>& expected_first_data,
|
||||
const void* seed,
|
||||
size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
|
||||
return this->active_key;
|
||||
}
|
||||
inline const std::string& get_seed() const {
|
||||
return this->seed;
|
||||
}
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
|
||||
std::shared_ptr<PSOBBEncryption> active_crypt;
|
||||
const std::unordered_set<std::string>& expected_first_data;
|
||||
std::string seed;
|
||||
};
|
||||
|
||||
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyImitatorEncryption(
|
||||
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
|
||||
const void* seed,
|
||||
size_t seed_size,
|
||||
bool jsd1_use_detector_seed);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<PSOBBEncryption> ensure_crypt();
|
||||
|
||||
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
std::shared_ptr<PSOBBEncryption> active_crypt;
|
||||
std::string seed;
|
||||
bool jsd1_use_detector_seed;
|
||||
};
|
||||
|
||||
class JSD0Encryption : public PSOEncryption {
|
||||
public:
|
||||
JSD0Encryption(const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
private:
|
||||
uint8_t key;
|
||||
};
|
||||
|
||||
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
|
||||
|
||||
uint32_t encrypt_challenge_time(uint16_t value);
|
||||
uint16_t decrypt_challenge_time(uint32_t value);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
class ChallengeTimeT {
|
||||
private:
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
U32T value;
|
||||
|
||||
public:
|
||||
ChallengeTimeT() = default;
|
||||
ChallengeTimeT(uint16_t v) {
|
||||
this->encode(v);
|
||||
}
|
||||
ChallengeTimeT(const ChallengeTimeT& other) = default;
|
||||
ChallengeTimeT(ChallengeTimeT&& other) = default;
|
||||
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
|
||||
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
|
||||
|
||||
bool has_value() const {
|
||||
return this->value != 0;
|
||||
}
|
||||
|
||||
uint32_t load_raw() const {
|
||||
return this->value;
|
||||
}
|
||||
void store_raw(uint32_t value) {
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
uint16_t decode() const {
|
||||
return decrypt_challenge_time(this->value);
|
||||
}
|
||||
void encode(uint16_t v) {
|
||||
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
|
||||
}
|
||||
|
||||
operator ChallengeTimeT<!IsBigEndian>() const {
|
||||
ChallengeTimeT<!IsBigEndian> ret;
|
||||
ret.store_raw(this->value);
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using ChallengeTime = ChallengeTimeT<false>;
|
||||
using ChallengeTimeBE = ChallengeTimeT<true>;
|
||||
check_struct_size(ChallengeTime, 4);
|
||||
check_struct_size(ChallengeTimeBE, 4);
|
||||
|
||||
std::string decrypt_v2_registry_value(const void* data, size_t size);
|
||||
|
||||
struct DecryptedPR2 {
|
||||
std::string compressed_data;
|
||||
size_t decompressed_size;
|
||||
};
|
||||
|
||||
template <bool IsBigEndian>
|
||||
DecryptedPR2 decrypt_pr2_data(const std::string& data) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
|
||||
if (data.size() < 8) {
|
||||
throw std::runtime_error("not enough data for PR2 header");
|
||||
}
|
||||
StringReader r(data);
|
||||
DecryptedPR2 ret = {
|
||||
.compressed_data = data.substr(8),
|
||||
.decompressed_size = r.get<U32T>()};
|
||||
PSOV2Encryption crypt(r.get<U32T>());
|
||||
if (IsBigEndian) {
|
||||
crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size());
|
||||
} else {
|
||||
crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string decrypt_and_decompress_pr2_data(const std::string& data) {
|
||||
auto decrypted = decrypt_pr2_data<IsBigEndian>(data);
|
||||
std::string decompressed = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed.size() != decrypted.decompressed_size) {
|
||||
throw std::runtime_error("decompressed size does not match expected size");
|
||||
}
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, uint32_t seed) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
|
||||
StringWriter w;
|
||||
w.put<U32T>(decompressed_size);
|
||||
w.put<U32T>(seed);
|
||||
w.write(data);
|
||||
|
||||
std::string ret = std::move(w.str());
|
||||
PSOV2Encryption crypt(seed);
|
||||
if (IsBigEndian) {
|
||||
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
|
||||
} else {
|
||||
crypt.decrypt(ret.data() + 8, ret.size() - 8);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||
return random_crypt ? random_crypt->next() : random_object<uint32_t>();
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class PSOEncryption {
|
||||
public:
|
||||
enum class Type {
|
||||
V2 = 0,
|
||||
V3,
|
||||
BB,
|
||||
JSD0,
|
||||
};
|
||||
|
||||
virtual ~PSOEncryption() = default;
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
inline void encrypt(std::string& data, bool advance = true) {
|
||||
this->encrypt(data.data(), data.size(), advance);
|
||||
}
|
||||
inline void decrypt(std::string& data, bool advance = true) {
|
||||
this->decrypt(data.data(), data.size(), advance);
|
||||
}
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
protected:
|
||||
PSOEncryption() = default;
|
||||
};
|
||||
|
||||
class PSOLFGEncryption : public PSOEncryption {
|
||||
public:
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian(void* data, size_t size, bool advance = true);
|
||||
void encrypt_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void encrypt_t(void* data, size_t size, bool advance = true);
|
||||
template <bool IsBigEndian>
|
||||
void encrypt_minus_t(void* data, size_t size, bool advance = true);
|
||||
|
||||
uint32_t next(bool advance = true);
|
||||
|
||||
inline uint32_t seed() const {
|
||||
return this->initial_seed;
|
||||
}
|
||||
uint32_t absolute_offset() const {
|
||||
return (this->cycles * this->end_offset) + this->offset;
|
||||
}
|
||||
|
||||
protected:
|
||||
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
|
||||
|
||||
virtual void update_stream() = 0;
|
||||
|
||||
std::vector<uint32_t> stream;
|
||||
size_t offset;
|
||||
size_t end_offset;
|
||||
uint32_t initial_seed;
|
||||
size_t cycles;
|
||||
};
|
||||
|
||||
class PSOV2Encryption : public PSOLFGEncryption {
|
||||
public:
|
||||
explicit PSOV2Encryption(uint32_t seed);
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
virtual void update_stream();
|
||||
|
||||
static constexpr size_t STREAM_LENGTH = 0x38;
|
||||
};
|
||||
|
||||
class PSOV3Encryption : public PSOLFGEncryption {
|
||||
public:
|
||||
explicit PSOV3Encryption(uint32_t key);
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
virtual void update_stream();
|
||||
|
||||
static constexpr size_t STREAM_LENGTH = 521;
|
||||
};
|
||||
|
||||
class PSOBBEncryption : public PSOEncryption {
|
||||
public:
|
||||
enum Subtype : uint8_t {
|
||||
STANDARD = 0x00,
|
||||
MOCB1 = 0x01,
|
||||
JSD1 = 0x02,
|
||||
TFS1 = 0x03,
|
||||
};
|
||||
|
||||
struct KeyFile {
|
||||
// initial_keys are actually a stream of uint32_ts, but we treat them as
|
||||
// bytes for code simplicity
|
||||
union InitialKeys {
|
||||
uint8_t jsd1_stream_offset;
|
||||
parray<uint8_t, 0x48> as8;
|
||||
parray<le_uint32_t, 0x12> as32;
|
||||
InitialKeys() : as32() {}
|
||||
InitialKeys(const InitialKeys& other) : as32(other.as32) {}
|
||||
} __packed_ws__(InitialKeys, 0x48);
|
||||
union PrivateKeys {
|
||||
parray<uint8_t, 0x1000> as8;
|
||||
parray<le_uint32_t, 0x400> as32;
|
||||
PrivateKeys() : as32() {}
|
||||
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
|
||||
} __packed_ws__(PrivateKeys, 0x1000);
|
||||
InitialKeys initial_keys;
|
||||
PrivateKeys private_keys;
|
||||
// This field only really needs to be one byte, but annoyingly, some
|
||||
// compilers pad this structure to a longer alignment, presumably because
|
||||
// the unions above contain structures with 32-bit alignment. To prevent
|
||||
// this structure's size from not matching the .nsk files' sizes, we use
|
||||
// an unnecessarily large size for this field.
|
||||
le_uint64_t subtype;
|
||||
} __packed_ws__(KeyFile, 0x1050);
|
||||
|
||||
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
KeyFile state;
|
||||
|
||||
void tfs1_scramble(uint32_t* out1, uint32_t* out2) const;
|
||||
void apply_seed(const void* original_seed, size_t seed_size);
|
||||
};
|
||||
|
||||
// The following classes provide support for automatically detecting which type
|
||||
// of encryption a client is using based on their initial response to the server
|
||||
|
||||
class PSOV2OrV3DetectorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOV2OrV3DetectorEncryption(
|
||||
uint32_t key,
|
||||
const std::unordered_set<uint32_t>& v2_matches,
|
||||
const std::unordered_set<uint32_t>& v3_matches);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
uint32_t key;
|
||||
const std::unordered_set<uint32_t>& v2_matches;
|
||||
const std::unordered_set<uint32_t>& v3_matches;
|
||||
std::unique_ptr<PSOEncryption> active_crypt;
|
||||
};
|
||||
|
||||
class PSOV2OrV3ImitatorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOV2OrV3ImitatorEncryption(
|
||||
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
uint32_t key;
|
||||
std::shared_ptr<const PSOV2OrV3DetectorEncryption> detector_crypt;
|
||||
std::shared_ptr<PSOEncryption> active_crypt;
|
||||
};
|
||||
|
||||
// The following classes provide support for multiple PSOBB private keys, and
|
||||
// the ability to automatically detect which key the client is using based on
|
||||
// the first 8 bytes they send
|
||||
|
||||
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyDetectorEncryption(
|
||||
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||
const std::unordered_set<std::string>& expected_first_data,
|
||||
const void* seed,
|
||||
size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
|
||||
return this->active_key;
|
||||
}
|
||||
inline const std::string& get_seed() const {
|
||||
return this->seed;
|
||||
}
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
|
||||
std::shared_ptr<PSOBBEncryption> active_crypt;
|
||||
const std::unordered_set<std::string>& expected_first_data;
|
||||
std::string seed;
|
||||
};
|
||||
|
||||
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
|
||||
public:
|
||||
PSOBBMultiKeyImitatorEncryption(
|
||||
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
|
||||
const void* seed,
|
||||
size_t seed_size,
|
||||
bool jsd1_use_detector_seed);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<PSOBBEncryption> ensure_crypt();
|
||||
|
||||
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
std::shared_ptr<PSOBBEncryption> active_crypt;
|
||||
std::string seed;
|
||||
bool jsd1_use_detector_seed;
|
||||
};
|
||||
|
||||
class JSD0Encryption : public PSOEncryption {
|
||||
public:
|
||||
JSD0Encryption(const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
private:
|
||||
uint8_t key;
|
||||
};
|
||||
|
||||
void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
|
||||
|
||||
uint32_t encrypt_challenge_time(uint16_t value);
|
||||
uint16_t decrypt_challenge_time(uint32_t value);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
class ChallengeTimeT {
|
||||
private:
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
U32T value;
|
||||
|
||||
public:
|
||||
ChallengeTimeT() = default;
|
||||
ChallengeTimeT(uint16_t v) {
|
||||
this->encode(v);
|
||||
}
|
||||
ChallengeTimeT(const ChallengeTimeT& other) = default;
|
||||
ChallengeTimeT(ChallengeTimeT&& other) = default;
|
||||
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
|
||||
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
|
||||
|
||||
bool has_value() const {
|
||||
return this->value != 0;
|
||||
}
|
||||
|
||||
uint32_t load_raw() const {
|
||||
return this->value;
|
||||
}
|
||||
void store_raw(uint32_t value) {
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
uint16_t decode() const {
|
||||
return decrypt_challenge_time(this->value);
|
||||
}
|
||||
void encode(uint16_t v) {
|
||||
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
|
||||
}
|
||||
|
||||
operator ChallengeTimeT<!IsBigEndian>() const {
|
||||
ChallengeTimeT<!IsBigEndian> ret;
|
||||
ret.store_raw(this->value);
|
||||
return ret;
|
||||
}
|
||||
} __packed__;
|
||||
using ChallengeTime = ChallengeTimeT<false>;
|
||||
using ChallengeTimeBE = ChallengeTimeT<true>;
|
||||
check_struct_size(ChallengeTime, 4);
|
||||
check_struct_size(ChallengeTimeBE, 4);
|
||||
|
||||
std::string decrypt_v2_registry_value(const void* data, size_t size);
|
||||
|
||||
struct DecryptedPR2 {
|
||||
std::string compressed_data;
|
||||
size_t decompressed_size;
|
||||
};
|
||||
|
||||
template <bool IsBigEndian>
|
||||
DecryptedPR2 decrypt_pr2_data(const std::string& data) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
|
||||
if (data.size() < 8) {
|
||||
throw std::runtime_error("not enough data for PR2 header");
|
||||
}
|
||||
StringReader r(data);
|
||||
DecryptedPR2 ret = {
|
||||
.compressed_data = data.substr(8),
|
||||
.decompressed_size = r.get<U32T>()};
|
||||
PSOV2Encryption crypt(r.get<U32T>());
|
||||
if (IsBigEndian) {
|
||||
crypt.encrypt_big_endian(ret.compressed_data.data(), ret.compressed_data.size());
|
||||
} else {
|
||||
crypt.decrypt(ret.compressed_data.data(), ret.compressed_data.size());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string decrypt_and_decompress_pr2_data(const std::string& data) {
|
||||
auto decrypted = decrypt_pr2_data<IsBigEndian>(data);
|
||||
std::string decompressed = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed.size() != decrypted.decompressed_size) {
|
||||
throw std::runtime_error("decompressed size does not match expected size");
|
||||
}
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, uint32_t seed) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
|
||||
StringWriter w;
|
||||
w.put<U32T>(decompressed_size);
|
||||
w.put<U32T>(seed);
|
||||
w.write(data);
|
||||
|
||||
std::string ret = std::move(w.str());
|
||||
PSOV2Encryption crypt(seed);
|
||||
if (IsBigEndian) {
|
||||
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
|
||||
} else {
|
||||
crypt.decrypt(ret.data() + 8, ret.size() - 8);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||
return random_crypt ? random_crypt->next() : random_object<uint32_t>();
|
||||
}
|
||||
|
||||
+270
-270
@@ -1,270 +1,270 @@
|
||||
#include "PSOProtocol.hh"
|
||||
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
PSOCommandHeader::PSOCommandHeader() {
|
||||
this->bb.size = 0;
|
||||
this->bb.command = 0;
|
||||
this->bb.flag = 0;
|
||||
}
|
||||
|
||||
uint16_t PSOCommandHeader::command(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.command;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.command;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.command;
|
||||
case Version::XB_V3:
|
||||
return this->xb.command;
|
||||
case Version::BB_V4:
|
||||
return this->bb.command;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_command(Version version, uint16_t command) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.command = command;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.command = command;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.command = command;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.command = command;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.command = command;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t PSOCommandHeader::size(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.size;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.size;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.size;
|
||||
case Version::XB_V3:
|
||||
return this->xb.size;
|
||||
case Version::BB_V4:
|
||||
return this->bb.size;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_size(Version version, uint32_t size) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.size = size;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.size = size;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.size = size;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.size = size;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.size = size;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PSOCommandHeader::flag(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.flag;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.flag;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.flag;
|
||||
case Version::XB_V3:
|
||||
return this->xb.flag;
|
||||
case Version::BB_V4:
|
||||
return this->bb.flag;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.flag = flag;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.flag = flag;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.flag = flag;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.flag = flag;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.flag = flag;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void check_size_v(size_t size, size_t min_size, size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (max_size < min_size) {
|
||||
max_size = min_size;
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
}
|
||||
|
||||
std::string prepend_command_header(
|
||||
Version version,
|
||||
bool encryption_enabled,
|
||||
uint16_t cmd,
|
||||
uint32_t flag,
|
||||
const std::string& data) {
|
||||
StringWriter ret;
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
case Version::XB_V3: {
|
||||
PSOCommandHeaderDCV3 header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2: {
|
||||
PSOCommandHeaderPC header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
case Version::BB_V4: {
|
||||
PSOCommandHeaderBB header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in prepend_command_header");
|
||||
}
|
||||
ret.write(data);
|
||||
return std::move(ret.str());
|
||||
}
|
||||
#include "PSOProtocol.hh"
|
||||
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
PSOCommandHeader::PSOCommandHeader() {
|
||||
this->bb.size = 0;
|
||||
this->bb.command = 0;
|
||||
this->bb.flag = 0;
|
||||
}
|
||||
|
||||
uint16_t PSOCommandHeader::command(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.command;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.command;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.command;
|
||||
case Version::XB_V3:
|
||||
return this->xb.command;
|
||||
case Version::BB_V4:
|
||||
return this->bb.command;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_command(Version version, uint16_t command) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.command = command;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.command = command;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.command = command;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.command = command;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.command = command;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t PSOCommandHeader::size(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.size;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.size;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.size;
|
||||
case Version::XB_V3:
|
||||
return this->xb.size;
|
||||
case Version::BB_V4:
|
||||
return this->bb.size;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_size(Version version, uint32_t size) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.size = size;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.size = size;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.size = size;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.size = size;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.size = size;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PSOCommandHeader::flag(Version version) const {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return this->pc.flag;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
return this->dc.flag;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return this->gc.flag;
|
||||
case Version::XB_V3:
|
||||
return this->xb.flag;
|
||||
case Version::BB_V4:
|
||||
return this->bb.flag;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->pc.flag = flag;
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->dc.flag = flag;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
this->gc.flag = flag;
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->xb.flag = flag;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->bb.flag = flag;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void check_size_v(size_t size, size_t min_size, size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (max_size < min_size) {
|
||||
max_size = min_size;
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
}
|
||||
|
||||
std::string prepend_command_header(
|
||||
Version version,
|
||||
bool encryption_enabled,
|
||||
uint16_t cmd,
|
||||
uint32_t flag,
|
||||
const std::string& data) {
|
||||
StringWriter ret;
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
case Version::XB_V3: {
|
||||
PSOCommandHeaderDCV3 header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2: {
|
||||
PSOCommandHeaderPC header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
case Version::BB_V4: {
|
||||
PSOCommandHeaderBB header;
|
||||
if (encryption_enabled) {
|
||||
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||
} else {
|
||||
header.size = (sizeof(header) + data.size());
|
||||
}
|
||||
header.command = cmd;
|
||||
header.flag = flag;
|
||||
ret.put(header);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in prepend_command_header");
|
||||
}
|
||||
ret.write(data);
|
||||
return std::move(ret.str());
|
||||
}
|
||||
|
||||
+130
-130
@@ -1,130 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <functional>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
struct PSOCommandHeaderPC {
|
||||
le_uint16_t size;
|
||||
uint8_t command;
|
||||
uint8_t flag;
|
||||
} __packed_ws__(PSOCommandHeaderPC, 4);
|
||||
|
||||
struct PSOCommandHeaderDCV3 {
|
||||
uint8_t command;
|
||||
uint8_t flag;
|
||||
le_uint16_t size;
|
||||
} __packed_ws__(PSOCommandHeaderDCV3, 4);
|
||||
|
||||
struct PSOCommandHeaderBB {
|
||||
le_uint16_t size;
|
||||
le_uint16_t command;
|
||||
le_uint32_t flag;
|
||||
} __packed_ws__(PSOCommandHeaderBB, 8);
|
||||
|
||||
union PSOCommandHeader {
|
||||
PSOCommandHeaderDCV3 dc;
|
||||
PSOCommandHeaderPC pc;
|
||||
PSOCommandHeaderDCV3 gc;
|
||||
PSOCommandHeaderDCV3 xb;
|
||||
PSOCommandHeaderBB bb;
|
||||
|
||||
uint16_t command(Version version) const;
|
||||
void set_command(Version version, uint16_t command);
|
||||
uint16_t size(Version version) const;
|
||||
void set_size(Version version, uint32_t size);
|
||||
uint32_t flag(Version version) const;
|
||||
void set_flag(Version version, uint32_t flag);
|
||||
static inline size_t header_size(Version version) {
|
||||
return (version == Version::BB_V4) ? 8 : 4;
|
||||
}
|
||||
|
||||
PSOCommandHeader();
|
||||
} __packed_ws__(PSOCommandHeader, 8);
|
||||
|
||||
// This function is used in a lot of places to check received command sizes and
|
||||
// cast them to the appropriate type
|
||||
template <typename RetT, typename PtrT>
|
||||
RetT& check_size_generic(
|
||||
PtrT data,
|
||||
size_t size,
|
||||
size_t min_size,
|
||||
size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
return *reinterpret_cast<RetT*>(data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data, size, min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data, size, sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size) {
|
||||
return check_size_generic<const T, const void*>(data, size, sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data, size, min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data, size, sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size) {
|
||||
return check_size_generic<T, void*>(data, size, sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
|
||||
|
||||
std::string prepend_command_header(
|
||||
Version version,
|
||||
bool encryption_enabled,
|
||||
uint16_t cmd,
|
||||
uint32_t flag,
|
||||
const std::string& data);
|
||||
#pragma once
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <functional>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
struct PSOCommandHeaderPC {
|
||||
le_uint16_t size;
|
||||
uint8_t command;
|
||||
uint8_t flag;
|
||||
} __packed_ws__(PSOCommandHeaderPC, 4);
|
||||
|
||||
struct PSOCommandHeaderDCV3 {
|
||||
uint8_t command;
|
||||
uint8_t flag;
|
||||
le_uint16_t size;
|
||||
} __packed_ws__(PSOCommandHeaderDCV3, 4);
|
||||
|
||||
struct PSOCommandHeaderBB {
|
||||
le_uint16_t size;
|
||||
le_uint16_t command;
|
||||
le_uint32_t flag;
|
||||
} __packed_ws__(PSOCommandHeaderBB, 8);
|
||||
|
||||
union PSOCommandHeader {
|
||||
PSOCommandHeaderDCV3 dc;
|
||||
PSOCommandHeaderPC pc;
|
||||
PSOCommandHeaderDCV3 gc;
|
||||
PSOCommandHeaderDCV3 xb;
|
||||
PSOCommandHeaderBB bb;
|
||||
|
||||
uint16_t command(Version version) const;
|
||||
void set_command(Version version, uint16_t command);
|
||||
uint16_t size(Version version) const;
|
||||
void set_size(Version version, uint32_t size);
|
||||
uint32_t flag(Version version) const;
|
||||
void set_flag(Version version, uint32_t flag);
|
||||
static inline size_t header_size(Version version) {
|
||||
return (version == Version::BB_V4) ? 8 : 4;
|
||||
}
|
||||
|
||||
PSOCommandHeader();
|
||||
} __packed_ws__(PSOCommandHeader, 8);
|
||||
|
||||
// This function is used in a lot of places to check received command sizes and
|
||||
// cast them to the appropriate type
|
||||
template <typename RetT, typename PtrT>
|
||||
RetT& check_size_generic(
|
||||
PtrT data,
|
||||
size_t size,
|
||||
size_t min_size,
|
||||
size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
return *reinterpret_cast<RetT*>(data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const std::string& data) {
|
||||
return check_size_generic<const T, const void*>(data.data(), data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(std::string& data) {
|
||||
return check_size_generic<T, void*>(data.data(), data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data, size, min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size, size_t max_size) {
|
||||
return check_size_generic<const T, const void*>(data, size, sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
const T& check_size_t(const void* data, size_t size) {
|
||||
return check_size_generic<const T, const void*>(data, size, sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size, size_t min_size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data, size, min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size, size_t max_size) {
|
||||
return check_size_generic<T, void*>(data, size, sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(void* data, size_t size) {
|
||||
return check_size_generic<T, void*>(data, size, sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
|
||||
|
||||
std::string prepend_command_header(
|
||||
Version version,
|
||||
bool encryption_enabled,
|
||||
uint16_t cmd,
|
||||
uint32_t flag,
|
||||
const std::string& data);
|
||||
|
||||
+162
-162
@@ -1,162 +1,162 @@
|
||||
#include "PatchFileIndex.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <functional>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
PatchFileIndex::File::File(PatchFileIndex* index)
|
||||
: index(index),
|
||||
crc32(0),
|
||||
size(0) {}
|
||||
|
||||
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
|
||||
if (!this->loaded_data) {
|
||||
string relative_path = join(this->path_directories, "/") + "/" + this->name;
|
||||
string full_path = this->index->root_dir + "/" + relative_path;
|
||||
patch_index_log.info("Loading data for %s", relative_path.c_str());
|
||||
this->loaded_data = make_shared<string>(load_file(full_path));
|
||||
this->size = this->loaded_data->size();
|
||||
}
|
||||
return this->loaded_data;
|
||||
}
|
||||
|
||||
PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
: root_dir(root_dir) {
|
||||
|
||||
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||
JSON metadata_cache_json;
|
||||
try {
|
||||
string metadata_text = load_file(metadata_cache_filename);
|
||||
metadata_cache_json = JSON::parse(metadata_text);
|
||||
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
metadata_cache_json = JSON::dict();
|
||||
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
}
|
||||
|
||||
// Assuming it's rare for patch files to change, we skip writing the metadata
|
||||
// cache if no files were changed at all (which should usually be the case)
|
||||
bool should_write_metadata_cache = false;
|
||||
JSON new_metadata_cache_json = JSON::dict();
|
||||
|
||||
vector<string> path_directories;
|
||||
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
|
||||
path_directories.emplace_back(dir);
|
||||
|
||||
string relative_dirs = join(path_directories, "/");
|
||||
string full_dir_path = root_dir + '/' + relative_dirs;
|
||||
patch_index_log.info("Listing directory %s", full_dir_path.c_str());
|
||||
|
||||
for (const auto& item : list_directory(full_dir_path)) {
|
||||
// Skip invisible files (e.g. .DS_Store on macOS)
|
||||
if (starts_with(item, ".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string relative_item_path = relative_dirs + '/' + item;
|
||||
string full_item_path = root_dir + '/' + relative_item_path;
|
||||
if (isdir(full_item_path)) {
|
||||
collect_dir(item);
|
||||
} else if (isfile(full_item_path)) {
|
||||
|
||||
auto st = stat(full_item_path);
|
||||
|
||||
auto f = make_shared<File>(this);
|
||||
f->path_directories = path_directories;
|
||||
f->name = item;
|
||||
|
||||
string compute_crc32s_message; // If not empty, should compute crc32s
|
||||
JSON cache_item_json;
|
||||
try {
|
||||
cache_item_json = metadata_cache_json.at(relative_item_path);
|
||||
uint64_t cached_size = cache_item_json.get_int(0);
|
||||
uint64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
|
||||
throw runtime_error("file has been modified");
|
||||
}
|
||||
if (static_cast<uint64_t>(st.st_size) != cached_size) {
|
||||
throw runtime_error("file size has changed");
|
||||
}
|
||||
f->size = cached_size;
|
||||
f->crc32 = cache_item_json.get_int(2);
|
||||
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
|
||||
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
compute_crc32s_message = e.what();
|
||||
}
|
||||
|
||||
if (!compute_crc32s_message.empty()) {
|
||||
auto data = f->load_data(); // Sets f->size
|
||||
f->crc32 = crc32(data->data(), f->size);
|
||||
for (size_t x = 0; x < data->size(); x += 0x4000) {
|
||||
size_t chunk_bytes = min<size_t>(f->size - x, 0x4000);
|
||||
f->chunk_crcs.emplace_back(::crc32(data->data() + x, chunk_bytes));
|
||||
}
|
||||
|
||||
// File was modified or cache item was missing; make a new cache item
|
||||
auto chunk_crcs_item = JSON::list();
|
||||
for (uint32_t chunk_crc : f->chunk_crcs) {
|
||||
chunk_crcs_item.emplace_back(chunk_crc);
|
||||
}
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
|
||||
should_write_metadata_cache = true;
|
||||
|
||||
} else {
|
||||
// File was not modified and cache item was valid; just use the
|
||||
// existing cache item
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, std::move(cache_item_json));
|
||||
}
|
||||
|
||||
this->files_by_patch_order.emplace_back(f);
|
||||
this->files_by_name.emplace(relative_item_path, f);
|
||||
if (compute_crc32s_message.empty()) {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
|
||||
} else {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path_directories.pop_back();
|
||||
};
|
||||
|
||||
collect_dir(".");
|
||||
|
||||
if (should_write_metadata_cache) {
|
||||
try {
|
||||
save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
}
|
||||
} else {
|
||||
patch_index_log.info("No files were modified; skipping metadata cache update");
|
||||
}
|
||||
}
|
||||
|
||||
const vector<shared_ptr<PatchFileIndex::File>>&
|
||||
PatchFileIndex::all_files() const {
|
||||
return this->files_by_patch_order;
|
||||
}
|
||||
|
||||
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(
|
||||
const string& filename) const {
|
||||
return this->files_by_name.at(filename);
|
||||
}
|
||||
#include "PatchFileIndex.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <functional>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
PatchFileIndex::File::File(PatchFileIndex* index)
|
||||
: index(index),
|
||||
crc32(0),
|
||||
size(0) {}
|
||||
|
||||
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
|
||||
if (!this->loaded_data) {
|
||||
string relative_path = join(this->path_directories, "/") + "/" + this->name;
|
||||
string full_path = this->index->root_dir + "/" + relative_path;
|
||||
patch_index_log.info("Loading data for %s", relative_path.c_str());
|
||||
this->loaded_data = make_shared<string>(load_file(full_path));
|
||||
this->size = this->loaded_data->size();
|
||||
}
|
||||
return this->loaded_data;
|
||||
}
|
||||
|
||||
PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
: root_dir(root_dir) {
|
||||
|
||||
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||
JSON metadata_cache_json;
|
||||
try {
|
||||
string metadata_text = load_file(metadata_cache_filename);
|
||||
metadata_cache_json = JSON::parse(metadata_text);
|
||||
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
metadata_cache_json = JSON::dict();
|
||||
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
}
|
||||
|
||||
// Assuming it's rare for patch files to change, we skip writing the metadata
|
||||
// cache if no files were changed at all (which should usually be the case)
|
||||
bool should_write_metadata_cache = false;
|
||||
JSON new_metadata_cache_json = JSON::dict();
|
||||
|
||||
vector<string> path_directories;
|
||||
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
|
||||
path_directories.emplace_back(dir);
|
||||
|
||||
string relative_dirs = join(path_directories, "/");
|
||||
string full_dir_path = root_dir + '/' + relative_dirs;
|
||||
patch_index_log.info("Listing directory %s", full_dir_path.c_str());
|
||||
|
||||
for (const auto& item : list_directory(full_dir_path)) {
|
||||
// Skip invisible files (e.g. .DS_Store on macOS)
|
||||
if (starts_with(item, ".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string relative_item_path = relative_dirs + '/' + item;
|
||||
string full_item_path = root_dir + '/' + relative_item_path;
|
||||
if (isdir(full_item_path)) {
|
||||
collect_dir(item);
|
||||
} else if (isfile(full_item_path)) {
|
||||
|
||||
auto st = stat(full_item_path);
|
||||
|
||||
auto f = make_shared<File>(this);
|
||||
f->path_directories = path_directories;
|
||||
f->name = item;
|
||||
|
||||
string compute_crc32s_message; // If not empty, should compute crc32s
|
||||
JSON cache_item_json;
|
||||
try {
|
||||
cache_item_json = metadata_cache_json.at(relative_item_path);
|
||||
uint64_t cached_size = cache_item_json.get_int(0);
|
||||
uint64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
|
||||
throw runtime_error("file has been modified");
|
||||
}
|
||||
if (static_cast<uint64_t>(st.st_size) != cached_size) {
|
||||
throw runtime_error("file size has changed");
|
||||
}
|
||||
f->size = cached_size;
|
||||
f->crc32 = cache_item_json.get_int(2);
|
||||
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
|
||||
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
compute_crc32s_message = e.what();
|
||||
}
|
||||
|
||||
if (!compute_crc32s_message.empty()) {
|
||||
auto data = f->load_data(); // Sets f->size
|
||||
f->crc32 = crc32(data->data(), f->size);
|
||||
for (size_t x = 0; x < data->size(); x += 0x4000) {
|
||||
size_t chunk_bytes = min<size_t>(f->size - x, 0x4000);
|
||||
f->chunk_crcs.emplace_back(::crc32(data->data() + x, chunk_bytes));
|
||||
}
|
||||
|
||||
// File was modified or cache item was missing; make a new cache item
|
||||
auto chunk_crcs_item = JSON::list();
|
||||
for (uint32_t chunk_crc : f->chunk_crcs) {
|
||||
chunk_crcs_item.emplace_back(chunk_crc);
|
||||
}
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
|
||||
should_write_metadata_cache = true;
|
||||
|
||||
} else {
|
||||
// File was not modified and cache item was valid; just use the
|
||||
// existing cache item
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, std::move(cache_item_json));
|
||||
}
|
||||
|
||||
this->files_by_patch_order.emplace_back(f);
|
||||
this->files_by_name.emplace(relative_item_path, f);
|
||||
if (compute_crc32s_message.empty()) {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
|
||||
} else {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path_directories.pop_back();
|
||||
};
|
||||
|
||||
collect_dir(".");
|
||||
|
||||
if (should_write_metadata_cache) {
|
||||
try {
|
||||
save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
}
|
||||
} else {
|
||||
patch_index_log.info("No files were modified; skipping metadata cache update");
|
||||
}
|
||||
}
|
||||
|
||||
const vector<shared_ptr<PatchFileIndex::File>>&
|
||||
PatchFileIndex::all_files() const {
|
||||
return this->files_by_patch_order;
|
||||
}
|
||||
|
||||
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(
|
||||
const string& filename) const {
|
||||
return this->files_by_name.at(filename);
|
||||
}
|
||||
|
||||
+52
-52
@@ -1,52 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct PatchFileIndex {
|
||||
explicit PatchFileIndex(const std::string& root_dir);
|
||||
|
||||
struct File {
|
||||
PatchFileIndex* index;
|
||||
std::vector<std::string> path_directories;
|
||||
std::string name;
|
||||
std::shared_ptr<const std::string> loaded_data;
|
||||
std::vector<uint32_t> chunk_crcs;
|
||||
uint32_t crc32;
|
||||
uint32_t size;
|
||||
|
||||
explicit File(PatchFileIndex* index);
|
||||
std::shared_ptr<const std::string> load_data();
|
||||
};
|
||||
|
||||
const std::vector<std::shared_ptr<File>>& all_files() const;
|
||||
std::shared_ptr<File> get(const std::string& filename) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<File>> files_by_patch_order;
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> files_by_name;
|
||||
std::string root_dir;
|
||||
};
|
||||
|
||||
struct PatchFileChecksumRequest {
|
||||
std::shared_ptr<PatchFileIndex::File> file;
|
||||
uint32_t crc32;
|
||||
uint32_t size;
|
||||
bool response_received;
|
||||
|
||||
explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file)
|
||||
: file(file),
|
||||
crc32(0),
|
||||
size(0),
|
||||
response_received(false) {}
|
||||
inline bool needs_update() const {
|
||||
return !this->response_received ||
|
||||
(this->crc32 != this->file->crc32) ||
|
||||
(this->size != this->file->size);
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct PatchFileIndex {
|
||||
explicit PatchFileIndex(const std::string& root_dir);
|
||||
|
||||
struct File {
|
||||
PatchFileIndex* index;
|
||||
std::vector<std::string> path_directories;
|
||||
std::string name;
|
||||
std::shared_ptr<const std::string> loaded_data;
|
||||
std::vector<uint32_t> chunk_crcs;
|
||||
uint32_t crc32;
|
||||
uint32_t size;
|
||||
|
||||
explicit File(PatchFileIndex* index);
|
||||
std::shared_ptr<const std::string> load_data();
|
||||
};
|
||||
|
||||
const std::vector<std::shared_ptr<File>>& all_files() const;
|
||||
std::shared_ptr<File> get(const std::string& filename) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<File>> files_by_patch_order;
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> files_by_name;
|
||||
std::string root_dir;
|
||||
};
|
||||
|
||||
struct PatchFileChecksumRequest {
|
||||
std::shared_ptr<PatchFileIndex::File> file;
|
||||
uint32_t crc32;
|
||||
uint32_t size;
|
||||
bool response_received;
|
||||
|
||||
explicit PatchFileChecksumRequest(std::shared_ptr<PatchFileIndex::File> file)
|
||||
: file(file),
|
||||
crc32(0),
|
||||
size(0),
|
||||
response_received(false) {}
|
||||
inline bool needs_update() const {
|
||||
return !this->response_received ||
|
||||
(this->crc32 != this->file->crc32) ||
|
||||
(this->size != this->file->size);
|
||||
}
|
||||
};
|
||||
|
||||
+478
-478
@@ -1,478 +1,478 @@
|
||||
#include "PatchServer.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "EventUtils.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static atomic<uint64_t> next_id(1);
|
||||
|
||||
PatchServer::Client::Client(
|
||||
shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, 0, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
|
||||
idle_timeout_usecs(idle_timeout_usecs),
|
||||
idle_timeout_event(
|
||||
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
|
||||
event_free) {
|
||||
this->reschedule_timeout_event();
|
||||
|
||||
// Don't print data sent to patch clients to the logs. The patch server
|
||||
// protocol is fully understood and data logs for patch clients are generally
|
||||
// more annoying than helpful at this point.
|
||||
if (hide_data_from_logs) {
|
||||
this->channel.terminal_recv_color = TerminalFormat::END;
|
||||
this->channel.terminal_send_color = TerminalFormat::END;
|
||||
}
|
||||
|
||||
this->log.info("Created");
|
||||
}
|
||||
|
||||
void PatchServer::Client::reschedule_timeout_event() {
|
||||
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
}
|
||||
|
||||
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->idle_timeout();
|
||||
}
|
||||
|
||||
void PatchServer::Client::idle_timeout() {
|
||||
this->log.info("Idle timeout expired");
|
||||
auto s = this->server.lock();
|
||||
if (s) {
|
||||
auto c = this->shared_from_this();
|
||||
s->disconnect_client(c);
|
||||
} else {
|
||||
this->channel.disconnect();
|
||||
this->log.info("Server is deleted; cannot disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::send_server_init(shared_ptr<Client> c) const {
|
||||
uint32_t server_key = random_object<uint32_t>();
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
|
||||
S_ServerInit_Patch_02 cmd;
|
||||
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
c->channel.send(0x02, 0x00, cmd);
|
||||
|
||||
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
|
||||
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
|
||||
}
|
||||
|
||||
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
|
||||
StringWriter w;
|
||||
try {
|
||||
if (c->version() == Version::PC_PATCH) {
|
||||
w.write(tt_encode_marked_optional(text, c->channel.language, true));
|
||||
} else if (c->version() == Version::BB_PATCH) {
|
||||
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
|
||||
} else {
|
||||
throw logic_error("non-patch client on patch server");
|
||||
}
|
||||
} catch (const runtime_error& e) {
|
||||
log_warning("Failed to encode message for patch message box command: %s", e.what());
|
||||
return;
|
||||
}
|
||||
w.put_u16(0);
|
||||
while (w.str().size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
c->channel.send(0x13, 0x00, w.str());
|
||||
}
|
||||
|
||||
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
|
||||
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
|
||||
c->channel.send(0x09, 0x00, cmd);
|
||||
}
|
||||
|
||||
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
c->channel.send(0x04, 0x00); // This requests the user's login information
|
||||
}
|
||||
|
||||
void PatchServer::change_to_directory(
|
||||
shared_ptr<Client> c,
|
||||
vector<string>& client_path_directories,
|
||||
const vector<string>& file_path_directories) const {
|
||||
// First, exit all leaf directories that don't match the desired path
|
||||
while (!client_path_directories.empty() &&
|
||||
((client_path_directories.size() > file_path_directories.size()) ||
|
||||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
|
||||
c->channel.send(0x0A, 0x00);
|
||||
client_path_directories.pop_back();
|
||||
}
|
||||
|
||||
// At this point, client_path_directories should be a prefix of
|
||||
// file_path_directories (or should match exactly)
|
||||
if (client_path_directories.size() > file_path_directories.size()) {
|
||||
throw logic_error("did not exit all necessary directories");
|
||||
}
|
||||
for (size_t x = 0; x < client_path_directories.size(); x++) {
|
||||
if (client_path_directories[x] != file_path_directories[x]) {
|
||||
throw logic_error("intermediate path is not a prefix of final path");
|
||||
}
|
||||
}
|
||||
|
||||
// Second, enter all necessary leaf directories
|
||||
while (client_path_directories.size() < file_path_directories.size()) {
|
||||
const string& dir = file_path_directories[client_path_directories.size()];
|
||||
this->send_enter_directory(c, dir);
|
||||
client_path_directories.emplace_back(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
|
||||
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
|
||||
|
||||
string username = cmd.username.decode();
|
||||
string password = cmd.password.decode();
|
||||
|
||||
// There are 3 cases here:
|
||||
// - No login information at all: just proceed without checking credentials
|
||||
// - Username: check that account exists if allow_unregistered_users is off
|
||||
// - Username and password: call verify_bb
|
||||
if (!username.empty() && !password.empty()) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, &password, false);
|
||||
|
||||
} catch (const AccountIndex::incorrect_password& e) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
if (!this->config->allow_unregistered_users) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!username.empty() && !this->config->allow_unregistered_users) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, nullptr, false);
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->config->message.empty()) {
|
||||
this->send_message_box(c, this->config->message.c_str());
|
||||
}
|
||||
|
||||
const auto& index = this->config->patch_file_index;
|
||||
if (index.get()) {
|
||||
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
|
||||
|
||||
vector<string> path_directories;
|
||||
for (const auto& file : index->all_files()) {
|
||||
this->change_to_directory(c, path_directories, file->path_directories);
|
||||
|
||||
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
|
||||
c->channel.send(0x0C, 0x00, req);
|
||||
c->patch_file_checksum_requests.emplace_back(file);
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
|
||||
c->channel.send(0x0D, 0x00); // End of checksum requests
|
||||
|
||||
} else {
|
||||
// No patch index present: just do something that will satisfy the client
|
||||
// without actually checking or downloading any files
|
||||
this->send_enter_directory(c, ".");
|
||||
this->send_enter_directory(c, "data");
|
||||
this->send_enter_directory(c, "scene");
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
|
||||
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
|
||||
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
|
||||
req.crc32 = cmd.checksum;
|
||||
req.size = cmd.size;
|
||||
req.response_received = true;
|
||||
}
|
||||
|
||||
void PatchServer::on_10(shared_ptr<Client> c, string&) {
|
||||
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (!req.response_received) {
|
||||
throw runtime_error("client did not respond to checksum request");
|
||||
}
|
||||
if (req.needs_update()) {
|
||||
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
|
||||
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
|
||||
start_cmd.total_bytes += req.file->size;
|
||||
start_cmd.num_files++;
|
||||
} else {
|
||||
c->log.info("File %s is up to date", req.file->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (start_cmd.num_files) {
|
||||
c->channel.send(0x11, 0x00, start_cmd);
|
||||
vector<string> path_directories;
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (req.needs_update()) {
|
||||
this->change_to_directory(c, path_directories, req.file->path_directories);
|
||||
|
||||
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
|
||||
c->channel.send(0x06, 0x00, open_cmd);
|
||||
|
||||
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
|
||||
auto data = req.file->load_data();
|
||||
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
|
||||
|
||||
vector<pair<const void*, size_t>> blocks;
|
||||
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
|
||||
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
|
||||
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
|
||||
c->channel.send(0x07, 0x00, blocks);
|
||||
}
|
||||
|
||||
S_CloseCurrentFile_Patch_08 close_cmd = {0};
|
||||
c->channel.send(0x08, 0x00, close_cmd);
|
||||
}
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
}
|
||||
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
|
||||
void PatchServer::disconnect_client(shared_ptr<Client> c) {
|
||||
if (c->channel.virtual_network_id) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
|
||||
} else if (c->channel.bev) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64, c->id);
|
||||
} else {
|
||||
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
|
||||
}
|
||||
|
||||
this->channel_to_client.erase(&c->channel);
|
||||
c->channel.disconnect();
|
||||
|
||||
// We can't just let c be destroyed here, since disconnect_client can be
|
||||
// called from within the client's channel's receive handler. So, we instead
|
||||
// move it to another set, which we'll clear in an immediately-enqueued
|
||||
// callback after the current event. This will also call the client's
|
||||
// disconnect hooks (if any).
|
||||
this->clients_to_destroy.insert(std::move(c));
|
||||
this->enqueue_destroy_clients();
|
||||
}
|
||||
|
||||
void PatchServer::enqueue_destroy_clients() {
|
||||
auto tv = usecs_to_timeval(0);
|
||||
event_add(this->destroy_clients_ev.get(), &tv);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd,
|
||||
struct sockaddr* address, int socklen, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_error(
|
||||
struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->config->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
try {
|
||||
listening_socket = &this->listening_sockets.at(listen_fd);
|
||||
} catch (const out_of_range& e) {
|
||||
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
auto c = make_shared<Client>(
|
||||
this->shared_from_this(),
|
||||
bev,
|
||||
listening_socket->version,
|
||||
this->config->idle_timeout_usecs,
|
||||
this->config->hide_data_from_logs);
|
||||
c->channel.on_command_received = PatchServer::on_client_input;
|
||||
c->channel.on_error = PatchServer::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
this->send_server_init(c);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_error(struct evconnlistener* listener) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.error("Failure on listening socket %d: %d (%s)",
|
||||
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
|
||||
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 0x02:
|
||||
server->on_02(c, data);
|
||||
break;
|
||||
case 0x04:
|
||||
server->on_04(c, data);
|
||||
break;
|
||||
case 0x0F:
|
||||
server->on_0F(c, data);
|
||||
break;
|
||||
case 0x10:
|
||||
server->on_10(c, data);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid command");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_client_error(Channel& ch, short events) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
PatchServer::PatchServer(shared_ptr<const Config> config)
|
||||
: config(config) {
|
||||
if (config->shared_base) {
|
||||
this->base = config->shared_base;
|
||||
this->base_is_shared = true;
|
||||
} else {
|
||||
this->base.reset(event_base_new(), event_base_free);
|
||||
this->base_is_shared = false;
|
||||
}
|
||||
this->destroy_clients_ev.reset(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
|
||||
if (!this->base_is_shared) {
|
||||
this->th = thread(&PatchServer::thread_fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::schedule_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::wait_for_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
this->th.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
|
||||
if (port == 0) {
|
||||
this->listen(addr_str, addr, version);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
|
||||
this->listen(addr_str, "", port, version);
|
||||
}
|
||||
|
||||
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
|
||||
: addr_str(addr_str),
|
||||
fd(fd),
|
||||
version(version),
|
||||
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
|
||||
evconnlistener_free) {
|
||||
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
|
||||
}
|
||||
|
||||
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
|
||||
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
|
||||
}
|
||||
|
||||
void PatchServer::thread_fn() {
|
||||
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
}
|
||||
|
||||
void PatchServer::set_config(std::shared_ptr<const Config> config) {
|
||||
if (this->base_is_shared) {
|
||||
this->config = config;
|
||||
} else {
|
||||
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
|
||||
s->config = config;
|
||||
});
|
||||
}
|
||||
}
|
||||
#include "PatchServer.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "EventUtils.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static atomic<uint64_t> next_id(1);
|
||||
|
||||
PatchServer::Client::Client(
|
||||
shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, 0, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
|
||||
idle_timeout_usecs(idle_timeout_usecs),
|
||||
idle_timeout_event(
|
||||
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
|
||||
event_free) {
|
||||
this->reschedule_timeout_event();
|
||||
|
||||
// Don't print data sent to patch clients to the logs. The patch server
|
||||
// protocol is fully understood and data logs for patch clients are generally
|
||||
// more annoying than helpful at this point.
|
||||
if (hide_data_from_logs) {
|
||||
this->channel.terminal_recv_color = TerminalFormat::END;
|
||||
this->channel.terminal_send_color = TerminalFormat::END;
|
||||
}
|
||||
|
||||
this->log.info("Created");
|
||||
}
|
||||
|
||||
void PatchServer::Client::reschedule_timeout_event() {
|
||||
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
}
|
||||
|
||||
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->idle_timeout();
|
||||
}
|
||||
|
||||
void PatchServer::Client::idle_timeout() {
|
||||
this->log.info("Idle timeout expired");
|
||||
auto s = this->server.lock();
|
||||
if (s) {
|
||||
auto c = this->shared_from_this();
|
||||
s->disconnect_client(c);
|
||||
} else {
|
||||
this->channel.disconnect();
|
||||
this->log.info("Server is deleted; cannot disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::send_server_init(shared_ptr<Client> c) const {
|
||||
uint32_t server_key = random_object<uint32_t>();
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
|
||||
S_ServerInit_Patch_02 cmd;
|
||||
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
c->channel.send(0x02, 0x00, cmd);
|
||||
|
||||
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
|
||||
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
|
||||
}
|
||||
|
||||
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
|
||||
StringWriter w;
|
||||
try {
|
||||
if (c->version() == Version::PC_PATCH) {
|
||||
w.write(tt_encode_marked_optional(text, c->channel.language, true));
|
||||
} else if (c->version() == Version::BB_PATCH) {
|
||||
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
|
||||
} else {
|
||||
throw logic_error("non-patch client on patch server");
|
||||
}
|
||||
} catch (const runtime_error& e) {
|
||||
log_warning("Failed to encode message for patch message box command: %s", e.what());
|
||||
return;
|
||||
}
|
||||
w.put_u16(0);
|
||||
while (w.str().size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
c->channel.send(0x13, 0x00, w.str());
|
||||
}
|
||||
|
||||
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
|
||||
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
|
||||
c->channel.send(0x09, 0x00, cmd);
|
||||
}
|
||||
|
||||
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
c->channel.send(0x04, 0x00); // This requests the user's login information
|
||||
}
|
||||
|
||||
void PatchServer::change_to_directory(
|
||||
shared_ptr<Client> c,
|
||||
vector<string>& client_path_directories,
|
||||
const vector<string>& file_path_directories) const {
|
||||
// First, exit all leaf directories that don't match the desired path
|
||||
while (!client_path_directories.empty() &&
|
||||
((client_path_directories.size() > file_path_directories.size()) ||
|
||||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
|
||||
c->channel.send(0x0A, 0x00);
|
||||
client_path_directories.pop_back();
|
||||
}
|
||||
|
||||
// At this point, client_path_directories should be a prefix of
|
||||
// file_path_directories (or should match exactly)
|
||||
if (client_path_directories.size() > file_path_directories.size()) {
|
||||
throw logic_error("did not exit all necessary directories");
|
||||
}
|
||||
for (size_t x = 0; x < client_path_directories.size(); x++) {
|
||||
if (client_path_directories[x] != file_path_directories[x]) {
|
||||
throw logic_error("intermediate path is not a prefix of final path");
|
||||
}
|
||||
}
|
||||
|
||||
// Second, enter all necessary leaf directories
|
||||
while (client_path_directories.size() < file_path_directories.size()) {
|
||||
const string& dir = file_path_directories[client_path_directories.size()];
|
||||
this->send_enter_directory(c, dir);
|
||||
client_path_directories.emplace_back(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
|
||||
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
|
||||
|
||||
string username = cmd.username.decode();
|
||||
string password = cmd.password.decode();
|
||||
|
||||
// There are 3 cases here:
|
||||
// - No login information at all: just proceed without checking credentials
|
||||
// - Username: check that account exists if allow_unregistered_users is off
|
||||
// - Username and password: call verify_bb
|
||||
if (!username.empty() && !password.empty()) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, &password, false);
|
||||
|
||||
} catch (const AccountIndex::incorrect_password& e) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
if (!this->config->allow_unregistered_users) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!username.empty() && !this->config->allow_unregistered_users) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, nullptr, false);
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->config->message.empty()) {
|
||||
this->send_message_box(c, this->config->message.c_str());
|
||||
}
|
||||
|
||||
const auto& index = this->config->patch_file_index;
|
||||
if (index.get()) {
|
||||
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
|
||||
|
||||
vector<string> path_directories;
|
||||
for (const auto& file : index->all_files()) {
|
||||
this->change_to_directory(c, path_directories, file->path_directories);
|
||||
|
||||
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
|
||||
c->channel.send(0x0C, 0x00, req);
|
||||
c->patch_file_checksum_requests.emplace_back(file);
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
|
||||
c->channel.send(0x0D, 0x00); // End of checksum requests
|
||||
|
||||
} else {
|
||||
// No patch index present: just do something that will satisfy the client
|
||||
// without actually checking or downloading any files
|
||||
this->send_enter_directory(c, ".");
|
||||
this->send_enter_directory(c, "data");
|
||||
this->send_enter_directory(c, "scene");
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
|
||||
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
|
||||
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
|
||||
req.crc32 = cmd.checksum;
|
||||
req.size = cmd.size;
|
||||
req.response_received = true;
|
||||
}
|
||||
|
||||
void PatchServer::on_10(shared_ptr<Client> c, string&) {
|
||||
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (!req.response_received) {
|
||||
throw runtime_error("client did not respond to checksum request");
|
||||
}
|
||||
if (req.needs_update()) {
|
||||
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
|
||||
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
|
||||
start_cmd.total_bytes += req.file->size;
|
||||
start_cmd.num_files++;
|
||||
} else {
|
||||
c->log.info("File %s is up to date", req.file->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (start_cmd.num_files) {
|
||||
c->channel.send(0x11, 0x00, start_cmd);
|
||||
vector<string> path_directories;
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (req.needs_update()) {
|
||||
this->change_to_directory(c, path_directories, req.file->path_directories);
|
||||
|
||||
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
|
||||
c->channel.send(0x06, 0x00, open_cmd);
|
||||
|
||||
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
|
||||
auto data = req.file->load_data();
|
||||
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
|
||||
|
||||
vector<pair<const void*, size_t>> blocks;
|
||||
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
|
||||
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
|
||||
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
|
||||
c->channel.send(0x07, 0x00, blocks);
|
||||
}
|
||||
|
||||
S_CloseCurrentFile_Patch_08 close_cmd = {0};
|
||||
c->channel.send(0x08, 0x00, close_cmd);
|
||||
}
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
}
|
||||
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
|
||||
void PatchServer::disconnect_client(shared_ptr<Client> c) {
|
||||
if (c->channel.virtual_network_id) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
|
||||
} else if (c->channel.bev) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64, c->id);
|
||||
} else {
|
||||
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
|
||||
}
|
||||
|
||||
this->channel_to_client.erase(&c->channel);
|
||||
c->channel.disconnect();
|
||||
|
||||
// We can't just let c be destroyed here, since disconnect_client can be
|
||||
// called from within the client's channel's receive handler. So, we instead
|
||||
// move it to another set, which we'll clear in an immediately-enqueued
|
||||
// callback after the current event. This will also call the client's
|
||||
// disconnect hooks (if any).
|
||||
this->clients_to_destroy.insert(std::move(c));
|
||||
this->enqueue_destroy_clients();
|
||||
}
|
||||
|
||||
void PatchServer::enqueue_destroy_clients() {
|
||||
auto tv = usecs_to_timeval(0);
|
||||
event_add(this->destroy_clients_ev.get(), &tv);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd,
|
||||
struct sockaddr* address, int socklen, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_error(
|
||||
struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->config->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
try {
|
||||
listening_socket = &this->listening_sockets.at(listen_fd);
|
||||
} catch (const out_of_range& e) {
|
||||
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
auto c = make_shared<Client>(
|
||||
this->shared_from_this(),
|
||||
bev,
|
||||
listening_socket->version,
|
||||
this->config->idle_timeout_usecs,
|
||||
this->config->hide_data_from_logs);
|
||||
c->channel.on_command_received = PatchServer::on_client_input;
|
||||
c->channel.on_error = PatchServer::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
this->send_server_init(c);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_error(struct evconnlistener* listener) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.error("Failure on listening socket %d: %d (%s)",
|
||||
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
|
||||
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 0x02:
|
||||
server->on_02(c, data);
|
||||
break;
|
||||
case 0x04:
|
||||
server->on_04(c, data);
|
||||
break;
|
||||
case 0x0F:
|
||||
server->on_0F(c, data);
|
||||
break;
|
||||
case 0x10:
|
||||
server->on_10(c, data);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid command");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_client_error(Channel& ch, short events) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
PatchServer::PatchServer(shared_ptr<const Config> config)
|
||||
: config(config) {
|
||||
if (config->shared_base) {
|
||||
this->base = config->shared_base;
|
||||
this->base_is_shared = true;
|
||||
} else {
|
||||
this->base.reset(event_base_new(), event_base_free);
|
||||
this->base_is_shared = false;
|
||||
}
|
||||
this->destroy_clients_ev.reset(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
|
||||
if (!this->base_is_shared) {
|
||||
this->th = thread(&PatchServer::thread_fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::schedule_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::wait_for_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
this->th.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
|
||||
if (port == 0) {
|
||||
this->listen(addr_str, addr, version);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
|
||||
this->listen(addr_str, "", port, version);
|
||||
}
|
||||
|
||||
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
|
||||
: addr_str(addr_str),
|
||||
fd(fd),
|
||||
version(version),
|
||||
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
|
||||
evconnlistener_free) {
|
||||
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
|
||||
}
|
||||
|
||||
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
|
||||
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
|
||||
}
|
||||
|
||||
void PatchServer::thread_fn() {
|
||||
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
}
|
||||
|
||||
void PatchServer::set_config(std::shared_ptr<const Config> config) {
|
||||
if (this->base_is_shared) {
|
||||
this->config = config;
|
||||
} else {
|
||||
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
|
||||
s->config = config;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+131
-131
@@ -1,131 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Channel.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class PatchServer : public std::enable_shared_from_this<PatchServer> {
|
||||
public:
|
||||
struct Config {
|
||||
bool allow_unregistered_users;
|
||||
bool hide_data_from_logs;
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::string message;
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<const PatchFileIndex> patch_file_index;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<struct event_base> shared_base;
|
||||
};
|
||||
|
||||
PatchServer() = delete;
|
||||
explicit PatchServer(std::shared_ptr<const Config> config);
|
||||
PatchServer(const PatchServer&) = delete;
|
||||
PatchServer(PatchServer&&) = delete;
|
||||
PatchServer& operator=(const PatchServer&) = delete;
|
||||
PatchServer& operator=(PatchServer&&) = delete;
|
||||
virtual ~PatchServer() = default;
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
|
||||
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
|
||||
void listen(const std::string& addr_str, int port, Version version);
|
||||
void add_socket(const std::string& addr_str, int fd, Version version);
|
||||
|
||||
void set_config(std::shared_ptr<const Config> config);
|
||||
|
||||
private:
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
std::weak_ptr<PatchServer> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
Channel channel;
|
||||
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
|
||||
uint64_t idle_timeout_usecs;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs);
|
||||
~Client() = default;
|
||||
|
||||
void reschedule_timeout_event();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
|
||||
const std::string& get_bb_username() const;
|
||||
void set_bb_username(const std::string& bb_username);
|
||||
};
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string addr_str;
|
||||
int fd;
|
||||
Version version;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
|
||||
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
bool base_is_shared;
|
||||
std::shared_ptr<const Config> config;
|
||||
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
|
||||
std::thread th;
|
||||
|
||||
void send_server_init(std::shared_ptr<Client> c) const;
|
||||
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
|
||||
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
|
||||
void change_to_directory(
|
||||
std::shared_ptr<Client> c,
|
||||
std::vector<std::string>& client_path_directories,
|
||||
const std::vector<std::string>& file_path_directories) const;
|
||||
void on_02(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_04(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_0F(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_10(std::shared_ptr<Client> c, std::string& data);
|
||||
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
void enqueue_destroy_clients();
|
||||
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
|
||||
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void on_client_error(Channel& ch, short events);
|
||||
|
||||
void thread_fn();
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Channel.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class PatchServer : public std::enable_shared_from_this<PatchServer> {
|
||||
public:
|
||||
struct Config {
|
||||
bool allow_unregistered_users;
|
||||
bool hide_data_from_logs;
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::string message;
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<const PatchFileIndex> patch_file_index;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<struct event_base> shared_base;
|
||||
};
|
||||
|
||||
PatchServer() = delete;
|
||||
explicit PatchServer(std::shared_ptr<const Config> config);
|
||||
PatchServer(const PatchServer&) = delete;
|
||||
PatchServer(PatchServer&&) = delete;
|
||||
PatchServer& operator=(const PatchServer&) = delete;
|
||||
PatchServer& operator=(PatchServer&&) = delete;
|
||||
virtual ~PatchServer() = default;
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
|
||||
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
|
||||
void listen(const std::string& addr_str, int port, Version version);
|
||||
void add_socket(const std::string& addr_str, int fd, Version version);
|
||||
|
||||
void set_config(std::shared_ptr<const Config> config);
|
||||
|
||||
private:
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
std::weak_ptr<PatchServer> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
Channel channel;
|
||||
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
|
||||
uint64_t idle_timeout_usecs;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs);
|
||||
~Client() = default;
|
||||
|
||||
void reschedule_timeout_event();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
|
||||
const std::string& get_bb_username() const;
|
||||
void set_bb_username(const std::string& bb_username);
|
||||
};
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string addr_str;
|
||||
int fd;
|
||||
Version version;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
|
||||
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
bool base_is_shared;
|
||||
std::shared_ptr<const Config> config;
|
||||
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
|
||||
std::thread th;
|
||||
|
||||
void send_server_init(std::shared_ptr<Client> c) const;
|
||||
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
|
||||
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
|
||||
void change_to_directory(
|
||||
std::shared_ptr<Client> c,
|
||||
std::vector<std::string>& client_path_directories,
|
||||
const std::vector<std::string>& file_path_directories) const;
|
||||
void on_02(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_04(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_0F(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_10(std::shared_ptr<Client> c, std::string& data);
|
||||
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
void enqueue_destroy_clients();
|
||||
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
|
||||
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void on_client_error(Channel& ch, short events);
|
||||
|
||||
void thread_fn();
|
||||
};
|
||||
|
||||
+119
-119
@@ -1,119 +1,119 @@
|
||||
#include "PlayerFilesManager.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
|
||||
: base(base),
|
||||
clear_expired_files_event(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
|
||||
event_free) {
|
||||
auto tv = usecs_to_timeval(30 * 1000 * 1000);
|
||||
event_add(this->clear_expired_files_event.get(), &tv);
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT>
|
||||
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
|
||||
size_t ret = 0;
|
||||
for (auto it = m.begin(); it != m.end();) {
|
||||
if (it->second.use_count() <= 1) {
|
||||
it = m.erase(it);
|
||||
ret++;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_system_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_character_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_guild_card_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_bank_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
|
||||
if (!this->loaded_system_files.emplace(filename, file).second) {
|
||||
throw runtime_error("Guild Card file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
|
||||
if (!this->loaded_character_files.emplace(filename, file).second) {
|
||||
throw runtime_error("character file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
|
||||
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
|
||||
throw runtime_error("Guild Card file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
|
||||
if (!this->loaded_bank_files.emplace(filename, file).second) {
|
||||
throw runtime_error("bank file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
|
||||
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
|
||||
size_t num_deleted = erase_unused(self->loaded_system_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_character_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_guild_card_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_bank_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
|
||||
}
|
||||
}
|
||||
#include "PlayerFilesManager.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
|
||||
: base(base),
|
||||
clear_expired_files_event(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
|
||||
event_free) {
|
||||
auto tv = usecs_to_timeval(30 * 1000 * 1000);
|
||||
event_add(this->clear_expired_files_event.get(), &tv);
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT>
|
||||
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
|
||||
size_t ret = 0;
|
||||
for (auto it = m.begin(); it != m.end();) {
|
||||
if (it->second.use_count() <= 1) {
|
||||
it = m.erase(it);
|
||||
ret++;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_system_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_character_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_guild_card_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PlayerBank200> PlayerFilesManager::get_bank(const std::string& filename) {
|
||||
try {
|
||||
return this->loaded_bank_files.at(filename);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
|
||||
if (!this->loaded_system_files.emplace(filename, file).second) {
|
||||
throw runtime_error("Guild Card file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
|
||||
if (!this->loaded_character_files.emplace(filename, file).second) {
|
||||
throw runtime_error("character file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
|
||||
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
|
||||
throw runtime_error("Guild Card file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
|
||||
if (!this->loaded_bank_files.emplace(filename, file).second) {
|
||||
throw runtime_error("bank file already loaded: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
|
||||
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
|
||||
size_t num_deleted = erase_unused(self->loaded_system_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_character_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_guild_card_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_bank_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
|
||||
}
|
||||
}
|
||||
|
||||
+47
-47
@@ -1,47 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Episode3/DataIndexes.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class PlayerFilesManager {
|
||||
public:
|
||||
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
|
||||
~PlayerFilesManager() = default;
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
|
||||
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
|
||||
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
|
||||
std::shared_ptr<PlayerBank200> get_bank(const std::string& filename);
|
||||
|
||||
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
|
||||
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
|
||||
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
|
||||
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
|
||||
|
||||
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Episode3/DataIndexes.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class PlayerFilesManager {
|
||||
public:
|
||||
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
|
||||
~PlayerFilesManager() = default;
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
|
||||
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
|
||||
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
|
||||
std::shared_ptr<PlayerBank200> get_bank(const std::string& filename);
|
||||
|
||||
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
|
||||
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
|
||||
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
|
||||
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
|
||||
|
||||
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
|
||||
};
|
||||
|
||||
+2376
-2376
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -1,15 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_proxy_command(
|
||||
std::shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
bool from_server,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
std::string& data);
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_proxy_command(
|
||||
std::shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
bool from_server,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
std::string& data);
|
||||
|
||||
+1029
-1029
File diff suppressed because it is too large
Load Diff
+307
-307
@@ -1,307 +1,307 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
|
||||
public:
|
||||
ProxyServer() = delete;
|
||||
ProxyServer(const ProxyServer&) = delete;
|
||||
ProxyServer(ProxyServer&&) = delete;
|
||||
ProxyServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state);
|
||||
virtual ~ProxyServer() = default;
|
||||
|
||||
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
|
||||
|
||||
void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
|
||||
|
||||
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
Channel client_channel;
|
||||
Channel server_channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
enum class DisconnectAction {
|
||||
LONG_TIMEOUT = 0,
|
||||
MEDIUM_TIMEOUT,
|
||||
SHORT_TIMEOUT,
|
||||
CLOSE_IMMEDIATELY,
|
||||
};
|
||||
DisconnectAction disconnect_action;
|
||||
|
||||
uint8_t prev_server_command_bytes[6];
|
||||
uint32_t remote_ip_crc;
|
||||
bool enable_remote_ip_crc_patch;
|
||||
|
||||
uint32_t sub_version;
|
||||
std::string character_name;
|
||||
std::string hardware_id; // Only used for DC sessions
|
||||
std::string login_command_bb;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
uint32_t challenge_rank_color_override;
|
||||
std::string challenge_rank_title_override;
|
||||
int64_t remote_guild_card_number;
|
||||
parray<uint8_t, 0x20> remote_client_config_data;
|
||||
Client::Config config;
|
||||
// A null handler in here means to forward the response to the remote server
|
||||
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
ItemData next_drop_item;
|
||||
uint32_t next_item_id;
|
||||
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
PASSTHROUGH,
|
||||
INTERCEPT,
|
||||
};
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<Map> map;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
std::string name;
|
||||
uint8_t language = 0;
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
size_t lobby_client_id;
|
||||
size_t leader_client_id;
|
||||
uint16_t floor;
|
||||
float x;
|
||||
float z;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t lobby_event;
|
||||
uint8_t lobby_difficulty;
|
||||
uint8_t lobby_section_id;
|
||||
GameMode lobby_mode;
|
||||
Episode lobby_episode;
|
||||
uint32_t lobby_random_seed;
|
||||
uint64_t client_ping_start_time = 0;
|
||||
uint64_t server_ping_start_time = 0;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
struct SavingFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
bool is_download;
|
||||
size_t remaining_bytes;
|
||||
std::deque<std::string> blocks;
|
||||
|
||||
SavingFile(
|
||||
const std::string& basename,
|
||||
const std::string& output_filename,
|
||||
size_t remaining_bytes,
|
||||
bool is_download);
|
||||
};
|
||||
std::unordered_map<std::string, SavingFile> saving_files;
|
||||
|
||||
// TODO: This first constructor should be private
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const Client::Config& config);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->client_channel.version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->client_channel.language;
|
||||
}
|
||||
void set_version(Version v);
|
||||
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
const std::string& character_name,
|
||||
const std::string& hardware_id,
|
||||
const XBNetworkLocation& xb_netloc,
|
||||
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
std::string&& login_command_bb);
|
||||
void resume(Channel&& client_channel);
|
||||
void resume_inner(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
|
||||
void connect();
|
||||
|
||||
static uint64_t timeout_for_disconnect_action(DisconnectAction action);
|
||||
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
|
||||
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
void on_timeout();
|
||||
|
||||
void update_channel_names();
|
||||
|
||||
void clear_lobby_players(size_t num_slots);
|
||||
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
|
||||
void send_to_game_server(const char* error_message = nullptr);
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<LinkedSession> get_session() const;
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
|
||||
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
|
||||
|
||||
std::shared_ptr<LinkedSession> create_logged_in_session(
|
||||
std::shared_ptr<Login> login,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const Client::Config& config);
|
||||
void delete_session(uint64_t id);
|
||||
|
||||
size_t num_sessions() const;
|
||||
|
||||
size_t delete_disconnected_sessions();
|
||||
|
||||
private:
|
||||
struct ListeningSocket {
|
||||
ProxyServer* server;
|
||||
|
||||
PrefixedLogger log;
|
||||
uint16_t port;
|
||||
scoped_fd fd;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
Version version;
|
||||
struct sockaddr_storage default_destination;
|
||||
|
||||
ListeningSocket(
|
||||
ProxyServer* server,
|
||||
const std::string& addr,
|
||||
uint16_t port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
void on_listen_accept(int fd);
|
||||
void on_listen_error();
|
||||
};
|
||||
|
||||
struct UnlinkedSession {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
|
||||
PrefixedLogger log;
|
||||
Channel channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
// Temporary state used just before resuming a LinkedSession. These aren't
|
||||
// just local variables inside on_input because XB requires two commands to
|
||||
// get started (9E and 9F), so we need to store this state somewhere between
|
||||
// those commands.
|
||||
std::shared_ptr<Login> login;
|
||||
uint32_t sub_version = 0;
|
||||
std::string character_name;
|
||||
Client::Config config;
|
||||
std::string login_command_bb;
|
||||
std::string hardware_id;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
UnlinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t port,
|
||||
Version version);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
void receive_and_process_commands();
|
||||
|
||||
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_sessions_ev;
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session;
|
||||
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
|
||||
uint64_t next_unlinked_session_id;
|
||||
uint64_t next_logged_out_session_id;
|
||||
|
||||
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
|
||||
void destroy_sessions();
|
||||
|
||||
void on_client_connect(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t listen_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000;
|
||||
static constexpr uint64_t MIN_LINKED_LOGGED_OUT_SESSION_ID = 0x1000000000000000;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
|
||||
public:
|
||||
ProxyServer() = delete;
|
||||
ProxyServer(const ProxyServer&) = delete;
|
||||
ProxyServer(ProxyServer&&) = delete;
|
||||
ProxyServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state);
|
||||
virtual ~ProxyServer() = default;
|
||||
|
||||
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
|
||||
|
||||
void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
|
||||
|
||||
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
Channel client_channel;
|
||||
Channel server_channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
enum class DisconnectAction {
|
||||
LONG_TIMEOUT = 0,
|
||||
MEDIUM_TIMEOUT,
|
||||
SHORT_TIMEOUT,
|
||||
CLOSE_IMMEDIATELY,
|
||||
};
|
||||
DisconnectAction disconnect_action;
|
||||
|
||||
uint8_t prev_server_command_bytes[6];
|
||||
uint32_t remote_ip_crc;
|
||||
bool enable_remote_ip_crc_patch;
|
||||
|
||||
uint32_t sub_version;
|
||||
std::string character_name;
|
||||
std::string hardware_id; // Only used for DC sessions
|
||||
std::string login_command_bb;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
uint32_t challenge_rank_color_override;
|
||||
std::string challenge_rank_title_override;
|
||||
int64_t remote_guild_card_number;
|
||||
parray<uint8_t, 0x20> remote_client_config_data;
|
||||
Client::Config config;
|
||||
// A null handler in here means to forward the response to the remote server
|
||||
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
ItemData next_drop_item;
|
||||
uint32_t next_item_id;
|
||||
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
PASSTHROUGH,
|
||||
INTERCEPT,
|
||||
};
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<Map> map;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
std::string name;
|
||||
uint8_t language = 0;
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
size_t lobby_client_id;
|
||||
size_t leader_client_id;
|
||||
uint16_t floor;
|
||||
float x;
|
||||
float z;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t lobby_event;
|
||||
uint8_t lobby_difficulty;
|
||||
uint8_t lobby_section_id;
|
||||
GameMode lobby_mode;
|
||||
Episode lobby_episode;
|
||||
uint32_t lobby_random_seed;
|
||||
uint64_t client_ping_start_time = 0;
|
||||
uint64_t server_ping_start_time = 0;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
struct SavingFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
bool is_download;
|
||||
size_t remaining_bytes;
|
||||
std::deque<std::string> blocks;
|
||||
|
||||
SavingFile(
|
||||
const std::string& basename,
|
||||
const std::string& output_filename,
|
||||
size_t remaining_bytes,
|
||||
bool is_download);
|
||||
};
|
||||
std::unordered_map<std::string, SavingFile> saving_files;
|
||||
|
||||
// TODO: This first constructor should be private
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const Client::Config& config);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->client_channel.version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->client_channel.language;
|
||||
}
|
||||
void set_version(Version v);
|
||||
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
const std::string& character_name,
|
||||
const std::string& hardware_id,
|
||||
const XBNetworkLocation& xb_netloc,
|
||||
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
std::string&& login_command_bb);
|
||||
void resume(Channel&& client_channel);
|
||||
void resume_inner(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
|
||||
void connect();
|
||||
|
||||
static uint64_t timeout_for_disconnect_action(DisconnectAction action);
|
||||
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
|
||||
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
void on_timeout();
|
||||
|
||||
void update_channel_names();
|
||||
|
||||
void clear_lobby_players(size_t num_slots);
|
||||
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
|
||||
void send_to_game_server(const char* error_message = nullptr);
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<LinkedSession> get_session() const;
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
|
||||
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
|
||||
|
||||
std::shared_ptr<LinkedSession> create_logged_in_session(
|
||||
std::shared_ptr<Login> login,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const Client::Config& config);
|
||||
void delete_session(uint64_t id);
|
||||
|
||||
size_t num_sessions() const;
|
||||
|
||||
size_t delete_disconnected_sessions();
|
||||
|
||||
private:
|
||||
struct ListeningSocket {
|
||||
ProxyServer* server;
|
||||
|
||||
PrefixedLogger log;
|
||||
uint16_t port;
|
||||
scoped_fd fd;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
Version version;
|
||||
struct sockaddr_storage default_destination;
|
||||
|
||||
ListeningSocket(
|
||||
ProxyServer* server,
|
||||
const std::string& addr,
|
||||
uint16_t port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
void on_listen_accept(int fd);
|
||||
void on_listen_error();
|
||||
};
|
||||
|
||||
struct UnlinkedSession {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
|
||||
PrefixedLogger log;
|
||||
Channel channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
// Temporary state used just before resuming a LinkedSession. These aren't
|
||||
// just local variables inside on_input because XB requires two commands to
|
||||
// get started (9E and 9F), so we need to store this state somewhere between
|
||||
// those commands.
|
||||
std::shared_ptr<Login> login;
|
||||
uint32_t sub_version = 0;
|
||||
std::string character_name;
|
||||
Client::Config config;
|
||||
std::string login_command_bb;
|
||||
std::string hardware_id;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
UnlinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t port,
|
||||
Version version);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
void receive_and_process_commands();
|
||||
|
||||
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_sessions_ev;
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session;
|
||||
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
|
||||
uint64_t next_unlinked_session_id;
|
||||
uint64_t next_logged_out_session_id;
|
||||
|
||||
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
|
||||
void destroy_sessions();
|
||||
|
||||
void on_client_connect(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t listen_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static constexpr uint64_t MIN_UNLINKED_SESSION_ID = 0xC000000000000000;
|
||||
static constexpr uint64_t MIN_LINKED_LOGGED_OUT_SESSION_ID = 0x1000000000000000;
|
||||
};
|
||||
|
||||
+1381
-1381
File diff suppressed because it is too large
Load Diff
+202
-202
@@ -1,202 +1,202 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "IntegralExpression.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
enum class QuestFileFormat {
|
||||
BIN_DAT = 0,
|
||||
BIN_DAT_UNCOMPRESSED,
|
||||
BIN_DAT_GCI,
|
||||
BIN_DAT_VMS,
|
||||
BIN_DAT_DLQ,
|
||||
QST,
|
||||
};
|
||||
|
||||
enum class QuestMenuType {
|
||||
NORMAL = 0,
|
||||
BATTLE = 1,
|
||||
CHALLENGE = 2,
|
||||
SOLO = 3,
|
||||
GOVERNMENT = 4,
|
||||
DOWNLOAD = 5,
|
||||
EP3_DOWNLOAD = 6,
|
||||
// 7 can't be used as a menu type (it enables the per-episode filter)
|
||||
};
|
||||
|
||||
struct QuestCategoryIndex {
|
||||
struct Category {
|
||||
uint32_t category_id;
|
||||
uint16_t enabled_flags;
|
||||
std::string directory_name;
|
||||
std::string name;
|
||||
std::string description;
|
||||
|
||||
explicit Category(uint32_t category_id, const JSON& json);
|
||||
|
||||
[[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const {
|
||||
return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type));
|
||||
}
|
||||
[[nodiscard]] inline bool enable_episode_filter() const {
|
||||
return this->enabled_flags & 0x080;
|
||||
}
|
||||
[[nodiscard]] inline bool use_ep2_icon() const {
|
||||
return this->enabled_flags & 0x100;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<Category>> categories;
|
||||
|
||||
explicit QuestCategoryIndex(const JSON& json);
|
||||
|
||||
std::shared_ptr<const Category> at(uint32_t category_id) const;
|
||||
};
|
||||
|
||||
struct VersionedQuest {
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
Version version;
|
||||
uint8_t language;
|
||||
bool is_dlq_encoded;
|
||||
std::string short_description;
|
||||
std::string long_description;
|
||||
std::shared_ptr<const std::string> bin_contents;
|
||||
std::shared_ptr<const std::string> dat_contents;
|
||||
std::shared_ptr<const std::string> dat_contents_decompressed;
|
||||
std::shared_ptr<const std::string> pvr_contents;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
|
||||
VersionedQuest(
|
||||
uint32_t quest_number,
|
||||
uint32_t category_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||
ssize_t challenge_template_index = -1,
|
||||
uint8_t description_flag = 0,
|
||||
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
|
||||
bool allow_start_from_chat_command = false,
|
||||
bool force_joinable = false,
|
||||
int16_t lock_status_register = -1);
|
||||
|
||||
std::string bin_filename() const;
|
||||
std::string dat_filename() const;
|
||||
std::string pvr_filename() const;
|
||||
std::string xb_filename() const;
|
||||
|
||||
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
|
||||
std::string encode_qst() const;
|
||||
};
|
||||
|
||||
class Quest {
|
||||
public:
|
||||
Quest() = delete;
|
||||
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
|
||||
Quest(const Quest&) = default;
|
||||
Quest(Quest&&) = default;
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
|
||||
|
||||
static uint32_t versions_key(Version v, uint8_t language);
|
||||
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
||||
};
|
||||
|
||||
struct QuestIndex {
|
||||
enum class IncludeState {
|
||||
HIDDEN = 0,
|
||||
AVAILABLE,
|
||||
DISABLED,
|
||||
};
|
||||
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
|
||||
|
||||
std::string directory;
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index;
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
|
||||
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
|
||||
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||
|
||||
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
|
||||
|
||||
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
||||
std::shared_ptr<const Quest> get(const std::string& name) const;
|
||||
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
IncludeCondition include_condition = nullptr) const;
|
||||
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition = nullptr,
|
||||
size_t limit = 0) const;
|
||||
};
|
||||
|
||||
std::string encode_download_quest_data(
|
||||
const std::string& compressed_data,
|
||||
size_t decompressed_size = 0,
|
||||
uint32_t encryption_seed = 0);
|
||||
|
||||
std::string decode_gci_data(
|
||||
const std::string& data,
|
||||
ssize_t find_seed_num_threads = -1,
|
||||
int64_t known_seed = -1,
|
||||
bool skip_checksum = false);
|
||||
std::string decode_vms_data(
|
||||
const std::string& data,
|
||||
ssize_t find_seed_num_threads = -1,
|
||||
int64_t known_seed = -1,
|
||||
bool skip_checksum = false);
|
||||
std::string decode_dlq_data(const std::string& data);
|
||||
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data);
|
||||
|
||||
std::string encode_qst_file(
|
||||
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
|
||||
const std::string& name,
|
||||
uint32_t quest_number,
|
||||
const std::string& xb_filename,
|
||||
Version version,
|
||||
bool is_dlq_encoded);
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "IntegralExpression.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
enum class QuestFileFormat {
|
||||
BIN_DAT = 0,
|
||||
BIN_DAT_UNCOMPRESSED,
|
||||
BIN_DAT_GCI,
|
||||
BIN_DAT_VMS,
|
||||
BIN_DAT_DLQ,
|
||||
QST,
|
||||
};
|
||||
|
||||
enum class QuestMenuType {
|
||||
NORMAL = 0,
|
||||
BATTLE = 1,
|
||||
CHALLENGE = 2,
|
||||
SOLO = 3,
|
||||
GOVERNMENT = 4,
|
||||
DOWNLOAD = 5,
|
||||
EP3_DOWNLOAD = 6,
|
||||
// 7 can't be used as a menu type (it enables the per-episode filter)
|
||||
};
|
||||
|
||||
struct QuestCategoryIndex {
|
||||
struct Category {
|
||||
uint32_t category_id;
|
||||
uint16_t enabled_flags;
|
||||
std::string directory_name;
|
||||
std::string name;
|
||||
std::string description;
|
||||
|
||||
explicit Category(uint32_t category_id, const JSON& json);
|
||||
|
||||
[[nodiscard]] inline bool check_flag(QuestMenuType menu_type) const {
|
||||
return this->enabled_flags & (1 << static_cast<uint8_t>(menu_type));
|
||||
}
|
||||
[[nodiscard]] inline bool enable_episode_filter() const {
|
||||
return this->enabled_flags & 0x080;
|
||||
}
|
||||
[[nodiscard]] inline bool use_ep2_icon() const {
|
||||
return this->enabled_flags & 0x100;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<Category>> categories;
|
||||
|
||||
explicit QuestCategoryIndex(const JSON& json);
|
||||
|
||||
std::shared_ptr<const Category> at(uint32_t category_id) const;
|
||||
};
|
||||
|
||||
struct VersionedQuest {
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
Version version;
|
||||
uint8_t language;
|
||||
bool is_dlq_encoded;
|
||||
std::string short_description;
|
||||
std::string long_description;
|
||||
std::shared_ptr<const std::string> bin_contents;
|
||||
std::shared_ptr<const std::string> dat_contents;
|
||||
std::shared_ptr<const std::string> dat_contents_decompressed;
|
||||
std::shared_ptr<const std::string> pvr_contents;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
|
||||
VersionedQuest(
|
||||
uint32_t quest_number,
|
||||
uint32_t category_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||
ssize_t challenge_template_index = -1,
|
||||
uint8_t description_flag = 0,
|
||||
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
|
||||
bool allow_start_from_chat_command = false,
|
||||
bool force_joinable = false,
|
||||
int16_t lock_status_register = -1);
|
||||
|
||||
std::string bin_filename() const;
|
||||
std::string dat_filename() const;
|
||||
std::string pvr_filename() const;
|
||||
std::string xb_filename() const;
|
||||
|
||||
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
|
||||
std::string encode_qst() const;
|
||||
};
|
||||
|
||||
class Quest {
|
||||
public:
|
||||
Quest() = delete;
|
||||
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
|
||||
Quest(const Quest&) = default;
|
||||
Quest(Quest&&) = default;
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
|
||||
|
||||
static uint32_t versions_key(Version v, uint8_t language);
|
||||
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
||||
};
|
||||
|
||||
struct QuestIndex {
|
||||
enum class IncludeState {
|
||||
HIDDEN = 0,
|
||||
AVAILABLE,
|
||||
DISABLED,
|
||||
};
|
||||
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
|
||||
|
||||
std::string directory;
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index;
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
|
||||
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
|
||||
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||
|
||||
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
|
||||
|
||||
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
||||
std::shared_ptr<const Quest> get(const std::string& name) const;
|
||||
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
IncludeCondition include_condition = nullptr) const;
|
||||
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition = nullptr,
|
||||
size_t limit = 0) const;
|
||||
};
|
||||
|
||||
std::string encode_download_quest_data(
|
||||
const std::string& compressed_data,
|
||||
size_t decompressed_size = 0,
|
||||
uint32_t encryption_seed = 0);
|
||||
|
||||
std::string decode_gci_data(
|
||||
const std::string& data,
|
||||
ssize_t find_seed_num_threads = -1,
|
||||
int64_t known_seed = -1,
|
||||
bool skip_checksum = false);
|
||||
std::string decode_vms_data(
|
||||
const std::string& data,
|
||||
ssize_t find_seed_num_threads = -1,
|
||||
int64_t known_seed = -1,
|
||||
bool skip_checksum = false);
|
||||
std::string decode_dlq_data(const std::string& data);
|
||||
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data);
|
||||
|
||||
std::string encode_qst_file(
|
||||
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
|
||||
const std::string& name,
|
||||
uint32_t quest_number,
|
||||
const std::string& xb_filename,
|
||||
Version version,
|
||||
bool is_dlq_encoded);
|
||||
|
||||
+653
-653
File diff suppressed because it is too large
Load Diff
+113
-113
@@ -1,113 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "AFSArchive.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class RareItemSet {
|
||||
public:
|
||||
struct ExpandedDrop {
|
||||
uint32_t probability = 0;
|
||||
ItemData data;
|
||||
|
||||
std::string str() const;
|
||||
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
};
|
||||
|
||||
RareItemSet();
|
||||
RareItemSet(const AFSArchive& afs, bool is_v1);
|
||||
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
|
||||
RareItemSet(const std::string& rel, bool is_big_endian);
|
||||
RareItemSet(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
|
||||
~RareItemSet() = default;
|
||||
|
||||
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
std::string serialize_afs(bool is_v1) const;
|
||||
std::string serialize_gsl(bool big_endian) const;
|
||||
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
void multiply_all_rates(double factor);
|
||||
|
||||
void print_collection(
|
||||
FILE* stream,
|
||||
GameMode mode,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
protected:
|
||||
struct SpecCollection {
|
||||
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
|
||||
std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
|
||||
};
|
||||
|
||||
struct ParsedRELData {
|
||||
struct PackedDrop {
|
||||
uint8_t probability = 0;
|
||||
parray<uint8_t, 3> item_code;
|
||||
|
||||
PackedDrop() = default;
|
||||
explicit PackedDrop(const ExpandedDrop&);
|
||||
ExpandedDrop expand() const;
|
||||
} __packed_ws__(PackedDrop, 4);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct OffsetsT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
/* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
|
||||
/* 04 */ U32T box_count; // Usually 30 (0x1E)
|
||||
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
|
||||
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
|
||||
/* 10 */
|
||||
} __packed__;
|
||||
using Offsets = OffsetsT<false>;
|
||||
using OffsetsBE = OffsetsT<true>;
|
||||
check_struct_size(Offsets, 0x10);
|
||||
check_struct_size(OffsetsBE, 0x10);
|
||||
|
||||
struct BoxRare {
|
||||
uint8_t area;
|
||||
ExpandedDrop drop;
|
||||
};
|
||||
|
||||
std::vector<ExpandedDrop> monster_rares;
|
||||
std::vector<BoxRare> box_rares;
|
||||
|
||||
ParsedRELData() = default;
|
||||
ParsedRELData(StringReader r, bool big_endian, bool is_v1);
|
||||
explicit ParsedRELData(const SpecCollection& collection);
|
||||
std::string serialize(bool big_endian, bool is_v1) const;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void parse_t(StringReader r, bool is_v1);
|
||||
template <bool IsBigEndian>
|
||||
std::string serialize_t(bool is_v1) const;
|
||||
|
||||
SpecCollection as_collection() const;
|
||||
};
|
||||
|
||||
std::unordered_map<uint16_t, SpecCollection> collections;
|
||||
|
||||
const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id);
|
||||
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
|
||||
|
||||
static uint32_t expand_rate(uint8_t pc);
|
||||
static uint8_t compress_rate(uint32_t probability);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "AFSArchive.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class RareItemSet {
|
||||
public:
|
||||
struct ExpandedDrop {
|
||||
uint32_t probability = 0;
|
||||
ItemData data;
|
||||
|
||||
std::string str() const;
|
||||
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
};
|
||||
|
||||
RareItemSet();
|
||||
RareItemSet(const AFSArchive& afs, bool is_v1);
|
||||
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
|
||||
RareItemSet(const std::string& rel, bool is_big_endian);
|
||||
RareItemSet(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
|
||||
~RareItemSet() = default;
|
||||
|
||||
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
std::string serialize_afs(bool is_v1) const;
|
||||
std::string serialize_gsl(bool big_endian) const;
|
||||
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
void multiply_all_rates(double factor);
|
||||
|
||||
void print_collection(
|
||||
FILE* stream,
|
||||
GameMode mode,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
protected:
|
||||
struct SpecCollection {
|
||||
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
|
||||
std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
|
||||
};
|
||||
|
||||
struct ParsedRELData {
|
||||
struct PackedDrop {
|
||||
uint8_t probability = 0;
|
||||
parray<uint8_t, 3> item_code;
|
||||
|
||||
PackedDrop() = default;
|
||||
explicit PackedDrop(const ExpandedDrop&);
|
||||
ExpandedDrop expand() const;
|
||||
} __packed_ws__(PackedDrop, 4);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct OffsetsT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
/* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
|
||||
/* 04 */ U32T box_count; // Usually 30 (0x1E)
|
||||
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
|
||||
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
|
||||
/* 10 */
|
||||
} __packed__;
|
||||
using Offsets = OffsetsT<false>;
|
||||
using OffsetsBE = OffsetsT<true>;
|
||||
check_struct_size(Offsets, 0x10);
|
||||
check_struct_size(OffsetsBE, 0x10);
|
||||
|
||||
struct BoxRare {
|
||||
uint8_t area;
|
||||
ExpandedDrop drop;
|
||||
};
|
||||
|
||||
std::vector<ExpandedDrop> monster_rares;
|
||||
std::vector<BoxRare> box_rares;
|
||||
|
||||
ParsedRELData() = default;
|
||||
ParsedRELData(StringReader r, bool big_endian, bool is_v1);
|
||||
explicit ParsedRELData(const SpecCollection& collection);
|
||||
std::string serialize(bool big_endian, bool is_v1) const;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void parse_t(StringReader r, bool is_v1);
|
||||
template <bool IsBigEndian>
|
||||
std::string serialize_t(bool is_v1) const;
|
||||
|
||||
SpecCollection as_collection() const;
|
||||
};
|
||||
|
||||
std::unordered_map<uint16_t, SpecCollection> collections;
|
||||
|
||||
const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id);
|
||||
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
|
||||
|
||||
static uint32_t expand_rate(uint8_t pc);
|
||||
static uint8_t compress_rate(uint32_t probability);
|
||||
};
|
||||
|
||||
+5676
-5676
File diff suppressed because it is too large
Load Diff
+31
-31
@@ -1,31 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
std::shared_ptr<Lobby> create_game_generic(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& name,
|
||||
const std::string& password = "",
|
||||
Episode episode = Episode::EP1,
|
||||
GameMode mode = GameMode::NORMAL,
|
||||
uint8_t difficulty = 0,
|
||||
bool allow_v1 = false,
|
||||
std::shared_ptr<Lobby> watched_lobby = nullptr,
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
|
||||
void set_lobby_quest(std::shared_ptr<Lobby> l, std::shared_ptr<const Quest> q, bool substitute_v3_for_ep3 = false);
|
||||
|
||||
void on_connect(std::shared_ptr<Client> c);
|
||||
void on_disconnect(std::shared_ptr<Client> c);
|
||||
void on_login_complete(std::shared_ptr<Client> c);
|
||||
|
||||
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
|
||||
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
|
||||
|
||||
void send_client_to_login_server(std::shared_ptr<Client> c);
|
||||
void send_client_to_lobby_server(std::shared_ptr<Client> c);
|
||||
void send_client_to_proxy_server(std::shared_ptr<Client> c);
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
std::shared_ptr<Lobby> create_game_generic(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& name,
|
||||
const std::string& password = "",
|
||||
Episode episode = Episode::EP1,
|
||||
GameMode mode = GameMode::NORMAL,
|
||||
uint8_t difficulty = 0,
|
||||
bool allow_v1 = false,
|
||||
std::shared_ptr<Lobby> watched_lobby = nullptr,
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
|
||||
void set_lobby_quest(std::shared_ptr<Lobby> l, std::shared_ptr<const Quest> q, bool substitute_v3_for_ep3 = false);
|
||||
|
||||
void on_connect(std::shared_ptr<Client> c);
|
||||
void on_disconnect(std::shared_ptr<Client> c);
|
||||
void on_login_complete(std::shared_ptr<Client> c);
|
||||
|
||||
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
|
||||
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
|
||||
|
||||
void send_client_to_login_server(std::shared_ptr<Client> c);
|
||||
void send_client_to_lobby_server(std::shared_ptr<Client> c);
|
||||
void send_client_to_proxy_server(std::shared_ptr<Client> c);
|
||||
|
||||
+4788
-4788
File diff suppressed because it is too large
Load Diff
+124
-124
@@ -1,124 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
|
||||
bool subcommand_is_implemented(uint8_t which);
|
||||
|
||||
void send_item_notification_if_needed(
|
||||
std::shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const Client::Config& config,
|
||||
const ItemData& item,
|
||||
bool is_from_rare_table);
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
|
||||
|
||||
struct DropReconcileResult {
|
||||
uint8_t effective_rt_index;
|
||||
bool is_box;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
|
||||
DropReconcileResult reconcile_drop_request_with_map(
|
||||
PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
bool mark_drop);
|
||||
|
||||
class Parsed6x70Data {
|
||||
public:
|
||||
Version from_version;
|
||||
bool from_client_customization;
|
||||
Version item_version;
|
||||
|
||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
||||
uint32_t unknown_a5_nte = 0;
|
||||
uint32_t unknown_a6_nte = 0;
|
||||
uint16_t bonus_hp_from_materials = 0;
|
||||
uint16_t bonus_tp_from_materials = 0;
|
||||
parray<uint8_t, 0x10> unknown_a5_112000;
|
||||
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
|
||||
uint32_t language = 0;
|
||||
uint32_t player_tag = 0;
|
||||
uint32_t guild_card_number = 0;
|
||||
uint32_t unknown_a6 = 0;
|
||||
uint32_t battle_team_number = 0;
|
||||
Telepipe6x70 telepipe;
|
||||
uint32_t unknown_a8 = 0;
|
||||
parray<uint8_t, 0x10> unknown_a9_nte_112000;
|
||||
G_Unknown_6x70_SubA1 unknown_a9_final;
|
||||
uint32_t area = 0;
|
||||
uint32_t flags2 = 0;
|
||||
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
||||
PlayerVisualConfig visual;
|
||||
std::string name;
|
||||
PlayerStats stats;
|
||||
uint32_t num_items = 0;
|
||||
parray<PlayerInventoryItem, 0x1E> items;
|
||||
uint32_t floor = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
uint32_t xb_unknown_a16 = 0;
|
||||
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
|
||||
|
||||
uint64_t default_xb_user_id() const;
|
||||
void clear_v1_unused_item_fields();
|
||||
void clear_dc_protos_unused_item_fields();
|
||||
|
||||
protected:
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BaseV1& base,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
|
||||
bool subcommand_is_implemented(uint8_t which);
|
||||
|
||||
void send_item_notification_if_needed(
|
||||
std::shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const Client::Config& config,
|
||||
const ItemData& item,
|
||||
bool is_from_rare_table);
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
|
||||
|
||||
struct DropReconcileResult {
|
||||
uint8_t effective_rt_index;
|
||||
bool is_box;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
|
||||
DropReconcileResult reconcile_drop_request_with_map(
|
||||
PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
bool mark_drop);
|
||||
|
||||
class Parsed6x70Data {
|
||||
public:
|
||||
Version from_version;
|
||||
bool from_client_customization;
|
||||
Version item_version;
|
||||
|
||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
||||
uint32_t unknown_a5_nte = 0;
|
||||
uint32_t unknown_a6_nte = 0;
|
||||
uint16_t bonus_hp_from_materials = 0;
|
||||
uint16_t bonus_tp_from_materials = 0;
|
||||
parray<uint8_t, 0x10> unknown_a5_112000;
|
||||
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
|
||||
uint32_t language = 0;
|
||||
uint32_t player_tag = 0;
|
||||
uint32_t guild_card_number = 0;
|
||||
uint32_t unknown_a6 = 0;
|
||||
uint32_t battle_team_number = 0;
|
||||
Telepipe6x70 telepipe;
|
||||
uint32_t unknown_a8 = 0;
|
||||
parray<uint8_t, 0x10> unknown_a9_nte_112000;
|
||||
G_Unknown_6x70_SubA1 unknown_a9_final;
|
||||
uint32_t area = 0;
|
||||
uint32_t flags2 = 0;
|
||||
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
||||
PlayerVisualConfig visual;
|
||||
std::string name;
|
||||
PlayerStats stats;
|
||||
uint32_t num_items = 0;
|
||||
parray<PlayerInventoryItem, 0x1E> items;
|
||||
uint32_t floor = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
uint32_t xb_unknown_a16 = 0;
|
||||
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
|
||||
|
||||
uint64_t default_xb_user_id() const;
|
||||
void clear_v1_unused_item_fields();
|
||||
void clear_dc_protos_unused_item_fields();
|
||||
|
||||
protected:
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BaseV1& base,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version,
|
||||
bool from_client_customization);
|
||||
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const;
|
||||
};
|
||||
|
||||
+810
-810
File diff suppressed because it is too large
Load Diff
+99
-99
@@ -1,99 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Channel.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class ReplaySession {
|
||||
public:
|
||||
ReplaySession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
FILE* input_log,
|
||||
std::shared_ptr<ServerState> state,
|
||||
bool require_basic_credentials);
|
||||
ReplaySession(const ReplaySession&) = delete;
|
||||
ReplaySession(ReplaySession&&) = delete;
|
||||
ReplaySession& operator=(const ReplaySession&) = delete;
|
||||
ReplaySession& operator=(ReplaySession&&) = delete;
|
||||
~ReplaySession() = default;
|
||||
|
||||
void start();
|
||||
|
||||
private:
|
||||
struct Event {
|
||||
enum class Type {
|
||||
CONNECT = 0,
|
||||
DISCONNECT,
|
||||
SEND,
|
||||
RECEIVE,
|
||||
};
|
||||
Type type;
|
||||
uint64_t client_id;
|
||||
std::string data; // Only used for SEND and RECEIVE
|
||||
std::string mask; // Only used for RECEIVE
|
||||
bool allow_size_disparity;
|
||||
bool complete;
|
||||
size_t line_num;
|
||||
|
||||
std::shared_ptr<Event> next_event;
|
||||
|
||||
Event(Type type, uint64_t client_id, size_t line_num);
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct Client {
|
||||
uint64_t id;
|
||||
uint16_t port;
|
||||
Version version;
|
||||
Channel channel;
|
||||
std::deque<std::shared_ptr<Event>> receive_events;
|
||||
std::shared_ptr<Event> disconnect_event;
|
||||
|
||||
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
bool require_basic_credentials;
|
||||
|
||||
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
|
||||
std::shared_ptr<Event> first_event;
|
||||
std::shared_ptr<Event> last_event;
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> timeout_ev;
|
||||
|
||||
size_t commands_sent;
|
||||
size_t bytes_sent;
|
||||
size_t commands_received;
|
||||
size_t bytes_received;
|
||||
|
||||
std::shared_ptr<ReplaySession::Event> create_event(
|
||||
Event::Type type, std::shared_ptr<Client> c, size_t line_num);
|
||||
void update_timeout_event();
|
||||
|
||||
void apply_default_mask(std::shared_ptr<Event> ev);
|
||||
void check_for_password(std::shared_ptr<const Event> ev) const;
|
||||
|
||||
static void dispatch_on_timeout(evutil_socket_t fd, short events, void* ctx);
|
||||
static void dispatch_on_command_received(
|
||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void dispatch_on_error(Channel& ch, short events);
|
||||
void on_command_received(
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
|
||||
void on_error(std::shared_ptr<Client> c, short events);
|
||||
|
||||
void execute_pending_events();
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Channel.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class ReplaySession {
|
||||
public:
|
||||
ReplaySession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
FILE* input_log,
|
||||
std::shared_ptr<ServerState> state,
|
||||
bool require_basic_credentials);
|
||||
ReplaySession(const ReplaySession&) = delete;
|
||||
ReplaySession(ReplaySession&&) = delete;
|
||||
ReplaySession& operator=(const ReplaySession&) = delete;
|
||||
ReplaySession& operator=(ReplaySession&&) = delete;
|
||||
~ReplaySession() = default;
|
||||
|
||||
void start();
|
||||
|
||||
private:
|
||||
struct Event {
|
||||
enum class Type {
|
||||
CONNECT = 0,
|
||||
DISCONNECT,
|
||||
SEND,
|
||||
RECEIVE,
|
||||
};
|
||||
Type type;
|
||||
uint64_t client_id;
|
||||
std::string data; // Only used for SEND and RECEIVE
|
||||
std::string mask; // Only used for RECEIVE
|
||||
bool allow_size_disparity;
|
||||
bool complete;
|
||||
size_t line_num;
|
||||
|
||||
std::shared_ptr<Event> next_event;
|
||||
|
||||
Event(Type type, uint64_t client_id, size_t line_num);
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct Client {
|
||||
uint64_t id;
|
||||
uint16_t port;
|
||||
Version version;
|
||||
Channel channel;
|
||||
std::deque<std::shared_ptr<Event>> receive_events;
|
||||
std::shared_ptr<Event> disconnect_event;
|
||||
|
||||
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
|
||||
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
bool require_basic_credentials;
|
||||
|
||||
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
|
||||
std::shared_ptr<Event> first_event;
|
||||
std::shared_ptr<Event> last_event;
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> timeout_ev;
|
||||
|
||||
size_t commands_sent;
|
||||
size_t bytes_sent;
|
||||
size_t commands_received;
|
||||
size_t bytes_received;
|
||||
|
||||
std::shared_ptr<ReplaySession::Event> create_event(
|
||||
Event::Type type, std::shared_ptr<Client> c, size_t line_num);
|
||||
void update_timeout_event();
|
||||
|
||||
void apply_default_mask(std::shared_ptr<Event> ev);
|
||||
void check_for_password(std::shared_ptr<const Event> ev) const;
|
||||
|
||||
static void dispatch_on_timeout(evutil_socket_t fd, short events, void* ctx);
|
||||
static void dispatch_on_command_received(
|
||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void dispatch_on_error(Channel& ch, short events);
|
||||
void on_command_received(
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
|
||||
void on_error(std::shared_ptr<Client> c, short events);
|
||||
|
||||
void execute_pending_events();
|
||||
};
|
||||
|
||||
+4251
-4251
File diff suppressed because it is too large
Load Diff
+456
-456
@@ -1,456 +1,456 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "Server.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
extern const std::unordered_set<uint32_t> v2_crypt_initial_client_commands;
|
||||
extern const std::unordered_set<uint32_t> v3_crypt_initial_client_commands;
|
||||
extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
|
||||
|
||||
constexpr size_t GC_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
|
||||
|
||||
// TODO: Many of these functions should take a Channel& instead of a
|
||||
// shared_ptr<Client>. Refactor functions appropriately.
|
||||
|
||||
// Note: There are so many versions of this function for a few reasons:
|
||||
// - There are a lot of different target types (sometimes we want to send a
|
||||
// command to one client, sometimes to everyone in a lobby, etc.)
|
||||
// - For the const void* versions, the data and size arguments should not be
|
||||
// independently optional - this can lead to bugs where a non-null data
|
||||
// pointer is given but size is accidentally not given (e.g. if the type of
|
||||
// data in the calling function is changed from string to void*).
|
||||
|
||||
template <typename CmdT>
|
||||
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) {
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive game commands; adding to queue");
|
||||
auto& q_cmd = c->game_join_command_queue->emplace_back();
|
||||
q_cmd.command = command;
|
||||
q_cmd.flag = flag;
|
||||
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd
|
||||
// and move it into q_cmd somehow, so q_cmd can free it when needed?
|
||||
q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
|
||||
} else {
|
||||
send_command(c, command, flag, &cmd, sizeof(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag, const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag) {
|
||||
send_command(c, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command_excluding_client(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
||||
const void* data, size_t size);
|
||||
|
||||
inline void send_command_excluding_client(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag) {
|
||||
send_command_excluding_client(l, c, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const void* data, size_t size);
|
||||
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const std::string& data) {
|
||||
send_command_if_not_loading(l, command, flag, data.data(), data.size());
|
||||
}
|
||||
template <typename StructT>
|
||||
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const StructT& data) {
|
||||
send_command_if_not_loading(l, command, flag, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag,
|
||||
const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) {
|
||||
send_command(l, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
|
||||
uint32_t flag, const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
|
||||
uint32_t flag) {
|
||||
send_command(s, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT>
|
||||
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const StructT& data) {
|
||||
send_command(c, command, flag, &data, sizeof(data));
|
||||
}
|
||||
|
||||
template <typename TargetT>
|
||||
static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const std::string& data) {
|
||||
send_command(c, command, flag, data.data(), data.size());
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT>
|
||||
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const std::vector<StructT>& data) {
|
||||
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
|
||||
}
|
||||
|
||||
template <typename StructT>
|
||||
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
|
||||
const std::vector<StructT>& data) {
|
||||
ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT, typename EntryT>
|
||||
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
|
||||
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
|
||||
all_data.append(reinterpret_cast<const char*>(array_data.data()),
|
||||
array_data.size() * sizeof(EntryT));
|
||||
send_command(c, command, flag, all_data.data(), all_data.size());
|
||||
}
|
||||
|
||||
void send_command_with_header(Channel& c, const void* data, size_t size);
|
||||
|
||||
enum SendServerInitFlag {
|
||||
IS_INITIAL_CONNECTION = 0x01,
|
||||
USE_SECONDARY_MESSAGE = 0x02,
|
||||
};
|
||||
|
||||
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
|
||||
prepare_server_init_contents_console(
|
||||
uint32_t server_key, uint32_t client_key, uint8_t flags);
|
||||
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
|
||||
prepare_server_init_contents_bb(
|
||||
const parray<uint8_t, 0x30>& server_key,
|
||||
const parray<uint8_t, 0x30>& client_key,
|
||||
uint8_t flags);
|
||||
void send_server_init(std::shared_ptr<Client> c, uint8_t flags);
|
||||
void send_update_client_config(std::shared_ptr<Client> c, bool always_send);
|
||||
|
||||
void empty_function_call_response_handler(uint32_t, uint32_t);
|
||||
|
||||
void send_quest_buffer_overflow(std::shared_ptr<Client> c);
|
||||
void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
|
||||
std::string prepare_send_function_call_data(
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t checksum_addr,
|
||||
uint32_t checksum_size,
|
||||
uint32_t override_relocations_offset,
|
||||
bool use_encrypted_format);
|
||||
void send_function_call(
|
||||
Channel& ch,
|
||||
const Client::Config& client_config,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0,
|
||||
uint32_t override_relocations_offset = 0);
|
||||
void send_function_call(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0,
|
||||
uint32_t override_relocations_offset = 0);
|
||||
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
|
||||
|
||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||
void send_pc_console_split_reconnect(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t address,
|
||||
uint16_t pc_port,
|
||||
uint16_t console_port);
|
||||
|
||||
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
||||
void send_system_file_bb(std::shared_ptr<Client> c);
|
||||
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataBBPreview* preview);
|
||||
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
|
||||
void send_guild_card_header_bb(std::shared_ptr<Client> c);
|
||||
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
|
||||
void send_stream_file_index_bb(std::shared_ptr<Client> c);
|
||||
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
|
||||
void send_approve_player_choice_bb(std::shared_ptr<Client> c);
|
||||
void send_complete_player_bb(std::shared_ptr<Client> c);
|
||||
|
||||
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
|
||||
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_quest_info(std::shared_ptr<Client> c, const std::string& text, uint8_t flag, bool is_download_quest);
|
||||
void send_lobby_message_box(std::shared_ptr<Client> c, const std::string& text, bool left_side_on_bb = false);
|
||||
void send_ship_info(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_ship_info(Channel& ch, const std::string& text);
|
||||
void send_text_message(Channel& ch, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
|
||||
void send_scrolling_message_bb(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_text_or_scrolling_message(std::shared_ptr<Client> c, const std::string& text, const std::string& scrolling);
|
||||
void send_text_or_scrolling_message(
|
||||
std::shared_ptr<Lobby> l, std::shared_ptr<Client> exclude_c, const std::string& text, const std::string& scrolling);
|
||||
|
||||
std::string prepare_chat_data(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
uint8_t from_client_id,
|
||||
const std::string& from_name,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_chat_message_from_client(
|
||||
Channel& ch,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_prepared_chat_message(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& prepared_data);
|
||||
void send_prepared_chat_message(
|
||||
std::shared_ptr<Lobby> l,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& prepared_data);
|
||||
void send_chat_message(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_simple_mail(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text);
|
||||
void send_simple_mail(
|
||||
std::shared_ptr<ServerState> s,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text);
|
||||
|
||||
template <typename TargetT>
|
||||
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
|
||||
TargetT& t, const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
std::string buf = string_vprintf(format, va);
|
||||
va_end(va);
|
||||
return send_text_message(t, buf.c_str());
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
|
||||
std::shared_ptr<ServerState> s, const char* format, ...);
|
||||
|
||||
void send_info_board(std::shared_ptr<Client> c);
|
||||
|
||||
void send_choice_search_choices(std::shared_ptr<Client> c);
|
||||
|
||||
void send_card_search_result(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Client> result,
|
||||
std::shared_ptr<Lobby> result_lobby);
|
||||
|
||||
void send_guild_card(
|
||||
Channel& ch,
|
||||
uint32_t guild_card_number,
|
||||
uint64_t xb_user_id,
|
||||
const std::string& name,
|
||||
const std::string& team_name,
|
||||
const std::string& description,
|
||||
uint8_t language,
|
||||
uint8_t section_id,
|
||||
uint8_t char_class);
|
||||
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
|
||||
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
|
||||
void send_game_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
bool is_spectator_team_list,
|
||||
bool is_tournament_game_list);
|
||||
void send_quest_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
|
||||
bool is_download_menu);
|
||||
void send_quest_categories_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const QuestIndex> quest_index,
|
||||
QuestMenuType menu_type,
|
||||
Episode episode);
|
||||
void send_lobby_list(std::shared_ptr<Client> c);
|
||||
|
||||
void send_player_records(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client = nullptr);
|
||||
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_update_lobby_data_bb(std::shared_ptr<Client> c);
|
||||
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
|
||||
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
|
||||
void send_self_leave_notification(std::shared_ptr<Client> c);
|
||||
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false);
|
||||
|
||||
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
|
||||
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
|
||||
|
||||
void send_arrow_update(std::shared_ptr<Lobby> l);
|
||||
void send_unblock_join(std::shared_ptr<Client> c);
|
||||
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
|
||||
|
||||
enum PlayerStatsChange {
|
||||
SUBTRACT_HP = 0,
|
||||
SUBTRACT_TP = 1,
|
||||
SUBTRACT_MESETA = 2,
|
||||
ADD_HP = 3,
|
||||
ADD_TP = 4,
|
||||
};
|
||||
|
||||
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_remove_conditions(std::shared_ptr<Client> c);
|
||||
void send_remove_conditions(Channel& ch, uint16_t client_id);
|
||||
void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private);
|
||||
void send_warp(std::shared_ptr<Client> c, uint32_t floor, bool is_private);
|
||||
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
|
||||
|
||||
void send_ep3_change_music(Channel& ch, uint32_t song);
|
||||
void send_revive_player(std::shared_ptr<Client> c);
|
||||
|
||||
void send_game_join_sync_command(
|
||||
std::shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
|
||||
void send_game_join_sync_command(
|
||||
std::shared_ptr<Client> c, const std::string& data, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
|
||||
void send_game_join_sync_command_compressed(
|
||||
std::shared_ptr<Client> c,
|
||||
const void* data,
|
||||
size_t size,
|
||||
size_t decompressed_size,
|
||||
uint8_t dc_nte_sc,
|
||||
uint8_t dc_11_2000_sc,
|
||||
uint8_t sc);
|
||||
void send_game_item_state(std::shared_ptr<Client> c);
|
||||
void send_game_enemy_state(std::shared_ptr<Client> c);
|
||||
void send_game_object_state(std::shared_ptr<Client> c);
|
||||
void send_game_set_state(std::shared_ptr<Client> c);
|
||||
void send_game_flag_state(std::shared_ptr<Client> c);
|
||||
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
|
||||
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_stacked_item_to_channel(
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
|
||||
void send_drop_stacked_item_to_lobby(
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
|
||||
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
|
||||
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
|
||||
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
|
||||
void send_destroy_item_to_lobby(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
|
||||
void send_destroy_floor_item_to_client(std::shared_ptr<Client> c, uint32_t item_id, uint32_t floor);
|
||||
void send_item_identify_result(std::shared_ptr<Client> c);
|
||||
void send_bank(std::shared_ptr<Client> c);
|
||||
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
|
||||
void send_level_up(std::shared_ptr<Client> c);
|
||||
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
|
||||
void send_set_exp_multiplier(std::shared_ptr<Lobby> l);
|
||||
void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<size_t>& indexes);
|
||||
|
||||
void send_quest_function_call(Channel& ch, uint16_t function_id);
|
||||
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t function_id);
|
||||
|
||||
void send_ep3_card_list_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_media_update(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t type,
|
||||
uint32_t which,
|
||||
const std::string& compressed_data);
|
||||
void send_ep3_rank_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
|
||||
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
|
||||
|
||||
void send_ep3_confirm_tournament_entry(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_tournament_list(
|
||||
std::shared_ptr<Client> c,
|
||||
bool is_for_spectator_team_create);
|
||||
void send_ep3_tournament_entry_list(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t,
|
||||
bool is_for_spectator_team_create);
|
||||
void send_ep3_tournament_info(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
|
||||
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
|
||||
|
||||
void send_ep3_tournament_details(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_game_details(
|
||||
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_card_auction(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
|
||||
|
||||
// Pass mask_key = 0 to unmask the command
|
||||
void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key);
|
||||
|
||||
enum class QuestFileType {
|
||||
ONLINE = 0,
|
||||
DOWNLOAD_WITHOUT_PVR,
|
||||
DOWNLOAD_WITH_PVR,
|
||||
EPISODE_3,
|
||||
GBA_DEMO,
|
||||
};
|
||||
|
||||
void send_open_quest_file(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& quest_name,
|
||||
const std::string& filename,
|
||||
const std::string& xb_filename,
|
||||
uint32_t quest_number,
|
||||
QuestFileType type,
|
||||
std::shared_ptr<const std::string> contents);
|
||||
void send_quest_file_chunk(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& filename,
|
||||
size_t chunk_index,
|
||||
const void* data,
|
||||
size_t size,
|
||||
bool is_download_quest);
|
||||
bool send_quest_barrier_if_all_clients_ready(std::shared_ptr<Lobby> l);
|
||||
bool send_ep3_start_tournament_deck_select_if_all_clients_ready(std::shared_ptr<Lobby> l);
|
||||
|
||||
void send_server_time(std::shared_ptr<Client> c);
|
||||
|
||||
void send_change_event(std::shared_ptr<Client> c, uint8_t new_event);
|
||||
void send_change_event(std::shared_ptr<Lobby> l, uint8_t new_event);
|
||||
void send_change_event(std::shared_ptr<ServerState> s, uint8_t new_event);
|
||||
|
||||
void send_team_membership_info(std::shared_ptr<Client> c); // 12EA
|
||||
void send_update_team_metadata_for_client(std::shared_ptr<Client> c); // 15EA (to all clients in lobby, with only c's data)
|
||||
void send_all_nearby_team_metadatas_to_client(std::shared_ptr<Client> c, bool is_13EA); // 13EA/15EA (to only c, with all lobby clients' data)
|
||||
void send_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
|
||||
void send_team_member_list(std::shared_ptr<Client> c); // 09EA
|
||||
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA
|
||||
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
|
||||
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "Server.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
extern const std::unordered_set<uint32_t> v2_crypt_initial_client_commands;
|
||||
extern const std::unordered_set<uint32_t> v3_crypt_initial_client_commands;
|
||||
extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
|
||||
|
||||
constexpr size_t GC_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
|
||||
|
||||
// TODO: Many of these functions should take a Channel& instead of a
|
||||
// shared_ptr<Client>. Refactor functions appropriately.
|
||||
|
||||
// Note: There are so many versions of this function for a few reasons:
|
||||
// - There are a lot of different target types (sometimes we want to send a
|
||||
// command to one client, sometimes to everyone in a lobby, etc.)
|
||||
// - For the const void* versions, the data and size arguments should not be
|
||||
// independently optional - this can lead to bugs where a non-null data
|
||||
// pointer is given but size is accidentally not given (e.g. if the type of
|
||||
// data in the calling function is changed from string to void*).
|
||||
|
||||
template <typename CmdT>
|
||||
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) {
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive game commands; adding to queue");
|
||||
auto& q_cmd = c->game_join_command_queue->emplace_back();
|
||||
q_cmd.command = command;
|
||||
q_cmd.flag = flag;
|
||||
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd
|
||||
// and move it into q_cmd somehow, so q_cmd can free it when needed?
|
||||
q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
|
||||
} else {
|
||||
send_command(c, command, flag, &cmd, sizeof(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag, const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag) {
|
||||
send_command(c, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command_excluding_client(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
||||
const void* data, size_t size);
|
||||
|
||||
inline void send_command_excluding_client(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, uint16_t command, uint32_t flag) {
|
||||
send_command_excluding_client(l, c, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const void* data, size_t size);
|
||||
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const std::string& data) {
|
||||
send_command_if_not_loading(l, command, flag, data.data(), data.size());
|
||||
}
|
||||
template <typename StructT>
|
||||
inline void send_command_if_not_loading(std::shared_ptr<Lobby> l,
|
||||
uint16_t command, uint32_t flag, const StructT& data) {
|
||||
send_command_if_not_loading(l, command, flag, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag,
|
||||
const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag) {
|
||||
send_command(l, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
|
||||
uint32_t flag, const void* data, size_t size);
|
||||
|
||||
inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
|
||||
uint32_t flag) {
|
||||
send_command(s, command, flag, nullptr, 0);
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT>
|
||||
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const StructT& data) {
|
||||
send_command(c, command, flag, &data, sizeof(data));
|
||||
}
|
||||
|
||||
template <typename TargetT>
|
||||
static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const std::string& data) {
|
||||
send_command(c, command, flag, data.data(), data.size());
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT>
|
||||
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const std::vector<StructT>& data) {
|
||||
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
|
||||
}
|
||||
|
||||
template <typename StructT>
|
||||
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
|
||||
const std::vector<StructT>& data) {
|
||||
ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
|
||||
}
|
||||
|
||||
template <typename TargetT, typename StructT, typename EntryT>
|
||||
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
|
||||
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
|
||||
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
|
||||
all_data.append(reinterpret_cast<const char*>(array_data.data()),
|
||||
array_data.size() * sizeof(EntryT));
|
||||
send_command(c, command, flag, all_data.data(), all_data.size());
|
||||
}
|
||||
|
||||
void send_command_with_header(Channel& c, const void* data, size_t size);
|
||||
|
||||
enum SendServerInitFlag {
|
||||
IS_INITIAL_CONNECTION = 0x01,
|
||||
USE_SECONDARY_MESSAGE = 0x02,
|
||||
};
|
||||
|
||||
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
|
||||
prepare_server_init_contents_console(
|
||||
uint32_t server_key, uint32_t client_key, uint8_t flags);
|
||||
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
|
||||
prepare_server_init_contents_bb(
|
||||
const parray<uint8_t, 0x30>& server_key,
|
||||
const parray<uint8_t, 0x30>& client_key,
|
||||
uint8_t flags);
|
||||
void send_server_init(std::shared_ptr<Client> c, uint8_t flags);
|
||||
void send_update_client_config(std::shared_ptr<Client> c, bool always_send);
|
||||
|
||||
void empty_function_call_response_handler(uint32_t, uint32_t);
|
||||
|
||||
void send_quest_buffer_overflow(std::shared_ptr<Client> c);
|
||||
void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
|
||||
std::string prepare_send_function_call_data(
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t checksum_addr,
|
||||
uint32_t checksum_size,
|
||||
uint32_t override_relocations_offset,
|
||||
bool use_encrypted_format);
|
||||
void send_function_call(
|
||||
Channel& ch,
|
||||
const Client::Config& client_config,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0,
|
||||
uint32_t override_relocations_offset = 0);
|
||||
void send_function_call(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const CompiledFunctionCode> code,
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const void* suffix_data = nullptr,
|
||||
size_t suffix_size = 0,
|
||||
uint32_t checksum_addr = 0,
|
||||
uint32_t checksum_size = 0,
|
||||
uint32_t override_relocations_offset = 0);
|
||||
bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t size, bool echo_to_lobby);
|
||||
|
||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||
void send_pc_console_split_reconnect(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t address,
|
||||
uint16_t pc_port,
|
||||
uint16_t console_port);
|
||||
|
||||
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
||||
void send_system_file_bb(std::shared_ptr<Client> c);
|
||||
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataBBPreview* preview);
|
||||
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
|
||||
void send_guild_card_header_bb(std::shared_ptr<Client> c);
|
||||
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
|
||||
void send_stream_file_index_bb(std::shared_ptr<Client> c);
|
||||
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
|
||||
void send_approve_player_choice_bb(std::shared_ptr<Client> c);
|
||||
void send_complete_player_bb(std::shared_ptr<Client> c);
|
||||
|
||||
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
|
||||
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_quest_info(std::shared_ptr<Client> c, const std::string& text, uint8_t flag, bool is_download_quest);
|
||||
void send_lobby_message_box(std::shared_ptr<Client> c, const std::string& text, bool left_side_on_bb = false);
|
||||
void send_ship_info(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_ship_info(Channel& ch, const std::string& text);
|
||||
void send_text_message(Channel& ch, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
|
||||
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
|
||||
void send_scrolling_message_bb(std::shared_ptr<Client> c, const std::string& text);
|
||||
void send_text_or_scrolling_message(std::shared_ptr<Client> c, const std::string& text, const std::string& scrolling);
|
||||
void send_text_or_scrolling_message(
|
||||
std::shared_ptr<Lobby> l, std::shared_ptr<Client> exclude_c, const std::string& text, const std::string& scrolling);
|
||||
|
||||
std::string prepare_chat_data(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
uint8_t from_client_id,
|
||||
const std::string& from_name,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_chat_message_from_client(
|
||||
Channel& ch,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_prepared_chat_message(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& prepared_data);
|
||||
void send_prepared_chat_message(
|
||||
std::shared_ptr<Lobby> l,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& prepared_data);
|
||||
void send_chat_message(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text,
|
||||
char private_flags);
|
||||
void send_simple_mail(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text);
|
||||
void send_simple_mail(
|
||||
std::shared_ptr<ServerState> s,
|
||||
uint32_t from_guild_card_number,
|
||||
const std::string& from_name,
|
||||
const std::string& text);
|
||||
|
||||
template <typename TargetT>
|
||||
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
|
||||
TargetT& t, const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
std::string buf = string_vprintf(format, va);
|
||||
va_end(va);
|
||||
return send_text_message(t, buf.c_str());
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
|
||||
std::shared_ptr<ServerState> s, const char* format, ...);
|
||||
|
||||
void send_info_board(std::shared_ptr<Client> c);
|
||||
|
||||
void send_choice_search_choices(std::shared_ptr<Client> c);
|
||||
|
||||
void send_card_search_result(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Client> result,
|
||||
std::shared_ptr<Lobby> result_lobby);
|
||||
|
||||
void send_guild_card(
|
||||
Channel& ch,
|
||||
uint32_t guild_card_number,
|
||||
uint64_t xb_user_id,
|
||||
const std::string& name,
|
||||
const std::string& team_name,
|
||||
const std::string& description,
|
||||
uint8_t language,
|
||||
uint8_t section_id,
|
||||
uint8_t char_class);
|
||||
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
|
||||
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
|
||||
void send_game_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
bool is_spectator_team_list,
|
||||
bool is_tournament_game_list);
|
||||
void send_quest_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
|
||||
bool is_download_menu);
|
||||
void send_quest_categories_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const QuestIndex> quest_index,
|
||||
QuestMenuType menu_type,
|
||||
Episode episode);
|
||||
void send_lobby_list(std::shared_ptr<Client> c);
|
||||
|
||||
void send_player_records(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client = nullptr);
|
||||
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_update_lobby_data_bb(std::shared_ptr<Client> c);
|
||||
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
|
||||
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
|
||||
void send_self_leave_notification(std::shared_ptr<Client> c);
|
||||
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false);
|
||||
|
||||
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
|
||||
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
|
||||
|
||||
void send_arrow_update(std::shared_ptr<Lobby> l);
|
||||
void send_unblock_join(std::shared_ptr<Client> c);
|
||||
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
|
||||
|
||||
enum PlayerStatsChange {
|
||||
SUBTRACT_HP = 0,
|
||||
SUBTRACT_TP = 1,
|
||||
SUBTRACT_MESETA = 2,
|
||||
ADD_HP = 3,
|
||||
ADD_TP = 4,
|
||||
};
|
||||
|
||||
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_remove_conditions(std::shared_ptr<Client> c);
|
||||
void send_remove_conditions(Channel& ch, uint16_t client_id);
|
||||
void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private);
|
||||
void send_warp(std::shared_ptr<Client> c, uint32_t floor, bool is_private);
|
||||
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
|
||||
|
||||
void send_ep3_change_music(Channel& ch, uint32_t song);
|
||||
void send_revive_player(std::shared_ptr<Client> c);
|
||||
|
||||
void send_game_join_sync_command(
|
||||
std::shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
|
||||
void send_game_join_sync_command(
|
||||
std::shared_ptr<Client> c, const std::string& data, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc);
|
||||
void send_game_join_sync_command_compressed(
|
||||
std::shared_ptr<Client> c,
|
||||
const void* data,
|
||||
size_t size,
|
||||
size_t decompressed_size,
|
||||
uint8_t dc_nte_sc,
|
||||
uint8_t dc_11_2000_sc,
|
||||
uint8_t sc);
|
||||
void send_game_item_state(std::shared_ptr<Client> c);
|
||||
void send_game_enemy_state(std::shared_ptr<Client> c);
|
||||
void send_game_object_state(std::shared_ptr<Client> c);
|
||||
void send_game_set_state(std::shared_ptr<Client> c);
|
||||
void send_game_flag_state(std::shared_ptr<Client> c);
|
||||
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
|
||||
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_stacked_item_to_channel(
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
|
||||
void send_drop_stacked_item_to_lobby(
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
|
||||
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
|
||||
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
|
||||
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
|
||||
void send_destroy_item_to_lobby(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
|
||||
void send_destroy_floor_item_to_client(std::shared_ptr<Client> c, uint32_t item_id, uint32_t floor);
|
||||
void send_item_identify_result(std::shared_ptr<Client> c);
|
||||
void send_bank(std::shared_ptr<Client> c);
|
||||
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
|
||||
void send_level_up(std::shared_ptr<Client> c);
|
||||
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
|
||||
void send_set_exp_multiplier(std::shared_ptr<Lobby> l);
|
||||
void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<size_t>& indexes);
|
||||
|
||||
void send_quest_function_call(Channel& ch, uint16_t function_id);
|
||||
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t function_id);
|
||||
|
||||
void send_ep3_card_list_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_media_update(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t type,
|
||||
uint32_t which,
|
||||
const std::string& compressed_data);
|
||||
void send_ep3_rank_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
|
||||
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
|
||||
|
||||
void send_ep3_confirm_tournament_entry(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_tournament_list(
|
||||
std::shared_ptr<Client> c,
|
||||
bool is_for_spectator_team_create);
|
||||
void send_ep3_tournament_entry_list(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t,
|
||||
bool is_for_spectator_team_create);
|
||||
void send_ep3_tournament_info(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
|
||||
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
|
||||
|
||||
void send_ep3_tournament_details(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_game_details(
|
||||
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_card_auction(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
|
||||
|
||||
// Pass mask_key = 0 to unmask the command
|
||||
void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key);
|
||||
|
||||
enum class QuestFileType {
|
||||
ONLINE = 0,
|
||||
DOWNLOAD_WITHOUT_PVR,
|
||||
DOWNLOAD_WITH_PVR,
|
||||
EPISODE_3,
|
||||
GBA_DEMO,
|
||||
};
|
||||
|
||||
void send_open_quest_file(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& quest_name,
|
||||
const std::string& filename,
|
||||
const std::string& xb_filename,
|
||||
uint32_t quest_number,
|
||||
QuestFileType type,
|
||||
std::shared_ptr<const std::string> contents);
|
||||
void send_quest_file_chunk(
|
||||
std::shared_ptr<Client> c,
|
||||
const std::string& filename,
|
||||
size_t chunk_index,
|
||||
const void* data,
|
||||
size_t size,
|
||||
bool is_download_quest);
|
||||
bool send_quest_barrier_if_all_clients_ready(std::shared_ptr<Lobby> l);
|
||||
bool send_ep3_start_tournament_deck_select_if_all_clients_ready(std::shared_ptr<Lobby> l);
|
||||
|
||||
void send_server_time(std::shared_ptr<Client> c);
|
||||
|
||||
void send_change_event(std::shared_ptr<Client> c, uint8_t new_event);
|
||||
void send_change_event(std::shared_ptr<Lobby> l, uint8_t new_event);
|
||||
void send_change_event(std::shared_ptr<ServerState> s, uint8_t new_event);
|
||||
|
||||
void send_team_membership_info(std::shared_ptr<Client> c); // 12EA
|
||||
void send_update_team_metadata_for_client(std::shared_ptr<Client> c); // 15EA (to all clients in lobby, with only c's data)
|
||||
void send_all_nearby_team_metadatas_to_client(std::shared_ptr<Client> c, bool is_13EA); // 13EA/15EA (to only c, with all lobby clients' data)
|
||||
void send_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
|
||||
void send_team_member_list(std::shared_ptr<Client> c); // 09EA
|
||||
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA
|
||||
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
|
||||
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
|
||||
|
||||
+353
-353
@@ -1,353 +1,353 @@
|
||||
#include "Server.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
void Server::disconnect_client(shared_ptr<Client> c) {
|
||||
if (c->channel.virtual_network_id) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
|
||||
} else if (c->channel.bev) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64, c->id);
|
||||
} else {
|
||||
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
|
||||
}
|
||||
|
||||
this->state->channel_to_client.erase(&c->channel);
|
||||
c->channel.disconnect();
|
||||
|
||||
try {
|
||||
on_disconnect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client disconnect cleanup: %s", e.what());
|
||||
}
|
||||
|
||||
// We can't just let c be destroyed here, since disconnect_client can be
|
||||
// called from within the client's channel's receive handler. So, we instead
|
||||
// move it to another set, which we'll clear in an immediately-enqueued
|
||||
// callback after the current event. This will also call the client's
|
||||
// disconnect hooks (if any).
|
||||
this->clients_to_destroy.insert(std::move(c));
|
||||
this->enqueue_destroy_clients();
|
||||
}
|
||||
|
||||
void Server::enqueue_destroy_clients() {
|
||||
auto tv = usecs_to_timeval(0);
|
||||
event_add(this->destroy_clients_ev.get(), &tv);
|
||||
}
|
||||
|
||||
void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->destroy_clients();
|
||||
}
|
||||
|
||||
void Server::destroy_clients() {
|
||||
for (auto c_it = this->clients_to_destroy.begin();
|
||||
c_it != this->clients_to_destroy.end();
|
||||
c_it = this->clients_to_destroy.erase(c_it)) {
|
||||
auto c = *c_it;
|
||||
// Note: It's important to move the disconnect hooks out of the client here
|
||||
// because the hooks could modify c->disconnect_hooks while it's being
|
||||
// iterated here, which would invalidate these iterators.
|
||||
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||
for (auto h_it : hooks) {
|
||||
try {
|
||||
h_it.second();
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::dispatch_on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd,
|
||||
struct sockaddr* address, int socklen, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen);
|
||||
}
|
||||
|
||||
void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->state->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
try {
|
||||
listening_socket = &this->listening_sockets.at(listen_fd);
|
||||
} catch (const out_of_range& e) {
|
||||
server_log.warning("Can\'t determine version for socket %d; disconnecting client",
|
||||
listen_fd);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
auto c = make_shared<Client>(this->shared_from_this(), bev, 0, listening_socket->version, listening_socket->behavior);
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
try {
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::connect_virtual_client(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint32_t address,
|
||||
uint16_t client_port,
|
||||
uint16_t server_port,
|
||||
Version version,
|
||||
ServerBehavior initial_state) {
|
||||
auto c = make_shared<Client>(this->shared_from_this(), bev, virtual_network_id, version, initial_state);
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info(
|
||||
"Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI",
|
||||
c->id,
|
||||
virtual_network_id,
|
||||
server_port,
|
||||
name_for_enum(version),
|
||||
name_for_enum(initial_state));
|
||||
|
||||
// Manually set the remote address, since the bufferevent has no fd and the
|
||||
// Channel constructor can't figure out the virtual remote address
|
||||
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
|
||||
remote_sin->sin_family = AF_INET;
|
||||
remote_sin->sin_addr.s_addr = htonl(address);
|
||||
remote_sin->sin_port = htons(client_port);
|
||||
|
||||
try {
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.error("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::connect_virtual_client(shared_ptr<Client> c, Channel&& ch) {
|
||||
c->channel.replace_with(std::move(ch), Server::on_client_input, Server::on_client_error, this, string_printf("C-%" PRIX64, c->id));
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
server_log.info("Client C-%" PRIX64 " added to game server", c->id);
|
||||
}
|
||||
|
||||
void Server::on_listen_error(struct evconnlistener* listener) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.error("Failure on listening socket %d: %d (%s)",
|
||||
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
|
||||
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
Server* server = reinterpret_cast<Server*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
|
||||
|
||||
if (c->should_disconnect) {
|
||||
server->disconnect_client(c);
|
||||
} else {
|
||||
if (server->state->catch_handler_exceptions) {
|
||||
try {
|
||||
on_command(c, command, flag, data);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
}
|
||||
} else {
|
||||
on_command(c, command, flag, data);
|
||||
}
|
||||
if (c->should_disconnect) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::on_client_error(Channel& ch, short events) {
|
||||
Server* server = reinterpret_cast<Server*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.warning("Client caused error %d (%s)", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
Server::Server(
|
||||
shared_ptr<struct event_base> base,
|
||||
shared_ptr<ServerState> state)
|
||||
: base(base),
|
||||
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
|
||||
state(state) {}
|
||||
|
||||
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s",
|
||||
socket_path.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version, behavior);
|
||||
}
|
||||
|
||||
void Server::listen(
|
||||
const std::string& addr_str,
|
||||
const string& addr,
|
||||
int port,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
if (port == 0) {
|
||||
this->listen(addr_str, addr, version, behavior);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d as %s",
|
||||
netloc_str.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version, behavior);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
|
||||
this->listen(addr_str, "", port, version, behavior);
|
||||
}
|
||||
|
||||
Server::ListeningSocket::ListeningSocket(
|
||||
Server* s, const std::string& addr_str,
|
||||
int fd, Version version, ServerBehavior behavior)
|
||||
: addr_str(addr_str),
|
||||
fd(fd),
|
||||
version(version),
|
||||
behavior(behavior),
|
||||
listener(evconnlistener_new(
|
||||
s->base.get(), Server::dispatch_on_listen_accept, s,
|
||||
LEV_OPT_REUSEABLE, 0, this->fd),
|
||||
evconnlistener_free) {
|
||||
evconnlistener_set_error_cb(
|
||||
this->listener.get(),
|
||||
Server::dispatch_on_listen_error);
|
||||
}
|
||||
|
||||
void Server::add_socket(
|
||||
const std::string& addr_str,
|
||||
int fd,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
this->listening_sockets.emplace(
|
||||
piecewise_construct, forward_as_tuple(fd),
|
||||
forward_as_tuple(this, addr_str, fd, version, behavior));
|
||||
}
|
||||
|
||||
shared_ptr<Client> Server::get_client() const {
|
||||
if (this->state->channel_to_client.empty()) {
|
||||
throw runtime_error("no clients on game server");
|
||||
}
|
||||
if (this->state->channel_to_client.size() > 1) {
|
||||
throw runtime_error("multiple clients on game server");
|
||||
}
|
||||
return this->state->channel_to_client.begin()->second;
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
|
||||
int64_t account_id_hex = -1;
|
||||
int64_t account_id_dec = -1;
|
||||
try {
|
||||
account_id_dec = stoul(ident, nullptr, 10);
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
try {
|
||||
account_id_hex = stoul(ident, nullptr, 16);
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
|
||||
// TODO: It's kind of not great that we do a linear search here, but this is
|
||||
// only used in the shell, so it should be pretty rare.
|
||||
vector<shared_ptr<Client>> results;
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
auto c = it.second;
|
||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->account->account_id == account_id_dec) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto p = c->character(false, false);
|
||||
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->channel.name == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (starts_with(c->channel.name, ident + " ")) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> Server::all_clients() const {
|
||||
vector<shared_ptr<Client>> ret;
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<struct event_base> Server::get_base() const {
|
||||
return this->base;
|
||||
}
|
||||
#include "Server.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
void Server::disconnect_client(shared_ptr<Client> c) {
|
||||
if (c->channel.virtual_network_id) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
|
||||
} else if (c->channel.bev) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64, c->id);
|
||||
} else {
|
||||
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
|
||||
}
|
||||
|
||||
this->state->channel_to_client.erase(&c->channel);
|
||||
c->channel.disconnect();
|
||||
|
||||
try {
|
||||
on_disconnect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client disconnect cleanup: %s", e.what());
|
||||
}
|
||||
|
||||
// We can't just let c be destroyed here, since disconnect_client can be
|
||||
// called from within the client's channel's receive handler. So, we instead
|
||||
// move it to another set, which we'll clear in an immediately-enqueued
|
||||
// callback after the current event. This will also call the client's
|
||||
// disconnect hooks (if any).
|
||||
this->clients_to_destroy.insert(std::move(c));
|
||||
this->enqueue_destroy_clients();
|
||||
}
|
||||
|
||||
void Server::enqueue_destroy_clients() {
|
||||
auto tv = usecs_to_timeval(0);
|
||||
event_add(this->destroy_clients_ev.get(), &tv);
|
||||
}
|
||||
|
||||
void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->destroy_clients();
|
||||
}
|
||||
|
||||
void Server::destroy_clients() {
|
||||
for (auto c_it = this->clients_to_destroy.begin();
|
||||
c_it != this->clients_to_destroy.end();
|
||||
c_it = this->clients_to_destroy.erase(c_it)) {
|
||||
auto c = *c_it;
|
||||
// Note: It's important to move the disconnect hooks out of the client here
|
||||
// because the hooks could modify c->disconnect_hooks while it's being
|
||||
// iterated here, which would invalidate these iterators.
|
||||
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||
for (auto h_it : hooks) {
|
||||
try {
|
||||
h_it.second();
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::dispatch_on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd,
|
||||
struct sockaddr* address, int socklen, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->on_listen_accept(listener, fd, address, socklen);
|
||||
}
|
||||
|
||||
void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->state->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
try {
|
||||
listening_socket = &this->listening_sockets.at(listen_fd);
|
||||
} catch (const out_of_range& e) {
|
||||
server_log.warning("Can\'t determine version for socket %d; disconnecting client",
|
||||
listen_fd);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
auto c = make_shared<Client>(this->shared_from_this(), bev, 0, listening_socket->version, listening_socket->behavior);
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)",
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
try {
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::connect_virtual_client(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint32_t address,
|
||||
uint16_t client_port,
|
||||
uint16_t server_port,
|
||||
Version version,
|
||||
ServerBehavior initial_state) {
|
||||
auto c = make_shared<Client>(this->shared_from_this(), bev, virtual_network_id, version, initial_state);
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info(
|
||||
"Client connected: C-%" PRIX64 " on virtual network N-%" PRIu64 " via T-%hu-%s-%s-VI",
|
||||
c->id,
|
||||
virtual_network_id,
|
||||
server_port,
|
||||
name_for_enum(version),
|
||||
name_for_enum(initial_state));
|
||||
|
||||
// Manually set the remote address, since the bufferevent has no fd and the
|
||||
// Channel constructor can't figure out the virtual remote address
|
||||
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
|
||||
remote_sin->sin_family = AF_INET;
|
||||
remote_sin->sin_addr.s_addr = htonl(address);
|
||||
remote_sin->sin_port = htons(client_port);
|
||||
|
||||
try {
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.error("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::connect_virtual_client(shared_ptr<Client> c, Channel&& ch) {
|
||||
c->channel.replace_with(std::move(ch), Server::on_client_input, Server::on_client_error, this, string_printf("C-%" PRIX64, c->id));
|
||||
this->state->channel_to_client.emplace(&c->channel, c);
|
||||
server_log.info("Client C-%" PRIX64 " added to game server", c->id);
|
||||
}
|
||||
|
||||
void Server::on_listen_error(struct evconnlistener* listener) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.error("Failure on listening socket %d: %d (%s)",
|
||||
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
|
||||
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
Server* server = reinterpret_cast<Server*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
|
||||
|
||||
if (c->should_disconnect) {
|
||||
server->disconnect_client(c);
|
||||
} else {
|
||||
if (server->state->catch_handler_exceptions) {
|
||||
try {
|
||||
on_command(c, command, flag, data);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
}
|
||||
} else {
|
||||
on_command(c, command, flag, data);
|
||||
}
|
||||
if (c->should_disconnect) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::on_client_error(Channel& ch, short events) {
|
||||
Server* server = reinterpret_cast<Server*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->state->channel_to_client.at(&ch);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.warning("Client caused error %d (%s)", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
Server::Server(
|
||||
shared_ptr<struct event_base> base,
|
||||
shared_ptr<ServerState> state)
|
||||
: base(base),
|
||||
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
|
||||
state(state) {}
|
||||
|
||||
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s",
|
||||
socket_path.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version, behavior);
|
||||
}
|
||||
|
||||
void Server::listen(
|
||||
const std::string& addr_str,
|
||||
const string& addr,
|
||||
int port,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
if (port == 0) {
|
||||
this->listen(addr_str, addr, version, behavior);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d as %s",
|
||||
netloc_str.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version, behavior);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
|
||||
this->listen(addr_str, "", port, version, behavior);
|
||||
}
|
||||
|
||||
Server::ListeningSocket::ListeningSocket(
|
||||
Server* s, const std::string& addr_str,
|
||||
int fd, Version version, ServerBehavior behavior)
|
||||
: addr_str(addr_str),
|
||||
fd(fd),
|
||||
version(version),
|
||||
behavior(behavior),
|
||||
listener(evconnlistener_new(
|
||||
s->base.get(), Server::dispatch_on_listen_accept, s,
|
||||
LEV_OPT_REUSEABLE, 0, this->fd),
|
||||
evconnlistener_free) {
|
||||
evconnlistener_set_error_cb(
|
||||
this->listener.get(),
|
||||
Server::dispatch_on_listen_error);
|
||||
}
|
||||
|
||||
void Server::add_socket(
|
||||
const std::string& addr_str,
|
||||
int fd,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
this->listening_sockets.emplace(
|
||||
piecewise_construct, forward_as_tuple(fd),
|
||||
forward_as_tuple(this, addr_str, fd, version, behavior));
|
||||
}
|
||||
|
||||
shared_ptr<Client> Server::get_client() const {
|
||||
if (this->state->channel_to_client.empty()) {
|
||||
throw runtime_error("no clients on game server");
|
||||
}
|
||||
if (this->state->channel_to_client.size() > 1) {
|
||||
throw runtime_error("multiple clients on game server");
|
||||
}
|
||||
return this->state->channel_to_client.begin()->second;
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident) const {
|
||||
int64_t account_id_hex = -1;
|
||||
int64_t account_id_dec = -1;
|
||||
try {
|
||||
account_id_dec = stoul(ident, nullptr, 10);
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
try {
|
||||
account_id_hex = stoul(ident, nullptr, 16);
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
|
||||
// TODO: It's kind of not great that we do a linear search here, but this is
|
||||
// only used in the shell, so it should be pretty rare.
|
||||
vector<shared_ptr<Client>> results;
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
auto c = it.second;
|
||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->account->account_id == account_id_dec) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto p = c->character(false, false);
|
||||
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->channel.name == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (starts_with(c->channel.name, ident + " ")) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> Server::all_clients() const {
|
||||
vector<shared_ptr<Client>> ret;
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<struct event_base> Server::get_base() const {
|
||||
return this->base;
|
||||
}
|
||||
|
||||
+82
-82
@@ -1,82 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class Server : public std::enable_shared_from_this<Server> {
|
||||
public:
|
||||
Server() = delete;
|
||||
Server(const Server&) = delete;
|
||||
Server(Server&&) = delete;
|
||||
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
|
||||
virtual ~Server() = default;
|
||||
|
||||
void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state);
|
||||
void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state);
|
||||
void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state);
|
||||
void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
|
||||
|
||||
void connect_virtual_client(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint32_t address,
|
||||
uint16_t client_port,
|
||||
uint16_t server_port,
|
||||
Version version,
|
||||
ServerBehavior initial_state);
|
||||
void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch);
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
std::shared_ptr<Client> get_client() const;
|
||||
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(const std::string& ident) const;
|
||||
std::vector<std::shared_ptr<Client>> all_clients() const;
|
||||
std::shared_ptr<struct event_base> get_base() const;
|
||||
|
||||
inline std::shared_ptr<ServerState> get_state() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string addr_str;
|
||||
int fd;
|
||||
Version version;
|
||||
ServerBehavior behavior;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
|
||||
ListeningSocket(
|
||||
Server* s,
|
||||
const std::string& name,
|
||||
int fd,
|
||||
Version version,
|
||||
ServerBehavior behavior);
|
||||
};
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
|
||||
void enqueue_destroy_clients();
|
||||
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
|
||||
void destroy_clients();
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
|
||||
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void on_client_error(Channel& ch, short events);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class Server : public std::enable_shared_from_this<Server> {
|
||||
public:
|
||||
Server() = delete;
|
||||
Server(const Server&) = delete;
|
||||
Server(Server&&) = delete;
|
||||
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
|
||||
virtual ~Server() = default;
|
||||
|
||||
void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state);
|
||||
void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state);
|
||||
void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state);
|
||||
void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
|
||||
|
||||
void connect_virtual_client(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint32_t address,
|
||||
uint16_t client_port,
|
||||
uint16_t server_port,
|
||||
Version version,
|
||||
ServerBehavior initial_state);
|
||||
void connect_virtual_client(std::shared_ptr<Client> c, Channel&& ch);
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
std::shared_ptr<Client> get_client() const;
|
||||
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(const std::string& ident) const;
|
||||
std::vector<std::shared_ptr<Client>> all_clients() const;
|
||||
std::shared_ptr<struct event_base> get_base() const;
|
||||
|
||||
inline std::shared_ptr<ServerState> get_state() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string addr_str;
|
||||
int fd;
|
||||
Version version;
|
||||
ServerBehavior behavior;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
|
||||
ListeningSocket(
|
||||
Server* s,
|
||||
const std::string& name,
|
||||
int fd,
|
||||
Version version,
|
||||
ServerBehavior behavior);
|
||||
};
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
|
||||
void enqueue_destroy_clients();
|
||||
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
|
||||
void destroy_clients();
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
|
||||
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void on_client_error(Channel& ch, short events);
|
||||
};
|
||||
|
||||
+1092
-1092
File diff suppressed because it is too large
Load Diff
+33
-33
@@ -1,33 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ServerShell : public std::enable_shared_from_this<ServerShell> {
|
||||
public:
|
||||
class exit_shell : public std::runtime_error {
|
||||
public:
|
||||
exit_shell();
|
||||
~exit_shell() = default;
|
||||
};
|
||||
|
||||
explicit ServerShell(std::shared_ptr<ServerState> state);
|
||||
ServerShell(const ServerShell&) = delete;
|
||||
ServerShell(ServerShell&&) = delete;
|
||||
ServerShell& operator=(const ServerShell&) = delete;
|
||||
ServerShell& operator=(ServerShell&&) = delete;
|
||||
~ServerShell();
|
||||
|
||||
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::thread th;
|
||||
|
||||
void thread_fn();
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ServerShell : public std::enable_shared_from_this<ServerShell> {
|
||||
public:
|
||||
class exit_shell : public std::runtime_error {
|
||||
public:
|
||||
exit_shell();
|
||||
~exit_shell() = default;
|
||||
};
|
||||
|
||||
explicit ServerShell(std::shared_ptr<ServerState> state);
|
||||
ServerShell(const ServerShell&) = delete;
|
||||
ServerShell(ServerShell&&) = delete;
|
||||
ServerShell& operator=(const ServerShell&) = delete;
|
||||
ServerShell& operator=(ServerShell&&) = delete;
|
||||
~ServerShell();
|
||||
|
||||
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::thread th;
|
||||
|
||||
void thread_fn();
|
||||
};
|
||||
|
||||
+2005
-2005
File diff suppressed because it is too large
Load Diff
+408
-408
@@ -1,408 +1,408 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Client.hh"
|
||||
#include "CommonItemSet.hh"
|
||||
#include "DNSServer.hh"
|
||||
#include "Episode3/DataIndexes.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "EventUtils.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "PatchServer.hh"
|
||||
#include "PlayerFilesManager.hh"
|
||||
#include "Quest.hh"
|
||||
#include "TeamIndex.hh"
|
||||
#include "WordSelectTable.hh"
|
||||
|
||||
// Forward declarations due to reference cycles
|
||||
class ProxyServer;
|
||||
class Server;
|
||||
class IPStackSimulator;
|
||||
|
||||
struct PortConfiguration {
|
||||
std::string name;
|
||||
std::string addr; // Blank = listen on all interfaces (default)
|
||||
uint16_t port;
|
||||
Version version;
|
||||
ServerBehavior behavior;
|
||||
};
|
||||
|
||||
struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
enum class RunShellBehavior {
|
||||
DEFAULT = 0,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
};
|
||||
enum class BehaviorSwitch {
|
||||
OFF = 0,
|
||||
OFF_BY_DEFAULT,
|
||||
ON_BY_DEFAULT,
|
||||
ON,
|
||||
};
|
||||
|
||||
static inline bool behavior_enabled(BehaviorSwitch b) {
|
||||
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
|
||||
}
|
||||
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
|
||||
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
|
||||
}
|
||||
|
||||
uint64_t creation_time;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
|
||||
std::string config_filename;
|
||||
std::shared_ptr<const JSON> config_json;
|
||||
bool is_replay = false;
|
||||
bool one_time_config_loaded = false;
|
||||
bool default_lobbies_created = false;
|
||||
|
||||
std::string name;
|
||||
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
|
||||
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
|
||||
std::string username;
|
||||
std::string dns_server_addr;
|
||||
uint16_t dns_server_port = 0;
|
||||
std::vector<std::string> ip_stack_addresses;
|
||||
std::vector<std::string> ppp_stack_addresses;
|
||||
std::vector<std::string> ppp_raw_addresses;
|
||||
std::vector<std::string> http_addresses;
|
||||
uint64_t client_ping_interval_usecs = 30000000;
|
||||
uint64_t client_idle_timeout_usecs = 60000000;
|
||||
uint64_t patch_client_idle_timeout_usecs = 300000000;
|
||||
bool ip_stack_debug = false;
|
||||
bool allow_unregistered_users = false;
|
||||
bool allow_pc_nte = false;
|
||||
bool use_temp_accounts_for_prototypes = true;
|
||||
bool allow_dc_pc_games = true;
|
||||
bool allow_gc_xb_games = true;
|
||||
bool enable_chat_commands = true;
|
||||
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||
uint32_t client_customization_name_color = 0x00000000;
|
||||
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
|
||||
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
|
||||
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
|
||||
uint8_t allowed_drop_modes_v3_normal = 0x1F;
|
||||
uint8_t allowed_drop_modes_v3_battle = 0x07;
|
||||
uint8_t allowed_drop_modes_v3_challenge = 0x07;
|
||||
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
|
||||
uint8_t allowed_drop_modes_v4_battle = 0x05;
|
||||
uint8_t allowed_drop_modes_v4_challenge = 0x05;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
|
||||
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
|
||||
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
|
||||
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
|
||||
uint64_t persistent_game_idle_timeout_usecs = 0;
|
||||
bool ep3_send_function_call_enabled = false;
|
||||
bool enable_v3_v4_protected_subcommands = false;
|
||||
bool catch_handler_exceptions = true;
|
||||
bool ep3_infinite_meseta = false;
|
||||
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
|
||||
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
|
||||
uint32_t ep3_final_round_meseta_bonus = 300;
|
||||
bool ep3_jukebox_is_free = false;
|
||||
uint32_t ep3_behavior_flags = 0;
|
||||
bool hide_download_commands = true;
|
||||
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
|
||||
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
|
||||
bool use_game_creator_section_id = false;
|
||||
bool default_rare_notifs_enabled_v1_v2 = false;
|
||||
bool default_rare_notifs_enabled_v3_v4 = false;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
|
||||
bool notify_server_for_max_level_achieved = false;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
|
||||
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
|
||||
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
|
||||
std::shared_ptr<const DOLFileIndex> dol_file_index;
|
||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
|
||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
|
||||
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
|
||||
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
|
||||
std::shared_ptr<const QuestIndex> default_quest_index;
|
||||
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v3;
|
||||
std::shared_ptr<const LevelTable> level_table_v4;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
std::shared_ptr<const GSLArchive> bb_data_gsl;
|
||||
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v2;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
|
||||
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
|
||||
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
|
||||
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
|
||||
std::shared_ptr<const TextIndex> text_index;
|
||||
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
|
||||
std::shared_ptr<const WordSelectTable> word_select_table;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
|
||||
struct QuestF960Result {
|
||||
uint32_t meseta_cost = 0;
|
||||
uint32_t base_probability = 0;
|
||||
uint32_t probability_upgrade = 0;
|
||||
std::array<std::vector<ItemData>, 7> results;
|
||||
|
||||
QuestF960Result() = default;
|
||||
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
|
||||
};
|
||||
|
||||
// Indexed as [type][difficulty][random_choice]
|
||||
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
|
||||
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
|
||||
std::vector<QuestF960Result> quest_F960_success_results;
|
||||
QuestF960Result quest_F960_failure_results;
|
||||
std::vector<ItemData> secret_lottery_results;
|
||||
uint16_t bb_global_exp_multiplier = 1;
|
||||
float exp_share_multiplier = 0.5;
|
||||
double server_global_drop_rate_multiplier = 1.0;
|
||||
|
||||
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
|
||||
|
||||
uint16_t ep3_card_auction_points = 0;
|
||||
uint16_t ep3_card_auction_min_size = 0;
|
||||
uint16_t ep3_card_auction_max_size = 0;
|
||||
struct CardAuctionPoolEntry {
|
||||
uint64_t probability;
|
||||
uint16_t card_id;
|
||||
uint16_t min_price;
|
||||
};
|
||||
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
|
||||
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
|
||||
struct Ep3LobbyBannerEntry {
|
||||
uint32_t type = 1;
|
||||
uint32_t which; // See B9 documentation in CommandFormats.hh
|
||||
std::string data;
|
||||
};
|
||||
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
|
||||
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<TeamIndex> team_index;
|
||||
JSON team_reward_defs_json;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu_v2;
|
||||
std::shared_ptr<const Menu> information_menu_v3;
|
||||
std::shared_ptr<std::vector<std::string>> information_contents_v2;
|
||||
std::shared_ptr<std::vector<std::string>> information_contents_v3;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
|
||||
std::pair<std::string, uint16_t> proxy_destination_patch;
|
||||
std::pair<std::string, uint16_t> proxy_destination_bb;
|
||||
std::string welcome_message;
|
||||
std::string pc_patch_server_message;
|
||||
std::string bb_patch_server_message;
|
||||
|
||||
std::shared_ptr<PlayerFilesManager> player_files_manager;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
|
||||
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
|
||||
std::shared_ptr<struct event> destroy_lobbies_event;
|
||||
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
|
||||
std::vector<uint32_t> client_customization_public_lobby_search_order;
|
||||
std::atomic<int32_t> next_lobby_id = 1;
|
||||
uint8_t pre_lobby_event = 0;
|
||||
int32_t ep3_menu_song = -1;
|
||||
|
||||
std::map<std::string, uint32_t> all_addresses;
|
||||
uint32_t local_address = 0;
|
||||
uint32_t external_address = 0;
|
||||
|
||||
bool proxy_allow_save_files = true;
|
||||
bool proxy_enable_login_options = false;
|
||||
|
||||
std::shared_ptr<IPStackSimulator> ip_stack_simulator;
|
||||
std::shared_ptr<DNSServer> dns_server;
|
||||
std::shared_ptr<ProxyServer> proxy_server;
|
||||
std::shared_ptr<Server> game_server;
|
||||
std::shared_ptr<PatchServer> pc_patch_server;
|
||||
std::shared_ptr<PatchServer> bb_patch_server;
|
||||
|
||||
explicit ServerState(const std::string& config_filename = "");
|
||||
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
|
||||
ServerState(const ServerState&) = delete;
|
||||
ServerState(ServerState&&) = delete;
|
||||
ServerState& operator=(const ServerState&) = delete;
|
||||
ServerState& operator=(ServerState&&) = delete;
|
||||
|
||||
void add_client_to_available_lobby(std::shared_ptr<Client> c);
|
||||
void remove_client_from_lobby(std::shared_ptr<Client> c);
|
||||
bool change_client_lobby(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Lobby> new_lobby,
|
||||
bool send_join_notification = true,
|
||||
ssize_t required_client_id = -1);
|
||||
|
||||
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> joining_client);
|
||||
|
||||
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
|
||||
std::vector<std::shared_ptr<Lobby>> all_lobbies();
|
||||
|
||||
std::shared_ptr<Lobby> create_lobby(bool is_game);
|
||||
void remove_lobby(std::shared_ptr<Lobby> l);
|
||||
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
|
||||
|
||||
std::shared_ptr<Client> find_client(
|
||||
const std::string* identifier = nullptr,
|
||||
uint64_t account_id = 0,
|
||||
std::shared_ptr<Lobby> l = nullptr);
|
||||
|
||||
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu(Version version) const;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
|
||||
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
|
||||
|
||||
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
|
||||
|
||||
std::shared_ptr<const LevelTable> level_table(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
|
||||
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
|
||||
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
|
||||
ItemData parse_item_description(Version version, const std::string& description) const;
|
||||
|
||||
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
|
||||
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
|
||||
return this->public_lobby_search_order(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
|
||||
}
|
||||
|
||||
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
|
||||
if (is_client_customization && this->client_customization_name_color) {
|
||||
return this->client_customization_name_color;
|
||||
}
|
||||
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
|
||||
}
|
||||
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
|
||||
return this->name_color_for_client(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
|
||||
}
|
||||
|
||||
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
|
||||
std::shared_ptr<const QuestIndex> quest_index(Version version) const;
|
||||
|
||||
size_t default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const;
|
||||
|
||||
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
|
||||
|
||||
std::shared_ptr<const std::string> load_bb_file(
|
||||
const std::string& patch_index_filename,
|
||||
const std::string& gsl_filename = "",
|
||||
const std::string& bb_directory_filename = "") const;
|
||||
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
|
||||
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
|
||||
|
||||
std::pair<std::string, uint16_t> parse_port_spec(const JSON& json) const;
|
||||
std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const;
|
||||
|
||||
template <typename T>
|
||||
inline void call_on_event_thread(std::function<T()>&& fn) {
|
||||
return ::call_on_event_thread<T>(this->base, std::move(fn));
|
||||
}
|
||||
inline void forward_to_event_thread(std::function<void()>&& fn) {
|
||||
::forward_to_event_thread(this->base, std::move(fn));
|
||||
}
|
||||
inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) {
|
||||
if (from_non_event_thread) {
|
||||
::forward_to_event_thread(this->base, std::move(fn));
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
|
||||
void update_dependent_server_configs() const;
|
||||
|
||||
// The following functions may only be called from a non-event thread if they
|
||||
// take a from_non_event_thread argument; any function that does not have this
|
||||
// argument must be called only from the event thread.
|
||||
void create_default_lobbies();
|
||||
void collect_network_addresses();
|
||||
void load_config_early();
|
||||
void load_config_late();
|
||||
void load_bb_private_keys(bool from_non_event_thread);
|
||||
void load_accounts(bool from_non_event_thread);
|
||||
void load_teams(bool from_non_event_thread);
|
||||
void load_patch_indexes(bool from_non_event_thread);
|
||||
void clear_map_file_caches();
|
||||
void load_battle_params(bool from_non_event_thread);
|
||||
void load_level_tables(bool from_non_event_thread);
|
||||
void load_text_index(bool from_non_event_thread);
|
||||
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
|
||||
std::shared_ptr<const ItemParameterTable> pmt,
|
||||
std::shared_ptr<const ItemData::StackLimits> limits,
|
||||
std::shared_ptr<const TextIndex> text_index) const;
|
||||
void load_item_name_indexes(bool from_non_event_thread);
|
||||
void load_drop_tables(bool from_non_event_thread);
|
||||
void load_item_definitions(bool from_non_event_thread);
|
||||
void load_set_data_tables(bool from_non_event_thread);
|
||||
void load_word_select_table(bool from_non_event_thread);
|
||||
void load_ep3_cards(bool from_non_event_thread);
|
||||
void load_ep3_maps(bool from_non_event_thread);
|
||||
void load_ep3_tournament_state(bool from_non_event_thread);
|
||||
void load_quest_index(bool from_non_event_thread);
|
||||
void compile_functions(bool from_non_event_thread);
|
||||
void load_dol_files(bool from_non_event_thread);
|
||||
void load_all();
|
||||
|
||||
void enqueue_destroy_lobbies();
|
||||
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
|
||||
|
||||
void disconnect_all_banned_clients();
|
||||
|
||||
std::string format_address_for_channel_name(const struct sockaddr_storage& remote_ss, uint64_t virtual_network);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Client.hh"
|
||||
#include "CommonItemSet.hh"
|
||||
#include "DNSServer.hh"
|
||||
#include "Episode3/DataIndexes.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "EventUtils.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "PatchServer.hh"
|
||||
#include "PlayerFilesManager.hh"
|
||||
#include "Quest.hh"
|
||||
#include "TeamIndex.hh"
|
||||
#include "WordSelectTable.hh"
|
||||
|
||||
// Forward declarations due to reference cycles
|
||||
class ProxyServer;
|
||||
class Server;
|
||||
class IPStackSimulator;
|
||||
|
||||
struct PortConfiguration {
|
||||
std::string name;
|
||||
std::string addr; // Blank = listen on all interfaces (default)
|
||||
uint16_t port;
|
||||
Version version;
|
||||
ServerBehavior behavior;
|
||||
};
|
||||
|
||||
struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
enum class RunShellBehavior {
|
||||
DEFAULT = 0,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
};
|
||||
enum class BehaviorSwitch {
|
||||
OFF = 0,
|
||||
OFF_BY_DEFAULT,
|
||||
ON_BY_DEFAULT,
|
||||
ON,
|
||||
};
|
||||
|
||||
static inline bool behavior_enabled(BehaviorSwitch b) {
|
||||
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
|
||||
}
|
||||
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
|
||||
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
|
||||
}
|
||||
|
||||
uint64_t creation_time;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
|
||||
std::string config_filename;
|
||||
std::shared_ptr<const JSON> config_json;
|
||||
bool is_replay = false;
|
||||
bool one_time_config_loaded = false;
|
||||
bool default_lobbies_created = false;
|
||||
|
||||
std::string name;
|
||||
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
|
||||
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
|
||||
std::string username;
|
||||
std::string dns_server_addr;
|
||||
uint16_t dns_server_port = 0;
|
||||
std::vector<std::string> ip_stack_addresses;
|
||||
std::vector<std::string> ppp_stack_addresses;
|
||||
std::vector<std::string> ppp_raw_addresses;
|
||||
std::vector<std::string> http_addresses;
|
||||
uint64_t client_ping_interval_usecs = 30000000;
|
||||
uint64_t client_idle_timeout_usecs = 60000000;
|
||||
uint64_t patch_client_idle_timeout_usecs = 300000000;
|
||||
bool ip_stack_debug = false;
|
||||
bool allow_unregistered_users = false;
|
||||
bool allow_pc_nte = false;
|
||||
bool use_temp_accounts_for_prototypes = true;
|
||||
bool allow_dc_pc_games = true;
|
||||
bool allow_gc_xb_games = true;
|
||||
bool enable_chat_commands = true;
|
||||
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||
uint32_t client_customization_name_color = 0x00000000;
|
||||
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
|
||||
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
|
||||
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
|
||||
uint8_t allowed_drop_modes_v3_normal = 0x1F;
|
||||
uint8_t allowed_drop_modes_v3_battle = 0x07;
|
||||
uint8_t allowed_drop_modes_v3_challenge = 0x07;
|
||||
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
|
||||
uint8_t allowed_drop_modes_v4_battle = 0x05;
|
||||
uint8_t allowed_drop_modes_v4_challenge = 0x05;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
|
||||
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
|
||||
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
|
||||
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
|
||||
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
|
||||
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
|
||||
uint64_t persistent_game_idle_timeout_usecs = 0;
|
||||
bool ep3_send_function_call_enabled = false;
|
||||
bool enable_v3_v4_protected_subcommands = false;
|
||||
bool catch_handler_exceptions = true;
|
||||
bool ep3_infinite_meseta = false;
|
||||
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
|
||||
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
|
||||
uint32_t ep3_final_round_meseta_bonus = 300;
|
||||
bool ep3_jukebox_is_free = false;
|
||||
uint32_t ep3_behavior_flags = 0;
|
||||
bool hide_download_commands = true;
|
||||
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
|
||||
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
|
||||
bool use_game_creator_section_id = false;
|
||||
bool default_rare_notifs_enabled_v1_v2 = false;
|
||||
bool default_rare_notifs_enabled_v3_v4 = false;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
|
||||
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
|
||||
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
|
||||
bool notify_server_for_max_level_achieved = false;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
|
||||
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
|
||||
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
|
||||
std::shared_ptr<const DOLFileIndex> dol_file_index;
|
||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
|
||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
|
||||
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
|
||||
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
|
||||
std::shared_ptr<const QuestIndex> default_quest_index;
|
||||
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v3;
|
||||
std::shared_ptr<const LevelTable> level_table_v4;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
std::shared_ptr<const GSLArchive> bb_data_gsl;
|
||||
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v2;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
|
||||
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
|
||||
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
|
||||
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
|
||||
std::shared_ptr<const TextIndex> text_index;
|
||||
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
|
||||
std::shared_ptr<const WordSelectTable> word_select_table;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
|
||||
struct QuestF960Result {
|
||||
uint32_t meseta_cost = 0;
|
||||
uint32_t base_probability = 0;
|
||||
uint32_t probability_upgrade = 0;
|
||||
std::array<std::vector<ItemData>, 7> results;
|
||||
|
||||
QuestF960Result() = default;
|
||||
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
|
||||
};
|
||||
|
||||
// Indexed as [type][difficulty][random_choice]
|
||||
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
|
||||
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
|
||||
std::vector<QuestF960Result> quest_F960_success_results;
|
||||
QuestF960Result quest_F960_failure_results;
|
||||
std::vector<ItemData> secret_lottery_results;
|
||||
uint16_t bb_global_exp_multiplier = 1;
|
||||
float exp_share_multiplier = 0.5;
|
||||
double server_global_drop_rate_multiplier = 1.0;
|
||||
|
||||
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
|
||||
|
||||
uint16_t ep3_card_auction_points = 0;
|
||||
uint16_t ep3_card_auction_min_size = 0;
|
||||
uint16_t ep3_card_auction_max_size = 0;
|
||||
struct CardAuctionPoolEntry {
|
||||
uint64_t probability;
|
||||
uint16_t card_id;
|
||||
uint16_t min_price;
|
||||
};
|
||||
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
|
||||
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
|
||||
struct Ep3LobbyBannerEntry {
|
||||
uint32_t type = 1;
|
||||
uint32_t which; // See B9 documentation in CommandFormats.hh
|
||||
std::string data;
|
||||
};
|
||||
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
|
||||
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<TeamIndex> team_index;
|
||||
JSON team_reward_defs_json;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu_v2;
|
||||
std::shared_ptr<const Menu> information_menu_v3;
|
||||
std::shared_ptr<std::vector<std::string>> information_contents_v2;
|
||||
std::shared_ptr<std::vector<std::string>> information_contents_v3;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
|
||||
std::pair<std::string, uint16_t> proxy_destination_patch;
|
||||
std::pair<std::string, uint16_t> proxy_destination_bb;
|
||||
std::string welcome_message;
|
||||
std::string pc_patch_server_message;
|
||||
std::string bb_patch_server_message;
|
||||
|
||||
std::shared_ptr<PlayerFilesManager> player_files_manager;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
|
||||
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
|
||||
std::shared_ptr<struct event> destroy_lobbies_event;
|
||||
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
|
||||
std::vector<uint32_t> client_customization_public_lobby_search_order;
|
||||
std::atomic<int32_t> next_lobby_id = 1;
|
||||
uint8_t pre_lobby_event = 0;
|
||||
int32_t ep3_menu_song = -1;
|
||||
|
||||
std::map<std::string, uint32_t> all_addresses;
|
||||
uint32_t local_address = 0;
|
||||
uint32_t external_address = 0;
|
||||
|
||||
bool proxy_allow_save_files = true;
|
||||
bool proxy_enable_login_options = false;
|
||||
|
||||
std::shared_ptr<IPStackSimulator> ip_stack_simulator;
|
||||
std::shared_ptr<DNSServer> dns_server;
|
||||
std::shared_ptr<ProxyServer> proxy_server;
|
||||
std::shared_ptr<Server> game_server;
|
||||
std::shared_ptr<PatchServer> pc_patch_server;
|
||||
std::shared_ptr<PatchServer> bb_patch_server;
|
||||
|
||||
explicit ServerState(const std::string& config_filename = "");
|
||||
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
|
||||
ServerState(const ServerState&) = delete;
|
||||
ServerState(ServerState&&) = delete;
|
||||
ServerState& operator=(const ServerState&) = delete;
|
||||
ServerState& operator=(ServerState&&) = delete;
|
||||
|
||||
void add_client_to_available_lobby(std::shared_ptr<Client> c);
|
||||
void remove_client_from_lobby(std::shared_ptr<Client> c);
|
||||
bool change_client_lobby(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Lobby> new_lobby,
|
||||
bool send_join_notification = true,
|
||||
ssize_t required_client_id = -1);
|
||||
|
||||
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> joining_client);
|
||||
|
||||
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
|
||||
std::vector<std::shared_ptr<Lobby>> all_lobbies();
|
||||
|
||||
std::shared_ptr<Lobby> create_lobby(bool is_game);
|
||||
void remove_lobby(std::shared_ptr<Lobby> l);
|
||||
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
|
||||
|
||||
std::shared_ptr<Client> find_client(
|
||||
const std::string* identifier = nullptr,
|
||||
uint64_t account_id = 0,
|
||||
std::shared_ptr<Lobby> l = nullptr);
|
||||
|
||||
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu(Version version) const;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
|
||||
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
|
||||
|
||||
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
|
||||
|
||||
std::shared_ptr<const LevelTable> level_table(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
|
||||
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
|
||||
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
|
||||
ItemData parse_item_description(Version version, const std::string& description) const;
|
||||
|
||||
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
|
||||
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
|
||||
return this->public_lobby_search_order(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
|
||||
}
|
||||
|
||||
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
|
||||
if (is_client_customization && this->client_customization_name_color) {
|
||||
return this->client_customization_name_color;
|
||||
}
|
||||
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
|
||||
}
|
||||
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
|
||||
return this->name_color_for_client(c->version(), c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
|
||||
}
|
||||
|
||||
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
|
||||
std::shared_ptr<const QuestIndex> quest_index(Version version) const;
|
||||
|
||||
size_t default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const;
|
||||
|
||||
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
|
||||
|
||||
std::shared_ptr<const std::string> load_bb_file(
|
||||
const std::string& patch_index_filename,
|
||||
const std::string& gsl_filename = "",
|
||||
const std::string& bb_directory_filename = "") const;
|
||||
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
|
||||
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
|
||||
|
||||
std::pair<std::string, uint16_t> parse_port_spec(const JSON& json) const;
|
||||
std::vector<PortConfiguration> parse_port_configuration(const JSON& json) const;
|
||||
|
||||
template <typename T>
|
||||
inline void call_on_event_thread(std::function<T()>&& fn) {
|
||||
return ::call_on_event_thread<T>(this->base, std::move(fn));
|
||||
}
|
||||
inline void forward_to_event_thread(std::function<void()>&& fn) {
|
||||
::forward_to_event_thread(this->base, std::move(fn));
|
||||
}
|
||||
inline void forward_or_call(bool from_non_event_thread, std::function<void()>&& fn) {
|
||||
if (from_non_event_thread) {
|
||||
::forward_to_event_thread(this->base, std::move(fn));
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
|
||||
void update_dependent_server_configs() const;
|
||||
|
||||
// The following functions may only be called from a non-event thread if they
|
||||
// take a from_non_event_thread argument; any function that does not have this
|
||||
// argument must be called only from the event thread.
|
||||
void create_default_lobbies();
|
||||
void collect_network_addresses();
|
||||
void load_config_early();
|
||||
void load_config_late();
|
||||
void load_bb_private_keys(bool from_non_event_thread);
|
||||
void load_accounts(bool from_non_event_thread);
|
||||
void load_teams(bool from_non_event_thread);
|
||||
void load_patch_indexes(bool from_non_event_thread);
|
||||
void clear_map_file_caches();
|
||||
void load_battle_params(bool from_non_event_thread);
|
||||
void load_level_tables(bool from_non_event_thread);
|
||||
void load_text_index(bool from_non_event_thread);
|
||||
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
|
||||
std::shared_ptr<const ItemParameterTable> pmt,
|
||||
std::shared_ptr<const ItemData::StackLimits> limits,
|
||||
std::shared_ptr<const TextIndex> text_index) const;
|
||||
void load_item_name_indexes(bool from_non_event_thread);
|
||||
void load_drop_tables(bool from_non_event_thread);
|
||||
void load_item_definitions(bool from_non_event_thread);
|
||||
void load_set_data_tables(bool from_non_event_thread);
|
||||
void load_word_select_table(bool from_non_event_thread);
|
||||
void load_ep3_cards(bool from_non_event_thread);
|
||||
void load_ep3_maps(bool from_non_event_thread);
|
||||
void load_ep3_tournament_state(bool from_non_event_thread);
|
||||
void load_quest_index(bool from_non_event_thread);
|
||||
void compile_functions(bool from_non_event_thread);
|
||||
void load_dol_files(bool from_non_event_thread);
|
||||
void load_all();
|
||||
|
||||
void enqueue_destroy_lobbies();
|
||||
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
|
||||
|
||||
void disconnect_all_banned_clients();
|
||||
|
||||
std::string format_address_for_channel_name(const struct sockaddr_storage& remote_ss, uint64_t virtual_network);
|
||||
};
|
||||
|
||||
+472
-472
@@ -1,472 +1,472 @@
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Image.hh>
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "GVMEncoder.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TeamIndex::Team::Member::Member(const JSON& json)
|
||||
: flags(json.get_int("Flags", 0)),
|
||||
points(json.get_int("Points", 0)),
|
||||
name(json.get_string("Name", "")) {
|
||||
try {
|
||||
this->account_id = json.get_int("AccountID");
|
||||
} catch (const out_of_range&) {
|
||||
this->account_id = json.get_int("SerialNumber");
|
||||
}
|
||||
}
|
||||
|
||||
JSON TeamIndex::Team::Member::json() const {
|
||||
return JSON::dict({
|
||||
{"AccountID", this->account_id},
|
||||
{"Flags", this->flags},
|
||||
{"Points", this->points},
|
||||
{"Name", this->name},
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t TeamIndex::Team::Member::privilege_level() const {
|
||||
if (this->check_flag(Member::Flag::IS_MASTER)) {
|
||||
return 0x40;
|
||||
} else if (this->check_flag(Member::Flag::IS_LEADER)) {
|
||||
return 0x30;
|
||||
} else {
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::Team::Team(uint32_t team_id) : Team() {
|
||||
this->team_id = team_id;
|
||||
}
|
||||
|
||||
string TeamIndex::Team::json_filename() const {
|
||||
return string_printf("system/teams/%08" PRIX32 ".json", this->team_id);
|
||||
}
|
||||
|
||||
string TeamIndex::Team::flag_filename() const {
|
||||
return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id);
|
||||
}
|
||||
|
||||
void TeamIndex::Team::load_config() {
|
||||
auto json = JSON::parse(load_file(this->json_filename()));
|
||||
this->name = json.get_string("Name");
|
||||
this->spent_points = json.get_int("SpentPoints");
|
||||
this->points = 0;
|
||||
for (const auto& member_it : json.get_list("Members")) {
|
||||
Member m(*member_it);
|
||||
this->points += m.points;
|
||||
uint32_t account_id = m.account_id;
|
||||
if (m.check_flag(Member::Flag::IS_MASTER)) {
|
||||
this->master_account_id = account_id;
|
||||
}
|
||||
this->members.emplace(account_id, std::move(m));
|
||||
}
|
||||
try {
|
||||
for (const auto& it : json.get_list("RewardKeys")) {
|
||||
this->reward_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->reward_flags = json.get_int("RewardFlags");
|
||||
}
|
||||
|
||||
void TeamIndex::Team::save_config() const {
|
||||
JSON members_json = JSON::list();
|
||||
for (const auto& it : this->members) {
|
||||
members_json.emplace_back(it.second.json());
|
||||
}
|
||||
JSON reward_keys_json = JSON::list();
|
||||
for (const auto& it : this->reward_keys) {
|
||||
reward_keys_json.emplace_back(it);
|
||||
}
|
||||
JSON root = JSON::dict({
|
||||
{"Name", this->name},
|
||||
{"SpentPoints", this->spent_points},
|
||||
{"Members", std::move(members_json)},
|
||||
{"RewardKeys", std::move(reward_keys_json)},
|
||||
{"RewardFlags", this->reward_flags},
|
||||
});
|
||||
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
|
||||
void TeamIndex::Team::load_flag() {
|
||||
Image img(this->flag_filename());
|
||||
if (img.get_width() != 32 || img.get_height() != 32) {
|
||||
throw runtime_error("incorrect flag image dimensions");
|
||||
}
|
||||
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
|
||||
for (size_t y = 0; y < 32; y++) {
|
||||
for (size_t x = 0; x < 32; x++) {
|
||||
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::Team::save_flag() const {
|
||||
if (!this->flag_data) {
|
||||
return;
|
||||
}
|
||||
Image img(32, 32, true);
|
||||
for (size_t y = 0; y < 32; y++) {
|
||||
for (size_t x = 0; x < 32; x++) {
|
||||
img.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x)));
|
||||
}
|
||||
}
|
||||
img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP);
|
||||
}
|
||||
|
||||
void TeamIndex::Team::delete_files() const {
|
||||
string json_filename = this->json_filename();
|
||||
string flag_filename = this->flag_filename();
|
||||
remove(json_filename.c_str());
|
||||
remove(flag_filename.c_str());
|
||||
}
|
||||
|
||||
PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const {
|
||||
const auto& m = this->members.at(account_id);
|
||||
|
||||
PSOBBTeamMembership ret;
|
||||
ret.team_master_guild_card_number = this->master_account_id;
|
||||
ret.team_id = this->team_id;
|
||||
ret.unknown_a5 = 0;
|
||||
ret.unknown_a6 = 0;
|
||||
ret.privilege_level = m.privilege_level();
|
||||
ret.unknown_a7 = 0;
|
||||
ret.unknown_a8 = 0;
|
||||
ret.unknown_a9 = 0;
|
||||
ret.team_name.encode(this->name);
|
||||
if (this->flag_data) {
|
||||
ret.flag_data = *this->flag_data;
|
||||
} else {
|
||||
ret.flag_data.clear();
|
||||
}
|
||||
ret.reward_flags = this->reward_flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::has_reward(const string& key) const {
|
||||
return this->reward_keys.count(key);
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::num_members() const {
|
||||
return this->members.size();
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::num_leaders() const {
|
||||
size_t count = 0;
|
||||
for (const auto& it : this->members) {
|
||||
if (it.second.check_flag(Member::Flag::IS_LEADER)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::max_members() const {
|
||||
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
return 100;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
||||
return 70;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
||||
return 40;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
||||
return 20;
|
||||
} else {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::max_leaders() const {
|
||||
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
return 10;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
||||
return 8;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
||||
return 5;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
||||
return 3;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::can_add_member() const {
|
||||
return this->num_members() < this->max_members();
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::can_promote_leader() const {
|
||||
return this->num_leaders() < this->max_leaders();
|
||||
}
|
||||
|
||||
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
|
||||
: menu_item_id(menu_item_id),
|
||||
key(def_json.get_string("Key")),
|
||||
name(def_json.get_string("Name")),
|
||||
description(def_json.get_string("Description")),
|
||||
is_unique(def_json.get_bool("IsUnique", true)),
|
||||
team_points(def_json.get_int("Points")) {
|
||||
try {
|
||||
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
|
||||
this->prerequisite_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem")));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
|
||||
: directory(directory),
|
||||
next_team_id(1) {
|
||||
uint32_t reward_menu_item_id = 0;
|
||||
for (const auto& it : reward_defs_json.as_list()) {
|
||||
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
|
||||
}
|
||||
|
||||
if (!isdir(this->directory)) {
|
||||
mkdir(this->directory.c_str(), 0755);
|
||||
return;
|
||||
}
|
||||
for (const auto& filename : list_directory(this->directory)) {
|
||||
string file_path = this->directory + "/" + filename;
|
||||
if (filename == "base.json") {
|
||||
auto json = JSON::parse(load_file(file_path));
|
||||
this->next_team_id = json.get_int("NextTeamID");
|
||||
} else if (ends_with(filename, ".json")) {
|
||||
try {
|
||||
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
|
||||
auto team = make_shared<Team>(team_id);
|
||||
team->load_config();
|
||||
try {
|
||||
team->load_flag();
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what());
|
||||
}
|
||||
this->add_to_indexes(team);
|
||||
static_game_data_log.info("Indexed team %08" PRIX32 " (%s) (%zu members)", team_id, team->name.c_str(), team->num_members());
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t TeamIndex::count() const {
|
||||
return this->id_to_team.size();
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
||||
try {
|
||||
return this->id_to_team.at(team_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
|
||||
try {
|
||||
return this->name_to_team.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
|
||||
try {
|
||||
return this->account_id_to_team.at(account_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
vector<shared_ptr<const Team>> ret;
|
||||
for (const auto& it : this->id_to_team) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
|
||||
auto team = make_shared<Team>(this->next_team_id++);
|
||||
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
|
||||
|
||||
Team::Member m;
|
||||
m.account_id = master_account_id;
|
||||
m.flags = 0;
|
||||
m.points = 0;
|
||||
m.name = master_name;
|
||||
m.set_flag(Team::Member::Flag::IS_MASTER);
|
||||
team->members.emplace(master_account_id, std::move(m));
|
||||
team->name = name;
|
||||
|
||||
team->save_config();
|
||||
this->add_to_indexes(team);
|
||||
return team;
|
||||
}
|
||||
|
||||
void TeamIndex::disband(uint32_t team_id) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
this->remove_from_indexes(team);
|
||||
team->delete_files();
|
||||
}
|
||||
|
||||
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->name_to_team.emplace(new_team_name, team).second) {
|
||||
throw runtime_error("team name is already in use");
|
||||
}
|
||||
this->name_to_team.erase(team->name);
|
||||
team->name = new_team_name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->account_id_to_team.emplace(account_id, team).second) {
|
||||
throw runtime_error("user is already in a different team");
|
||||
}
|
||||
|
||||
Team::Member m;
|
||||
m.account_id = account_id;
|
||||
m.flags = 0;
|
||||
m.points = 0;
|
||||
m.name = name;
|
||||
team->members.emplace(account_id, std::move(m));
|
||||
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::remove_member(uint32_t account_id) {
|
||||
auto team_it = this->account_id_to_team.find(account_id);
|
||||
if (team_it == this->account_id_to_team.end()) {
|
||||
throw runtime_error("client is not in any team");
|
||||
}
|
||||
auto team = std::move(team_it->second);
|
||||
this->account_id_to_team.erase(team_it);
|
||||
team->members.erase(account_id);
|
||||
if (team->members.empty()) {
|
||||
this->disband(team->team_id);
|
||||
} else {
|
||||
team->save_config();
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
|
||||
auto team = this->account_id_to_team.at(account_id);
|
||||
auto& m = team->members.at(account_id);
|
||||
m.name = name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
|
||||
auto team = this->account_id_to_team.at(account_id);
|
||||
auto& m = team->members.at(account_id);
|
||||
m.points += points;
|
||||
team->points += points;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
|
||||
team->save_flag();
|
||||
}
|
||||
|
||||
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
|
||||
return false;
|
||||
}
|
||||
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
|
||||
return false;
|
||||
}
|
||||
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& new_master_m = team->members.at(new_master_account_id);
|
||||
|
||||
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
team->master_account_id = new_master_account_id;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (team->spent_points + points > team->points) {
|
||||
throw runtime_error("not enough points available");
|
||||
}
|
||||
team->reward_keys.emplace(key);
|
||||
team->spent_points += points;
|
||||
if (reward_flag != Team::RewardFlag::NONE) {
|
||||
team->set_reward_flag(reward_flag);
|
||||
}
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
|
||||
if (!this->id_to_team.emplace(team->team_id, team).second) {
|
||||
throw runtime_error("team ID is already in use");
|
||||
}
|
||||
if (!this->name_to_team.emplace(team->name, team).second) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
throw runtime_error("team name is already in use");
|
||||
}
|
||||
for (const auto& it : team->members) {
|
||||
if (!this->account_id_to_team.emplace(it.second.account_id, team).second) {
|
||||
static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams",
|
||||
it.second.account_id, it.second.account_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
this->name_to_team.erase(team->name);
|
||||
for (const auto& it : team->members) {
|
||||
this->account_id_to_team.erase(it.second.account_id);
|
||||
}
|
||||
}
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Image.hh>
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "GVMEncoder.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TeamIndex::Team::Member::Member(const JSON& json)
|
||||
: flags(json.get_int("Flags", 0)),
|
||||
points(json.get_int("Points", 0)),
|
||||
name(json.get_string("Name", "")) {
|
||||
try {
|
||||
this->account_id = json.get_int("AccountID");
|
||||
} catch (const out_of_range&) {
|
||||
this->account_id = json.get_int("SerialNumber");
|
||||
}
|
||||
}
|
||||
|
||||
JSON TeamIndex::Team::Member::json() const {
|
||||
return JSON::dict({
|
||||
{"AccountID", this->account_id},
|
||||
{"Flags", this->flags},
|
||||
{"Points", this->points},
|
||||
{"Name", this->name},
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t TeamIndex::Team::Member::privilege_level() const {
|
||||
if (this->check_flag(Member::Flag::IS_MASTER)) {
|
||||
return 0x40;
|
||||
} else if (this->check_flag(Member::Flag::IS_LEADER)) {
|
||||
return 0x30;
|
||||
} else {
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::Team::Team(uint32_t team_id) : Team() {
|
||||
this->team_id = team_id;
|
||||
}
|
||||
|
||||
string TeamIndex::Team::json_filename() const {
|
||||
return string_printf("system/teams/%08" PRIX32 ".json", this->team_id);
|
||||
}
|
||||
|
||||
string TeamIndex::Team::flag_filename() const {
|
||||
return string_printf("system/teams/%08" PRIX32 ".bmp", this->team_id);
|
||||
}
|
||||
|
||||
void TeamIndex::Team::load_config() {
|
||||
auto json = JSON::parse(load_file(this->json_filename()));
|
||||
this->name = json.get_string("Name");
|
||||
this->spent_points = json.get_int("SpentPoints");
|
||||
this->points = 0;
|
||||
for (const auto& member_it : json.get_list("Members")) {
|
||||
Member m(*member_it);
|
||||
this->points += m.points;
|
||||
uint32_t account_id = m.account_id;
|
||||
if (m.check_flag(Member::Flag::IS_MASTER)) {
|
||||
this->master_account_id = account_id;
|
||||
}
|
||||
this->members.emplace(account_id, std::move(m));
|
||||
}
|
||||
try {
|
||||
for (const auto& it : json.get_list("RewardKeys")) {
|
||||
this->reward_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->reward_flags = json.get_int("RewardFlags");
|
||||
}
|
||||
|
||||
void TeamIndex::Team::save_config() const {
|
||||
JSON members_json = JSON::list();
|
||||
for (const auto& it : this->members) {
|
||||
members_json.emplace_back(it.second.json());
|
||||
}
|
||||
JSON reward_keys_json = JSON::list();
|
||||
for (const auto& it : this->reward_keys) {
|
||||
reward_keys_json.emplace_back(it);
|
||||
}
|
||||
JSON root = JSON::dict({
|
||||
{"Name", this->name},
|
||||
{"SpentPoints", this->spent_points},
|
||||
{"Members", std::move(members_json)},
|
||||
{"RewardKeys", std::move(reward_keys_json)},
|
||||
{"RewardFlags", this->reward_flags},
|
||||
});
|
||||
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
|
||||
void TeamIndex::Team::load_flag() {
|
||||
Image img(this->flag_filename());
|
||||
if (img.get_width() != 32 || img.get_height() != 32) {
|
||||
throw runtime_error("incorrect flag image dimensions");
|
||||
}
|
||||
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
|
||||
for (size_t y = 0; y < 32; y++) {
|
||||
for (size_t x = 0; x < 32; x++) {
|
||||
this->flag_data->at(y * 0x20 + x) = encode_rgba8888_to_argb1555(img.read_pixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::Team::save_flag() const {
|
||||
if (!this->flag_data) {
|
||||
return;
|
||||
}
|
||||
Image img(32, 32, true);
|
||||
for (size_t y = 0; y < 32; y++) {
|
||||
for (size_t x = 0; x < 32; x++) {
|
||||
img.write_pixel(x, y, decode_argb1555_to_rgba8888(this->flag_data->at(y * 0x20 + x)));
|
||||
}
|
||||
}
|
||||
img.save(this->flag_filename(), Image::Format::WINDOWS_BITMAP);
|
||||
}
|
||||
|
||||
void TeamIndex::Team::delete_files() const {
|
||||
string json_filename = this->json_filename();
|
||||
string flag_filename = this->flag_filename();
|
||||
remove(json_filename.c_str());
|
||||
remove(flag_filename.c_str());
|
||||
}
|
||||
|
||||
PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const {
|
||||
const auto& m = this->members.at(account_id);
|
||||
|
||||
PSOBBTeamMembership ret;
|
||||
ret.team_master_guild_card_number = this->master_account_id;
|
||||
ret.team_id = this->team_id;
|
||||
ret.unknown_a5 = 0;
|
||||
ret.unknown_a6 = 0;
|
||||
ret.privilege_level = m.privilege_level();
|
||||
ret.unknown_a7 = 0;
|
||||
ret.unknown_a8 = 0;
|
||||
ret.unknown_a9 = 0;
|
||||
ret.team_name.encode(this->name);
|
||||
if (this->flag_data) {
|
||||
ret.flag_data = *this->flag_data;
|
||||
} else {
|
||||
ret.flag_data.clear();
|
||||
}
|
||||
ret.reward_flags = this->reward_flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::has_reward(const string& key) const {
|
||||
return this->reward_keys.count(key);
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::num_members() const {
|
||||
return this->members.size();
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::num_leaders() const {
|
||||
size_t count = 0;
|
||||
for (const auto& it : this->members) {
|
||||
if (it.second.check_flag(Member::Flag::IS_LEADER)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::max_members() const {
|
||||
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
return 100;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
||||
return 70;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
||||
return 40;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
||||
return 20;
|
||||
} else {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::max_leaders() const {
|
||||
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
return 10;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
||||
return 8;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
||||
return 5;
|
||||
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
||||
return 3;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::can_add_member() const {
|
||||
return this->num_members() < this->max_members();
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::can_promote_leader() const {
|
||||
return this->num_leaders() < this->max_leaders();
|
||||
}
|
||||
|
||||
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
|
||||
: menu_item_id(menu_item_id),
|
||||
key(def_json.get_string("Key")),
|
||||
name(def_json.get_string("Name")),
|
||||
description(def_json.get_string("Description")),
|
||||
is_unique(def_json.get_bool("IsUnique", true)),
|
||||
team_points(def_json.get_int("Points")) {
|
||||
try {
|
||||
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
|
||||
this->prerequisite_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_item = ItemData::from_data(parse_data_string(def_json.get_string("RewardItem")));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
|
||||
: directory(directory),
|
||||
next_team_id(1) {
|
||||
uint32_t reward_menu_item_id = 0;
|
||||
for (const auto& it : reward_defs_json.as_list()) {
|
||||
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
|
||||
}
|
||||
|
||||
if (!isdir(this->directory)) {
|
||||
mkdir(this->directory.c_str(), 0755);
|
||||
return;
|
||||
}
|
||||
for (const auto& filename : list_directory(this->directory)) {
|
||||
string file_path = this->directory + "/" + filename;
|
||||
if (filename == "base.json") {
|
||||
auto json = JSON::parse(load_file(file_path));
|
||||
this->next_team_id = json.get_int("NextTeamID");
|
||||
} else if (ends_with(filename, ".json")) {
|
||||
try {
|
||||
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
|
||||
auto team = make_shared<Team>(team_id);
|
||||
team->load_config();
|
||||
try {
|
||||
team->load_flag();
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("Failed to load flag for team %08" PRIX32 ": %s", team_id, e.what());
|
||||
}
|
||||
this->add_to_indexes(team);
|
||||
static_game_data_log.info("Indexed team %08" PRIX32 " (%s) (%zu members)", team_id, team->name.c_str(), team->num_members());
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("Failed to index team from %s: %s", filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t TeamIndex::count() const {
|
||||
return this->id_to_team.size();
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
||||
try {
|
||||
return this->id_to_team.at(team_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
|
||||
try {
|
||||
return this->name_to_team.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
|
||||
try {
|
||||
return this->account_id_to_team.at(account_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
vector<shared_ptr<const Team>> ret;
|
||||
for (const auto& it : this->id_to_team) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
|
||||
auto team = make_shared<Team>(this->next_team_id++);
|
||||
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
|
||||
|
||||
Team::Member m;
|
||||
m.account_id = master_account_id;
|
||||
m.flags = 0;
|
||||
m.points = 0;
|
||||
m.name = master_name;
|
||||
m.set_flag(Team::Member::Flag::IS_MASTER);
|
||||
team->members.emplace(master_account_id, std::move(m));
|
||||
team->name = name;
|
||||
|
||||
team->save_config();
|
||||
this->add_to_indexes(team);
|
||||
return team;
|
||||
}
|
||||
|
||||
void TeamIndex::disband(uint32_t team_id) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
this->remove_from_indexes(team);
|
||||
team->delete_files();
|
||||
}
|
||||
|
||||
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->name_to_team.emplace(new_team_name, team).second) {
|
||||
throw runtime_error("team name is already in use");
|
||||
}
|
||||
this->name_to_team.erase(team->name);
|
||||
team->name = new_team_name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->account_id_to_team.emplace(account_id, team).second) {
|
||||
throw runtime_error("user is already in a different team");
|
||||
}
|
||||
|
||||
Team::Member m;
|
||||
m.account_id = account_id;
|
||||
m.flags = 0;
|
||||
m.points = 0;
|
||||
m.name = name;
|
||||
team->members.emplace(account_id, std::move(m));
|
||||
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::remove_member(uint32_t account_id) {
|
||||
auto team_it = this->account_id_to_team.find(account_id);
|
||||
if (team_it == this->account_id_to_team.end()) {
|
||||
throw runtime_error("client is not in any team");
|
||||
}
|
||||
auto team = std::move(team_it->second);
|
||||
this->account_id_to_team.erase(team_it);
|
||||
team->members.erase(account_id);
|
||||
if (team->members.empty()) {
|
||||
this->disband(team->team_id);
|
||||
} else {
|
||||
team->save_config();
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
|
||||
auto team = this->account_id_to_team.at(account_id);
|
||||
auto& m = team->members.at(account_id);
|
||||
m.name = name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
|
||||
auto team = this->account_id_to_team.at(account_id);
|
||||
auto& m = team->members.at(account_id);
|
||||
m.points += points;
|
||||
team->points += points;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
|
||||
team->save_flag();
|
||||
}
|
||||
|
||||
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
|
||||
return false;
|
||||
}
|
||||
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
|
||||
return false;
|
||||
}
|
||||
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) {
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& new_master_m = team->members.at(new_master_account_id);
|
||||
|
||||
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
team->master_account_id = new_master_account_id;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (team->spent_points + points > team->points) {
|
||||
throw runtime_error("not enough points available");
|
||||
}
|
||||
team->reward_keys.emplace(key);
|
||||
team->spent_points += points;
|
||||
if (reward_flag != Team::RewardFlag::NONE) {
|
||||
team->set_reward_flag(reward_flag);
|
||||
}
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
|
||||
if (!this->id_to_team.emplace(team->team_id, team).second) {
|
||||
throw runtime_error("team ID is already in use");
|
||||
}
|
||||
if (!this->name_to_team.emplace(team->name, team).second) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
throw runtime_error("team name is already in use");
|
||||
}
|
||||
for (const auto& it : team->members) {
|
||||
if (!this->account_id_to_team.emplace(it.second.account_id, team).second) {
|
||||
static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams",
|
||||
it.second.account_id, it.second.account_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
this->name_to_team.erase(team->name);
|
||||
for (const auto& it : team->members) {
|
||||
this->account_id_to_team.erase(it.second.account_id);
|
||||
}
|
||||
}
|
||||
|
||||
+155
-155
@@ -1,155 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class TeamIndex {
|
||||
public:
|
||||
struct Team {
|
||||
struct Member {
|
||||
enum class Flag {
|
||||
IS_MASTER = 0x01,
|
||||
IS_LEADER = 0x02,
|
||||
};
|
||||
uint32_t account_id = 0;
|
||||
uint8_t flags = 0;
|
||||
uint64_t points = 0;
|
||||
std::string name;
|
||||
|
||||
Member() = default;
|
||||
explicit Member(const JSON& json);
|
||||
JSON json() const;
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(static_cast<uint8_t>(flag) & this->flags);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->flags |= static_cast<uint8_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->flags &= (~static_cast<uint8_t>(flag));
|
||||
}
|
||||
|
||||
uint32_t privilege_level() const;
|
||||
};
|
||||
|
||||
enum class RewardFlag {
|
||||
// Only 0x00000001 and 0x00000002 are used by the client; the rest are
|
||||
// free to be used however the server chooses.
|
||||
NONE = 0x00000000,
|
||||
TEAM_FLAG = 0x00000001,
|
||||
DRESSING_ROOM = 0x00000002,
|
||||
MEMBERS_20_LEADERS_3 = 0x00000004,
|
||||
MEMBERS_40_LEADERS_5 = 0x00000008,
|
||||
MEMBERS_70_LEADERS_8 = 0x00000010,
|
||||
MEMBERS_100_LEADERS_10 = 0x00000020,
|
||||
};
|
||||
|
||||
uint32_t team_id = 0;
|
||||
uint32_t points = 0;
|
||||
uint32_t spent_points = 0;
|
||||
std::string name;
|
||||
uint32_t master_account_id = 0;
|
||||
std::unordered_map<uint32_t, Member> members;
|
||||
uint32_t reward_flags = 0;
|
||||
std::unordered_set<std::string> reward_keys;
|
||||
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
|
||||
|
||||
Team() = default;
|
||||
explicit Team(uint32_t team_id);
|
||||
JSON json() const;
|
||||
|
||||
std::string json_filename() const;
|
||||
std::string flag_filename() const;
|
||||
|
||||
void load_config();
|
||||
void save_config() const;
|
||||
void load_flag();
|
||||
void save_flag() const;
|
||||
void delete_files() const;
|
||||
|
||||
PSOBBTeamMembership membership_for_member(uint32_t account_id) const;
|
||||
|
||||
[[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const {
|
||||
return !!(static_cast<uint8_t>(flag) & this->reward_flags);
|
||||
}
|
||||
inline void set_reward_flag(RewardFlag flag) {
|
||||
this->reward_flags |= static_cast<uint8_t>(flag);
|
||||
}
|
||||
inline void clear_reward_flag(RewardFlag flag) {
|
||||
this->reward_flags &= (~static_cast<uint8_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_reward(const std::string& key) const;
|
||||
|
||||
size_t num_members() const;
|
||||
size_t num_leaders() const;
|
||||
size_t max_members() const;
|
||||
size_t max_leaders() const;
|
||||
bool can_add_member() const;
|
||||
bool can_promote_leader() const;
|
||||
};
|
||||
|
||||
struct Reward {
|
||||
uint32_t menu_item_id = 0;
|
||||
std::string key;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::unordered_set<std::string> prerequisite_keys;
|
||||
bool is_unique = true;
|
||||
uint32_t team_points = 0;
|
||||
Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
|
||||
ItemData reward_item;
|
||||
|
||||
Reward(uint32_t menu_item_id, const JSON& def_json);
|
||||
};
|
||||
|
||||
TeamIndex(const std::string& directory, const JSON& reward_defs_json);
|
||||
~TeamIndex() = default;
|
||||
|
||||
inline const std::vector<Reward>& reward_definitions() const {
|
||||
return this->reward_defs;
|
||||
}
|
||||
|
||||
size_t count() const;
|
||||
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
|
||||
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
|
||||
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) const;
|
||||
std::vector<std::shared_ptr<const Team>> all() const;
|
||||
|
||||
std::shared_ptr<const Team> create(const std::string& name, uint32_t master_account_id, const std::string& master_name);
|
||||
void disband(uint32_t team_id);
|
||||
void rename(uint32_t team_id, const std::string& new_name);
|
||||
|
||||
void add_member(uint32_t team_id, uint32_t account_id, const std::string& name);
|
||||
void remove_member(uint32_t account_id);
|
||||
void update_member_name(uint32_t account_id, const std::string& name);
|
||||
void add_member_points(uint32_t account_id, uint32_t points);
|
||||
void set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data);
|
||||
bool promote_leader(uint32_t master_account_id, uint32_t leader_account_id);
|
||||
bool demote_leader(uint32_t master_account_id, uint32_t leader_account_id);
|
||||
void change_master(uint32_t master_account_id, uint32_t new_master_account_id);
|
||||
void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
|
||||
|
||||
protected:
|
||||
std::string directory;
|
||||
uint32_t next_team_id;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team;
|
||||
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
|
||||
std::vector<Reward> reward_defs;
|
||||
|
||||
void add_to_indexes(std::shared_ptr<Team> team);
|
||||
void remove_from_indexes(std::shared_ptr<Team> team);
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class TeamIndex {
|
||||
public:
|
||||
struct Team {
|
||||
struct Member {
|
||||
enum class Flag {
|
||||
IS_MASTER = 0x01,
|
||||
IS_LEADER = 0x02,
|
||||
};
|
||||
uint32_t account_id = 0;
|
||||
uint8_t flags = 0;
|
||||
uint64_t points = 0;
|
||||
std::string name;
|
||||
|
||||
Member() = default;
|
||||
explicit Member(const JSON& json);
|
||||
JSON json() const;
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return !!(static_cast<uint8_t>(flag) & this->flags);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->flags |= static_cast<uint8_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->flags &= (~static_cast<uint8_t>(flag));
|
||||
}
|
||||
|
||||
uint32_t privilege_level() const;
|
||||
};
|
||||
|
||||
enum class RewardFlag {
|
||||
// Only 0x00000001 and 0x00000002 are used by the client; the rest are
|
||||
// free to be used however the server chooses.
|
||||
NONE = 0x00000000,
|
||||
TEAM_FLAG = 0x00000001,
|
||||
DRESSING_ROOM = 0x00000002,
|
||||
MEMBERS_20_LEADERS_3 = 0x00000004,
|
||||
MEMBERS_40_LEADERS_5 = 0x00000008,
|
||||
MEMBERS_70_LEADERS_8 = 0x00000010,
|
||||
MEMBERS_100_LEADERS_10 = 0x00000020,
|
||||
};
|
||||
|
||||
uint32_t team_id = 0;
|
||||
uint32_t points = 0;
|
||||
uint32_t spent_points = 0;
|
||||
std::string name;
|
||||
uint32_t master_account_id = 0;
|
||||
std::unordered_map<uint32_t, Member> members;
|
||||
uint32_t reward_flags = 0;
|
||||
std::unordered_set<std::string> reward_keys;
|
||||
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
|
||||
|
||||
Team() = default;
|
||||
explicit Team(uint32_t team_id);
|
||||
JSON json() const;
|
||||
|
||||
std::string json_filename() const;
|
||||
std::string flag_filename() const;
|
||||
|
||||
void load_config();
|
||||
void save_config() const;
|
||||
void load_flag();
|
||||
void save_flag() const;
|
||||
void delete_files() const;
|
||||
|
||||
PSOBBTeamMembership membership_for_member(uint32_t account_id) const;
|
||||
|
||||
[[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const {
|
||||
return !!(static_cast<uint8_t>(flag) & this->reward_flags);
|
||||
}
|
||||
inline void set_reward_flag(RewardFlag flag) {
|
||||
this->reward_flags |= static_cast<uint8_t>(flag);
|
||||
}
|
||||
inline void clear_reward_flag(RewardFlag flag) {
|
||||
this->reward_flags &= (~static_cast<uint8_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_reward(const std::string& key) const;
|
||||
|
||||
size_t num_members() const;
|
||||
size_t num_leaders() const;
|
||||
size_t max_members() const;
|
||||
size_t max_leaders() const;
|
||||
bool can_add_member() const;
|
||||
bool can_promote_leader() const;
|
||||
};
|
||||
|
||||
struct Reward {
|
||||
uint32_t menu_item_id = 0;
|
||||
std::string key;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::unordered_set<std::string> prerequisite_keys;
|
||||
bool is_unique = true;
|
||||
uint32_t team_points = 0;
|
||||
Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
|
||||
ItemData reward_item;
|
||||
|
||||
Reward(uint32_t menu_item_id, const JSON& def_json);
|
||||
};
|
||||
|
||||
TeamIndex(const std::string& directory, const JSON& reward_defs_json);
|
||||
~TeamIndex() = default;
|
||||
|
||||
inline const std::vector<Reward>& reward_definitions() const {
|
||||
return this->reward_defs;
|
||||
}
|
||||
|
||||
size_t count() const;
|
||||
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
|
||||
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
|
||||
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) const;
|
||||
std::vector<std::shared_ptr<const Team>> all() const;
|
||||
|
||||
std::shared_ptr<const Team> create(const std::string& name, uint32_t master_account_id, const std::string& master_name);
|
||||
void disband(uint32_t team_id);
|
||||
void rename(uint32_t team_id, const std::string& new_name);
|
||||
|
||||
void add_member(uint32_t team_id, uint32_t account_id, const std::string& name);
|
||||
void remove_member(uint32_t account_id);
|
||||
void update_member_name(uint32_t account_id, const std::string& name);
|
||||
void add_member_points(uint32_t account_id, uint32_t points);
|
||||
void set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data);
|
||||
bool promote_leader(uint32_t master_account_id, uint32_t leader_account_id);
|
||||
bool demote_leader(uint32_t master_account_id, uint32_t leader_account_id);
|
||||
void change_master(uint32_t master_account_id, uint32_t new_master_account_id);
|
||||
void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
|
||||
|
||||
protected:
|
||||
std::string directory;
|
||||
uint32_t next_team_id;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team;
|
||||
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
|
||||
std::vector<Reward> reward_defs;
|
||||
|
||||
void add_to_indexes(std::shared_ptr<Team> team);
|
||||
void remove_from_indexes(std::shared_ptr<Team> team);
|
||||
};
|
||||
|
||||
+516
-516
File diff suppressed because it is too large
Load Diff
+724
-724
File diff suppressed because it is too large
Load Diff
+345
-345
@@ -1,345 +1,345 @@
|
||||
#include "Version.hh"
|
||||
|
||||
#include <strings.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char* login_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "console-login";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-login";
|
||||
case Version::XB_V3:
|
||||
return "xb-login";
|
||||
case Version::BB_V4:
|
||||
return "bb-init";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
const char* lobby_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "console-lobby";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-lobby";
|
||||
case Version::XB_V3:
|
||||
return "xb-lobby";
|
||||
case Version::BB_V4:
|
||||
return "bb-lobby";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
const char* proxy_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
return "dc-proxy";
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "gc-proxy";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-proxy";
|
||||
case Version::XB_V3:
|
||||
return "xb-proxy";
|
||||
case Version::BB_V4:
|
||||
return "bb-proxy";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Version>(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "PC_PATCH";
|
||||
case Version::BB_PATCH:
|
||||
return "BB_PATCH";
|
||||
case Version::DC_NTE:
|
||||
return "DC_NTE";
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return "DC_V1_11_2000_PROTOTYPE";
|
||||
case Version::DC_V1:
|
||||
return "DC_V1";
|
||||
case Version::DC_V2:
|
||||
return "DC_V2";
|
||||
case Version::PC_NTE:
|
||||
return "PC_NTE";
|
||||
case Version::PC_V2:
|
||||
return "PC_V2";
|
||||
case Version::GC_NTE:
|
||||
return "GC_NTE";
|
||||
case Version::GC_V3:
|
||||
return "GC_V3";
|
||||
case Version::GC_EP3_NTE:
|
||||
return "GC_EP3_NTE";
|
||||
case Version::GC_EP3:
|
||||
return "GC_EP3";
|
||||
case Version::XB_V3:
|
||||
return "XB_V3";
|
||||
case Version::BB_V4:
|
||||
return "BB_V4";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
Version enum_for_name<Version>(const char* name) {
|
||||
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
|
||||
return Version::PC_PATCH;
|
||||
} else if (!strcmp(name, "BB_PATCH")) {
|
||||
return Version::BB_PATCH;
|
||||
} else if (!strcmp(name, "DC_NTE")) {
|
||||
return Version::DC_NTE;
|
||||
} else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) {
|
||||
return Version::DC_V1_11_2000_PROTOTYPE;
|
||||
} else if (!strcmp(name, "DC_V1")) {
|
||||
return Version::DC_V1;
|
||||
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
|
||||
return Version::DC_V2;
|
||||
} else if (!strcmp(name, "PC_NTE")) {
|
||||
return Version::PC_NTE;
|
||||
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
|
||||
return Version::PC_V2;
|
||||
} else if (!strcmp(name, "GC_NTE")) {
|
||||
return Version::GC_NTE;
|
||||
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
|
||||
return Version::GC_V3;
|
||||
} else if (!strcmp(name, "GC_EP3_NTE")) {
|
||||
return Version::GC_EP3_NTE;
|
||||
} else if (!strcmp(name, "GC_EP3")) {
|
||||
return Version::GC_EP3;
|
||||
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
|
||||
return Version::XB_V3;
|
||||
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
|
||||
return Version::BB_V4;
|
||||
} else {
|
||||
throw invalid_argument("incorrect version name");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
|
||||
switch (behavior) {
|
||||
case ServerBehavior::PC_CONSOLE_DETECT:
|
||||
return "pc_console_detect";
|
||||
case ServerBehavior::LOGIN_SERVER:
|
||||
return "login_server";
|
||||
case ServerBehavior::LOBBY_SERVER:
|
||||
return "lobby_server";
|
||||
case ServerBehavior::PATCH_SERVER_PC:
|
||||
return "patch_server_pc";
|
||||
case ServerBehavior::PATCH_SERVER_BB:
|
||||
return "patch_server_bb";
|
||||
case ServerBehavior::PROXY_SERVER:
|
||||
return "proxy_server";
|
||||
}
|
||||
throw logic_error("invalid server behavior");
|
||||
}
|
||||
|
||||
template <>
|
||||
ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
|
||||
if (!strcasecmp(name, "pc_console_detect")) {
|
||||
return ServerBehavior::PC_CONSOLE_DETECT;
|
||||
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) {
|
||||
return ServerBehavior::LOGIN_SERVER;
|
||||
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
|
||||
return ServerBehavior::LOBBY_SERVER;
|
||||
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
|
||||
return ServerBehavior::PATCH_SERVER_PC;
|
||||
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
|
||||
return ServerBehavior::PATCH_SERVER_BB;
|
||||
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
|
||||
return ServerBehavior::PROXY_SERVER;
|
||||
} else {
|
||||
throw invalid_argument("incorrect server behavior name");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
|
||||
// For versions that don't support send_function_call by default, we need
|
||||
// to set the specific_version based on sub_version. Fortunately, all
|
||||
// versions that share sub_version values also support send_function_call,
|
||||
// so for those versions we get the specific_version later by sending the
|
||||
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
return SPECIFIC_VERSION_DC_NTE; // 1OJ1
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
|
||||
case Version::DC_V1:
|
||||
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
|
||||
case Version::DC_V2:
|
||||
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
|
||||
case Version::PC_V2:
|
||||
return SPECIFIC_VERSION_PC_V2; // 2OJW
|
||||
case Version::GC_NTE:
|
||||
return SPECIFIC_VERSION_GC_NTE; // 3OJT
|
||||
case Version::GC_V3:
|
||||
switch (sub_version) {
|
||||
case 0x32: // GC Ep1&2 EU 50Hz
|
||||
case 0x33: // GC Ep1&2 EU 60Hz
|
||||
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
|
||||
case 0x36: // GC Ep1&2 US v1.2 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
|
||||
case 0x34: // GC Ep1&2 JP v1.3
|
||||
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
|
||||
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
|
||||
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
|
||||
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case Version::GC_EP3_NTE:
|
||||
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
|
||||
case Version::GC_EP3:
|
||||
switch (sub_version) {
|
||||
case 0x41: // GC Ep3 US
|
||||
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
|
||||
case 0x42: // GC Ep3 EU 50Hz
|
||||
case 0x43: // GC Ep3 EU 60Hz
|
||||
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
case 0x40: // GC Ep3 trial and GC Ep3 JP
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
|
||||
}
|
||||
case Version::XB_V3:
|
||||
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
|
||||
case Version::BB_V4:
|
||||
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
|
||||
default:
|
||||
return SPECIFIC_VERSION_INDETERMINATE;
|
||||
}
|
||||
}
|
||||
|
||||
bool specific_version_is_indeterminate(uint32_t specific_version) {
|
||||
return ((specific_version & 0x000000FF) == 0);
|
||||
}
|
||||
|
||||
bool specific_version_is_dc(uint32_t specific_version) {
|
||||
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
|
||||
uint8_t major_version = specific_version >> 24;
|
||||
if (major_version < 0x31 || major_version > 0x32) {
|
||||
return false;
|
||||
}
|
||||
return (specific_version != 0x324F4A57);
|
||||
}
|
||||
|
||||
bool specific_version_is_gc(uint32_t specific_version) {
|
||||
// GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
|
||||
if ((specific_version & 0xFF000000) != 0x33000000) {
|
||||
return false;
|
||||
}
|
||||
char game = specific_version >> 16;
|
||||
if ((game != 'O') && (game != 'S')) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return (isdigit(revision) || (revision == 'T'));
|
||||
}
|
||||
|
||||
bool specific_version_is_xb(uint32_t specific_version) {
|
||||
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
|
||||
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
|
||||
}
|
||||
|
||||
bool specific_version_is_bb(uint32_t specific_version) {
|
||||
// BB specific_versions are 5XXX, where X is an encoding of the revision number
|
||||
return (specific_version & 0xFF000000) == 0x35000000;
|
||||
}
|
||||
|
||||
const char* file_path_token_for_version(Version version) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
return "dc-nte";
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return "dc-11-2000";
|
||||
case Version::DC_V1:
|
||||
return "dc-v1";
|
||||
case Version::DC_V2:
|
||||
return "dc-v2";
|
||||
case Version::PC_NTE:
|
||||
return "pc-nte";
|
||||
case Version::PC_V2:
|
||||
return "pc-v2";
|
||||
case Version::GC_NTE:
|
||||
return "gc-nte";
|
||||
case Version::GC_V3:
|
||||
return "gc-v3";
|
||||
case Version::GC_EP3_NTE:
|
||||
return "gc-ep3-nte";
|
||||
case Version::GC_EP3:
|
||||
return "gc-ep3";
|
||||
case Version::XB_V3:
|
||||
return "xb-v3";
|
||||
case Version::BB_V4:
|
||||
return "bb-v4";
|
||||
default:
|
||||
throw runtime_error("invalid game version");
|
||||
}
|
||||
}
|
||||
#include "Version.hh"
|
||||
|
||||
#include <strings.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char* login_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "console-login";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-login";
|
||||
case Version::XB_V3:
|
||||
return "xb-login";
|
||||
case Version::BB_V4:
|
||||
return "bb-init";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
const char* lobby_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "console-lobby";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-lobby";
|
||||
case Version::XB_V3:
|
||||
return "xb-lobby";
|
||||
case Version::BB_V4:
|
||||
return "bb-lobby";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
const char* proxy_port_name_for_version(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::GC_NTE:
|
||||
return "dc-proxy";
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
return "gc-proxy";
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
return "pc-proxy";
|
||||
case Version::XB_V3:
|
||||
return "xb-proxy";
|
||||
case Version::BB_V4:
|
||||
return "bb-proxy";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Version>(Version v) {
|
||||
switch (v) {
|
||||
case Version::PC_PATCH:
|
||||
return "PC_PATCH";
|
||||
case Version::BB_PATCH:
|
||||
return "BB_PATCH";
|
||||
case Version::DC_NTE:
|
||||
return "DC_NTE";
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return "DC_V1_11_2000_PROTOTYPE";
|
||||
case Version::DC_V1:
|
||||
return "DC_V1";
|
||||
case Version::DC_V2:
|
||||
return "DC_V2";
|
||||
case Version::PC_NTE:
|
||||
return "PC_NTE";
|
||||
case Version::PC_V2:
|
||||
return "PC_V2";
|
||||
case Version::GC_NTE:
|
||||
return "GC_NTE";
|
||||
case Version::GC_V3:
|
||||
return "GC_V3";
|
||||
case Version::GC_EP3_NTE:
|
||||
return "GC_EP3_NTE";
|
||||
case Version::GC_EP3:
|
||||
return "GC_EP3";
|
||||
case Version::XB_V3:
|
||||
return "XB_V3";
|
||||
case Version::BB_V4:
|
||||
return "BB_V4";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
Version enum_for_name<Version>(const char* name) {
|
||||
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
|
||||
return Version::PC_PATCH;
|
||||
} else if (!strcmp(name, "BB_PATCH")) {
|
||||
return Version::BB_PATCH;
|
||||
} else if (!strcmp(name, "DC_NTE")) {
|
||||
return Version::DC_NTE;
|
||||
} else if (!strcmp(name, "DC_V1_11_2000_PROTOTYPE")) {
|
||||
return Version::DC_V1_11_2000_PROTOTYPE;
|
||||
} else if (!strcmp(name, "DC_V1")) {
|
||||
return Version::DC_V1;
|
||||
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
|
||||
return Version::DC_V2;
|
||||
} else if (!strcmp(name, "PC_NTE")) {
|
||||
return Version::PC_NTE;
|
||||
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
|
||||
return Version::PC_V2;
|
||||
} else if (!strcmp(name, "GC_NTE")) {
|
||||
return Version::GC_NTE;
|
||||
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
|
||||
return Version::GC_V3;
|
||||
} else if (!strcmp(name, "GC_EP3_NTE")) {
|
||||
return Version::GC_EP3_NTE;
|
||||
} else if (!strcmp(name, "GC_EP3")) {
|
||||
return Version::GC_EP3;
|
||||
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
|
||||
return Version::XB_V3;
|
||||
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
|
||||
return Version::BB_V4;
|
||||
} else {
|
||||
throw invalid_argument("incorrect version name");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
|
||||
switch (behavior) {
|
||||
case ServerBehavior::PC_CONSOLE_DETECT:
|
||||
return "pc_console_detect";
|
||||
case ServerBehavior::LOGIN_SERVER:
|
||||
return "login_server";
|
||||
case ServerBehavior::LOBBY_SERVER:
|
||||
return "lobby_server";
|
||||
case ServerBehavior::PATCH_SERVER_PC:
|
||||
return "patch_server_pc";
|
||||
case ServerBehavior::PATCH_SERVER_BB:
|
||||
return "patch_server_bb";
|
||||
case ServerBehavior::PROXY_SERVER:
|
||||
return "proxy_server";
|
||||
}
|
||||
throw logic_error("invalid server behavior");
|
||||
}
|
||||
|
||||
template <>
|
||||
ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
|
||||
if (!strcasecmp(name, "pc_console_detect")) {
|
||||
return ServerBehavior::PC_CONSOLE_DETECT;
|
||||
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login") || !strcasecmp(name, "data_server_bb")) {
|
||||
return ServerBehavior::LOGIN_SERVER;
|
||||
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
|
||||
return ServerBehavior::LOBBY_SERVER;
|
||||
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
|
||||
return ServerBehavior::PATCH_SERVER_PC;
|
||||
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
|
||||
return ServerBehavior::PATCH_SERVER_BB;
|
||||
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
|
||||
return ServerBehavior::PROXY_SERVER;
|
||||
} else {
|
||||
throw invalid_argument("incorrect server behavior name");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
|
||||
// For versions that don't support send_function_call by default, we need
|
||||
// to set the specific_version based on sub_version. Fortunately, all
|
||||
// versions that share sub_version values also support send_function_call,
|
||||
// so for those versions we get the specific_version later by sending the
|
||||
// VersionDetectDC, VersionDetectGC, or VersionDetectXB call.
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
return SPECIFIC_VERSION_DC_NTE; // 1OJ1
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return SPECIFIC_VERSION_DC_11_2000_PROTOTYPE; // 1OJ2
|
||||
case Version::DC_V1:
|
||||
return SPECIFIC_VERSION_DC_V1_INDETERMINATE; // 1___; need to send VersionDetectDC (but can't on V1; rip)
|
||||
case Version::DC_V2:
|
||||
return SPECIFIC_VERSION_DC_V2_INDETERMINATE; // 2___; need to send VersionDetectDC
|
||||
case Version::PC_V2:
|
||||
return SPECIFIC_VERSION_PC_V2; // 2OJW
|
||||
case Version::GC_NTE:
|
||||
return SPECIFIC_VERSION_GC_NTE; // 3OJT
|
||||
case Version::GC_V3:
|
||||
switch (sub_version) {
|
||||
case 0x32: // GC Ep1&2 EU 50Hz
|
||||
case 0x33: // GC Ep1&2 EU 60Hz
|
||||
return SPECIFIC_VERSION_GC_V3_EU; // 3OP0
|
||||
case 0x36: // GC Ep1&2 US v1.2 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_US_12; // 3OE2
|
||||
case 0x34: // GC Ep1&2 JP v1.3
|
||||
return SPECIFIC_VERSION_GC_V3_JP_13; // 3OJ3
|
||||
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_JP_14; // 3OJ4
|
||||
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
|
||||
return SPECIFIC_VERSION_GC_V3_JP_15; // 3OJ5
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of PSO XB
|
||||
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetectGC
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case Version::GC_EP3_NTE:
|
||||
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
|
||||
case Version::GC_EP3:
|
||||
switch (sub_version) {
|
||||
case 0x41: // GC Ep3 US
|
||||
return SPECIFIC_VERSION_GC_EP3_US; // 3SE0
|
||||
case 0x42: // GC Ep3 EU 50Hz
|
||||
case 0x43: // GC Ep3 EU 60Hz
|
||||
return SPECIFIC_VERSION_GC_EP3_EU; // 3SP0
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
case 0x40: // GC Ep3 trial and GC Ep3 JP
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_EP3_INDETERMINATE; // 3SJ_; need to send VersionDetectGC
|
||||
}
|
||||
case Version::XB_V3:
|
||||
return SPECIFIC_VERSION_XB_V3_INDETERMINATE; // 4O__; need to send VersionDetectXB
|
||||
case Version::BB_V4:
|
||||
return SPECIFIC_VERSION_BB_V4_INDETERMINATE; // 5___; we should be able to determine version from initial login
|
||||
default:
|
||||
return SPECIFIC_VERSION_INDETERMINATE;
|
||||
}
|
||||
}
|
||||
|
||||
bool specific_version_is_indeterminate(uint32_t specific_version) {
|
||||
return ((specific_version & 0x000000FF) == 0);
|
||||
}
|
||||
|
||||
bool specific_version_is_dc(uint32_t specific_version) {
|
||||
// All v1 and v2 specific_versions are DC except 324F4A57 (2OJW), which is PC
|
||||
uint8_t major_version = specific_version >> 24;
|
||||
if (major_version < 0x31 || major_version > 0x32) {
|
||||
return false;
|
||||
}
|
||||
return (specific_version != 0x324F4A57);
|
||||
}
|
||||
|
||||
bool specific_version_is_gc(uint32_t specific_version) {
|
||||
// GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
|
||||
if ((specific_version & 0xFF000000) != 0x33000000) {
|
||||
return false;
|
||||
}
|
||||
char game = specific_version >> 16;
|
||||
if ((game != 'O') && (game != 'S')) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return (isdigit(revision) || (revision == 'T'));
|
||||
}
|
||||
|
||||
bool specific_version_is_xb(uint32_t specific_version) {
|
||||
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
|
||||
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
|
||||
}
|
||||
|
||||
bool specific_version_is_bb(uint32_t specific_version) {
|
||||
// BB specific_versions are 5XXX, where X is an encoding of the revision number
|
||||
return (specific_version & 0xFF000000) == 0x35000000;
|
||||
}
|
||||
|
||||
const char* file_path_token_for_version(Version version) {
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
return "pc-patch";
|
||||
case Version::BB_PATCH:
|
||||
return "bb-patch";
|
||||
case Version::DC_NTE:
|
||||
return "dc-nte";
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return "dc-11-2000";
|
||||
case Version::DC_V1:
|
||||
return "dc-v1";
|
||||
case Version::DC_V2:
|
||||
return "dc-v2";
|
||||
case Version::PC_NTE:
|
||||
return "pc-nte";
|
||||
case Version::PC_V2:
|
||||
return "pc-v2";
|
||||
case Version::GC_NTE:
|
||||
return "gc-nte";
|
||||
case Version::GC_V3:
|
||||
return "gc-v3";
|
||||
case Version::GC_EP3_NTE:
|
||||
return "gc-ep3-nte";
|
||||
case Version::GC_EP3:
|
||||
return "gc-ep3";
|
||||
case Version::XB_V3:
|
||||
return "xb-v3";
|
||||
case Version::BB_V4:
|
||||
return "bb-v4";
|
||||
default:
|
||||
throw runtime_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
+189
-189
@@ -1,189 +1,189 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/Types.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class Version {
|
||||
PC_PATCH = 0,
|
||||
BB_PATCH = 1,
|
||||
DC_NTE = 2,
|
||||
DC_V1_11_2000_PROTOTYPE = 3,
|
||||
DC_V1 = 4,
|
||||
DC_V2 = 5,
|
||||
PC_NTE = 6,
|
||||
PC_V2 = 7,
|
||||
GC_NTE = 8,
|
||||
GC_V3 = 9,
|
||||
GC_EP3_NTE = 10,
|
||||
GC_EP3 = 11,
|
||||
XB_V3 = 12,
|
||||
BB_V4 = 13,
|
||||
UNKNOWN = 15,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
|
||||
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
|
||||
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
|
||||
|
||||
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don't forget to update VersionNameColors in config.json");
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Version>(Version v);
|
||||
template <>
|
||||
Version enum_for_name<Version>(const char* name);
|
||||
|
||||
inline bool is_any_nte(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::GC_NTE) ||
|
||||
(version == Version::GC_EP3_NTE);
|
||||
}
|
||||
|
||||
inline bool is_patch(Version version) {
|
||||
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
|
||||
}
|
||||
inline bool is_pre_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
|
||||
}
|
||||
inline bool is_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
|
||||
}
|
||||
inline bool is_v2(Version version) {
|
||||
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
|
||||
}
|
||||
inline bool is_v1_or_v2(Version version) {
|
||||
return is_v1(version) || is_v2(version);
|
||||
}
|
||||
inline bool is_v3(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool is_v4(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool is_ep3(Version version) {
|
||||
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_dc(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_gc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_sh4(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_x86(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::XB_V3) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
inline bool is_ppc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_big_endian(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
inline bool uses_v2_encryption(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::GC_NTE);
|
||||
}
|
||||
inline bool uses_v3_encryption(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool uses_v4_encryption(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool uses_utf16(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_V2_INDETERMINATE = 0x32000000; // 2___
|
||||
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_EU = 0x334F5030; // 3OP0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_13 = 0x334F4A33; // 3OJ3
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_15 = 0x334F4A35; // 3OJ5
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_INDETERMINATE = 0x33534A00; // 3SJ_
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_JP = 0x33534A30; // 3SJ0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_EU = 0x33535030; // 3SP0
|
||||
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__
|
||||
constexpr uint32_t SPECIFIC_VERSION_BB_V4_INDETERMINATE = 0x35000000; // 5___
|
||||
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
|
||||
|
||||
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
|
||||
bool specific_version_is_indeterminate(uint32_t specific_version);
|
||||
bool specific_version_is_dc(uint32_t specific_version);
|
||||
bool specific_version_is_gc(uint32_t specific_version);
|
||||
bool specific_version_is_xb(uint32_t specific_version);
|
||||
bool specific_version_is_bb(uint32_t specific_version);
|
||||
|
||||
enum class ServerBehavior {
|
||||
PC_CONSOLE_DETECT = 0,
|
||||
LOGIN_SERVER,
|
||||
LOBBY_SERVER,
|
||||
PATCH_SERVER_PC,
|
||||
PATCH_SERVER_BB,
|
||||
PROXY_SERVER,
|
||||
};
|
||||
|
||||
const char* login_port_name_for_version(Version v);
|
||||
const char* lobby_port_name_for_version(Version v);
|
||||
const char* proxy_port_name_for_version(Version v);
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
|
||||
template <>
|
||||
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
|
||||
|
||||
const char* file_path_token_for_version(Version version);
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/Types.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class Version {
|
||||
PC_PATCH = 0,
|
||||
BB_PATCH = 1,
|
||||
DC_NTE = 2,
|
||||
DC_V1_11_2000_PROTOTYPE = 3,
|
||||
DC_V1 = 4,
|
||||
DC_V2 = 5,
|
||||
PC_NTE = 6,
|
||||
PC_V2 = 7,
|
||||
GC_NTE = 8,
|
||||
GC_V3 = 9,
|
||||
GC_EP3_NTE = 10,
|
||||
GC_EP3 = 11,
|
||||
XB_V3 = 12,
|
||||
BB_V4 = 13,
|
||||
UNKNOWN = 15,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
|
||||
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
|
||||
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
|
||||
|
||||
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don't forget to update VersionNameColors in config.json");
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Version>(Version v);
|
||||
template <>
|
||||
Version enum_for_name<Version>(const char* name);
|
||||
|
||||
inline bool is_any_nte(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::GC_NTE) ||
|
||||
(version == Version::GC_EP3_NTE);
|
||||
}
|
||||
|
||||
inline bool is_patch(Version version) {
|
||||
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
|
||||
}
|
||||
inline bool is_pre_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
|
||||
}
|
||||
inline bool is_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
|
||||
}
|
||||
inline bool is_v2(Version version) {
|
||||
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
|
||||
}
|
||||
inline bool is_v1_or_v2(Version version) {
|
||||
return is_v1(version) || is_v2(version);
|
||||
}
|
||||
inline bool is_v3(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool is_v4(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool is_ep3(Version version) {
|
||||
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_dc(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_gc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_sh4(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_x86(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::XB_V3) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
inline bool is_ppc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_big_endian(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
inline bool uses_v2_encryption(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::GC_NTE);
|
||||
}
|
||||
inline bool uses_v3_encryption(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool uses_v4_encryption(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool uses_utf16(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_NTE = 0x314F4A31; // 1OJ1
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_11_2000_PROTOTYPE = 0x314F4A32; // 1OJ2
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_V1_INDETERMINATE = 0x31000000; // 1___
|
||||
constexpr uint32_t SPECIFIC_VERSION_DC_V2_INDETERMINATE = 0x32000000; // 2___
|
||||
constexpr uint32_t SPECIFIC_VERSION_PC_V2 = 0x324F4A57; // 2OJW
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_NTE = 0x334F4A54; // 3OJT
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_EU = 0x334F5030; // 3OP0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_US_12 = 0x334F4532; // 3OE2
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_13 = 0x334F4A33; // 3OJ3
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_14 = 0x334F4A34; // 3OJ4
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_JP_15 = 0x334F4A35; // 3OJ5
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_V3_INDETERMINATE = 0x334F0000; // 3O__
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_INDETERMINATE = 0x33534A00; // 3SJ_
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_NTE = 0x33534A54; // 3SJT
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_JP = 0x33534A30; // 3SJ0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_US = 0x33534530; // 3SE0
|
||||
constexpr uint32_t SPECIFIC_VERSION_GC_EP3_EU = 0x33535030; // 3SP0
|
||||
constexpr uint32_t SPECIFIC_VERSION_XB_V3_INDETERMINATE = 0x344F0000; // 4O__
|
||||
constexpr uint32_t SPECIFIC_VERSION_BB_V4_INDETERMINATE = 0x35000000; // 5___
|
||||
constexpr uint32_t SPECIFIC_VERSION_INDETERMINATE = 0x00000000; // ____
|
||||
|
||||
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
|
||||
bool specific_version_is_indeterminate(uint32_t specific_version);
|
||||
bool specific_version_is_dc(uint32_t specific_version);
|
||||
bool specific_version_is_gc(uint32_t specific_version);
|
||||
bool specific_version_is_xb(uint32_t specific_version);
|
||||
bool specific_version_is_bb(uint32_t specific_version);
|
||||
|
||||
enum class ServerBehavior {
|
||||
PC_CONSOLE_DETECT = 0,
|
||||
LOGIN_SERVER,
|
||||
LOBBY_SERVER,
|
||||
PATCH_SERVER_PC,
|
||||
PATCH_SERVER_BB,
|
||||
PROXY_SERVER,
|
||||
};
|
||||
|
||||
const char* login_port_name_for_version(Version v);
|
||||
const char* lobby_port_name_for_version(Version v);
|
||||
const char* proxy_port_name_for_version(Version v);
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
|
||||
template <>
|
||||
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
|
||||
|
||||
const char* file_path_token_for_version(Version version);
|
||||
|
||||
+319
-319
@@ -1,319 +1,319 @@
|
||||
#include "WordSelectTable.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename RetT, typename ReadT>
|
||||
static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<RetT> ret;
|
||||
auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
|
||||
while (!entries_r.eof()) {
|
||||
ret.emplace_back(entries_r.get<ReadT>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename RetT, typename ReadT, typename OffsetT>
|
||||
static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<vector<RetT>> ret;
|
||||
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
|
||||
while (!pointers_r.eof()) {
|
||||
uint32_t sub_offset = pointers_r.get<OffsetT>();
|
||||
uint32_t sub_count = pointers_r.get<OffsetT>();
|
||||
ret.emplace_back(read_direct_table<RetT, ReadT>(base_r, sub_offset, sub_count));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct NonWindowsRootT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
U32T strings_table;
|
||||
U32T table1;
|
||||
U32T table2;
|
||||
U32T token_id_to_string_id_table;
|
||||
U32T table4;
|
||||
U32T article_types_table;
|
||||
U32T table6;
|
||||
} __packed__;
|
||||
|
||||
using NonWindowsRoot = NonWindowsRootT<false>;
|
||||
using NonWindowsRootBE = NonWindowsRootT<true>;
|
||||
check_struct_size(NonWindowsRoot, 0x1C);
|
||||
check_struct_size(NonWindowsRootBE, 0x1C);
|
||||
|
||||
struct PCV2Root {
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t table1;
|
||||
le_uint32_t table2;
|
||||
le_uint32_t token_id_to_string_id_table;
|
||||
le_uint32_t table4;
|
||||
le_uint32_t article_types_table;
|
||||
le_uint32_t table6;
|
||||
} __packed_ws__(PCV2Root, 0x20);
|
||||
|
||||
struct BBRoot {
|
||||
le_uint32_t table1;
|
||||
le_uint32_t table2;
|
||||
le_uint32_t token_id_to_string_id_table;
|
||||
le_uint32_t table4;
|
||||
le_uint32_t article_types_table;
|
||||
le_uint32_t table6;
|
||||
} __packed_ws__(BBRoot, 0x18);
|
||||
|
||||
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
|
||||
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
|
||||
StringReader r(data);
|
||||
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
|
||||
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
|
||||
|
||||
{
|
||||
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
|
||||
while (!string_offset_r.eof()) {
|
||||
string raw_s = r.pget_cstr(string_offset_r.template get<U32T>());
|
||||
this->strings.emplace_back(use_sjis ? tt_sega_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
|
||||
}
|
||||
}
|
||||
|
||||
// this->table1 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table2, Table2Count);
|
||||
this->token_id_to_string_id = read_direct_table<size_t, U16T>(r, root.token_id_to_string_id_table, TokenCount);
|
||||
// this->table4 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table4, Table4Count);
|
||||
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
|
||||
// this->table6 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table6, Table6Count);
|
||||
}
|
||||
|
||||
template <typename RootT, size_t TokenCount>
|
||||
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
|
||||
if (!unitxt_collection) {
|
||||
throw runtime_error("a unitxt collection is required");
|
||||
}
|
||||
|
||||
StringReader r(data);
|
||||
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
|
||||
const auto& root = r.pget<RootT>(root_offset);
|
||||
this->strings = *unitxt_collection;
|
||||
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
|
||||
this->token_id_to_string_id = read_direct_table<size_t, le_uint16_t>(r, root.token_id_to_string_id_table, TokenCount);
|
||||
// this->table4 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table4, Table4Count);
|
||||
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
|
||||
// this->table6 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table6, Table6Count);
|
||||
}
|
||||
|
||||
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
|
||||
switch (version) {
|
||||
case Version::DC_NTE: {
|
||||
if (data.size() < 4) {
|
||||
throw runtime_error("data is too small");
|
||||
}
|
||||
string decrypted = data.substr(0, data.size() - 4);
|
||||
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
|
||||
PSOV2Encryption crypt(seed);
|
||||
crypt.decrypt(decrypted);
|
||||
this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis);
|
||||
break;
|
||||
}
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3:
|
||||
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unsupported word select data version");
|
||||
}
|
||||
}
|
||||
|
||||
const string& WordSelectSet::string_for_token(uint16_t token_id) const {
|
||||
return this->strings.at(this->token_id_to_string_id.at(token_id));
|
||||
}
|
||||
|
||||
void WordSelectSet::print(FILE* stream) const {
|
||||
fprintf(stream, "strings:\n");
|
||||
for (size_t z = 0; z < this->strings.size(); z++) {
|
||||
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
|
||||
}
|
||||
fprintf(stream, "token_id_to_string_id:\n");
|
||||
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) {
|
||||
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
WordSelectTable::WordSelectTable(
|
||||
const WordSelectSet& dc_nte_ws,
|
||||
const WordSelectSet& dc_112000_ws,
|
||||
const WordSelectSet& dc_v1_ws,
|
||||
const WordSelectSet& dc_v2_ws,
|
||||
const WordSelectSet& pc_nte_ws,
|
||||
const WordSelectSet& pc_v2_ws,
|
||||
const WordSelectSet& gc_nte_ws,
|
||||
const WordSelectSet& gc_v3_ws,
|
||||
const WordSelectSet& gc_ep3_nte_ws,
|
||||
const WordSelectSet& gc_ep3_ws,
|
||||
const WordSelectSet& xb_v3_ws,
|
||||
const WordSelectSet& bb_v4_ws,
|
||||
const vector<vector<string>>& name_alias_lists) {
|
||||
|
||||
unordered_map<string, string> name_to_canonical_name;
|
||||
for (const auto& alias_list : name_alias_lists) {
|
||||
if (alias_list.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
auto it = alias_list.begin();
|
||||
auto canonical_name = *it;
|
||||
for (it++; it != alias_list.end(); it++) {
|
||||
name_to_canonical_name.emplace(*it, canonical_name);
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Token>> dynamic_tokens;
|
||||
{
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z);
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
token->canonical_name = "__BLANK__";
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
|
||||
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
|
||||
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
|
||||
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws,
|
||||
&pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_ws,
|
||||
&gc_ep3_nte_ws, &gc_ep3_ws, &xb_v3_ws, &bb_v4_ws};
|
||||
|
||||
for (size_t s_version = 0; s_version < ws_sets.size(); s_version++) {
|
||||
Version version = static_cast<Version>(static_cast<size_t>(Version::DC_NTE) + s_version);
|
||||
const auto& ws_set = *ws_sets[s_version];
|
||||
auto& index = this->tokens_by_version.at(s_version);
|
||||
|
||||
index.reserve(ws_set.num_tokens());
|
||||
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
|
||||
const string& str = ws_set.string_for_token(token_id);
|
||||
|
||||
string canonical_name;
|
||||
try {
|
||||
canonical_name = name_to_canonical_name.at(str);
|
||||
} catch (const out_of_range&) {
|
||||
canonical_name = str;
|
||||
}
|
||||
|
||||
auto token_it = this->name_to_token.find(canonical_name);
|
||||
if (token_it == this->name_to_token.end()) {
|
||||
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first;
|
||||
token_it->second->canonical_name = std::move(canonical_name);
|
||||
}
|
||||
token_it->second->slot_for_version(version) = token_id;
|
||||
index.emplace_back(token_it->second);
|
||||
}
|
||||
|
||||
size_t dynamic_token_base_id = ws_set.num_tokens();
|
||||
for (size_t z = 0; z < dynamic_tokens.size(); z++) {
|
||||
auto& token = dynamic_tokens[z];
|
||||
token->slot_for_version(version) = dynamic_token_base_id + z;
|
||||
index.emplace_back(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WordSelectTable::print(FILE* stream) const {
|
||||
fprintf(stream, "DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
|
||||
for (const auto& it : this->name_to_token) {
|
||||
const auto& token = it.second;
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (token->values_by_version[z] == 0xFFFF) {
|
||||
fprintf(stream, " ");
|
||||
} else {
|
||||
fprintf(stream, "%04hX ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
fprintf(stream, "%s\n", serialized.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WordSelectTable::print_index(FILE* stream, Version v) const {
|
||||
fprintf(stream, " DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
|
||||
const auto& index = this->tokens_for_version(v);
|
||||
for (size_t token_id = 0; token_id < index.size(); token_id++) {
|
||||
const auto& token = index[token_id];
|
||||
fprintf(stream, "%04zX => ", token_id);
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (token->values_by_version[z] == 0xFFFF) {
|
||||
fprintf(stream, " ");
|
||||
} else {
|
||||
fprintf(stream, "%04hX ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
fprintf(stream, "%s\n", serialized.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
WordSelectMessage WordSelectTable::translate(
|
||||
const WordSelectMessage& msg,
|
||||
Version from_version,
|
||||
Version to_version) const {
|
||||
const auto& index = this->tokens_for_version(from_version);
|
||||
|
||||
WordSelectMessage ret;
|
||||
for (size_t z = 0; z < ret.tokens.size(); z++) {
|
||||
if (msg.tokens[z] == 0xFFFF) {
|
||||
ret.tokens[z] = 0xFFFF;
|
||||
} else {
|
||||
const auto& token = index.at(msg.tokens[z]);
|
||||
if (!token) {
|
||||
throw runtime_error(string_printf("token %04hX does not exist in the index", msg.tokens[z].load()));
|
||||
}
|
||||
ret.tokens[z] = token->slot_for_version(to_version);
|
||||
if (ret.tokens[z] == 0xFFFF) {
|
||||
throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.num_tokens = msg.num_tokens;
|
||||
ret.target_type = msg.target_type;
|
||||
ret.numeric_parameter = msg.numeric_parameter;
|
||||
ret.unknown_a4 = msg.unknown_a4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
WordSelectTable::Token::Token() {
|
||||
for (size_t z = 0; z < this->values_by_version.size(); z++) {
|
||||
this->values_by_version[z] = 0xFFFF;
|
||||
}
|
||||
}
|
||||
#include "WordSelectTable.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename RetT, typename ReadT>
|
||||
static vector<RetT> read_direct_table(const StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<RetT> ret;
|
||||
auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
|
||||
while (!entries_r.eof()) {
|
||||
ret.emplace_back(entries_r.get<ReadT>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename RetT, typename ReadT, typename OffsetT>
|
||||
static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<vector<RetT>> ret;
|
||||
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
|
||||
while (!pointers_r.eof()) {
|
||||
uint32_t sub_offset = pointers_r.get<OffsetT>();
|
||||
uint32_t sub_count = pointers_r.get<OffsetT>();
|
||||
ret.emplace_back(read_direct_table<RetT, ReadT>(base_r, sub_offset, sub_count));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct NonWindowsRootT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
U32T strings_table;
|
||||
U32T table1;
|
||||
U32T table2;
|
||||
U32T token_id_to_string_id_table;
|
||||
U32T table4;
|
||||
U32T article_types_table;
|
||||
U32T table6;
|
||||
} __packed__;
|
||||
|
||||
using NonWindowsRoot = NonWindowsRootT<false>;
|
||||
using NonWindowsRootBE = NonWindowsRootT<true>;
|
||||
check_struct_size(NonWindowsRoot, 0x1C);
|
||||
check_struct_size(NonWindowsRootBE, 0x1C);
|
||||
|
||||
struct PCV2Root {
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t table1;
|
||||
le_uint32_t table2;
|
||||
le_uint32_t token_id_to_string_id_table;
|
||||
le_uint32_t table4;
|
||||
le_uint32_t article_types_table;
|
||||
le_uint32_t table6;
|
||||
} __packed_ws__(PCV2Root, 0x20);
|
||||
|
||||
struct BBRoot {
|
||||
le_uint32_t table1;
|
||||
le_uint32_t table2;
|
||||
le_uint32_t token_id_to_string_id_table;
|
||||
le_uint32_t table4;
|
||||
le_uint32_t article_types_table;
|
||||
le_uint32_t table6;
|
||||
} __packed_ws__(BBRoot, 0x18);
|
||||
|
||||
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
|
||||
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
|
||||
StringReader r(data);
|
||||
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
|
||||
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
|
||||
|
||||
{
|
||||
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
|
||||
while (!string_offset_r.eof()) {
|
||||
string raw_s = r.pget_cstr(string_offset_r.template get<U32T>());
|
||||
this->strings.emplace_back(use_sjis ? tt_sega_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
|
||||
}
|
||||
}
|
||||
|
||||
// this->table1 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table2, Table2Count);
|
||||
this->token_id_to_string_id = read_direct_table<size_t, U16T>(r, root.token_id_to_string_id_table, TokenCount);
|
||||
// this->table4 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table4, Table4Count);
|
||||
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
|
||||
// this->table6 = read_indirect_table<uint16_t, U16T, U32T>(r, root.table6, Table6Count);
|
||||
}
|
||||
|
||||
template <typename RootT, size_t TokenCount>
|
||||
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
|
||||
if (!unitxt_collection) {
|
||||
throw runtime_error("a unitxt collection is required");
|
||||
}
|
||||
|
||||
StringReader r(data);
|
||||
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
|
||||
const auto& root = r.pget<RootT>(root_offset);
|
||||
this->strings = *unitxt_collection;
|
||||
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
|
||||
this->token_id_to_string_id = read_direct_table<size_t, le_uint16_t>(r, root.token_id_to_string_id_table, TokenCount);
|
||||
// this->table4 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table4, Table4Count);
|
||||
// this->article_types = read_direct_table<uint8_t, uint8_t>(r, root.article_types_table, ArticleTypesCount);
|
||||
// this->table6 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table6, Table6Count);
|
||||
}
|
||||
|
||||
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
|
||||
switch (version) {
|
||||
case Version::DC_NTE: {
|
||||
if (data.size() < 4) {
|
||||
throw runtime_error("data is too small");
|
||||
}
|
||||
string decrypted = data.substr(0, data.size() - 4);
|
||||
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
|
||||
PSOV2Encryption crypt(seed);
|
||||
crypt.decrypt(decrypted);
|
||||
this->parse_non_windows_t<false, 0x469, 0x466>(decrypted, use_sjis);
|
||||
break;
|
||||
}
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
this->parse_non_windows_t<false, 0x45E, 0x44B>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
this->parse_non_windows_t<false, 0x467, 0x457>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3:
|
||||
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
this->parse_non_windows_t<false, 0x67B, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), use_sjis);
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unsupported word select data version");
|
||||
}
|
||||
}
|
||||
|
||||
const string& WordSelectSet::string_for_token(uint16_t token_id) const {
|
||||
return this->strings.at(this->token_id_to_string_id.at(token_id));
|
||||
}
|
||||
|
||||
void WordSelectSet::print(FILE* stream) const {
|
||||
fprintf(stream, "strings:\n");
|
||||
for (size_t z = 0; z < this->strings.size(); z++) {
|
||||
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
|
||||
}
|
||||
fprintf(stream, "token_id_to_string_id:\n");
|
||||
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) {
|
||||
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
WordSelectTable::WordSelectTable(
|
||||
const WordSelectSet& dc_nte_ws,
|
||||
const WordSelectSet& dc_112000_ws,
|
||||
const WordSelectSet& dc_v1_ws,
|
||||
const WordSelectSet& dc_v2_ws,
|
||||
const WordSelectSet& pc_nte_ws,
|
||||
const WordSelectSet& pc_v2_ws,
|
||||
const WordSelectSet& gc_nte_ws,
|
||||
const WordSelectSet& gc_v3_ws,
|
||||
const WordSelectSet& gc_ep3_nte_ws,
|
||||
const WordSelectSet& gc_ep3_ws,
|
||||
const WordSelectSet& xb_v3_ws,
|
||||
const WordSelectSet& bb_v4_ws,
|
||||
const vector<vector<string>>& name_alias_lists) {
|
||||
|
||||
unordered_map<string, string> name_to_canonical_name;
|
||||
for (const auto& alias_list : name_alias_lists) {
|
||||
if (alias_list.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
auto it = alias_list.begin();
|
||||
auto canonical_name = *it;
|
||||
for (it++; it != alias_list.end(); it++) {
|
||||
name_to_canonical_name.emplace(*it, canonical_name);
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Token>> dynamic_tokens;
|
||||
{
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
token->canonical_name = string_printf("__PLAYER_%zu_NAME__", z);
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
token->canonical_name = "__BLANK__";
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
|
||||
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
|
||||
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
|
||||
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws,
|
||||
&pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_ws,
|
||||
&gc_ep3_nte_ws, &gc_ep3_ws, &xb_v3_ws, &bb_v4_ws};
|
||||
|
||||
for (size_t s_version = 0; s_version < ws_sets.size(); s_version++) {
|
||||
Version version = static_cast<Version>(static_cast<size_t>(Version::DC_NTE) + s_version);
|
||||
const auto& ws_set = *ws_sets[s_version];
|
||||
auto& index = this->tokens_by_version.at(s_version);
|
||||
|
||||
index.reserve(ws_set.num_tokens());
|
||||
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
|
||||
const string& str = ws_set.string_for_token(token_id);
|
||||
|
||||
string canonical_name;
|
||||
try {
|
||||
canonical_name = name_to_canonical_name.at(str);
|
||||
} catch (const out_of_range&) {
|
||||
canonical_name = str;
|
||||
}
|
||||
|
||||
auto token_it = this->name_to_token.find(canonical_name);
|
||||
if (token_it == this->name_to_token.end()) {
|
||||
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first;
|
||||
token_it->second->canonical_name = std::move(canonical_name);
|
||||
}
|
||||
token_it->second->slot_for_version(version) = token_id;
|
||||
index.emplace_back(token_it->second);
|
||||
}
|
||||
|
||||
size_t dynamic_token_base_id = ws_set.num_tokens();
|
||||
for (size_t z = 0; z < dynamic_tokens.size(); z++) {
|
||||
auto& token = dynamic_tokens[z];
|
||||
token->slot_for_version(version) = dynamic_token_base_id + z;
|
||||
index.emplace_back(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WordSelectTable::print(FILE* stream) const {
|
||||
fprintf(stream, "DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
|
||||
for (const auto& it : this->name_to_token) {
|
||||
const auto& token = it.second;
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (token->values_by_version[z] == 0xFFFF) {
|
||||
fprintf(stream, " ");
|
||||
} else {
|
||||
fprintf(stream, "%04hX ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
fprintf(stream, "%s\n", serialized.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WordSelectTable::print_index(FILE* stream, Version v) const {
|
||||
fprintf(stream, " DCN DC11 DCv1 DCv2 PCN PCv2 GCN GCv3 Ep3N Ep3 XBv3 BBv4 CANONICAL-NAME\n");
|
||||
const auto& index = this->tokens_for_version(v);
|
||||
for (size_t token_id = 0; token_id < index.size(); token_id++) {
|
||||
const auto& token = index[token_id];
|
||||
fprintf(stream, "%04zX => ", token_id);
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (token->values_by_version[z] == 0xFFFF) {
|
||||
fprintf(stream, " ");
|
||||
} else {
|
||||
fprintf(stream, "%04hX ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = JSON(token->canonical_name).serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
fprintf(stream, "%s\n", serialized.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
WordSelectMessage WordSelectTable::translate(
|
||||
const WordSelectMessage& msg,
|
||||
Version from_version,
|
||||
Version to_version) const {
|
||||
const auto& index = this->tokens_for_version(from_version);
|
||||
|
||||
WordSelectMessage ret;
|
||||
for (size_t z = 0; z < ret.tokens.size(); z++) {
|
||||
if (msg.tokens[z] == 0xFFFF) {
|
||||
ret.tokens[z] = 0xFFFF;
|
||||
} else {
|
||||
const auto& token = index.at(msg.tokens[z]);
|
||||
if (!token) {
|
||||
throw runtime_error(string_printf("token %04hX does not exist in the index", msg.tokens[z].load()));
|
||||
}
|
||||
ret.tokens[z] = token->slot_for_version(to_version);
|
||||
if (ret.tokens[z] == 0xFFFF) {
|
||||
throw runtime_error(string_printf("token %04hX has no translation", msg.tokens[z].load()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.num_tokens = msg.num_tokens;
|
||||
ret.target_type = msg.target_type;
|
||||
ret.numeric_parameter = msg.numeric_parameter;
|
||||
ret.unknown_a4 = msg.unknown_a4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
WordSelectTable::Token::Token() {
|
||||
for (size_t z = 0; z < this->values_by_version.size(); z++) {
|
||||
this->values_by_version[z] = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
+110
-110
@@ -1,110 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "QuestScript.hh"
|
||||
|
||||
class WordSelectSet {
|
||||
public:
|
||||
WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis);
|
||||
~WordSelectSet() = default;
|
||||
|
||||
inline size_t num_strings() const {
|
||||
return this->strings.size();
|
||||
}
|
||||
inline size_t num_tokens() const {
|
||||
return this->token_id_to_string_id.size();
|
||||
}
|
||||
|
||||
const std::string& string_for_token(uint16_t token_id) const;
|
||||
|
||||
void print(FILE* stream) const;
|
||||
|
||||
protected:
|
||||
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
|
||||
void parse_non_windows_t(const std::string& data, bool use_sjis);
|
||||
template <typename RootT, size_t TokenCount>
|
||||
void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection);
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::vector<size_t> token_id_to_string_id;
|
||||
// Note: PC NTE and PC have exactly the same parameters
|
||||
// => DC NTE DC112000 DCv1 DCv2 PCNTE/PC GC NTE GC XB Ep3 NTE Ep3 USA BB
|
||||
// root: => 000074DC 000072A4 0000755C 0000755C 00002B50 0000AB04 0000BCAC 0000B620 0000B648 0000B914 0000B5FC
|
||||
// u32 ???: => 00002A9C
|
||||
// TODO
|
||||
// u32 ???: => 00002B14
|
||||
// TODO
|
||||
// u32 strings_table: => 00006338 0000612C 000063C0 000063C0 (unitxt) 00009208 00009C9C 00009C34 00009C5C 00009904 (unitxt)
|
||||
// u32 string_offset[COUNT]: => 469 45E 467 467 (unitxt) 63F 804 67B 67C 804 (unitxt)
|
||||
// char string[...\0]
|
||||
// u32 table1: => 00000B90 00000B54 00000D3C 00000D3C 00001018 0000100C 000012F0 000012F0 000012F0 000011D0 000012F0
|
||||
// {u32 offset, u32 count}[COUNT]: => 94 122 93 93 F9 F9 126 126 126 17F 126
|
||||
// u16[count]
|
||||
// u32 table2: => 00001178 00001108 00001300 00001300 000019D8 000019CC 00001EE8 00001EE8 00001EE8 00001DC8 00001EE8
|
||||
// {u32 offset, u32 count}[COUNT]: => 7 7 7 7 7 7 13 13 13 13 13
|
||||
// u16[count]
|
||||
// u32 token_id_to_string_id_table => 000011B0 00001140 00001338 00001338 00001A10 00001A04 00001F80 00001F80 00001F80 00001E60 00001F80
|
||||
// u16[COUNT] string_id_for_token_id => 466 44B 457 457 645 693 68C 68C 68C 68C 68C
|
||||
// u32 table4: => 00001A5C 00001B08 00001D1C 00001D1C 000027D0 000027C4 00002DCC 00002DCC 00002DCC 00002CAC 00002DCC
|
||||
// (non-NTE) {u32 offset, u32 count}[COUNT]: => 2 2 2 2 2 2 2 2 2 2
|
||||
// u16[count]
|
||||
// (NTE) u16[COUNT] => E1
|
||||
// u32 article_types_table: => 00001C1E 00001B18 00001D2C 00001D2C 000027E0 000027D4 00002DDC 00002DDC 00002DDC 00002CBC 00002DDC
|
||||
// u8[COUNT] article_types => 1C8 166 166 166 266 266 28A 28A 28A 28A 266
|
||||
// u32 table6: => 00001E28 00001CBC 00001ED0 00001ED0 00002A84 00002A78 000030A4 000030A4 000030A4 00002F84 00003080
|
||||
// {u32 offset, u32 count}[3]:
|
||||
// u16[count]
|
||||
};
|
||||
|
||||
class WordSelectTable {
|
||||
public:
|
||||
WordSelectTable(
|
||||
const WordSelectSet& dc_nte_ws,
|
||||
const WordSelectSet& dc_112000_ws,
|
||||
const WordSelectSet& dc_v1_ws,
|
||||
const WordSelectSet& dc_v2_ws,
|
||||
const WordSelectSet& pc_nte_ws,
|
||||
const WordSelectSet& pc_v2_ws,
|
||||
const WordSelectSet& gc_nte_ws,
|
||||
const WordSelectSet& gc_v3_ws,
|
||||
const WordSelectSet& gc_ep3_nte_ws,
|
||||
const WordSelectSet& gc_ep3_ws,
|
||||
const WordSelectSet& xb_v3_ws,
|
||||
const WordSelectSet& bb_v4_ws,
|
||||
const std::vector<std::vector<std::string>>& name_alias_lists);
|
||||
|
||||
void print(FILE* stream) const;
|
||||
void print_index(FILE* stream, Version v) const;
|
||||
|
||||
WordSelectMessage translate(
|
||||
const WordSelectMessage& msg,
|
||||
Version from_version,
|
||||
Version to_version) const;
|
||||
|
||||
private:
|
||||
struct Token {
|
||||
std::array<uint16_t, 12> values_by_version;
|
||||
std::string canonical_name;
|
||||
|
||||
Token();
|
||||
|
||||
inline uint16_t& slot_for_version(Version version) {
|
||||
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
inline uint16_t slot_for_version(Version version) const {
|
||||
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::string, std::shared_ptr<Token>> name_to_token;
|
||||
std::array<std::vector<std::shared_ptr<Token>>, 12> tokens_by_version;
|
||||
|
||||
inline const std::vector<std::shared_ptr<Token>>& tokens_for_version(Version version) const {
|
||||
return this->tokens_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "QuestScript.hh"
|
||||
|
||||
class WordSelectSet {
|
||||
public:
|
||||
WordSelectSet(const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis);
|
||||
~WordSelectSet() = default;
|
||||
|
||||
inline size_t num_strings() const {
|
||||
return this->strings.size();
|
||||
}
|
||||
inline size_t num_tokens() const {
|
||||
return this->token_id_to_string_id.size();
|
||||
}
|
||||
|
||||
const std::string& string_for_token(uint16_t token_id) const;
|
||||
|
||||
void print(FILE* stream) const;
|
||||
|
||||
protected:
|
||||
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
|
||||
void parse_non_windows_t(const std::string& data, bool use_sjis);
|
||||
template <typename RootT, size_t TokenCount>
|
||||
void parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection);
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::vector<size_t> token_id_to_string_id;
|
||||
// Note: PC NTE and PC have exactly the same parameters
|
||||
// => DC NTE DC112000 DCv1 DCv2 PCNTE/PC GC NTE GC XB Ep3 NTE Ep3 USA BB
|
||||
// root: => 000074DC 000072A4 0000755C 0000755C 00002B50 0000AB04 0000BCAC 0000B620 0000B648 0000B914 0000B5FC
|
||||
// u32 ???: => 00002A9C
|
||||
// TODO
|
||||
// u32 ???: => 00002B14
|
||||
// TODO
|
||||
// u32 strings_table: => 00006338 0000612C 000063C0 000063C0 (unitxt) 00009208 00009C9C 00009C34 00009C5C 00009904 (unitxt)
|
||||
// u32 string_offset[COUNT]: => 469 45E 467 467 (unitxt) 63F 804 67B 67C 804 (unitxt)
|
||||
// char string[...\0]
|
||||
// u32 table1: => 00000B90 00000B54 00000D3C 00000D3C 00001018 0000100C 000012F0 000012F0 000012F0 000011D0 000012F0
|
||||
// {u32 offset, u32 count}[COUNT]: => 94 122 93 93 F9 F9 126 126 126 17F 126
|
||||
// u16[count]
|
||||
// u32 table2: => 00001178 00001108 00001300 00001300 000019D8 000019CC 00001EE8 00001EE8 00001EE8 00001DC8 00001EE8
|
||||
// {u32 offset, u32 count}[COUNT]: => 7 7 7 7 7 7 13 13 13 13 13
|
||||
// u16[count]
|
||||
// u32 token_id_to_string_id_table => 000011B0 00001140 00001338 00001338 00001A10 00001A04 00001F80 00001F80 00001F80 00001E60 00001F80
|
||||
// u16[COUNT] string_id_for_token_id => 466 44B 457 457 645 693 68C 68C 68C 68C 68C
|
||||
// u32 table4: => 00001A5C 00001B08 00001D1C 00001D1C 000027D0 000027C4 00002DCC 00002DCC 00002DCC 00002CAC 00002DCC
|
||||
// (non-NTE) {u32 offset, u32 count}[COUNT]: => 2 2 2 2 2 2 2 2 2 2
|
||||
// u16[count]
|
||||
// (NTE) u16[COUNT] => E1
|
||||
// u32 article_types_table: => 00001C1E 00001B18 00001D2C 00001D2C 000027E0 000027D4 00002DDC 00002DDC 00002DDC 00002CBC 00002DDC
|
||||
// u8[COUNT] article_types => 1C8 166 166 166 266 266 28A 28A 28A 28A 266
|
||||
// u32 table6: => 00001E28 00001CBC 00001ED0 00001ED0 00002A84 00002A78 000030A4 000030A4 000030A4 00002F84 00003080
|
||||
// {u32 offset, u32 count}[3]:
|
||||
// u16[count]
|
||||
};
|
||||
|
||||
class WordSelectTable {
|
||||
public:
|
||||
WordSelectTable(
|
||||
const WordSelectSet& dc_nte_ws,
|
||||
const WordSelectSet& dc_112000_ws,
|
||||
const WordSelectSet& dc_v1_ws,
|
||||
const WordSelectSet& dc_v2_ws,
|
||||
const WordSelectSet& pc_nte_ws,
|
||||
const WordSelectSet& pc_v2_ws,
|
||||
const WordSelectSet& gc_nte_ws,
|
||||
const WordSelectSet& gc_v3_ws,
|
||||
const WordSelectSet& gc_ep3_nte_ws,
|
||||
const WordSelectSet& gc_ep3_ws,
|
||||
const WordSelectSet& xb_v3_ws,
|
||||
const WordSelectSet& bb_v4_ws,
|
||||
const std::vector<std::vector<std::string>>& name_alias_lists);
|
||||
|
||||
void print(FILE* stream) const;
|
||||
void print_index(FILE* stream, Version v) const;
|
||||
|
||||
WordSelectMessage translate(
|
||||
const WordSelectMessage& msg,
|
||||
Version from_version,
|
||||
Version to_version) const;
|
||||
|
||||
private:
|
||||
struct Token {
|
||||
std::array<uint16_t, 12> values_by_version;
|
||||
std::string canonical_name;
|
||||
|
||||
Token();
|
||||
|
||||
inline uint16_t& slot_for_version(Version version) {
|
||||
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
inline uint16_t slot_for_version(Version version) const {
|
||||
return this->values_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::string, std::shared_ptr<Token>> name_to_token;
|
||||
std::array<std::vector<std::shared_ptr<Token>>, 12> tokens_by_version;
|
||||
|
||||
inline const std::vector<std::shared_ptr<Token>>& tokens_for_version(Version version) const {
|
||||
return this->tokens_by_version.at(static_cast<size_t>(version) - static_cast<size_t>(Version::DC_NTE));
|
||||
}
|
||||
};
|
||||
|
||||
+1232
-1232
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user