Skip to content

Commit a2572d2

Browse files
authored
feat: Implement support for SteamOS detection + crashpad support (#1123)
* Implement support for SteamOS detection + crashpad SP option support
1 parent 0f8ac11 commit a2572d2

File tree

8 files changed

+422
-0
lines changed

8 files changed

+422
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8250)
1818
- [diff](https://github.com/getsentry/sentry-java/compare/8.24.0...8.25.0)
1919

20+
### Features
21+
22+
- Implement Wine/Proton detection and automatic Crashpad stack capture adjustment for SteamOS/Bazzite compatibility ([#1123](https://github.com/getsentry/sentry-unreal/pull/1123))
23+
2024
## 1.2.0
2125

2226
### Features

plugin-dev/Source/Sentry/Private/Linux/LinuxSentrySubsystem.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "SentryBeforeSendHandler.h"
77
#include "SentryDefines.h"
88
#include "SentrySettings.h"
9+
#include "Utils/SentryPlatformDetectionUtils.h"
910

1011
#include "GenericPlatform/GenericPlatformOutputDevices.h"
1112
#include "Misc/Paths.h"
@@ -17,6 +18,31 @@ void FLinuxSentrySubsystem::InitWithSettings(const USentrySettings* Settings, US
1718
FGenericPlatformSentrySubsystem::InitWithSettings(Settings, BeforeSendHandler, BeforeBreadcrumbHandler, BeforeLogHandler, TraceSampler);
1819

1920
InitCrashReporter(Settings->GetEffectiveRelease(), Settings->GetEffectiveEnvironment());
21+
22+
// Add platform context if detected
23+
if (IsEnabled())
24+
{
25+
// Set OS context for SteamOS or Bazzite
26+
if (FSentryPlatformDetectionUtils::IsSteamOS())
27+
{
28+
TMap<FString, FSentryVariant> OSContext;
29+
OSContext.Add(TEXT("name"), TEXT("SteamOS"));
30+
SetContext(TEXT("os"), OSContext);
31+
SetTag(TEXT("steamos"), TEXT("true"));
32+
}
33+
else if (FSentryPlatformDetectionUtils::IsBazzite())
34+
{
35+
TMap<FString, FSentryVariant> OSContext;
36+
OSContext.Add(TEXT("name"), TEXT("Bazzite"));
37+
SetContext(TEXT("os"), OSContext);
38+
SetTag(TEXT("bazzite"), TEXT("true"));
39+
}
40+
41+
if (FSentryPlatformDetectionUtils::IsRunningSteam())
42+
{
43+
SetTag(TEXT("steam"), TEXT("true"));
44+
}
45+
}
2046
}
2147

2248
void FLinuxSentrySubsystem::ConfigureHandlerPath(sentry_options_t* Options)
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) 2025 Sentry. All Rights Reserved.
2+
3+
#include "SentryPlatformDetectionUtils.h"
4+
5+
#include "SentryDefines.h"
6+
7+
#if PLATFORM_WINDOWS
8+
#include "Windows/AllowWindowsPlatformTypes.h"
9+
#include "Windows/HideWindowsPlatformTypes.h"
10+
#include <winternl.h>
11+
#endif
12+
13+
FWineProtonInfo FSentryPlatformDetectionUtils::DetectWineProton()
14+
{
15+
FWineProtonInfo Info;
16+
17+
#if PLATFORM_WINDOWS
18+
// Check for Wine DLL on Windows builds running under Wine/Proton
19+
HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll");
20+
if (hNtDll != nullptr)
21+
{
22+
// wine_get_version is exported by Wine's ntdll
23+
typedef const char*(CDECL * wine_get_version_t)(void);
24+
#ifdef _MSC_VER
25+
#pragma warning(push)
26+
#pragma warning(disable : 4191) // unsafe conversion from FARPROC
27+
#endif
28+
wine_get_version_t wine_get_version =
29+
reinterpret_cast<wine_get_version_t>(GetProcAddress(hNtDll, "wine_get_version"));
30+
#ifdef _MSC_VER
31+
#pragma warning(pop)
32+
#endif
33+
34+
if (wine_get_version != nullptr)
35+
{
36+
const char* version = wine_get_version();
37+
Info.bIsRunningUnderWine = true;
38+
Info.Version = FString(version);
39+
ParseWineVersion(Info.Version, Info);
40+
41+
UE_LOG(LogSentrySdk, Log, TEXT("Detected Wine version: %s"), *Info.Version);
42+
}
43+
}
44+
45+
// Check environment variables (common in Proton)
46+
if (!Info.bIsRunningUnderWine)
47+
{
48+
FString WineVersion = FPlatformMisc::GetEnvironmentVariable(TEXT("WINE_VERSION"));
49+
if (!WineVersion.IsEmpty())
50+
{
51+
Info.bIsRunningUnderWine = true;
52+
Info.Version = WineVersion;
53+
ParseWineVersion(Info.Version, Info);
54+
55+
UE_LOG(LogSentrySdk, Log, TEXT("Detected Wine/Proton via WINE_VERSION environment variable: %s"), *Info.Version);
56+
}
57+
}
58+
59+
// Check for Proton-specific environment variables
60+
if (Info.bIsRunningUnderWine)
61+
{
62+
FString SteamCompatPath = FPlatformMisc::GetEnvironmentVariable(TEXT("STEAM_COMPAT_DATA_PATH"));
63+
FString SteamCompatTool = FPlatformMisc::GetEnvironmentVariable(TEXT("STEAM_COMPAT_CLIENT_INSTALL_PATH"));
64+
65+
if (!SteamCompatPath.IsEmpty() || !SteamCompatTool.IsEmpty())
66+
{
67+
Info.bIsProton = true;
68+
UE_LOG(LogSentrySdk, Log, TEXT("Detected Proton environment"));
69+
}
70+
71+
// Try to get Proton build name from PROTON_VERSION environment variable
72+
FString ProtonVersion = FPlatformMisc::GetEnvironmentVariable(TEXT("PROTON_VERSION"));
73+
if (!ProtonVersion.IsEmpty())
74+
{
75+
Info.ProtonBuildName = ProtonVersion;
76+
Info.bIsExperimental = ProtonVersion.Contains(TEXT("Experimental"), ESearchCase::IgnoreCase);
77+
}
78+
}
79+
#endif
80+
81+
return Info;
82+
}
83+
84+
bool FSentryPlatformDetectionUtils::IsSteamOS()
85+
{
86+
// Check for multiple SteamOS-specific indicators
87+
88+
// Check for explicit SteamOS variable (Gaming Mode)
89+
FString SteamOSVar = FPlatformMisc::GetEnvironmentVariable(TEXT("SteamOS"));
90+
if (!SteamOSVar.IsEmpty())
91+
{
92+
UE_LOG(LogSentrySdk, Log, TEXT("Detected SteamOS via SteamOS environment variable"));
93+
return true;
94+
}
95+
96+
// Check for HOME directory containing "deck" user (common on Steam Deck/SteamOS)
97+
FString HomeDir = FPlatformMisc::GetEnvironmentVariable(TEXT("HOME"));
98+
if (HomeDir.Contains(TEXT("/home/deck"), ESearchCase::IgnoreCase))
99+
{
100+
UE_LOG(LogSentrySdk, Log, TEXT("Detected SteamOS via HOME directory path"));
101+
return true;
102+
}
103+
104+
// Check for USER environment variable
105+
FString UserVar = FPlatformMisc::GetEnvironmentVariable(TEXT("USER"));
106+
if (UserVar.Equals(TEXT("deck"), ESearchCase::IgnoreCase))
107+
{
108+
UE_LOG(LogSentrySdk, Log, TEXT("Detected SteamOS via USER environment variable (deck)"));
109+
return true;
110+
}
111+
112+
// Check for STEAM_RUNTIME (indicates Steam runtime environment)
113+
FString SteamRuntime = FPlatformMisc::GetEnvironmentVariable(TEXT("STEAM_RUNTIME"));
114+
if (!SteamRuntime.IsEmpty() && (SteamRuntime.Contains(TEXT("steamrt")) || SteamRuntime.Contains(TEXT("steam-runtime"))))
115+
{
116+
UE_LOG(LogSentrySdk, Log, TEXT("Detected SteamOS via STEAM_RUNTIME environment variable"));
117+
return true;
118+
}
119+
120+
// Check for SteamOS-specific XDG directories
121+
FString XdgCurrentDesktop = FPlatformMisc::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"));
122+
if (XdgCurrentDesktop.Contains(TEXT("gamescope"), ESearchCase::IgnoreCase))
123+
{
124+
UE_LOG(LogSentrySdk, Log, TEXT("Detected SteamOS via XDG_CURRENT_DESKTOP (gamescope)"));
125+
return true;
126+
}
127+
128+
return false;
129+
}
130+
131+
bool FSentryPlatformDetectionUtils::IsBazzite()
132+
{
133+
// Bazzite sets specific environment variables
134+
FString ImageName = FPlatformMisc::GetEnvironmentVariable(TEXT("IMAGE_NAME"));
135+
FString ImageVendor = FPlatformMisc::GetEnvironmentVariable(TEXT("IMAGE_VENDOR"));
136+
FString ImageFlavor = FPlatformMisc::GetEnvironmentVariable(TEXT("IMAGE_FLAVOR"));
137+
138+
// Check for Bazzite-specific image name
139+
if (ImageName.Contains(TEXT("bazzite"), ESearchCase::IgnoreCase))
140+
{
141+
UE_LOG(LogSentrySdk, Log, TEXT("Detected Bazzite via IMAGE_NAME environment variable"));
142+
return true;
143+
}
144+
145+
// Check for Bazzite vendor
146+
if (ImageVendor.Contains(TEXT("bazzite"), ESearchCase::IgnoreCase))
147+
{
148+
UE_LOG(LogSentrySdk, Log, TEXT("Detected Bazzite via IMAGE_VENDOR environment variable"));
149+
return true;
150+
}
151+
152+
return false;
153+
}
154+
155+
bool FSentryPlatformDetectionUtils::IsRunningSteam()
156+
{
157+
// Check for Steam-specific environment variables
158+
FString SteamAppId = FPlatformMisc::GetEnvironmentVariable(TEXT("SteamAppId"));
159+
FString SteamGameId = FPlatformMisc::GetEnvironmentVariable(TEXT("SteamGameId"));
160+
FString SteamOverlayGameId = FPlatformMisc::GetEnvironmentVariable(TEXT("SteamOverlayGameId"));
161+
162+
return !SteamAppId.IsEmpty() || !SteamGameId.IsEmpty() || !SteamOverlayGameId.IsEmpty();
163+
}
164+
165+
FString FSentryPlatformDetectionUtils::GetRuntimeName(const FWineProtonInfo& WineProtonInfo)
166+
{
167+
return WineProtonInfo.bIsProton ? TEXT("Proton") : TEXT("Wine");
168+
}
169+
170+
FString FSentryPlatformDetectionUtils::GetRuntimeVersion(const FWineProtonInfo& WineProtonInfo)
171+
{
172+
// For Proton, use build name if available, otherwise fall back to Wine version
173+
if (WineProtonInfo.bIsProton && !WineProtonInfo.ProtonBuildName.IsEmpty())
174+
{
175+
return WineProtonInfo.ProtonBuildName;
176+
}
177+
return WineProtonInfo.Version;
178+
}
179+
180+
void FSentryPlatformDetectionUtils::ParseWineVersion(const FString& VersionString, FWineProtonInfo& OutInfo)
181+
{
182+
// Wine versions typically look like:
183+
// - "9.0" (standard Wine)
184+
// - "8.0-3" (Proton)
185+
// - "7.0-rc1" (Wine release candidate)
186+
187+
// Check if this looks like a Proton version (contains dash with number after it)
188+
if (VersionString.Contains(TEXT("-")))
189+
{
190+
TArray<FString> Parts;
191+
VersionString.ParseIntoArray(Parts, TEXT("-"));
192+
193+
if (Parts.Num() >= 2)
194+
{
195+
// Check if the part after dash is numeric (Proton style) or GE build
196+
if (Parts[1].IsNumeric() || Parts[1].Equals(TEXT("GE"), ESearchCase::IgnoreCase))
197+
{
198+
OutInfo.bIsProton = true;
199+
200+
// Construct Proton build name if not already set
201+
if (OutInfo.ProtonBuildName.IsEmpty())
202+
{
203+
// Generate a default Proton name
204+
if (Parts[1].Equals(TEXT("GE"), ESearchCase::IgnoreCase))
205+
{
206+
OutInfo.ProtonBuildName = FString::Printf(TEXT("Proton-GE %s"), *VersionString);
207+
}
208+
else
209+
{
210+
OutInfo.ProtonBuildName = FString::Printf(TEXT("Proton %s"), *Parts[0]);
211+
}
212+
}
213+
}
214+
}
215+
}
216+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2025 Sentry. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
#include "SentryPlatformInfo.h"
7+
8+
/**
9+
* Utility class for detecting platform-specific runtime environments
10+
* including Wine/Proton, Linux distributions, and handheld devices
11+
*/
12+
class FSentryPlatformDetectionUtils
13+
{
14+
public:
15+
/**
16+
* Detects if running under Wine or Proton (Windows binaries on Linux)
17+
* Works by checking for Wine's ntdll.dll exports and environment variables
18+
*
19+
* @return Wine/Proton detection information
20+
*/
21+
static FWineProtonInfo DetectWineProton();
22+
23+
/**
24+
* Detects if running on SteamOS by checking environment variables
25+
* Does not rely on file system access or hardware detection
26+
*
27+
* @return true if SteamOS is detected
28+
*/
29+
static bool IsSteamOS();
30+
31+
/**
32+
* Detects if running on Bazzite by checking environment variables
33+
* Does not rely on file system access or hardware detection
34+
*
35+
* @return true if Bazzite is detected
36+
*/
37+
static bool IsBazzite();
38+
39+
/**
40+
* Checks if running under Steam (any platform)
41+
*
42+
* @return true if Steam environment is detected
43+
*/
44+
static bool IsRunningSteam();
45+
46+
/**
47+
* Gets the runtime name for Sentry context based on Wine/Proton info
48+
* Returns "Proton" or "Wine"
49+
*/
50+
static FString GetRuntimeName(const FWineProtonInfo& WineProtonInfo);
51+
52+
/**
53+
* Gets the runtime version for Sentry context based on Wine/Proton info
54+
* Returns Proton build name or Wine version
55+
*/
56+
static FString GetRuntimeVersion(const FWineProtonInfo& WineProtonInfo);
57+
58+
private:
59+
/** Parses Wine version string to extract version and Proton information */
60+
static void ParseWineVersion(const FString& VersionString, FWineProtonInfo& OutInfo);
61+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025 Sentry. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
7+
/**
8+
* Platform detection information for Wine/Proton environments
9+
*/
10+
struct FWineProtonInfo
11+
{
12+
/** Whether Wine or Proton is detected */
13+
bool bIsRunningUnderWine = false;
14+
15+
/** Whether running specifically under Proton (Steam's Wine fork) */
16+
bool bIsProton = false;
17+
18+
/** Wine/Proton version string (e.g., "8.0-3" for Proton, "9.0" for Wine) */
19+
FString Version;
20+
21+
/** Proton build name (e.g., "Proton 8.0", "Proton Experimental") */
22+
FString ProtonBuildName;
23+
24+
/** Whether this is an experimental Proton build */
25+
bool bIsExperimental = false;
26+
};

0 commit comments

Comments
 (0)