Add player history

This commit is contained in:
RipleyTom 2024-02-01 07:38:22 +01:00 committed by Megamouse
parent c04cd2228e
commit c589001dff
5 changed files with 323 additions and 10 deletions

View File

@ -1814,7 +1814,7 @@ error_code sceNpBasicGetFriendPresenceByNpId2(vm::cptr<SceNpId> npid, vm::ptr<Sc
return nph.get_friend_presence_by_npid(*npid, pres.get_ptr());
}
error_code sceNpBasicAddPlayersHistory(vm::cptr<SceNpId> npid, vm::ptr<char> description)
error_code sceNpBasicAddPlayersHistory(vm::cptr<SceNpId> npid, vm::cptr<char> description)
{
sceNp.todo("sceNpBasicAddPlayersHistory(npid=*0x%x, description=*0x%x)", npid, description);
@ -1835,10 +1835,12 @@ error_code sceNpBasicAddPlayersHistory(vm::cptr<SceNpId> npid, vm::ptr<char> des
return SCE_NP_BASIC_ERROR_EXCEEDS_MAX;
}
nph.add_player_to_history(npid.get_ptr(), description ? description.get_ptr() : nullptr);
return CELL_OK;
}
error_code sceNpBasicAddPlayersHistoryAsync(vm::cptr<SceNpId> npids, u32 count, vm::ptr<char> description, vm::ptr<u32> reqId)
error_code sceNpBasicAddPlayersHistoryAsync(vm::cptr<SceNpId> npids, u32 count, vm::cptr<char> description, vm::ptr<u32> reqId)
{
sceNp.todo("sceNpBasicAddPlayersHistoryAsync(npids=*0x%x, count=%d, description=*0x%x, reqId=*0x%x)", npids, count, description, reqId);
@ -1877,7 +1879,7 @@ error_code sceNpBasicAddPlayersHistoryAsync(vm::cptr<SceNpId> npids, u32 count,
return SCE_NP_BASIC_ERROR_EXCEEDS_MAX;
}
auto req_id = nph.add_players_to_history(npids, count);
auto req_id = nph.add_players_to_history(npids.get_ptr(), description ? description.get_ptr() : nullptr, count);
if (reqId)
{
@ -1919,8 +1921,7 @@ error_code sceNpBasicGetPlayersHistoryEntryCount(u32 options, vm::ptr<u32> count
return SCE_NP_ERROR_ID_NOT_FOUND;
}
// TODO: Check if there are players histories
*count = 0;
*count = nph.get_players_history_count(options);
return CELL_OK;
}
@ -1958,6 +1959,11 @@ error_code sceNpBasicGetPlayersHistoryEntry(u32 options, u32 index, vm::ptr<SceN
return SCE_NP_ERROR_ID_NOT_FOUND;
}
if (!nph.get_player_history_entry(options, index, npid.get_ptr()))
{
return SCE_NP_ERROR_ID_NOT_FOUND;
}
return CELL_OK;
}

View File

@ -42,6 +42,7 @@
#endif
#include "util/asm.hpp"
#include "util/yaml.hpp"
#include <span>
@ -55,6 +56,63 @@ LOG_CHANNEL(ticket_log, "Ticket");
namespace np
{
std::string get_players_history_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/players_history.yml";
#else
return fs::get_config_dir() + "players_history.yml";
#endif
}
std::map<std::string, player_history> load_players_history()
{
const auto parsing_error = [](std::string_view error) -> std::map<std::string, player_history>
{
nph_log.error("Error parsing %s: %s", get_players_history_path(), error);
return {};
};
std::map<std::string, player_history> history;
if (fs::file history_file{get_players_history_path(), fs::read + fs::create})
{
auto [yml_players_history, error] = yaml_load(history_file.to_string());
if (!error.empty())
return parsing_error(error);
for (const auto& player : yml_players_history)
{
std::string username = player.first.Scalar();
const auto& seq = player.second;
if (!seq.IsSequence() || seq.size() != 3)
return parsing_error("Player history is not a proper sequence!");
const u64 timestamp = get_yaml_node_value<u64>(seq[0], error);
if (!error.empty())
return parsing_error(error);
std::string description = seq[1].Scalar();
if (!seq[2].IsSequence())
return parsing_error("Expected communication ids sequence");
std::set<std::string> com_ids;
for (usz i = 0; i < seq[2].size(); i++)
{
com_ids.insert(seq[2][i].Scalar());
}
history.insert(std::make_pair(std::move(username), player_history{.timestamp = timestamp, .communication_ids = std::move(com_ids), .description = std::move(description)}));
}
}
return history;
}
ticket::ticket(std::vector<u8>&& raw_data)
: raw_data(raw_data)
{
@ -363,6 +421,13 @@ namespace np
np_handler::np_handler()
{
{
auto history = load_players_history();
std::lock_guard lock(mutex_history);
players_history = std::move(history);
}
g_fxo->need<named_thread<signaling_handler>>();
is_connected = (g_cfg.net.net_active == np_internet_status::enabled);
@ -1217,13 +1282,169 @@ namespace np
return ::at32(match2_req_results, event_key);
}
u32 np_handler::add_players_to_history(vm::cptr<SceNpId> /*npids*/, u32 /*count*/)
player_history& np_handler::get_player_and_set_timestamp(const SceNpId& npid, u64 timestamp)
{
std::string npid_str = std::string(npid.handle.data);
if (!players_history.contains(npid_str))
{
auto [it, success] = players_history.insert(std::make_pair(std::move(npid_str), player_history{.timestamp = timestamp}));
ensure(success);
return it->second;
}
auto& history = ::at32(players_history, npid_str);
history.timestamp = timestamp;
return history;
}
constexpr usz MAX_HISTORY_ENTRIES = 200;
void np_handler::add_player_to_history(const SceNpId* npid, const char* description)
{
std::lock_guard lock(mutex_history);
auto& history = get_player_and_set_timestamp(*npid, get_system_time());
if (description)
history.description = description;
while (players_history.size() > MAX_HISTORY_ENTRIES)
{
auto it = std::min_element(players_history.begin(), players_history.end(), [](const auto& a, const auto& b) { return a.second.timestamp < b.second.timestamp; } );
players_history.erase(it);
}
save_players_history();
}
u32 np_handler::add_players_to_history(const SceNpId* npids, const char* description, u32 count)
{
std::lock_guard lock(mutex_history);
const std::string communication_id_str = std::string(basic_handler.context.data);
for (u32 i = 0; i < count; i++)
{
auto& history = get_player_and_set_timestamp(npids[i], get_system_time());
if (description)
history.description = description;
history.communication_ids.insert(communication_id_str);
}
while (players_history.size() > MAX_HISTORY_ENTRIES)
{
auto it = std::min_element(players_history.begin(), players_history.end(), [](const auto& a, const auto& b) { return a.second.timestamp < b.second.timestamp; } );
players_history.erase(it);
}
save_players_history();
const u32 req_id = get_req_id(REQUEST_ID_HIGH::MISC);
send_basic_event(SCE_NP_BASIC_EVENT_ADD_PLAYERS_HISTORY_RESULT, 0, req_id);
return req_id;
}
u32 np_handler::get_players_history_count(u32 options)
{
const bool all_history = (options == SCE_NP_BASIC_PLAYERS_HISTORY_OPTIONS_ALL);
std::lock_guard lock(mutex_history);
if (all_history)
{
return ::size32(players_history);
}
const std::string communication_id_str = std::string(basic_handler.context.data);
u32 count = 0;
for (auto it = players_history.begin(); it != players_history.end(); it++)
{
if (it->second.communication_ids.contains(communication_id_str))
{
count++;
}
}
return count;
}
bool np_handler::get_player_history_entry(u32 options, u32 index, SceNpId* npid)
{
const bool all_history = (options == SCE_NP_BASIC_PLAYERS_HISTORY_OPTIONS_ALL);
std::lock_guard lock(mutex_history);
if (all_history)
{
auto it = players_history.begin();
std::advance(it, index);
if (it != players_history.end())
{
string_to_npid(it->first, *npid);
return true;
}
}
else
{
const std::string communication_id_str = std::string(basic_handler.context.data);
for (auto it = players_history.begin(); it != players_history.end(); it++)
{
if (it->second.communication_ids.contains(communication_id_str))
{
if (index == 0)
{
string_to_npid(it->first, *npid);
return true;
}
index--;
}
}
}
return false;
}
void np_handler::save_players_history()
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
if (!fs::create_path(path_to_cfg))
{
nph_log.error("Could not create path: %s", path_to_cfg);
}
#endif
fs::file history_file(get_players_history_path(), fs::rewrite);
if (!history_file)
return;
YAML::Emitter out;
out << YAML::BeginMap;
for (const auto& [player_npid, player_info] : players_history)
{
out << player_npid;
out << YAML::BeginSeq;
out << player_info.timestamp;
out << player_info.description;
out << YAML::BeginSeq;
for (const auto& com_id : player_info.communication_ids)
{
out << com_id;
}
out << YAML::EndSeq;
out << YAML::EndSeq;
}
out << YAML::EndMap;
history_file.write(out.c_str(), out.size());
}
u32 np_handler::get_num_friends()
{
return get_rpcn()->get_num_friends();

View File

@ -40,6 +40,13 @@ namespace np
} data;
};
struct player_history
{
u64 timestamp;
std::set<std::string> communication_ids;
std::string description;
};
class ticket
{
public:
@ -215,7 +222,10 @@ namespace np
// Misc stuff
void req_ticket(u32 version, const SceNpId* npid, const char* service_id, const u8* cookie, u32 cookie_size, const char* entitlement_id, u32 consumed_count);
const ticket& get_ticket() const;
u32 add_players_to_history(vm::cptr<SceNpId> npids, u32 count);
void add_player_to_history(const SceNpId* npid, const char* description);
u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count);
u32 get_players_history_count(u32 options);
bool get_player_history_entry(u32 options, u32 index, SceNpId* npid);
bool abort_request(u32 req_id);
// For signaling
@ -346,6 +356,7 @@ namespace np
u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr<SceNpMatching2RequestOptParam> optParam, SceNpMatching2Event event_type);
std::optional<callback_info> take_pending_request(u32 req_id);
private:
shared_mutex mutex_pending_requests;
std::unordered_map<u32, callback_info> pending_requests;
shared_mutex mutex_pending_sign_infos_requests;
@ -356,7 +367,6 @@ namespace np
bool m_inited_np_handler_dependencies = false;
private:
// Basic event handler;
struct
{
@ -441,5 +451,11 @@ namespace np
std::string pr_comment;
std::vector<u8> pr_data;
} presence_self;
player_history& get_player_and_set_timestamp(const SceNpId& npid, u64 timestamp);
void save_players_history();
shared_mutex mutex_history;
std::map<std::string, player_history> players_history; // npid / history
};
} // namespace np

