diff --git a/tests/login_smoke.cpp b/tests/login_smoke.cpp index 8b2fee8..d30a62a 100644 --- a/tests/login_smoke.cpp +++ b/tests/login_smoke.cpp @@ -155,6 +155,14 @@ struct PacketCGPlayerCreate uint8_t dex; }; +struct PacketCGPlayerDelete +{ + uint16_t header; + uint16_t length; + uint8_t index; + char private_code[8]; +}; + struct PacketGCPlayerCreateSuccess { uint16_t header; @@ -170,6 +178,13 @@ struct PacketGCCreateFailure uint8_t type; }; +struct PacketGCPlayerDeleteSuccess +{ + uint16_t header; + uint16_t length; + uint8_t account_index; +}; + struct PacketCGPlayerSelect { uint16_t header; @@ -247,6 +262,7 @@ struct SmokeOptions std::string client_version = "1215955205"; std::string expect_auth_failure; std::string expect_channel_failure; + std::string delete_private_code; std::string mall_password; }; @@ -262,14 +278,17 @@ struct SmokeResult int character_index = -1; std::string character_name; bool character_created = false; + bool character_deleted = false; int game_channel = -1; bool mall_opened = false; int mall_size = -1; + int deleted_character_index = -1; int64_t auth_handshake_ms = -1; int64_t auth_login_ms = -1; int64_t channel_handshake_ms = -1; int64_t channel_login_ms = -1; int64_t character_create_ms = -1; + int64_t character_delete_ms = -1; int64_t character_select_ms = -1; int64_t entergame_ms = -1; int64_t mall_open_ms = -1; @@ -358,6 +377,8 @@ void PrintJson(const SmokeResult& result) << ",\"character_index\":" << result.character_index << ",\"character_name\":\"" << JsonEscape(result.character_name) << "\"" << ",\"character_created\":" << (result.character_created ? "true" : "false") + << ",\"character_deleted\":" << (result.character_deleted ? "true" : "false") + << ",\"deleted_character_index\":" << result.deleted_character_index << ",\"game_channel\":" << result.game_channel << ",\"mall_opened\":" << (result.mall_opened ? "true" : "false") << ",\"mall_size\":" << result.mall_size @@ -367,6 +388,7 @@ void PrintJson(const SmokeResult& result) << ",\"channel_handshake\":" << result.channel_handshake_ms << ",\"channel_login\":" << result.channel_login_ms << ",\"character_create\":" << result.character_create_ms + << ",\"character_delete\":" << result.character_delete_ms << ",\"character_select\":" << result.character_select_ms << ",\"entergame\":" << result.entergame_ms << ",\"mall_open\":" << result.mall_open_ms @@ -735,6 +757,53 @@ uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create throw std::runtime_error("did not receive PLAYER_CREATE_SUCCESS"); } +void DeleteCharacter(EncryptedClient& channel_client, uint8_t character_index, std::string_view private_code, SmokeResult& result, bool json) +{ + Expect(private_code.size() == 7, "delete private code must be exactly 7 characters"); + + PacketCGPlayerDelete delete_packet {}; + delete_packet.header = CG::CHARACTER_DELETE; + delete_packet.length = sizeof(delete_packet); + delete_packet.index = character_index; + std::strncpy(delete_packet.private_code, private_code.data(), sizeof(delete_packet.private_code) - 1); + channel_client.SendEncryptedPacket(delete_packet); + + std::vector frame; + for (int i = 0; i < 16; ++i) + { + Expect(channel_client.WaitForFrame(frame, 5000), "timed out waiting for character delete response"); + const auto header = FrameHeader(frame); + + if (header == GC::PHASE) + continue; + + if (header == GC::LOGIN_FAILURE) + { + Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected character delete failure size"); + const auto* failure = reinterpret_cast(frame.data()); + throw std::runtime_error(std::string("character delete failed: ") + failure->status); + } + + if (header == GC::PLAYER_DELETE_WRONG_SOCIAL_ID) + { + throw std::runtime_error("character delete failed: wrong private code"); + } + + if (header == GC::PLAYER_DELETE_SUCCESS) + { + Expect(frame.size() == sizeof(PacketGCPlayerDeleteSuccess), "unexpected player delete success size"); + const auto* success = reinterpret_cast(frame.data()); + Expect(success->account_index == character_index, "character delete returned unexpected index"); + result.character_deleted = true; + result.deleted_character_index = success->account_index; + EmitEvent(result, json, "character_deleted index=" + std::to_string(static_cast(success->account_index))); + return; + } + } + + throw std::runtime_error("did not receive PLAYER_DELETE_SUCCESS"); +} + void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index, SmokeResult& result, bool json) { PacketCGPlayerSelect select_packet {}; @@ -918,7 +987,8 @@ int main(int argc, char** argv) "usage: metin_login_smoke \n" " or: metin_login_smoke --password-env=ENV_NAME\n" " optional: --create-character-name=NAME [--client-version=VERSION] [--json] " - "[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--mall-password=PASSWORD]"); + "[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--delete-private-code=CODE] " + "[--mall-password=PASSWORD]"); const std::string host = argv[1]; const uint16_t auth_port = static_cast(std::stoi(argv[2])); @@ -946,6 +1016,7 @@ int main(int argc, char** argv) const std::string version_prefix = "--client-version="; const std::string auth_failure_prefix = "--expect-auth-failure="; const std::string channel_failure_prefix = "--expect-channel-failure="; + const std::string delete_private_code_prefix = "--delete-private-code="; const std::string mall_password_prefix = "--mall-password="; if (create_arg.rfind(create_prefix, 0) == 0) { @@ -976,6 +1047,14 @@ int main(int argc, char** argv) continue; } + if (create_arg.rfind(delete_private_code_prefix, 0) == 0) + { + options.delete_private_code = create_arg.substr(delete_private_code_prefix.size()); + Expect(!options.delete_private_code.empty(), "delete private code is empty"); + Expect(options.delete_private_code.size() == 7, "delete private code must be exactly 7 characters"); + continue; + } + if (create_arg.rfind(mall_password_prefix, 0) == 0) { options.mall_password = create_arg.substr(mall_password_prefix.size()); @@ -1087,6 +1166,21 @@ int main(int argc, char** argv) result.character_index = character_index; } + if (!options.delete_private_code.empty()) + { + result.stage = "character_delete"; + started_at = std::chrono::steady_clock::now(); + DeleteCharacter(channel_client, character_index, options.delete_private_code, result, options.json); + result.character_delete_ms = ElapsedMs(started_at); + result.ok = true; + result.result = "delete_success"; + result.stage = "complete"; + EmitEvent(result, options.json, "delete_success"); + if (options.json) + PrintJson(result); + return 0; + } + result.stage = "character_select"; started_at = std::chrono::steady_clock::now(); SelectCharacter(channel_client, character_index, result, options.json);