Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
c281c16
ScoreSaber 2
Umbranoxio Jun 4, 2026
e2becc5
replays: suppress scrub miss effects
Umbranoxio Jun 7, 2026
e19ee39
replays: port main replay fixes
Umbranoxio Jun 7, 2026
9268755
ci: set upload trust OIDC audience
Umbranoxio Jun 7, 2026
19ff029
leaderboard: show bottom row separator
Umbranoxio Jun 7, 2026
170242f
leaderboard: align row click handling
Umbranoxio Jun 7, 2026
85b2ae5
leaderboard: play row click sound
Umbranoxio Jun 7, 2026
d3d266b
leaderboard: align panel prompt
Umbranoxio Jun 7, 2026
e624222
leaderboard: preserve status prompts
Umbranoxio Jun 7, 2026
289db77
score info: show controllers
Umbranoxio Jun 7, 2026
0bf39be
leaderboard: load api data in parallel
Umbranoxio Jun 7, 2026
21c36ea
global leaderboard: speed up page loads
Umbranoxio Jun 7, 2026
1eee64c
global leaderboard: play cell click sound
Umbranoxio Jun 7, 2026
2ba149c
replay: reset energy ui on scrub
Umbranoxio Jun 7, 2026
8059dc1
replay: skip combo break animation on scrub
Umbranoxio Jun 7, 2026
cc61719
replay: remove shared accessors
Umbranoxio Jun 7, 2026
5e1db54
build: simplify local build
Umbranoxio Jun 8, 2026
9081842
docs: trim local settings
Umbranoxio Jun 8, 2026
7d6e489
leaderboards: hide PP for unranked details
Umbranoxio Jun 8, 2026
596609c
leaderboards: fix replay and level handling
Umbranoxio Jun 8, 2026
2d2ace0
replays: restore Camera2 compatibility
Umbranoxio Jun 8, 2026
9cd76f3
im a dumbass
Umbranoxio Jun 8, 2026
782362f
tooling: monkaOgre
Umbranoxio Jun 9, 2026
1ab94c7
ci: fixes
Umbranoxio Jun 9, 2026
596c7b5
ci: :(
Umbranoxio Jun 9, 2026
fe1b1eb
ci: clean up release artifact names
Umbranoxio Jun 9, 2026
841e47a
compatibility!
Umbranoxio Jun 10, 2026
5cdd000
compatibility: update version config
Umbranoxio Jun 10, 2026
15c9e67
tooling: VS + BSMT DX improvement
Umbranoxio Jun 10, 2026
6c5ea31
replay: support chain head arc scoring
Umbranoxio Jun 10, 2026
67718ad
ci: use verified beatmods deps only
Umbranoxio Jun 10, 2026
678222d
Fix: Ensure replay camera FOV is set after FPFC toggle
speecil Jun 15, 2026
2dbf951
Fix: "Laser" obj off if player has not failed
speecil Jun 15, 2026
23cd596
Fix: FC bars would remain active after scrubbing past a miss
speecil Jun 15, 2026
e307d21
Replays: Add arrow key seeking
speecil Jun 15, 2026
2704e17
Fix: Energy icon restoration when BE enabled
speecil Jun 15, 2026
84657e9
Replays: stop imber ui being togglable in fpfc
speecil Jun 15, 2026
69d5500
Leaderboards: Fix time parsing to fix time set text
speecil Jun 15, 2026
9f7f583
Devices: fix 1.29.1 device detection
speecil Jun 15, 2026
149405a
Replays: Update OnGUI UI seek label
speecil Jun 16, 2026
6acfef3
leaderboard: add fading + tweening service
speecil Jun 16, 2026
9c91fe0
leaderboard: add prompt tweening
speecil Jun 16, 2026
1a8947e
replays: fix energy bar icon colours on scrub
speecil Jun 16, 2026
e56b64c
bsml: update bsml schema
speecil Jun 16, 2026
8ad9da8
live: add tournament play and spectating support
Umbranoxio Jun 23, 2026
b3f7546
Merge branch 'feat/ludus-client' into refactor/scoresaber-2
Umbranoxio Jun 23, 2026
5960b5f
leaderboard: remove usage of leaderboard core's INotifyScoreUpload
Qwasyx Jun 23, 2026
80caab9
live: harden ludus reconnects
Umbranoxio Jun 24, 2026
0fb4677
leaderboard: improve missing leaderboard state
Umbranoxio Jun 24, 2026
5b3ca37
leaderboards: support LeaderboardCore switching
Umbranoxio Jun 24, 2026
5444872
replays: add extension metadata support
Umbranoxio Jun 24, 2026
6518c6e
live: guard fresh auth refresh
Umbranoxio Jun 24, 2026
fabfc16
manifest: bump version to 3.3.20
Umbranoxio Jun 24, 2026
badc15e
leaderboards: suppress platform custom warning
Umbranoxio Jun 24, 2026
ae82184
beatsaver: harden map downloads
Umbranoxio Jun 24, 2026
bc27463
live: handle close room command
Umbranoxio Jun 24, 2026
e752f3a
ludus: chat ui fixes
Umbranoxio Jun 24, 2026
df3e07d
live: wait for stable FPS before map start
Umbranoxio Jun 24, 2026
2cb4b20
live: upload host-stopped maps as quit
Umbranoxio Jun 24, 2026
28be6a3
live: defer replay packet encoding
Umbranoxio Jun 24, 2026
980a3db
manifest: bump version to 3.3.21
Umbranoxio Jun 24, 2026
be10403
live: defer FPS gate pause
Umbranoxio Jun 24, 2026
e103764
live: harden FPS start gate
Umbranoxio Jun 24, 2026
04ce57f
compete: handle closed room joins
Umbranoxio Jun 24, 2026
add4d97
settings: add HSV profile sharing opt-in
Umbranoxio Jun 24, 2026
792e050
live: delay start presence until FPS gate
Umbranoxio Jun 24, 2026
d8f26e9
manifest: bump version to 3.3.22
Umbranoxio Jun 24, 2026
0282e49
leaderboards: guard legacy LeaderboardCore patch
Umbranoxio Jun 24, 2026
bd191cf
live: report installed mods to ludus
Umbranoxio Jun 24, 2026
f24a76c
live: show denied mod join errors
Umbranoxio Jun 24, 2026
b490e52
live: show denied mods while joining
Umbranoxio Jun 24, 2026
4ffe8de
manifest: bump version to 3.3.23
Umbranoxio Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"nswag.consolecore": {
"version": "14.3.0",
"commands": [
"nswag"
],
"rollForward": false
}
}
}
11 changes: 9 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ indent_style = space
tab_width = 4

