From 911dd0fc1d118d00010cb567c43172c7aa85efa1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 15 Dec 2025 22:25:40 -0800
Subject: [PATCH] Added avatars for Steam Remote Play Together players
---
external/SteamworksSDK | 2 +-
game/MaelstromUI.cpp | 8 +++++
game/MaelstromUI.h | 1 +
game/gameinfo.cpp | 24 ++++++++-----
game/steam.cpp | 70 ++++++++++++++++++++++++++++++++++++
game/steam.h | 1 +
screenlib/UIElement.cpp | 8 +++++
screenlib/UIElement.h | 1 +
screenlib/UIImageInterface.h | 1 +
9 files changed, 107 insertions(+), 9 deletions(-)
diff --git a/external/SteamworksSDK b/external/SteamworksSDK
index b70e485f..56ceee16 160000
--- a/external/SteamworksSDK
+++ b/external/SteamworksSDK
@@ -1 +1 @@
-Subproject commit b70e485fc8a6f6d2f135f0a2aee5074d9e8f18e9
+Subproject commit 56ceee1634d78aabd33461d1dcd549f64ce37a49
diff --git a/game/MaelstromUI.cpp b/game/MaelstromUI.cpp
index 5a8d9c65..0840a130 100644
--- a/game/MaelstromUI.cpp
+++ b/game/MaelstromUI.cpp
@@ -315,6 +315,14 @@ MaelstromUI::CreateImage(const char *name)
return Load_Image(screen, name);
}
+UITexture *
+MaelstromUI::CreateImage(SDL_Surface *surface)
+{
+ SDL_Texture *texture = screen->LoadImage(surface);
+ SDL_DestroySurface(surface);
+ return new UITexture(texture, 1.0f);
+}
+
void
MaelstromUI::FreeImage(UITexture *texture)
{
diff --git a/game/MaelstromUI.h b/game/MaelstromUI.h
index 92598572..b451bf79 100644
--- a/game/MaelstromUI.h
+++ b/game/MaelstromUI.h
@@ -44,6 +44,7 @@ class MaelstromUI : public UIManager
// UIImageInterface
//
virtual UITexture *CreateImage(const char *name);
+ virtual UITexture *CreateImage(SDL_Surface *surface);
virtual void FreeImage(UITexture *texture);
virtual UITexture *CreateBackground(const char *name);
virtual void FreeBackground(UITexture *texture);
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index bde5aa6f..2546d2f8 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -548,9 +548,17 @@ GameInfo::UpdateUI(GameInfoPlayer *player)
}
if (player->UI.control) {
- char name[128];
- SDL_snprintf(name, sizeof(name), "control%d", player->controlMask);
- player->UI.control->SetImage(name);
+ SDL_Surface *surface = nullptr;
+ if (IS_REMOTE_CONTROL(player->controlMask)) {
+ surface = GetRemotePlayerAvatar(player->controlMask);
+ }
+ if (surface) {
+ player->UI.control->SetImage(surface);
+ } else {
+ char name[128];
+ SDL_snprintf(name, sizeof(name), "control%d", player->controlMask);
+ player->UI.control->SetImage(name);
+ }
}
if (player->UI.desc) {
@@ -673,12 +681,12 @@ printf("Game 0x%8.8x: node 0x%8.8x since last ping %d (TIMEDOUT)\n",
#endif
node->ping.status = PING_TIMEDOUT;
}
- }
- // Update the UI for matching players
- for (int i = 0; i < MAX_PLAYERS; ++i) {
- if (players[i].nodeID == node->nodeID) {
- UpdateUI(&players[i]);
+ // Update the UI for matching players
+ for (int i = 0; i < MAX_PLAYERS; ++i) {
+ if (players[i].nodeID == node->nodeID) {
+ UpdateUI(&players[i]);
+ }
}
}
}
diff --git a/game/steam.cpp b/game/steam.cpp
index f083d155..9330563f 100644
--- a/game/steam.cpp
+++ b/game/steam.cpp
@@ -52,6 +52,7 @@ class SteamInterface
RemotePlaySessionID_t GetRemoteSessionForGamepad(SDL_Gamepad *gamepad);
const char *GetRemotePlayerName(Uint8 controlType);
+ SDL_Surface *GetRemotePlayerAvatar(Uint8 controlType);
const bool *GetRemotePlayerKeyboardState(Uint8 controlType);
void EnableRemoteInput();
@@ -80,6 +81,7 @@ class SteamInterface
RemoteSession_t *m_players[MAX_PLAYERS - 1] = { };
STEAM_CALLBACK(SteamInterface, OnRemotePlaySessionConnected, SteamRemotePlaySessionConnected_t);
+ STEAM_CALLBACK(SteamInterface, OnRemotePlaySessionAvatarLoaded, SteamRemotePlaySessionAvatarLoaded_t);
STEAM_CALLBACK(SteamInterface, OnRemotePlaySessionDisconnected, SteamRemotePlaySessionDisconnected_t);
};
static SteamInterface steam;
@@ -246,6 +248,59 @@ const char *SteamInterface::GetRemotePlayerName(Uint8 controlType)
return session->name;
}
+SDL_Surface *SteamInterface::GetRemotePlayerAvatar(Uint8 controlType)
+{
+ RemoteSession_t *session = GetSessionForControl(controlType);
+ if (!session) {
+ return nullptr;
+ }
+
+ SDL_Surface *surface = nullptr;
+ int image = SteamRemotePlay()->GetSmallSessionAvatar(session->id);
+ if (image > 0) {
+ uint32 width, height;
+ if (!SteamUtils()->GetImageSize(image, &width, &height)) {
+ SDL_Log("Couldn't get image size");
+ return nullptr;
+ }
+
+ SDL_Surface *avatar = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32);
+ if (!avatar) {
+ return nullptr;
+ }
+ // Steam returns tightly packed images
+ SDL_assert(avatar->pitch == width * 4);
+
+ if (!SteamUtils()->GetImageRGBA(image, (uint8 *)avatar->pixels, avatar->h * avatar->pitch)) {
+ SDL_Log("Couldn't get image pixels");
+ SDL_DestroySurface(surface);
+ return nullptr;
+ }
+
+ const int AVATAR_SIZE = 36;
+ surface = SDL_CreateSurface(AVATAR_SIZE, AVATAR_SIZE, SDL_PIXELFORMAT_RGBA32);
+ if (surface) {
+ SDL_Rect rect;
+
+ // Add a black border
+ rect.w = avatar->w + 2;
+ rect.h = avatar->h + 2;
+ rect.x = (surface->w - rect.w) / 2;
+ rect.y = (surface->h - rect.h) / 2;
+ SDL_FillSurfaceRect(surface, &rect, SDL_MapSurfaceRGB(surface, 0, 0, 0));
+
+ // Center the avatar
+ rect.w = avatar->w;
+ rect.h = avatar->h;
+ rect.x = (surface->w - rect.w) / 2;
+ rect.y = (surface->h - rect.h) / 2;
+ SDL_BlitSurface(avatar, NULL, surface, &rect);
+ }
+ SDL_DestroySurface(avatar);
+ }
+ return surface;
+}
+
const bool *SteamInterface::GetRemotePlayerKeyboardState(Uint8 controlType)
{
RemoteSession_t *session = GetSessionForControl(controlType);
@@ -273,6 +328,11 @@ void SteamInterface::DisableRemoteInput()
SteamRemotePlay()->DisableRemotePlayTogetherDirectInput();
}
+void SteamInterface::OnRemotePlaySessionAvatarLoaded(SteamRemotePlaySessionAvatarLoaded_t *pParam)
+{
+ UpdatePlayers();
+}
+
void SteamInterface::OnRemotePlaySessionConnected(SteamRemotePlaySessionConnected_t *pParam)
{
RemotePlaySessionID_t sessionID = pParam->m_unSessionID;
@@ -346,6 +406,11 @@ const char *GetRemotePlayerName(Uint8 controlType)
return steam.GetRemotePlayerName(controlType);
}
+SDL_Surface *GetRemotePlayerAvatar(Uint8 controlType)
+{
+ return steam.GetRemotePlayerAvatar(controlType);
+}
+
const bool *GetRemotePlayerKeyboardState(Uint8 controlType)
{
return steam.GetRemotePlayerKeyboardState(controlType);
@@ -393,6 +458,11 @@ const char *GetRemotePlayerName(Uint8 controlType)
return nullptr;
}
+SDL_Surface *GetRemotePlayerAvatar(Uint8 controlType)
+{
+ return nullptr;
+}
+
const bool *GetRemotePlayerKeyboardState(Uint8 controlType)
{
return nullptr;
diff --git a/game/steam.h b/game/steam.h
index 6ea3a99b..881e10ad 100644
--- a/game/steam.h
+++ b/game/steam.h
@@ -32,6 +32,7 @@ extern bool InitSteam();
extern RemotePlaySessionID_t GetRemoteSessionForGamepad(SDL_Gamepad *gamepad);
extern Uint8 GetRemoteSessionControl(RemotePlaySessionID_t sessionID);
extern const char *GetRemotePlayerName(Uint8 controlType);
+extern SDL_Surface* GetRemotePlayerAvatar(Uint8 controlType);
extern const bool *GetRemotePlayerKeyboardState(Uint8 controlType);
extern void EnableRemoteInput();
extern void DisableRemoteInput();
diff --git a/screenlib/UIElement.cpp b/screenlib/UIElement.cpp
index c84124dd..a88463e4 100644
--- a/screenlib/UIElement.cpp
+++ b/screenlib/UIElement.cpp
@@ -537,6 +537,14 @@ UIElement::SetImage(const char *name)
return true;
}
+void
+UIElement::SetImage(SDL_Surface *surface)
+{
+ UITexture *image = m_ui->CreateImage(surface);
+
+ SetImage(image);
+}
+
void
UIElement::SetImage(UITexture *image)
{
diff --git a/screenlib/UIElement.h b/screenlib/UIElement.h
index 94cbad83..2a29203f 100644
--- a/screenlib/UIElement.h
+++ b/screenlib/UIElement.h
@@ -221,6 +221,7 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
// Image information
bool SetImage(const char *name);
+ void SetImage(SDL_Surface *surface);
void SetImage(UITexture *image);
UITexture *GetImage() const {
return m_image;
diff --git a/screenlib/UIImageInterface.h b/screenlib/UIImageInterface.h
index 75430aae..82108451 100644
--- a/screenlib/UIImageInterface.h
+++ b/screenlib/UIImageInterface.h
@@ -28,6 +28,7 @@ class UIImageInterface
{
public:
virtual UITexture *CreateImage(const char *name) = 0;
+ virtual UITexture* CreateImage(SDL_Surface* surface) = 0;
virtual void FreeImage(UITexture *texture) = 0;
virtual UITexture *CreateBackground(const char *name) = 0;