diff --git a/data/ui_scripts/frontend_menus/__init__.lua b/data/ui_scripts/frontend_menus/__init__.lua index 7ee38ff2..30cba4f5 100644 --- a/data/ui_scripts/frontend_menus/__init__.lua +++ b/data/ui_scripts/frontend_menus/__init__.lua @@ -87,7 +87,10 @@ end local addCustomButtons = function(controller, menuId, buttonTable, isLeader) if menuId == LobbyData.UITargets.UI_MPLOBBYMAIN.id then utils.RemoveSpaces(buttonTable) - utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.THEATER_MP) - 1) + local theaterIndex = utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.THEATER_MP) + if theaterIndex ~= nil then + utils.AddSpacer(buttonTable, theaterIndex - 1) + end end if menuId == LobbyData.UITargets.UI_MPLOBBYONLINE.id or menuId == LobbyData.UITargets.UI_ZMLOBBYONLINE.id then @@ -121,11 +124,14 @@ local addCustomButtons = function(controller, menuId, buttonTable, isLeader) if menuId == LobbyData.UITargets.UI_ZMLOBBYONLINE.id then utils.RemoveButton(buttonTable, CoD.LobbyButtons.THEATER_ZM) - utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.THEATER_ZM, #buttonTable + 1) + utils.AddLargeButton(controller, buttonTable, CoD.LobbyButtons.THEATER_ZM) utils.RemoveSpaces(buttonTable) utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.SERVER_BROWSER)) - utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.ZM_BUBBLEGUM_BUFFS) - 1) + local bgbIndex = utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.ZM_BUBBLEGUM_BUFFS) + if bgbIndex ~= nil then + utils.AddSpacer(buttonTable, bgbIndex - 1) + end utils.AddSpacer(buttonTable, utils.GetButtonIndex(buttonTable, CoD.LobbyButtons.STATS)) end end diff --git a/data/ui_scripts/frontend_menus/utils.lua b/data/ui_scripts/frontend_menus/utils.lua index 7abe1926..f394e0ae 100644 --- a/data/ui_scripts/frontend_menus/utils.lua +++ b/data/ui_scripts/frontend_menus/utils.lua @@ -17,6 +17,9 @@ local SetButtonState = function(button, state) end local RemoveButton = function(buttonTable, button) + if not button then + return + end for id, v in pairs(buttonTable) do if buttonTable[id].optionDisplay == button.stringRef then table.remove(buttonTable, id) @@ -31,6 +34,9 @@ local RemoveSpaces = function(buttonTable) end local GetButtonIndex = function(buttonTable, button) + if not button then + return nil + end for id, v in pairs(buttonTable) do if buttonTable[id].optionDisplay == button.stringRef then return id diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp index 9e3fcbfc..e21a5fc9 100644 --- a/src/client/component/server_list.cpp +++ b/src/client/component/server_list.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "network.hpp" #include "scheduler.hpp" @@ -20,15 +21,17 @@ namespace server_list struct state { game::netadr_t address{}; - bool requesting{false}; + bool requesting{ false }; std::chrono::high_resolution_clock::time_point query_start{}; callback callback{}; }; utils::concurrency::container master_state; + utils::concurrency::container favorite_servers{}; + void handle_server_list_response(const game::netadr_t& target, - const network::data_view& data, state& s) + const network::data_view& data, state& s) { if (!s.requesting || s.address != target) { @@ -87,6 +90,48 @@ namespace server_list game::Lua_SetTableInt("botCount", botCount, state); } } + + std::string get_favorite_servers_file_path() + { + return "players/user/favorite_servers.txt"; + } + + void write_favorite_servers() + { + favorite_servers.access([&](std::unordered_set& servers) + { + std::string servers_buffer = ""; + for (auto itr : servers) + { + servers_buffer.append(utils::string::va("%i.%i.%i.%i:%u\n", itr.ipv4.a, itr.ipv4.b, itr.ipv4.c, itr.ipv4.d, itr.port)); + } + 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.access([&path](std::unordered_set& servers) + { + servers.clear(); + std::string filedata; + if (utils::io::read_file(path, &filedata)) + { + auto srv = utils::string::split(filedata, '\n'); + for (auto server_address : srv) + { + auto server = network::address_from_string(server_address); + servers.insert(server); + } + } + }); + } } bool get_master_server(game::netadr_t& address) @@ -98,20 +143,50 @@ namespace server_list void request_servers(callback callback) { master_state.access([&callback](state& s) - { - game::netadr_t addr{}; - if (!get_master_server(addr)) { - return; - } + game::netadr_t addr{}; + if (!get_master_server(addr)) + { + return; + } - s.requesting = true; - s.address = addr; - s.callback = std::move(callback); - s.query_start = std::chrono::high_resolution_clock::now(); + s.requesting = true; + s.address = addr; + s.callback = std::move(callback); + s.query_start = std::chrono::high_resolution_clock::now(); - network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); + network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); + }); + } + + void add_favorite_server(game::netadr_t addr) + { + favorite_servers.access([&addr](std::unordered_set& servers) + { + servers.insert(addr); }); + write_favorite_servers(); + } + + void remove_favorite_server(game::netadr_t addr) + { + favorite_servers.access([&addr](std::unordered_set& servers) + { + for (auto it = servers.begin(); it != servers.end(); ++it) + { + if (network::are_addresses_equal(*it, addr)) + { + servers.erase(it); + break; + } + } + }); + write_favorite_servers(); + } + + utils::concurrency::container& get_favorite_servers() + { + return favorite_servers; } struct component final : client_component @@ -119,44 +194,49 @@ namespace server_list void post_unpack() override { network::on("getServersResponse", [](const game::netadr_t& target, const network::data_view& data) - { - master_state.access([&](state& s) { - handle_server_list_response(target, data, s); + master_state.access([&](state& s) + { + handle_server_list_response(target, data, s); + }); }); - }); scheduler::loop([] - { - master_state.access([](state& s) { - if (!s.requesting) - { - return; - } + master_state.access([](state& s) + { + if (!s.requesting) + { + return; + } - const auto now = std::chrono::high_resolution_clock::now(); - if ((now - s.query_start) < 2s) - { - return; - } + const auto now = std::chrono::high_resolution_clock::now(); + if ((now - s.query_start) < 2s) + { + return; + } - s.requesting = false; - s.callback(false, {}); - s.callback = {}; - }); - }, scheduler::async, 200ms); + s.requesting = false; + s.callback(false, {}); + s.callback = {}; + }); + }, scheduler::async, 200ms); lua_serverinfo_to_table_hook.create(0x141F1FD10_g, lua_serverinfo_to_table_stub); + + scheduler::once([] + { + read_favorite_servers(); + }, scheduler::main); } void pre_destroy() override { master_state.access([](state& s) - { - s.requesting = false; - s.callback = {}; - }); + { + s.requesting = false; + s.callback = {}; + }); } }; } diff --git a/src/client/component/server_list.hpp b/src/client/component/server_list.hpp index 628795d9..aed5cc71 100644 --- a/src/client/component/server_list.hpp +++ b/src/client/component/server_list.hpp @@ -1,10 +1,17 @@ #pragma once -#include +#include + +#include 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); + using server_list = std::unordered_set; + utils::concurrency::container& get_favorite_servers(); } 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 2bc6127b..cb39d939 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -22,11 +22,14 @@ namespace steam }; auto* const internet_request = reinterpret_cast(1); + auto* const favorites_request = reinterpret_cast(4); using servers = std::vector; - ::utils::concurrency::container queried_servers{}; - std::atomic current_response{}; + ::utils::concurrency::container internet_servers{}; + ::utils::concurrency::container favorites_servers{}; + std::atomic internet_response{}; + std::atomic favorites_response{}; template void copy_safe(T& dest, const char* in) @@ -84,11 +87,12 @@ namespace steam } void handle_server_respone(const bool success, const game::netadr_t& host, const ::utils::info_string& info, - const uint32_t ping) + const uint32_t ping, ::utils::concurrency::container& server_list, + std::atomic& response, void* request) { bool all_handled = false; std::optional index{}; - queried_servers.access([&](servers& srvs) + server_list.access([&](servers& srvs) { size_t i = 0; for (; i < srvs.size(); ++i) @@ -122,7 +126,7 @@ namespace steam all_handled = true; }); - const auto res = current_response.load(); + const auto res = response.load(); if (!index || !res) { return; @@ -130,33 +134,46 @@ namespace steam if (success) { - res->ServerResponded(internet_request, *index); + res->ServerResponded(request, *index); } else { - res->ServerFailedToRespond(internet_request, *index); + res->ServerFailedToRespond(request, *index); } if (all_handled) { - res->RefreshComplete(internet_request, eServerResponded); + res->RefreshComplete(request, eServerResponded); } } - void ping_server(const game::netadr_t& server) + void handle_internet_server_response(const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) { - party::query_server(server, handle_server_respone); + handle_server_respone(success, host, info, ping, internet_servers, internet_response, internet_request); + } + + + void handle_favorites_server_response(const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) + { + handle_server_respone(success, host, info, ping, favorites_servers, favorites_response, favorites_request); + } + + void ping_server(const game::netadr_t& server, party::query_callback callback) + { + party::query_server(server, callback); } } void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, matchmaking_server_list_response* pRequestServersResponse) { - current_response = pRequestServersResponse; + internet_response = pRequestServersResponse; server_list::request_servers([](const bool success, const std::unordered_set& s) { - const auto res = current_response.load(); + const auto res = internet_response.load(); if (!res) { return; @@ -174,7 +191,7 @@ namespace steam return; } - queried_servers.access([&s](servers& srvs) + internet_servers.access([&s](servers& srvs) { srvs = {}; srvs.reserve(s.size()); @@ -191,7 +208,7 @@ namespace steam for (auto& srv : s) { - ping_server(srv); + ping_server(srv, handle_internet_server_response); } }); @@ -213,7 +230,45 @@ namespace steam void* matchmaking_servers::RequestFavoritesServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, matchmaking_server_list_response* pRequestServersResponse) { - return reinterpret_cast(4); + favorites_response = pRequestServersResponse; + + auto& srvs = server_list::get_favorite_servers(); + srvs.access([&](std::unordered_set s) + { + const auto res = favorites_response.load(); + if (!res) + { + return; + } + + if (s.empty()) + { + res->RefreshComplete(favorites_request, eNoServersListedOnMasterServer); + return; + } + + favorites_servers.access([s](servers& srvs) + { + srvs = {}; + srvs.reserve(s.size()); + + for (auto& address : s) + { + 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, handle_favorites_server_response); + } + }); + + return favorites_request; } void* matchmaking_servers::RequestHistoryServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, @@ -232,19 +287,25 @@ namespace steam { if (internet_request == hServerListRequest) { - current_response = nullptr; + internet_response = nullptr; + } + if (favorites_request == hServerListRequest) + { + favorites_response = nullptr; } } gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) { - if (internet_request != hRequest) + if (internet_request != hRequest && favorites_request != hRequest) { return nullptr; } + auto& servers_list = hRequest == favorites_request ? favorites_servers : internet_servers; + static thread_local gameserveritem_t server_item{}; - return queried_servers.access([iServer](const servers& s) -> gameserveritem_t* { + return servers_list.access([iServer](const servers& s) -> gameserveritem_t* { if (iServer < 0 || static_cast(iServer) >= s.size()) { return nullptr; @@ -270,12 +331,13 @@ namespace steam int matchmaking_servers::GetServerCount(void* hRequest) { - if (internet_request != hRequest) + if (internet_request != hRequest && favorites_request != hRequest) { return 0; } - return queried_servers.access([](const servers& s) + auto& servers_list = hRequest == favorites_request ? favorites_servers : internet_servers; + return servers_list.access([](const servers& s) { return static_cast(s.size()); }); @@ -283,13 +345,14 @@ namespace steam void matchmaking_servers::RefreshServer(void* hRequest, const int iServer) { - if (internet_request != hRequest) + if (internet_request != hRequest && favorites_request != hRequest) { return; } std::optional address{}; - queried_servers.access([&](const servers& s) + auto& servers_list = hRequest == favorites_request ? favorites_servers : internet_servers; + servers_list.access([&](const servers& s) { if (iServer < 0 || static_cast(iServer) >= s.size()) { @@ -301,7 +364,8 @@ namespace steam if (address) { - ping_server(*address); + auto callback = hRequest == favorites_request ? handle_favorites_server_response : handle_internet_server_response; + ping_server(*address, callback); } }