# New line preferences
end_of_line = crlf
insert_final_newline = false
insert_final_newline = true

#### .NET Coding Conventions ####

Expand Down Expand Up @@ -219,3 +218,11 @@ dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

[src/Core/Api/Generated/**/*.cs]
generated_code = true
dotnet_analyzer_diagnostic.severity = none

[src/Libraries/SevenZip/**/*.cs]
generated_code = true
dotnet_analyzer_diagnostic.severity = none
12 changes: 10 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# Auto detect text files and perform LF normalization
* text=auto
# Normalize text files to LF in the repository.
* text=auto eol=lf

*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary
*.furry binary
15 changes: 2 additions & 13 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,2 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: scoresaber
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
github: ScoreSaber
patreon: scoresaber
24 changes: 24 additions & 0 deletions .github/version-matrix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"versions": [
{
"base": "1.29.0",
"compatible": ["1.29.0", "1.29.1"]
},
{
"base": "1.37.1",
"compatible": ["1.37.1", "1.37.2"]
},
{
"base": "1.38.0",
"compatible": ["1.38.0", "1.39.0", "1.39.1"]
},
{
"base": "1.40.0",
"compatible": ["1.40.0", "1.40.1", "1.40.2", "1.40.3", "1.40.4", "1.40.5", "1.40.6", "1.40.7", "1.40.8"]
},
{
"base": "1.42.0",
"compatible": ["1.42.0", "1.42.1", "1.42.2", "1.42.3", "1.43.0", "1.44.0"]
}
]
}
221 changes: 197 additions & 24 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,242 @@ name: Build

on:
workflow_dispatch:
inputs:
base_version:
description: Beat Saber base version to build
required: true
default: All
type: choice
options:
- All
- 1.29.0
- 1.37.1
- 1.38.0
- 1.40.0
- 1.42.0

permissions:
contents: read
id-token: write

jobs:
Plan:
runs-on: ubuntu-22.04
outputs:
versions: ${{ steps.plan.outputs.versions }}
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Plan build matrix
id: plan
run: |
selected="${{ inputs.base_version }}"
if [ "$selected" = "All" ]; then
matrix=$(jq -c '.versions' .github/version-matrix.json)
else
matrix=$(jq -c --arg selected "$selected" '[.versions[] | select(.base == $selected)]' .github/version-matrix.json)
fi

if [ "$matrix" = "[]" ]; then
echo "Unsupported base version: $selected" >&2
exit 1
fi

echo "versions=$matrix" >> "$GITHUB_OUTPUT"

Build:
needs: Plan
name: Build (${{ matrix.base }})
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.Plan.outputs.versions) }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup dotnet
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Read manifest and set game version
id: read-json
run: |
json_content=$(cat ${{github.workspace}}/ScoreSaber/manifest.json)
base_version="${{ matrix.base }}"
stripped_refs_version="$base_version"
if [ "$base_version" = "1.29.0" ]; then
stripped_refs_version="1.29.1"
fi
compatible_versions='${{ toJson(matrix.compatible) }}'
manifest="${{ github.workspace }}/src/Manifests/$base_version/manifest.json"
if [ ! -f "$manifest" ]; then
manifest="${{ github.workspace }}/src/manifest.json"
fi