View File

@ -884,6 +884,19 @@ void friend_callback(void* param, rpcn::NotificationType ntype, const std::strin
dlg->callback_handler(ntype, username, status);
}
// Avoid including np_handler.h
namespace np
{
struct player_history
{
u64 timestamp;
std::set<std::string> communication_ids;
std::string description;
};
std::map<std::string, player_history> load_players_history();
}
rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
: QDialog(parent),
m_green_icon(gui::utils::circle_pixmap(QColorConstants::Svg::green, devicePixelRatioF() * 2)),
@ -944,6 +957,14 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
grp_list_blocks->setLayout(vbox_lst_blocks);
hbox_groupboxes->addWidget(grp_list_blocks);
QGroupBox* grp_list_history = new QGroupBox(tr("Recent Players"));
QVBoxLayout* vbox_lst_history = new QVBoxLayout();
m_lst_history = new QListWidget(this);
m_lst_history->setContextMenuPolicy(Qt::CustomContextMenu);
vbox_lst_history->addWidget(m_lst_history);
grp_list_history->setLayout(vbox_lst_history);
hbox_groupboxes->addWidget(grp_list_history);
vbox_global->addLayout(hbox_groupboxes);
setLayout(vbox_global);
@ -990,6 +1011,20 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
add_update_list(m_lst_blocks, QString::fromStdString(blck), m_red_icon, QVariant(false));
}
auto history = np::load_players_history();
std::map<u64, std::string, std::greater<u64>> sorted_history;
for (const auto& [username, user_info] : history)
{
if (!data.friends.contains(username) && !data.requests_sent.contains(username) && !data.requests_received.contains(username))
sorted_history.insert(std::make_pair(user_info.timestamp, std::move(username)));
}
for (const auto& [_, username] : sorted_history)
{
m_lst_history->addItem(new QListWidgetItem(QString::fromStdString(username)));
}
connect(this, &rpcn_friends_dialog::signal_add_update_friend, this, &rpcn_friends_dialog::add_update_friend);
connect(this, &rpcn_friends_dialog::signal_remove_friend, this, &rpcn_friends_dialog::remove_friend);
connect(this, &rpcn_friends_dialog::signal_add_query, this, &rpcn_friends_dialog::add_query);
@ -1041,9 +1076,9 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
std::string str_sel_friend = selected_item->text().toStdString();
QMenu* context_menu = new QMenu();
QAction* remove_friend_action = context_menu->addAction(tr("&Accept Request"));
QAction* accept_request_action = context_menu->addAction(tr("&Accept Request"));
connect(remove_friend_action, &QAction::triggered, this, [this, str_sel_friend]()
connect(accept_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
@ -1059,6 +1094,38 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
context_menu->deleteLater();
});
connect(m_lst_history, &QListWidget::customContextMenuRequested, this, [this](const QPoint& pos)
{
if (!m_lst_history->itemAt(pos) || m_lst_history->selectedItems().count() != 1)
{
return;
}
QListWidgetItem* selected_item = m_lst_history->selectedItems().first();
std::string str_sel_friend = selected_item->text().toStdString();
QMenu* context_menu = new QMenu();
QAction* send_friend_request_action = context_menu->addAction(tr("&Send Friend Request"));
connect(send_friend_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
QMessageBox::critical(this, tr("Error sending a friend request!"), tr("An error occurred while trying to send a friend request!"), QMessageBox::Ok);
}
else
{
QString qstr_friend = QString::fromStdString(str_sel_friend);
add_update_list(m_lst_requests, qstr_friend, m_orange_icon, QVariant(false));
remove_list(m_lst_history, qstr_friend);
}
});
context_menu->exec(m_lst_history->viewport()->mapToGlobal(pos));
context_menu->deleteLater();
});
connect(btn_addfriend, &QAbstractButton::clicked, this, [this]()
{
std::string str_friend_username;
@ -1146,6 +1213,7 @@ void rpcn_friends_dialog::remove_friend(QString name)
void rpcn_friends_dialog::add_query(QString name)
{
add_update_list(m_lst_requests, name, m_yellow_icon, QVariant(true));
remove_list(m_lst_history, name);
}
void rpcn_friends_dialog::callback_handler(rpcn::NotificationType ntype, std::string username, bool status)

View File

@ -131,6 +131,8 @@ private:
QListWidget* m_lst_requests = nullptr;
// list of people blocked by the user
QListWidget* m_lst_blocks = nullptr;
// list of players in history
QListWidget* m_lst_history = nullptr;
std::shared_ptr<rpcn::rpcn_client> m_rpcn;
bool m_rpcn_ok = false;