Compare commits

..

21 Commits

Author SHA1 Message Date
Martin Michelsen cd008ab0ba rewrite DeckState::draw_card_by_ref 2024-03-23 21:02:00 -07:00
Martin Michelsen 53b36d7074 put an extra \n in choice search result text 2024-03-23 21:02:00 -07:00
Martin Michelsen 5a1880bd65 allow sender_c to be null in Ep3 server command handlers 2024-03-23 21:02:00 -07:00
Martin Michelsen 8e280a1464 fix wrong type in default ep3 behavior flags 2024-03-22 22:25:14 -07:00
Martin Michelsen 0bcdd9997e define choice_search_config in gc char file format 2024-03-22 22:25:04 -07:00
Martin Michelsen d5351c4580 set BB player mag color at char creation time 2024-03-22 22:24:45 -07:00
Martin Michelsen 76bc2385ca add PSOBB Hangame functions 2024-03-22 22:24:04 -07:00
Martin Michelsen 325f7c6efc add UnlockAllAreas config option 2024-03-18 10:03:37 -07:00
Martin Michelsen 93d97d3e5b factor out debug mode check 2024-03-17 21:16:31 -07:00
Martin Michelsen 66b64603a0 add $sb command 2024-03-17 19:03:24 -07:00
Martin Michelsen 7405eaea0b add format-ep3-battle-record command 2024-03-17 14:12:57 -07:00
Martin Michelsen 477e433361 update some command notes 2024-03-17 14:12:57 -07:00
Martin Michelsen 7ca2012bc4 add CA commands into Ep3 battle record format 2024-03-16 18:48:27 -07:00
Martin Michelsen dace165ef2 fix enemy data json in /y/data/common-tables 2024-03-16 18:45:35 -07:00
Martin Michelsen f6df2b5b45 add note about C4 crash 2024-03-16 18:45:11 -07:00
Martin Michelsen 1a310df17e fix choice search crash 2024-03-16 09:57:35 -07:00
Martin Michelsen 31edec701b refine game info messages 2024-03-15 22:59:50 -07:00
Martin Michelsen dc36d2ae8d fix quest expr checks from lobby 2024-03-15 10:20:19 -07:00
Martin Michelsen 4e733b0dc6 add object type name in map disassembly 2024-03-15 00:32:00 -07:00
Martin Michelsen 6eadaaca66 use pthreads for libevent on windows 2024-03-15 00:31:50 -07:00
Martin Michelsen d778340999 add BB format of 6x6F command 2024-03-15 00:31:33 -07:00
33 changed files with 638 additions and 266 deletions
+2 -1
View File
@@ -439,7 +439,8 @@ Some commands only work on the game server and not on the proxy server. The chat
* `$qsyncall <reg-num> <value>`: Set a quest register's value for everyone in the game. `<reg-num>` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `<value>` is parsed as a floating-point value instead of as an integer.
* `$gc` (game server only): Send your own Guild Card to yourself.
* `$sc <data>`: Send a command to yourself.
* `$ss <data>` (proxy server only): Send a command to the remote server.
* `$ss <data>`: Send a command to the remote server (if in a proxy session) or to the game server.
* `$sb <data>`: Send a command to yourself, and to the remote server or game server.
* `$meseta <amount>` (game server only; Episode 3 only): Add the given amount to your Meseta total.
* `$auction` (Episode 3 only): Bring up the CARD Auction menu, regardless of how many players are in the game or if you have a VIP card.
* `$ep3battledebug` (game server only; Episode 3 only): Enable or disable TCard00_Select. If enabled, the game will enter the debug menu when you start a battle.
+33 -29
View File
@@ -65,6 +65,12 @@ static void check_is_ep3(shared_ptr<Client> c, bool is_ep3) {
}
}
static void check_debug_enabled(shared_ptr<Client> c) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
throw precondition_failed("$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
}
}
static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> c) {
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && !c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) {
throw precondition_failed("$C6This command can\nonly be used in\ncheat mode.");
@@ -290,10 +296,7 @@ static void server_command_debug(shared_ptr<Client> c, const std::string&) {
}
static void server_command_quest(shared_ptr<Client> c, const std::string& args) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
Version effective_version = is_ep3(c->version()) ? Version::GC_V3 : c->version();
@@ -330,10 +333,7 @@ static void server_command_qcheck(shared_ptr<Client> c, const std::string& args)
}
static void server_command_qset_qclear(shared_ptr<Client> c, const std::string& args, bool should_set) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
auto l = c->require_lobby();
if (!l->is_game()) {
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
@@ -419,10 +419,7 @@ static void server_command_qgwrite(shared_ptr<Client> c, const std::string& args
send_text_message(c, "$C6This command can\nonly be used on BB");
return;
}
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
auto l = c->require_lobby();
if (!l->is_game()) {
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
@@ -449,10 +446,7 @@ static void server_command_qgwrite(shared_ptr<Client> c, const std::string& args
}
static void server_command_qsync_qsyncall(shared_ptr<Client> c, const std::string& args, bool send_to_lobby) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
auto l = c->require_lobby();
if (!l->is_game()) {
send_text_message(c, "$C6This command cannot\nbe used in the lobby");
@@ -531,10 +525,7 @@ static void proxy_command_qsyncall(shared_ptr<ProxyServer::LinkedSession> ses, c
}
static void server_command_qcall(shared_ptr<Client> c, const std::string& args) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
auto l = c->require_lobby();
if (l->is_game() && l->quest) {
@@ -742,11 +733,19 @@ static void proxy_command_get_player_card(shared_ptr<ProxyServer::LinkedSession>
}
static void server_command_send_client(shared_ptr<Client> c, const std::string& args) {
check_debug_enabled(c);
string data = parse_data_string(args);
data.resize((data.size() + 3) & (~3));
c->channel.send(data);
}
static void server_command_send_server(shared_ptr<Client> c, const std::string& args) {
check_debug_enabled(c);
string data = parse_data_string(args);
data.resize((data.size() + 3) & (~3));
on_command_with_header(c, data);
}
static void proxy_command_send_client(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
string data = parse_data_string(args);
data.resize((data.size() + 3) & (~3));
@@ -759,6 +758,16 @@ static void proxy_command_send_server(shared_ptr<ProxyServer::LinkedSession> ses
ses->server_channel.send(data);
}
static void server_command_send_both(shared_ptr<Client> c, const std::string& args) {
server_command_send_client(c, args);
server_command_send_server(c, args);
}
static void proxy_command_send_both(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
proxy_command_send_client(ses, args);
proxy_command_send_server(ses, args);
}
////////////////////////////////////////////////////////////////////////////////
// Lobby commands
@@ -932,10 +941,7 @@ static void server_command_playrec(shared_ptr<Client> c, const std::string& args
static void server_command_meseta(shared_ptr<Client> c, const std::string& args) {
check_is_ep3(c, true);
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
uint32_t amount = stoul(args, nullptr, 0);
c->license->ep3_current_meseta += amount;
@@ -1874,10 +1880,7 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
}
static void server_command_enable_ep3_battle_debug_menu(shared_ptr<Client> c, const std::string& args) {
if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)");
return;
}
check_debug_enabled(c);
auto l = c->require_lobby();
check_is_game(l, true);
@@ -2242,6 +2245,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$save", {server_command_save, nullptr}},
{"$savechar", {server_command_savechar, nullptr}},
{"$saverec", {server_command_saverec, nullptr}},
{"$sb", {server_command_send_both, proxy_command_send_both}},
{"$sc", {server_command_send_client, proxy_command_send_client}},
{"$secid", {server_command_secid, proxy_command_secid}},
{"$setassist", {server_command_ep3_replace_assist_card, nullptr}},
@@ -2249,7 +2253,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$silence", {server_command_silence, nullptr}},
{"$song", {server_command_song, proxy_command_song}},
{"$spec", {server_command_toggle_spectator_flag, nullptr}},
{"$ss", {nullptr, proxy_command_send_server}},
{"$ss", {server_command_send_server, proxy_command_send_server}},
{"$stat", {server_command_get_ep3_battle_stat, nullptr}},
{"$surrender", {server_command_surrender, nullptr}},
{"$swa", {server_command_switch_assist, proxy_command_switch_assist}},
+23 -7
View File
@@ -201,6 +201,7 @@ Client::Client(
card_battle_table_number(-1),
card_battle_table_seat_number(0),
card_battle_table_seat_state(0),
last_game_info_requested(0),
should_update_play_time(false),
bb_character_index(-1),
next_exp_value(0),
@@ -265,7 +266,7 @@ void Client::set_license(shared_ptr<License> l) {
if (this->version() == Version::BB_V4) {
// Make sure bb_username is filename-safe
for (char ch : l->bb_username) {
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
if (!isalnum(ch) && (ch != '-') && (ch != '_') && (ch != '@')) {
throw runtime_error("invalid characters in username");
}
}
@@ -350,6 +351,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
bool Client::evaluate_quest_availability_expression(
shared_ptr<const QuestAvailabilityExpression> expr,
shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
@@ -360,10 +362,12 @@ bool Client::evaluate_quest_availability_expression(
if (!expr) {
return true;
}
auto l = this->lobby.lock();
if (game && !game->quest_flag_values) {
throw logic_error("quest flags are missing from game");
}
auto p = this->character();
QuestAvailabilityExpression::Env env = {
.flags = (l && !l->quest_flags_known) ? &l->quest_flag_values->data.at(difficulty) : &p->quest_flags.data.at(difficulty),
.flags = (game && !game->quest_flags_known) ? &game->quest_flag_values->data.at(difficulty) : &p->quest_flags.data.at(difficulty),
.challenge_records = &p->challenge_records,
.team = this->team(),
.num_players = num_players,
@@ -378,12 +382,24 @@ bool Client::evaluate_quest_availability_expression(
return ret;
}
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const {
return this->evaluate_quest_availability_expression(q->available_expression, event, difficulty, num_players, v1_present);
bool Client::can_see_quest(
shared_ptr<const Quest> q,
shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const {
return this->evaluate_quest_availability_expression(q->available_expression, game, event, difficulty, num_players, v1_present);
}
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const {
return this->evaluate_quest_availability_expression(q->enabled_expression, event, difficulty, num_players, v1_present);
bool Client::can_play_quest(
shared_ptr<const Quest> q,
shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const {
return this->evaluate_quest_availability_expression(q->enabled_expression, game, event, difficulty, num_players, v1_present);
}
bool Client::can_use_chat_commands() const {
+16 -2
View File
@@ -219,6 +219,7 @@ public:
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<Episode3::BattleRecord> ep3_prev_battle_record;
std::shared_ptr<const Menu> last_menu_sent;
uint32_t last_game_info_requested;
struct JoinCommand {
uint16_t command;
uint32_t flag;
@@ -294,12 +295,25 @@ public:
bool evaluate_quest_availability_expression(
std::shared_ptr<const QuestAvailabilityExpression> expr,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_see_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_play_quest(
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
size_t num_players,
bool v1_present) const;
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const;
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const;
bool can_use_chat_commands() const;
+22 -7
View File
@@ -2491,6 +2491,9 @@ struct C_CreateGame_BB_C1 : C_CreateGame<TextEncoding::UTF16> {
// C4 (S->C): Choice search results (DCv2 and later versions)
// Internal name: RcvChoiceAns
// There is a bug that can cause the client to crash or display garbage if this
// command is sent with no entries. To work around this, newserv sends a blank
// entry (but still with header.flag = 0) if there are no results.
// Command is a list of these; header.flag is the entry count
template <typename HeaderT, TextEncoding NameEncoding, TextEncoding DescEncoding, TextEncoding LocatorEncoding>
@@ -4763,9 +4766,9 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
le_uint16_t event_set_flags_size = 0;
le_uint16_t switch_flags_size = 0;
// Variable-length fields follow here:
// EntitySetFlags entity_set_flags; // Total size is set_flags_size
// EntitySetFlags entity_set_flags; // Total size is entity_set_flags_size
// le_uint16_t event_set_flags[event_set_flags_size / 2]; // Same order as in map files (NOT sorted by event_id)
// SwitchFlags switch_flags; // 0x200 bytes on v1 abd earlier; 0x240 bytes on v2 and later
// SwitchFlags switch_flags; // 0x200 bytes (0x10 floors) on v1 and earlier; 0x240 bytes (0x12 floors) on v2 and later
struct EntitySetFlags {
le_uint32_t object_set_flags_offset = 0;
@@ -4780,19 +4783,29 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
// 6x6F: Set quest flags (used while loading into game)
struct G_SetQuestFlagsV1_6x6F {
struct G_SetQuestFlags_DCv1_6x6F {
G_UnusedHeader header;
QuestFlagsV1 quest_flags;
} __packed__;
struct G_SetQuestFlagsV2V3V4_6x6F {
struct G_SetQuestFlags_V2_V3_6x6F {
G_UnusedHeader header;
QuestFlags quest_flags;
} __packed__;
struct G_SetQuestFlags_BB_6x6F {
G_UnusedHeader header;
QuestFlags quest_flags;
// If use_apply_mask is 1, only the flags set in bb_quest_flag_apply_mask
// (in PlayerSubordinates.cc) are overwritten on the receiving client's end.
// The client always sends this with use_apply_mask = 1.
le_uint32_t use_apply_mask = 1;
} __packed__;
// 6x70: Sync player disp data and inventory (used while loading into game)
// Annoyingly, they didn't use the same format as the 65/67/68 commands here,
// and instead rearranged a bunch of things.
// and instead rearranged a bunch of things. This is presumably because this
// structure also includes transient state (e.g. current HP).
struct Telepipe {
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
@@ -4960,9 +4973,11 @@ struct G_DoneLoadingIntoGame_6x72 {
G_UnusedHeader header;
} __packed__;
// 6x73: Unknown
// 6x73: Exit quest
// This command misbehaves if sent in a lobby or in a game when no quest is
// loaded.
struct G_Unknown_6x73 {
struct G_ExitQuest_6x73 {
G_UnusedHeader header;
} __packed__;
+4 -11
View File
@@ -50,19 +50,12 @@ JSON CommonItemSet::Table::json() const {
for (size_t z = 0; z < 0x64; z++) {
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
for (Episode episode : episodes) {
JSON enemy_meseta_ranges_episode_json = JSON::dict();
JSON enemy_type_drop_probs_episode_json = JSON::dict();
JSON enemy_item_classes_episode_json = JSON::dict();
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
string type_str = name_for_enum(type);
enemy_meseta_ranges_episode_json.emplace(type_str, to_json(this->enemy_meseta_ranges[z]));
enemy_type_drop_probs_episode_json.emplace(type_str, this->enemy_type_drop_probs[z]);
enemy_item_classes_episode_json.emplace(type_str, this->enemy_item_classes[z]);
string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type));
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
}
string name = name_for_episode(episode);
enemy_meseta_ranges_json.emplace(name, std::move(enemy_meseta_ranges_episode_json));
enemy_type_drop_probs_json.emplace(name, std::move(enemy_type_drop_probs_episode_json));
enemy_item_classes_json.emplace(name, std::move(enemy_item_classes_episode_json));
}
}
return JSON::dict({
+72
View File
@@ -9,6 +9,12 @@ using namespace std;
namespace Episode3 {
void BattleRecord::PlayerEntry::print(FILE* stream) const {
// TODO: Format this nicely somehow. Maybe factor out the functions in
// QuestScript that format some of these structures
print_data(stream, this, sizeof(this));
}
BattleRecord::Event::Event(StringReader& r) {
this->type = r.get<Event::Type>();
this->timestamp = r.get_u64l();
@@ -32,6 +38,7 @@ BattleRecord::Event::Event(StringReader& r) {
case Event::Type::GAME_COMMAND:
case Event::Type::BATTLE_COMMAND:
case Event::Type::EP3_GAME_COMMAND:
case Event::Type::SERVER_DATA_COMMAND:
this->data = r.read(r.get_u16l());
break;
default:
@@ -64,6 +71,7 @@ void BattleRecord::Event::serialize(StringWriter& w) const {
case Event::Type::GAME_COMMAND:
case Event::Type::BATTLE_COMMAND:
case Event::Type::EP3_GAME_COMMAND:
case Event::Type::SERVER_DATA_COMMAND:
w.put_u16l(this->data.size());
w.write(this->data);
break;
@@ -72,6 +80,51 @@ void BattleRecord::Event::serialize(StringWriter& w) const {
}
}
void BattleRecord::Event::print(FILE* stream) const {
string time_str = format_time(this->timestamp);
fprintf(stream, "Event @%016" PRIX64 " (%s) ", this->timestamp, time_str.c_str());
switch (this->type) {
case Type::PLAYER_JOIN:
fprintf(stream, "PLAYER_JOIN %02" PRIX32 "\n", this->players[0].lobby_data.client_id.load());
this->players[0].print(stream);
break;
case Type::PLAYER_LEAVE:
fprintf(stream, "PLAYER_LEAVE %02hhu\n", this->leaving_client_id);
break;
case Type::SET_INITIAL_PLAYERS:
fprintf(stream, "SET_INITIAL_PLAYERS");
for (const auto& player : this->players) {
fprintf(stream, " %02" PRIX32, player.lobby_data.client_id.load());
}
for (const auto& player : this->players) {
player.print(stream);
}
break;
case Type::BATTLE_COMMAND:
fprintf(stream, "BATTLE_COMMAND\n");
print_data(stream, this->data);
break;
case Type::GAME_COMMAND:
fprintf(stream, "GAME_COMMAND\n");
print_data(stream, this->data);
break;
case Type::EP3_GAME_COMMAND:
fprintf(stream, "EP3_GAME_COMMAND\n");
print_data(stream, this->data);
break;
case Type::CHAT_MESSAGE:
fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number);
print_data(stream, this->data);
break;
case Type::SERVER_DATA_COMMAND:
fprintf(stream, "SERVER_DATA_COMMAND\n");
print_data(stream, this->data);
break;
default:
throw runtime_error("unknown event type in batlte record");
}
}
BattleRecord::BattleRecord(uint32_t behavior_flags)
: is_writable(true),
behavior_flags(behavior_flags),
@@ -274,6 +327,21 @@ void BattleRecord::set_battle_end_timestamp() {
this->battle_end_timestamp = now();
}
void BattleRecord::print(FILE* stream) const {
string start_str = format_time(this->battle_start_timestamp);
string end_str = format_time(this->battle_end_timestamp);
fprintf(stream, "BattleRecord %s behavior_flags=%08" PRIX32 " start=%016" PRIX64 " (%s) end=%016" PRIX64 " (%s); %zu events\n",
this->is_writable ? "writable" : "read-only",
this->behavior_flags,
this->battle_start_timestamp,
start_str.c_str(),
this->battle_end_timestamp,
end_str.c_str(), this->events.size());
for (const auto& event : this->events) {
event.print(stream);
}
}
BattleRecordPlayer::BattleRecordPlayer(
shared_ptr<const BattleRecord> rec,
shared_ptr<struct event_base> base)
@@ -356,6 +424,10 @@ void BattleRecordPlayer::schedule_events() {
case BattleRecord::Event::Type::CHAT_MESSAGE:
send_prepared_chat_message(l, ev.guild_card_number, ev.data);
break;
case BattleRecord::Event::Type::SERVER_DATA_COMMAND:
// These are not replayed, since the battle record also contains
// the results of these commands.
break;
}
this->event_it++;
+6
View File
@@ -24,6 +24,8 @@ public:
PlayerInventory inventory;
PlayerDispDataDCPCV3 disp;
le_uint32_t level;
void print(FILE* stream) const;
} __attribute__((packed));
struct Event {
@@ -35,6 +37,7 @@ public:
GAME_COMMAND = 4,
EP3_GAME_COMMAND = 5,
CHAT_MESSAGE = 6,
SERVER_DATA_COMMAND = 7,
};
// Fields used for all events
@@ -52,6 +55,7 @@ public:
Event() = default;
explicit Event(StringReader& r);
void serialize(StringWriter& w) const;
void print(FILE* stream) const;
};
explicit BattleRecord(uint32_t behavior_flags);
@@ -79,6 +83,8 @@ public:
void set_battle_start_timestamp();
void set_battle_end_timestamp();
void print(FILE* stream) const;
private:
static constexpr uint64_t SIGNATURE = 0x14C946D56D1DAC50;
+30 -28
View File
@@ -84,43 +84,45 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
}
uint8_t index = index_for_card_ref(card_ref);
if (index > this->entries.size()) {
if (index >= this->entries.size()) {
return false;
}
if (this->entries[index].state == CardState::DISCARDED) {
auto& entry = this->entries[index];
if (entry.state == CardState::DISCARDED) {
// If the card is discarded, then it should be before the draw index, and we
// can just change its state.
this->entries[index].state = CardState::IN_HAND;
entry.state = CardState::IN_HAND;
return true;
}
} else if (this->entries[index].state == CardState::DRAWABLE) {
// If the card is still drawable, we need to move it so it's just in front
// of the draw index, then immediately draw it. Ep3 NTE does not handle this
// case, but we do even when playing NTE.
ssize_t ref_index;
for (ref_index = this->card_refs.size(); ref_index >= 0; ref_index--) {
if (this->card_refs[ref_index] == card_ref) {
break;
}
}
if (ref_index < 0) {
return false;
}
size_t ref_uindex = ref_index;
for (; ref_uindex > this->draw_index; ref_uindex--) {
// Note: draw_index is also unsigned, so ref_uindex cannot be zero here
this->card_refs[ref_uindex] = this->card_refs[ref_uindex - 1];
}
this->card_refs[this->draw_index] = card_ref;
this->entries[index].state = CardState::IN_HAND;
this->draw_index++;
return true;
} else {
if (entry.state != CardState::DRAWABLE) {
return false;
}
// If the card is still drawable, we need to move it so it's just in front of
// the draw index, then immediately draw it. Ep3 NTE does not handle this
// case, but we do even when playing NTE.
size_t ref_index;
for (ref_index = 0; ref_index < this->card_refs.size(); ref_index++) {
if (this->card_refs[ref_index] == card_ref) {
break;
}
}
if (ref_index >= this->card_refs.size()) {
return false;
}
for (; ref_index > this->draw_index; ref_index--) {
// this->draw_index is also unsigned, so ref_index cannot be zero here
this->card_refs[ref_index] = this->card_refs[ref_index - 1];
}
this->card_refs[this->draw_index] = card_ref;
// Draw the card
entry.state = CardState::IN_HAND;
this->draw_index++;
return true;
}
uint16_t DeckState::card_id_for_card_ref(uint16_t card_ref) const {
+1 -2
View File
@@ -224,8 +224,7 @@ void PlayerState::apply_assist_card_effect_on_set(
size_t log_index;
for (log_index = 0; log_index < 0x10; log_index++) {
auto ce = s->definition_for_card_ref(
this->discard_log_card_refs[log_index]);
auto ce = s->definition_for_card_ref(this->discard_log_card_refs[log_index]);
if (ce && ((ce->def.type == CardType::ITEM || ce->def.type == CardType::CREATURE))) {
break;
}
+9 -4
View File
@@ -1837,7 +1837,12 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
throw runtime_error("unknown CAx subsubcommand");
}
if ((sender_c->version() == Version::GC_EP3_NTE) || !header.mask_key) {
auto l = this->lobby.lock();
if (l && l->battle_record && l->battle_record->writable()) {
l->battle_record->add_command(BattleRecord::Event::Type::SERVER_DATA_COMMAND, data.data(), data.size());
}
if ((sender_c && (sender_c->version() == Version::GC_EP3_NTE)) || !header.mask_key) {
(this->*handler)(sender_c, data);
} else {
string unmasked_data = data;
@@ -2167,7 +2172,8 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
// in the case of NTE, no values at all, since the Rules structure is
// smaller). So, use the values from the last chosen map if applicable, or
// the values from the $dicerange command if available.
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(c->language())->map->default_rules : nullptr;
uint8_t language = c ? c->language() : 1;
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr;
auto& server_rules = this->map_and_rules->rules;
// NTE can specify the DEF dice value range in its Rules struct, so we use
// that unless the map or $dicerange overrides it.
@@ -2554,8 +2560,7 @@ void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& d
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_CAx40>(data);
this->send_debug_command_received_message(
in_cmd.header.subsubcommand, "MAP LIST");
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
auto l = this->lobby.lock();
if (!l) {
+1 -4
View File
@@ -848,10 +848,7 @@ JSON HTTPServer::generate_common_tables_json() const {
auto [set_v2, set_v3_v4] = call_on_event_thread<pair<shared_ptr<const CommonItemSet>, shared_ptr<const CommonItemSet>>>(this->state->base, [&]() {
return make_pair(this->state->common_item_set_v2, this->state->common_item_set_v3_v4);
});
return JSON::dict({
{"v1_v2", set_v2->json()},
{"v3_v4", set_v3_v4->json()},
});
return JSON::dict({{"v1_v2", set_v2->json()}, {"v3_v4", set_v3_v4->json()}});
}
JSON HTTPServer::generate_rare_tables_json() const {
+5 -4
View File
@@ -607,12 +607,13 @@ private:
class MagEvolutionTable {
public:
struct TableOffsets {
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[0x53], offset -> (same as first offset)]
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[0x53]
// num_mags = 0x53 in BB, 0x43 in V3
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[num_mags], offset -> (same as first offset)]
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[num_mags]
/* 08 / 04AE */ le_uint32_t unknown_a3; // -> (0xA8 bytes; possibly (8-byte struct)[0x15])
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[0x53]
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags]
/* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48]
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[0x53]
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags]
} __attribute__((packed));
struct EvolutionNumberTable {
+6 -4
View File
@@ -816,8 +816,9 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
if (this->quest) {
size_t num_clients = this->count_clients() + 1;
bool v1_present = is_v1(c->version()) || this->any_v1_clients_present();
if (!c->can_see_quest(this->quest, this->event, this->difficulty, num_clients, v1_present) ||
!c->can_play_quest(this->quest, this->event, this->difficulty, num_clients, v1_present)) {
auto this_sh = this->shared_from_this();
if (!c->can_see_quest(this->quest, this_sh, this->event, this->difficulty, num_clients, v1_present) ||
!c->can_play_quest(this->quest, this_sh, this->event, this->difficulty, num_clients, v1_present)) {
return JoinError::NO_ACCESS_TO_QUEST;
}
}
@@ -930,10 +931,11 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
return [this, num_players, v1_present](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
bool is_enabled = true;
for (const auto& lc : this->clients) {
if (lc && !lc->can_see_quest(q, this->event, this->difficulty, num_players, v1_present)) {
auto this_sh = this->shared_from_this();
if (lc && !lc->can_see_quest(q, this_sh, this->event, this->difficulty, num_players, v1_present)) {
return QuestIndex::IncludeState::HIDDEN;
}
if (lc && !lc->can_play_quest(q, this->event, this->difficulty, num_players, v1_present)) {
if (lc && !lc->can_play_quest(q, this_sh, this->event, this->difficulty, num_players, v1_present)) {
is_enabled = false;
}
}
+17 -6
View File
@@ -2285,6 +2285,22 @@ Action a_diff_dol_files(
}
});
Action a_generate_hangame_creds(
"generate-hangame-creds", nullptr, +[](Arguments& args) {
const string& user_id = args.get<string>(1);
const string& token = args.get<string>(2);
const string& unused = args.get<string>(3, false);
string hex = format_data_string(encode_psobb_hangame_credentials(user_id, token, unused));
fprintf(stdout, "psobb.exe 1196310600 %s\n", hex.c_str());
});
Action a_format_ep3_battle_record(
"format-ep3-battle-record", nullptr, +[](Arguments& args) {
string data = read_input_data(args);
Episode3::BattleRecord rec(data);
rec.print(stdout);
});
Action a_replay_ep3_battle_commands(
"replay-ep3-battle-commands", nullptr, +[](Arguments& args) {
auto s = make_shared<ServerState>();
@@ -2341,12 +2357,7 @@ Action a_run_server_replay_log(
config_log.info("newserv %s compiled at %s", GIT_REVISION_HASH, build_date.c_str());
}
#ifdef PHOSG_WINDOWS
int evthread_ret = evthread_use_windows_threads();
#else
int evthread_ret = evthread_use_pthreads();
#endif
if (evthread_ret) {
if (evthread_use_pthreads()) {
throw runtime_error("failed to setup libevent threads");
}
+3 -1
View File
@@ -607,8 +607,10 @@ JSON Map::RareEnemyRates::json() const {
}
string Map::ObjectEntry::str() const {
return string_printf("[ObjectEntry type=%04hX flags=%04hX index=%04hX a2=%04hX entity_id=%04hX group=%04hX section=%04hX a3=%04hX x=%g y=%g z=%g x_angle=%08" PRIX32 " y_angle=%08" PRIX32 " z_angle=%08" PRIX32 " params=[%g %g %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] unused=%08" PRIX32 "]",
string name_str = Map::name_for_object_type(this->base_type);
return string_printf("[ObjectEntry type=%04hX \"%s\" flags=%04hX index=%04hX a2=%04hX entity_id=%04hX group=%04hX section=%04hX a3=%04hX x=%g y=%g z=%g x_angle=%08" PRIX32 " y_angle=%08" PRIX32 " z_angle=%08" PRIX32 " params=[%g %g %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] unused=%08" PRIX32 "]",
this->base_type.load(),
name_str.c_str(),
this->flags.load(),
this->index.load(),
this->unknown_a2.load(),
+44
View File
@@ -1114,3 +1114,47 @@ string RecentSwitchFlags::enable_commands(uint8_t floor) const {
}
return std::move(w.str());
}
const QuestFlagsForDifficulty bb_quest_flag_apply_mask{{
// clang-format off
/* 0000 */ 0x00, 0x3F, 0xFF, 0xE3, 0xE0, 0xFF, 0xFF, 0x00,
/* 0040 */ 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
/* 0080 */ 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00,
/* 00C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x00,
/* 0140 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
/* 0180 */ 0xFE, 0x00, 0x7F, 0xFE, 0x0F, 0xFF, 0xFF, 0x80,
/* 01C0 */ 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF,
/* 0200 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
/* 0240 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0280 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
/* 02C0 */ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0300 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0340 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0380 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 03C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// clang-format on
// The flags in the above mask are:
// 000A 000B 000C 000D 000E 000F 0010 0011 0012 0013 0014 0015 0016 0017
// 0018 0019 001A 001E 001F 0020 0021 0022 0028 0029 002A 002B 002C 002D
// 002E 002F 0030 0031 0032 0033 0034 0035 0036 0037 0046 0047 0048 0049
// 004A 004B 004C 004D 004E 004F 0050 0051 0052 0053 0054 0055 0056 0057
// 0058 0059 005A 005B 005C 005D 005E 005F 0060 0061 0062 0063 0097 0098
// 0099 009A 012D 012E 012F 0130 0131 0132 0133 0134 0135 0140 0141 0142
// 0143 0144 0145 0146 0147 0148 0149 014A 014B 014C 014D 014E 014F 0150
// 0151 0152 0153 0154 0155 0156 0157 0158 0159 015A 015B 015C 015D 015E
// 015F 0160 0161 0162 0163 0164 0165 0166 0167 0168 0169 016A 016B 016C
// 016D 016E 016F 0170 0171 0172 0173 0174 0175 0176 0177 0178 0179 017A
// 017B 017C 017D 017E 017F 0180 0181 0182 0183 0184 0185 0186 0191 0192
// 0193 0194 0195 0196 0197 0198 0199 019A 019B 019C 019D 019E 01A4 01A5
// 01A6 01A7 01A8 01A9 01AA 01AB 01AC 01AD 01AE 01AF 01B0 01B1 01B2 01B3
// 01B4 01B5 01B6 01B7 01B8 01C2 01C3 01C4 01C5 01C6 01C7 01C8 01C9 01CA
// 01CB 01CC 01CD 01CE 01CF 01D0 01D1 01D2 01D3 01D4 01D5 01D6 01F4 01F5
// 01F6 01F7 01F8 01F9 01FA 01FB 01FC 01FD 01FE 01FF 0200 0201 0202 0203
// 0204 0205 0206 0207 0208 0209 020A 020B 020C 020D 020E 020F 0210 0211
// 0212 0213 0214 0215 0216 0217 0218 0219 021A 021B 021C 021D 021E 021F
// 0220 0221 0222 0223 0224 0225 0226 0227 0228 0229 022A 022B 022C 022D
// 022E 022F 0230 0231 0232 0233 0234 0235 02BD 02BE 02BF 02C0 02C1 02C2
// 02C3 02C4
}};
+2
View File
@@ -745,3 +745,5 @@ struct RecentSwitchFlags {
std::string enable_commands(uint8_t floor) const;
};
extern const QuestFlagsForDifficulty bb_quest_flag_apply_mask;
+144 -107
View File
@@ -1720,7 +1720,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
send_text_message(l, "$C6Recording enabled");
send_text_message(l, "$C7Recording enabled");
}
}
}
@@ -1795,10 +1795,10 @@ static void on_E2_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string&) {
if (tourn) {
send_ep3_tournament_entry_list(c, tourn, false);
} else {
send_lobby_message_box(c, "$C6The tournament\nhas concluded.");
send_lobby_message_box(c, "$C7The tournament\nhas concluded.");
}
} else {
send_lobby_message_box(c, "$C6You are not\nregistered in a\ntournament.");
send_lobby_message_box(c, "$C7You are not\nregistered in a\ntournament.");
}
break;
}
@@ -1822,7 +1822,7 @@ static void on_E2_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string&) {
}
case 0x03: // Create tournament spectator team (get battle list)
case 0x04: // Join tournament spectator team (get team list)
send_lobby_message_box(c, "$C6Use View Regular\nBattle for this");
send_lobby_message_box(c, "$C7Use View Regular\nBattle for this");
break;
default:
throw runtime_error("invalid tournament operation");
@@ -1857,7 +1857,7 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
bool is_download_quest = !c->lobby.lock();
auto quest_index = s->quest_index(c->version());
if (!quest_index) {
send_quest_info(c, "$C6Quests are not available.", is_download_quest);
send_quest_info(c, "$C7Quests are not available.", is_download_quest);
} else {
auto q = quest_index->get(cmd.item_id);
if (!q) {
@@ -1889,72 +1889,43 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
string info;
for (size_t x = 0; x < game->max_clients; x++) {
const auto& game_c = game->clients[x];
if (game_c.get()) {
auto player = game_c->character();
string name = escape_player_name(player->disp.name.decode(game_c->language()));
if (game->is_ep3()) {
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
x + 1, name.c_str(), player->disp.stats.level + 1);
} else {
info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n",
x + 1, name.c_str(),
abbreviation_for_char_class(player->disp.visual.char_class),
player->disp.stats.level + 1);
if (c->last_game_info_requested != game->lobby_id) {
// Send page 1 (players)
c->last_game_info_requested = game->lobby_id;
for (size_t x = 0; x < game->max_clients; x++) {
const auto& game_c = game->clients[x];
if (game_c.get()) {
auto player = game_c->character();
string name = escape_player_name(player->disp.name.decode(game_c->language()));
info += string_printf("%s\n %s Lv%" PRIu32 " %c\n",
name.c_str(),
name_for_char_class(player->disp.visual.char_class),
player->disp.stats.level + 1,
char_for_language_code(game_c->language()));
}
}
}
info += string_printf("%s %c %s %s\n",
abbreviation_for_episode(game->episode),
abbreviation_for_difficulty(game->difficulty),
abbreviation_for_mode(game->mode),
abbreviation_for_section_id(game->effective_section_id()));
// If page 1 is blank (there are no players) or we sent page 1 last
// time, send page 2 (extended info)
if (info.empty()) {
c->last_game_info_requested = 0;
info += string_printf("Section ID: %s\n", name_for_section_id(game->effective_section_id()));
if (game->max_level != 0xFFFFFFFF) {
info += string_printf("Req. level: %" PRIu32 "-%" PRIu32 "\n", game->min_level + 1, game->max_level + 1);
} else if (game->min_level != 0) {
info += string_printf("Req. level: %" PRIu32 "+\n", game->min_level + 1);
}
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
info += string_printf("%s\n", name_for_enum(game->base_version));
}
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
vector<const char*> flags_tokens;
string quest_name;
if (game->check_flag(Lobby::Flag::CHEATS_ENABLED)) {
flags_tokens.emplace_back("$C6C$C7");
info += "$C6Cheats enabled$C7\n";
}
if (game->check_flag(Lobby::Flag::PERSISTENT)) {
flags_tokens.emplace_back("$C6P$C7");
}
if (!game->password.empty()) {
flags_tokens.emplace_back("$C4L$C7");
}
if (game->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) {
flags_tokens.emplace_back("$C8ST$C7");
}
if (game->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
flags_tokens.emplace_back("$C8NS$C7");
}
if (game->quest) {
flags_tokens.emplace_back(game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) ? "$C3JQ$C7" : "$C3Q$C7");
quest_name = remove_color(game->quest->name);
} else if (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) {
flags_tokens.emplace_back("$C3JQ$C7");
} else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
flags_tokens.emplace_back("$C3Q$C7");
} else if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
flags_tokens.emplace_back("$C3B$C7");
}
info += ("Flags: " + join(flags_tokens, ",") + "\n");
if (!quest_name.empty()) {
info += ("Q: $C6" + quest_name + "$C7\n");
}
info += string_printf("Version: %s\n", name_for_enum(game->base_version));
} else {
bool cheats_enabled = game->check_flag(Lobby::Flag::CHEATS_ENABLED);
bool locked = !game->password.empty();
if (cheats_enabled && locked) {
info += "$C4Locked$C7, $C6cheats on$C7\n";
} else if (cheats_enabled) {
info += "$C6Cheats on$C7\n";
} else if (locked) {
info += "$C4Locked$C7\n";
info += "$C6Persistence enabled$C7\n";
}
if (game->quest) {
@@ -1965,15 +1936,26 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
info += "$C6Quest in progress\n";
} else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
info += "$C4Quest in progress\n";
} else if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
info += "$C4Battle in progress\n";
}
if (game->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
info += "$C4View Battle forbidden\n";
switch (game->drop_mode) {
case Lobby::DropMode::DISABLED:
info += "$C6Drops disabled$C7\n";
break;
case Lobby::DropMode::CLIENT:
info += "$C6Client drops$C7\n";
break;
case Lobby::DropMode::SERVER_SHARED:
info += "$C6Server drops$C7\n";
break;
case Lobby::DropMode::SERVER_PRIVATE:
info += "$C6Private drops$C7\n";
break;
case Lobby::DropMode::SERVER_DUPLICATE:
info += "$C6Duplicate drops$C7\n";
break;
}
}
strip_trailing_whitespace(info);
send_ship_info(c, info);
}
@@ -2500,7 +2482,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto s = c->require_server_state();
auto game = s->find_lobby(item_id);
if (!game) {
send_lobby_message_box(c, "$C6You cannot join this\ngame because it no\nlonger exists.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because it no\nlonger exists.");
break;
}
switch (game->join_error_for_client(c, &password)) {
@@ -2522,37 +2504,43 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
break;
case Lobby::JoinError::FULL:
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\nfull.");
break;
case Lobby::JoinError::VERSION_CONFLICT:
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
break;
case Lobby::JoinError::QUEST_IN_PROGRESS:
send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because a\nquest is already\nin progress.");
break;
case Lobby::JoinError::BATTLE_IN_PROGRESS:
send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because a\nbattle is already\nin progress.");
break;
case Lobby::JoinError::LOADING:
send_lobby_message_box(c, "$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
break;
case Lobby::JoinError::SOLO:
send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\na Solo Mode game.");
send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\na Solo Mode game.");
break;
case Lobby::JoinError::INCORRECT_PASSWORD:
send_lobby_message_box(c, "$C6Incorrect password.");
send_lobby_message_box(c, "$C7Incorrect password.");
break;
case Lobby::JoinError::LEVEL_TOO_LOW:
send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame.");
case Lobby::JoinError::LEVEL_TOO_LOW: {
string msg = string_printf("$C7You must be level\n%zu or above to\njoin this game.",
static_cast<size_t>(game->min_level + 1));
send_lobby_message_box(c, msg);
break;
case Lobby::JoinError::LEVEL_TOO_HIGH:
send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame.");
}
case Lobby::JoinError::LEVEL_TOO_HIGH: {
string msg = string_printf("$C7You must be level\n%zu or below to\njoin this game.",
static_cast<size_t>(game->max_level + 1));
send_lobby_message_box(c, msg);
break;
}
case Lobby::JoinError::NO_ACCESS_TO_QUEST:
send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game, or there\nis no space for another\nplayer in the quest.");
send_lobby_message_box(c, "$C7You don't have access\nto the quest in progress\nin this game, or there\nis no space for another\nplayer in the quest.");
break;
default:
send_lobby_message_box(c, "$C6You cannot join this\ngame.");
send_lobby_message_box(c, "$C7You cannot join this\ngame.");
break;
}
break;
@@ -2562,7 +2550,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto s = c->require_server_state();
auto quest_index = s->quest_index(c->version());
if (!quest_index) {
send_lobby_message_box(c, "$C6Quests are not available.");
send_lobby_message_box(c, "$C7Quests are not available.");
break;
}
@@ -2583,12 +2571,12 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto s = c->require_server_state();
auto quest_index = s->quest_index(c->version());
if (!quest_index) {
send_lobby_message_box(c, "$C6Quests are not\navailable.");
send_lobby_message_box(c, "$C7Quests are not\navailable.");
break;
}
auto q = quest_index->get(item_id);
if (!q) {
send_lobby_message_box(c, "$C6Quest does not exist.");
send_lobby_message_box(c, "$C7Quest does not exist.");
break;
}
@@ -2596,21 +2584,21 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
// Otherwise, they must be in a game to load a quest.
auto l = c->lobby.lock();
if (l && !l->is_game()) {
send_lobby_message_box(c, "$C6Quests cannot be\nloaded in lobbies.");
send_lobby_message_box(c, "$C7Quests cannot be\nloaded in lobbies.");
break;
}
if (l) {
if (q->episode == Episode::EP3) {
send_lobby_message_box(c, "$C6Episode 3 quests\ncannot be loaded\nvia this interface.");
send_lobby_message_box(c, "$C7Episode 3 quests\ncannot be loaded\nvia this interface.");
break;
}
if (l->quest) {
send_lobby_message_box(c, "$C6A quest is already\nin progress.");
send_lobby_message_box(c, "$C7A quest is already\nin progress.");
break;
}
if (l->quest_include_condition()(q) != QuestIndex::IncludeState::AVAILABLE) {
send_lobby_message_box(c, "$C6This quest has not\nbeen unlocked for\nall players in this\ngame.");
send_lobby_message_box(c, "$C7This quest has not\nbeen unlocked for\nall players in this\ngame.");
break;
}
set_lobby_quest(l, q);
@@ -2618,7 +2606,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
auto vq = q->version(c->version(), c->language());
if (!vq) {
send_lobby_message_box(c, "$C6Quest does not exist\nfor this game version.");
send_lobby_message_box(c, "$C7Quest does not exist\nfor this game version.");
break;
}
// Episode 3 uses the download quest commands (A6/A7) but does not
@@ -2699,7 +2687,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
throw runtime_error("non-Episode 3 client attempted to join tournament");
}
if (c->ep3_tournament_team.lock()) {
send_lobby_message_box(c, "$C6You are registered\nin a different\ntournament already");
send_lobby_message_box(c, "$C7You are registered\nin a different\ntournament already");
break;
}
if (team_name.empty()) {
@@ -2899,14 +2887,14 @@ static void on_A2(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
auto l = c->lobby.lock();
if (!l || !l->is_game()) {
send_lobby_message_box(c, "$C6Quests are not available\nin lobbies.");
send_lobby_message_box(c, "$C7Quests are not available\nin lobbies.");
return;
}
// In Episode 3, there are no quest categories, so skip directly to the quest
// filter menu.
if (is_ep3(c->version())) {
send_lobby_message_box(c, "$C6Episode 3 does not\nprovide online quests\nvia this interface.");
send_lobby_message_box(c, "$C7Episode 3 does not\nprovide online quests\nvia this interface.");
} else {
QuestMenuType menu_type;
if ((c->version() == Version::BB_V4) && flag) {
@@ -3288,7 +3276,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
bb_player->choice_search_config = player->choice_search_config;
try {
Client::save_character_file(filename, c->system_file(), bb_player);
send_text_message(c, "$C6Character data saved");
send_text_message(c, "$C7Character data saved");
} catch (const exception& e) {
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
}
@@ -3871,19 +3859,18 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
auto& result = results.emplace_back();
result.guild_card_number = lc->license->serial_number;
result.name.encode(lp->disp.name.decode(lc->language()), c->language());
string info_string = string_printf("%s Lv%zu %s",
string info_string = string_printf("%s Lv%zu\n%s\n",
name_for_char_class(lp->disp.visual.char_class),
static_cast<size_t>(lp->disp.stats.level + 1),
name_for_section_id(lp->disp.visual.section_id));
abbreviation_for_section_id(lp->disp.visual.section_id));
result.info_string.encode(info_string, c->language());
string lobby_name = l->is_game() ? l->name : string_printf("BLOCK01-%02" PRIu32, l->lobby_id);
string location_string;
if (l->is_game()) {
location_string = string_printf("%s,BLOCK01,%s", l->name.c_str(), s->name.c_str());
location_string = string_printf("%s,,BLOCK01,%s", l->name.c_str(), s->name.c_str());
} else if (l->is_ep3()) {
location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", l->lobby_id - 15, s->name.c_str());
location_string = string_printf("BLOCK01-C%02" PRIu32 ",,BLOCK01,%s", l->lobby_id - 15, s->name.c_str());
} else {
location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", l->lobby_id, s->name.c_str());
location_string = string_printf("BLOCK01-%02" PRIu32 ",,BLOCK01,%s", l->lobby_id, s->name.c_str());
}
result.location_string.encode(location_string, c->language());
result.reconnect_command_header.command = 0x19;
@@ -3894,6 +3881,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
result.meet_user.lobby_refs[0].menu_id = MenuID::LOBBY;
result.meet_user.lobby_refs[0].item_id = l->lobby_id;
result.meet_user.player_name.encode(lp->disp.name.decode(lc->language()), c->language());
// The client can only handle 32 results
if (results.size() >= 0x20) {
break;
}
@@ -3901,7 +3889,21 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
}
}
send_command_vt(c, 0xC4, results.size(), results);
if (results.empty()) {
// There is a client bug that causes garbage to appear in the info window
// when the server returns no entries in this command, since the client
// tries to display the first entry in the list even if the list contains
// "No player". If the server sends no entries at all, the entry will
// uninitialized memory which can cause crashes on v2, so we send a blank
// entry to prevent this.
auto& result = results.emplace_back();
result.reconnect_command_header.command = 0x00;
result.reconnect_command_header.flag = 0x00;
result.reconnect_command_header.size = 0x0000;
send_command_vt(c, 0xC4, 0, results);
} else {
send_command_vt(c, 0xC4, results.size(), results);
}
}
static void on_C3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
@@ -4107,7 +4109,8 @@ shared_ptr<Lobby> create_game_generic(
if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might
// actually encounter while playing the game normally
send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty");
string msg = string_printf("You must be level %zu\nor above to play\nthis difficulty.", static_cast<size_t>(min_level + 1));
send_lobby_message_box(c, msg);
return nullptr;
}
@@ -4319,6 +4322,40 @@ shared_ptr<Lobby> create_game_generic(
game->quest_flag_values = make_unique<QuestFlags>();
game->quest_flags_known = make_unique<QuestFlags>();
}
if (s->unlock_all_areas) {
static const vector<uint16_t> flags_ep1_v123 = {0x0017, 0x0020, 0x002A};
static const vector<uint16_t> flags_ep1_v4 = {0x01F9, 0x0201, 0x0207};
static const vector<uint16_t> flags_ep2_v123 = {0x004C, 0x004F, 0x0052};
static const vector<uint16_t> flags_ep2_v4 = {0x021B, 0x0225, 0x022F};
static const vector<uint16_t> flags_ep4_v4 = {0x02BD, 0x02BE, 0x02BF, 0x02C0, 0x02C1};
const vector<uint16_t>* flags_to_enable;
switch (game->episode) {
case Episode::EP1:
flags_to_enable = is_v4(game->base_version) ? &flags_ep1_v4 : &flags_ep1_v123;
break;
case Episode::EP2:
flags_to_enable = is_v4(game->base_version) ? &flags_ep2_v4 : &flags_ep2_v123;
break;
case Episode::EP4:
flags_to_enable = &flags_ep1_v4;
break;
default:
flags_to_enable = nullptr;
}
if (flags_to_enable) {
for (uint16_t flag_num : *flags_to_enable) {
game->quest_flag_values->set(game->difficulty, flag_num);
if (game->quest_flags_known) {
game->quest_flags_known->set(game->difficulty, flag_num);
}
}
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
}
}
game->switch_flags = make_unique<SwitchFlags>();
return game;
@@ -4389,11 +4426,11 @@ static void on_0C_C1_E7_EC(shared_ptr<Client> c, uint16_t command, uint32_t, str
}
watched_lobby = s->find_lobby(cmd.item_id);
if (!watched_lobby) {
send_lobby_message_box(c, "$C6This game no longer\nexists");
send_lobby_message_box(c, "$C7This game no longer\nexists");
return;
}
if (watched_lobby->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) {
send_lobby_message_box(c, "$C6This game does not\nallow spectators");
send_lobby_message_box(c, "$C7This game does not\nallow spectators");
return;
}
}
@@ -4451,11 +4488,11 @@ static void on_C1_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
episode = Episode::EP4;
// Disallow battle/challenge in Ep4
if (mode == GameMode::BATTLE) {
send_lobby_message_box(c, "$C6Episode 4 does not\nsupport Battle Mode.");
send_lobby_message_box(c, "$C7Episode 4 does not\nsupport Battle Mode.");
return;
}
if (mode == GameMode::CHALLENGE) {
send_lobby_message_box(c, "$C6Episode 4 does not\nsupport Challenge Mode.");
send_lobby_message_box(c, "$C7Episode 4 does not\nsupport Challenge Mode.");
return;
}
break;
@@ -4515,7 +4552,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
if (c) {
c->ep3_prev_battle_record = l->battle_record;
if ((s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
send_text_message(l, "$C6Recording complete");
send_text_message(l, "$C7Recording complete");
}
}
}
+4 -2
View File
@@ -644,9 +644,11 @@ static void on_sync_joining_player_quest_flags_t(shared_ptr<Client> c, uint8_t c
static void on_sync_joining_player_quest_flags(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
if (is_v1(c->version())) {
on_sync_joining_player_quest_flags_t<G_SetQuestFlagsV1_6x6F>(c, command, flag, data, size);
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_DCv1_6x6F>(c, command, flag, data, size);
} else if (!is_v4(c->version())) {
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_V2_V3_6x6F>(c, command, flag, data, size);
} else {
on_sync_joining_player_quest_flags_t<G_SetQuestFlagsV2V3V4_6x6F>(c, command, flag, data, size);
on_sync_joining_player_quest_flags_t<G_SetQuestFlags_BB_6x6F>(c, command, flag, data, size);
}
}
+132
View File
@@ -1,5 +1,6 @@
#include "SaveFileFormats.hh"
#include <phosg/Hash.hh>
#include <stdexcept>
#include <string>
@@ -355,6 +356,31 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
for (size_t z = 0; z < initial_items.size(); z++) {
ret->inventory.items[z] = initial_items[z];
}
// Set mag color based on initial costume
static const array<array<uint8_t, 25>, 12> mag_colors = {{
{0x09, 0x01, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x0B, 0x05, 0x00, 0x07, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x08, 0x11, 0x0D, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x01, 0x02, 0x11, 0x04, 0x0E, 0x06, 0x01, 0x0E, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x04, 0x11, 0x0D, 0x01, 0x0B, 0x11, 0x0D, 0x05, 0x06},
{0x00, 0x01, 0x0B, 0x11, 0x04, 0x05, 0x06, 0x0F, 0x05, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x0F, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x01, 0x0B, 0x11, 0x0A, 0x05, 0x06, 0x06, 0x09, 0x09, 0x01, 0x02, 0x11, 0x0A, 0x0E, 0x06, 0x01, 0x04, 0x0D, 0x07, 0x01, 0x0C, 0x0A, 0x05, 0x06},
{0x10, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x0A, 0x00, 0x07, 0x00, 0x01, 0x08, 0x11, 0x04, 0x09, 0x0F, 0x0D, 0x02, 0x0A, 0x07, 0x02, 0x0C, 0x04, 0x0E, 0x0E},
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x07, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x0D, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x00, 0x11, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x04, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x10, 0x05, 0x09, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x0F, 0x0A, 0x04, 0x0D, 0x01, 0x08, 0x11, 0x04, 0x05, 0x0F, 0x05, 0x10, 0x10, 0x07, 0x02, 0x0B, 0x0A, 0x0A, 0x0F},
{0x00, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0D, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x09, 0x0C, 0x00, 0x01, 0x02, 0x11, 0x0D, 0x05, 0x10, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}};
uint8_t char_class = (visual.char_class > 0x0B) ? 0 : visual.char_class;
uint8_t mag_color_index;
if (char_class == 2 || char_class == 4 || char_class == 5 || char_class == 9) {
mag_color_index = (visual.skin >= 25) ? 0 : visual.skin.load();
} else {
mag_color_index = (visual.costume >= 18) ? 0 : visual.costume.load();
}
ret->inventory.items[2].data.data2[3] = mag_colors.at(char_class).at(mag_color_index);
ret->inventory.items[13].extension_data2 = 1;
const auto& config = (ret->disp.visual.class_flags & 0x80) ? config_force : config_hunter_ranger;
@@ -633,3 +659,109 @@ const array<uint8_t, 0x0038> PSOBBBaseSystemFile::DEFAULT_JOYSTICK_CONFIG = {
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
static uint16_t crc16(const void* data, size_t size) {
static const uint16_t table[0x100] = {
// clang-format off
/* 00 */ 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
/* 08 */ 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
/* 10 */ 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
/* 18 */ 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
/* 20 */ 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
/* 28 */ 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
/* 30 */ 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
/* 38 */ 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
/* 40 */ 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
/* 48 */ 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
/* 50 */ 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
/* 58 */ 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
/* 60 */ 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
/* 68 */ 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
/* 70 */ 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
/* 78 */ 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
/* 80 */ 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
/* 88 */ 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
/* 90 */ 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
/* 98 */ 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
/* A0 */ 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
/* A8 */ 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
/* B0 */ 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
/* B8 */ 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
/* C0 */ 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
/* C8 */ 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
/* D0 */ 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
/* D8 */ 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
/* E0 */ 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
/* E8 */ 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
/* F0 */ 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
/* F8 */ 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78,
// clang-format on
};
uint16_t ret = 0xFFFF;
StringReader r(data, size);
while (!r.eof()) {
ret = (ret >> 8) ^ table[r.get_u8() ^ (ret & 0xFF)];
}
return ret ^ 0xFFFF;
}
string encode_psobb_hangame_credentials(const string& user_id, const string& token, const string& unused) {
if (user_id.size() < 4) {
throw runtime_error("user_id must be at least 4 characters");
}
if (user_id.size() > 12) {
throw runtime_error("user_id must be at most 12 characters");
}
if (!ends_with(user_id, "@HG")) {
throw runtime_error("user_id must end with \"@HG\"");
}
if (token.empty()) {
throw runtime_error("token must not be empty");
}
if (token.size() > 8) {
throw runtime_error("token must be at most 8 characters");
}
for (char ch : token) {
if (!isdigit(ch)) {
throw runtime_error("token must contain only decimal digits");
}
}
if (unused.size() > 0xFF) {
throw runtime_error("unused must be at most 255 characters");
}
// The encoded format is:
// parray<uint8_t, 4> mask_key; // xor this with all bytes starting with checksum
// le_uint16_t checksum; // crc16(&unused, EOF - &unused)
// uint8_t unused;
// uint8_t user_id_size;
// char user_id[user_id_size]; // Length must be in [4, 12] and must end with "@HG"
// uint8_t token_size;
// char token[token_size]; // Length must be in [1, 8] and must be all decimal digits
// uint8_t unused_size;
// char unused[unused_size]; // Ignored (possibly email address?)
// We'll fill in mask_key and checksum after all the other fields.
string data(7, '\0'); // mask_key, checksum, unused
data.push_back(user_id.size());
data += user_id;
data.push_back(token.size());
data += token;
data.push_back(unused.size());
data += unused;
uint16_t checksum = crc16(data.data() + 6, data.size() - 6);
uint32_t timestamp = time(nullptr);
data[0] = (timestamp & 0xFF);
data[1] = ((timestamp >> 8) & 0xFF);
data[2] = ((timestamp >> 16) & 0xFF);
data[3] = ((timestamp >> 24) & 0xFF);
data[4] = checksum & 0xFF;
data[5] = (checksum >> 8) & 0xFF;
for (size_t z = 0; z < data.size() - 4; z++) {
data[z + 4] ^= data[z % 3];
}
return data;
}
+4 -1
View File
@@ -360,7 +360,8 @@ struct PSOGCCharacterFile {
/* 25DC:21C0 */ parray<uint8_t, 4> unknown_a2;
/* 25E0:21C4 */ PlayerRecordsV3_Challenge<true> challenge_records;
/* 26E0:22C4 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
/* 2708:22EC */ parray<uint8_t, 0x28> unknown_a6;
/* 2708:22EC */ ChoiceSearchConfig choice_search_config;
/* 2720:2304 */ parray<uint8_t, 0x10> unknown_a6;
/* 2730:2314 */ parray<be_uint32_t, 0x10> quest_counters;
/* 2770:2354 */ PlayerRecords_Battle<true> offline_battle_records;
/* 2788:236C */ parray<uint8_t, 4> unknown_f5;
@@ -788,3 +789,5 @@ struct LegacySavedAccountDataBB { // .nsa file format
/* F060 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> team_name;
/* F080 */
} __attribute__((packed));
std::string encode_psobb_hangame_credentials(const std::string& user_id, const std::string& token, const std::string& unused = "");
+11 -7
View File
@@ -1113,11 +1113,11 @@ void send_card_search_result_t(
string location_string;
if (result_lobby->is_game()) {
location_string = string_printf("%s,BLOCK01,%s", result_lobby->name.c_str(), s->name.c_str());
location_string = string_printf("%s,,BLOCK01,%s", result_lobby->name.c_str(), s->name.c_str());
} else if (result_lobby->is_ep3()) {
location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id - 15, s->name.c_str());
location_string = string_printf("BLOCK01-C%02" PRIu32 ",,BLOCK01,%s", result_lobby->lobby_id - 15, s->name.c_str());
} else {
location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id, s->name.c_str());
location_string = string_printf("BLOCK01-%02" PRIu32 ",,BLOCK01,%s", result_lobby->lobby_id, s->name.c_str());
}
cmd.location_string.encode(location_string, c->language());
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
@@ -2709,10 +2709,14 @@ void send_game_flag_state_t(shared_ptr<Client> c) {
void send_game_flag_state(shared_ptr<Client> c) {
// DC NTE and 11/2000 don't have this command at all; v1 has it but it doesn't
// include flags for Ultimate.
if (!is_v1(c->version())) {
send_game_flag_state_t<G_SetQuestFlagsV2V3V4_6x6F>(c);
} else if (!is_pre_v1(c->version())) {
send_game_flag_state_t<G_SetQuestFlagsV1_6x6F>(c);
if (is_pre_v1(c->version())) {
return;
} else if (is_v1(c->version())) {
send_game_flag_state_t<G_SetQuestFlags_DCv1_6x6F>(c);
} else if (!is_v4(c->version())) {
send_game_flag_state_t<G_SetQuestFlags_V2_V3_6x6F>(c);
} else {
send_game_flag_state_t<G_SetQuestFlags_BB_6x6F>(c);
}
}
+2 -1
View File
@@ -758,7 +758,7 @@ void ServerState::load_config_early() {
}
this->ep3_final_round_meseta_bonus = this->config_json->get_int("Episode3FinalRoundMesetaBonus", 300);
this->ep3_jukebox_is_free = this->config_json->get_bool("Episode3JukeboxIsFree", false);
this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", false);
this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", 0);
this->ep3_card_auction_points = this->config_json->get_int("CardAuctionPoints", 0);
this->hide_download_commands = this->config_json->get_bool("HideDownloadCommands", true);
this->proxy_allow_save_files = this->config_json->get_bool("ProxyAllowSaveFiles", true);
@@ -890,6 +890,7 @@ void ServerState::load_config_early() {
this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true);
this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true);
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
this->unlock_all_areas = this->config_json->get_bool("UnlockAllAreas", false);
this->version_name_colors.reset();
try {
+1
View File
@@ -91,6 +91,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true;
bool enable_chat_commands = true;
bool unlock_all_areas = false;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
+7
View File
@@ -87,6 +87,7 @@
"pc": [9300, "pc", "login_server"],
"pc-patch": [10000, "patch", "patch_server_pc"],
"bb-patch": [11000, "patch", "patch_server_bb"],
"bb-patch-hg": [11200, "patch", "patch_server_bb"],
"bb-init": [12000, "bb", "login_server"],
// PSO Xbox tunnels its connections through the Xbox Live service (or, in
@@ -957,6 +958,12 @@
// available on the proxy server.
"CheatModeBehavior": "OnByDefault",
// Whether to unlock all areas by default in Ep1/2/4 games. If this is on,
// the Ragol warp in Pioneer 2 will allow access to all base areas (Forest 1,
// Cave 1, Mine 1, and Ruins 1 in Episode 1, for example) even if the player
// who created the game does not yet have access to those areas.
"UnlockAllAreas": false,
// Whether to enable rare drop notifications by default. Players can toggle
// this behavior for themselves with the $itemnotifs command.
"RareNotificationsEnabledByDefaultV1V2": false,
+3 -4
View File
@@ -1251,10 +1251,9 @@ I 91446 2023-12-31 21:06:28 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=
I 91446 2023-12-31 21:06:30 - [Commands] Received from C-4 (BBBBBBBBBBBB) (version=DC_NTE command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 91446 2023-12-31 21:06:30 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=11 flag=00)
0000 | 11 00 3C 00 00 00 00 00 00 00 00 00 31 3A 20 41 | < 1: A
0010 | 42 43 44 45 46 47 48 49 4A 4B 4C 20 52 41 63 74 | BCDEFGHIJKL RAct
0020 | 20 4C 31 0A 45 70 31 20 4E 20 4E 6D 6C 20 52 65 | L1 Ep1 N Nml Re
0030 | 64 0A 43 68 65 61 74 73 20 6F 6E 00 | d Cheats on
0000 | 11 00 28 00 00 00 00 00 00 00 00 00 41 42 43 44 | ( ABCD
0010 | 45 46 47 48 49 4A 4B 4C 0A 20 20 52 41 63 61 73 | EFGHIJKL RAcas
0020 | 74 20 4C 76 31 20 4A 00 | t Lv1 J
I 91446 2023-12-31 21:06:36 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=60 flag=00)
0000 | 60 00 1C 00 36 06 00 00 00 00 00 00 00 00 00 00 | ` 6
0010 | 5C 0F C6 43 00 00 00 00 C3 75 95 42 | \ C u B
+16 -20
View File
@@ -1170,19 +1170,17 @@ I 94381 2023-12-29 15:36:34 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 c
I 94381 2023-12-29 15:36:34 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 94381 2023-12-29 15:36:34 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
0040 | 00 00 00 00 |
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 4A 65 73 73 | $ Jess
0010 | 0A 20 20 52 41 6D 61 72 6C 20 4C 76 32 39 20 45 | RAmarl Lv29 E
0020 | 00 00 00 00 |
I 94381 2023-12-29 15:36:36 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 94381 2023-12-29 15:36:36 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
0040 | 00 00 00 00 |
0000 | 11 00 48 00 00 00 00 00 00 00 00 00 53 65 63 74 | H Sect
0010 | 69 6F 6E 20 49 44 3A 20 50 69 6E 6B 61 6C 0A 09 | ion ID: Pinkal
0020 | 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 64 | C6Cheats enabled
0030 | 09 43 37 0A 09 43 36 43 6C 69 65 6E 74 20 64 72 | C7 C6Client dr
0040 | 6F 70 73 09 43 37 00 00 | ops C7
I 94381 2023-12-29 15:36:44 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 00 00 | ` ?
0010 | 00 00 20 41 00 00 00 00 00 00 70 43 | A pC
@@ -1198,11 +1196,9 @@ I 94381 2023-12-29 15:36:44 - [Commands] Sending to C-2 (Jess) (version=GC_V3 co
I 94381 2023-12-29 15:36:45 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 94381 2023-12-29 15:36:45 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
0040 | 00 00 00 00 |
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 4A 65 73 73 | $ Jess
0010 | 0A 20 20 52 41 6D 61 72 6C 20 4C 76 32 39 20 45 | RAmarl Lv29 E
0020 | 00 00 00 00 |
I 94381 2023-12-29 15:36:45 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=99 flag=00)
0000 | 99 00 04 00 |
I 94381 2023-12-29 15:36:49 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=10 flag=00)
@@ -7967,11 +7963,11 @@ I 94381 2023-12-29 15:42:16 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 c
I 94381 2023-12-29 15:42:16 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 94381 2023-12-29 15:42:16 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=11 flag=00)
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
0010 | 43 36 4A 65 73 73 09 43 37 20 52 41 6D 6C 20 4C | C6Jess C7 RAml L
0020 | 32 39 0A 45 70 32 20 4E 20 4E 6D 6C 20 50 6E 6B | 29 Ep2 N Nml Pnk
0030 | 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 37 | C6Cheats on C7
0040 | 00 00 00 00 |
0000 | 11 00 48 00 00 00 00 00 00 00 00 00 53 65 63 74 | H Sect
0010 | 69 6F 6E 20 49 44 3A 20 50 69 6E 6B 61 6C 0A 09 | ion ID: Pinkal
0020 | 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 64 | C6Cheats enabled
0030 | 09 43 37 0A 09 43 36 50 72 69 76 61 74 65 20 64 | C7 C6Private d
0040 | 72 6F 70 73 09 43 37 00 | rops C7
I 94381 2023-12-29 15:42:18 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=10 flag=00)
0000 | 10 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 94381 2023-12-29 15:42:18 - [C-5] Assigned inventory item IDs
+3 -3
View File
@@ -4023,7 +4023,7 @@ I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC comma
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
0020 | 00 00 00 00 |
I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
0020 | 00 00 00 00 |
I 16332 2023-09-17 10:14:46 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
@@ -30338,7 +30338,7 @@ I 16332 2023-09-17 10:22:26 - [Commands] Received from C-2 (Tali) (version=GC co
I 16332 2023-09-17 10:22:26 - [Commands] Received from C-2 (Tali) (version=GC command=6F flag=00)
0000 | 6F 00 04 00 | o
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 63 6F 6D 70 6C 65 74 | ecording complet
0020 | 65 00 00 00 | e
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
@@ -30424,7 +30424,7 @@ I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC comma
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
0020 | 00 00 00 00 |
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
0020 | 00 00 00 00 |
I 16332 2023-09-17 10:22:26 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
@@ -4023,7 +4023,7 @@ I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC comma
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
0020 | 00 00 00 00 |
I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
0020 | 00 00 00 00 |
I 17097 2023-09-19 21:52:59 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
@@ -77175,7 +77175,7 @@ I 17097 2023-09-19 22:11:24 - [Commands] Received from C-2 (Tali) (version=GC co
I 17097 2023-09-19 22:11:24 - [Commands] Received from C-2 (Tali) (version=GC command=6F flag=00)
0000 | 6F 00 04 00 | o
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 63 6F 6D 70 6C 65 74 | ecording complet
0020 | 65 00 00 00 | e
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B1 flag=00)
@@ -77261,7 +77261,7 @@ I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC comma
0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60
0020 | 00 00 00 00 |
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R
0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 37 52 | $ C7R
0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled
0020 | 00 00 00 00 |
I 17097 2023-09-19 22:11:24 - [Commands] Sending to C-2 (Tali) (version=GC command=B0 flag=00)
+7 -3
View File
@@ -5281,9 +5281,13 @@ I 23921 2024-03-03 21:21:17 - [Commands] Received from C-2 (Jess) (version=GC_V3
I 23921 2024-03-03 21:21:19 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 23921 2024-03-03 21:21:19 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=11 flag=00)
0000 | 11 00 2C 00 00 00 00 00 00 00 00 00 45 70 32 20 | , Ep2
0010 | 4E 20 4E 6D 6C 20 56 69 72 0A 09 43 36 43 68 65 | N Nml Vir C6Che
0020 | 61 74 73 20 6F 6E 09 43 37 00 00 00 | ats on C7
0000 | 11 00 64 00 00 00 00 00 00 00 00 00 53 65 63 74 | d Sect
0010 | 69 6F 6E 20 49 44 3A 20 56 69 72 69 64 69 61 0A | ion ID: Viridia
0020 | 09 43 36 43 68 65 61 74 73 20 65 6E 61 62 6C 65 | C6Cheats enable
0030 | 64 09 43 37 0A 09 43 36 50 65 72 73 69 73 74 65 | d C7 C6Persiste
0040 | 6E 63 65 20 65 6E 61 62 6C 65 64 09 43 37 0A 09 | nce enabled C7
0050 | 43 36 43 6C 69 65 6E 74 20 64 72 6F 70 73 09 43 | C6Client drops C
0060 | 37 00 00 00 | 7
I 23921 2024-03-03 21:21:22 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
0000 | 60 00 10 00 52 03 00 00 00 00 00 00 00 80 FF FF | ` R
I 23921 2024-03-03 21:21:22 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
+3 -5
View File
@@ -839,11 +839,9 @@ I 97037 2023-12-29 15:57:03 - [Commands] Sending to C-5 (88888888) (version=DC_V
I 97037 2023-12-29 15:57:05 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=09 flag=00)
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 97037 2023-12-29 15:57:05 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=11 flag=00)
0000 | 11 00 44 00 00 00 00 00 00 00 00 00 31 3A 20 09 | D 1:
0010 | 43 36 54 61 6C 69 09 43 37 20 52 41 63 6C 20 4C | C6Tali C7 RAcl L
0020 | 31 38 35 0A 45 70 31 20 4E 20 4E 6D 6C 20 50 72 | 185 Ep1 N Nml Pr
0030 | 70 0A 09 43 36 43 68 65 61 74 73 20 6F 6E 09 43 | p C6Cheats on C
0040 | 37 00 00 00 | 7
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 54 61 6C 69 | $ Tali
0010 | 0A 20 20 52 41 63 61 73 65 61 6C 20 4C 76 31 38 | RAcaseal Lv18
0020 | 35 20 45 00 | 5 E
I 97037 2023-12-29 15:57:06 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=10 flag=00)
0000 | 10 00 0C 00 44 00 00 44 15 00 00 00 | D D
I 97037 2023-12-29 15:57:06 - [C-5] Assigned inventory item IDs
+2
View File
@@ -30,6 +30,7 @@
"DefaultDropModeV4Battle": "SERVER_SHARED",
"DefaultDropModeV4Challenge": "SERVER_SHARED",
"CheatModeBehavior": "OnByDefault",
"UnlockAllAreas": false,
"RareNotificationsEnabledByDefault": false,
"LocalAddress": "en0",
@@ -65,6 +66,7 @@
"xb": [9500, "xb", "login_server"],
"pc-patch": [10000, "patch", "patch_server_pc"],
"bb-patch": [11000, "patch", "patch_server_bb"],
"bb-patch-hg": [11200, "patch", "patch_server_bb"],
"bb-patch2": [11100, "patch", "patch_server_bb"],
"bb-patch3": [10500, "patch", "patch_server_bb"],
"bb-init": [12000, "bb", "login_server"],