Merge branch 'fuzziqersoftware:master' into master
This commit is contained in:
@@ -323,8 +323,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$savechar <slot>`: Saves your current character data on the server in the specified slot (each serial number has 4 slots, numbered 1-4). These slots are separate from BB character slots; using this command does not affect BB characters.
|
||||
* `$loadchar <slot>` (v1 and v2 only): Loads your character data from the specified slot. The changes will be undone if you join a game - to save your changes, disconnect from the lobby.
|
||||
* `$bbchar <username> <password> <slot>`: Use this command when playing on a non-BB version of PSO. If the username and password are correct, this command converts your current character to BB format and saves it on the server in the given slot (1-4). Any character already in that slot is overwritten. (This command is similar to `$savechar`, except it overwrites a BB character slot, and can transfer characters across accounts.) Note that the character's chat data, quick menu config, and bank contents are not copied, since there is no way for the server to request those types of data.
|
||||
* `$edit <stat> <value>`: Modifies your character data. If you are on V3 (GameCube/Xbox), or if the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing. If you are on V1 or V2 (DC or PC, not BB), your changes will be undone if you join a game - to save your changes, disconnect from the lobby.
|
||||
|
||||
* `$edit <stat> <value>`: Modifies your character data. If you are on V3 (GameCube/Xbox), this command does nothing. If you are on V1 or V2 (DC or PC, not BB), your changes will be undone if you join a game - to save your changes, disconnect from the lobby. If cheats are allowed on the server, `<stat>` can be any of `atp`, `mst`, `evp`, `hp`, `dfp`, `ata`, `lck`, `meseta`, `exp`, `level`, `namecolor`, `secid`, `name`, `npc`, or `tech`. If cheats are not allowed, only `namecolor`, `name`, and `npc` can be used.
|
||||
* Blue Burst player commands (game server only)
|
||||
* `$bank [number]`: Switches your current bank, so you can access your other character's banks (if `number` is 1-4) or your shared account bank (if `number` is 0). If `number` is not given, switches back to your current character's bank.
|
||||
* `$save`: Saves your character, system, and Guild Card data immediately. (By default, your character is saved every 60 seconds while online, and your account and Guild Card data are saved whenever they change.)
|
||||
|
||||
+67
-20
@@ -1017,35 +1017,32 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO.");
|
||||
}
|
||||
|
||||
if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && !(c->license->flags & License::Flag::CHEAT_ANYWHERE)) {
|
||||
send_text_message(l, "$C6Cheats are disabled\non this server");
|
||||
return;
|
||||
}
|
||||
bool cheats_allowed = ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || (c->license->flags & License::Flag::CHEAT_ANYWHERE));
|
||||
|
||||
string encoded_args = tolower(args);
|
||||
vector<string> tokens = split(encoded_args, ' ');
|
||||
|
||||
try {
|
||||
auto p = c->character();
|
||||
if (tokens.at(0) == "atp") {
|
||||
if (tokens.at(0) == "atp" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "mst") {
|
||||
} else if (tokens.at(0) == "mst" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.mst = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "evp") {
|
||||
} else if (tokens.at(0) == "evp" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.evp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "hp") {
|
||||
} else if (tokens.at(0) == "hp" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.hp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "dfp") {
|
||||
} else if (tokens.at(0) == "dfp" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.dfp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "ata") {
|
||||
} else if (tokens.at(0) == "ata" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.ata = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "lck") {
|
||||
} else if (tokens.at(0) == "lck" && cheats_allowed) {
|
||||
p->disp.stats.char_stats.lck = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "meseta") {
|
||||
} else if (tokens.at(0) == "meseta" && cheats_allowed) {
|
||||
p->disp.stats.meseta = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "exp") {
|
||||
} else if (tokens.at(0) == "exp" && cheats_allowed) {
|
||||
p->disp.stats.experience = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "level") {
|
||||
} else if (tokens.at(0) == "level" && cheats_allowed) {
|
||||
uint32_t level = stoul(tokens.at(1)) - 1;
|
||||
p->disp.stats.reset_to_base(p->disp.visual.char_class, s->level_table);
|
||||
p->disp.stats.advance_to_level(p->disp.visual.char_class, level, s->level_table);
|
||||
@@ -1053,7 +1050,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
uint32_t new_color;
|
||||
sscanf(tokens.at(1).c_str(), "%8X", &new_color);
|
||||
p->disp.visual.name_color = new_color;
|
||||
} else if (tokens.at(0) == "secid") {
|
||||
} else if (tokens.at(0) == "secid" && cheats_allowed) {
|
||||
uint8_t secid = section_id_for_name(tokens.at(1));
|
||||
if (secid == 0xFF) {
|
||||
send_text_message(c, "$C6No such section ID");
|
||||
@@ -1079,7 +1076,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
p->disp.visual.extra_model = npc;
|
||||
p->disp.visual.validation_flags |= 0x02;
|
||||
}
|
||||
} else if (tokens.at(0) == "tech") {
|
||||
} else if (tokens.at(0) == "tech" && cheats_allowed) {
|
||||
uint8_t level = stoul(tokens.at(2)) - 1;
|
||||
if (tokens.at(1) == "all") {
|
||||
for (size_t x = 0; x < 0x14; x++) {
|
||||
@@ -1765,6 +1762,58 @@ static void server_command_ep3_set_def_dice_range(shared_ptr<Client> c, const st
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_ep3_replace_assist_card(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_is_ep3(c, true);
|
||||
check_cheats_enabled(l, c);
|
||||
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
send_text_message(c, "$C6Episode 3 server\nis not initialized");
|
||||
return;
|
||||
}
|
||||
if (l->ep3_server->setup_phase != Episode3::SetupPhase::MAIN_BATTLE) {
|
||||
send_text_message(c, "$C6Battle has not\nyet begun");
|
||||
return;
|
||||
}
|
||||
if (args.empty()) {
|
||||
send_text_message(c, "$C6Missing arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t client_id;
|
||||
string card_name;
|
||||
if (isdigit(args[0])) {
|
||||
auto tokens = split(args, ' ', 1);
|
||||
client_id = stoul(tokens.at(0), nullptr, 0) - 1;
|
||||
card_name = tokens.at(1);
|
||||
} else {
|
||||
client_id = c->lobby_client_id;
|
||||
card_name = args;
|
||||
}
|
||||
if (client_id >= 4) {
|
||||
send_text_message(c, "$C6Invalid client ID");
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<const Episode3::CardIndex::CardEntry> ce;
|
||||
try {
|
||||
ce = l->ep3_server->options.card_index->definition_for_name_normalized(card_name);
|
||||
} catch (const out_of_range&) {
|
||||
send_text_message(c, "$C6Card not found");
|
||||
return;
|
||||
}
|
||||
if (ce->def.type != Episode3::CardType::ASSIST) {
|
||||
send_text_message(c, "$C6Card is not an\nAssist card");
|
||||
return;
|
||||
}
|
||||
l->ep3_server->force_replace_assist_card(client_id, ce->def.card_id);
|
||||
}
|
||||
|
||||
static void server_command_ep3_unset_field_character(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
@@ -1826,10 +1875,7 @@ static void server_command_get_ep3_battle_stat(shared_ptr<Client> c, const std::
|
||||
send_text_message(c, "$C6Battle has not\nyet started");
|
||||
return;
|
||||
}
|
||||
if (c->lobby_client_id >= 4) {
|
||||
throw logic_error("client ID is too large");
|
||||
}
|
||||
auto ps = l->ep3_server->player_states[c->lobby_client_id];
|
||||
auto ps = l->ep3_server->player_states.at(c->lobby_client_id);
|
||||
if (!ps) {
|
||||
send_text_message(c, "$C6Player is missing");
|
||||
return;
|
||||
@@ -1953,6 +1999,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$saverec", {server_command_saverec, nullptr}},
|
||||
{"$sc", {server_command_send_client, proxy_command_send_client}},
|
||||
{"$secid", {server_command_secid, proxy_command_secid}},
|
||||
{"$setassist", {server_command_ep3_replace_assist_card, nullptr}},
|
||||
{"$si", {server_command_server_info, nullptr}},
|
||||
{"$silence", {server_command_silence, nullptr}},
|
||||
{"$song", {server_command_song, proxy_command_song}},
|
||||
|
||||
@@ -135,6 +135,18 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::Config::should_update_vs(const Config& other) const {
|
||||
constexpr uint64_t mask = static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK);
|
||||
return ((this->enabled_flags ^ other.enabled_flags) & mask) ||
|
||||
(this->specific_version != other.specific_version) ||
|
||||
(this->override_random_seed != other.override_random_seed) ||
|
||||
(this->override_section_id != other.override_section_id) ||
|
||||
(this->override_lobby_event != other.override_lobby_event) ||
|
||||
(this->override_lobby_number != other.override_lobby_number) ||
|
||||
(this->proxy_destination_address != other.proxy_destination_address) ||
|
||||
(this->proxy_destination_port != other.proxy_destination_port);
|
||||
}
|
||||
|
||||
Client::Client(
|
||||
shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
@@ -243,6 +255,23 @@ void Client::set_license(shared_ptr<License> l) {
|
||||
this->license = l;
|
||||
}
|
||||
|
||||
void Client::convert_license_to_temporary_if_nte() {
|
||||
// If the session is a prototype version and the license was created and we
|
||||
// should use a temporary license instead, delete the permanent license and
|
||||
// replace it with a temporary license.
|
||||
auto s = this->require_server_state();
|
||||
if (s->use_temp_licenses_for_prototypes &&
|
||||
this->config.check_flag(Client::Flag::LICENSE_WAS_CREATED) &&
|
||||
is_any_nte(this->version())) {
|
||||
this->log.info("Client is a prototype version and the license was created during this session; converting permanent license to temporary license");
|
||||
s->license_index->remove(this->license->serial_number);
|
||||
auto new_l = s->license_index->create_temporary_license();
|
||||
this->license->delete_file();
|
||||
*new_l = std::move(*this->license);
|
||||
this->set_license(new_l);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ServerState> Client::require_server_state() const {
|
||||
auto server = this->server.lock();
|
||||
if (!server) {
|
||||
|
||||
+20
-10
@@ -30,8 +30,15 @@ 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 = 0xFFFFFFFFFC0FFFFB,
|
||||
|
||||
// Version-related flags
|
||||
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
|
||||
LICENSE_WAS_CREATED = 0x0000000000000004, // Server-side only
|
||||
NO_D6_AFTER_LOBBY = 0x0000000000000100,
|
||||
NO_D6 = 0x0000000000000200,
|
||||
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
|
||||
@@ -44,20 +51,20 @@ public:
|
||||
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
|
||||
|
||||
// State flags
|
||||
LOADING = 0x0000000000100000,
|
||||
LOADING_QUEST = 0x0000000000200000,
|
||||
LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000,
|
||||
LOADING_TOURNAMENT = 0x0000000000800000,
|
||||
IN_INFORMATION_MENU = 0x0000000001000000,
|
||||
AT_WELCOME_MESSAGE = 0x0000000002000000,
|
||||
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,
|
||||
AT_BANK_COUNTER = 0x0000000080000000,
|
||||
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000,
|
||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000,
|
||||
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
|
||||
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
|
||||
@@ -99,6 +106,8 @@ public:
|
||||
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));
|
||||
}
|
||||
@@ -139,7 +148,7 @@ public:
|
||||
StringWriter w;
|
||||
w.put_u32l(CLIENT_CONFIG_MAGIC);
|
||||
w.put_u32l(this->specific_version);
|
||||
w.put_u64l(this->enabled_flags);
|
||||
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);
|
||||
@@ -263,6 +272,7 @@ public:
|
||||
}
|
||||
|
||||
void set_license(std::shared_ptr<License> l);
|
||||
void convert_license_to_temporary_if_nte();
|
||||
|
||||
void sync_config();
|
||||
|
||||
|
||||
@@ -1850,7 +1850,7 @@ bool CardSpecial::execute_effect(
|
||||
if (client_id == 0xFF) {
|
||||
return false;
|
||||
}
|
||||
auto ps = this->server()->player_states[client_id];
|
||||
auto ps = this->server()->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return false;
|
||||
}
|
||||
@@ -1993,7 +1993,7 @@ bool CardSpecial::execute_effect(
|
||||
if (client_id == 0xFF) {
|
||||
return false;
|
||||
}
|
||||
auto ps = this->server()->player_states[client_id];
|
||||
auto ps = this->server()->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return false;
|
||||
}
|
||||
@@ -2034,8 +2034,8 @@ bool CardSpecial::execute_effect(
|
||||
uint8_t attacker_client_id = client_id_for_card_ref(cond.card_ref);
|
||||
uint8_t target_client_id = client_id_for_card_ref(card->get_card_ref());
|
||||
if ((attacker_client_id != 0xFF) && (target_client_id != 0xFF)) {
|
||||
auto attacker_ps = this->server()->player_states[attacker_client_id];
|
||||
auto target_ps = this->server()->player_states[target_client_id];
|
||||
auto attacker_ps = this->server()->player_states.at(attacker_client_id);
|
||||
auto target_ps = this->server()->player_states.at(target_client_id);
|
||||
if (attacker_ps && target_ps) {
|
||||
uint8_t attacker_team_id = attacker_ps->get_team_id();
|
||||
uint8_t target_team_id = target_ps->get_team_id();
|
||||
@@ -2157,8 +2157,8 @@ bool CardSpecial::execute_effect(
|
||||
case ConditionType::GIVE_OR_TAKE_EXP:
|
||||
if (unknown_p7 & 4) {
|
||||
uint8_t client_id = client_id_for_card_ref(card->get_card_ref());
|
||||
if ((client_id != 0xFF) && this->server()->player_states[client_id]) {
|
||||
uint8_t team_id = this->server()->player_states[client_id]->get_team_id();
|
||||
if ((client_id != 0xFF) && this->server()->player_states.at(client_id)) {
|
||||
uint8_t team_id = this->server()->player_states.at(client_id)->get_team_id();
|
||||
int32_t existing_exp = this->server()->team_exp[team_id];
|
||||
if ((clamped_expr_value + existing_exp) < 0) {
|
||||
clamped_expr_value = -existing_exp;
|
||||
@@ -3672,7 +3672,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
|
||||
DiceRoll dice_roll;
|
||||
uint8_t client_id = client_id_for_card_ref(dice_cmd.effect.target_card_ref);
|
||||
auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states[client_id];
|
||||
auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states.at(client_id);
|
||||
|
||||
dice_roll.value = 1;
|
||||
if (set_card_ps) {
|
||||
|
||||
@@ -42,7 +42,7 @@ PlayerState::PlayerState(uint8_t client_id, shared_ptr<Server> server)
|
||||
void PlayerState::init() {
|
||||
auto s = this->server();
|
||||
|
||||
if (s->player_states[this->client_id].get() != this) {
|
||||
if (s->player_states.at(this->client_id).get() != this) {
|
||||
// Note: The original code handles this, but we don't. This appears not to
|
||||
// ever happen, so we didn't bother implementing it.
|
||||
throw logic_error("replacing a player state object is not permitted");
|
||||
@@ -1267,7 +1267,7 @@ bool PlayerState::set_card_from_hand(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto target_ps = s->player_states[assist_target_client_id];
|
||||
auto target_ps = s->player_states.at(assist_target_client_id);
|
||||
if (target_ps) {
|
||||
uint16_t prev_assist_card_ref = target_ps->card_refs[6];
|
||||
target_ps->discard_set_assist_card();
|
||||
@@ -1752,7 +1752,7 @@ void PlayerState::unknown_8023C174() {
|
||||
this->send_set_card_updates(0);
|
||||
}
|
||||
|
||||
void PlayerState::handle_homesick_assist_effect(shared_ptr<Card> card) {
|
||||
void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr<Card> card) {
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
int16_t get_assist_turns_remaining();
|
||||
bool set_action_cards_for_action_state(const ActionState& pa);
|
||||
void unknown_8023C174();
|
||||
void handle_homesick_assist_effect(std::shared_ptr<Card> card);
|
||||
void handle_homesick_assist_effect_from_bomb(std::shared_ptr<Card> card);
|
||||
void apply_main_die_assist_effects(uint8_t* die_value) const;
|
||||
void roll_main_dice();
|
||||
void unknown_8023C110();
|
||||
|
||||
+33
-21
@@ -418,7 +418,7 @@ shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||
if (client_id == 0xFF) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ps = this->player_states[client_id];
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -444,7 +444,7 @@ shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||
if (client_id == 0xFF) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ps = this->player_states[client_id];
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -464,10 +464,11 @@ shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||
uint16_t Server::card_id_for_card_ref(uint16_t card_ref) const {
|
||||
uint8_t client_id = client_id_for_card_ref(card_ref);
|
||||
if (client_id != 0xFF) {
|
||||
if (!this->player_states[client_id]) {
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return 0xFFFF;
|
||||
}
|
||||
auto deck = this->player_states[client_id]->get_deck();
|
||||
auto deck = ps->get_deck();
|
||||
if (deck) {
|
||||
return deck->card_id_for_card_ref(card_ref);
|
||||
}
|
||||
@@ -558,8 +559,19 @@ bool Server::check_for_battle_end() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
throw runtime_error("player does not exist");
|
||||
}
|
||||
ps->replace_assist_card_by_id(card_id);
|
||||
}
|
||||
|
||||
void Server::force_destroy_field_character(uint8_t client_id, size_t visible_index) {
|
||||
auto ps = this->player_states[client_id];
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
throw runtime_error("player does not exist");
|
||||
}
|
||||
|
||||
// TODO: Is it possible for there to be gaps in the set cards array? If not,
|
||||
// we could just do a direct array lookup here instead of this loop
|
||||
@@ -584,7 +596,7 @@ void Server::force_destroy_field_character(uint8_t client_id, size_t visible_ind
|
||||
}
|
||||
|
||||
void Server::force_battle_result(uint8_t specified_client_id, bool set_winner) {
|
||||
auto specified_ps = this->player_states[specified_client_id];
|
||||
auto specified_ps = this->player_states.at(specified_client_id);
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto ps = this->player_states[z];
|
||||
if (ps) {
|
||||
@@ -824,7 +836,7 @@ void Server::end_attack_list_for_client(uint8_t client_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = this->player_states[client_id];
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return;
|
||||
}
|
||||
@@ -1670,10 +1682,11 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data)
|
||||
error_code = -0x78;
|
||||
}
|
||||
if (error_code == 0) {
|
||||
if (!this->player_states[in_cmd.client_id]) {
|
||||
auto ps = this->player_states.at(in_cmd.client_id);
|
||||
if (!ps) {
|
||||
error_code = -0x72;
|
||||
} else {
|
||||
this->player_states[in_cmd.client_id]->do_mulligan();
|
||||
ps->do_mulligan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1709,18 +1722,17 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& d
|
||||
this->send(out_cmd_ack);
|
||||
|
||||
if (error_code == 0) {
|
||||
if (!this->player_states[in_cmd.client_id]) {
|
||||
auto ps = this->player_states.at(in_cmd.client_id);
|
||||
if (!ps) {
|
||||
error_code = -0x72;
|
||||
} else {
|
||||
this->clients_done_in_mulligan_phase[in_cmd.client_id] = true;
|
||||
auto ps = this->player_states[in_cmd.client_id];
|
||||
ps->assist_flags |= AssistFlag::READY_TO_END_PHASE;
|
||||
ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
|
||||
bool all_clients_ready = true;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (this->player_states[z] &&
|
||||
!this->clients_done_in_mulligan_phase[z]) {
|
||||
if (this->player_states[z] && !this->clients_done_in_mulligan_phase[z]) {
|
||||
all_clients_ready = false;
|
||||
break;
|
||||
}
|
||||
@@ -1781,7 +1793,7 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const strin
|
||||
}
|
||||
|
||||
if (error_code == 0) {
|
||||
auto ps = this->player_states[in_cmd.client_id];
|
||||
auto ps = this->player_states.at(in_cmd.client_id);
|
||||
if (!ps) {
|
||||
error_code = -0x72;
|
||||
} else if (!(ps->assist_flags & AssistFlag::IS_SKIPPING_TURN)) {
|
||||
@@ -1824,11 +1836,11 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
||||
}
|
||||
if (error_code == 0) {
|
||||
this->ruler_server->error_code1 = 0;
|
||||
if (!this->player_states[in_cmd.client_id]) {
|
||||
auto ps = this->player_states.at(in_cmd.client_id);
|
||||
if (!ps) {
|
||||
this->ruler_server->error_code1 = -0x72;
|
||||
} else {
|
||||
this->player_states[in_cmd.client_id]->set_card_from_hand(
|
||||
in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0);
|
||||
ps->set_card_from_hand(in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0);
|
||||
}
|
||||
} else {
|
||||
this->ruler_server->error_code1 = error_code;
|
||||
@@ -1861,12 +1873,12 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
||||
error_code = -0x78;
|
||||
}
|
||||
if (error_code == 0) {
|
||||
if (!this->player_states[in_cmd.client_id]) {
|
||||
auto ps = this->player_states.at(in_cmd.client_id);
|
||||
if (!ps) {
|
||||
this->ruler_server->error_code2 = -0x72;
|
||||
} else {
|
||||
this->ruler_server->error_code2 = 0;
|
||||
this->player_states[in_cmd.client_id]->move_card_to_location_by_card_index(
|
||||
in_cmd.set_index, in_cmd.loc);
|
||||
ps->move_card_to_location_by_card_index(in_cmd.set_index, in_cmd.loc);
|
||||
}
|
||||
} else {
|
||||
this->ruler_server->error_code2 = error_code;
|
||||
@@ -2655,7 +2667,7 @@ void Server::execute_bomb_assist_effect() {
|
||||
auto card = ps->get_set_card(set_index);
|
||||
if (card && !(card->card_flags & 2) &&
|
||||
((card->get_current_hp() == max_hp) || (card->get_current_hp() == min_hp))) {
|
||||
card->player_state()->handle_homesick_assist_effect(card);
|
||||
card->player_state()->handle_homesick_assist_effect_from_bomb(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ public:
|
||||
void send_commands_for_joining_spectator(Channel& ch) const;
|
||||
|
||||
void force_battle_result(uint8_t surrendered_client_id, bool set_winner);
|
||||
void force_replace_assist_card(uint8_t client_id, uint16_t card_id);
|
||||
void force_destroy_field_character(uint8_t client_id, size_t set_index);
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void send_debug_message_printf(const char* fmt, ...) const;
|
||||
@@ -288,7 +289,7 @@ public:
|
||||
uint32_t should_copy_prev_states_to_current_states;
|
||||
std::shared_ptr<CardSpecial> card_special;
|
||||
std::shared_ptr<StateFlags> state_flags;
|
||||
std::shared_ptr<PlayerState> player_states[4];
|
||||
std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
parray<uint32_t, 4> clients_done_in_mulligan_phase;
|
||||
uint32_t num_pending_attacks_with_cards;
|
||||
std::shared_ptr<Card> attack_cards[0x20];
|
||||
|
||||
+1
-1
@@ -298,8 +298,8 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
|
||||
}
|
||||
if (pb_num >= 4) {
|
||||
throw runtime_error("left photon blast number is too high");
|
||||
pb_nums |= (pb_num << 6);
|
||||
}
|
||||
pb_nums |= (pb_num << 6);
|
||||
flags |= 4;
|
||||
}
|
||||
}
|
||||
|
||||
+83
-68
@@ -254,7 +254,15 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||
uint8_t char_class,
|
||||
uint8_t section_id,
|
||||
bool version_has_rare_mags) {
|
||||
|
||||
static const unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||
{0x030000, 0}, // Monomate
|
||||
{0x030001, 1}, // Dimate
|
||||
@@ -269,14 +277,8 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
{0x030500, 10}, // Star Atomizer
|
||||
});
|
||||
|
||||
auto s = c->require_server_state();
|
||||
auto player = c->character();
|
||||
auto& fed_item = player->inventory.items[fed_item_index];
|
||||
auto& mag_item = player->inventory.items[mag_item_index];
|
||||
|
||||
size_t result_index = result_index_for_fed_item.at(fed_item.data.primary_identifier());
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
const auto& mag_def = item_parameter_table->get_mag(mag_item.data.data1[1]);
|
||||
size_t result_index = result_index_for_fed_item.at(fed_item.primary_identifier());
|
||||
const auto& mag_def = item_parameter_table->get_mag(mag_item.data1[1]);
|
||||
const auto& feed_result = item_parameter_table->get_mag_feed_result(mag_def.feed_table, result_index);
|
||||
|
||||
auto update_stat = +[](ItemData& data, size_t which, int8_t delta) -> void {
|
||||
@@ -293,17 +295,17 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
}
|
||||
};
|
||||
|
||||
update_stat(mag_item.data, 2, feed_result.def);
|
||||
update_stat(mag_item.data, 3, feed_result.pow);
|
||||
update_stat(mag_item.data, 4, feed_result.dex);
|
||||
update_stat(mag_item.data, 5, feed_result.mind);
|
||||
mag_item.data.data2[0] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data.data2[0]) + feed_result.synchro, 0, 120);
|
||||
mag_item.data.data2[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data.data2[1]) + feed_result.iq, 0, 200);
|
||||
update_stat(mag_item, 2, feed_result.def);
|
||||
update_stat(mag_item, 3, feed_result.pow);
|
||||
update_stat(mag_item, 4, feed_result.dex);
|
||||
update_stat(mag_item, 5, feed_result.mind);
|
||||
mag_item.data2[0] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[0]) + feed_result.synchro, 0, 120);
|
||||
mag_item.data2[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
|
||||
|
||||
uint8_t mag_level = mag_item.data.compute_mag_level();
|
||||
mag_item.data.data1[2] = mag_level;
|
||||
uint8_t evolution_number = s->mag_evolution_table->get_evolution_number(mag_item.data.data1[1]);
|
||||
uint8_t mag_number = mag_item.data.data1[1];
|
||||
uint8_t mag_level = mag_item.compute_mag_level();
|
||||
mag_item.data1[2] = mag_level;
|
||||
uint8_t evolution_number = mag_evolution_table->get_evolution_number(mag_item.data1[1]);
|
||||
uint8_t mag_number = mag_item.data1[1];
|
||||
|
||||
// Note: Sega really did just hardcode all these rules into the client. There
|
||||
// is no data file describing these evolutions, unfortunately.
|
||||
@@ -313,24 +315,24 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
|
||||
} else if (mag_level < 35) { // Level 10 evolution
|
||||
if (evolution_number < 1) {
|
||||
switch (player->disp.visual.char_class) {
|
||||
switch (char_class) {
|
||||
case 0: // HUmar
|
||||
case 1: // HUnewearl
|
||||
case 2: // HUcast
|
||||
case 9: // HUcaseal
|
||||
mag_item.data.data1[1] = 0x01; // Varuna
|
||||
mag_item.data1[1] = 0x01; // Varuna
|
||||
break;
|
||||
case 3: // RAmar
|
||||
case 11: // RAmarl
|
||||
case 4: // RAcast
|
||||
case 5: // RAcaseal
|
||||
mag_item.data.data1[1] = 0x0D; // Kalki
|
||||
mag_item.data1[1] = 0x0D; // Kalki
|
||||
break;
|
||||
case 10: // FOmar
|
||||
case 6: // FOmarl
|
||||
case 7: // FOnewm
|
||||
case 8: // FOnewearl
|
||||
mag_item.data.data1[1] = 0x19; // Vritra
|
||||
mag_item.data1[1] = 0x19; // Vritra
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid character class");
|
||||
@@ -339,30 +341,30 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
|
||||
} else if (mag_level < 50) { // Level 35 evolution
|
||||
if (evolution_number < 2) {
|
||||
uint16_t flags = mag_item.data.compute_mag_strength_flags();
|
||||
uint16_t flags = mag_item.compute_mag_strength_flags();
|
||||
if (mag_number == 0x0D) {
|
||||
if ((flags & 0x110) == 0) {
|
||||
mag_item.data.data1[1] = 0x02;
|
||||
mag_item.data1[1] = 0x02;
|
||||
} else if (flags & 8) {
|
||||
mag_item.data.data1[1] = 0x03;
|
||||
mag_item.data1[1] = 0x03;
|
||||
} else if (flags & 0x20) {
|
||||
mag_item.data.data1[1] = 0x0B;
|
||||
mag_item.data1[1] = 0x0B;
|
||||
}
|
||||
} else if (mag_number == 1) {
|
||||
if (flags & 0x108) {
|
||||
mag_item.data.data1[1] = 0x0E;
|
||||
mag_item.data1[1] = 0x0E;
|
||||
} else if (flags & 0x10) {
|
||||
mag_item.data.data1[1] = 0x0F;
|
||||
mag_item.data1[1] = 0x0F;
|
||||
} else if (flags & 0x20) {
|
||||
mag_item.data.data1[1] = 0x04;
|
||||
mag_item.data1[1] = 0x04;
|
||||
}
|
||||
} else if (mag_number == 0x19) {
|
||||
if (flags & 0x120) {
|
||||
mag_item.data.data1[1] = 0x1A;
|
||||
mag_item.data1[1] = 0x1A;
|
||||
} else if (flags & 8) {
|
||||
mag_item.data.data1[1] = 0x1B;
|
||||
mag_item.data1[1] = 0x1B;
|
||||
} else if (flags & 0x10) {
|
||||
mag_item.data.data1[1] = 0x14;
|
||||
mag_item.data1[1] = 0x14;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,18 +372,18 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
} else if ((mag_level % 5) == 0) { // Level 50 (and beyond) evolutions
|
||||
if (evolution_number < 4) {
|
||||
|
||||
if ((mag_level >= 100) && !is_v1_or_v2(c->version())) {
|
||||
uint8_t section_id_group = player->disp.visual.section_id % 3;
|
||||
uint16_t def = mag_item.data.data1w[2] / 100;
|
||||
uint16_t pow = mag_item.data.data1w[3] / 100;
|
||||
uint16_t dex = mag_item.data.data1w[4] / 100;
|
||||
uint16_t mind = mag_item.data.data1w[5] / 100;
|
||||
bool is_male = char_class_is_male(player->disp.visual.char_class);
|
||||
if ((mag_level >= 100) && version_has_rare_mags) {
|
||||
uint8_t section_id_group = section_id % 3;
|
||||
uint16_t def = mag_item.data1w[2] / 100;
|
||||
uint16_t pow = mag_item.data1w[3] / 100;
|
||||
uint16_t dex = mag_item.data1w[4] / 100;
|
||||
uint16_t mind = mag_item.data1w[5] / 100;
|
||||
bool is_male = char_class_is_male(char_class);
|
||||
size_t table_index = (is_male ? 0 : 1) + section_id_group * 2;
|
||||
|
||||
bool is_hunter = char_class_is_hunter(player->disp.visual.char_class);
|
||||
bool is_ranger = char_class_is_ranger(player->disp.visual.char_class);
|
||||
bool is_force = char_class_is_force(player->disp.visual.char_class);
|
||||
bool is_hunter = char_class_is_hunter(char_class);
|
||||
bool is_ranger = char_class_is_ranger(char_class);
|
||||
bool is_force = char_class_is_force(char_class);
|
||||
if (is_force) {
|
||||
table_index += 12;
|
||||
} else if (is_ranger) {
|
||||
@@ -404,77 +406,77 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
0x41, 0x3F, 0x41, 0x40, 0x41, 0x40, // Force
|
||||
};
|
||||
// clang-format on
|
||||
mag_item.data.data1[1] = result_table[table_index];
|
||||
mag_item.data1[1] = result_table[table_index];
|
||||
}
|
||||
}
|
||||
|
||||
// If a special evolution did not occur, do a normal level 50 evolution
|
||||
if (mag_number == mag_item.data.data1[1]) {
|
||||
uint16_t flags = mag_item.data.compute_mag_strength_flags();
|
||||
uint16_t def = mag_item.data.data1w[2] / 100;
|
||||
uint16_t pow = mag_item.data.data1w[3] / 100;
|
||||
uint16_t dex = mag_item.data.data1w[4] / 100;
|
||||
uint16_t mind = mag_item.data.data1w[5] / 100;
|
||||
if (mag_number == mag_item.data1[1]) {
|
||||
uint16_t flags = mag_item.compute_mag_strength_flags();
|
||||
uint16_t def = mag_item.data1w[2] / 100;
|
||||
uint16_t pow = mag_item.data1w[3] / 100;
|
||||
uint16_t dex = mag_item.data1w[4] / 100;
|
||||
uint16_t mind = mag_item.data1w[5] / 100;
|
||||
|
||||
bool is_hunter = char_class_is_hunter(player->disp.visual.char_class);
|
||||
bool is_ranger = char_class_is_ranger(player->disp.visual.char_class);
|
||||
bool is_force = char_class_is_force(player->disp.visual.char_class);
|
||||
bool is_hunter = char_class_is_hunter(char_class);
|
||||
bool is_ranger = char_class_is_ranger(char_class);
|
||||
bool is_force = char_class_is_force(char_class);
|
||||
if (is_hunter + is_ranger + is_force != 1) {
|
||||
throw logic_error("char class is not exactly one of the top-level classes");
|
||||
}
|
||||
|
||||
if (is_hunter) {
|
||||
if (flags & 0x108) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((dex < mind) ? 0x08 : 0x06)
|
||||
: ((dex < mind) ? 0x0C : 0x05);
|
||||
} else if (flags & 0x010) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((mind < pow) ? 0x12 : 0x10)
|
||||
: ((mind < pow) ? 0x17 : 0x13);
|
||||
} else if (flags & 0x020) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((pow < dex) ? 0x16 : 0x24)
|
||||
: ((pow < dex) ? 0x07 : 0x1E);
|
||||
}
|
||||
} else if (is_ranger) {
|
||||
if (flags & 0x110) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((mind < pow) ? 0x0A : 0x05)
|
||||
: ((mind < pow) ? 0x0C : 0x06);
|
||||
} else if (flags & 0x008) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((dex < mind) ? 0x0A : 0x26)
|
||||
: ((dex < mind) ? 0x0C : 0x06);
|
||||
} else if (flags & 0x020) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((pow < dex) ? 0x18 : 0x1E)
|
||||
: ((pow < dex) ? 0x08 : 0x05);
|
||||
}
|
||||
} else if (is_force) {
|
||||
if (flags & 0x120) {
|
||||
if (def < 45) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((pow < dex) ? 0x17 : 0x09)
|
||||
: ((pow < dex) ? 0x1E : 0x1C);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x24;
|
||||
mag_item.data1[1] = 0x24;
|
||||
}
|
||||
} else if (flags & 0x008) {
|
||||
if (def < 45) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((dex < mind) ? 0x1C : 0x20)
|
||||
: ((dex < mind) ? 0x1F : 0x25);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x23;
|
||||
mag_item.data1[1] = 0x23;
|
||||
}
|
||||
} else if (flags & 0x010) {
|
||||
if (def < 45) {
|
||||
mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
|
||||
mag_item.data1[1] = (section_id & 1)
|
||||
? ((mind < pow) ? 0x12 : 0x0C)
|
||||
: ((mind < pow) ? 0x15 : 0x11);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x24;
|
||||
mag_item.data1[1] = 0x24;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,8 +485,21 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
}
|
||||
|
||||
// If the mag has evolved, add its new photon blast
|
||||
if (mag_number != mag_item.data.data1[1]) {
|
||||
const auto& new_mag_def = item_parameter_table->get_mag(mag_item.data.data1[1]);
|
||||
mag_item.data.add_mag_photon_blast(new_mag_def.photon_blast);
|
||||
if (mag_number != mag_item.data1[1]) {
|
||||
const auto& new_mag_def = item_parameter_table->get_mag(mag_item.data1[1]);
|
||||
mag_item.add_mag_photon_blast(new_mag_def.photon_blast);
|
||||
}
|
||||
}
|
||||
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index) {
|
||||
auto s = c->require_server_state();
|
||||
auto player = c->character();
|
||||
apply_mag_feed_result(
|
||||
player->inventory.items[mag_item_index].data,
|
||||
player->inventory.items[fed_item_index].data,
|
||||
s->item_parameter_table_for_version(c->version()),
|
||||
s->mag_evolution_table,
|
||||
player->disp.visual.char_class,
|
||||
player->disp.visual.section_id,
|
||||
!is_v1_or_v2(c->version()));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,20 @@
|
||||
#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> random_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);
|
||||
|
||||
+60
-1
@@ -96,7 +96,7 @@ Version get_cli_version(Arguments& args, Version default_value = Version::UNKNOW
|
||||
return Version::BB_PATCH;
|
||||
} else if (args.get<bool>("dc-nte")) {
|
||||
return Version::DC_NTE;
|
||||
} else if (args.get<bool>("dc-proto")) {
|
||||
} else if (args.get<bool>("dc-proto") || args.get<bool>("dc-11-2000")) {
|
||||
return Version::DC_V1_11_2000_PROTOTYPE;
|
||||
} else if (args.get<bool>("dc-v1")) {
|
||||
return Version::DC_V1;
|
||||
@@ -1244,6 +1244,65 @@ Action a_encode_unicode_text_set(
|
||||
write_output_data(args, encoded.data(), encoded.size(), "prs");
|
||||
});
|
||||
|
||||
Action a_decode_word_select_set(
|
||||
"decode-word-select-set", "\
|
||||
decode-word-select-set [INPUT-FILENAME]\n\
|
||||
Decode a Word Select data file and print all the tokens. A version option\n\
|
||||
(e.g. --gc-ep3) is required. If the Word Select set is for PC or BB, the\n\
|
||||
--unitxt option is also required, and must point to a unitxt file in prs\n\
|
||||
or JSON format. For PC (V2), the unitxt_e.prs file should be used; for BB,\n\
|
||||
the unitxt_ws_e.prs file should be used.\n",
|
||||
+[](Arguments& args) {
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
string unitxt_filename = args.get<string>("unitxt");
|
||||
vector<string> unitxt_collection;
|
||||
if (!unitxt_filename.empty()) {
|
||||
vector<vector<string>> unitxt_data;
|
||||
if (ends_with(unitxt_filename, ".prs")) {
|
||||
unitxt_data = parse_unicode_text_set(load_file(unitxt_filename));
|
||||
} else if (ends_with(unitxt_filename, ".json")) {
|
||||
auto json = JSON::parse(load_file(unitxt_filename));
|
||||
for (const auto& coll_it : json.as_list()) {
|
||||
auto& coll = unitxt_data.emplace_back();
|
||||
for (const auto& str_it : coll_it->as_list()) {
|
||||
coll.emplace_back(str_it->as_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("unitxt filename must end in .prs or .json");
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
unitxt_collection = std::move(unitxt_data.at(0));
|
||||
} else {
|
||||
unitxt_collection = std::move(unitxt_data.at(35));
|
||||
}
|
||||
}
|
||||
|
||||
WordSelectSet ws(read_input_data(args), version, &unitxt_collection, args.get<bool>("japanese"));
|
||||
ws.print(stdout);
|
||||
});
|
||||
Action a_print_word_select_table(
|
||||
"print-word-select-table", "\
|
||||
print-word-select-table\n\
|
||||
Print the Word Select token translation table. If a version option is\n\
|
||||
given, prints the table sorted by token ID for that version. If no version\n\
|
||||
option is given, prints the token table sorted by canonical name.\n",
|
||||
+[](Arguments& args) {
|
||||
auto table = ServerState::load_word_select_table_from_system();
|
||||
Version v = Version::UNKNOWN;
|
||||
try {
|
||||
v = get_cli_version(args);
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
|
||||
if (v != Version::UNKNOWN) {
|
||||
table->print_index(stdout, v);
|
||||
} else {
|
||||
table->print(stdout);
|
||||
}
|
||||
});
|
||||
|
||||
Action a_cat_client(
|
||||
"cat-client", "\
|
||||
cat-client ADDR:PORT\n\
|
||||
|
||||
+13
-1
@@ -5,10 +5,12 @@
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh" // for parray
|
||||
#include "Compression.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class PSOEncryption {
|
||||
public:
|
||||
@@ -277,6 +279,16 @@ DecryptedPR2 decrypt_pr2_data(const std::string& data) {
|
||||
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>;
|
||||
|
||||
@@ -143,12 +143,17 @@ void PlayerVisualConfig::enforce_lobby_join_limits_for_version(Version v) {
|
||||
this->head = maxes->head ? (this->head % maxes->head) : 0;
|
||||
this->hair = maxes->hair ? (this->hair % maxes->hair) : 0;
|
||||
|
||||
if (this->name_color == 0) {
|
||||
this->name_color = 0xFFFFFFFF;
|
||||
}
|
||||
if (is_v1_or_v2(v)) {
|
||||
this->compute_name_color_checksum();
|
||||
} else {
|
||||
this->name_color_checksum = 0;
|
||||
}
|
||||
this->class_flags = class_flags_for_class(this->char_class);
|
||||
|
||||
if (is_v4(v) && (this->name.at(0) == '\t') && (this->name.at(1) == 'J' || this->name.at(1) == 'E')) {
|
||||
if (!is_v4(v) && (this->name.at(0) == '\t') && (this->name.at(1) == 'J' || this->name.at(1) == 'E')) {
|
||||
this->name.encode(this->name.decode().substr(2));
|
||||
}
|
||||
}
|
||||
@@ -910,24 +915,7 @@ BattleRules::MesetaMode enum_for_name<BattleRules::MesetaMode>(const char* name)
|
||||
}
|
||||
|
||||
static PlayerInventoryItem make_template_item(bool equipped, uint64_t first_data, uint64_t second_data) {
|
||||
PlayerInventoryItem ret(ItemData(), equipped);
|
||||
ret.data.data1[0] = first_data >> 56;
|
||||
ret.data.data1[1] = first_data >> 48;
|
||||
ret.data.data1[2] = first_data >> 40;
|
||||
ret.data.data1[3] = first_data >> 32;
|
||||
ret.data.data1[4] = first_data >> 24;
|
||||
ret.data.data1[5] = first_data >> 16;
|
||||
ret.data.data1[6] = first_data >> 8;
|
||||
ret.data.data1[7] = first_data >> 0;
|
||||
ret.data.data1[8] = second_data >> 56;
|
||||
ret.data.data1[9] = second_data >> 48;
|
||||
ret.data.data1[10] = second_data >> 40;
|
||||
ret.data.data1[11] = second_data >> 32;
|
||||
ret.data.data2[0] = second_data >> 24;
|
||||
ret.data.data2[1] = second_data >> 16;
|
||||
ret.data.data2[2] = second_data >> 8;
|
||||
ret.data.data2[3] = second_data >> 0;
|
||||
return ret;
|
||||
return PlayerInventoryItem(ItemData(first_data, second_data), equipped);
|
||||
}
|
||||
|
||||
static PlayerInventoryItem v2_item(bool equipped, uint64_t first_data, uint64_t second_data) {
|
||||
|
||||
+16
-7
@@ -257,6 +257,8 @@ static void send_main_menu(shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
void on_login_complete(shared_ptr<Client> c) {
|
||||
c->convert_license_to_temporary_if_nte();
|
||||
|
||||
// On BB, this function is called when the data server phase is done (and we
|
||||
// should send the ship select menu), so we don't need to check for it here.
|
||||
switch (c->server_behavior) {
|
||||
@@ -428,6 +430,7 @@ static void on_DB_V3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -472,6 +475,7 @@ static void on_88_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
||||
send_message_box(c, "Incorrect serial number");
|
||||
c->should_disconnect = true;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -515,6 +519,7 @@ static void on_8B_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
||||
send_message_box(c, "Incorrect serial number");
|
||||
c->should_disconnect = true;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -569,6 +574,7 @@ static void on_90_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
send_command(c, 0x90, 0x03);
|
||||
c->should_disconnect = true;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -626,6 +632,7 @@ static void on_93_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -743,6 +750,7 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
|
||||
} else if (is_v1_or_v2(c->version())) {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -805,6 +813,7 @@ static void on_9C(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = cmd.access_key.decode();
|
||||
@@ -942,16 +951,13 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
||||
return;
|
||||
|
||||
} catch (const LicenseIndex::missing_license& e) {
|
||||
// On GC, the client should have sent a different command containing the
|
||||
// password already, which should have created and added a license. So, if
|
||||
// no license exists at this point, disconnect the client even if
|
||||
// unregistered clients are allowed.
|
||||
if (is_v3(c->version()) || !s->allow_unregistered_users || (serial_number == 0)) {
|
||||
if (!s->allow_unregistered_users || (serial_number == 0)) {
|
||||
send_command(c, 0x04, 0x04);
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
|
||||
} else if (is_v1_or_v2(c->version())) {
|
||||
} else if (is_v1_or_v2(c->version()) || is_v3(c->version())) {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = serial_number;
|
||||
l->access_key = base_cmd->access_key.decode();
|
||||
@@ -1022,6 +1028,7 @@ static void on_9E_XB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
return;
|
||||
|
||||
} catch (const LicenseIndex::missing_license& e) {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = fnv1a32(xb_gamertag) & 0x7FFFFFFF;
|
||||
l->xb_gamertag = xb_gamertag;
|
||||
@@ -1113,6 +1120,7 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = fnv1a32(username) & 0x7FFFFFFF;
|
||||
l->bb_username = username;
|
||||
@@ -3034,6 +3042,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
if (c->config.specific_version == 0x33000000) {
|
||||
c->config.specific_version = 0x33534A54; // 3SJT
|
||||
}
|
||||
c->convert_license_to_temporary_if_nte();
|
||||
}
|
||||
cmd = &check_size_t<C_CharacterData_V3_61_98>(data, 0xFFFF);
|
||||
}
|
||||
@@ -5071,7 +5080,7 @@ static void on_04_P(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
|
||||
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
|
||||
auto l = s->license_index->create_license();
|
||||
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
|
||||
l->bb_username = cmd.username.decode();
|
||||
|
||||
+212
-147
@@ -26,6 +26,7 @@ struct SubcommandDefinition {
|
||||
enum Flag {
|
||||
ALWAYS_FORWARD_TO_WATCHERS = 0x01,
|
||||
ALLOW_FORWARD_TO_WATCHED_LOBBY = 0x02,
|
||||
USE_JOIN_COMMAND_QUEUE = 0x04,
|
||||
};
|
||||
uint8_t nte_subcommand;
|
||||
uint8_t proto_subcommand;
|
||||
@@ -37,7 +38,7 @@ using SDF = SubcommandDefinition::Flag;
|
||||
|
||||
extern const SubcommandDefinition subcommand_definitions[0x100];
|
||||
|
||||
static const SubcommandDefinition& def_for_nte_subcommand(uint8_t subcommand) {
|
||||
static const SubcommandDefinition* def_for_nte_subcommand(uint8_t subcommand) {
|
||||
static std::array<uint8_t, 0x100> nte_to_final_map;
|
||||
static bool nte_to_final_map_populated = false;
|
||||
if (!nte_to_final_map_populated) {
|
||||
@@ -55,10 +56,11 @@ static const SubcommandDefinition& def_for_nte_subcommand(uint8_t subcommand) {
|
||||
}
|
||||
nte_to_final_map_populated = true;
|
||||
}
|
||||
return subcommand_definitions[nte_to_final_map[subcommand]];
|
||||
uint8_t final_subcommand = nte_to_final_map[subcommand];
|
||||
return final_subcommand ? &subcommand_definitions[final_subcommand] : nullptr;
|
||||
}
|
||||
|
||||
static const SubcommandDefinition& def_for_proto_subcommand(uint8_t subcommand) {
|
||||
static const SubcommandDefinition* def_for_proto_subcommand(uint8_t subcommand) {
|
||||
static std::array<uint8_t, 0x100> proto_to_final_map;
|
||||
static bool proto_to_final_map_populated = false;
|
||||
if (!proto_to_final_map_populated) {
|
||||
@@ -76,7 +78,31 @@ static const SubcommandDefinition& def_for_proto_subcommand(uint8_t subcommand)
|
||||
}
|
||||
proto_to_final_map_populated = true;
|
||||
}
|
||||
return subcommand_definitions[proto_to_final_map[subcommand]];
|
||||
uint8_t final_subcommand = proto_to_final_map[subcommand];
|
||||
return final_subcommand ? &subcommand_definitions[final_subcommand] : nullptr;
|
||||
}
|
||||
|
||||
const SubcommandDefinition* def_for_subcommand(Version version, uint8_t subcommand) {
|
||||
if (version == Version::DC_NTE) {
|
||||
return def_for_nte_subcommand(subcommand);
|
||||
} else if (version == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
return def_for_proto_subcommand(subcommand);
|
||||
} else {
|
||||
return &subcommand_definitions[subcommand];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t translate_subcommand_number(Version to_version, Version from_version, uint8_t subcommand) {
|
||||
const auto* def = def_for_subcommand(from_version, subcommand);
|
||||
if (!def) {
|
||||
return 0x00;
|
||||
} else if (to_version == Version::DC_NTE) {
|
||||
return def->nte_subcommand;
|
||||
} else if (to_version == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
return def->proto_subcommand;
|
||||
} else {
|
||||
return def->final_subcommand;
|
||||
}
|
||||
}
|
||||
|
||||
// The functions in this file are called when a client sends a game command
|
||||
@@ -99,7 +125,7 @@ bool command_is_private(uint8_t command) {
|
||||
return (command == 0x62) || (command == 0x6D);
|
||||
}
|
||||
|
||||
static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
|
||||
// If the command is an Ep3-only command, make sure an Ep3 client sent it
|
||||
bool command_is_ep3 = (command & 0xF0) == 0xC0;
|
||||
if (command_is_ep3 && !is_ep3(c->version())) {
|
||||
@@ -113,14 +139,8 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
}
|
||||
|
||||
auto& header = check_size_t<G_UnusedHeader>(data, size, 0xFFFF);
|
||||
const SubcommandDefinition* def;
|
||||
if (c->version() == Version::DC_NTE) {
|
||||
def = &def_for_nte_subcommand(header.subcommand);
|
||||
} else if (c->version() == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
def = &def_for_proto_subcommand(header.subcommand);
|
||||
} else {
|
||||
def = &subcommand_definitions[header.subcommand];
|
||||
}
|
||||
const auto* def = def_for_subcommand(c->version(), header.subcommand);
|
||||
uint8_t def_flags = def ? def->flags : 0;
|
||||
|
||||
string nte_data;
|
||||
string proto_data;
|
||||
@@ -128,33 +148,51 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
Version c_version = c->version();
|
||||
auto send_to_client = [&](shared_ptr<Client> lc) -> void {
|
||||
Version lc_version = lc->version();
|
||||
const void* data_to_send = nullptr;
|
||||
size_t size_to_send = 0;
|
||||
if ((!is_pre_v1(lc_version) && !is_pre_v1(c_version)) || (lc_version == c_version)) {
|
||||
send_command(lc, command, flag, data, size);
|
||||
data_to_send = data;
|
||||
size_to_send = size;
|
||||
} else if (lc->version() == Version::DC_NTE) {
|
||||
if (def->nte_subcommand) {
|
||||
if (def && def->nte_subcommand) {
|
||||
if (nte_data.empty()) {
|
||||
nte_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
nte_data[0] = def->nte_subcommand;
|
||||
}
|
||||
send_command(lc, command, flag, nte_data);
|
||||
data_to_send = nte_data.data();
|
||||
size_to_send = nte_data.size();
|
||||
}
|
||||
} else if (lc->version() == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
if (def->proto_subcommand) {
|
||||
if (def && def->proto_subcommand) {
|
||||
if (proto_data.empty()) {
|
||||
proto_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
proto_data[0] = def->proto_subcommand;
|
||||
}
|
||||
send_command(lc, command, flag, proto_data);
|
||||
data_to_send = proto_data.data();
|
||||
size_to_send = proto_data.size();
|
||||
}
|
||||
} else {
|
||||
if (def->final_subcommand) {
|
||||
if (def && def->final_subcommand) {
|
||||
if (final_data.empty()) {
|
||||
final_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
final_data[0] = def->final_subcommand;
|
||||
}
|
||||
send_command(lc, command, flag, final_data);
|
||||
data_to_send = final_data.data();
|
||||
size_to_send = final_data.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (!data_to_send || !size_to_send) {
|
||||
lc->log.info("Command cannot be translated to client\'s version");
|
||||
} else if ((def_flags & SDF::USE_JOIN_COMMAND_QUEUE) && lc->game_join_command_queue) {
|
||||
lc->log.info("Client not ready to receive join commands; adding to queue");
|
||||
auto& cmd = lc->game_join_command_queue->emplace_back();
|
||||
cmd.command = command;
|
||||
cmd.flag = flag;
|
||||
cmd.data.assign(reinterpret_cast<const char*>(data_to_send), size_to_send);
|
||||
} else {
|
||||
send_command(lc, command, flag, data_to_send, size_to_send);
|
||||
}
|
||||
};
|
||||
|
||||
if (command_is_private(command)) {
|
||||
@@ -177,7 +215,7 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
}
|
||||
if ((command == 0xCB) &&
|
||||
l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) &&
|
||||
(def->flags & SDF::ALLOW_FORWARD_TO_WATCHED_LOBBY)) {
|
||||
(def_flags & SDF::ALLOW_FORWARD_TO_WATCHED_LOBBY)) {
|
||||
auto watched_lobby = l->watched_lobby.lock();
|
||||
if (watched_lobby) {
|
||||
for (auto& lc : watched_lobby->clients) {
|
||||
@@ -202,7 +240,7 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
// cause the battle setup menu to appear in the spectator room, which looks
|
||||
// weird and is generally undesirable.)
|
||||
if ((l->ep3_server && (l->ep3_server->setup_phase != Episode3::SetupPhase::REGISTRATION)) ||
|
||||
(def->flags & SDF::ALWAYS_FORWARD_TO_WATCHERS)) {
|
||||
(def_flags & SDF::ALWAYS_FORWARD_TO_WATCHERS)) {
|
||||
for (const auto& watcher_lobby : l->watcher_lobbies) {
|
||||
for (auto& target : watcher_lobby->clients) {
|
||||
if (target && is_ep3(target->version())) {
|
||||
@@ -221,6 +259,15 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
}
|
||||
}
|
||||
|
||||
static void forward_subcommand_m(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static void forward_subcommand_t(shared_ptr<Client> c, uint8_t command, uint8_t flag, const CmdT& cmd) {
|
||||
forward_subcommand(c, command, flag, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void on_invalid(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_UnusedHeader>(data, size, 0xFFFF);
|
||||
if ((c->version() == Version::DC_NTE) || c->version() == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
@@ -250,99 +297,98 @@ static void on_unimplemented(shared_ptr<Client> c, uint8_t command, uint8_t flag
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static void send_or_enqueue_joining_player_command(shared_ptr<Client> c, uint8_t command, uint8_t flag, const CmdT& data) {
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive join commands; adding to queue");
|
||||
auto& cmd = c->game_join_command_queue->emplace_back();
|
||||
cmd.command = command;
|
||||
cmd.flag = flag;
|
||||
cmd.data.assign(reinterpret_cast<const char*>(&data), sizeof(data));
|
||||
} else {
|
||||
send_command(c, command, flag, &data, sizeof(data));
|
||||
}
|
||||
}
|
||||
|
||||
static void send_or_enqueue_joining_player_command(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive join commands; adding to queue");
|
||||
auto& cmd = c->game_join_command_queue->emplace_back();
|
||||
cmd.command = command;
|
||||
cmd.flag = flag;
|
||||
cmd.data.assign(reinterpret_cast<const char*>(data), size);
|
||||
} else {
|
||||
send_command(c, command, flag, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_forward_check_game_loading(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
return;
|
||||
if (l->is_game() && l->any_client_loading()) {
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
if (command_is_private(command)) {
|
||||
auto target = l->clients.at(flag);
|
||||
if (target) {
|
||||
send_or_enqueue_joining_player_command(target, command, flag, data, size);
|
||||
}
|
||||
} else {
|
||||
for (auto lc : l->clients) {
|
||||
if (lc) {
|
||||
send_or_enqueue_joining_player_command(lc, command, flag, data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static shared_ptr<Client> get_sync_target(shared_ptr<Client> sender_c, uint8_t command, uint8_t flag, bool allow_if_not_loading) {
|
||||
if (!command_is_private(command)) {
|
||||
throw runtime_error("sync data sent via public command");
|
||||
}
|
||||
auto l = sender_c->require_lobby();
|
||||
if (l->is_game() && (allow_if_not_loading || l->any_client_loading()) && (flag < l->max_clients)) {
|
||||
return l->clients[flag];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void on_forward_sync_joining_player_state(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
auto target = get_sync_target(c, command, flag, false);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t subcommand;
|
||||
size_t decompressed_size;
|
||||
size_t compressed_size;
|
||||
const void* compressed_data;
|
||||
if (is_pre_v1(c->version())) {
|
||||
const auto& cmd = check_size_t<G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E>(data, size, 0xFFFF);
|
||||
size_t compressed_size = size - sizeof(cmd);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
string decompressed = bc0_decompress(reinterpret_cast<const char*>(data) + sizeof(cmd), compressed_size);
|
||||
c->log.info("Decompressed sync data (%zX -> %zX bytes; expected %" PRIX32 "):",
|
||||
compressed_size, decompressed.size(), cmd.decompressed_size.load());
|
||||
print_data(stderr, decompressed);
|
||||
}
|
||||
subcommand = cmd.header.basic_header.subcommand;
|
||||
decompressed_size = cmd.decompressed_size;
|
||||
compressed_size = size - sizeof(cmd);
|
||||
compressed_data = reinterpret_cast<const char*>(data) + sizeof(cmd);
|
||||
} else {
|
||||
const auto& cmd = check_size_t<G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E>(data, size, 0xFFFF);
|
||||
if (cmd.compressed_size > size - sizeof(cmd)) {
|
||||
throw runtime_error("compressed end offset is beyond end of command");
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
string decompressed = bc0_decompress(reinterpret_cast<const char*>(data) + sizeof(cmd), cmd.compressed_size);
|
||||
c->log.info("Decompressed sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):",
|
||||
cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load());
|
||||
print_data(stderr, decompressed);
|
||||
}
|
||||
subcommand = cmd.header.basic_header.subcommand;
|
||||
decompressed_size = cmd.decompressed_size;
|
||||
compressed_size = cmd.compressed_size;
|
||||
compressed_data = reinterpret_cast<const char*>(data) + sizeof(cmd);
|
||||
}
|
||||
|
||||
on_forward_check_game_loading(c, command, flag, data, size);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
string decompressed = bc0_decompress(compressed_data, compressed_size);
|
||||
c->log.info("Decompressed sync data (%zX -> %zX bytes; expected %zX):",
|
||||
compressed_size, decompressed.size(), decompressed_size);
|
||||
print_data(stderr, decompressed);
|
||||
}
|
||||
|
||||
if (is_pre_v1(c->version()) == is_pre_v1(target->version())) {
|
||||
on_forward_check_game_loading(c, command, flag, data, size);
|
||||
|
||||
} else if (is_pre_v1(target->version())) {
|
||||
StringWriter w;
|
||||
uint32_t cmd_size = ((compressed_size + sizeof(G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E)) + 3) & (~3);
|
||||
w.put(G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E{
|
||||
.header = {{subcommand, 0x00, 0x0000}, cmd_size},
|
||||
.decompressed_size = decompressed_size,
|
||||
});
|
||||
w.write(compressed_data, compressed_size);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
const string& data_to_send = w.str();
|
||||
forward_subcommand(c, command, flag, data_to_send.data(), data_to_send.size());
|
||||
|
||||
} else {
|
||||
StringWriter w;
|
||||
uint32_t cmd_size = ((compressed_size + sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E)) + 3) & (~3);
|
||||
w.put(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E{
|
||||
.header = {{subcommand, 0x00, 0x0000}, cmd_size},
|
||||
.decompressed_size = decompressed_size,
|
||||
.compressed_size = compressed_size,
|
||||
});
|
||||
w.write(compressed_data, compressed_size);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
const string& data_to_send = w.str();
|
||||
forward_subcommand(c, command, flag, data_to_send.data(), data_to_send.size());
|
||||
}
|
||||
}
|
||||
|
||||
static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm lazy and this should never happen (this command should always be
|
||||
// private to the joining player)
|
||||
if (!command_is_private(command)) {
|
||||
throw runtime_error("6x6D sent via public command");
|
||||
}
|
||||
if (flag >= l->max_clients) {
|
||||
return;
|
||||
}
|
||||
auto target = l->clients[flag];
|
||||
auto target = get_sync_target(c, command, flag, false);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const auto& l = c->require_lobby();
|
||||
|
||||
string decompressed;
|
||||
size_t compressed_size;
|
||||
@@ -403,16 +449,6 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
|
||||
send_game_item_state(target);
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
void clear_dc_protos_unused_6x70_item_fields(CmdT& cmd) {
|
||||
for (size_t z = 0; z < min<uint32_t>(cmd.num_items, 30); z++) {
|
||||
auto& item = cmd.items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Parsed6x70Data {
|
||||
public:
|
||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
||||
@@ -630,12 +666,22 @@ public:
|
||||
return (0xAE00000000000000 | this->guild_card_number);
|
||||
}
|
||||
|
||||
void clear_v1_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dc_protos_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
item.data.data2d = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,22 +769,18 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
|
||||
// In V1/V2 games, this command sometimes is sent after the new client has
|
||||
// finished loading, so we don't check l->any_client_loading() here.
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
auto target = get_sync_target(c, command, flag, true);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command_is_private(command)) {
|
||||
// I'm lazy and this should never happen (this command should always be
|
||||
// private to the joining player)
|
||||
throw runtime_error("6x70 sent via public command");
|
||||
}
|
||||
if (flag >= l->max_clients) {
|
||||
return;
|
||||
}
|
||||
auto target = l->clients[flag];
|
||||
if (!target) {
|
||||
return;
|
||||
// If the sender is the leader and is pre-V1, and the target is V1 or later,
|
||||
// we need to synthesize a 6x71 command to tell the target all state has been
|
||||
// sent. (If both are pre-V1, the target won't expect this command; if both
|
||||
// are V1 or later, the leader will send this command itself.)
|
||||
if (is_pre_v1(c->version()) && !is_pre_v1(target->version())) {
|
||||
static const be_uint32_t data = 0x71010000;
|
||||
send_command(target, 0x62, target->lobby_client_id, &data, sizeof(data));
|
||||
}
|
||||
|
||||
unique_ptr<Parsed6x70Data> parsed;
|
||||
@@ -764,6 +806,9 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(data, size),
|
||||
c->license->serial_number);
|
||||
if (c->version() == Version::DC_V1) {
|
||||
parsed->clear_v1_unused_item_fields();
|
||||
}
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
@@ -792,28 +837,28 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
|
||||
switch (target->version()) {
|
||||
case Version::DC_NTE:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_dc_nte());
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_nte());
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_dc_112000());
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_112000());
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_dc_pc());
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_pc());
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_gc());
|
||||
forward_subcommand_t(target, command, flag, parsed->as_gc());
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_xb());
|
||||
forward_subcommand_t(target, command, flag, parsed->as_xb());
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
send_or_enqueue_joining_player_command(target, command, flag, parsed->as_bb(target->language()));
|
||||
forward_subcommand_t(target, command, flag, parsed->as_bb(target->language()));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("6x70 command from unknown game version");
|
||||
@@ -1320,17 +1365,22 @@ void forward_subcommand_with_item_transcode_t(shared_ptr<Client> c, uint8_t comm
|
||||
|
||||
auto l = c->require_lobby();
|
||||
auto s = c->require_server_state();
|
||||
for (auto& other_c : l->clients) {
|
||||
if (!other_c || other_c == c) {
|
||||
for (auto& lc : l->clients) {
|
||||
if (!lc || lc == c) {
|
||||
continue;
|
||||
}
|
||||
if (c->version() != other_c->version()) {
|
||||
if (c->version() != lc->version()) {
|
||||
CmdT out_cmd = cmd;
|
||||
out_cmd.item_data.decode_for_version(c->version());
|
||||
out_cmd.item_data.encode_for_version(other_c->version(), s->item_parameter_table_for_version(other_c->version()));
|
||||
send_command_t(other_c, command, flag, out_cmd);
|
||||
out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
|
||||
if (out_cmd.header.subcommand) {
|
||||
out_cmd.item_data.decode_for_version(c->version());
|
||||
out_cmd.item_data.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
} else {
|
||||
lc->log.info("Subcommand cannot be translated to client\'s version");
|
||||
}
|
||||
} else {
|
||||
send_command_t(other_c, command, flag, cmd);
|
||||
send_command_t(lc, command, flag, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1520,12 +1570,19 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
|
||||
if (!lc || lc == c) {
|
||||
continue;
|
||||
}
|
||||
CmdT out_cmd = cmd;
|
||||
if (c->version() != lc->version()) {
|
||||
out_cmd.item.item.decode_for_version(c->version());
|
||||
out_cmd.item.item.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
|
||||
CmdT out_cmd = cmd;
|
||||
out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
|
||||
if (out_cmd.header.subcommand) {
|
||||
out_cmd.item.item.decode_for_version(c->version());
|
||||
out_cmd.item.item.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
} else {
|
||||
lc->log.info("Subcommand cannot be translated to client\'s version");
|
||||
}
|
||||
} else {
|
||||
send_command_t(lc, command, flag, cmd);
|
||||
}
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2694,7 +2751,22 @@ static void on_destroy_floor_item(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
for (size_t z = 0; z < l->clients.size(); z++) {
|
||||
auto lc = l->clients[z];
|
||||
if (lc && fi->visible_to_client(z)) {
|
||||
send_command_t(lc, command, flag, cmd);
|
||||
if (lc->version() != c->version()) {
|
||||
G_DestroyFloorItem_6x63 out_cmd = cmd;
|
||||
switch (lc->version()) {
|
||||
case Version::DC_NTE:
|
||||
out_cmd.header.subcommand = 0x55;
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
out_cmd.header.subcommand = 0x5C;
|
||||
break;
|
||||
default:
|
||||
out_cmd.header.subcommand = 0x63;
|
||||
}
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
} else {
|
||||
send_command_t(lc, command, flag, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3350,7 +3422,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x38 */ {0x33, 0x34, 0x38, nullptr},
|
||||
/* 6x39 */ {0x00, 0x36, 0x39, on_forward_check_game},
|
||||
/* 6x3A */ {0x00, 0x37, 0x3A, on_forward_check_game},
|
||||
/* 6x3B */ {0x00, 0x38, 0x3B, forward_subcommand},
|
||||
/* 6x3B */ {0x00, 0x38, 0x3B, forward_subcommand_m},
|
||||
/* 6x3C */ {0x34, 0x39, 0x3C, nullptr},
|
||||
/* 6x3D */ {0x00, 0x00, 0x3D, nullptr},
|
||||
/* 6x3E */ {0x00, 0x00, 0x3E, on_movement_with_floor<G_StopAtPosition_6x3E>},
|
||||
@@ -3373,7 +3445,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x4F */ {0x43, 0x49, 0x4F, on_forward_check_game_client},
|
||||
/* 6x50 */ {0x44, 0x4A, 0x50, on_forward_check_game_client},
|
||||
/* 6x51 */ {0x00, 0x00, 0x51, nullptr},
|
||||
/* 6x52 */ {0x46, 0x4C, 0x52, forward_subcommand},
|
||||
/* 6x52 */ {0x46, 0x4C, 0x52, forward_subcommand_m},
|
||||
/* 6x53 */ {0x47, 0x4D, 0x53, on_forward_check_game},
|
||||
/* 6x54 */ {0x48, 0x4E, 0x54, nullptr},
|
||||
/* 6x55 */ {0x49, 0x4F, 0x55, on_forward_check_game_client},
|
||||
@@ -3481,11 +3553,11 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xBB */ {0x00, 0x00, 0xBB, on_open_bank_bb_or_card_trade_counter_ep3},
|
||||
/* 6xBC */ {0x00, 0x00, 0xBC, on_forward_check_ep3_game},
|
||||
/* 6xBD */ {0x00, 0x00, 0xBD, on_ep3_private_word_select_bb_bank_action, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||
/* 6xBE */ {0x00, 0x00, 0xBE, forward_subcommand, SDF::ALWAYS_FORWARD_TO_WATCHERS | SDF::ALLOW_FORWARD_TO_WATCHED_LOBBY},
|
||||
/* 6xBE */ {0x00, 0x00, 0xBE, forward_subcommand_m, SDF::ALWAYS_FORWARD_TO_WATCHERS | SDF::ALLOW_FORWARD_TO_WATCHED_LOBBY},
|
||||
/* 6xBF */ {0x00, 0x00, 0xBF, on_forward_check_ep3_lobby},
|
||||
/* 6xC0 */ {0x00, 0x00, 0xC0, on_sell_item_at_shop_bb},
|
||||
/* 6xC1 */ {0x00, 0x00, 0xC1, forward_subcommand},
|
||||
/* 6xC2 */ {0x00, 0x00, 0xC2, forward_subcommand},
|
||||
/* 6xC1 */ {0x00, 0x00, 0xC1, forward_subcommand_m},
|
||||
/* 6xC2 */ {0x00, 0x00, 0xC2, forward_subcommand_m},
|
||||
/* 6xC3 */ {0x00, 0x00, 0xC3, on_drop_partial_stack_bb},
|
||||
/* 6xC4 */ {0x00, 0x00, 0xC4, on_sort_inventory_bb},
|
||||
/* 6xC5 */ {0x00, 0x00, 0xC5, on_medical_center_bb},
|
||||
@@ -3496,8 +3568,8 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xCA */ {0x00, 0x00, 0xCA, on_item_reward_request_bb},
|
||||
/* 6xCB */ {0x00, 0x00, 0xCB, on_transfer_item_via_mail_message_bb},
|
||||
/* 6xCC */ {0x00, 0x00, 0xCC, on_exchange_item_for_team_points_bb},
|
||||
/* 6xCD */ {0x00, 0x00, 0xCD, forward_subcommand},
|
||||
/* 6xCE */ {0x00, 0x00, 0xCE, forward_subcommand},
|
||||
/* 6xCD */ {0x00, 0x00, 0xCD, forward_subcommand_m},
|
||||
/* 6xCE */ {0x00, 0x00, 0xCE, forward_subcommand_m},
|
||||
/* 6xCF */ {0x00, 0x00, 0xCF, on_battle_restart_bb},
|
||||
/* 6xD0 */ {0x00, 0x00, 0xD0, on_battle_level_up_bb},
|
||||
/* 6xD1 */ {0x00, 0x00, 0xD1, on_request_challenge_grave_recovery_item_bb},
|
||||
@@ -3581,15 +3653,8 @@ void on_subcommand_multi(shared_ptr<Client> c, uint8_t command, uint8_t flag, st
|
||||
}
|
||||
void* cmd_data = data.data() + offset;
|
||||
|
||||
const SubcommandDefinition* def;
|
||||
if (c->version() == Version::DC_NTE) {
|
||||
def = &def_for_nte_subcommand(header->subcommand);
|
||||
} else if (c->version() == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
def = &def_for_proto_subcommand(header->subcommand);
|
||||
} else {
|
||||
def = &subcommand_definitions[header->subcommand];
|
||||
}
|
||||
if (def->handler) {
|
||||
const auto* def = def_for_subcommand(c->version(), header->subcommand);
|
||||
if (def && def->handler) {
|
||||
def->handler(c, command, flag, cmd_data, cmd_size);
|
||||
} else {
|
||||
on_unimplemented(c, command, flag, cmd_data, cmd_size);
|
||||
|
||||
@@ -389,6 +389,15 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x6D:
|
||||
if (!is_pre_v1(version)) {
|
||||
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
||||
if (header.subcommand == 0x70) {
|
||||
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(mask_data, mask_size, 0xFFFF);
|
||||
mask.base.visual.name_color_checksum = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+39
-30
@@ -282,7 +282,7 @@ void send_server_init(shared_ptr<Client> c, uint8_t flags) {
|
||||
}
|
||||
|
||||
void send_update_client_config(shared_ptr<Client> c, bool always_send) {
|
||||
if (always_send || (is_v3(c->version()) && (c->config != c->synced_config))) {
|
||||
if (always_send || (is_v3(c->version()) && (c->config.should_update_vs(c->synced_config)))) {
|
||||
switch (c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
@@ -1929,33 +1929,38 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
|
||||
send_player_records_t<RecordsT>(c, l, joining_client);
|
||||
}
|
||||
|
||||
uint8_t lobby_type;
|
||||
if (c->config.override_lobby_number != 0x80) {
|
||||
lobby_type = c->config.override_lobby_number;
|
||||
} else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) {
|
||||
lobby_type = is_ep3(c->version()) ? 15 : 0;
|
||||
uint8_t lobby_type, lobby_block;
|
||||
if (l->is_game()) {
|
||||
lobby_type = 0;
|
||||
lobby_block = 0;
|
||||
} else {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
|
||||
// Allow non-canonical lobby types on GC. They may work on other versions too,
|
||||
// but I haven't verified which values don't crash on each version.
|
||||
switch (c->version()) {
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
if ((lobby_type > 0x14) && (lobby_type < 0xE9)) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
if ((lobby_type > 0x11) && (lobby_type != 0x67) && (lobby_type != 0xD4) && (lobby_type < 0xFC)) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (lobby_type > 0x0E) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
if (c->config.override_lobby_number != 0x80) {
|
||||
lobby_type = c->config.override_lobby_number;
|
||||
} else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) {
|
||||
lobby_type = is_ep3(c->version()) ? 15 : 0;
|
||||
} else {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
// Allow non-canonical lobby types on GC. They may work on other versions too,
|
||||
// but I haven't verified which values don't crash on each version.
|
||||
switch (c->version()) {
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
if ((lobby_type > 0x14) && (lobby_type < 0xE9)) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
if ((lobby_type > 0x11) && (lobby_type != 0x67) && (lobby_type != 0xD4) && (lobby_type < 0xFC)) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (lobby_type > 0x0E) {
|
||||
lobby_type = l->block - 1;
|
||||
}
|
||||
}
|
||||
lobby_block = l->block;
|
||||
}
|
||||
|
||||
S_JoinLobby<LobbyFlags, LobbyDataT, DispDataT> cmd;
|
||||
@@ -1963,7 +1968,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
|
||||
cmd.lobby_flags.leader_id = l->leader_id;
|
||||
cmd.lobby_flags.disable_udp = 0x01;
|
||||
cmd.lobby_flags.lobby_number = lobby_type;
|
||||
cmd.lobby_flags.block_number = l->block;
|
||||
cmd.lobby_flags.block_number = lobby_block;
|
||||
cmd.lobby_flags.unknown_a1 = 0;
|
||||
cmd.lobby_flags.event = l->event;
|
||||
cmd.lobby_flags.unknown_a2 = 0;
|
||||
@@ -2101,8 +2106,12 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
populate_lobby_data_for_client(e.lobby_data, lc, c);
|
||||
e.inventory = lp->inventory;
|
||||
e.inventory.encode_for_client(c);
|
||||
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language);
|
||||
e.disp.enforce_lobby_join_limits_for_version(c->version());
|
||||
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
|
||||
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
|
||||
} else {
|
||||
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language);
|
||||
e.disp.enforce_lobby_join_limits_for_version(c->version());
|
||||
}
|
||||
}
|
||||
|
||||
send_command(c, command, used_entries, &cmd, cmd.size(used_entries));
|
||||
|
||||
+54
-1
@@ -14,6 +14,7 @@
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "Text.hh"
|
||||
#include "UnicodeTextSet.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -38,6 +39,7 @@ ServerState::ServerState(shared_ptr<struct event_base> base, const string& confi
|
||||
ip_stack_debug(false),
|
||||
allow_unregistered_users(false),
|
||||
allow_pc_nte(false),
|
||||
use_temp_licenses_for_prototypes(true),
|
||||
allow_dc_pc_games(false),
|
||||
allow_gc_xb_games(true),
|
||||
allowed_drop_modes_v1_v2_normal(0x1F),
|
||||
@@ -668,6 +670,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
this->ip_stack_debug = json.get_bool("IPStackDebug", this->ip_stack_debug);
|
||||
this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", this->allow_unregistered_users);
|
||||
this->allow_pc_nte = json.get_bool("AllowPCNTE", this->allow_pc_nte);
|
||||
this->use_temp_licenses_for_prototypes = json.get_bool("UseTemporaryLicensesForPrototypes", this->use_temp_licenses_for_prototypes);
|
||||
this->allowed_drop_modes_v1_v2_normal = json.get_int("AllowedDropModesV1V2Normal", this->allowed_drop_modes_v1_v2_normal);
|
||||
this->allowed_drop_modes_v1_v2_battle = json.get_int("AllowedDropModesV1V2Battle", this->allowed_drop_modes_v1_v2_battle);
|
||||
this->allowed_drop_modes_v1_v2_challenge = json.get_int("AllowedDropModesV1V2Challenge", this->allowed_drop_modes_v1_v2_challenge);
|
||||
@@ -1106,9 +1109,59 @@ void ServerState::load_level_table() {
|
||||
this->level_table = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
|
||||
}
|
||||
|
||||
shared_ptr<WordSelectTable> ServerState::load_word_select_table_from_system() {
|
||||
vector<vector<string>> name_alias_lists;
|
||||
auto json = JSON::parse(load_file("system/word-select/name-alias-lists.json"));
|
||||
for (const auto& coll_it : json.as_list()) {
|
||||
auto& coll = name_alias_lists.emplace_back();
|
||||
for (const auto& str_it : coll_it->as_list()) {
|
||||
coll.emplace_back(str_it->as_string());
|
||||
}
|
||||
}
|
||||
|
||||
config_log.info("(Word select) Loading pc_unitxt.prs");
|
||||
vector<vector<string>> pc_unitxt_data = parse_unicode_text_set(load_file("system/word-select/pc_unitxt.prs"));
|
||||
config_log.info("(Word select) Loading bb_unitxt_ws.prs");
|
||||
vector<vector<string>> bb_unitxt_data = parse_unicode_text_set(load_file("system/word-select/bb_unitxt_ws.prs"));
|
||||
vector<string> pc_unitxt_collection = std::move(pc_unitxt_data.at(35));
|
||||
vector<string> bb_unitxt_collection = std::move(bb_unitxt_data.at(0));
|
||||
|
||||
config_log.info("(Word select) Loading DC_NTE data");
|
||||
WordSelectSet dc_nte_ws(load_file("system/word-select/dc_nte_ws_data.bin"), Version::DC_NTE, nullptr, true);
|
||||
config_log.info("(Word select) Loading DC_V1_11_2000_PROTOTYPE data");
|
||||
WordSelectSet dc_112000_ws(load_file("system/word-select/dc_112000_ws_data.bin"), Version::DC_V1_11_2000_PROTOTYPE, nullptr, false);
|
||||
config_log.info("(Word select) Loading DC_V1 data");
|
||||
WordSelectSet dc_v1_ws(load_file("system/word-select/dcv1_ws_data.bin"), Version::DC_V1, nullptr, false);
|
||||
config_log.info("(Word select) Loading DC_V2 data");
|
||||
WordSelectSet dc_v2_ws(load_file("system/word-select/dcv2_ws_data.bin"), Version::DC_V2, nullptr, false);
|
||||
config_log.info("(Word select) Loading PC_NTE data");
|
||||
WordSelectSet pc_nte_ws(load_file("system/word-select/pc_nte_ws_data.bin"), Version::PC_NTE, &pc_unitxt_collection, false);
|
||||
config_log.info("(Word select) Loading PC_V2 data");
|
||||
WordSelectSet pc_v2_ws(load_file("system/word-select/pc_ws_data.bin"), Version::PC_V2, &pc_unitxt_collection, false);
|
||||
config_log.info("(Word select) Loading GC_NTE data");
|
||||
WordSelectSet gc_nte_ws(load_file("system/word-select/gc_nte_ws_data.bin"), Version::GC_NTE, nullptr, false);
|
||||
config_log.info("(Word select) Loading GC_V3 data");
|
||||
WordSelectSet gc_v3_ws(load_file("system/word-select/gc_ws_data.bin"), Version::GC_V3, nullptr, false);
|
||||
config_log.info("(Word select) Loading GC_EP3_NTE data");
|
||||
WordSelectSet gc_ep3_nte_ws(load_file("system/word-select/gc_ep3_nte_ws_data.bin"), Version::GC_EP3_NTE, nullptr, false);
|
||||
config_log.info("(Word select) Loading GC_EP3 data");
|
||||
WordSelectSet gc_ep3_ws(load_file("system/word-select/gc_ep3_ws_data.bin"), Version::GC_EP3, nullptr, false);
|
||||
config_log.info("(Word select) Loading XB_V3 data");
|
||||
WordSelectSet xb_v3_ws(load_file("system/word-select/xb_ws_data.bin"), Version::XB_V3, nullptr, false);
|
||||
config_log.info("(Word select) Loading BB_V4 data");
|
||||
WordSelectSet bb_v4_ws(load_file("system/word-select/bb_ws_data.bin"), Version::BB_V4, &bb_unitxt_collection, false);
|
||||
|
||||
config_log.info("(Word select) Generating table");
|
||||
return make_shared<WordSelectTable>(
|
||||
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,
|
||||
name_alias_lists);
|
||||
}
|
||||
|
||||
void ServerState::load_word_select_table() {
|
||||
config_log.info("Loading Word Select table");
|
||||
this->word_select_table = make_shared<WordSelectTable>(JSON::parse(load_file("system/word-select-table.json")));
|
||||
this->word_select_table = this->load_word_select_table_from_system();
|
||||
}
|
||||
|
||||
void ServerState::load_item_name_index() {
|
||||
|
||||
@@ -75,6 +75,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
bool ip_stack_debug;
|
||||
bool allow_unregistered_users;
|
||||
bool allow_pc_nte;
|
||||
bool use_temp_licenses_for_prototypes;
|
||||
bool allow_dc_pc_games;
|
||||
bool allow_gc_xb_games;
|
||||
uint8_t allowed_drop_modes_v1_v2_normal;
|
||||
@@ -284,6 +285,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_level_table();
|
||||
void load_item_name_index();
|
||||
void load_item_tables();
|
||||
static std::shared_ptr<WordSelectTable> load_word_select_table_from_system();
|
||||
void load_word_select_table();
|
||||
void load_ep3_data();
|
||||
void resolve_ep3_card_names();
|
||||
|
||||
@@ -29,6 +29,14 @@ 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);
|
||||
}
|
||||
|
||||
+262
-72
@@ -5,68 +5,276 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void index_add(vector<size_t>& index, uint16_t position, size_t value) {
|
||||
if (position != 0xFFFF) {
|
||||
if (index.size() <= position) {
|
||||
index.resize(position + 1);
|
||||
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 NonWindowsRoot {
|
||||
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;
|
||||
} __attribute__((packed));
|
||||
|
||||
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;
|
||||
} __attribute__((packed));
|
||||
|
||||
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;
|
||||
} __attribute__((packed));
|
||||
|
||||
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<NonWindowsRoot<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_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
|
||||
}
|
||||
index[position] = value;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
WordSelectTable::WordSelectTable(const JSON& json) {
|
||||
this->tokens.reserve(json.size());
|
||||
for (const auto& item : json.as_list()) {
|
||||
JSON dc_value_json = item->at(0);
|
||||
JSON pc_value_json = item->at(1);
|
||||
JSON gc_value_json = item->at(2);
|
||||
JSON ep3_value_json = item->at(3);
|
||||
JSON bb_value_json = item->at(4);
|
||||
uint16_t dc_value = dc_value_json.is_null() ? 0xFFFF : dc_value_json.as_int();
|
||||
uint16_t pc_value = pc_value_json.is_null() ? 0xFFFF : pc_value_json.as_int();
|
||||
uint16_t gc_value = gc_value_json.is_null() ? 0xFFFF : gc_value_json.as_int();
|
||||
uint16_t ep3_value = ep3_value_json.is_null() ? 0xFFFF : ep3_value_json.as_int();
|
||||
uint16_t bb_value = bb_value_json.is_null() ? 0xFFFF : bb_value_json.as_int();
|
||||
this->tokens.emplace_back(Token{
|
||||
.dc_value = dc_value,
|
||||
.pc_value = pc_value,
|
||||
.gc_value = gc_value,
|
||||
.ep3_value = ep3_value,
|
||||
.bb_value = bb_value,
|
||||
});
|
||||
index_add(this->dc_index, dc_value, this->tokens.size() - 1);
|
||||
index_add(this->pc_index, pc_value, this->tokens.size() - 1);
|
||||
index_add(this->gc_index, gc_value, this->tokens.size() - 1);
|
||||
index_add(this->ep3_index, ep3_value, this->tokens.size() - 1);
|
||||
index_add(this->bb_index, bb_value, this->tokens.size() - 1);
|
||||
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);
|
||||
}
|
||||
|
||||
uint16_t WordSelectTable::Token::value_for_version(Version version) const {
|
||||
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
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:
|
||||
return this->dc_value;
|
||||
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:
|
||||
return this->pc_value;
|
||||
this->parse_windows_t<PCV2Root, 0x645>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
|
||||
// but this may not be true
|
||||
return this->gc_value;
|
||||
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:
|
||||
return this->ep3_value;
|
||||
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:
|
||||
return this->bb_value;
|
||||
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid word select version");
|
||||
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);
|
||||
}
|
||||
|
||||
array<const WordSelectSet*, 12> 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();
|
||||
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();
|
||||
fprintf(stream, "%s\n", serialized.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,42 +282,18 @@ WordSelectMessage WordSelectTable::translate(
|
||||
const WordSelectMessage& msg,
|
||||
Version from_version,
|
||||
Version to_version) const {
|
||||
const std::vector<size_t>* index;
|
||||
switch (from_version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
index = &this->dc_index;
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
index = &this->pc_index;
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
|
||||
// but this may not be true
|
||||
index = &this->gc_index;
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
index = &this->ep3_index;
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
index = &this->bb_index;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid word select version");
|
||||
}
|
||||
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 {
|
||||
ret.tokens[z] = this->tokens.at(index->at(msg.tokens[z])).value_for_version(to_version);
|
||||
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()));
|
||||
}
|
||||
@@ -121,3 +305,9 @@ WordSelectMessage WordSelectTable::translate(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+87
-14
@@ -2,16 +2,84 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#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:
|
||||
explicit WordSelectTable(const JSON& json);
|
||||
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,
|
||||
@@ -20,18 +88,23 @@ public:
|
||||
|
||||
private:
|
||||
struct Token {
|
||||
uint16_t dc_value;
|
||||
uint16_t pc_value;
|
||||
uint16_t gc_value;
|
||||
uint16_t ep3_value;
|
||||
uint16_t bb_value;
|
||||
std::array<uint16_t, 12> values_by_version;
|
||||
std::string canonical_name;
|
||||
|
||||
uint16_t value_for_version(Version version) const;
|
||||
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::vector<size_t> dc_index;
|
||||
std::vector<size_t> pc_index;
|
||||
std::vector<size_t> gc_index;
|
||||
std::vector<size_t> ep3_index;
|
||||
std::vector<size_t> bb_index;
|
||||
std::vector<Token> tokens;
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,6 +241,12 @@
|
||||
// number is a hash of the username.
|
||||
"AllowUnregisteredUsers": true,
|
||||
|
||||
// If this option is enabled and AllowUnregisteredUsers is enabled, the server
|
||||
// will use temporary licenses for the prototype versions (DC NTE, DC 11/2000,
|
||||
// GC NTE, and Ep3 NTE) instead of permanent licenses. In this case, you can
|
||||
// still manually create permanent licenses for NTE players.
|
||||
"UseTemporaryLicensesForPrototypes": true,
|
||||
|
||||
// If this option is enabled, PC NTE users will be allowed to connect. This is
|
||||
// the only version of the game that does not have any way to identify the
|
||||
// player (no serial number, username, etc.), so PC NTE players receive random
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,268 @@
|
||||
[
|
||||
[",", "\u00E3\u0080\u0081"],
|
||||
["!", "\u00EF\u00BC\u0081"],
|
||||
["?", "\u00EF\u00BC\u009F"],
|
||||
[".", "\u00E3\u0080\u0082"],
|
||||
["affected by \"SHOCK\"", "taking effect of \"SHOCK\"", "taking effect of \"SHORT\""],
|
||||
["alone", "solitude"],
|
||||
["Android,\nlay down a trap for me.", "Android, \nlay down a trap for me."],
|
||||
["are you going to meet", "are you meeting with"],
|
||||
["are you headed for", "are you headed"],
|
||||
["Are you ready to\ngo on an adventure?", "Are you ready to\ngo adventure?", "Are you ready to go adventure?"],
|
||||
["are you venturing", "are you traveling"],
|
||||
["AREA:MINE", "AREA:MACHINE MINE"],
|
||||
["AREA:RUINS", "AREA:ANCIENT"],
|
||||
["ARLAN", "ALRAN"],
|
||||
["ARMS", "arms"],
|
||||
["art appreciation", "art"],
|
||||
["bad fortune", "bad luck"],
|
||||
["body boarding", "body boad"],
|
||||
["bonsai trees", "dwarf tree"],
|
||||
["CADUCEUS", "CADUSEUS"],
|
||||
["camping", "camp"],
|
||||
["can equip with", "can equip"],
|
||||
["Can I join in?\nI'm a beginner.", "Can I join in? I'm a beginner."],
|
||||
["Can you come again\nwhen you have\nmore experience?", "Can you come again when you have more experience?"],
|
||||
["Can you come over\nto see me?", "Can you come over to see me?"],
|
||||
["Can you play some\nmore?", "Can you play some more?"],
|
||||
["Can you wait for me\nfor just a little while\nlonger?", "Can you wait for me for just a little while longer?"],
|
||||
["can't equip with", "can't equip"],
|
||||
["canoeing", "canoe"],
|
||||
["CHAINSAWD", "CHAINSWORD"],
|
||||
["CHRISTMAS PRESENT", "CHIRISTMAS PRESENT"],
|
||||
["collecting things", "collection"],
|
||||
["commanding the PC", "using computers"],
|
||||
["CUSTOM BARRIER ver.00", "CUSTOM BARRIER ver.OO"],
|
||||
["CUSTOM RAY ver.00", "CUSTOM RAY", "CUSTOM RAY ver.OO"],
|
||||
["DAL RA LIE", "DAL LA LIE"],
|
||||
["DARK BELRA", "DARTH BELRA"],
|
||||
["Did we come to this\nplace before?", "Did we come to this place before?"],
|
||||
["Did you restore\nyour HP?", "Did you restore your HP?"],
|
||||
["Do you want to chat\nwith me?", "Do you want to chat with me?"],
|
||||
["Do you want\nanything from me?", "Do you want anything\nfrom me?", "Do you want anything from me?"],
|
||||
["don't stay", "don't stay in"],
|
||||
["DRAW", "SUCK"],
|
||||
["driving a car", "drive a car"],
|
||||
["Drop a Mag for me!", "Drop a Mag!"],
|
||||
["Drop a weapon for me!", "Drop a weapon!"],
|
||||
["Drop some armor for me!", "Drop some armor!"],
|
||||
["DULGER", "DURGA"],
|
||||
["equiped with", "equipped"],
|
||||
["EVADE MATERIAL", "AVOID MATERIAL"],
|
||||
["film appreciation", "animated film"],
|
||||
["FIXED:INTRODUCTION", "FIXED:SHORT MAIL"],
|
||||
["Forget about them.\nLet's move.", "Forget about them. Let's move."],
|
||||
["Give me some armor.", "Give me some armors."],
|
||||
["Give me your GUILD CARD.", "Give me your Guild Card."],
|
||||
["Go into the\nTELEPORTER.", "Go into the\nTRANSPORTER.", "Go into the TRANSPORTER."],
|
||||
["go move on toward", "go move on towards"],
|
||||
["going to the movies", "appreciateing movie"],
|
||||
["Good to see you.\nGot time to talk?", "Good to see you. Got time to talk?"],
|
||||
["H&S25 JUSTICE", "HS25 JUSTICE"],
|
||||
["Hang on a second.\nI'll be there.", "Hang on a second. I'll be there."],
|
||||
["Hello!? Are you\nwith me?", "Hello!? Are you with me?"],
|
||||
["Help me out! I can't\nrun away!", "Help me out! I can't run away!"],
|
||||
["Help me!\nI don't want to die!", "I don't want to die!"],
|
||||
["Help, I have almost\nno HP!", "Help, I have almost no HP!"],
|
||||
["Here's a MONOFLUID.", "Here's a MONOFLUIO."],
|
||||
["Here's a MONOMATE.", "Here's a MONOMAIT."],
|
||||
["Here's my GUILD CARD.", "Here's my Guild Card."],
|
||||
["Hey, what happened?", "Hey, what happened??"],
|
||||
["Hi. Can you go\nadventuring with me?", "Hi. Can you go adventuring with me?"],
|
||||
["How about going\nto the east?", "How about going to the east?"],
|
||||
["How about going\nto the north?", "How about going to the north?"],
|
||||
["How about going\nto the south?", "How about going to the south?"],
|
||||
["How about going\nto the west?", "How about going to the west?"],
|
||||
["How about the day\nafter tomorrow?", "How about the day after tomorrow?"],
|
||||
["I also want to go\nadventure.", "I also want to go adventure."],
|
||||
["I can't tell right\nnow.", "I can't tell right now."],
|
||||
["I don't understand\nwhat you mean.", "I don't understand\nthe meaning.", "I don't understand the meaning."],
|
||||
["I doubt you've heard\nof my country.\nIt's very small.", "I doubt you've heard of my country. It's very small."],
|
||||
["I have to leave\nnow.", "I have to leave now."],
|
||||
["I prefer Chatting\nrather than going\non an adventure.", "I prefer Chatting rather than going on an adventure."],
|
||||
["I prefer going on\na free adventure,\nnot a QUEST.", "I prefer going on\na free adventure,\nnot in a QUEST.", "I prefer going on a free adventure, not in a QUEST."],
|
||||
["I returned to the\nprevious area.", "I returned to the\nprevious room.", "I returned to the previous room."],
|
||||
["I think it's about time\nto stop playing...", "I think it's about time to stop playing..."],
|
||||
["I think we should\ngo together.", "I think we should go together."],
|
||||
["I want to continue\nthe adventure.", "I want to continue the adventure."],
|
||||
["I want to go on\na free adventure.", "I want to go on a free adventure."],
|
||||
["I'll cast ANTI on\nyou.", "I'll cast ANTI on you."],
|
||||
["I'll cast DEBAND on\nyou.", "I'll cast DEBAND on you."],
|
||||
["I'll cast RESTA on\nyou.", "I'll cast RESTA on you."],
|
||||
["I'll cast RYUKER.", "I'll cast REUKER on\nyou.", "I'll cast REUKER on you."],
|
||||
["I'll cast SHIFTA on\nyou.", "I'll cast SHIFTA on you."],
|
||||
["I'll generate the\nPHOTON BLAST!", "I'll generate the PHOTON BLAST!"],
|
||||
["I'll get closer to\nyou.", "I'll get closer to you."],
|
||||
["I'll lead the enemies\nover here. Somebody\nback me up.", "I'll lead the enemies\nover here. Somebody\nsupport me.", "I'll lead the enemies over here. Somebody support me."],
|
||||
["I'll take care of\nthis!", "I'll take care of this!"],
|
||||
["I'll turn on the\nswitch.", "I'll turn on the switch."],
|
||||
["I'm coming to save you,\nbut it will take time.", "I'm coming to save you, but it will take time."],
|
||||
["I'm coming to save\nyou. Hold on.", "I'm coming to save you. Hold on."],
|
||||
["I'm coming to\nsee you.", "I'm coiming to see you."],
|
||||
["I'm going ahead of\nyou.", "I'm going ahead of you."],
|
||||
["I'm going in.\nBack me up, OK?", "I'm going in. Back me up, OK?"],
|
||||
["I'm going to be\nkilled.", "I'm going to be killed."],
|
||||
["I'm not ready.\nHold on.", "I'm not ready. Hold on."],
|
||||
["is exiting the game", "is quiting the game"],
|
||||
["is leading", "is heading"],
|
||||
["is returning", "returning"],
|
||||
["Is this the right\ndirection?", "Is this the right direction?"],
|
||||
["It's a waste of time.\nCome later.", "It's a waste of time. Come later."],
|
||||
["It's OK! We can do\nit!", "It's OK! We can do it!"],
|
||||
["Just joking.", "Jusy joking."],
|
||||
["JUSTY-23ST", "JUSTY'23ST"],
|
||||
["L&K14 COMBAT", "LK14 COMBAT"],
|
||||
["Let me take care\nof them.", "Let me take care of them."],
|
||||
["Let's exchange\nGUILD CARDS.", "Let's exchange\nGUILD-CARDs", "Let's exchange COM-CARDs"],
|
||||
["Let's get out of\nhere.", "Let's get out of here."],
|
||||
["Let's go back to\nthe previous room.", "Let's go back to the previous room."],
|
||||
["Let's move to the\nnext area.", "Let's move to the next area."],
|
||||
["Let's split up into\ntwo groups.", "Let's split up into two groups."],
|
||||
["Let's trade\nsomething.", "Let's trade something."],
|
||||
["listening to music", "appreciateing music"],
|
||||
["Long time no see.", "Long time no see you."],
|
||||
["looked older", "looking older"],
|
||||
["M&A60 VISE", "MA60 VISE"],
|
||||
["MADHU", "MADU"],
|
||||
["making art", "art"],
|
||||
["making art", "pictorial art"],
|
||||
["MASTER/ABILITY", "MASTER/ABILYTY"],
|
||||
["MELQUEEK", "MELQEEK"],
|
||||
["MERLAN", "MELRAN"],
|
||||
["Mexico", "United Mexican States"],
|
||||
["mountain biking", "riding a mountain bike"],
|
||||
["must stay", "must stay in"],
|
||||
["Nice playing with\nyou.", "Nice playing with you."],
|
||||
["No problem. I know\nthe route.", "No problem. I know the route."],
|
||||
["No way! I'll help\nyou!", "No way! I'll help you!"],
|
||||
["No, I want to play\nsome more!", "No, I want to play some more!"],
|
||||
["No, I'll go there.\nWhere are you?", "No, I'll go there. Where are you?"],
|
||||
["No, I've played this\ngame for some time.", "No, I've played this game for some time."],
|
||||
["OK, I'll catch up\nwith you later.", "OK, I'll catch up with you later."],
|
||||
["OK, I'll wait\nfor you here.", "OK, I'll wait for you here."],
|
||||
["OK, let me create\nit.", "OK, let me create it."],
|
||||
["OK, what do you\nwant?", "OK, what do you want?"],
|
||||
["OK, where shall\nwe go?", "OK, where shall we go?"],
|
||||
["OK, where shall\nwe meet?", "OK, where shall we meet?"],
|
||||
["OK!", "OK"],
|
||||
["OK.\nBut wait a minute.", "OK. I'll be right\nback."],
|
||||
["Okay,\nI'm going to throw the switch.\nEverybody get ready to run like mad!", "Okay,I'm going to throw the switch.\nEverybody get ready to run like mad!"],
|
||||
["P-ARM'S ARMS", "P-ARM's ARMs"],
|
||||
["PIONEER 2", "CITY"],
|
||||
["playing American football", "playing american football"],
|
||||
["playing basketball", "pleying basketball"],
|
||||
["playing darts", "playing dart"],
|
||||
["playing ice hockey", "playing Ice hockey"],
|
||||
["playing online games", "ON-LINE GAME"],
|
||||
["playing piano", "playing a piano"],
|
||||
["playing video games", "playing a video game"],
|
||||
["Please cast ANTI\non me!", "Please cast ANTI on me!"],
|
||||
["Please cast RESTA\non me!", "Please cast RESTA on me!"],
|
||||
["Please clear the\ntrap.", "Please clear the trap."],
|
||||
["Please create a\nTEAM.", "Please create a TEAM."],
|
||||
["Please send me\nyour SIMPLE MAIL again\na little later.", "Please send me your\nSIMPLE MAIL again\na little later.", "Please send me your SHORT MAIL again a little later."],
|
||||
["Please take care\nof them!", "Please take care of them!"],
|
||||
["Please use\nWORD SELECT.", "Please use WORD SELECT."],
|
||||
["Please wait for\nme there.", "Please wait for me there."],
|
||||
["powered down with \"DEF DOWN\"", "powered up with \"DE DOWN\""],
|
||||
["powered up with \"DEF UP\"", "powered up with \"DE UP\""],
|
||||
["PSYCHO", "PSYCHO WAND"],
|
||||
["puzzles", "puzzle"],
|
||||
["reading comics", "reading a cartoon"],
|
||||
["Really?", "Really\u00EF\u00BC\u009F"],
|
||||
["rollerblading", "roller blade"],
|
||||
["RUDRA", "RUDORA"],
|
||||
["running marathons", "marathon"],
|
||||
["Russia", "Rossiya"],
|
||||
["Select Form.", "Select form."],
|
||||
["Select Subject.", "Select subject."],
|
||||
["Select Target.", "Select target."],
|
||||
["Select Topic.", "Select topic."],
|
||||
["Shall we go and get\na QUEST first?", "Shall we go and get\na QUEST?", "Shall we go and get a QUEST?"],
|
||||
["Shall we go back ?", "Shall we go back?"],
|
||||
["Shall we go back\nto PIONEER 2?", "Shall we go back\nto the Morgue?", "Shall we go back\nto PIONEER 2", "Shall we go back\nto the CITY?", "Shall we go back to the CITY?"],
|
||||
["Shall we go to the\nCHECK ROOM first?", "Shall we go to the CHECK ROOM first?"],
|
||||
["Shall we go to the\nMEDICAL ROOM first?", "Shall we go to the MEDICAL ROOM first?"],
|
||||
["Shall we go to the\nPRINCIPAL'S ROOM first?", "Shall we go to\nthe PRINCIPAL'S ROOM first?", "Shall we go to the PRINCIPAL'S ROOM first?"],
|
||||
["Shall we go to the\nSHOPS first?", "Shall we go to the\nshops first?", "Shall we go to the shops first?"],
|
||||
["Shall we go to the\nTEKKER first?", "Shall we go to the TEKKER first?"],
|
||||
["Shall we go to\nthe VISUAL LOBBY first?", "Shall we go to the\nVISUAL LOBBY first?", "Shall we go to the VISUAL LOBBY first?"],
|
||||
["Shall we retreat\nnow?", "Shall we retreat now?"],
|
||||
["SHIELD", "Shield"],
|
||||
["snowboarding", "snow board"],
|
||||
["Somebody go and\nhelp.", "Somebody go and help."],
|
||||
["Somebody please\nrestore my HP!", "Somebody please restore my HP!"],
|
||||
["Somebody please\nrestore my TP!", "Somebody please restore my TP!"],
|
||||
["Sorry to keep you\nwaiting.", "Sorry to keep you waiting."],
|
||||
["Sorry, I can't save\nyou.", "Sorry, I can't save you."],
|
||||
["Sorry, I can't!", "Sorry, I can_t."],
|
||||
["Sorry, I don't have\nany good items.", "Sorry, I don't have any good items."],
|
||||
["Sorry, I have an\nappointment.", "Sorry,\nI have an appointment.", "Sorry, I have an appointment."],
|
||||
["Sorry, I have no\nTechnique to save\nyou.", "Sorry, I have no Technique to save you."],
|
||||
["Sorry, I have no\ntime.", "Sorry, I have no time."],
|
||||
["Sorry, I have nothing\nright now.", "Sorry, I have nothing right now."],
|
||||
["Sorry, I have nothing\nthat'd help you right now.", "Sorry, I have nothing that'd help you right now."],
|
||||
["Sorry, I wasn't paying\nattention.", "Sorry, I wasn't paying attention."],
|
||||
["Sorry, I'm waiting\nfor my friends.", "Sorry,\nI'm waiting\nfor my friends.", "Sorry, I'm waiting for my friends."],
|
||||
["Sorry,I can't help\nyou.", "Sorry,I can't help you."],
|
||||
["sports", "sport"],
|
||||
["STANDSTILL SHIELD", "STANDDTILL SHIELD"],
|
||||
["stay", "stay in"],
|
||||
["Sure thing!", "Sure,"],
|
||||
["Sure, go ahead.", "Sure, go ahead"],
|
||||
["Sure, let me handle\nthis!", "Sure, let me handle this!"],
|
||||
["taking photographs", "takeing a photograph"],
|
||||
["Thanks for your\nhelp.", "Thanks for your help."],
|
||||
["The enemies here\nare too strong.\nLet's leave.", "The enemies here are too strong. Let's leave."],
|
||||
["the next room", "the next loom"],
|
||||
["There are some\nmore enemies left.", "There are some more enemies left."],
|
||||
["There are still enemies\nto defeat.", "There are still enemies to defeat."],
|
||||
["There are still items\nto get.", "There are still items to get."],
|
||||
["There must be a\nswitch.", "There must be a switch."],
|
||||
["This door won't\nopen.", "This door won't open."],
|
||||
["This is the first time\nI've played the game.", "This is the first time I've played the game."],
|
||||
["This way.\nFollow me.", "This way. Follow\nme.", "This way. Follow me."],
|
||||
["TOPIC: MAG", "TOPIC: BARRIER/MAG Card (FORCE)"],
|
||||
["TOPIC: TECHNIQUE", "TOPIC: TECHINIQUE", "TOPIC: Technique Card"],
|
||||
["travelling", "travel"],
|
||||
["U.S.A.", "U.S.A"],
|
||||
["VISK-235W", "VISK'235W"],
|
||||
["VOL OPT ver.2", "VOL OPT Ver2"],
|
||||
["WALS-MK2", "WALS'MK2"],
|
||||
["Want me to lend you a MAG?", "Want me to lend you a Mag?"],
|
||||
["was taken", "did you lose"],
|
||||
["Watch out for the\nenemies!", "Watch out for the enemies!"],
|
||||
["Watch out.\nThere's a trap here.", "Watch out. There's a trap here."],
|
||||
["watching cartoons", "taking a movie"],
|
||||
["watching TV", "watching the television"],
|
||||
["We can't move unless\nwe kill all of them.", "We can't move unless we kill all of them."],
|
||||
["We don't need to.\nLet's go.", "We don't need to. Let's go."],
|
||||
["What is our TEAM\nname?", "What is our TEAM name?"],
|
||||
["What is the name\nof your TEAM?", "What is the name of your TEAM?"],
|
||||
["What is your\nlanguage setting?", "What is\nyour language setting?", "What is your language setting?"],
|
||||
["What level are\nyou?", "What level are\nyou ?", "What level are you ?", "What level are you?"],
|
||||
["What's wrong with\nyou?", "What's wrong with you?"],
|
||||
["When can we meet\nagain?", "When can we meet again?"],
|
||||
["Which AREA do you\nwant to go to?", "Which AREA do you want to go to?"],
|
||||
["Which country do\nyou live in?", "Which country do you live in?", "Which direction shall\nwe go?", "Which direction shall we go?"],
|
||||
["Which lobby are\nyou in now?", "Which lobby are you in now?"],
|
||||
["Which QUEST do you\nwant to play?", "Which QUEST do you want to play?", "Which TEAM\nwill you join?"],
|
||||
["Which TEAM will\nyou join?", "Which TEAM will you\njoin in?", "Which TEAM will you join in?", "Which country\ndo you live in?"],
|
||||
["Who is creating a TEAM?", "Who creating a TEAM?"],
|
||||
["Why don't we take\na break?", "Why don't we\ntake a break?", "Why don't we take\na rest?", "Why don't we take a rest?"],
|
||||
["will attack", "make attack on"],
|
||||
["will equip with", "will equip"],
|
||||
["will gather", "will gather at"],
|
||||
["will move on toward", "will move on towards"],
|
||||
["Will you be\nstaying here?", "Will you still be\nstaying here?"],
|
||||
["will you bet on", "will you bet"],
|
||||
["will you exit", "will you quit"],
|
||||
["Will you go with\nme?", "Will you go with me?"],
|
||||
["Will you still be\nstaying here?", "Will you still stay\nhere?", "Will you still stay here?"],
|
||||
["will you take with you", "will you take with"],
|
||||
["YASMINKOV 2000H", "YASMINKOV 2000"],
|
||||
["YASMINKOV 3000R", "YASMINKOV 3000"],
|
||||
["YELLOWBOZE", "YELLOWBOOZE"],
|
||||
["You create a Team,\nplease.", "You create a Team, please."],
|
||||
["You create a TEAM,\nplease.", "You create a TEAM, please."]
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -254,7 +254,7 @@ I 25793 2023-11-24 23:06:59 - [Commands] Sending to C-8 (ABCDEFGHIJKL) (version=
|
||||
0390 | 00 00 00 00 64 00 00 00 41 42 43 44 45 46 47 48 | d ABCDEFGH
|
||||
03A0 | 49 4A 4B 4C 00 00 00 00 00 00 00 00 00 00 00 00 | IJKL
|
||||
03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
03C0 | 00 00 00 00 F3 2E F3 2E 06 04 00 00 51 00 00 00 | . . Q
|
||||
03C0 | 00 00 00 00 F3 2E F3 2E 06 04 00 01 51 00 00 00 | . . Q
|
||||
03D0 | 00 00 01 00 00 00 00 00 00 00 1F 00 3F 00 B3 00 | ?
|
||||
03E0 | 00 C0 D5 3E 00 82 E9 3E 00 00 00 00 01 00 00 00 | > >
|
||||
03F0 | 02 00 01 00 02 01 01 00 04 00 01 00 01 00 00 00 |
|
||||
@@ -381,7 +381,7 @@ I 25793 2023-11-24 23:07:09 - [Commands] Sending to C-8 (ABCDEFGHIJKL) (version=
|
||||
0390 | 00 00 00 00 64 00 00 00 41 42 43 44 45 46 47 48 | d ABCDEFGH
|
||||
03A0 | 49 4A 4B 4C 00 00 00 00 00 00 00 00 00 00 00 00 | IJKL
|
||||
03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
03C0 | 00 00 00 00 F1 7D F1 7D 06 04 00 00 51 00 00 00 | } } Q
|
||||
03C0 | 00 00 00 00 F1 7D F1 7D 06 04 00 01 51 00 00 00 | } } Q
|
||||
03D0 | 00 00 01 00 00 00 00 00 00 00 1F 00 3F 00 B3 00 | ?
|
||||
03E0 | 00 C0 D5 3E 00 82 E9 3E 00 00 00 00 01 00 00 00 | > >
|
||||
03F0 | 02 00 01 00 02 01 01 00 04 00 01 00 01 00 00 00 |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (version=GC command=9
|
||||
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 42 20 00 00 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 40 20 00 00 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -73,7 +73,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (version=GC command=9
|
||||
0000 | 96 00 0C 00 03 1B 8F 2C C0 00 00 00 | ,
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 42 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 40 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 30 39 3A 31 37 3A 20 | 2023:09:17:
|
||||
@@ -2623,16 +2623,12 @@ I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=CC f
|
||||
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 4A 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 48 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00)
|
||||
0000 | 99 00 04 00 |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 48 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06)
|
||||
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
|
||||
@@ -58,7 +58,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (version=GC command=9
|
||||
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 42 20 00 00 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 40 20 00 00 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -73,7 +73,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (version=GC command=9
|
||||
0000 | 96 00 0C 00 03 1B 8F 2C 06 01 00 00 | ,
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 42 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 40 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 30 39 3A 32 30 3A 20 | 2023:09:20:
|
||||
@@ -2623,16 +2623,12 @@ I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=CC f
|
||||
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 4A 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0010 | 30 45 53 33 00 91 00 48 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00)
|
||||
0000 | 99 00 04 00 |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 45 53 33 00 91 00 48 20 00 04 00 00 00 00 00 | 0ES3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06)
|
||||
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
@@ -5871,7 +5867,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (version=GC command=9
|
||||
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
|
||||
0010 | 00 00 00 33 00 A1 00 42 20 00 00 00 00 00 00 00 | 3@ `
|
||||
0010 | 00 00 00 33 00 A1 00 40 20 00 00 00 00 00 00 00 | 3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -5886,7 +5882,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (version=GC command=9
|
||||
0000 | 96 00 0C 00 30 AA 74 2C 27 00 00 00 | 0 t,'
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
|
||||
0010 | 00 00 00 33 00 A1 00 42 20 00 04 00 00 00 00 00 | 3@ `
|
||||
0010 | 00 00 00 33 00 A1 00 40 20 00 04 00 00 00 00 00 | 3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 30 39 3A 32 30 3A 20 | 2023:09:20:
|
||||
@@ -8436,16 +8432,12 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=CC f
|
||||
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
|
||||
0010 | 30 4A 53 33 00 A1 00 4A 20 00 04 00 00 00 00 00 | 3@ `
|
||||
0010 | 30 4A 53 33 00 A1 00 48 20 00 04 00 00 00 00 00 | 3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=99 flag=00)
|
||||
0000 | 99 00 04 00 |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
|
||||
0010 | 30 4A 53 33 00 A1 00 48 20 00 04 00 00 00 00 00 | 3@ `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=07 flag=08)
|
||||
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
|
||||
@@ -62,7 +62,7 @@ I 25793 2023-11-24 23:03:38 - [Commands] Received from C-4 (version=GC_EP3 comma
|
||||
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 00 00 00 33 00 A1 00 42 20 00 00 00 00 00 00 00 | 3 B`
|
||||
0010 | 00 00 00 33 00 A1 00 40 20 00 00 00 00 00 00 00 | 3 B`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -77,7 +77,7 @@ I 25793 2023-11-24 23:03:38 - [Commands] Received from C-4 (version=GC_EP3 comma
|
||||
0000 | 96 00 0C 00 5C E6 6B 2C 3B 00 00 00 | \ k,;
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 00 00 00 33 00 A1 00 42 20 00 04 00 00 00 00 00 | 3 B`
|
||||
0010 | 00 00 00 33 00 A1 00 40 20 00 04 00 00 00 00 00 | 3 B`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 31 31 3A 32 35 3A 20 | 2023:11:25:
|
||||
@@ -2134,16 +2134,12 @@ I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3_NTE comm
|
||||
7940 | 00 00 00 00 |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3_NTE command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 54 4A 53 33 00 81 00 4A 20 00 04 00 00 00 00 00 | TJS3 J`
|
||||
0010 | 54 4A 53 33 00 81 00 48 20 00 04 00 00 00 00 00 | TJS3 J`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Received from C-4 (Tali) (version=GC_EP3_NTE command=99 flag=00)
|
||||
0000 | 99 00 04 00 |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Received from C-4 (Tali) (version=GC_EP3_NTE command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3_NTE command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 54 4A 53 33 00 81 00 48 20 00 04 00 00 00 00 00 | TJS3 J`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (Tali) (version=GC_EP3_NTE command=07 flag=08)
|
||||
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
|
||||
@@ -58,7 +58,7 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9
|
||||
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 50 4F 33 00 01 00 42 20 00 00 00 00 00 00 00 | 3 `
|
||||
0010 | 30 50 4F 33 00 01 00 40 20 00 00 00 00 00 00 00 | 3 `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
@@ -68,7 +68,7 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9
|
||||
0000 | 96 00 0C 00 C7 32 CE 2A 57 00 00 00 | 2 *W
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 50 4F 33 00 01 00 42 20 00 04 00 00 00 00 00 | 3 `
|
||||
0010 | 30 50 4F 33 00 01 00 40 20 00 04 00 00 00 00 00 | 3 `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 30 35 3A 32 36 3A 20 | 2023:05:26:
|
||||
@@ -77,10 +77,6 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9
|
||||
0000 | 99 00 04 00 |
|
||||
I 49108 2023-05-26 16:18:02 - [Commands] Received from C-1 (version=GC command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
|
||||
0010 | 30 50 4F 33 00 01 00 40 20 00 04 00 00 00 00 00 | 3 `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=07 flag=08)
|
||||
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
@@ -1200,20 +1196,6 @@ I 49108 2023-05-26 16:19:05 - [Commands] Received from C-2 (Jess) (version=GC co
|
||||
I 49108 2023-05-26 16:19:11 - [Commands] Received from C-2 (Jess) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 18 00 00 00 00 00 00 00 00 00 09 45 24 69 | E$i
|
||||
0010 | 6E 66 68 70 00 00 00 00 | nfhp
|
||||
I 49108 2023-05-26 16:19:11 - [Commands] Sending to C-2 (Jess) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 3C 00 00 00 00 00 00 00 00 00 09 43 36 54 | @ C6T
|
||||
0010 | 68 69 73 20 63 6F 6D 6D 61 6E 64 20 63 61 6E 0A | his command can
|
||||
0020 | 6F 6E 6C 79 20 62 65 20 75 73 65 64 20 69 6E 0A | only be used in
|
||||
0030 | 63 68 65 61 74 20 6D 6F 64 65 2E 00 | cheat mode.
|
||||
I 49108 2023-05-26 16:19:13 - [Commands] Received from C-2 (Jess) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 18 00 00 00 00 00 00 00 00 00 09 45 24 63 | E$c
|
||||
0010 | 68 65 61 74 00 00 00 00 | heat
|
||||
I 49108 2023-05-26 16:19:13 - [Commands] Sending to C-2 (Jess) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 20 00 00 00 00 00 00 00 00 00 43 68 65 61 | Chea
|
||||
0010 | 74 20 6D 6F 64 65 20 65 6E 61 62 6C 65 64 00 00 | t mode enabled
|
||||
I 49108 2023-05-26 16:19:14 - [Commands] Received from C-2 (Jess) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 18 00 00 00 00 00 00 00 00 00 09 45 24 69 | E$i
|
||||
0010 | 6E 66 68 70 00 00 00 00 | nfhp
|
||||
I 49108 2023-05-26 16:19:14 - [Commands] Sending to C-2 (Jess) (version=GC command=B0 flag=00)
|
||||
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 49 | $ C6I
|
||||
0010 | 6E 66 69 6E 69 74 65 20 48 50 20 65 6E 61 62 6C | nfinite HP enabl
|
||||
|
||||
@@ -705,27 +705,6 @@ I 49484 2023-05-26 16:36:25 - [Commands] Received from C-3 (Tali) (version=PC co
|
||||
0010 | 24 00 69 00 74 00 65 00 6D 00 20 00 30 00 33 00 | $ i t e m 0 3
|
||||
0020 | 30 00 30 00 30 00 30 00 30 00 30 00 30 00 30 00 | 0 0 0 0 0 0 0 0
|
||||
0030 | 30 00 34 00 00 00 00 00 | 0 4
|
||||
I 49484 2023-05-26 16:36:25 - [Commands] Sending to C-3 (Tali) (version=PC command=B0 flag=00)
|
||||
0000 | 6C 00 B0 00 00 00 00 00 00 00 00 00 09 00 43 00 | p C
|
||||
0010 | 36 00 54 00 68 00 69 00 73 00 20 00 63 00 6F 00 | 6 T h i s c o
|
||||
0020 | 6D 00 6D 00 61 00 6E 00 64 00 20 00 63 00 61 00 | m m a n d c a
|
||||
0030 | 6E 00 0A 00 6F 00 6E 00 6C 00 79 00 20 00 62 00 | n o n l y b
|
||||
0040 | 65 00 20 00 75 00 73 00 65 00 64 00 20 00 69 00 | e u s e d i
|
||||
0050 | 6E 00 0A 00 63 00 68 00 65 00 61 00 74 00 20 00 | n c h e a t
|
||||
0060 | 6D 00 6F 00 64 00 65 00 2E 00 00 00 | m o d e .
|
||||
I 49484 2023-05-26 16:36:29 - [Commands] Received from C-3 (Tali) (version=PC command=06 flag=00)
|
||||
0000 | 20 00 06 00 00 00 00 00 00 00 00 00 09 00 45 00 | E
|
||||
0010 | 24 00 63 00 68 00 65 00 61 00 74 00 00 00 00 00 | $ c h e a t
|
||||
I 49484 2023-05-26 16:36:29 - [Commands] Sending to C-3 (Tali) (version=PC command=B0 flag=00)
|
||||
0000 | 34 00 B0 00 00 00 00 00 00 00 00 00 43 00 68 00 | 4 C h
|
||||
0010 | 65 00 61 00 74 00 20 00 6D 00 6F 00 64 00 65 00 | e a t m o d e
|
||||
0020 | 20 00 65 00 6E 00 61 00 62 00 6C 00 65 00 64 00 | e n a b l e d
|
||||
0030 | 00 00 00 00 |
|
||||
I 49484 2023-05-26 16:36:33 - [Commands] Received from C-3 (Tali) (version=PC command=06 flag=00)
|
||||
0000 | 38 00 06 00 00 00 00 00 00 00 00 00 09 00 45 00 | 8 E
|
||||
0010 | 24 00 69 00 74 00 65 00 6D 00 20 00 30 00 33 00 | $ i t e m 0 3
|
||||
0020 | 30 00 30 00 30 00 30 00 30 00 30 00 30 00 30 00 | 0 0 0 0 0 0 0 0
|
||||
0030 | 30 00 34 00 00 00 00 00 | 0 4
|
||||
I 49484 2023-05-26 16:36:33 - [Commands] Sending to C-3 (Tali) (version=PC command=60 flag=00)
|
||||
0000 | 2C 00 60 00 5D 0A 00 00 01 00 00 00 64 7A 05 44 | , ` ] dz D
|
||||
0010 | 49 EA E8 43 03 00 00 00 00 04 00 00 00 00 00 00 | I C
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@ I 16496 2023-11-08 01:54:08 - [Commands] Received from C-1 (version=XB command=9
|
||||
0020 | 00 00 00 00 |
|
||||
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
|
||||
0010 | 00 00 00 00 00 81 00 42 20 00 00 00 00 00 00 00 | 4 B`
|
||||
0010 | 00 00 00 00 00 81 00 40 20 00 00 00 00 00 00 00 | 4 B`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
@@ -62,7 +62,7 @@ I 16496 2023-11-08 01:54:09 - [Commands] Received from C-1 (version=XB command=9
|
||||
0000 | 96 00 0C 00 7C 9C DA 2C 62 00 00 00 | | ,b
|
||||
I 16496 2023-11-08 01:54:09 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
|
||||
0010 | 00 00 00 00 00 81 00 42 20 00 04 00 00 00 00 00 | 4 @`
|
||||
0010 | 00 00 00 00 00 81 00 40 20 00 04 00 00 00 00 00 | 4 @`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16496 2023-11-08 01:54:09 - [Commands] Sending to C-1 (version=XB command=B1 flag=00)
|
||||
0000 | B1 00 1C 00 32 30 32 33 3A 31 31 3A 30 38 3A 20 | 2023:11:08:
|
||||
@@ -71,10 +71,6 @@ I 16496 2023-11-08 01:54:09 - [Commands] Received from C-1 (version=XB command=9
|
||||
0000 | 99 00 04 00 |
|
||||
I 16496 2023-11-08 01:54:13 - [Commands] Received from C-1 (version=XB command=D6 flag=00)
|
||||
0000 | D6 00 04 00 |
|
||||
I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
|
||||
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
|
||||
0010 | 00 00 00 00 00 81 00 40 20 00 04 00 00 00 00 00 | 4 @`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=07 flag=05)
|
||||
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
|
||||
+4
-1
@@ -29,7 +29,7 @@
|
||||
"DefaultDropModeV4Normal": "SERVER_SHARED",
|
||||
"DefaultDropModeV4Battle": "SERVER_SHARED",
|
||||
"DefaultDropModeV4Challenge": "SERVER_SHARED",
|
||||
|
||||
"CheatModeBehavior": "OnByDefault",
|
||||
|
||||
"LocalAddress": "en0",
|
||||
"ExternalAddress": "en0",
|
||||
@@ -108,7 +108,10 @@
|
||||
"HideDownloadCommands": true,
|
||||
|
||||
"AllowUnregisteredUsers": true,
|
||||
"UseTemporaryLicensesForPrototypes": true,
|
||||
"AllowPCNTE": true,
|
||||
"AllowDCPCGames": true,
|
||||
"AllowGCXBGames": true,
|
||||
|
||||
"InformationMenuContents": [
|
||||
["Text colors", "$C7Display color values", "These values can be used to color text in\nsome situations, with escape codes like %sC6.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"],
|
||||
|
||||
Reference in New Issue
Block a user