diff --git a/SConstruct b/SConstruct index 117528de37c..e63abacbceb 100644 --- a/SConstruct +++ b/SConstruct @@ -254,6 +254,7 @@ opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise e opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False)) opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determines RAM use)", "0") opts.Add(BoolVariable("engine_update_check", "Enable engine update checks in the Project Manager", True)) +opts.Add(BoolVariable("steamapi", "Enable minimal SteamAPI integration for usage time tracking (editor only)", False)) # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) diff --git a/main/SCsub b/main/SCsub index 27b746a9379..f3807167a2a 100644 --- a/main/SCsub +++ b/main/SCsub @@ -10,6 +10,9 @@ env_main = env.Clone() env_main.add_source_files(env.main_sources, "*.cpp") +if env["steamapi"] and env.editor_build: + env_main.Append(CPPDEFINES=["STEAMAPI_ENABLED"]) + if env["tests"]: env_main.Append(CPPDEFINES=["TESTS_ENABLED"]) diff --git a/main/main.cpp b/main/main.cpp index 2bd421e5af9..4fea497d08e 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -112,6 +112,10 @@ #endif // DISABLE_DEPRECATED #endif // TOOLS_ENABLED +#if defined(STEAMAPI_ENABLED) +#include "main/steam_tracker.h" +#endif + #include "modules/modules_enabled.gen.h" // For mono. #if defined(MODULE_MONO_ENABLED) && defined(TOOLS_ENABLED) @@ -142,6 +146,10 @@ static ZipArchive *zip_packed_data = nullptr; #endif static MessageQueue *message_queue = nullptr; +#if defined(STEAMAPI_ENABLED) +static SteamTracker *steam_tracker = nullptr; +#endif + // Initialized in setup2() static AudioServer *audio_server = nullptr; static CameraServer *camera_server = nullptr; @@ -2428,6 +2436,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->benchmark_end_measure("Startup", "Core"); +#if defined(STEAMAPI_ENABLED) + if (editor || project_manager) { + steam_tracker = memnew(SteamTracker); + } +#endif + if (p_second_phase) { return setup2(); } @@ -2486,6 +2500,12 @@ error: OS::get_singleton()->benchmark_end_measure("Startup", "Core"); OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); +#if defined(STEAMAPI_ENABLED) + if (steam_tracker) { + memdelete(steam_tracker); + } +#endif + OS::get_singleton()->finalize_core(); locale = String(); @@ -4313,6 +4333,12 @@ void Main::cleanup(bool p_force) { message_queue->flush(); memdelete(message_queue); +#if defined(STEAMAPI_ENABLED) + if (steam_tracker) { + memdelete(steam_tracker); + } +#endif + unregister_core_driver_types(); unregister_core_extensions(); uninitialize_modules(MODULE_INITIALIZATION_LEVEL_CORE); diff --git a/main/steam_tracker.cpp b/main/steam_tracker.cpp new file mode 100644 index 00000000000..4c90b5d8642 --- /dev/null +++ b/main/steam_tracker.cpp @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* steam_tracker.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#if defined(STEAMAPI_ENABLED) + +#include "steam_tracker.h" + +// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown + +SteamTracker::SteamTracker() { + String path; + if (OS::get_singleton()->has_feature("linuxbsd")) { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("libsteam_api.so"); + if (!FileAccess::exists(path)) { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("../lib").path_join("libsteam_api.so"); + if (!FileAccess::exists(path)) { + return; + } + } + } else if (OS::get_singleton()->has_feature("windows")) { + if (OS::get_singleton()->has_feature("64")) { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("steam_api64.dll"); + } else { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("steam_api.dll"); + } + if (!FileAccess::exists(path)) { + return; + } + } else if (OS::get_singleton()->has_feature("macos")) { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("libsteam_api.dylib"); + if (!FileAccess::exists(path)) { + path = OS::get_singleton()->get_executable_path().get_base_dir().path_join("../Frameworks").path_join("libsteam_api.dylib"); + if (!FileAccess::exists(path)) { + return; + } + } + } else { + return; + } + + Error err = OS::get_singleton()->open_dynamic_library(path, steam_library_handle); + if (err != OK) { + steam_library_handle = nullptr; + return; + } + print_verbose("Loaded SteamAPI library"); + + void *symbol_handle = nullptr; + err = OS::get_singleton()->get_dynamic_library_symbol_handle(steam_library_handle, "SteamAPI_InitFlat", symbol_handle, true); // Try new API, 1.59+. + if (err != OK) { + err = OS::get_singleton()->get_dynamic_library_symbol_handle(steam_library_handle, "SteamAPI_Init", symbol_handle); // Try old API. + if (err != OK) { + return; + } + steam_init_function = (SteamAPI_InitFunction)symbol_handle; + } else { + steam_init_flat_function = (SteamAPI_InitFlatFunction)symbol_handle; + } + + err = OS::get_singleton()->get_dynamic_library_symbol_handle(steam_library_handle, "SteamAPI_Shutdown", symbol_handle); + if (err != OK) { + return; + } + steam_shutdown_function = (SteamAPI_ShutdownFunction)symbol_handle; + + if (steam_init_flat_function) { + char err_msg[1024] = {}; + steam_initalized = (steam_init_flat_function(&err_msg[0]) == SteamAPIInitResult_OK); + } else if (steam_init_function) { + steam_initalized = steam_init_function(); + } +} + +SteamTracker::~SteamTracker() { + if (steam_shutdown_function && steam_initalized) { + steam_shutdown_function(); + } + if (steam_library_handle) { + OS::get_singleton()->close_dynamic_library(steam_library_handle); + } +} + +#endif // STEAMAPI_ENABLED diff --git a/main/steam_tracker.h b/main/steam_tracker.h new file mode 100644 index 00000000000..535b81b9507 --- /dev/null +++ b/main/steam_tracker.h @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* steam_tracker.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef STEAM_TRACKER_H +#define STEAM_TRACKER_H + +#if defined(STEAMAPI_ENABLED) + +#include "core/os/os.h" + +// SteamTracker is used to load SteamAPI dynamic library and initialize +// the interface, this notifies Steam that Godot editor is running and +// allow tracking of the usage time of child instances of the engine +// (e.g., opened projects). +// +// Currently, SteamAPI is not used by the engine in any way, and is not +// exposed to the scripting APIs. + +enum SteamAPIInitResult { + SteamAPIInitResult_OK = 0, + SteamAPIInitResult_FailedGeneric = 1, + SteamAPIInitResult_NoSteamClient = 2, + SteamAPIInitResult_VersionMismatch = 3, +}; + +// https://partner.steamgames.com/doc/api/steam_api#SteamAPI_Init +typedef bool (*SteamAPI_InitFunction)(); +typedef SteamAPIInitResult (*SteamAPI_InitFlatFunction)(char *r_err_msg); + +// https://partner.steamgames.com/doc/api/steam_api#SteamAPI_Shutdown +typedef void (*SteamAPI_ShutdownFunction)(); + +class SteamTracker { + void *steam_library_handle = nullptr; + SteamAPI_InitFunction steam_init_function = nullptr; + SteamAPI_InitFlatFunction steam_init_flat_function = nullptr; + SteamAPI_ShutdownFunction steam_shutdown_function = nullptr; + bool steam_initalized = false; + +public: + SteamTracker(); + ~SteamTracker(); +}; + +#endif // STEAMAPI_ENABLED + +#endif // STEAM_TRACKER_H