diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp index 7c0ca6ad..ebca233a 100644 --- a/src/client/component/server_list.cpp +++ b/src/client/component/server_list.cpp @@ -6,7 +6,8 @@ #include #include -#include +#include +#include #include "network.hpp" #include "scheduler.hpp" @@ -15,7 +16,7 @@ namespace server_list { namespace { - utils::hook::detour lua_serverinfo_to_table_hook; + utils::hook::detour lua_serverinfo_to_table_hook; struct state { @@ -25,7 +26,9 @@ namespace server_list callback callback{}; }; - utils::concurrency::container master_state; + utils::concurrency::container master_state; + + std::vector favorite_servers{}; void handle_server_list_response(const game::netadr_t& target, const network::data_view& data, state& s) @@ -75,17 +78,63 @@ namespace server_list } callback(true, result); + } + + void lua_serverinfo_to_table_stub(game::hks::lua_State* state, game::ServerInfo serverInfo, int index) + { + lua_serverinfo_to_table_hook.invoke(state, serverInfo, index); + + if (state) + { + auto botCount = atoi(game::Info_ValueForKey(serverInfo.tags, "bots")); + game::Lua_SetTableInt("botCount", botCount, state); + } } - void lua_serverinfo_to_table_stub(game::hks::lua_State* state, game::ServerInfo serverInfo, int index) - { - lua_serverinfo_to_table_hook.invoke(state, serverInfo, index); + std::string get_favorite_servers_file_path() + { + return "players/user/favorite_servers.csv"; + } - if (state) + void write_favorite_servers() + { + std::string servers_buffer = ""; + for (auto itr : favorite_servers) { - auto botCount = atoi(game::Info_ValueForKey(serverInfo.tags, "bots")); - game::Lua_SetTableInt("botCount", botCount, state); - } + servers_buffer.append(utils::string::va("%u,%u\n", itr.addr, itr.port)); + } + + if (servers_buffer.empty()) + { + return; + } + + utils::io::write_file(get_favorite_servers_file_path(), servers_buffer); + } + + void read_favorite_servers() + { + const std::string path = get_favorite_servers_file_path(); + if (!utils::io::file_exists(path)) + { + return; + } + + favorite_servers.clear(); + + std::string filedata; + if (utils::io::read_file(path, &filedata)) + { + auto servers = utils::string::split(filedata, '\n'); + for (auto server_data : servers) + { + auto data = utils::string::split(server_data, ','); + auto addr = std::stoul(data[0].c_str()); + auto port = (uint16_t)atoi(data[1].c_str()); + auto server = network::address_from_ip(addr, port); + favorite_servers.push_back(server); + } + } } } @@ -112,6 +161,37 @@ namespace server_list network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); }); + } + + void add_favorite_server(game::netadr_t addr) + { + if (has_favorited_server(addr)) + { + return; + } + + favorite_servers.push_back(addr); + write_favorite_servers(); + } + + void remove_favorite_server(game::netadr_t addr) + { + for (auto it = favorite_servers.begin(); it != favorite_servers.end(); ++it) + { + if (network::are_addresses_equal(*it, addr)) + { + favorite_servers.erase(it); + break; + } + } + + write_favorite_servers(); + } + + bool has_favorited_server(game::netadr_t addr) + { + auto it = std::find_if(favorite_servers.begin(), favorite_servers.end(), [&addr](const game::netadr_t& obj) { return network::are_addresses_equal(addr, obj); }); + return it != favorite_servers.end(); } struct component final : client_component @@ -145,9 +225,11 @@ namespace server_list s.callback(false, {}); s.callback = {}; }); - }, scheduler::async, 200ms); + }, scheduler::async, 200ms); + + lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub); - lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub); + read_favorite_servers(); } void pre_destroy() override diff --git a/src/client/component/server_list.hpp b/src/client/component/server_list.hpp index 628795d9..d414f4fc 100644 --- a/src/client/component/server_list.hpp +++ b/src/client/component/server_list.hpp @@ -6,5 +6,9 @@ namespace server_list bool get_master_server(game::netadr_t& address); using callback = std::function&)>; - void request_servers(callback callback); + void request_servers(callback callback); + + void add_favorite_server(game::netadr_t addr); + void remove_favorite_server(game::netadr_t addr); + bool has_favorited_server(game::netadr_t addr); } diff --git a/src/client/steam/interfaces/matchmaking.cpp b/src/client/steam/interfaces/matchmaking.cpp index 8b8dec7b..298ab8fe 100644 --- a/src/client/steam/interfaces/matchmaking.cpp +++ b/src/client/steam/interfaces/matchmaking.cpp @@ -1,5 +1,8 @@ #include -#include "../steam.hpp" +#include "../steam.hpp" + +#include "component/network.hpp" +#include "component/server_list.hpp" namespace steam { @@ -18,13 +21,17 @@ namespace steam int matchmaking::AddFavoriteGame(unsigned int nAppID, unsigned int nIP, unsigned short nConnPort, unsigned short nQueryPort, unsigned int unFlags, unsigned int rTime32LastPlayedOnServer) - { + { + auto addr = network::address_from_ip(htonl(nIP), nConnPort); + server_list::add_favorite_server(addr); return 0; } bool matchmaking::RemoveFavoriteGame(unsigned int nAppID, unsigned int nIP, unsigned short nConnPort, unsigned short nQueryPort, unsigned int unFlags) - { + { + auto addr = network::address_from_ip(htonl(nIP), nConnPort); + server_list::remove_favorite_server(addr); return false; } diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 79f0f784..f85f0711 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -21,7 +21,8 @@ namespace steam gameserveritem_t server_item{}; }; - auto* const internet_request = reinterpret_cast(1); + auto* const internet_request = reinterpret_cast(1); + auto* const favorites_request = reinterpret_cast(4); using servers = std::vector; @@ -198,7 +199,54 @@ namespace steam void* matchmaking_servers::RequestFavoritesServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, matchmaking_server_list_response* pRequestServersResponse) { - return reinterpret_cast(4); + current_response = pRequestServersResponse; + + server_list::request_servers([](const bool success, const std::unordered_set& s) + { + const auto res = current_response.load(); + if (!res) + { + return; + } + + if (!success) + { + res->RefreshComplete(favorites_request, eServerFailedToRespond); + return; + } + + if (s.empty()) + { + res->RefreshComplete(favorites_request, eNoServersListedOnMasterServer); + return; + } + + queried_servers.access([&s](servers& srvs) + { + srvs = {}; + srvs.reserve(s.size()); + + for (auto& address : s) + { + if (!server_list::has_favorited_server(address)) + { + continue; + } + server new_server{}; + new_server.address = address; + new_server.server_item = create_server_item(address, {}, 0, false); + + srvs.push_back(new_server); + } + }); + + for (auto& srv : s) + { + ping_server(srv); + } + }); + + return favorites_request; } void* matchmaking_servers::RequestHistoryServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters,