From 4a12f70f2cd43ab554da60cc58681df86fe06e58 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Mon, 12 Jan 2026 17:29:45 +0100 Subject: [PATCH] Add Delete command to RPCN --- rpcs3/Emu/NP/rpcn_client.cpp | 51 +++++++-- rpcs3/Emu/NP/rpcn_client.h | 1 + rpcs3/Emu/NP/rpcn_types.h | 1 + rpcs3/rpcs3qt/rpcn_settings_dialog.cpp | 144 ++++++++++++++++++------- rpcs3/rpcs3qt/rpcn_settings_dialog.h | 9 ++ 5 files changed, 159 insertions(+), 47 deletions(-) diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 1ebbfdd14e..dc0af98f6c 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -101,6 +101,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case rpcn::CommandType::Login: return "Login"; case rpcn::CommandType::Terminate: return "Terminate"; case rpcn::CommandType::Create: return "Create"; + case rpcn::CommandType::Delete: return "Delete"; case rpcn::CommandType::SendToken: return "SendToken"; case rpcn::CommandType::SendResetToken: return "SendResetToken"; case rpcn::CommandType::ResetPassword: return "ResetPassword"; @@ -255,7 +256,7 @@ namespace rpcn rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); } - constexpr u32 RPCN_PROTOCOL_VERSION = 27; + constexpr u32 RPCN_PROTOCOL_VERSION = 28; constexpr usz RPCN_HEADER_SIZE = 15; const char* error_to_explanation(rpcn::ErrorType error) @@ -656,7 +657,7 @@ namespace rpcn } // Those commands are handled synchronously and won't be forwarded to NP Handler - if (command == CommandType::Login || command == CommandType::GetServerList || command == CommandType::Create || + if (command == CommandType::Login || command == CommandType::GetServerList || command == CommandType::Create || command == CommandType::Delete || command == CommandType::AddFriend || command == CommandType::RemoveFriend || command == CommandType::AddBlock || command == CommandType::RemoveBlock || command == CommandType::SendMessage || command == CommandType::SendToken || @@ -1192,7 +1193,7 @@ namespace rpcn std::copy(token.begin(), token.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; @@ -1278,7 +1279,7 @@ namespace rpcn bool rpcn_client::terminate_connection() { - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; std::vector data; @@ -1314,7 +1315,7 @@ namespace rpcn std::copy(email.begin(), email.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::Create, req_id, data, packet_data)) @@ -1348,7 +1349,7 @@ namespace rpcn std::copy(password.begin(), password.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::SendToken, req_id, data, packet_data)) @@ -1381,7 +1382,7 @@ namespace rpcn std::copy(email.begin(), email.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::SendResetToken, req_id, data, packet_data)) @@ -1416,7 +1417,7 @@ namespace rpcn std::copy(password.begin(), password.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::ResetPassword, req_id, data, packet_data)) @@ -1435,13 +1436,43 @@ namespace rpcn return error; } + ErrorType rpcn_client::delete_account() + { + const auto npid = g_cfg_rpcn.get_npid(); + const auto password = g_cfg_rpcn.get_password(); + + std::vector data; + std::copy(npid.begin(), npid.end(), std::back_inserter(data)); + data.push_back(0); + std::copy(password.begin(), password.end(), std::back_inserter(data)); + data.push_back(0); + + const u64 req_id = rpcn_request_counter.fetch_add(1); + + std::vector packet_data; + if (!forge_send_reply(CommandType::Delete, req_id, data, packet_data)) + { + return ErrorType::Malformed; + } + + vec_stream reply(packet_data); + auto error = static_cast(reply.get()); + + if (error == rpcn::ErrorType::NoError) + { + rpcn_log.success("Account was successfully deleted!"); + } + + return error; + } + bool rpcn_client::add_friend(const std::string& friend_username) { std::vector data; std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::AddFriend, req_id, data, packet_data)) @@ -1467,7 +1498,7 @@ namespace rpcn std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::RemoveFriend, req_id, data, packet_data)) diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 6d7126ffa6..3781711912 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -304,6 +304,7 @@ namespace rpcn ErrorType resend_token(const std::string& npid, const std::string& password); ErrorType send_reset_token(std::string_view npid, std::string_view email); ErrorType reset_password(std::string_view npid, std::string_view token, std::string_view password); + ErrorType delete_account(); bool add_friend(const std::string& friend_username); bool remove_friend(const std::string& friend_username); diff --git a/rpcs3/Emu/NP/rpcn_types.h b/rpcs3/Emu/NP/rpcn_types.h index 684f0e65e9..15fba827d5 100644 --- a/rpcs3/Emu/NP/rpcn_types.h +++ b/rpcs3/Emu/NP/rpcn_types.h @@ -9,6 +9,7 @@ namespace rpcn Login, Terminate, Create, + Delete, SendToken, SendResetToken, ResetPassword, diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp index 74d5b14e6a..24aa0767aa 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp @@ -76,6 +76,20 @@ std::string derive_password(std::string_view user_password) return derived_password; } +std::shared_ptr get_rpcn_connection(QWidget* parent) +{ + const auto rpcn = rpcn::rpcn_client::get_instance(0); + + if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) + { + const QString error_message = QObject::tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); + QMessageBox::critical(parent, QObject::tr("Error Connecting to RPCN!"), error_message, QMessageBox::Ok); + return nullptr; + } + + return rpcn; +} + rpcn_settings_dialog::rpcn_settings_dialog(QWidget* parent) : QDialog(parent) { @@ -279,15 +293,11 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); const auto avatar_url = "https://rpcs3.net/cdn/netplay/DefaultAvatar.png"; + const auto rpcn = get_rpcn_connection(this); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting"), error_message, QMessageBox::Ok); + if (!rpcn) return; - } if (auto error = rpcn->create_user(*username, *password, *username, avatar_url, *email); error != rpcn::ErrorType::NoError) { @@ -334,14 +344,11 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) connect(btn_test, &QAbstractButton::clicked, this, [this]() { - auto rpcn = rpcn::rpcn_client::get_instance(0); + const auto rpcn = get_rpcn_connection(this); - if (auto res = rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure) - { - const QString error_msg = tr("Failed to connect to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); - QMessageBox::warning(this, tr("Error connecting to RPCN!"), error_msg, QMessageBox::Ok); + if (!rpcn) return; - } + if (auto res = rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure) { const QString error_msg = tr("Failed to authentify to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); @@ -657,6 +664,68 @@ const std::optional& rpcn_ask_token_dialog::get_token() const return m_token; } +rpcn_confirm_delete_dialog::rpcn_confirm_delete_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Confirm Account Deletion")); + setObjectName("rpcn_confirm_delete_dialog"); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QLabel* lbl_description = new QLabel(tr("Are you sure you want to delete RPCN account \"%1\"?\n\n" + "Important:\n" + "Deleting your account will blacklist your username and email for 3 months.\n" + "To confirm, type your username below and click \"Yes\".\n") + .arg(QString::fromStdString(g_cfg_rpcn.get_npid()))); + + QLineEdit* edit_for_delete = new QLineEdit(); + edit_for_delete->setPlaceholderText(tr("Type your username to confirm")); + + QPushButton* btn_yes = new QPushButton(tr("Yes")); + btn_yes->setEnabled(false); + QPushButton* btn_no = new QPushButton(tr("No")); + QDialogButtonBox* btn_box = new QDialogButtonBox(Qt::Horizontal); + btn_box->addButton(btn_yes, QDialogButtonBox::AcceptRole); + btn_box->addButton(btn_no, QDialogButtonBox::RejectRole); + + vbox_global->addWidget(lbl_description); + vbox_global->addWidget(edit_for_delete); + vbox_global->addWidget(btn_box); + + setLayout(vbox_global); + + connect(edit_for_delete, &QLineEdit::textChanged, this, [btn_yes](const QString& text) + { + btn_yes->setEnabled(text == g_cfg_rpcn.get_npid()); + }); + + connect(btn_box, &QDialogButtonBox::accepted, this, [this]() + { + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) + return QDialog::reject(); + + if (auto error = rpcn->delete_account(); error != rpcn::ErrorType::NoError) + { + QString error_message; + switch (error) + { + case rpcn::ErrorType::LoginError: error_message = tr("Invalid login or password."); break; + case rpcn::ErrorType::LoginAlreadyLoggedIn: error_message = tr("Cannot delete a currently logged-in account."); break; + default: error_message = tr("An unknown error occurred."); break; + } + QMessageBox::critical(this, tr("Deletion Failed"), tr("Failed to delete the account:\n%1").arg(error_message), QMessageBox::Ok); + QDialog::reject(); + return; + } + + QMessageBox::information(this, tr("Account Deleted"), tr("Your account has been successfully deleted."), QMessageBox::Ok); + QDialog::accept(); + }); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) : QDialog(parent) { @@ -682,6 +751,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) QPushButton* btn_resendtoken = new QPushButton(tr("Resend Token"), this); QPushButton* btn_change_password = new QPushButton(tr("Change Password"), this); + QPushButton* btn_delete_account = new QPushButton(tr("Delete Account"), this); QPushButton* btn_save = new QPushButton(tr("Save"), this); vbox_labels->addWidget(lbl_username); @@ -694,6 +764,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) hbox_buttons->addWidget(btn_resendtoken); hbox_buttons->addWidget(btn_change_password); + hbox_buttons->addWidget(btn_delete_account); hbox_buttons->addStretch(); hbox_buttons->addWidget(btn_save); @@ -727,6 +798,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) }); connect(btn_resendtoken, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::resend_token); connect(btn_change_password, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::change_password); + connect(btn_delete_account, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::delete_account); g_cfg_rpcn.load(); @@ -770,17 +842,13 @@ void rpcn_account_edit_dialog::resend_token() if (!save_config()) return; - const auto rpcn = rpcn::rpcn_client::get_instance(0); - const std::string npid = g_cfg_rpcn.get_npid(); const std::string password = g_cfg_rpcn.get_password(); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->resend_token(npid, password); error != rpcn::ErrorType::NoError) { @@ -823,13 +891,10 @@ void rpcn_account_edit_dialog::change_password() return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->send_reset_token(*username, *email); error != rpcn::ErrorType::NoError) { @@ -868,13 +933,10 @@ void rpcn_account_edit_dialog::change_password() return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->reset_password(*username, *token, *password); error != rpcn::ErrorType::NoError) { @@ -903,6 +965,17 @@ void rpcn_account_edit_dialog::change_password() } } +void rpcn_account_edit_dialog::delete_account() +{ + if (g_cfg_rpcn.get_npid().empty() || g_cfg_rpcn.get_password().empty()) + { + QMessageBox::warning(this, tr("Account Not Configured"), tr("Please configure your account in the settings before deleting it."), QMessageBox::Ok); + return; + } + rpcn_confirm_delete_dialog dlg_delete(this); + dlg_delete.exec(); +} + void friend_callback(void* param, rpcn::NotificationType ntype, const std::string& username, bool status) { auto* dlg = static_cast(param); @@ -1057,14 +1130,11 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent) setLayout(vbox_global); // Tries to connect to RPCN - m_rpcn = rpcn::rpcn_client::get_instance(0); + m_rpcn = get_rpcn_connection(this); - if (auto res = m_rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure) - { - const QString error_msg = tr("Failed to connect to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); - QMessageBox::warning(parent, tr("Error connecting to RPCN!"), error_msg, QMessageBox::Ok); + if (!m_rpcn) return; - } + if (auto res = m_rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure) { const QString error_msg = tr("Failed to authentify to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.h b/rpcs3/rpcs3qt/rpcn_settings_dialog.h index 3db92bbc7c..89d8253220 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.h +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -83,6 +84,13 @@ private: std::optional m_token; }; +class rpcn_confirm_delete_dialog : public QDialog +{ + Q_OBJECT +public: + rpcn_confirm_delete_dialog(QWidget* parent); +}; + class rpcn_account_edit_dialog : public QDialog { Q_OBJECT @@ -95,6 +103,7 @@ private: private Q_SLOTS: void resend_token(); void change_password(); + void delete_account(); protected: QLineEdit *m_edit_username, *m_edit_token;