diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 62a86455..4593f7bb 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -495,7 +495,8 @@ static void proxy_command_lobby_event(shared_ptr, send_text_message(session.client_channel, u"$C6No such lobby event."); } else { session.options.override_lobby_event = new_event; - if ((session.version == GameVersion::GC && !(session.newserv_client_config.cfg.flags & Client::Flag::IS_TRIAL_EDITION)) || + // This command is supported on all V3 versions except Ep1&2 Trial + if ((session.version == GameVersion::GC && !(session.newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || (session.version == GameVersion::XB) || (session.version == GameVersion::BB)) { session.client_channel.send(0xDA, session.options.override_lobby_event); diff --git a/src/Client.cc b/src/Client.cc index 9b919d8d..8b9d5268 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -101,7 +101,7 @@ Client::~Client() { QuestScriptVersion Client::quest_version() const { switch (this->version()) { case GameVersion::DC: - if (this->flags & Flag::IS_TRIAL_EDITION) { + if (this->flags & Flag::IS_DC_TRIAL_EDITION) { return QuestScriptVersion::DC_NTE; } else if (this->flags & Flag::IS_DC_V1) { return QuestScriptVersion::DC_V1; @@ -111,7 +111,7 @@ QuestScriptVersion Client::quest_version() const { case GameVersion::PC: return QuestScriptVersion::PC_V2; case GameVersion::GC: - if (this->flags & Flag::IS_TRIAL_EDITION) { + if (this->flags & Flag::IS_GC_TRIAL_EDITION) { return QuestScriptVersion::GC_NTE; } else if (this->flags & Flag::IS_EPISODE_3) { return QuestScriptVersion::GC_EP3; diff --git a/src/Client.hh b/src/Client.hh index 15953bb8..30cce253 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -49,16 +49,9 @@ struct ClientOptions { struct Client { enum Flag { - // This flag has two meanings. If set on a client with GameVersion::DC, then - // IS_DC_V1 is also set. In this case, the client is DC Network Trial - // Edition, which uses several commands that no other version uses. If this - // flag is set without IS_DC_V1, then the client is GC Episodes 1 & 2 Trial - // Edition, and therefore uses V2 encryption instead of V3 encryption, and - // doesn't support some commands. - // Note that this flag is NOT set for Episode 3 Trial Edition clients, since - // that version is similar enough to the release version of Episode 3 that - // newserv does not have to change its behavior at all. - IS_TRIAL_EDITION = 0x00002000, + // Client is DC Network Trial Edition, which is missing a lot of features + // and uses some different command numbers than any other version + IS_DC_TRIAL_EDITION = 0x00002000, // A 90 01 command has been sent (which proto will send a 93 in response to, // and actual DCv1 will send a 92) CHECKED_FOR_DC_V1_PROTOTYPE = 0x00080000, @@ -66,6 +59,14 @@ struct Client { IS_DC_V1_PROTOTYPE = 0x00040000, // Client is DC v1 IS_DC_V1 = 0x00000010, + // Client is GC Episodes 1&2 Trial Edition, which is much more like PC than + // actual GC Episodes 1&2 - it uses PC encryption and is missing most of the + // features added in Episodes 1&2 + IS_GC_TRIAL_EDITION = 0x00200000, + // Client is GC Episode 3 Trial Edition, which is fairly close to the final + // Episode 3 build, but is missing a few commands that we'll have to avoid + // sending + IS_EP3_TRIAL_EDITION = 0x00400000, // For patch server clients, client is Blue Burst rather than PC IS_BB_PATCH = 0x00000001, // After joining a lobby, client will no longer send D6 commands when they @@ -107,6 +108,8 @@ struct Client { // Client has received newserv's Episode 3 card definitions, so don't send // them again HAS_EP3_CARD_DEFS = 0x00004000, + + UNUSED_FLAG_BITS = 0xFF800000, }; uint64_t id; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index aa2dcaca..e1a8776c 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -891,8 +891,9 @@ static HandlerResult S_V3_1A_D5(shared_ptr, static HandlerResult S_V3_BB_DA(shared_ptr, ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string&) { + // This command is supported on all V3 versions except Ep1&2 Trial if ((session.version == GameVersion::GC) && - (session.newserv_client_config.cfg.flags & Client::Flag::IS_TRIAL_EDITION)) { + (session.newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) { return HandlerResult::Type::SUPPRESS; } else if ((session.options.override_lobby_event >= 0) && (static_cast(flag) != session.options.override_lobby_event)) { diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 3aa4eda1..f9aaa5c2 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -628,7 +628,7 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) { session->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); if (is_server_stream && (session->options.override_lobby_event >= 0) && - (((session->version == GameVersion::GC) && !(session->newserv_client_config.cfg.flags & Client::Flag::IS_TRIAL_EDITION)) || + (((session->version == GameVersion::GC) && !(session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || (session->version == GameVersion::XB) || (session->version == GameVersion::BB))) { session->client_channel.send(0xDA, session->options.override_lobby_event); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 1805192b..5a1e1bcb 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -235,7 +235,7 @@ void on_login_complete(shared_ptr s, shared_ptr c) { auto team = s->ep3_tournament_index->team_for_serial_number(c->license->serial_number); auto tourn = team ? team->tournament.lock() : nullptr; c->ep3_tournament_team = team; - if (!(c->flags & Client::Flag::IS_TRIAL_EDITION)) { + if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(s, c, tourn); } } @@ -302,7 +302,7 @@ static void set_console_client_flags( c->channel.version = GameVersion::DC; c->log.info("Game version changed to DC"); } else if (c->version() == GameVersion::GC) { - c->flags |= Client::Flag::IS_TRIAL_EDITION; + c->flags |= Client::Flag::IS_GC_TRIAL_EDITION; c->log.info("Trial edition flag set"); } } @@ -358,7 +358,7 @@ static void on_88_DCNTE(shared_ptr s, shared_ptr c, const auto& cmd = check_size_t(data); c->channel.version = GameVersion::DC; c->flags |= flags_for_version(c->version(), -1); - c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_TRIAL_EDITION; + c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { @@ -390,7 +390,7 @@ static void on_8B_DCNTE(shared_ptr s, shared_ptr c, const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); c->channel.version = GameVersion::DC; c->flags |= flags_for_version(c->version(), -1); - c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_TRIAL_EDITION; + c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { @@ -1261,7 +1261,7 @@ static void on_tournament_bracket_updated( !c->license || !serial_numbers.count(c->license->serial_number) || c->ep3_tournament_team.expired() || - (c->flags & Client::Flag::IS_TRIAL_EDITION)) { + (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { continue; } send_ep3_confirm_tournament_entry(s, c, tourn); @@ -1644,7 +1644,7 @@ static void on_10(shared_ptr s, shared_ptr c, // DC NTE and the v1 prototype crash if they receive a 97 command, // so we instead do the redirect immediately if ((c->version() == GameVersion::DC) && - (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { send_client_to_lobby_server(s, c); } else { send_command(c, 0x97, 0x01); @@ -1830,7 +1830,7 @@ static void on_10(shared_ptr s, shared_ptr c, } else { // Clear Check Tactics menu so client won't see newserv tournament // state while logically on another server - if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_TRIAL_EDITION)) { + if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(s, c, nullptr); } @@ -1984,7 +1984,7 @@ static void on_10(shared_ptr s, shared_ptr c, // check/clear it later. if ((l->clients[x]->version() != GameVersion::DC) && (l->clients[x]->version() != GameVersion::PC) && - !(l->clients[x]->flags & Client::Flag::IS_TRIAL_EDITION)) { + !(l->clients[x]->flags & Client::Flag::IS_GC_TRIAL_EDITION)) { l->clients[x]->flags |= Client::Flag::LOADING_QUEST; l->clients[x]->disconnect_hooks.emplace(QUEST_BARRIER_DISCONNECT_HOOK_NAME, [l]() -> void { send_quest_barrier_if_all_clients_ready(l); @@ -2197,7 +2197,7 @@ static void on_A1(shared_ptr s, shared_ptr c, static void on_8E_DCNTE(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, const string& data) { - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { on_A0(s, c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8E"); @@ -2206,7 +2206,7 @@ static void on_8E_DCNTE(shared_ptr s, shared_ptr c, static void on_8F_DCNTE(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, const string& data) { - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { on_A1(s, c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8F"); @@ -2368,7 +2368,10 @@ static void on_AA(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { + throw runtime_error("pre-V3 client sent update quest stats command"); + } + if (c->flags & Client::Flag::IS_GC_TRIAL_EDITION) { throw runtime_error("trial edition client sent update quest stats command"); } @@ -3257,7 +3260,7 @@ shared_ptr create_game_generic( bool is_solo = (game->mode == GameMode::SOLO); // Generate the map variations - if (game->is_ep3() || (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)))) { + if (game->is_ep3() || (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)))) { game->variations.clear(0); } else { generate_variations(game->variations, game->random_crypt, game->episode, is_solo); @@ -3338,7 +3341,7 @@ static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, uint16_t command, uint32_t, const string& data) { shared_ptr game; - if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { const auto& cmd = check_size_t>(data); u16string name = decode_sjis(cmd.name); u16string password = decode_sjis(cmd.password); @@ -3449,7 +3452,7 @@ static void on_C1_BB(shared_ptr s, shared_ptr c, static void on_8A(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { - if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_TRIAL_EDITION)) { + if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { const auto& cmd = check_size_t(data); set_console_client_flags(c, cmd.sub_version); send_command(c, 0x8A, 0x01); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 79536f14..8fb7cb03 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2044,7 +2044,7 @@ void on_subcommand_multi(shared_ptr s, shared_ptr l, if (data.empty()) { throw runtime_error("game command is empty"); } - if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { // TODO: We should convert these to non-trial formats and vice versa forward_subcommand(l, c, command, flag, data.data(), data.size()); } else { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index d1a1d00b..52820fda 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1104,7 +1104,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info switch (c->version()) { case GameVersion::DC: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DC); - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DCNTE); } break; @@ -1113,7 +1113,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info break; case GameVersion::GC: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC); - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->flags & Client::Flag::IS_GC_TRIAL_EDITION) { is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC_TRIAL_EDITION); } break; @@ -1752,7 +1752,7 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { send_join_game_t(c, l); break; case GameVersion::DC: - if (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { + if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { send_join_game_dc_nte(c, l); break; } @@ -1772,7 +1772,7 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { } else { switch (c->version()) { case GameVersion::DC: - if (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { + if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { send_join_lobby_dc_nte(c, l); } else { send_join_lobby_t(c, l); @@ -1807,7 +1807,7 @@ void send_player_join_notification(shared_ptr c, shared_ptr l, shared_ptr joining_client) { switch (c->version()) { case GameVersion::DC: - if (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { + if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { send_join_lobby_dc_nte(c, l, joining_client); } else { send_join_lobby_t(c, l, joining_client); @@ -1851,7 +1851,7 @@ void send_self_leave_notification(shared_ptr c) { void send_get_player_info(shared_ptr c) { if ((c->version() == GameVersion::DC) && - (c->flags & Client::Flag::IS_TRIAL_EDITION)) { + (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { send_command(c, 0x8D, 0x00); } else { send_command(c, 0x95, 0x00); @@ -2254,7 +2254,7 @@ void send_ep3_confirm_tournament_entry( shared_ptr s, shared_ptr c, shared_ptr tourn) { - if (c->flags & Client::Flag::IS_TRIAL_EDITION) { + if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) { throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); } @@ -2850,7 +2850,7 @@ void send_change_event(shared_ptr c, uint8_t 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::IS_TRIAL_EDITION)) { + (c->flags & Client::Flag::IS_GC_TRIAL_EDITION)) { return; } send_command(c, 0xDA, new_event); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 1d168e33..a41c8b10 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -707,7 +707,7 @@ Proxy session commands:\n\ } else { session->options.override_lobby_event = event_for_name(command_args); if ((session->version != GameVersion::DC) && - (session->version != GameVersion::PC) && (!((session->version == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_TRIAL_EDITION)))) { + (session->version != GameVersion::PC) && (!((session->version == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) { session->client_channel.send(0xDA, session->options.override_lobby_event); } } diff --git a/src/Version.cc b/src/Version.cc index 6cbe845f..7181f933 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -42,9 +42,9 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { // TODO: Which other sub_versions of DC v1 and v2 exist? case 0x20: // DCNTE - // In the case of DCNTE, the IS_TRIAL_EDITION flag is already set when we - // get here, so the remaining flags are the same as DCv1 case 0x21: // DCv1 US + // In the case of DCNTE, the IS_DC_TRIAL_EDITION flag is already set when + // we get here, so the remaining flags are the same as DCv1 return Client::Flag::IS_DC_V1 | Client::Flag::NO_D6 | Client::Flag::NO_SEND_FUNCTION_CALL; @@ -65,6 +65,9 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { 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 + // In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is + // already set when we get here (because the client has used V2 encryption + // instead of V3) return 0; case 0x32: // GC Ep1&2 EU 50Hz case 0x33: // GC Ep1&2 EU 60Hz @@ -79,8 +82,11 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { Client::Flag::NO_SEND_FUNCTION_CALL; case 0x40: // GC Ep3 trial + // TODO: Is this sub_version used for any other Ep3 versions? Is the final + // JP release really 42 and not 40? return Client::Flag::NO_D6_AFTER_LOBBY | Client::Flag::IS_EPISODE_3 | + Client::Flag::IS_EP3_TRIAL_EDITION | Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL | Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case 0x42: // GC Ep3 JP