add support for PSO DC v1/v2

This commit is contained in:
Martin Michelsen
2022-08-27 01:34:49 -07:00
parent 4abd91cb8f
commit 5d3d1e1900
30 changed files with 4004 additions and 585 deletions
+258 -143
View File
@@ -126,7 +126,7 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
} else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) {
if (c->version == GameVersion::BB) {
if (c->version() == GameVersion::BB) {
// This implicitly loads the client's account and player data
send_complete_player_bb(c);
}
@@ -161,14 +161,14 @@ static void set_console_client_flags(
shared_ptr<Client> c, uint32_t sub_version) {
if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) {
if (sub_version < 0x28) {
c->version = GameVersion::DC;
c->channel.version = GameVersion::DC;
c->log.info("Game version changed to DC");
} else if (c->version == GameVersion::GC) {
} else if (c->version() == GameVersion::GC) {
c->flags |= Client::Flag::GC_TRIAL_EDITION;
c->log.info("Trial edition flag set");
}
}
c->flags |= flags_for_version(c->version, sub_version);
c->flags |= flags_for_version(c->version(), sub_version);
}
void process_verify_license_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -212,6 +212,79 @@ void process_verify_license_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
}
void process_login_0_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 90
const auto& cmd = check_size_t<C_LoginV1_DC_PC_V3_90>(data);
c->channel.version = GameVersion::DC;
c->flags |= flags_for_version(c->version(), -1);
c->flags |= Client::Flag::DCV1;
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x90, 0x02);
} catch (const incorrect_access_key& e) {
send_command(c, 0x90, 0x03);
c->should_disconnect = true;
} catch (const missing_license& e) {
if (!s->allow_unregistered_users) {
send_command(c, 0x90, 0x03);
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
send_command(c, 0x90, 0x01);
}
}
}
void process_login_2_dc(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 92
check_size_t<C_RegisterV1_DC_92>(data);
send_command(c, 0x92, 0x01);
}
void process_login_3_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 93
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data,
sizeof(C_LoginV1_DC_93), sizeof(C_LoginExtendedV1_DC_93));
set_console_client_flags(c, cmd.sub_version);
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
c->set_license(l);
} catch (const incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
return;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
}
}
send_update_client_config(c);
process_login_complete(s, c);
}
void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 9A
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(data);
@@ -220,7 +293,8 @@ void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
switch (c->version) {
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
break;
@@ -252,11 +326,11 @@ void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
// license. So, if no license exists at this point, disconnect the client
// even if unregistered clients are allowed.
shared_ptr<License> l;
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
if ((c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) {
send_command(c, 0x9A, 0x04);
c->should_disconnect = true;
return;
} else if (c->version == GameVersion::PC) {
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
l = LicenseManager::create_license_pc(serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
@@ -271,12 +345,13 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 9C
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(data);
c->flags |= flags_for_version(c->version, cmd.sub_version);
c->flags |= flags_for_version(c->version(), cmd.sub_version);
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
switch (c->version) {
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
break;
@@ -305,7 +380,8 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
return;
} else {
shared_ptr<License> l;
switch (c->version) {
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = LicenseManager::create_license_pc(serial_number, cmd.access_key,
true);
@@ -327,14 +403,14 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
}
void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_login_d_e_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) { // 9D 9E
const C_Login_PC_GC_9D* base_cmd;
const C_Login_DC_PC_GC_9D* base_cmd;
if (command == 0x9D) {
base_cmd = &check_size_t<C_Login_PC_GC_9D>(data,
sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
base_cmd = &check_size_t<C_Login_DC_PC_GC_9D>(data,
sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
if (base_cmd->is_extended) {
const auto& cmd = check_size_t<C_LoginExtended_PC_GC_9D>(data);
const auto& cmd = check_size_t<C_LoginExtended_DC_PC_GC_9D>(data);
if (cmd.extension.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.extension.preferred_lobby_id;
}
@@ -351,7 +427,7 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
break;
case sizeof(C_Login_XB_9E):
case sizeof(C_LoginExtended_XB_9E):
c->version = GameVersion::XB;
c->channel.version = GameVersion::XB;
c->log.info("Game version set to XB");
break;
default:
@@ -384,7 +460,8 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
switch (c->version) {
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, base_cmd->access_key);
break;
@@ -415,11 +492,11 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
// license. So, if no license exists at this point, disconnect the client
// even if unregistered clients are allowed.
shared_ptr<License> l;
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
if ((c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) {
send_command(c, 0x04, 0x04);
c->should_disconnect = true;
return;
} else if (c->version == GameVersion::PC) {
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
l = LicenseManager::create_license_pc(serial_number, base_cmd->access_key, true);
s->license_manager->add(l);
c->set_license(l);
@@ -447,7 +524,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
throw runtime_error("invalid size for 93 command");
}
c->flags |= flags_for_version(c->version, -1);
c->flags |= flags_for_version(c->version(), -1);
try {
auto l = s->license_manager->verify_bb(cmd.username, cmd.password);
@@ -521,7 +598,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_return_client_config(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 9F
if (c->version == GameVersion::BB) {
if (c->version() == GameVersion::BB) {
const auto& cfg = check_size_t<ClientConfigBB>(data);
c->import_config(cfg);
} else {
@@ -552,7 +629,7 @@ void process_server_time_request(shared_ptr<ServerState> s, shared_ptr<Client> c
if (c->should_send_to_lobby_server) {
static const vector<string> version_to_port_name({
"console-lobby", "pc-lobby", "bb-lobby", "console-lobby", "console-lobby", "bb-lobby"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
}
@@ -691,7 +768,7 @@ void process_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client> c,
check_size_v(data.size(), 0);
if (c->flags & Client::Flag::IN_INFORMATION_MENU) {
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version));
*s->information_menu_for_version(c->version()));
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
@@ -718,7 +795,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
} else {
try {
// we use item_id + 1 here because "go back" is the first item
send_ship_info(c, s->information_menu_for_version(c->version)->at(cmd.item_id + 1).description.c_str());
send_ship_info(c, s->information_menu_for_version(c->version())->at(cmd.item_id + 1).description.c_str());
} catch (const out_of_range&) {
send_ship_info(c, u"$C4Missing information\nmenu item");
}
@@ -730,7 +807,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
send_ship_info(c, u"Return to the\nmain menu.");
} else {
try {
const auto& menu = s->proxy_destinations_menu_for_version(c->version);
const auto& menu = s->proxy_destinations_menu_for_version(c->version());
// we use item_id + 1 here because "go back" is the first item
send_ship_info(c, menu.at(cmd.item_id + 1).description.c_str());
} catch (const out_of_range&) {
@@ -750,7 +827,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id);
break;
}
auto q = s->quest_index->get(c->version, cmd.item_id);
auto q = s->quest_index->get(c->version(), cmd.item_id);
if (!q) {
send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id);
break;
@@ -797,10 +874,18 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
episode = 3;
}
string secid_str = name_for_section_id(game->section_id);
const char* mode_abbrev = "Nml";
if (game->flags & Lobby::Flag::BATTLE_MODE) {
mode_abbrev = "Btl";
} else if (game->flags & Lobby::Flag::CHALLENGE_MODE) {
mode_abbrev = "Chl";
} else if (game->flags & Lobby::Flag::SOLO_MODE) {
mode_abbrev = "Solo";
}
info += string_printf("Ep%d %c %s %s\n",
episode,
abbreviation_for_difficulty(game->difficulty),
abbreviation_for_game_mode(game->mode),
mode_abbrev,
secid_str.c_str());
bool cheats_enabled = game->flags & Lobby::Flag::CHEATS_ENABLED;
@@ -858,7 +943,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 10
bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB));
bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB));
uint32_t menu_id;
uint32_t item_id;
@@ -894,7 +979,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else {
static const vector<string> version_to_port_name({
"console-lobby", "pc-lobby", "bb-lobby", "console-lobby", "console-lobby", "bb-lobby"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
}
@@ -903,20 +988,20 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
case MainMenuItemID::INFORMATION:
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version));
*s->information_menu_for_version(c->version()));
c->flags |= Client::Flag::IN_INFORMATION_MENU;
break;
case MainMenuItemID::PROXY_DESTINATIONS:
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
s->proxy_destinations_menu_for_version(c->version));
s->proxy_destinations_menu_for_version(c->version()));
break;
case MainMenuItemID::DOWNLOAD_QUESTS:
if (c->flags & Client::Flag::EPISODE_3) {
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
auto quests = s->quest_index->filter(
c->version, false, QuestCategory::EPISODE_3);
c->version(), c->flags & Client::Flag::DCV1, QuestCategory::EPISODE_3);
if (quests.empty()) {
send_lobby_message_box(c, u"$C6There are no quests\navailable.");
} else {
@@ -977,7 +1062,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else {
const pair<string, uint16_t>* dest = nullptr;
try {
dest = &s->proxy_destinations_for_version(c->version).at(item_id);
dest = &s->proxy_destinations_for_version(c->version()).at(item_id);
} catch (const out_of_range&) { }
if (!dest) {
@@ -990,8 +1075,8 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
// license/char name/etc. for remote auth)
static const vector<string> version_to_port_name({
"console-proxy", "pc-proxy", "", "console-proxy", "console-proxy", "bb-proxy"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
"dc-proxy", "pc-proxy", "", "gc-proxy", "xb-proxy", "bb-proxy"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
uint16_t local_port = s->name_to_port_config.at(port_name)->port;
c->proxy_destination_address = resolve_ipv4(dest->first);
@@ -1000,7 +1085,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
s->proxy_server->delete_session(c->license->serial_number);
s->proxy_server->create_licensed_session(
c->license, local_port, c->version, c->export_config_bb());
c->license, local_port, c->version(), c->export_config_bb());
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
@@ -1022,8 +1107,9 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfull.");
break;
}
if ((game->version != c->version) ||
(!(game->flags & Lobby::Flag::EPISODE_3_ONLY) != !(c->flags & Client::Flag::EPISODE_3))) {
if ((game->version != c->version()) ||
(!(game->flags & Lobby::Flag::EPISODE_3_ONLY) != !(c->flags & Client::Flag::EPISODE_3)) ||
((game->flags & Lobby::Flag::DC_V2_ONLY) && (c->flags & Client::Flag::DCV1))) {
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
break;
}
@@ -1035,7 +1121,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_lobby_message_box(c, u"$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
break;
}
if (game->mode == 3) {
if (game->flags & Lobby::Flag::SOLO_MODE) {
send_lobby_message_box(c, u"$C6You cannot join this\n game because it is\na Solo Mode game.");
break;
}
@@ -1068,7 +1154,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
break;
}
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
auto quests = s->quest_index->filter(c->version,
auto quests = s->quest_index->filter(c->version(),
c->flags & Client::Flag::DCV1,
static_cast<QuestCategory>(item_id & 0xFF));
if (quests.empty()) {
@@ -1087,7 +1173,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_lobby_message_box(c, u"$C6Quests are not available.");
break;
}
auto q = s->quest_index->get(c->version, item_id);
auto q = s->quest_index->get(c->version(), item_id);
if (!q) {
send_lobby_message_box(c, u"$C6Quest does not exist.");
break;
@@ -1145,8 +1231,8 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
// (C->S 13 commands) like there are on GC. So, for PC/Trial clients,
// we can just not set the loading flag, since we never need to
// check/clear it later.
if ((l->clients[x]->version != GameVersion::DC) &&
(l->clients[x]->version != GameVersion::PC) &&
if ((l->clients[x]->version() != GameVersion::DC) &&
(l->clients[x]->version() != GameVersion::PC) &&
!(l->clients[x]->flags & Client::Flag::GC_TRIAL_EDITION)) {
l->clients[x]->flags |= Client::Flag::LOADING_QUEST;
}
@@ -1256,11 +1342,11 @@ void process_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_game_menu(c, s);
}
void process_information_menu_request_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 1F
void process_information_menu_request_dc_pc(shared_ptr<ServerState> s,
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) { // 1F
check_size_v(data.size(), 0);
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version), true);
*s->information_menu_for_version(c->version()), true);
}
void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -1281,8 +1367,8 @@ void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_message_box(c, u"");
static const vector<string> version_to_port_name({
"dc-login", "pc-login", "bb-patch", "gc-us3", "xb-login", "bb-init"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
"console-login", "pc-login", "bb-patch", "console-login", "console-login", "bb-init"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
@@ -1370,19 +1456,17 @@ void process_quest_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else {
vector<MenuItem>* menu = nullptr;
if ((c->version == GameVersion::BB) && flag) {
if ((c->version() == GameVersion::BB) && flag) {
menu = &quest_government_menu;
} else {
if (l->mode == 0) {
menu = &quest_categories_menu;
} else if (l->mode == 1) {
if (l->flags & Lobby::Flag::BATTLE_MODE) {
menu = &quest_battle_menu;
} else if (l->mode == 2) {
} else if (l->flags & Lobby::Flag::CHALLENGE_MODE) {
menu = &quest_challenge_menu;
} else if (l->mode == 3) {
} else if (l->flags & Lobby::Flag::SOLO_MODE) {
menu = &quest_solo_menu;
} else {
throw logic_error("no quest menu available for mode");
menu = &quest_categories_menu;
}
}
@@ -1428,7 +1512,7 @@ void process_quest_barrier(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_update_quest_statistics(shared_ptr<ServerState> s,
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) { // AA
const auto& cmd = check_size_t<C_UpdateQuestStatistics_AA>(data);
const auto& cmd = check_size_t<C_UpdateQuestStatistics_V3_BB_AA>(data);
if (c->flags & Client::Flag::GC_TRIAL_EDITION) {
throw runtime_error("trial edition client sent update quest stats command");
@@ -1440,7 +1524,7 @@ void process_update_quest_statistics(shared_ptr<ServerState> s,
return;
}
S_ConfirmUpdateQuestStatistics_AB response;
S_ConfirmUpdateQuestStatistics_V3_BB_AB response;
response.unknown_a1 = 0x0000;
response.unknown_a2 = 0x0000;
response.request_token = cmd.request_token;
@@ -1469,10 +1553,11 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
// Note: we add extra buffer on the end when checking sizes because the
// autoreply text is a variable length
switch (c->version) {
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC: {
const auto& pd = check_size_t<PSOPlayerDataPC>(data,
sizeof(PSOPlayerDataPC), 0xFFFF);
const auto& pd = check_size_t<PSOPlayerDataDCPC>(data,
sizeof(PSOPlayerDataDCPC), 0xFFFF);
c->game_data.import_player(pd);
break;
}
@@ -1950,25 +2035,29 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 81
u16string message;
uint32_t to_guild_card_number;
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
const auto& cmd = check_size_t<SC_SimpleMail_V3_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = decode_sjis(cmd.text);
} else if (c->version == GameVersion::PC) {
const auto& cmd = check_size_t<SC_SimpleMail_PC_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = cmd.text;
} else if (c->version == GameVersion::BB) {
const auto& cmd = check_size_t<SC_SimpleMail_BB_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = cmd.text;
} else {
// TODO
send_text_message(c, u"$C6Simple Mail is not\nsupported yet on\nthis platform.");
return;
switch (c->version()) {
case GameVersion::DC:
case GameVersion::GC:
case GameVersion::XB: {
const auto& cmd = check_size_t<SC_SimpleMail_DC_V3_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = decode_sjis(cmd.text);
break;
}
case GameVersion::PC: {
const auto& cmd = check_size_t<SC_SimpleMail_PC_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = cmd.text;
break;
}
case GameVersion::BB: {
const auto& cmd = check_size_t<SC_SimpleMail_BB_81>(data);
to_guild_card_number = cmd.to_guild_card_number;
message = cmd.text;
break;
}
default:
throw logic_error("invalid game version");
}
auto target = s->find_client(nullptr, to_guild_card_number);
@@ -2032,7 +2121,7 @@ void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C6
if (c->version == GameVersion::BB) {
if (c->version() == GameVersion::BB) {
const auto& cmd = check_size_t<C_SetBlockedSenders_BB_C6>(data);
c->game_data.account()->blocked_senders = cmd.blocked_senders;
} else {
@@ -2046,10 +2135,13 @@ void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client
////////////////////////////////////////////////////////////////////////////////
// Game commands
shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
shared_ptr<Client> c, const std::u16string& name,
const std::u16string& password, uint8_t episode, uint8_t difficulty,
uint8_t battle, uint8_t challenge, uint8_t solo) {
static shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
shared_ptr<Client> c,
const std::u16string& name,
const std::u16string& password,
uint8_t episode,
uint8_t difficulty,
uint32_t flags) {
// A player's actual level is their displayed level - 1, so the minimums for
// Episode 1 (for example) are actually 1, 20, 40, 80.
@@ -2058,13 +2150,13 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
{0, 29, 49, 89}, // episode 2
{0, 39, 79, 109}}; // episode 4
bool is_ep3 = (flags & Lobby::Flag::EPISODE_3_ONLY);
if (episode == 0) {
episode = 0xFF;
}
if (((episode != 0xFF) && (episode > 3)) || (episode == 0)) {
throw invalid_argument("incorrect episode number");
}
bool is_ep3 = (episode == 0xFF);
if (difficulty > 3) {
throw invalid_argument("incorrect difficulty level");
@@ -2081,25 +2173,16 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
throw invalid_argument("level too low for difficulty");
}
bool item_tracking_enabled = (c->version == GameVersion::BB) | s->item_tracking_enabled;
bool item_tracking_enabled = (c->version() == GameVersion::BB) | s->item_tracking_enabled;
shared_ptr<Lobby> game = s->create_lobby();
game->name = name;
game->password = password;
game->version = c->version;
game->version = c->version();
game->section_id = c->override_section_id >= 0
? c->override_section_id : c->game_data.player()->disp.section_id;
game->episode = episode;
game->difficulty = difficulty;
if (battle) {
game->mode = 1;
}
if (challenge) {
game->mode = 2;
}
if (solo) {
game->mode = 3;
}
if (c->override_random_seed >= 0) {
game->random_seed = c->override_random_seed;
game->random->seed(game->random_seed);
@@ -2112,7 +2195,8 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
game->flags =
(is_ep3 ? Lobby::Flag::EPISODE_3_ONLY : 0) |
(item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) |
Lobby::Flag::GAME;
Lobby::Flag::GAME |
flags;
game->min_level = min_level;
game->max_level = 0xFFFFFFFF;
@@ -2126,17 +2210,17 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
}
game->next_game_item_id = 0x00810000;
auto bp_subtable = s->battle_params->get_subtable(game->mode == 3,
game->episode - 1, game->difficulty);
bool is_solo = (game->flags & Lobby::Flag::SOLO_MODE);
auto bp_subtable = s->battle_params->get_subtable(
is_solo, game->episode - 1, game->difficulty);
generate_variations(
game->variations, game->random, game->episode, game->mode == 3);
generate_variations(game->variations, game->random, game->episode, is_solo);
for (size_t x = 0; x < 0x10; x++) {
try {
auto file = map_data_for_variation(
game->episode,
game->mode == 3,
is_solo,
x,
game->variations[x * 2 + 0],
game->variations[x * 2 + 1]);
@@ -2184,41 +2268,67 @@ void process_create_game_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C1
const auto& cmd = check_size_t<C_CreateGame_PC_C1>(data);
create_game_generic(s, c, cmd.name, cmd.password, 1,
cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0);
uint32_t flags = 0;
if (cmd.battle_mode) {
flags |= Lobby::Flag::BATTLE_MODE;
}
if (cmd.challenge_mode) {
flags |= Lobby::Flag::CHALLENGE_MODE;
}
create_game_generic(s, c, cmd.name, cmd.password, 1, cmd.difficulty, flags);
}
void process_create_game_dc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) { // C1 EC (EC Ep3 only)
const auto& cmd = check_size_t<C_CreateGame_DC_V3_C1_Ep3_EC>(data);
uint16_t command, uint32_t, const string& data) { // 0C C1 EC (EC Ep3 only)
const auto& cmd = check_size_t<C_CreateGame_DC_V3_0C_C1_Ep3_EC>(data);
// only allow EC from Ep3 clients
// Only allow EC from Ep3 clients
bool client_is_ep3 = c->flags & Client::Flag::EPISODE_3;
if ((command == 0xEC) && !client_is_ep3) {
return;
}
uint8_t episode = cmd.episode;
if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) {
uint32_t flags = 0;
if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
if (episode) {
flags |= Lobby::Flag::DC_V2_ONLY;
}
episode = 1;
}
if (client_is_ep3) {
} else if (client_is_ep3) {
flags |= Lobby::Flag::EPISODE_3_ONLY;
episode = 0xFF;
}
u16string name = decode_sjis(cmd.name);
u16string password = decode_sjis(cmd.password);
create_game_generic(s, c, name.c_str(), password.c_str(),
episode, cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0);
if (cmd.battle_mode) {
flags |= Lobby::Flag::BATTLE_MODE;
}
if (cmd.challenge_mode) {
flags |= Lobby::Flag::CHALLENGE_MODE;
}
create_game_generic(
s, c, name.c_str(), password.c_str(), episode, cmd.difficulty, flags);
}
void process_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C1
const auto& cmd = check_size_t<C_CreateGame_BB_C1>(data);
create_game_generic(s, c, cmd.name, cmd.password, cmd.episode,
cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, cmd.solo_mode);
uint32_t flags = 0;
if (cmd.battle_mode) {
flags |= Lobby::Flag::BATTLE_MODE;
}
if (cmd.challenge_mode) {
flags |= Lobby::Flag::CHALLENGE_MODE;
}
if (cmd.solo_mode) {
flags |= Lobby::Flag::SOLO_MODE;
}
create_game_generic(
s, c, cmd.name, cmd.password, cmd.episode, cmd.difficulty, flags);
}
void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -2237,8 +2347,7 @@ void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto l = s->find_lobby(c->lobby_id);
if (!l || !l->is_game()) {
// go home client; you're drunk
throw invalid_argument("ready command cannot be sent outside game");
throw runtime_error("client sent ready command ontside of game");
}
c->flags &= (~Client::Flag::LOADING);
@@ -2247,7 +2356,7 @@ void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
// Only get player info again on BB, since on other versions the returned info
// only includes items that would be saved if the client disconnects
// unexpectedly (that is, only equipped items are included).
if (c->version == GameVersion::BB) {
if (c->version() == GameVersion::BB) {
send_get_player_info(c);
}
}
@@ -2523,7 +2632,7 @@ void process_ignored_command(shared_ptr<ServerState>, shared_ptr<Client>,
void process_unimplemented_command(shared_ptr<ServerState>,
shared_ptr<Client> c, uint16_t command, uint32_t flag, const string& data) {
c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32 "\n",
c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32,
data.size(), command, flag);
throw invalid_argument("unimplemented command");
}
@@ -2541,13 +2650,13 @@ static process_command_t dc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr,
nullptr, process_ignored_command, process_chat_dc_v3, nullptr,
process_game_list_request, process_menu_item_info_request, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
process_create_game_dc_v3, nullptr, nullptr, nullptr,
// 10
process_menu_selection, nullptr, nullptr, process_ignored_command,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, process_ignored_command, nullptr, nullptr,
nullptr, process_ignored_command, nullptr, process_information_menu_request_dc_pc,
// 20
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
@@ -2567,7 +2676,7 @@ static process_command_t dc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
// 60
process_game_command, nullptr, process_game_command, nullptr,
process_game_command, process_player_data, process_game_command, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
process_game_command, process_game_command, nullptr, process_client_ready,
@@ -2583,15 +2692,15 @@ static process_command_t dc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr,
// 90
nullptr, nullptr, nullptr, nullptr,
process_login_0_dc_pc_v3, nullptr, process_login_2_dc, process_login_3_dc_pc_v3,
nullptr, nullptr, process_client_checksum, nullptr,
process_player_data, process_ignored_command, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, nullptr, nullptr,
// A0
process_change_ship, process_change_block, process_quest_list_request, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, process_ignored_command, process_update_quest_statistics, nullptr,
nullptr, process_ignored_command, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
// B0
@@ -2600,15 +2709,14 @@ static process_command_t dc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
// C0
nullptr, process_create_game_dc_v3, nullptr, nullptr,
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char>,
process_disable_auto_reply, nullptr, nullptr, nullptr,
process_choice_search, process_create_game_dc_v3, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
// D0
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
process_info_board_request, process_write_info_board_t<char>, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
// E0
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
@@ -2630,7 +2738,7 @@ static process_command_t pc_handlers[0x100] = {
process_menu_selection, nullptr, nullptr, process_ignored_command,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, process_ignored_command, nullptr, process_information_menu_request_pc,
nullptr, process_ignored_command, nullptr, process_information_menu_request_dc_pc,
// 20
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
@@ -2669,7 +2777,7 @@ static process_command_t pc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, process_client_checksum, nullptr,
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, nullptr,
// A0
process_change_ship, process_change_block, process_quest_list_request, nullptr,
@@ -2750,10 +2858,10 @@ static process_command_t gc_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr,
// 90
nullptr, nullptr, nullptr, nullptr,
process_login_0_dc_pc_v3, nullptr, nullptr, process_login_3_dc_pc_v3,
nullptr, nullptr, process_client_checksum, nullptr,
process_player_data, process_ignored_command, nullptr, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, process_return_client_config,
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, process_return_client_config,
// A0
process_change_ship, process_change_block, process_quest_list_request, nullptr,
@@ -2841,7 +2949,7 @@ static process_command_t xb_handlers[0x100] = {
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, process_client_checksum, nullptr,
process_player_data, process_ignored_command, nullptr, nullptr,
process_login_c_dc_pc_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, process_return_client_config,
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, process_return_client_config,
// A0
process_change_ship, process_change_block, process_quest_list_request, nullptr,
@@ -3028,9 +3136,10 @@ static process_command_t* handlers[6] = {
void check_unlicensed_command(GameVersion version, uint8_t command) {
switch (version) {
case GameVersion::DC:
// TODO: Fix this when we support DC. It will be pretty obvious during
// testing that this should be fixed (and how to fix it).
throw runtime_error("no unlicensed commands are valid on DC");
// newserv doesn't actually know that DC clients are DC until it receives
// an appropriate login command (93, 9A, or 9D), but those commands also
// log the client in, so this case should never be executed.
throw logic_error("cannot check unlicensed command for DC client");
case GameVersion::PC:
if (command != 0x9A && command != 0x9D) {
throw runtime_error("only commands 9A and 9D may be sent before login");
@@ -3038,8 +3147,14 @@ void check_unlicensed_command(GameVersion version, uint8_t command) {
break;
case GameVersion::GC:
case GameVersion::XB:
if (command != 0xDB && command != 0x9A && command != 0x9E) {
throw runtime_error("only commands DB, 9A and 9E may be sent before login");
// See comment in the DC case above for why DC commands are included here.
if (command != 0x90 && // DC v1
command != 0x93 && // DC v1
command != 0x9A && // DC v2
command != 0x9D && // DC v2, GC trial edition
command != 0x9E && // GC non-trial
command != 0xDB) { // GC non-trial
throw runtime_error("only commands 90, 93, 9A, 9D, 9E, and DB may be sent before login");
}
break;
case GameVersion::BB:
@@ -3070,10 +3185,10 @@ void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
// is allowed to access normal functionality. This check prevents clients from
// sneakily sending commands to access functionality without logging in.
if (!c->license.get()) {
check_unlicensed_command(c->version, command);
check_unlicensed_command(c->version(), command);
}
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
auto fn = handlers[static_cast<size_t>(c->version())][command & 0xFF];
if (fn) {
fn(s, c, command, flag, data);
} else {