json_content=$(cat "$manifest")
game_version=$(echo "$json_content" | jq -r .gameVersion)
manifest_plugin_version=$(echo "$json_content" | jq -r .version)
plugin_version="$manifest_plugin_version"
if [[ "$plugin_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
plugin_version="$plugin_version.0"
fi
upload_version_hash=$(printf "%s%s" "$plugin_version" "$game_version" | md5sum | awk '{print $1}')
artifact_game_version=$(echo "$compatible_versions" | jq -r '.[0] + "-to-" + .[-1]')
short_sha=$(echo "${{ github.sha }}" | cut -c1-7)
artifact_name="ScoreSaber-v$manifest_plugin_version-bs$artifact_game_version-$short_sha"

supported_versions="[]"
for version in $(echo "$compatible_versions" | jq -r '.[]'); do
version_hash=$(printf "%s%s" "$plugin_version" "$version" | md5sum | awk '{print $1}')
supported_versions=$(echo "$supported_versions" | jq -c \
--arg gameVersion "$version" \
--arg uploadVersionHash "$version_hash" \
'. + [{ gameVersion: $gameVersion, uploadVersionHash: $uploadVersionHash }]')
done

echo "Base Version: $base_version"
echo "Stripped Refs Version: $stripped_refs_version"
echo "Game Version: $game_version"
echo "::set-output name=game_version::$game_version"
echo "Manifest Plugin Version: $manifest_plugin_version"
echo "Upload Plugin Version: $plugin_version"
echo "Artifact Name: $artifact_name.zip"
echo "Supported Versions: $supported_versions"
echo "base_version=$base_version" >> "$GITHUB_OUTPUT"
echo "stripped_refs_version=$stripped_refs_version" >> "$GITHUB_OUTPUT"
echo "game_version=$game_version" >> "$GITHUB_OUTPUT"
echo "manifest_plugin_version=$manifest_plugin_version" >> "$GITHUB_OUTPUT"
echo "plugin_version=$plugin_version" >> "$GITHUB_OUTPUT"
echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT"
echo "upload_version_hash=$upload_version_hash" >> "$GITHUB_OUTPUT"
echo "manifest=$manifest" >> "$GITHUB_OUTPUT"
echo "supported_versions=$supported_versions" >> "$GITHUB_OUTPUT"

- name: Register official build
id: official-build
shell: pwsh
run: |
$audience = [Uri]::EscapeDataString("scoresaber-upload-build-registration")
$oidcResponse = Invoke-RestMethod `
-Uri "$env:ACTIONS_ID_TOKEN_REQUEST_URL&audience=$audience" `
-Headers @{ Authorization = "Bearer $env:ACTIONS_ID_TOKEN_REQUEST_TOKEN" }

$supportedVersions = '${{ steps.read-json.outputs.supported_versions }}' | ConvertFrom-Json
$buildId = "scoresaber-pc-${{ steps.read-json.outputs.base_version }}-${{ github.run_id }}-${{ github.run_attempt }}"
$body = @{
buildId = $buildId
pluginVersion = "${{ steps.read-json.outputs.plugin_version }}"
gameVersion = "${{ steps.read-json.outputs.game_version }}"
uploadVersionHash = "${{ steps.read-json.outputs.upload_version_hash }}"
supportedVersions = $supportedVersions
protocolVersion = 2
commitSha = "${{ github.sha }}"
sourceRepository = "${{ github.repository }}"
sourceRef = "${{ github.ref }}"
sourceWorkflowRef = "${{ github.workflow_ref }}"
} | ConvertTo-Json -Depth 8

$response = Invoke-RestMethod `
-Uri "https://scoresaber.com/api/v2/game/official-builds" `
-Method Post `
-ContentType "application/json" `
-Headers @{ Authorization = "Bearer $($oidcResponse.value)" } `
-Body $body

Write-Output "::add-mask::$($response.credential)"
"build_id=$($response.build.buildId)" >> $env:GITHUB_OUTPUT
"credential=$($response.credential)" >> $env:GITHUB_OUTPUT

- name: Checkout stripped dlls
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: ScoreSaber/scoresaber-pc-ci-stripped
repository: ScoreSaber/pc-ci-stripped
token: ${{ secrets.CI_PAT }}
path: Stripped
sparse-checkout: |
Versions/${{ steps.read-json.outputs.game_version }}
Versions/${{ steps.read-json.outputs.stripped_refs_version }}
sparse-checkout-cone-mode: false

- name: Verify that stripped dlls actually exist
run: |
if [ -z "$(ls -A Stripped/Versions/${{ steps.read-json.outputs.game_version }})" ]; then
if [ -z "$(ls -A Stripped/Versions/${{ steps.read-json.outputs.stripped_refs_version }})" ]; then
echo "Error: Stripped dependencies folder is empty!"
exit 1
fi

- name: Move stripped dlls to refs
run: mv Stripped/Versions/${{ steps.read-json.outputs.game_version }} Refs
run: mv Stripped/Versions/${{ steps.read-json.outputs.stripped_refs_version }} Refs

- name: Acquire mod references
uses: Goobwabber/download-beatmods-deps@1.3
with:
manifest: ${{github.workspace}}/ScoreSaber/manifest.json
shell: pwsh
run: |
$gameVersion = "${{ steps.read-json.outputs.game_version }}"
$manifest = Get-Content "${{ steps.read-json.outputs.manifest }}" -Raw | ConvertFrom-Json
$deps = @($manifest.dependsOn.PSObject.Properties.Name) + "LeaderboardCore"

$response = Invoke-RestMethod -Uri "https://beatmods.com/api/mods?gameVersion=$gameVersion&status=verified"
foreach ($name in $deps) {
$entry = $response.mods | Where-Object { $_.mod.name -eq $name } | Select-Object -First 1
if ($null -eq $entry) {
throw "No verified version of '$name' on BeatMods for $gameVersion"
}

$version = $entry.latest
Write-Output "Downloading $name $($version.modVersion)..."
Invoke-WebRequest -Uri "https://beatmods.com/cdn/mod/$($version.zipHash).zip" -OutFile "$name.zip"
Expand-Archive -LiteralPath "$name.zip" -DestinationPath Refs -Force
}

- name: Build
id: Build
env:
FrameworkPathOverride: /usr/lib/mono/4.8-api
run: dotnet build --configuration Release
ScoreSaberOfficialBuildId: ${{ steps.official-build.outputs.build_id }}
ScoreSaberOfficialBuildCredential: ${{ steps.official-build.outputs.credential }}
run: |
dotnet build ScoreSaber.sln \
--configuration Release \
-p:Platform="BS${{ steps.read-json.outputs.base_version }}" \
-p:ScoreSaberTargetVersion="${{ steps.read-json.outputs.base_version }}" \
-p:ScoreSaberUseLocalRefs=true \
-p:LocalRefsDir="${{ github.workspace }}/Refs" \
-p:GameReferences="${{ github.workspace }}/Refs" \
-p:Version="${{ steps.read-json.outputs.manifest_plugin_version }}" \
-p:AssemblyVersion="${{ steps.read-json.outputs.plugin_version }}" \
-p:FileVersion="${{ steps.read-json.outputs.plugin_version }}" \
-p:DisableCopyToGame=True \
-p:DisableZipRelease=False

- name: Find artifact
id: artifact
run: |
artifact_dir="src/bin/${{ steps.read-json.outputs.base_version }}/Release"
artifact_path="$(find "$artifact_dir" -path '*/zip/*.zip' -type f | sort | head -n 1)"
if [ -z "$artifact_path" ]; then
echo "No release zip found under $artifact_dir" >&2
find "$artifact_dir" -maxdepth 4 -type f | sort >&2
exit 1
fi

echo "Artifact: $artifact_path"
staging_dir="${{ runner.temp }}/${{ steps.read-json.outputs.artifact_name }}"
rm -rf "$staging_dir"
mkdir -p "$staging_dir"
unzip -q "$artifact_path" -d "$staging_dir"

echo "Staged artifact contents: $staging_dir"
echo "path=$staging_dir" >> "$GITHUB_OUTPUT"

- name: Check formatting
run: dotnet format whitespace ScoreSaber.sln --verify-no-changes --no-restore --exclude src/Core/Api/Generated src/Libraries/SevenZip

- name: Git Status
run: git status

- name: Echo file name
run: echo $BUILDTEXT \($ASSEMBLYNAME\)
env:
BUILDTEXT: Filename=${{ steps.Build.outputs.filename }}
ASSEMBLYNAME: AssemblyName=${{ steps.Build.outputs.assemblyname }}

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.Build.outputs.filename }}
path: ${{ steps.Build.outputs.artifactpath }}
name: ${{ steps.read-json.outputs.artifact_name }}
path: ${{ steps.artifact.outputs.path }}/*
if-no-files-found: error
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ bld/
[Ll]og/
[Ll]ogs/

# Local MSBuild developer settings and secrets
Directory.Build.local.props
[Rr]efs/

# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
Expand Down
26 changes: 26 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/tools/openapi/ScoreSaber.OpenApiTools/bin/Debug/net8.0/ScoreSaber.OpenApiTools.dll",
"args": [],
"cwd": "${workspaceFolder}/tools/openapi/ScoreSaber.OpenApiTools",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
Loading
Loading