diff --git a/dll/dll/settings.h b/dll/dll/settings.h index d37823aa..33d14fa9 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -170,7 +170,21 @@ struct Overlay_Appearance { static NotificationPosition translate_notification_position(const std::string &str); }; +struct Branch_Info { + std::string name{}; + std::string description{}; + bool branch_protected = false; + EBetaBranchFlags flags = EBetaBranchFlags::k_EBetaBranch_None; + uint32 build_id = 10; // not sure if 0 as an initial value is a good idea + uint32 time_updated_epoch = (uint32)std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // may be changed by the game, I assume only 1 branch should be active + // added in sdk 1.60 and currently unused + bool active = false; +}; + class Settings { +private: CSteamID steam_id{}; // user id CGameID game_id{}; std::string name{}; @@ -197,7 +211,6 @@ class Settings { std::string supported_languages{}; public: - //Depots std::vector depots{}; @@ -214,9 +227,6 @@ public: bool matchmaking_server_list_always_lan_type = true; bool matchmaking_server_details_via_source_query = false; - //app build id - int build_id = 10; - //make lobby creation fail in the matchmaking interface bool disable_lobby_creation = false; @@ -259,9 +269,11 @@ public: // get the alpha-2 code from: https://www.iban.com/country-codes std::string ip_country = "US"; + // branches info //is playing on beta branch + current/forced branch name bool is_beta_branch = false; - std::string current_branch_name = "public"; + long selected_branch_idx{}; + std::vector branches{}; // in settings parser we must ensure we have the default "public" branch, force-add it if not defined by the user //controller struct Controller_Settings controller_settings{}; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 75112633..21ce4472 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1135,18 +1135,6 @@ static void parse_mods_folder(class Settings *settings_client, Settings *setting -// app::general::build_id -static void parse_build_id(class Settings *settings_client, class Settings *settings_server) -{ - std::string line(common_helpers::string_strip(ini.GetValue("app::general", "build_id", ""))); - if (line.size()) { - int build_id = std::stoi(line); - PRINT_DEBUG(" setting build id = %i", build_id); - settings_client->build_id = build_id; - settings_server->build_id = build_id; - } -} - // main::general::crash_printer_location static void parse_crash_printer_location() { @@ -1195,6 +1183,96 @@ static void parse_auto_accept_invite(class Settings *settings_client, class Sett } } +// branches.json +static bool parse_branches_file( + const std::string &base_path, const bool force_load, + class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) +{ + static constexpr auto branches_json_file = "branches.json"; + + std::vector result{}; + long public_branch_idx = -1; + long user_branch_idx = -1; + + std::string branches_file = base_path + branches_json_file; + auto branches = nlohmann::json{}; + if (!local_storage->load_json(branches_file, branches) && !force_load) { + return false; + } + + // app::general::branch_name + std::string selected_branch = common_helpers::string_strip(ini.GetValue("app::general", "branch_name", "")); + if (selected_branch.empty()) { + selected_branch = "public"; + PRINT_DEBUG("no branch name specified, defaulting to 'public'"); + } + PRINT_DEBUG("selected branch name '%s'", selected_branch.c_str()); + + PRINT_DEBUG("loaded %zu branches from file '%s'", branches.size(), branches_file.c_str()); + auto current_epoch = (uint32)std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + for (const auto &branch_data : branches) { + auto &new_banch = result.emplace_back(Branch_Info{}); + + new_banch.name = branch_data.value("name", new_banch.name); + new_banch.description = branch_data.value("description", new_banch.description); + new_banch.branch_protected = branch_data.value("protected", new_banch.branch_protected); + new_banch.build_id = branch_data.value("build_id", new_banch.build_id); + new_banch.time_updated_epoch = branch_data.value("time_updated", new_banch.time_updated_epoch); + + new_banch.flags = EBetaBranchFlags::k_EBetaBranch_Available; + if (new_banch.branch_protected) { + new_banch.flags = static_cast(new_banch.flags | EBetaBranchFlags::k_EBetaBranch_Private); + } + if (public_branch_idx < 0 && common_helpers::str_cmp_insensitive("public", new_banch.name)) { + public_branch_idx = static_cast(result.size() - 1); + PRINT_DEBUG("found default 'public' branch [%li]", public_branch_idx); + } + if (user_branch_idx < 0 && common_helpers::str_cmp_insensitive(selected_branch, new_banch.name)) { + user_branch_idx = static_cast(result.size() - 1); + PRINT_DEBUG("found your branch '%s' [%li]", selected_branch.c_str(), user_branch_idx); + } + + PRINT_DEBUG("added branch '%s'", new_banch.name.c_str()); + PRINT_DEBUG(" description '%s'", new_banch.description.c_str()); + PRINT_DEBUG(" branch_protected %i", (int)new_banch.branch_protected); + PRINT_DEBUG(" build_id %u", new_banch.build_id); + PRINT_DEBUG(" time_updated_epoch %u", new_banch.time_updated_epoch); + } + + if (public_branch_idx < 0) { + PRINT_DEBUG("[?] 'public' branch not found, adding it"); + auto &public_branch = result.emplace_back(Branch_Info{}); + public_branch_idx = static_cast(result.size() - 1); + } + + if (user_branch_idx < 0) { + PRINT_DEBUG("[?] selected branch '%s' wasn't loaded, forcing selection to the default 'public'", selected_branch.c_str()); + user_branch_idx = public_branch_idx; + } + + { + auto& public_branch = result[public_branch_idx]; + public_branch.name = "public"; + public_branch.flags = static_cast(public_branch.flags | EBetaBranchFlags::k_EBetaBranch_Default | EBetaBranchFlags::k_EBetaBranch_Available); + } + + { + auto& user_branch = result[user_branch_idx]; + user_branch.active = true; + user_branch.flags = static_cast(user_branch.flags | EBetaBranchFlags::k_EBetaBranch_Available | EBetaBranchFlags::k_EBetaBranch_Installed | EBetaBranchFlags::k_EBetaBranch_Selected); + } + + settings_client->branches = result; + settings_server->branches = result; + + settings_client->selected_branch_idx = user_branch_idx; + settings_server->selected_branch_idx = user_branch_idx; + + PRINT_DEBUG("selected branch index in the list [%li]", user_branch_idx); + + return true; +} + // user::general::ip_country static void parse_ip_country(class Local_Storage *local_storage, class Settings *settings_client, class Settings *settings_server) { @@ -1264,16 +1342,6 @@ static void parse_overlay_general_config(class Settings *settings_client, class // mainly enable/disable features static void parse_simple_features(class Settings *settings_client, class Settings *settings_server) { - // app::general::branch_name - { - std::string line(common_helpers::string_strip(ini.GetValue("app::general", "branch_name", ""))); - if (line.size()) { - settings_client->current_branch_name = line; - settings_server->current_branch_name = line; - PRINT_DEBUG("setting current branch name to '%s'", line.c_str()); - } - } - // [main::general] settings_client->enable_new_app_ticket = ini.GetBoolValue("main::general", "new_app_ticket", settings_client->enable_new_app_ticket); settings_server->enable_new_app_ticket = ini.GetBoolValue("main::general", "new_app_ticket", settings_server->enable_new_app_ticket); @@ -1595,8 +1663,6 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s settings_client->set_supported_languages(supported_languages); settings_server->set_supported_languages(supported_languages); - parse_build_id(settings_client, settings_server); - parse_simple_features(settings_client, settings_server); parse_dlc(settings_client, settings_server); @@ -1614,6 +1680,11 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s load_gamecontroller_settings(settings_client); parse_auto_accept_invite(settings_client, settings_server); parse_ip_country(local_storage, settings_client, settings_server); + + // try local "steam_settings" then saves path, on second trial force load defaults + if (!parse_branches_file(steam_settings_path, false, settings_client, settings_server, local_storage)) { + parse_branches_file(local_storage->get_global_settings_path(), true, settings_client, settings_server, local_storage); + } parse_overlay_general_config(settings_client, settings_server); load_overlay_appearance(settings_client, settings_server, local_storage); diff --git a/dll/steam_apps.cpp b/dll/steam_apps.cpp index 71979551..f33c37ce 100644 --- a/dll/steam_apps.cpp +++ b/dll/steam_apps.cpp @@ -243,9 +243,11 @@ bool Steam_Apps::GetCurrentBetaName( char *pchName, int cchNameBufferSize ) { PRINT_DEBUG("%p [%i]", pchName, cchNameBufferSize); std::lock_guard lock(global_mutex); - if (pchName && cchNameBufferSize > 0 && static_cast(cchNameBufferSize) > settings->current_branch_name.size()) { + + const auto ¤t_branch_name = settings->branches[settings->selected_branch_idx].name; + if (pchName && cchNameBufferSize > 0 && static_cast(cchNameBufferSize) > current_branch_name.size()) { memset(pchName, 0, cchNameBufferSize); - memcpy(pchName, settings->current_branch_name.c_str(), settings->current_branch_name.size()); + memcpy(pchName, current_branch_name.c_str(), current_branch_name.size()); } PRINT_DEBUG("returned '%s'", pchName); @@ -365,7 +367,7 @@ int Steam_Apps::GetAppBuildId() { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - return this->settings->build_id; + return static_cast(this->settings->branches[settings->selected_branch_idx].build_id); } @@ -462,24 +464,53 @@ bool Steam_Apps::BIsTimedTrial( uint32* punSecondsAllowed, uint32* punSecondsPla return false; } +// TODO no public docs // set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions bool Steam_Apps::SetDlcContext( AppId_t nAppID ) { - PRINT_DEBUG("%u", nAppID); + PRINT_DEBUG("%u // TODO", nAppID); std::lock_guard lock(global_mutex); - return true; + + if (nAppID == 0) return false; // TODO is this correct? (see Steam_Apps::BIsDlcInstalled) + if (nAppID == UINT32_MAX) return false; // TODO is this correct? (see Steam_Apps::BIsDlcInstalled) + + if (nAppID == settings->get_local_game_id().AppID()) return true; // TODO is this correct? + + return settings->hasDLC(nAppID); } +// TODO no public docs // returns total number of known app beta branches (including default "public" branch ) int Steam_Apps::GetNumBetas( int *pnAvailable, int *pnPrivate ) { PRINT_DEBUG("%p, %p", pnAvailable, pnPrivate); std::lock_guard lock(global_mutex); - if (pnAvailable) *pnAvailable = 1; // TODO what is this? - if (pnPrivate) *pnPrivate = 0; // TODO what is this? - // There is no "betas.txt" we, we always return 1 since "public" branch - return 1; + // I assume 'available' means installed on the user's disk and could be used + // in that case only 1 should be *available* since the user can only have 1 active and usable branch with the emu, unlike real steam + // the user can switch the active (available) branch from configs.app.ini + // right?? + if (pnAvailable) { // TODO what is this? + *pnAvailable = 0; + for (const auto &item : settings->branches) { + if (item.flags & EBetaBranchFlags::k_EBetaBranch_Available) { + *pnAvailable += 1; + } + } + PRINT_DEBUG("available branches = %i", *pnAvailable); + } + + if (pnPrivate) { + *pnPrivate = 0; + for (const auto &item : settings->branches) { + if (item.flags & EBetaBranchFlags::k_EBetaBranch_Private) { + *pnPrivate += 1; + } + } + PRINT_DEBUG("private branches = %i", *pnPrivate); + } + + return static_cast(settings->branches.size()); // we always return at least 1 since "public" branch } // TODO no public docs @@ -487,45 +518,57 @@ int Steam_Apps::GetNumBetas( int *pnAvailable, int *pnPrivate ) bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) // iterate through { // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range - PRINT_DEBUG("%i %p %p --- %p %i --- %p %i", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); + PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); std::lock_guard lock(global_mutex); if (iBetaIndex < 0) return false; - if (iBetaIndex != 0) return false; // TODO remove this once we have a proper betas/branches list - // if (iBetaIndex >= settings->beta_branches.size()) return false; // TODO implement this + if (static_cast(iBetaIndex) >= settings->branches.size()) return false; - if (punFlags) { - *punFlags = EBetaBranchFlags::k_EBetaBranch_Default | EBetaBranchFlags::k_EBetaBranch_Available | - EBetaBranchFlags::k_EBetaBranch_Selected | EBetaBranchFlags::k_EBetaBranch_Installed; - } + const auto &branch = settings->branches[iBetaIndex]; + + if (punFlags) *punFlags = branch.flags; + if (punBuildID) *punBuildID = branch.build_id; - if (punBuildID) *punBuildID = 0; - - if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > settings->current_branch_name.size()) { + if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > branch.name.size()) { memset(pchBetaName, 0, cchBetaName); - memcpy(pchBetaName, settings->current_branch_name.c_str(), settings->current_branch_name.size()); + memcpy(pchBetaName, branch.name.c_str(), branch.name.size()); } - std::string description = "public"; - if (pchDescription && cchDescription > 0 && static_cast(cchDescription) > description.size()) { + if (pchDescription && cchDescription > 0 && static_cast(cchDescription) > branch.description.size()) { memset(pchDescription, 0, cchDescription); - memcpy(pchDescription, description.c_str(), description.size()); + memcpy(pchDescription, branch.description.c_str(), branch.description.size()); } return true; } +// TODO no public docs // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch bool Steam_Apps::SetActiveBeta( const char *pchBetaName ) { PRINT_DEBUG("'%s'", pchBetaName); std::lock_guard lock(global_mutex); - if (!pchBetaName || !pchBetaName[0]) return false; + // (sdk 1.60) apparently steam doesn't verify this condition, tested by 'universal963' on appid 480 + //if (!pchBetaName) return false; - // TODO check if branch name in betas.txt once we implement that + std::string beta_name = pchBetaName ? pchBetaName : ""; + auto branch_it = std::find_if(settings->branches.begin(), settings->branches.end(), [&beta_name](const Branch_Info &item){ + return common_helpers::str_cmp_insensitive(beta_name, item.name); + }); - return true; + if (settings->branches.end() != branch_it) { + // reset the 'active' flag for all branches + for (auto &item : settings->branches) { + item.active = false; + } + // then set the flag for this branch + branch_it->active = true; + PRINT_DEBUG("game changed active beta branch!"); + return true; + } + + return true; // (sdk 1.60) apparently steam doesn't even care and just returns true anyway, tested by 'universal963' on appid 480 } diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 1fe2905c..3e82e8c3 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -3,13 +3,11 @@ # ############################################################################## # [app::general] -# allow the app/game to show the correct build id -# 0 means you're not running a build downloaded from steam -build_id=1234 -# by default the emu will report a `non-beta` branch with the name `public` when the game calls `Steam_Apps::GetCurrentBetaName()` +# by default the emu will report a `non-beta` branch when the game calls `Steam_Apps::GetCurrentBetaName()` # make the game/app think we're playing on a beta branch is_beta_branch=0 -# the name of the beta branch +# the name of the current branch, this must also exist in branches.json +# otherwise will be ignored by the emu and the default 'public' branch will be used branch_name=public [app::dlcs]