add proxy options menu

This commit is contained in:
Martin Michelsen
2022-09-29 11:08:35 -07:00
parent 7d950e01ab
commit 0951132c01
16 changed files with 222 additions and 54 deletions
+1
View File
@@ -40,6 +40,7 @@ Current known issues / missing features / things to do:
- Implement private and overflow lobbies.
- Enforce client-side size limits (e.g. for 60/62 commands) on the server side as well. (For 60/62 specifically, perhaps transform them to 6C/6D if needed.)
- Encapsulate BB server-side random state and make replays deterministic.
- The internal menu abstraction is ugly and hard to work with. Rewrite it.
## Compatibility
+16
View File
@@ -198,6 +198,21 @@ static void server_command_dbgid(shared_ptr<ServerState>, shared_ptr<Lobby>,
c->prefer_high_lobby_client_id ? "high" : "low");
}
static void server_command_proxygc(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
uint32_t proxy_remote_guild_card_number = stoll(encode_sjis(args), nullptr, 0);
client_options_cache.replace(
string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number),
string_printf("%08" PRIu32, proxy_remote_guild_card_number));
send_text_message_printf(c, "Proxy remote Guild\nCard number set to\n$C6%" PRIu32,
proxy_remote_guild_card_number);
}
static void proxy_command_proxygc(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
session.remote_guild_card_number = stoll(encode_sjis(args), nullptr, 0);
}
static void server_command_persist(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_privileges(c, Privilege::DEBUG);
@@ -919,6 +934,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"$next", {server_command_next, nullptr, u"Usage:\nnext"}},
{u"$password", {server_command_password, nullptr, u"Usage:\nlock [password]\nomit password to\nunlock game"}},
{u"$persist", {server_command_persist, nullptr, u"Usage:\npersist"}},
{u"$proxygc", {server_command_proxygc, proxy_command_proxygc, u"Usage:\nproxygc <gc#>"}},
{u"$rand", {server_command_rand, proxy_command_rand, u"Usage:\nrand [hex seed]\nomit seed to revert\nto default"}},
{u"$secid", {server_command_secid, proxy_command_secid, u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}},
{u"$silence", {server_command_silence, nullptr, u"Usage:\nsilence <name-or-number>"}},
+3
View File
@@ -19,6 +19,7 @@ using namespace std;
const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839;
FileContentsCache client_options_cache(3600 * 1000 * 1000);
static atomic<uint64_t> next_id(1);
@@ -59,6 +60,8 @@ Client::Client(
switch_assist(false),
can_chat(true),
pending_bb_save_player_index(0),
proxy_save_files(false),
proxy_suppress_remote_login(false),
dol_base_addr(0) {
this->last_switch_enabled_command.subcommand = 0;
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
+5
View File
@@ -6,6 +6,7 @@
#include "Channel.hh"
#include "CommandFormats.hh"
#include "FileContentsCache.hh"
#include "FunctionCompiler.hh"
#include "License.hh"
#include "PatchFileIndex.hh"
@@ -17,6 +18,7 @@
extern const uint64_t CLIENT_CONFIG_MAGIC;
extern FileContentsCache client_options_cache;
@@ -112,6 +114,9 @@ struct Client {
std::string pending_bb_save_username;
uint8_t pending_bb_save_player_index;
bool proxy_save_files;
bool proxy_suppress_remote_login;
// DOL file loading state
uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::DOLFile> loading_dol_file;
+4 -5
View File
@@ -620,11 +620,10 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 {
// 17 (S->C): Start encryption at login server (except on BB)
// Same format and usage as 02 command, but a different copyright string:
// "DreamCast Port Map. Copyright SEGA Enterprises. 1999"
// Unlike the 02 command, V3 clients will respond with a DB command the first
// time they receive a 17 command in any online session, with the exception of
// Episodes 1&2 trial edition (which responds with a 9A). After the first time,
// V3 clients will respond with a 9E. DCv1 will respond with a 90. Other non-V3
// clients will respond with a 9A or 9D.
// Unlike the 02 command, V3 clients will respond with a DB command when they
// receive a 17 command in any online session, with the exception of Episodes
// 1&2 trial edition (which responds with a 9A). DCv1 will respond with a 90.
// Other non-V3 clients will respond with a 9A or 9D.
// 18 (S->C): License verification result (PC/V3)
// Behaves exactly the same as 9A (S->C). No arguments except header.flag.
+1 -2
View File
@@ -621,9 +621,8 @@ int main(int argc, char** argv) {
use_terminal_colors = true;
}
shared_ptr<ServerState> state(new ServerState());
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
shared_ptr<ServerState> state(new ServerState());
config_log.info("Reading network addresses");
state->all_addresses = get_local_addresses();
-1
View File
@@ -4,7 +4,6 @@
#include <phosg/Strings.hh>
#include "Loggers.hh"
#include "FileContentsCache.hh"
using namespace std;
+15 -4
View File
@@ -22,6 +22,7 @@ namespace MenuID {
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
constexpr uint32_t PROGRAMS = 0x88000088;
constexpr uint32_t PATCHES = 0x99000099;
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
}
namespace MainMenuItemID {
@@ -37,19 +38,29 @@ namespace MainMenuItemID {
namespace InformationMenuItemID {
constexpr uint32_t GO_BACK = 0x22FFFF22;
};
}
namespace ProxyDestinationsMenuItemID {
constexpr uint32_t GO_BACK = 0x77FFFF77;
};
constexpr uint32_t OPTIONS = 0x77EEEE77;
}
namespace ProgramsMenuItemID {
constexpr uint32_t GO_BACK = 0x88FFFF88;
};
}
namespace PatchesMenuItemID {
constexpr uint32_t GO_BACK = 0x99FFFF99;
};
}
namespace ProxyOptionsMenuItemID {
constexpr uint32_t GO_BACK = 0xAAFFFFAA;
constexpr uint32_t INFINITE_HP = 0xAA1111AA;
constexpr uint32_t INFINITE_TP = 0xAA2222AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA3333AA;
constexpr uint32_t SAVE_FILES = 0xAA4444AA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAA5555AA;
}
+67 -31
View File
@@ -62,29 +62,6 @@ static void check_implemented_subcommand(
static void send_text_message_to_client(
ProxyServer::LinkedSession& session,
uint8_t command,
const std::string& message) {
StringWriter w;
w.put<SC_TextHeader_01_06_11_B0_EE>({0, 0});
if ((session.version == GameVersion::PC) ||
(session.version == GameVersion::BB)) {
auto decoded = decode_sjis(message);
w.write(decoded.data(), decoded.size() * sizeof(decoded[0]));
w.put_u16l(0);
} else {
w.write(message);
w.put_u8(0);
}
while (w.size() & 3) {
w.put_u8(0);
}
session.client_channel.send(command, 0x00, w.str());
}
// Command handlers. These are called to preprocess or react to specific
// commands in either direction. If they return true, the command (which the
// function may have modified) is forwarded to the other end; if they return
@@ -125,9 +102,26 @@ static HandlerResult on_server_97(shared_ptr<ServerState>,
return HandlerResult::Type::FORWARD;
}
static HandlerResult on_client_gc_9E(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (session.suppress_remote_login) {
le_uint64_t checksum = random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum));
S_UpdateClientConfig_DC_PC_V3_04 cmd = {0x00010000, session.license->serial_number, ClientConfig()};
session.client_channel.send(0x04, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
return HandlerResult::Type::FORWARD;
}
}
static HandlerResult on_server_gc_9A(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (!session.license) {
if (!session.license || session.suppress_remote_login) {
return HandlerResult::Type::FORWARD;
}
@@ -318,6 +312,39 @@ static HandlerResult on_server_dc_pc_v3_patch_02_17(
session.server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else if (session.suppress_remote_login) {
uint32_t guild_card_number;
if (session.remote_guild_card_number >= 0) {
guild_card_number = session.remote_guild_card_number;
log_info("Using Guild Card number %" PRIu32 " from session", guild_card_number);
} else {
guild_card_number = random_object<uint32_t>();
log_info("Using Guild Card number %" PRIu32 " from random generator", guild_card_number);
}
uint32_t fake_serial_number = random_object<uint32_t>() & 0x7FFFFFFF;
uint64_t fake_access_key = random_object<uint64_t>();
string fake_access_key_str = string_printf("00000000000%" PRIu64, fake_access_key);
if (fake_access_key_str.size() > 12) {
fake_access_key_str = fake_access_key_str.substr(fake_access_key_str.size() - 12);
}
C_LoginExtended_GC_9E cmd;
cmd.player_tag = 0x00010000;
cmd.guild_card_number = guild_card_number;
cmd.unused = 0;
cmd.sub_version = session.sub_version;
cmd.is_extended = 0;
cmd.language = session.language;
cmd.serial_number = string_printf("%08" PRIX32, fake_serial_number);
cmd.access_key = fake_access_key_str;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.name = session.character_name;
cmd.client_config.data = session.remote_client_config_data;
session.server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E));
return HandlerResult::Type::SUPPRESS;
} else {
// For command 02, send the same as if we had received 9A from the server
return on_server_gc_9A(s, session, command, flag, data);
@@ -389,6 +416,13 @@ static HandlerResult on_server_bb_03(shared_ptr<ServerState> s,
static HandlerResult on_server_dc_pc_v3_04(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Suppress extremely short commands from the server instead of disconnecting.
if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_V3_04, cfg)) {
le_uint64_t checksum = random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum));
return HandlerResult::Type::SUPPRESS;
}
// Some servers send a short 04 command if they don't use all of the 0x20
// bytes available. We should be prepared to handle that.
auto& cmd = check_size_t<S_UpdateClientConfig_DC_PC_V3_04>(data,
@@ -404,9 +438,10 @@ static HandlerResult on_server_dc_pc_v3_04(shared_ptr<ServerState>,
session.remote_guild_card_number = cmd.guild_card_number;
session.log.info("Remote guild card number set to %" PRId64,
session.remote_guild_card_number);
send_text_message_to_client(session, 0x11, string_printf(
string message = string_printf(
"The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64,
session.remote_guild_card_number));
session.remote_guild_card_number);
send_ship_info(session.client_channel, decode_sjis(message));
}
if (session.license) {
cmd.guild_card_number = session.license->serial_number;
@@ -431,10 +466,6 @@ static HandlerResult on_server_dc_pc_v3_04(shared_ptr<ServerState>,
// the first 04 command the client has received. The client responds with a 96
// (checksum) in that case.
if (!had_guild_card_number) {
// We don't actually have a client checksum, of course... hopefully just
// random data will do (probably no private servers check this at all)
// TODO: Presumably we can save these values from the client when they
// connected to newserv originally, but I'm too lazy to do this right now
le_uint64_t checksum = random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum));
}
@@ -918,6 +949,7 @@ static HandlerResult on_server_65_67_68(shared_ptr<ServerState>,
auto& cmd = check_size_t<CmdT>(data, expected_size, expected_size);
bool modified = false;
size_t num_replacements = 0;
session.lobby_client_id = cmd.client_id;
update_leader_id(session, cmd.leader_id);
for (size_t x = 0; x < flag; x++) {
@@ -927,6 +959,7 @@ static HandlerResult on_server_65_67_68(shared_ptr<ServerState>,
} else {
if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) {
cmd.entries[x].lobby_data.guild_card = session.license->serial_number;
num_replacements++;
modified = true;
}
session.lobby_players[index].guild_card_number = cmd.entries[x].lobby_data.guild_card;
@@ -938,6 +971,9 @@ static HandlerResult on_server_65_67_68(shared_ptr<ServerState>,
session.lobby_players[index].name.c_str());
}
}
if (num_replacements > 1) {
throw runtime_error("proxied player appears multiple times in lobby");
}
if (session.override_lobby_event >= 0) {
cmd.event = session.override_lobby_event;
@@ -2157,7 +2193,7 @@ static on_command_t handlers[6][0x100][2] = {
/* 9B */ {nullptr, nullptr},
/* 9C */ {nullptr, nullptr},
/* 9D */ {nullptr, nullptr},
/* 9E */ {nullptr, nullptr},
/* 9E */ {nullptr, on_client_gc_9E},
/* 9F */ {nullptr, nullptr},
// (GC) SERVER CLIENT
/* A0 */ {nullptr, on_client_dc_pc_v3_A0_A1},
+1
View File
@@ -480,6 +480,7 @@ ProxyServer::LinkedSession::LinkedSession(
infinite_hp(false),
infinite_tp(false),
save_files(false),
suppress_remote_login(false),
function_call_return_value(-1),
next_item_id(0x0F000000),
override_section_id(-1),
+1
View File
@@ -65,6 +65,7 @@ public:
bool infinite_hp;
bool infinite_tp;
bool save_files;
bool suppress_remote_login;
int64_t function_call_return_value; // -1 = don't block function calls
G_SwitchStateChanged_6x05 last_switch_enabled_command;
PlayerInventoryItem next_drop_item;
+105 -4
View File
@@ -875,18 +875,44 @@ static void on_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Clie
case MenuID::PROXY_DESTINATIONS:
if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) {
send_ship_info(c, u"Return to the\nmain menu.");
send_ship_info(c, u"Return to the\nmain menu");
} else if (cmd.item_id == ProxyDestinationsMenuItemID::OPTIONS) {
send_ship_info(c, u"Set proxy session\noptions");
} else {
try {
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());
// We use item_id + 2 here because "go back" and "options" are the
// first items
send_ship_info(c, menu.at(cmd.item_id + 2).description.c_str());
} catch (const out_of_range&) {
send_ship_info(c, u"$C4Missing proxy\ndestination");
}
}
break;
case MenuID::PROXY_OPTIONS:
switch (cmd.item_id) {
case ProxyOptionsMenuItemID::GO_BACK:
send_ship_info(c, u"Return to the\nproxy menu");
break;
case ProxyOptionsMenuItemID::INFINITE_HP:
send_ship_info(c, u"Enable or disable\ninfinite HP\n\nYou can change this\nduring the session\nwith the %sinfhp\ncommand");
break;
case ProxyOptionsMenuItemID::INFINITE_TP:
send_ship_info(c, u"Enable or disable\ninfinite TP\n\nYou can change this\nduring the session\nwith the %sinftp\ncommand");
break;
case ProxyOptionsMenuItemID::SWITCH_ASSIST:
send_ship_info(c, u"Enable or disable\nswitch assist\n\nYou can change this\nduring the session\nwith the %sswa\ncommand");
break;
case ProxyOptionsMenuItemID::SAVE_FILES:
send_ship_info(c, u"Enable or disable\nsaving of files from\nthe remote server\n(quests, etc.)");
break;
case ProxyOptionsMenuItemID::SUPPRESS_LOGIN:
send_ship_info(c, u"Enable or disable\nalternate login\nsequence");
break;
}
break;
case MenuID::QUEST_FILTER:
// Don't send anything here. The quest filter menu already has short
// descriptions included with the entries, which the client shows in the
@@ -1012,6 +1038,29 @@ static void on_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Clie
}
}
static vector<MenuItem> proxy_options_menu_for_client(
shared_ptr<const Client> c) {
vector<MenuItem> ret;
ret.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back",
u"Return to the\nproxy menu", 0);
ret.emplace_back(ProxyOptionsMenuItemID::INFINITE_HP,
c->infinite_hp ? u"Infinite HP ON" : u"Infinite HP OFF",
u"Enable or disable\ninfinite HP", 0);
ret.emplace_back(ProxyOptionsMenuItemID::INFINITE_TP,
c->infinite_tp ? u"Infinite TP ON" : u"Infinite TP OFF",
u"Enable or disable\ninfinite TP", 0);
ret.emplace_back(ProxyOptionsMenuItemID::SWITCH_ASSIST,
c->switch_assist ? u"Switch assist ON" : u"Switch assist OFF",
u"Enable or disable\nswitch assist", 0);
ret.emplace_back(ProxyOptionsMenuItemID::SAVE_FILES,
c->proxy_save_files ? u"Save files ON" : u"Save files OFF",
u"Enable or disable\nsaving of files from\nthe remote server\n(quests, etc.)", 0);
ret.emplace_back(ProxyOptionsMenuItemID::SUPPRESS_LOGIN,
c->proxy_suppress_remote_login ? u"Skip login ON" : u"Skip login OFF",
u"Enable or disable\nalternate login\nsequence", 0);
return ret;
}
static void on_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));
@@ -1066,6 +1115,13 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
case MainMenuItemID::PROXY_DESTINATIONS:
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
s->proxy_destinations_menu_for_version(c->version()));
try {
string key = string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number);
const auto& entry = client_options_cache.get_or_throw(key);
uint32_t proxy_remote_guild_card_number = stoul(entry->data, nullptr, 10);
string info_str = string_printf("Your remote Guild\nCard number is\noverridden as\n$C6%" PRIu32, proxy_remote_guild_card_number);
send_ship_info(c, decode_sjis(info_str));
} catch (const out_of_range&) { }
break;
case MainMenuItemID::DOWNLOAD_QUESTS:
@@ -1127,10 +1183,45 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
break;
}
case MenuID::PROXY_OPTIONS: {
switch (item_id) {
case ProxyOptionsMenuItemID::GO_BACK:
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
s->proxy_destinations_menu_for_version(c->version()));
break;
case ProxyOptionsMenuItemID::INFINITE_HP:
c->infinite_hp = !c->infinite_hp;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::INFINITE_TP:
c->infinite_tp = !c->infinite_tp;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::SWITCH_ASSIST:
c->switch_assist = !c->switch_assist;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::SAVE_FILES:
c->proxy_save_files = !c->proxy_save_files;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::SUPPRESS_LOGIN:
c->proxy_suppress_remote_login = !c->proxy_suppress_remote_login;
resend_proxy_options_menu:
send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS,
proxy_options_menu_for_client(c));
break;
default:
send_message_box(c, u"Incorrect menu item ID.");
break;
}
break;
}
case MenuID::PROXY_DESTINATIONS: {
if (item_id == ProxyDestinationsMenuItemID::GO_BACK) {
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
} else if (item_id == ProxyDestinationsMenuItemID::OPTIONS) {
send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS,
proxy_options_menu_for_client(c));
} else {
const pair<string, uint16_t>* dest = nullptr;
try {
@@ -1156,8 +1247,18 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_update_client_config(c);
s->proxy_server->delete_session(c->license->serial_number);
s->proxy_server->create_licensed_session(
auto session = s->proxy_server->create_licensed_session(
c->license, local_port, c->version(), c->export_config_bb());
session->infinite_hp = c->infinite_hp;
session->infinite_tp = c->infinite_tp;
session->switch_assist = c->switch_assist;
session->save_files = c->proxy_save_files;
session->suppress_remote_login = c->proxy_suppress_remote_login;
try {
string key = string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number);
const auto& entry = client_options_cache.get_or_throw(key);
session->remote_guild_card_number = stoul(entry->data, nullptr, 10);
} catch (const out_of_range&) { }
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
-5
View File
@@ -21,11 +21,6 @@ using namespace std;
extern bool use_terminal_colors;
extern FileContentsCache file_cache;
const unordered_set<uint32_t> v2_crypt_initial_client_commands({
0x00260088, // (17) DCNTE license check
0x00280090, // (17) DCv1 license check
+2
View File
@@ -344,6 +344,8 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
ret_menu.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
ret_menu.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options",
u"Set proxy options", 0);
uint32_t item_id = 0;
for (const auto& item : items->as_dict()) {
+1
View File
@@ -100,6 +100,7 @@ struct ServerState {
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
std::shared_ptr<FileContentsCache> client_options_cache;
ServerState();
-2
View File
@@ -2,8 +2,6 @@
#include <array>
#include "FileContentsCache.hh"
using namespace std;