implement GC Ep1&2 trial edition

This commit is contained in:
Martin Michelsen
2022-08-13 00:35:10 -07:00
parent 2a7fdceba9
commit 202427e331
11 changed files with 109 additions and 47 deletions
+3
View File
@@ -51,6 +51,9 @@ struct Client {
ENCRYPTED_SEND_FUNCTION_CALL = 0x0800,
// Client supports send_function_call but does not actually run the code
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x1000,
// Client is GC Trial Edition, and therefore uses V2 encryption instead of
// V3, and doesn't support some commands
GC_TRIAL_EDITION = 0x2000,
};
uint64_t id;
+34 -21
View File
@@ -376,8 +376,10 @@ struct C_LegacyLogin_BB_04 {
// 05 = Server down for maintenance (108)
// 06 = Incorrect password (127)
// Any other nonzero value = Generic failure (101)
// The client config field in this command is only used by V3 clients. newserv
// sends it anyway to clients on earlier versions, but they ignore it.
// The client config field in this command is ignored by pre-V3 clients as well
// as Episodes 1&2 Trial Edition. All other V3 clients save it as opaque data to
// be returned in a 9E or 9F command later. newserv sends the client config
// anyway to clients that ignore it.
// Client will respond with a 96 command, but only the first time it receives
// this command - for later 04 commands, the client will still update its client
// config but will not respond. Changing the security data at any time seems ok,
@@ -433,8 +435,8 @@ struct S_MenuEntry {
le_uint16_t flags; // should be 0x0F04
ptext<CharT, EntryLength> text;
};
struct S_MenuEntry_PC_BB_07_1F : S_MenuEntry<char16_t, 17> { };
struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry<char, 18> { };
struct S_MenuEntry_PC_BB_07_1F : S_MenuEntry<char16_t, 0x11> { };
struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry<char, 0x12> { };
// 08 (C->S): Request game list
// No arguments
@@ -578,8 +580,9 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 {
// All commands after this command will be encrypted with PSO V2 encryption on
// PC, or PSO V3 encryption on V3.
// V3 clients will respond with a DB command the first time they receive a 17
// command in any online session; after the first time, they will respond with a
// 9E. Non-V3 clients will respond with a 9D.
// 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. Non-V3 clients will respond with a 9D.
// The copyright field in the structure must contain the following text:
// "DreamCast Port Map. Copyright SEGA Enterprises. 1999"
@@ -1307,8 +1310,9 @@ struct C_Register_BB_9C {
// displayed if the header.flag field is zero. If header.flag is nonzero, the
// client proceeds with the login procedure by sending a 9D/9E.
// 9D (C->S): Log in without client config (PC)
// Not used on V3 - the client sends 9E instead.
// 9D (C->S): Log in without client config (PC/GC)
// Not used on most versions of V3 - the client sends 9E instead. The one
// type of PSO V3 that uses 9D is the Trial Edition of Episodes 1&2.
// The extended version of this command is sent if the client has not yet
// received an 04 (in which case the extended fields are blank) or if the client
// selected the Meet User option, in which case it specifies the requested lobby
@@ -1322,7 +1326,7 @@ struct C_Login_MeetUserExtension {
ptext<CharT, 0x20> target_player_name;
};
struct C_Login_PC_9D {
struct C_Login_PC_GC_9D {
le_uint32_t player_tag; // 0x00010000 if guild card is set (via 04)
le_uint32_t guild_card_number; // 0xFFFFFFFF if not set
le_uint64_t unused;
@@ -1338,7 +1342,7 @@ struct C_Login_PC_9D {
ptext<char, 0x30> access_key2; // On XB, this is the XBL user ID
ptext<char, 0x10> name;
};
struct C_LoginExtended_PC_9D : C_Login_PC_9D {
struct C_LoginExtended_PC_GC_9D : C_Login_PC_GC_9D {
C_Login_MeetUserExtension<char16_t> extension;
};
@@ -1346,7 +1350,7 @@ struct C_LoginExtended_PC_9D : C_Login_PC_9D {
// The extended version of this command is used in the same circumstances as
// when PSO PC uses the extended version of the 9D command.
struct C_Login_GC_9E : C_Login_PC_9D {
struct C_Login_GC_9E : C_Login_PC_GC_9D {
union ClientConfigFields {
ClientConfig cfg;
parray<uint8_t, 0x20> data;
@@ -1383,6 +1387,8 @@ struct C_LoginExtended_BB_9E {
};
// 9F (S->C): Request client config / security data (V3/BB)
// This command is not valid on PSO GC Episodes 1&2 Trial Edition, nor any
// pre-V3 PSO versions.
// No arguments
// 9F (C->S): Client config / security data response (V3/BB)
@@ -1476,6 +1482,8 @@ struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry<char16_t, 0x7A> { };
// specific to that quest. The structure here represents the only instance I've
// seen so far.
// The server will respond with an AB command.
// This command is likely never sent by PSO GC Episodes 1&2 Trial Edition,
// because the following command (AB) is definitely not valid on that version.
struct C_UpdateQuestStatistics_AA {
le_uint16_t quest_internal_id;
@@ -1489,6 +1497,7 @@ struct C_UpdateQuestStatistics_AA {
};
// AB (S->C): Confirm update quest statistics
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
// TODO: Does this command have a different meaning in Episode 3? Is it used at
// all there, or is the handler an undeleted vestige from Episodes 1&2?
@@ -1508,6 +1517,7 @@ struct S_ConfirmUpdateQuestStatistics_AB {
// which starts the quest for all players at (approximately) the same time.
// Sending this command to a GC client when it is not waiting to start a quest
// will cause it to crash.
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
// AD: Invalid command
// AE: Invalid command
@@ -1939,6 +1949,7 @@ struct C_GBAGameRequest_V3_D7 {
// D7 (S->C): Unknown (V3/BB)
// No arguments
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
// On PSO V3, this command does... something. The command isn't *completely*
// ignored: it sets a global state variable, but it's not clear what that
// variable does. That variable is also set when a D7 is sent by the client, so
@@ -1950,6 +1961,7 @@ struct C_GBAGameRequest_V3_D7 {
// The server should respond with a D8 command (described below).
// D8 (S->C): Info board contents (V3/BB)
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
// Command is a list of these; header.flag is the entry count. There should be
// one entry for each player in the current lobby/game.
@@ -1967,6 +1979,7 @@ struct S_InfoBoardEntry_V3_D8 : S_InfoBoardEntry_D8<char> { };
// DA (S->C): Change lobby event (V3/BB)
// header.flag = new event number; no other arguments.
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
// DB (C->S): Verify license (V3/BB)
// Server should respond with a 9A command.
@@ -3007,10 +3020,10 @@ struct G_EnemyDropItemRequest_6x60 {
// 68: Telepipe/Ryuker
// 69: Unknown (supported; game only)
// 6A: Unknown (supported; game only; not valid on Episode 3)
// 6B: Unknown (used while loading into game)
// 6C: Unknown (used while loading into game)
// 6D: Unknown (used while loading into game)
// 6E: Unknown (used while loading into game)
// 6B: Sync enemy state (used while loading into game)
// 6C: Sync object state (used while loading into game)
// 6D: Sync item state (used while loading into game)
// 6E: Sync flag state (used while loading into game)
// 6F: Unknown (used while loading into game)
// 70: Unknown (used while loading into game)
// 71: Unknown (used while loading into game)
@@ -3087,12 +3100,12 @@ struct G_BoxItemDropRequest_6xA2 {
// AA: Episode 2 boss actions (not valid on PC or Episode 3)
// AB: Create lobby chair (not valid on PC)
// AC: Unknown (not valid on PC)
// AD: Unknown (not valid on PC or Episode 3)
// AE: Set chair state? (sent by existing clients at join time; not valid on PC)
// AF: Turn in lobby chair (not valid on PC)
// B0: Move in lobby chair (not valid on PC)
// B1: Unknown (not valid on PC)
// B2: Unknown (not valid on PC)
// AD: Unknown (not valid on PC, Episode 3, or GC Trial Edition)
// AE: Set chair state? (sent by existing clients at join time; not valid on PC or GC Trial Edition)
// AF: Turn in lobby chair (not valid on PC or GC Trial Edition)
// B0: Move in lobby chair (not valid on PC or GC Trial Edition)
// B1: Unknown (not valid on PC or GC Trial Edition)
// B2: Unknown (not valid on PC or GC Trial Edition)
// B3: Unknown (Episode 3 only)
// B4: Unknown (Episode 3 only)
// B5: Episode 3 game setup menu state sync
+1 -1
View File
@@ -217,7 +217,7 @@ static HandlerResult process_server_pc_v3_patch_02_17(shared_ptr<ServerState> s,
return HandlerResult::Type::SUPPRESS;
} else if (session.version == GameVersion::PC) {
C_Login_PC_9D cmd;
C_Login_PC_GC_9D cmd;
if (session.remote_guild_card_number == 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
+2 -2
View File
@@ -269,8 +269,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (command != 0x9D) {
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_PC_9D>(
data, sizeof(C_Login_PC_9D), sizeof(C_LoginExtended_PC_9D));
const auto& cmd = check_size_t<C_Login_PC_GC_9D>(
data, sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
license = session->server->state->license_manager->verify_pc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
+25 -10
View File
@@ -76,6 +76,9 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
break;
}
case ServerBehavior::LOGIN_SERVER_GC_TRIAL_EDITION:
c->flags |= Client::Flag::GC_TRIAL_EDITION;
[[fallthrough]];
case ServerBehavior::LOGIN_SERVER:
send_server_init(s, c, true, false);
if (s->pre_lobby_event) {
@@ -85,6 +88,11 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
case ServerBehavior::PATCH_SERVER_BB:
c->flags |= Client::Flag::BB_PATCH;
send_server_init(s, c, false, false);
break;
case ServerBehavior::LOBBY_SERVER_GC_TRIAL_EDITION:
c->flags |= Client::Flag::GC_TRIAL_EDITION;
[[fallthrough]];
case ServerBehavior::PATCH_SERVER_PC:
case ServerBehavior::DATA_SERVER_BB:
@@ -319,12 +327,12 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
// The client sends extra unused data the first time it sends these commands,
// hence the odd check_size calls here
const C_Login_PC_9D* base_cmd;
const C_Login_PC_GC_9D* base_cmd;
if (command == 0x9D) {
base_cmd = &check_size_t<C_Login_PC_9D>(data,
sizeof(C_Login_PC_9D), sizeof(C_LoginExtended_PC_9D));
base_cmd = &check_size_t<C_Login_PC_GC_9D>(data,
sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
if (base_cmd->is_extended) {
const auto& cmd = check_size_t<C_LoginExtended_PC_9D>(data);
const auto& cmd = check_size_t<C_LoginExtended_PC_GC_9D>(data);
if (cmd.extension.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.extension.preferred_lobby_id;
}
@@ -1119,12 +1127,15 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_quest_file(l->clients[x], dat_basename + ".dat", dat_basename,
*dat_contents, QuestFileType::ONLINE);
// There is no such thing as command AC on PSO PC - quests just start
// immediately when they're done downloading. There are also no chunk
// acknowledgements (C->S 13 commands) like there are on GC. So, for
// PC clients, we can just not set the loading flag, since we never
// need to check/clear it later.
if (l->clients[x]->version != GameVersion::PC) {
// There is no such thing as command AC on PSO V2 - quests just start
// immediately when they're done downloading. (This is also the case
// on V3 Trial Edition.) There are also no chunk acknowledgements
// (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) &&
!(l->clients[x]->flags & Client::Flag::GC_TRIAL_EDITION)) {
l->clients[x]->flags |= Client::Flag::LOADING_QUEST;
}
}
@@ -1407,6 +1418,10 @@ 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);
if (c->flags & Client::Flag::GC_TRIAL_EDITION) {
throw runtime_error("trial edition client sent update quest stats command");
}
auto l = s->find_lobby(c->lobby_id);
if (!l || !l->is_game() || !l->loading_quest.get() ||
(l->loading_quest->internal_id != cmd.quest_internal_id)) {
+2 -2
View File
@@ -105,8 +105,8 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
check_ak(cmd.access_key);
check_pw(cmd.password);
} else if (header.command == 0x9D) {
const auto& cmd = check_size_t<C_Login_PC_9D>(cmd_data, cmd_size,
sizeof(C_Login_PC_9D), sizeof(C_LoginExtended_PC_9D));
const auto& cmd = check_size_t<C_Login_PC_GC_9D>(cmd_data, cmd_size,
sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
}
+14 -9
View File
@@ -104,8 +104,7 @@ S_ServerInit_DC_PC_V3_02_17_91_9B prepare_server_init_contents_dc_pc_v3(
return cmd;
}
void send_server_init_dc_pc_v3(shared_ptr<Client> c,
bool initial_connection) {
void send_server_init_dc_pc_v3(shared_ptr<Client> c, bool initial_connection) {
uint8_t command = initial_connection ? 0x17 : 0x02;
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
@@ -122,8 +121,13 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c,
break;
case GameVersion::GC:
case GameVersion::XB:
c->channel.crypt_out.reset(new PSOV3Encryption(server_key));
c->channel.crypt_in.reset(new PSOV3Encryption(client_key));
if (c->flags & Client::Flag::GC_TRIAL_EDITION) {
c->channel.crypt_out.reset(new PSOV2Encryption(server_key));
c->channel.crypt_in.reset(new PSOV2Encryption(client_key));
} else {
c->channel.crypt_out.reset(new PSOV3Encryption(server_key));
c->channel.crypt_in.reset(new PSOV3Encryption(client_key));
}
break;
default:
throw invalid_argument("incorrect client version");
@@ -1584,12 +1588,13 @@ void send_server_time(shared_ptr<Client> c) {
}
void send_change_event(shared_ptr<Client> c, uint8_t new_event) {
// THis command isn't supported on versions before V3
if ((c->version == GameVersion::GC) ||
(c->version == GameVersion::XB) ||
(c->version == GameVersion::BB)) {
send_command(c, 0xDA, new_event);
// This command isn't supported on versions before V3, nor on Trial Edition.
if ((c->version == GameVersion::DC) ||
(c->version == GameVersion::PC) ||
(c->flags & Client::Flag::GC_TRIAL_EDITION)) {
return;
}
send_command(c, 0xDA, new_event);
}
void send_change_event(shared_ptr<Lobby> l, uint8_t new_event) {
+12
View File
@@ -39,6 +39,10 @@ uint16_t flags_for_version(GameVersion version, int64_t sub_version) {
return Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION |
Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY;
// TODO: GC Ep1&2 trial edition uses sub_version 0x30, but also uses PSO V2
// encryption! Find a way to tell that version apart from the others before
// starting encryption (hopefully it connects on a port not shared by other
// versions).
case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00
case 0x34: // GC Ep1&2 JP v1.03
@@ -114,8 +118,12 @@ const char* name_for_server_behavior(ServerBehavior behavior) {
return "split_reconnect";
case ServerBehavior::LOGIN_SERVER:
return "login_server";
case ServerBehavior::LOGIN_SERVER_GC_TRIAL_EDITION:
return "login_server_gcte";
case ServerBehavior::LOBBY_SERVER:
return "lobby_server";
case ServerBehavior::LOBBY_SERVER_GC_TRIAL_EDITION:
return "lobby_server_gcte";
case ServerBehavior::DATA_SERVER_BB:
return "data_server_bb";
case ServerBehavior::PATCH_SERVER_PC:
@@ -134,8 +142,12 @@ ServerBehavior server_behavior_for_name(const char* name) {
return ServerBehavior::SPLIT_RECONNECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login")) {
return ServerBehavior::LOGIN_SERVER;
} else if (!strcasecmp(name, "login_server_gcte") || !strcasecmp(name, "login_gcte")) {
return ServerBehavior::LOGIN_SERVER_GC_TRIAL_EDITION;
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "lobby_server_gcte") || !strcasecmp(name, "lobby_gcte")) {
return ServerBehavior::LOBBY_SERVER_GC_TRIAL_EDITION;
} else if (!strcasecmp(name, "data_server_bb") || !strcasecmp(name, "data_server") || !strcasecmp(name, "data")) {
return ServerBehavior::DATA_SERVER_BB;
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
+2
View File
@@ -16,7 +16,9 @@ enum class GameVersion {
enum class ServerBehavior {
SPLIT_RECONNECT = 0,
LOGIN_SERVER,
LOGIN_SERVER_GC_TRIAL_EDITION,
LOBBY_SERVER,
LOBBY_SERVER_GC_TRIAL_EDITION,
DATA_SERVER_BB,
PATCH_SERVER_PC,
PATCH_SERVER_BB,
+8 -1
View File
@@ -23,14 +23,19 @@
// Various versions of PSO hardcode these ports in the clients. Don't change
// these unless you don't want to support certain versions of PSO.
// TODO: GC Episodes 1&2 Trial Edition also uses port 9000, but a real
// version of PSO uses that port too. Figure out a way to differentiate
// between the two versions.
"gc-jp10": [9000, "gc", "login_server"],
"gc-jp11": [9001, "gc", "login_server"],
"gc-jpte": [9002, "gc", "login_server"],
"gc-jp3te": [9002, "gc", "login_server"],
"gc-jp3": [9003, "gc", "login_server"],
"gc-us12t1": [9064, "gc", "login_server_gcte"],
"gc-us10": [9100, "pc", "split_reconnect"],
"gc-us3": [9103, "gc", "login_server"],
"gc-eu10": [9200, "gc", "login_server"],
"gc-eu11": [9201, "gc", "login_server"],
"gc-us12t2": [9202, "gc", "login_server_gcte"],
"gc-eu3": [9203, "gc", "login_server"],
"pc-login": [9300, "pc", "login_server"],
"pc-patch": [10000, "patch", "patch_server_pc"],
@@ -60,10 +65,12 @@
"gc-lobby": [9421, "gc", "lobby_server"],
"bb-lobby": [9422, "bb", "lobby_server"],
"xb-lobby": [9423, "xb", "lobby_server"],
"gct-lobby": [9424, "gc", "lobby_server_gcte"],
"pc-proxy": [9520, "pc", "proxy_server"],
"gc-proxy": [9521, "gc", "proxy_server"],
"bb-proxy": [9522, "bb", "proxy_server"],
"xb-proxy": [9523, "xb", "proxy_server"],
// TODO: Implement proxy server for GC Trial Edition
"bb-data1": [12004, "bb", "data_server_bb"],
"bb-data2": [12005, "bb", "data_server_bb"],
},
+6 -1
View File
@@ -19,12 +19,14 @@
"PortConfiguration": {
"gc-jp10": [9000, "gc", "login_server"],
"gc-jp11": [9001, "gc", "login_server"],
"gc-jpte": [9002, "gc", "login_server"],
"gc-jp3te": [9002, "gc", "login_server"],
"gc-jp3": [9003, "gc", "login_server"],
"gc-us12t1": [9064, "gc", "login_server_gcte"],
"gc-us10": [9100, "pc", "split_reconnect"],
"gc-us3": [9103, "gc", "login_server"],
"gc-eu10": [9200, "gc", "login_server"],
"gc-eu11": [9201, "gc", "login_server"],
"gc-us12t2": [9202, "gc", "login_server_gcte"],
"gc-eu3": [9203, "gc", "login_server"],
"pc-login": [9300, "pc", "login_server"],
"pc-patch": [10000, "patch", "patch_server_pc"],
@@ -38,9 +40,12 @@
"pc-lobby": [9420, "pc", "lobby_server"],
"gc-lobby": [9421, "gc", "lobby_server"],
"bb-lobby": [9422, "bb", "lobby_server"],
"xb-lobby": [9423, "xb", "lobby_server"],
"gct-lobby": [9424, "gc", "lobby_server_gcte"],
"pc-proxy": [9520, "pc", "proxy_server"],
"gc-proxy": [9521, "gc", "proxy_server"],
"bb-proxy": [9522, "bb", "proxy_server"],
"xb-proxy": [9523, "xb", "proxy_server"],
},
"ProxyDestinations-GC": {