From 68cb8ebd3faac5792d166b42eb7f35540fccafa0 Mon Sep 17 00:00:00 2001 From: Dorin Date: Thu, 8 Jan 2026 16:00:37 +0200 Subject: [PATCH 1/6] update external interface --- source/gameanalytics/GameAnalyticsExtern.cpp | 65 ++++----- source/gameanalytics/GameAnalyticsExtern.h | 137 ++++++++++++------- 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/source/gameanalytics/GameAnalyticsExtern.cpp b/source/gameanalytics/GameAnalyticsExtern.cpp index 86989e7..c6694b5 100644 --- a/source/gameanalytics/GameAnalyticsExtern.cpp +++ b/source/gameanalytics/GameAnalyticsExtern.cpp @@ -1,8 +1,9 @@ -#if GA_SHARED_LIB +#include "GameAnalyticsExtern.h" + +#ifdef GA_SHARED_LIB #include "GameAnalytics/GameAnalytics.h" #include "GAUtilities.h" -#include "GameAnalyticsExtern.h" gameanalytics::StringVector makeStringVector(const char** arr, int size) { @@ -23,24 +24,14 @@ gameanalytics::StringVector makeStringVector(const char** arr, int size) return {}; } -GAErrorCode copyStringBuffer(std::string const& s, char* out, int* size) +void gameAnalytics_freeString(const char* ptr) { - if(size && (*size > 0)) - { - if(out && (*size >= s.size())) - { - std::memcpy(out, s.data(), s.size()); - } - else - { - return EGABufferError; - } - - *size = static_cast(s.size()); - return EGANoError; - } + std::free((void*)ptr); +} - return EGAFailure; +const char* gameAnalytics_allocString(std::string const& s) +{ + return strndup(s.c_str(), s.size()); } void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size) @@ -129,7 +120,7 @@ void gameAnalytics_addBusinessEvent(const char *currency, double amount, const c gameanalytics::GameAnalytics::addBusinessEvent(currency, (int)amount, itemType, itemId, cartType, fields, mergeFields); } -void gameAnalytics_addResourceEvent(int flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *fields, GAStatus mergeFields) +void gameAnalytics_addResourceEvent(GAResourceFlowType flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *fields, GAStatus mergeFields) { gameanalytics::GameAnalytics::addResourceEvent((gameanalytics::EGAResourceFlowType)flowType, currency, (float)amount, itemType, itemId, fields, mergeFields); } @@ -234,16 +225,28 @@ void gameAnalytics_onQuit() gameanalytics::GameAnalytics::onQuit(); } -GAErrorCode gameAnalytics_getRemoteConfigsValueAsString(const char *key, char* out, int* size) +const char* gameAnalytics_getUserId() +{ + std::string returnValue = gameanalytics::GameAnalytics::getUserId(); + return gameAnalytics_allocString(returnValue); +} + +const char* gameAnalytics_getExternalUserId() +{ + std::string returnValue = gameanalytics::GameAnalytics::getExternalUserId(); + return gameAnalytics_allocString(returnValue); +} + +const char* gameAnalytics_getRemoteConfigsValueAsString(const char *key) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } -GAErrorCode gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue, char* out, int* size) +const char* gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key, defaultValue); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } GAStatus gameAnalytics_isRemoteConfigsReady() @@ -251,28 +254,28 @@ GAStatus gameAnalytics_isRemoteConfigsReady() return gameanalytics::GameAnalytics::isRemoteConfigsReady() ? EGAEnabled : EGADisabled; } -GAErrorCode gameAnalytics_getRemoteConfigsContentAsString(char* out, int* size) +const char* gameAnalytics_getRemoteConfigsContentAsString() { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsContentAsString(); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } -GAErrorCode gameAnalytics_getRemoteConfigsValueAsJson(const char* key, char* out, int* size) +const char* gameAnalytics_getRemoteConfigsValueAsJson(const char* key) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsJson(key); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } -GAErrorCode gameAnalytics_getABTestingId(char* out, int* size) +const char* gameAnalytics_getABTestingId() { std::string returnValue = gameanalytics::GameAnalytics::getABTestingId(); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } -GAErrorCode gameAnalytics_getABTestingVariantId(char* out, int* size) +const char* gameAnalytics_getABTestingVariantId(char* out) { std::string returnValue = gameanalytics::GameAnalytics::getABTestingVariantId(); - return copyStringBuffer(returnValue, out, size); + return gameAnalytics_allocString(returnValue); } long long gameAnalytics_getElapsedSessionTime() diff --git a/source/gameanalytics/GameAnalyticsExtern.h b/source/gameanalytics/GameAnalyticsExtern.h index 0d60af7..3352c53 100644 --- a/source/gameanalytics/GameAnalyticsExtern.h +++ b/source/gameanalytics/GameAnalyticsExtern.h @@ -6,14 +6,18 @@ extern "C" { #endif -#if GA_SHARED_LIB +#ifdef GA_SHARED_LIB #if defined(_WIN32) - #define GA_EXPORT __declspec(dllexport) + #define GA_API __declspec(dllexport) #else - #define GA_EXPORT __attribute__((visibility("default"))) + #define GA_API __attribute__((visibility("default"))) #endif #else - #define GA_EXPORT + #if defined (_WIN32) + #define GA_API __declspec(dllimport) + #else + #define GA_API + #endif #endif enum GAErrorCode @@ -29,82 +33,109 @@ enum GAStatus: char EGAEnabled }; +enum GAResourceFlowType +{ + EGASource = 1, + EGASink = 2 +}; + +enum GAProgressionStatus +{ + EGAStart = 1, + EGAComplete = 2, + EGAFail = 3 +}; + +enum GAErrorSeverity +{ + EGADebug = 1, + EGAInfo = 2, + EGAWarning = 3, + EGAError = 4, + EGACritical = 5 +}; + typedef float(*GAFpsTracker)(void); -GA_EXPORT void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size); -GA_EXPORT void gameAnalytics_configureAvailableCustomDimensions02(const char **customDimensions, int size); -GA_EXPORT void gameAnalytics_configureAvailableCustomDimensions03(const char **customDimensions, int size); -GA_EXPORT void gameAnalytics_configureAvailableResourceCurrencies(const char **resourceCurrencies, int size); -GA_EXPORT void gameAnalytics_configureAvailableResourceItemTypes(const char **resourceItemTypes, int size); -GA_EXPORT void gameAnalytics_configureBuild(const char *build); -GA_EXPORT void gameAnalytics_configureWritablePath(const char *writablePath); -GA_EXPORT void gameAnalytics_configureDeviceModel(const char *deviceModel); -GA_EXPORT void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer); +GA_API void gameAnalytics_freeString(const char* ptr); + +GA_API void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size); +GA_API void gameAnalytics_configureAvailableCustomDimensions02(const char **customDimensions, int size); +GA_API void gameAnalytics_configureAvailableCustomDimensions03(const char **customDimensions, int size); +GA_API void gameAnalytics_configureAvailableResourceCurrencies(const char **resourceCurrencies, int size); +GA_API void gameAnalytics_configureAvailableResourceItemTypes(const char **resourceItemTypes, int size); +GA_API void gameAnalytics_configureBuild(const char *build); +GA_API void gameAnalytics_configureWritablePath(const char *writablePath); +GA_API void gameAnalytics_configureDeviceModel(const char *deviceModel); +GA_API void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer); // the version of SDK code used in an engine. Used for sdk_version field. // !! if set then it will override the SdkWrapperVersion. // example "unity 4.6.9" -GA_EXPORT void gameAnalytics_configureSdkGameEngineVersion(const char *sdkGameEngineVersion); +GA_API void gameAnalytics_configureSdkGameEngineVersion(const char *sdkGameEngineVersion); // the version of the game engine (if used and version is available) -GA_EXPORT void gameAnalytics_configureGameEngineVersion(const char *engineVersion); +GA_API void gameAnalytics_configureGameEngineVersion(const char *engineVersion); -GA_EXPORT void gameAnalytics_configureUserId(const char *uId); +GA_API void gameAnalytics_configureUserId(const char *uId); -GA_EXPORT void gameAnalytics_configureExternalUserId(const char* extId); +GA_API void gameAnalytics_configureExternalUserId(const char* extId); // initialize - starting SDK (need configuration before starting) -GA_EXPORT void gameAnalytics_initialize(const char *gameKey, const char *gameSecret); +GA_API void gameAnalytics_initialize(const char *gameKey, const char *gameSecret); // add events -GA_EXPORT void gameAnalytics_addBusinessEvent(const char *currency, double amount, const char *itemType, const char *itemId, const char *cartType, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addResourceEvent(int flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addProgressionEvent(int progressionStatus, const char *progression01, const char *progression02, const char *progression03, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addProgressionEventWithScore(int progressionStatus, const char *progression01, const char *progression02, const char *progression03, int score, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addDesignEvent(const char *eventId, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addDesignEventWithValue(const char *eventId, double value, const char *customFields, GAStatus mergeFields); -GA_EXPORT void gameAnalytics_addErrorEvent(int severity, const char *message, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addBusinessEvent(const char *currency, double amount, const char *itemType, const char *itemId, const char *cartType, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addResourceEvent(GAResourceFlowType flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addProgressionEvent(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addProgressionEventWithScore(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, int score, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addDesignEvent(const char *eventId, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addDesignEventWithValue(const char *eventId, double value, const char *customFields, GAStatus mergeFields); +GA_API void gameAnalytics_addErrorEvent(GAErrorSeverity severity, const char *message, const char *customFields, GAStatus mergeFields); // set calls can be changed at any time (pre- and post-initialize) // some calls only work after a configure is called (setCustomDimension) -GA_EXPORT void gameAnalytics_setEnabledInfoLog(GAStatus flag); -GA_EXPORT void gameAnalytics_setEnabledVerboseLog(GAStatus flag); -GA_EXPORT void gameAnalytics_setEnabledManualSessionHandling(GAStatus flag); -GA_EXPORT void gameAnalytics_setEnabledErrorReporting(GAStatus flag); -GA_EXPORT void gameAnalytics_setEnabledEventSubmission(GAStatus flag); -GA_EXPORT void gameAnalytics_setCustomDimension01(const char *dimension01); -GA_EXPORT void gameAnalytics_setCustomDimension02(const char *dimension02); -GA_EXPORT void gameAnalytics_setCustomDimension03(const char *dimension03); +GA_API void gameAnalytics_setEnabledInfoLog(GAStatus flag); +GA_API void gameAnalytics_setEnabledVerboseLog(GAStatus flag); +GA_API void gameAnalytics_setEnabledManualSessionHandling(GAStatus flag); +GA_API void gameAnalytics_setEnabledErrorReporting(GAStatus flag); +GA_API void gameAnalytics_setEnabledEventSubmission(GAStatus flag); +GA_API void gameAnalytics_setCustomDimension01(const char *dimension01); +GA_API void gameAnalytics_setCustomDimension02(const char *dimension02); +GA_API void gameAnalytics_setCustomDimension03(const char *dimension03); -GA_EXPORT void gameAnalytics_setGlobalCustomEventFields(const char *customFields); +GA_API void gameAnalytics_setGlobalCustomEventFields(const char *customFields); -GA_EXPORT void gameAnalytics_startSession(); -GA_EXPORT void gameAnalytics_endSession(); +GA_API void gameAnalytics_startSession(); +GA_API void gameAnalytics_endSession(); // game state changes // will affect how session is started / ended -GA_EXPORT void gameAnalytics_onResume(); -GA_EXPORT void gameAnalytics_onSuspend(); -GA_EXPORT void gameAnalytics_onQuit(); +GA_API void gameAnalytics_onResume(); +GA_API void gameAnalytics_onSuspend(); +GA_API void gameAnalytics_onQuit(); + +GA_API const char* gameAnalytics_getRemoteConfigsValueAsString(const char *key); +GA_API const char* gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue); +GA_API const char* gameAnalytics_getRemoteConfigsValueAsJson(const char* key); -GA_EXPORT GAErrorCode gameAnalytics_getRemoteConfigsValueAsString(const char *key, char* out, int* size); -GA_EXPORT GAErrorCode gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue, char* out, int* bufferSize); -GA_EXPORT GAErrorCode gameAnalytics_getRemoteConfigsValueAsJson(const char* key, char* out, int* size); +GA_API const char* gameAnalytics_getUserId(); +GA_API const char* gameAnalytics_getExternalUserId(); -GA_EXPORT GAStatus gameAnalytics_isRemoteConfigsReady(); -GA_EXPORT GAErrorCode gameAnalytics_getRemoteConfigsContentAsString(char* out, int* size); +GA_API GAStatus gameAnalytics_isRemoteConfigsReady(); +GA_API const char* gameAnalytics_getRemoteConfigsContentAsString(); -GA_EXPORT GAErrorCode gameAnalytics_getABTestingId(char* out, int* size); -GA_EXPORT GAErrorCode gameAnalytics_getABTestingVariantId(char* out, int* size); +GA_API const char* gameAnalytics_getABTestingId(); +GA_API const char* gameAnalytics_getABTestingVariantId(); -GA_EXPORT long long gameAnalytics_getElapsedSessionTime(); -GA_EXPORT long long gameAnalytics_getElapsedTimeFromAllSessions(); -GA_EXPORT long long gameAnalytics_getElapsedTimeForPreviousSession(); +GA_API long long gameAnalytics_getElapsedSessionTime(); +GA_API long long gameAnalytics_getElapsedTimeFromAllSessions(); +GA_API long long gameAnalytics_getElapsedTimeForPreviousSession(); -GA_EXPORT void gameAnalytics_enableSDKInitEvent(GAStatus status); -GA_EXPORT void gameAnalytics_enableMemoryHistogram(GAStatus status); -GA_EXPORT void gameAnalytics_enableFPSHistogram(GAFpsTracker tracker, GAStatus status); -GA_EXPORT void gameAnalytics_enableHardwareTracking(GAStatus status); +GA_API void gameAnalytics_enableSDKInitEvent(GAStatus status); +GA_API void gameAnalytics_enableMemoryHistogram(GAStatus status); +GA_API void gameAnalytics_enableFPSHistogram(GAFpsTracker tracker, GAStatus status); +GA_API void gameAnalytics_enableHardwareTracking(GAStatus status); #ifdef __cplusplus } From d1e90f189039de601d2f65b76c872fc0c5907dd9 Mon Sep 17 00:00:00 2001 From: Dorin Date: Thu, 8 Jan 2026 16:01:21 +0200 Subject: [PATCH 2/6] allow boolean custom fields to pass validation --- source/gameanalytics/GAState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAState.cpp b/source/gameanalytics/GAState.cpp index ff20123..229760e 100644 --- a/source/gameanalytics/GAState.cpp +++ b/source/gameanalytics/GAState.cpp @@ -1037,7 +1037,7 @@ namespace gameanalytics { const json& value = fields[key]; - if(value.is_number()) + if(value.is_number() || value.is_boolean()) { result[key] = value; ++count; From c4417e8052eb0ed35773fe1f4aa74cc9404c0aa1 Mon Sep 17 00:00:00 2001 From: Dorin Date: Fri, 5 Jun 2026 11:53:05 +0300 Subject: [PATCH 3/6] fix for getRemoteConfigsValue --- source/gameanalytics/GAState.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/source/gameanalytics/GAState.h b/source/gameanalytics/GAState.h index af504e3..a6a3352 100644 --- a/source/gameanalytics/GAState.h +++ b/source/gameanalytics/GAState.h @@ -150,11 +150,15 @@ namespace gameanalytics inline static T getRemoteConfigsValue(std::string const& key, T const& defaultValue) { std::lock_guard lg(getInstance()._mtx); - if(getInstance()._gameRemoteConfigsJson.contains(key)) + + for(auto& config : getInstance()._gameRemoteConfigsJson) { - json& config = getInstance()._gameRemoteConfigsJson[key]; - T value = utilities::getOptionalValue(config, "value", defaultValue); - return value; + std::string configKey = utilities::getOptionalValue(config, "key", ""); + if(configKey == key) + { + T value = utilities::getOptionalValue(config, "value", defaultValue); + return value; + } } return defaultValue; @@ -177,6 +181,8 @@ namespace gameanalytics private: + static GAState instance; + GAState(); ~GAState(); GAState(const GAState&) = delete; @@ -185,7 +191,7 @@ namespace gameanalytics static constexpr const char* CategorySdkError = "sdk_error"; template - void LogAndAddErrorEvent(EGAErrorSeverity severity, std::string const& fmt, args_t&&... args) + void LogAndAddErrorEvent(EGAErrorSeverity severity, const char* fmt, args_t&&... args) { const std::string msg = utilities::printString(fmt, std::forward(args)...); logging::GALogger::w(msg.c_str()); From 1f3b90b2beb13cb809a859d5aecf43766bd5cd3b Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 8 Jun 2026 12:04:17 +0300 Subject: [PATCH 4/6] last session count is now calculated properly in seconds --- source/gameanalytics/GAState.cpp | 2 +- source/gameanalytics/GAState.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/gameanalytics/GAState.cpp b/source/gameanalytics/GAState.cpp index 229760e..23c913d 100644 --- a/source/gameanalytics/GAState.cpp +++ b/source/gameanalytics/GAState.cpp @@ -1100,7 +1100,7 @@ namespace gameanalytics void GAState::updateTotalSessionTime() { - _lastSessionTime = calculateSessionLength(); + _lastSessionTime = calculateSessionLength(); _totalElapsedSessionTime += _lastSessionTime; _gaStore.setState("last_session_time", std::to_string(_lastSessionTime)); diff --git a/source/gameanalytics/GAState.h b/source/gameanalytics/GAState.h index a6a3352..28d84fa 100644 --- a/source/gameanalytics/GAState.h +++ b/source/gameanalytics/GAState.h @@ -164,7 +164,7 @@ namespace gameanalytics return defaultValue; } - template + template inline int64_t calculateSessionLength() const { auto len = std::chrono::high_resolution_clock::now() - _startTimepoint; From 424a87d77c7d9cc9a686e0b2484f52005145e723 Mon Sep 17 00:00:00 2001 From: dorin_ga Date: Wed, 9 Sep 2026 15:27:17 +0300 Subject: [PATCH 5/6] build the static library with position independent code --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d6239a..256628e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,12 @@ set_target_properties(GameAnalytics PROPERTIES C_VISIBILITY_PRESET hidden CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON + # The static build gets linked into consumers' shared libraries (every Godot + # GDExtension is a .so), which fails on Linux without PIC: + # relocation R_X86_64_PC32 against symbol `_ZSt4cerr' can not be used when + # making a shared object; recompile with -fPIC + # No-op for MSVC, already the default on macOS. + POSITION_INDEPENDENT_CODE ON ) if(${GA_SHARED_LIB}) From 156383d76496a5190adf09992e4670b70f3dec44 Mon Sep 17 00:00:00 2001 From: dorin_ga Date: Mon, 2 Nov 2026 12:26:04 +0200 Subject: [PATCH 6/6] improve cpu detection --- source/gameanalytics/Platform/GAWin32.cpp | 234 +++++++++++++++++----- source/gameanalytics/Platform/GAWin32.h | 7 - 2 files changed, 183 insertions(+), 58 deletions(-) diff --git a/source/gameanalytics/Platform/GAWin32.cpp b/source/gameanalytics/Platform/GAWin32.cpp index aaa8e2e..cf06281 100644 --- a/source/gameanalytics/Platform/GAWin32.cpp +++ b/source/gameanalytics/Platform/GAWin32.cpp @@ -10,6 +10,11 @@ #include #include +#include + +#include +#include +#include #pragma comment(lib, "Wininet.lib") @@ -23,51 +28,94 @@ void (*GAPlatformWin32::old_state_abrt) (int) = nullptr; void (*GAPlatformWin32::old_state_fpe) (int) = nullptr; void (*GAPlatformWin32::old_state_segv) (int) = nullptr; -// no official helper function exists for win11 (for the time being at least) -bool IsWin11OrGreater() +// RtlGetVersion reports the real OS version. GetVersionEx and the VersionHelpers.h functions +// are subject to the app manifest: without a supportedOS entry for Windows 10 they report 6.2 (Windows 8), +// and they have no notion of a build number so cannot tell Windows 11 apart from Windows 10. +bool getWindowsVersion(DWORD& major, DWORD& minor, DWORD& build) { - constexpr DWORD MAJOR_VERSION = 10; - constexpr DWORD MINOR_VERSION = 0; - constexpr DWORD BUILD_NUM = 21996; + using RtlGetVersionFn = LONG (WINAPI*)(PRTL_OSVERSIONINFOW); + + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) + { + return false; + } + + auto rtlGetVersion = reinterpret_cast(GetProcAddress(ntdll, "RtlGetVersion")); + if (!rtlGetVersion) + { + return false; + } + + RTL_OSVERSIONINFOW info = {}; + info.dwOSVersionInfoSize = sizeof(info); + + // returns an NTSTATUS, 0 is STATUS_SUCCESS + if (rtlGetVersion(&info) != 0) + { + return false; + } + + major = info.dwMajorVersion; + minor = info.dwMinorVersion; + build = info.dwBuildNumber; - return IsWindowsVersionOrGreater(MAJOR_VERSION, MINOR_VERSION, BUILD_NUM); + return true; } std::string GAPlatformWin32::getOSVersion() { std::string osVersion = getBuildPlatform() + " "; - #if (_MSC_VER >= 1900) - if(IsWin11OrGreater()) - { - return osVersion + "11"; - } - else if (IsWindows10OrGreater()) - { - return osVersion + "10"; - } - #endif + DWORD major = 0; + DWORD minor = 0; + DWORD build = 0; - if (IsWindows8Point1OrGreater()) - { - return osVersion + "6.3"; - } - else if (IsWindows8OrGreater()) - { - return osVersion + "6.2"; - } - else if (IsWindows7OrGreater()) + if (getWindowsVersion(major, minor, build)) + { + // Windows 11 kept the 10.0 version number, the first release build is 22000 + constexpr DWORD WIN11_FIRST_BUILD = 22000; + + if (major == 10) { - return osVersion + "6.1"; + return osVersion + (build >= WIN11_FIRST_BUILD ? "11" : "10"); } - else if (IsWindowsVistaOrGreater()) + else if (major > 10) { - return osVersion + "6.1"; + return osVersion + std::to_string(major); } - else if (IsWindowsXPOrGreater()) + + return osVersion + std::to_string(major) + "." + std::to_string(minor); + } + + // fallback, manifest dependent (see above) + #if (_MSC_VER >= 1900) + if (IsWindows10OrGreater()) { - return osVersion + "5.1"; + return osVersion + "10"; } + #endif + + if (IsWindows8Point1OrGreater()) + { + return osVersion + "6.3"; + } + else if (IsWindows8OrGreater()) + { + return osVersion + "6.2"; + } + else if (IsWindows7OrGreater()) + { + return osVersion + "6.1"; + } + else if (IsWindowsVistaOrGreater()) + { + return osVersion + "6.0"; + } + else if (IsWindowsXPOrGreater()) + { + return osVersion + "5.1"; + } return osVersion + "0.0.0"; } @@ -119,29 +167,31 @@ std::string GAPlatformWin32::getPersistentPath() std::string getRegistryKey(HKEY key, const TCHAR* subkey, const TCHAR* value) { + // RegGetValue reports errors through its return value, not GetLastError, + // and the size it reports is in bytes (including the null terminator) + DWORD size = 0; + if (RegGetValue(key, subkey, value, RRF_RT_REG_SZ, nullptr, nullptr, &size) != ERROR_SUCCESS || size <= sizeof(TCHAR)) { - constexpr DWORD maxBufSize = 128; + return UNKNOWN_VALUE; + } - DWORD size = maxBufSize * sizeof(TCHAR); - TCHAR buffer[maxBufSize] = _T(""); - RegGetValue(key, subkey, value, RRF_RT_REG_SZ, NULL, buffer, &size); + std::vector buffer(size / sizeof(TCHAR) + 1, 0); + size = static_cast(buffer.size() * sizeof(TCHAR)); - if (!GetLastError() && size > 0) - { - std::string val; + if (RegGetValue(key, subkey, value, RRF_RT_REG_SZ, nullptr, buffer.data(), &size) != ERROR_SUCCESS) + { + return UNKNOWN_VALUE; + } + + std::string val; #ifdef UNICODE - std::wstring wstr(buffer, buffer + size); - val = utilities::GAUtilities::ws2s(wstr); + val = utilities::GAUtilities::ws2s(std::wstring(buffer.data())); #else - val = std::string(buffer, buffer + size); + val = std::string(buffer.data()); #endif - return val; - } - } - - return UNKNOWN_VALUE; + return val.empty() ? UNKNOWN_VALUE : val; } std::string GAPlatformWin32::getDeviceModel() @@ -160,20 +210,102 @@ std::string GAPlatformWin32::getDeviceManufacturer() return getRegistryKey(HKEY_LOCAL_MACHINE, subkey, value); } +// trims and collapses repeated whitespace (brand strings are often padded, e.g. " Intel(R) Core(TM) i7 CPU") +std::string normalizeCpuName(std::string const& name) +{ + std::string result; + result.reserve(name.size()); + + for (char c : name) + { + if (c == '\0') + { + break; + } + + if (std::isspace(static_cast(c))) + { + if (!result.empty() && result.back() != ' ') + { + result += ' '; + } + } + else + { + result += c; + } + } + + if (!result.empty() && result.back() == ' ') + { + result.pop_back(); + } + + return result; +} + +std::string getCpuidBrandString() +{ +#if (defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) + int regs[4] = {}; + + __cpuid(regs, 0x80000000); + if (static_cast(regs[0]) < 0x80000004) + { + return ""; + } + + char brand[49] = {}; + for (int i = 0; i < 3; ++i) + { + __cpuid(regs, 0x80000002 + i); + std::memcpy(brand + i * sizeof(regs), regs, sizeof(regs)); + } + + return normalizeCpuName(brand); +#else + return ""; +#endif +} + +std::string getCpuArchitecture() +{ + SYSTEM_INFO info = {}; + GetNativeSystemInfo(&info); + + switch (info.wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64"; + case PROCESSOR_ARCHITECTURE_ARM64: return "arm64"; + case PROCESSOR_ARCHITECTURE_INTEL: return "x86"; + case PROCESSOR_ARCHITECTURE_ARM: return "arm"; + default: return UNKNOWN_VALUE; + } +} + std::string GAPlatformWin32::getCpuModel() const { + // the registry holds the name of the physical cpu (also correct for x64 apps emulated on arm64) constexpr const TCHAR* subkey = _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"); + constexpr const TCHAR* value = _T("ProcessorNameString"); - constexpr const TCHAR* values[] = { _T("ProcessorName"), _T("ProcessorNameString") }; + std::string name = getRegistryKey(HKEY_LOCAL_MACHINE, subkey, value); + if (name != UNKNOWN_VALUE) + { + name = normalizeCpuName(name); + if (!name.empty()) + { + return name; + } + } - for (auto& val : values) + name = getCpuidBrandString(); + if (!name.empty()) { - std::string s = getRegistryKey(HKEY_LOCAL_MACHINE, subkey, val); - if (!s.empty()) - return s; + return name; } - return UNKNOWN_VALUE; + return getCpuArchitecture(); } void GAPlatformWin32::setupUncaughtExceptionHandler() diff --git a/source/gameanalytics/Platform/GAWin32.h b/source/gameanalytics/Platform/GAWin32.h index 446b4d4..836d910 100644 --- a/source/gameanalytics/Platform/GAWin32.h +++ b/source/gameanalytics/Platform/GAWin32.h @@ -19,13 +19,6 @@ #include #include - #ifdef GA_SHARED_LIB - #include - #include - #pragma comment(lib, "wbemuuid.lib") - #endif - - #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "crypt32.lib")