Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
31dc7ad
Initial plan
Copilot Mar 21, 2026
1bc3bbb
Add RetroArch WebSocket client in src/retro-achievements
Copilot Mar 21, 2026
56bfe54
Refactor and streamline JSON parsing logic; adjust formatting for con…
kzryzstof Mar 21, 2026
5c30d57
Wire retro_achievements_monitor start/stop into OBS plugin lifecycle
Copilot Mar 21, 2026
9c4971c
Handle "achievements" WebSocket message from retro-arch#4
Copilot Mar 21, 2026
0f8d2a5
Handle "user"/"no_user" WebSocket messages from retro-arch#4
Copilot Mar 21, 2026
4308173
Refactor JSON parsing for achievements; replace `cJSON` type checks w…
kzryzstof Mar 21, 2026
b5de374
Merge remote-tracking branch 'origin/copilot/add-web-socket-client' i…
kzryzstof Mar 21, 2026
a7621f6
Refactor and reorganize Xbox and RetroAchievements integrations; migr…
kzryzstof Mar 21, 2026
77974a4
Introduce unified monitoring service for Xbox and RetroAchievements i…
kzryzstof Mar 21, 2026
5e04441
Refactor achievements handling: consolidate Xbox and RetroAchievement…
kzryzstof Mar 22, 2026
ab8fbdf
Refactor: rename and generalize Xbox-specific sources to platform-agn…
kzryzstof Mar 23, 2026
18481e8
Adjust achievement name formatting: remove redundant "G" suffix befor…
kzryzstof Mar 23, 2026
01fe2f9
Improve logging levels and achievement count handling; add 'Mastered'…
kzryzstof Mar 25, 2026
6c2d957
Adjust struct member alignment in tests for consistent formatting acr…
kzryzstof Mar 25, 2026
ffa9de7
Refactor game copy logic and improve Xbox game monitoring: handle nul…
kzryzstof Mar 25, 2026
ba55ab9
Refactor logging levels, enhance HTTP URL normalization, improve sess…
kzryzstof Apr 1, 2026
c30bbda
Implement active identity retrieval and notification improvements; en…
kzryzstof Apr 2, 2026
4a44974
Update GitHub Actions workflow: ensure vcpkg definitions are up-to-da…
kzryzstof Apr 2, 2026
33d4038
Define PATH_MAX fallback for non-POSIX systems; use MAX_PATH for Wind…
kzryzstof Apr 2, 2026
c5af97a
Refactor: simplify game ID parsing and improve identity source priori…
kzryzstof Apr 3, 2026
8cde15b
Add unit tests for `monitoring_service`; adjust identity notification…
kzryzstof Apr 3, 2026
349d5a3
Add RetroAchievements integration and unify monitoring logic for Xbox…
kzryzstof Apr 3, 2026
5fb4b44
Add comprehensive unit tests for `monitoring_service` to verify activ…
kzryzstof Apr 3, 2026
90496f2
Add comprehensive unit tests for `monitoring_service` to verify activ…
kzryzstof Apr 3, 2026
a8a0396
Add `time_stub.c` to monitoring service test stubs
kzryzstof Apr 3, 2026
a81951e
Add `test_monitoring_service` to macOS build tests; update README and…
kzryzstof Apr 3, 2026
b9eeeed
Refine active identity notification logic for Retro sources; add corr…
kzryzstof Apr 3, 2026
b432fc9
Standardize log messages across Xbox integration modules for consiste…
kzryzstof Apr 3, 2026
56d0b9d
Generalize source type names and descriptions; replace "Xbox" termino…
kzryzstof Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/build-macos
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ build() {
# Run tests if requested
if (( run_tests )) {
log_group "Building test targets..."
cmake --build build_macos --config ${config} --target test_encoder test_crypto test_convert test_parsers test_xbox_session test_types
cmake --build build_macos --config ${config} --target test_encoder test_crypto test_convert test_parsers test_monitoring_service test_xbox_session test_types

log_group "Running tests..."
ctest --test-dir build_macos --build-config ${config} --output-on-failure
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/build-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@ jobs:

- name: Install Dependencies 🔐
run: |
# Update vcpkg to the latest port definitions before installing.
# The port snapshots bundled with the windows-2022 runner image can
# become stale: if GitHub regenerates a release tarball the recorded
# SHA-512 hash in the port no longer matches and the download fails.
# Pulling the latest vcpkg tree ensures we get corrected hashes.
git -C "$env:VCPKG_INSTALLATION_ROOT" pull --ff-only

# Install OpenSSL and libwebsockets for x64 via vcpkg.
# The obs-deps prebuilt package does not ship either on Windows.
# The system OpenSSL must be replaced with the vcpkg one: the system
Expand Down Expand Up @@ -471,6 +478,10 @@ jobs:

- name: Install OpenSSL for ARM64 🔐
run: |
# Update vcpkg to the latest port definitions before installing.
# See the x64 step for the full explanation.
git -C "$env:VCPKG_INSTALLATION_ROOT" pull --ff-only

# Install OpenSSL and libwebsockets for ARM64 via vcpkg.
# The windows-2022 runner ships vcpkg at $env:VCPKG_INSTALLATION_ROOT.
# The system OpenSSL is x64-only; we need an arm64-windows build.
Expand Down
103 changes: 76 additions & 27 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,14 @@ target_sources(
${CMAKE_PROJECT_NAME}
PRIVATE
src/main.c
src/sources/xbox/gamerpic.c
src/sources/xbox/game_cover.c
src/sources/xbox/gamerscore.c
src/sources/xbox/gamertag.c
src/sources/xbox/achievement_name.c
src/sources/xbox/achievement_description.c
src/sources/xbox/achievement_icon.c
src/sources/xbox/achievements_count.c
src/sources/gamerpic.c
src/sources/game_cover.c
src/sources/gamerscore.c
src/sources/gamertag.c
src/sources/achievement_name.c
src/sources/achievement_description.c
src/sources/achievement_icon.c
src/sources/achievements_count.c
src/sources/common/text_source.c
src/sources/common/image_source.c
src/sources/common/achievement_cycle.c
Expand All @@ -494,12 +494,14 @@ target_sources(
src/net/browser/browser.c
src/net/http/http.c
src/net/json/json.c
src/oauth/util.c
src/oauth/xbox-live.c
src/xbox/account_manager.c
src/xbox/xbox_session.c
src/xbox/xbox_client.c
src/xbox/xbox_monitor.c
src/integrations/xbox/oauth/util.c
src/integrations/xbox/oauth/xbox-live.c
src/integrations/xbox/account_manager.c
src/integrations/xbox/xbox_session.c
src/integrations/xbox/xbox_client.c
src/integrations/xbox/xbox_monitor.c
src/integrations/monitoring_service.c
src/integrations/retro-achievements/retro_achievements_monitor.c
src/ui/xbox_account_config.cpp
src/io/state.c
src/io/cache.c
Expand All @@ -509,14 +511,16 @@ target_sources(
src/text/parsers.c
src/time/time.c
src/common/achievement.c
src/common/achievement_progress.c
src/common/device.c
src/common/game.c
src/common/gamerscore.c
src/common/identity.c
src/common/token.c
src/common/unlocked_achievement.c
src/common/xbox_identity.c
src/common/xbox_session.c
src/integrations/xbox/contracts/xbox_achievement.c
src/integrations/xbox/contracts/xbox_achievement_progress.c
src/integrations/xbox/contracts/xbox_unlocked_achievement.c
src/integrations/xbox/entities/xbox_identity.c
src/integrations/xbox/entities/xbox_session.c
)

# Link vendored deps
Expand Down Expand Up @@ -721,21 +725,65 @@ if(BUILD_TESTING)

target_link_test_deps(test_parsers)

# ------------------------------
# test_monitoring_service
# ------------------------------
add_executable(
test_monitoring_service
test/test_monitoring_service.c
${unity_SOURCE_DIR}/src/unity.c
src/integrations/monitoring_service.c
src/common/achievement.c
src/common/game.c
src/common/gamerscore.c
src/common/identity.c
src/common/token.c
src/integrations/xbox/contracts/xbox_achievement.c
src/integrations/xbox/contracts/xbox_achievement_progress.c
src/integrations/xbox/contracts/xbox_unlocked_achievement.c
src/integrations/xbox/entities/xbox_identity.c
test/stubs/bmem_stub.c
test/stubs/integrations/xbox_monitor_stub.c
test/stubs/integrations/retro_achievements_monitor_stub.c
test/stubs/io/cache_stub.c
test/stubs/time/time_stub.c
)

add_test(NAME test_monitoring_service COMMAND test_monitoring_service)

if(ENABLE_COVERAGE)
enable_coverage(test_monitoring_service)
endif()

target_include_directories(
test_monitoring_service
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/test/stubs
${CMAKE_CURRENT_SOURCE_DIR}/src
${unity_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/test
)

target_compile_definitions(test_monitoring_service PRIVATE UNITY_INCLUDE_CONFIG_H)

target_link_test_deps(test_monitoring_service)

# ------------------------------
# test_xbox_session
# ------------------------------
add_executable(
test_xbox_session
test/test_xbox_session.c
${unity_SOURCE_DIR}/src/unity.c
src/xbox/xbox_session.c
src/integrations/xbox/xbox_session.c
src/common/achievement.c
src/common/achievement_progress.c
src/common/game.c
src/common/gamerscore.c
src/common/token.c
src/common/unlocked_achievement.c
src/common/xbox_session.c
src/integrations/xbox/contracts/xbox_achievement.c
src/integrations/xbox/contracts/xbox_achievement_progress.c
src/integrations/xbox/contracts/xbox_unlocked_achievement.c
src/integrations/xbox/entities/xbox_session.c
test/stubs/bmem_stub.c
test/stubs/xbox/xbox_client_stub.c
test/stubs/time/time_stub.c
Expand Down Expand Up @@ -768,14 +816,15 @@ if(BUILD_TESTING)
test_types
test/test_types.c
${unity_SOURCE_DIR}/src/unity.c
src/xbox/xbox_session.c
src/integrations/xbox/xbox_session.c
src/common/achievement.c
src/common/achievement_progress.c
src/common/game.c
src/common/gamerscore.c
src/common/token.c
src/common/unlocked_achievement.c
src/common/xbox_session.c
src/integrations/xbox/contracts/xbox_achievement.c
src/integrations/xbox/contracts/xbox_achievement_progress.c
src/integrations/xbox/contracts/xbox_unlocked_achievement.c
src/integrations/xbox/entities/xbox_session.c
test/stubs/bmem_stub.c
test/stubs/xbox/xbox_client_stub.c
test/stubs/time/time_stub.c
Expand Down Expand Up @@ -805,6 +854,6 @@ if(BUILD_TESTING)
# Coverage target (must be after all test targets are defined)
# ------------------------------
if(ENABLE_COVERAGE)
add_coverage_target(test_encoder test_crypto test_convert test_parsers test_xbox_session test_types)
add_coverage_target(test_encoder test_crypto test_convert test_parsers test_monitoring_service test_xbox_session test_types)
endif()
endif()
113 changes: 81 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# OBS Achievements Tracker

A cross-platform OBS Studio plugin that displays Xbox Live profile data, current game information, and achievement progress for the signed-in Xbox user.
A cross-platform OBS Studio plugin that displays Xbox Live and RetroAchievements profile data, current game information, and achievement progress for the signed-in user.

## Features

- **Global Xbox account configuration dialog** using Microsoft's device-code flow
- **Real-time game and achievement tracking** through Xbox Live RTA monitoring when available
- **RetroAchievements integration** via a local RetroArch WebSocket server for retro game tracking
- **Unified monitoring service** that handles both Xbox and RetroAchievements sessions with last-game-received priority
- **Profile sources** for gamertag, gamerpic, and gamerscore
- **Achievement sources** for name, description, icon, and progress count
- **Customizable text sources** with persisted font and gradient color settings
Expand Down Expand Up @@ -59,32 +61,32 @@ After installation, restart OBS Studio.
3. Use the global Xbox Account dialog to review the current status and click **Sign in with Xbox**.
4. A browser window opens for Microsoft account authentication.
5. Once authentication succeeds, return to OBS. The dialog will update to show the connected account.
6. Add any of the Xbox display sources you want to use in your scene.
6. Add any of the display sources you want to use in your scene.

All Xbox sources in the plugin share the same authenticated account.
All Xbox sources in the plugin share the same authenticated account. RetroAchievements sources connect automatically when a RetroArch WebSocket server is detected on the local machine.

![Xbox Account dialog](plugin-xbox-account.png)
![Xbox Account dialog](images/plugin-xbox-account.png)

### Available OBS Sources

#### Account & profile

- **Xbox Gamertag**: text source for the current gamertag
- **Xbox Gamerpic**: image source for the current gamerpic
- **Xbox Gamerscore**: text source for the current gamerscore
- **Gamertag**: text source for the current gamertag or RetroAchievements display name
- **Gamerpic**: image source for the current gamerpic or RetroAchievements avatar
- **Gamerscore**: text source for the current gamerscore or RetroAchievements score

Account sign-in and sign-out are managed globally from **Tools** → **Xbox Account**.

#### Game

- **Xbox Game Cover**: image source for the currently active game's cover art
- **Game Cover**: image source for the currently active game's cover art

#### Achievements

- **Xbox Achievement (Name)**: current achievement name, including gamerscore when available
- **Xbox Achievement (Description)**: current achievement description
- **Xbox Achievement (Icon)**: current achievement icon
- **Xbox Achievements Count**: unlocked / total achievements for the current game (for example `12 / 50`)
- **Achievement (Name)**: current achievement name, including gamerscore when available
- **Achievement (Description)**: current achievement description
- **Achievement (Icon)**: current achievement icon
- **Achievements Count**: unlocked / total achievements for the current game (for example `12 / 50`)

#### Real-time updates

Expand All @@ -93,6 +95,14 @@ When Xbox Live monitoring is available, the plugin subscribes to:
- current game / presence changes
- achievement progression updates

When a local RetroArch WebSocket server is detected, the plugin additionally tracks:

- current retro game changes
- achievement list and unlock updates
- user identity (display name, score, avatar)

The active identity shown in profile sources is determined by whichever integration last reported a game change. If only one integration has an active game, that integration's identity is used.

Profile-derived sources such as gamerscore, gamertag, and gamerpic refresh from the authenticated session data used by the plugin.

---
Expand All @@ -104,27 +114,63 @@ Profile-derived sources such as gamerscore, gamertag, and gamerpic refresh from
```text
achievements-tracker-plugin/
├── src/
│ ├── main.c # OBS module entry point
│ ├── common/ # Shared types and value objects
│ ├── crypto/ # Proof-of-possession signing helpers
│ ├── diagnostics/ # Logging helpers
│ ├── drawing/ # Color and image rendering helpers
│ ├── encoding/ # Base64 helpers
│ ├── io/ # Persistent state and cache helpers
│ ├── net/ # Browser, HTTP, and JSON helpers
│ ├── oauth/ # Xbox/Microsoft authentication flow
│ ├── main.c # OBS module entry point
│ ├── common/ # Shared platform-agnostic types and value objects
│ │ ├── achievement.{c,h} # Generic achievement abstraction
│ │ ├── game.{c,h} # Generic game abstraction
│ │ ├── gamerscore.{c,h} # Gamerscore value object
│ │ ├── identity.{c,h} # Unified user identity (Xbox + RetroAchievements)
│ │ └── token.{c,h} # Auth token value object
│ ├── crypto/ # Proof-of-possession signing helpers
│ ├── diagnostics/ # Logging helpers
│ ├── drawing/ # Color and image rendering helpers
│ ├── encoding/ # Base64 helpers
│ ├── integrations/
│ │ ├── monitoring_service.{c,h} # Unified event fan-out for all integrations
│ │ ├── retro-achievements/ # RetroAchievements WebSocket monitor
│ │ └── xbox/
│ │ ├── account_manager.{c,h} # Xbox account lifecycle
│ │ ├── contracts/ # Xbox-specific wire types (achievements, progress)
│ │ ├── entities/ # Xbox identity and session value objects
│ │ ├── oauth/ # Xbox/Microsoft authentication flow
│ │ ├── xbox_client.{c,h} # Xbox REST API client
│ │ ├── xbox_monitor.{c,h} # Xbox Live RTA WebSocket monitor
│ │ └── xbox_session.{c,h} # Xbox session state
│ ├── io/ # Persistent state and cache helpers
│ ├── net/
│ │ ├── browser/ # System browser launcher
│ │ ├── http/ # HTTP client helpers
│ │ └── json/ # JSON helpers
│ ├── sources/
│ │ ├── common/ # Shared text/image source helpers
│ │ └── xbox/ # OBS source implementations
│ ├── text/ # Conversion and parsing helpers
│ ├── time/ # Time parsing utilities
│ ├── util/ # UUID and portability helpers
│ └── xbox/ # Xbox client, monitor, and session logic
├── test/ # Unity-based unit tests and stubs
├── data/ # Locale files and effects/resources
├── external/cjson/ # Vendored cJSON
├── cmake/ # Platform-specific CMake helpers
├── .github/ # CI workflows and composite actions
│ │ ├── common/ # Shared text/image source helpers and achievement cycle
│ │ ├── achievement_description.{c,h}
│ │ ├── achievement_icon.{c,h}
│ │ ├── achievement_name.{c,h}
│ │ ├── achievements_count.{c,h}
│ │ ├── game_cover.{c,h}
│ │ ├── gamerpic.{c,h}
│ │ ├── gamerscore.{c,h}
│ │ └── gamertag.{c,h}
│ ├── text/ # Conversion and parsing helpers
│ ├── time/ # Time parsing utilities
│ └── util/ # UUID and portability helpers
├── test/ # Unity-based unit tests and stubs
│ ├── stubs/
│ │ ├── integrations/ # Stubs for xbox_monitor and retro_achievements_monitor
│ │ ├── io/ # Stub for cache
│ │ ├── xbox/ # Stub for xbox_client
│ │ └── ...
│ ├── test_convert.c
│ ├── test_crypto.c
│ ├── test_encoder.c
│ ├── test_monitoring_service.c # Tests for the unified monitoring service
│ ├── test_parsers.c
│ ├── test_types.c
│ └── test_xbox_session.c
├── data/ # Locale files and effects/resources
├── external/cjson/ # Vendored cJSON
├── cmake/ # Platform-specific CMake helpers
├── .github/ # CI workflows and composite actions
├── CMakeLists.txt
├── CMakePresets.json
└── buildspec.json
Expand Down Expand Up @@ -421,6 +467,9 @@ cmake --build build_macos_dev --target test_convert --config Debug

cmake --build build_macos_dev --target test_parsers --config Debug
./build_macos_dev/Debug/test_parsers

cmake --build build_macos_dev --target test_monitoring_service --config Debug
./build_macos_dev/Debug/test_monitoring_service
```

---
Expand Down
Binary file added images/plugin-xbox-account.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading