diff --git a/README.md b/README.md index 212e5ef..48930ce 100644 --- a/README.md +++ b/README.md @@ -84,10 +84,11 @@ The `extract_tickets` tool dumps the `AppTicket` and `ETicket` hex strings you n > **Note:** Tickets are only valid when extracted from an account that **genuinely owns** the game. -### Stats and Achievements -- Enable stats and achievements for unowned games. -- Uses `setStat(appid, "steamid")` to configure which SteamID's achievement data to pull. -- If no `setStat` is configured for an app, falls back to the hardcoded default SteamID `76561198028121353`. +### Stats and Achievements +- Enable stats and achievements for unowned games. +- Uses `setStat(appid, "steamid")` to configure which SteamID's achievement data to pull. +- If no `setStat` is configured for an app, OpenSteamTool queries `https://stats.opensteamtool.com/{appid}` when `[stats] enable_api = true` (default). +- Priority: `setStat` > stats API when enabled and valid > hardcoded preset SteamID `76561198028121353`. ### Online Fix - Add `-onlinefix` to the Steam launch parameters to enable 480-based online play in games that use lobby matchmaking. The current limitation is that only one such game can run at a time.To revert, simply remove -onlinefix from the launch parameters — online play returns to normal on the next launch. @@ -116,9 +117,9 @@ setAppTicket(1361510,"0100000000000000...") -- write AppTicket to the credential setETicket(1361510,"0100000000000000...") -- write ETicket to the credential store; on Windows: HKCU\Software\Valve\Steam\Apps\1361510\ETicket -setStat(1361510, "76561197960287930") -- use the specified SteamID's achievement data for appid 1361510 --- If not configured, default SteamID 76561198028121353 is used. -``` +setStat(1361510, "76561197960287930") -- use the specified SteamID's achievement data for appid 1361510 +-- If not configured, the stats API is used when enabled; otherwise default SteamID 76561198028121353 is used. +``` All function names are **case-insensitive**. `setAppTicket`, `setappticket`, `SetAppticket`, `SETAPPTICKET` etc. are all equivalent. The same applies to every registered function (`addAppId`, `AddToken`, `SETManifestid`, etc.). @@ -139,13 +140,18 @@ url = "opensteamtool" # HTTP timeouts for manifest requests (milliseconds) timeout_resolve_ms = 5000 -timeout_connect_ms = 5000 -timeout_send_ms = 10000 -timeout_recv_ms = 10000 - -# Additional Lua config directories (optional). -# Files are loaded after the default /config/lua folder. -# The default folder is always loaded last so user files take priority. +timeout_connect_ms = 5000 +timeout_send_ms = 10000 +timeout_recv_ms = 10000 + +[stats] +# Query https://stats.opensteamtool.com/{appid} when no Lua setStat override exists. +# Priority: setStat > stats API > hardcoded preset SteamID. +enable_api = true + +# Additional Lua config directories (optional). +# Files are loaded after the default /config/lua folder. +# The default folder is always loaded last so user files take priority. [lua] paths = [] diff --git a/README_ES.md b/README_ES.md index 8053280..5324dca 100644 --- a/README_ES.md +++ b/README_ES.md @@ -87,7 +87,8 @@ La herramienta `extract_tickets` vuelca las cadenas hexadecimales de `AppTicket` ### Estadísticas y logros - Activa las estadísticas y los logros para los juegos que no poseas. - Utiliza `setStat(appid, "steamid")` para configurar de qué SteamID se deben extraer los datos de los logros. -- Si no hay ningún `setStat` configurado para una aplicación, recurre por defecto al SteamID predefinido en el código `76561198028121353`. +- Si no hay ningún `setStat` configurado para una aplicación, OpenSteamTool consulta `https://stats.opensteamtool.com/{appid}` cuando `[stats] enable_api = true` (valor predeterminado). +- Prioridad: `setStat` > API de estadísticas cuando está habilitada y devuelve un valor válido > SteamID predefinido `76561198028121353`. ### Online Fix(Reparacion para habilitar el Online) - Añade `-onlinefix` a los parámetros de lanzamiento de Steam para habilitar el juego en línea basado en el AppId 480 en juegos que utilizan emparejamiento (matchmaking) por salas (lobbies). La limitación actual es que solo se puede ejecutar uno de estos juegos a la vez. Para revertirlo, simplemente elimina -onlinefix de los parámetros de lanzamiento; el juego en línea volverá a la normalidad en el próximo inicio. @@ -117,7 +118,7 @@ setAppTicket(1361510,"0100000000000000...") -- escribe AppTicket (REG_BINARY) en setETicket(1361510,"0100000000000000...") -- escribe ETicket (REG_BINARY) en HKCU\Software\Valve\Steam\Apps\1361510\ETicket setStat(1361510, "76561197960287930") -- utiliza los datos de logros del SteamID especificado para el appid 1361510 --- Si no se configura, se utilizará el SteamID por defecto 76561198028121353. +-- Si no se configura, se utiliza la API de estadísticas cuando está habilitada; de lo contrario se usa el SteamID por defecto 76561198028121353. ``` Los nombres de todas las funciones **no distinguen entre mayúsculas y minúsculas**. `setAppTicket`, `setappticket`, `SetAppticket`, `SETAPPTICKET`, etc., son todas equivalentes. Lo mismo se aplica a cada función registrada (`addAppId`, `AddToken`, `SETManifestid`, etc.). @@ -142,6 +143,11 @@ timeout_connect_ms = 5000 timeout_send_ms = 10000 timeout_recv_ms = 10000 +[stats] +# Consulta https://stats.opensteamtool.com/{appid} cuando no existe setStat en Lua. +# Prioridad: setStat > API de estadísticas > SteamID predefinido. +enable_api = true + # Directorios adicionales de configuración de Lua (opcional). # Los archivos se cargan después de la carpeta predeterminada /config/lua. # La carpeta predeterminada siempre se carga al final para que los archivos del usuario tengan prioridad. diff --git a/opensteamtool.example.toml b/opensteamtool.example.toml index 97b874c..762c3dc 100644 --- a/opensteamtool.example.toml +++ b/opensteamtool.example.toml @@ -56,6 +56,12 @@ timeout_connect_ms = 5000 timeout_send_ms = 10000 timeout_recv_ms = 10000 +[stats] +# Automatically query https://stats.opensteamtool.com/{appid} for a recommended +# SteamID when no Lua setStat(appid, "steamid") override exists. +# Priority: setStat > stats API when enabled and valid > hardcoded preset SteamID. +enable_api = true + # Additional Lua config directories (optional). # Files are loaded after the default /config/lua folder. # The default folder is always loaded last so user files take priority. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ec605f7..e5414fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -113,6 +113,7 @@ add_library(OpenSteamTool SHARED Utils/SteamMetadata/ManifestClient.cpp Utils/SteamMetadata/PatternLoader.cpp Utils/SteamMetadata/RemoteToml.cpp + Utils/SteamMetadata/StatsClient.cpp Utils/SteamMetadata/SteamDiagnostics.cpp # Pipe subsystem: generic handshake registry + ProcessInspector core, plus diff --git a/src/Utils/Config/Config.cpp b/src/Utils/Config/Config.cpp index eaedee3..a779053 100644 --- a/src/Utils/Config/Config.cpp +++ b/src/Utils/Config/Config.cpp @@ -17,6 +17,7 @@ namespace { std::string logDir; std::vector luaPaths; std::string remoteUrlTemplate; + bool statsEnableApi = true; InjectionSettings injection; }; @@ -50,6 +51,7 @@ namespace { logDir = snapshot.logDir; luaPaths = snapshot.luaPaths; remoteUrlTemplate = snapshot.remoteUrlTemplate; + statsEnableApi = snapshot.statsEnableApi; injectEnabled = snapshot.injection.enabled; injectLibraryX86 = snapshot.injection.libraryX86; injectLibraryX64 = snapshot.injection.libraryX64; @@ -80,10 +82,11 @@ namespace { LOG_INFO("Config file not found, using defaults"); ApplyManifestProvider(snapshot.manifestProvider); LoadResult result = ApplySnapshotLocked(snapshot); - LOG_INFO("Config loaded: manifest.url={} log.level={} lua.paths={} remote.url_template={}", + LOG_INFO("Config loaded: manifest.url={} log.level={} lua.paths={} stats.enable_api={} remote.url_template={}", ManifestClient::ActiveProviderName(), ToString(GetLogLevel()), (uint32_t)GetLuaPaths().size(), + GetStatsEnableApi(), GetRemoteUrlTemplate().empty() ? "" : GetRemoteUrlTemplate()); return result; } @@ -135,6 +138,13 @@ namespace { } } + // [stats] + if (auto stats = tbl["stats"].as_table()) { + if (auto val = (*stats)["enable_api"].value()) { + snapshot.statsEnableApi = *val; + } + } + // [inject] if (auto inject = tbl["inject"].as_table()) { if (auto val = (*inject)["enabled"].value()) @@ -147,10 +157,11 @@ namespace { ApplyManifestProvider(snapshot.manifestProvider); LoadResult result = ApplySnapshotLocked(snapshot); - LOG_INFO("Config loaded: manifest.url={} log.level={} lua.paths={} remote.url_template={}", + LOG_INFO("Config loaded: manifest.url={} log.level={} lua.paths={} stats.enable_api={} remote.url_template={}", ManifestClient::ActiveProviderName(), ToString(snapshot.logLevel), (uint32_t)snapshot.luaPaths.size(), + snapshot.statsEnableApi, snapshot.remoteUrlTemplate.empty() ? "" : snapshot.remoteUrlTemplate); return result; @@ -214,4 +225,9 @@ namespace { }; } + bool GetStatsEnableApi() { + std::lock_guard lock(g_mutex); + return statsEnableApi; + } + } diff --git a/src/Utils/Config/Config.h b/src/Utils/Config/Config.h index c681170..d69e764 100644 --- a/src/Utils/Config/Config.h +++ b/src/Utils/Config/Config.h @@ -34,6 +34,7 @@ namespace Config { std::vector GetLuaPaths(); std::string GetRemoteUrlTemplate(); InjectionSettings GetInjectionSettings(); + bool GetStatsEnableApi(); // [manifest] — provider selection lives in ManifestClient (table-driven). inline uint32_t manifestTimeoutResolve = 5000; @@ -50,9 +51,12 @@ namespace Config { // [lua] inline std::vector luaPaths; - // [remote] - inline std::string remoteUrlTemplate; - + // [remote] + inline std::string remoteUrlTemplate; + + // [stats] + inline bool statsEnableApi = true; + // [inject] - optional library injection into game processes. inline bool injectEnabled = false; inline std::string injectLibraryX86; diff --git a/src/Utils/Config/LuaConfig.cpp b/src/Utils/Config/LuaConfig.cpp index 31d2421..905fa12 100644 --- a/src/Utils/Config/LuaConfig.cpp +++ b/src/Utils/Config/LuaConfig.cpp @@ -2,6 +2,7 @@ #include "OSTPlatform/include/Http.h" #include "OSTPlatform/include/Numbers.h" #include "Utils/Config/LuaConfig.h" +#include "Utils/SteamMetadata/StatsClient.h" #include "Utils/Tickets/AppTicket.h" #include @@ -511,6 +512,9 @@ namespace LuaConfig{ uint64_t GetStatSteamId(AppId_t AppId) { if (StatSteamIdSet.count(AppId)) return StatSteamIdSet[AppId]; + uint64_t apiSteamId = 0; + if (StatsClient::FetchStatSteamId(AppId, &apiSteamId)) + return apiSteamId; return kDefaultStatSteamId; } diff --git a/src/Utils/SteamMetadata/StatsClient.cpp b/src/Utils/SteamMetadata/StatsClient.cpp new file mode 100644 index 0000000..8080293 --- /dev/null +++ b/src/Utils/SteamMetadata/StatsClient.cpp @@ -0,0 +1,74 @@ +#include "StatsClient.h" + +#include "OSTPlatform/include/Http.h" +#include "OSTPlatform/include/Numbers.h" +#include "Utils/Config/Config.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include +#include +#include + +namespace StatsClient { +namespace { + + std::mutex g_mutex; + std::unordered_map g_cache; + + bool ParseSteamId(std::string_view body, uint64_t* outSteamId) { + const auto parsed = OSTPlatform::Numbers::ParseUInt64(body); + if (!parsed || *parsed == 0) return false; + *outSteamId = *parsed; + return true; + } + +} // namespace + +bool FetchStatSteamId(AppId_t appId, uint64_t* outSteamId) { + if (!outSteamId || appId == k_uAppIdInvalid) return false; + + if (!Config::GetStatsEnableApi()) { + LOG_ACHIEVEMENT_DEBUG("Stats SteamID API disabled for appid={}", appId); + return false; + } + + { + std::lock_guard lock(g_mutex); + auto it = g_cache.find(appId); + if (it != g_cache.end()) { + *outSteamId = it->second; + LOG_ACHIEVEMENT_DEBUG("Stats SteamID API cache hit appid={} steamid={}", appId, *outSteamId); + return true; + } + } + + char url[128]; + std::snprintf(url, sizeof(url), "https://stats.opensteamtool.com/%u", appId); + + auto r = OSTPlatform::Http::Execute(L"GET", url); + LOG_ACHIEVEMENT_INFO("Stats SteamID API status={} appid={}", r.status, appId); + + if (!r.ok || r.status != 200) { + LOG_ACHIEVEMENT_WARN("Stats SteamID API failed appid={} status={} ok={}", appId, r.status, r.ok); + return false; + } + + uint64_t steamId = 0; + if (!ParseSteamId(r.body, &steamId)) { + LOG_ACHIEVEMENT_WARN("Stats SteamID API returned invalid body appid={} bytes={}", appId, r.body.size()); + return false; + } + + { + std::lock_guard lock(g_mutex); + g_cache[appId] = steamId; + } + + *outSteamId = steamId; + LOG_ACHIEVEMENT_INFO("Stats SteamID API resolved appid={} steamid={}", appId, steamId); + return true; +} + +} // namespace StatsClient diff --git a/src/Utils/SteamMetadata/StatsClient.h b/src/Utils/SteamMetadata/StatsClient.h new file mode 100644 index 0000000..334d57b --- /dev/null +++ b/src/Utils/SteamMetadata/StatsClient.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Steam/Types.h" + +#include + +namespace StatsClient { + + bool FetchStatSteamId(AppId_t appId, uint64_t* outSteamId); + +} // namespace StatsClient