Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
0d16bca
begin moving scoresaber to leaderboardcore
speecil Oct 13, 2024
f59914c
More stuff i forgot, not working
speecil Oct 14, 2024
b4caac8
Closer, but bsml macros are laughing at me
speecil Oct 15, 2024
b5c2569
somewhere
speecil Oct 15, 2024
fbcc9d4
Fixed Page
speecil Oct 15, 2024
7722966
Merge remote-tracking branch 'upstream/main'
speecil Oct 16, 2024
1479e6b
Showing scores now, still buggy
speecil Oct 16, 2024
ef71707
VERY WORKING STATE
speecil Oct 16, 2024
0805e41
Cache leaderboardInfoMaps and fade cell text in
speecil Oct 17, 2024
9ad13ed
Handle ost
speecil Oct 17, 2024
82bf098
MapInfoModal, fix lb pfp on first launch with less than 10 cells
speecil Oct 17, 2024
2c4c5a2
fix panelview misalignment
speecil Oct 17, 2024
9e37d6a
begin desktop imber ui
speecil Oct 18, 2024
d53b9ae
Ensure seperator is not scaled
speecil Oct 18, 2024
7f41d33
Add graphic raycaster and scaler
speecil Oct 18, 2024
c25e39c
Display Imber on desktop, not clickable rn
speecil Oct 19, 2024
130bc29
Graphic raycaster on viewcontroller
speecil Oct 19, 2024
d92b5d2
Working imber ui + scrubber, cant move it around yet
speecil Oct 21, 2024
e78761b
Move desktop imber ui, add settings, bug fixes
speecil Oct 22, 2024
3a1fdf2
Add hiding replay watermark if it is the users replay
speecil Oct 22, 2024
60c3dde
Fix replay sync
speecil Oct 22, 2024
fc4d7ca
Make sure playback is paused while scrubbing
speecil Oct 22, 2024
2e7f7d6
Fix legacyreplayplayer throwing
speecil Oct 22, 2024
80242d5
Update README.md
speecil Oct 22, 2024
46e6902
C to unlock/lock cursor while in replay
speecil Oct 22, 2024
d8c058f
Merge branch 'main' of https://github.com/speecil/scoresaber-plugin
speecil Oct 22, 2024
5dc64aa
Make tab selector bigger in imber desktop
speecil Oct 22, 2024
90ed814
Fix energyplayer
speecil Oct 22, 2024
70a4d95
smooth profile picture fade in
speecil Oct 23, 2024
602d70e
Improve timebar
speecil Oct 23, 2024
a2cbeb8
Move tweening service, add gaymode and denyah mode back
speecil Oct 23, 2024
4b44641
Enable controller details in score modal
speecil Oct 23, 2024
3795421
forgot about the bsml
speecil Oct 23, 2024
50a2374
Increase score modal height
speecil Oct 23, 2024
9604a51
Ensure time is paused while scrubbing from bg
speecil Oct 23, 2024
ee1e2db
Actually remove LB Patches file :)
speecil Oct 23, 2024
0c15aec
remove LeaderboardMap unity engine include
speecil Nov 4, 2024
5244203
enable scrub keybinds
speecil Nov 4, 2024
4eca372
set if users replay earlier
speecil Nov 4, 2024
bd4806d
save fpfc variable to plugin
speecil Nov 4, 2024
6149138
align panelview prompt left
speecil Nov 4, 2024
890479e
make header text show map info instead
speecil Nov 4, 2024
dddb5a3
add desktop replay ui toggle hint
speecil Nov 4, 2024
32a94f6
make replay ui not italic
speecil Nov 4, 2024
1f4fedb
:)
speecil Nov 4, 2024
fdd30f9
Unskew buttons too
speecil Nov 4, 2024
0c6dd88
add CCT to team
speecil Nov 4, 2024
2e9a016
Promo Banner WIP
speecil Nov 10, 2024
c988bb7
Add arrow key bind to scrub +- 5 seconds
speecil Nov 16, 2024
2c9a812
fade desktop imber
speecil Nov 16, 2024
90b4e81
Rich presence init
speecil Dec 3, 2024
6b5dbe7
Add toggle for rich presence
speecil Dec 4, 2024
b98bc2f
bugfix richpresence
speecil Dec 4, 2024
367c845
Show current beatmap in rich presence, can download / go to beatmap
speecil Dec 5, 2024
cb18a0b
Add map presence UI to profile modal
speecil Dec 10, 2024
b0fad27
Qwasyx Review 1
speecil Dec 11, 2024
5c616cb
Qwasyx review 2
speecil Dec 11, 2024
0ae231b
Publicize DataModels
speecil Dec 11, 2024
4e25796
bugfix
speecil Dec 11, 2024
01f5b59
Qwasyx Review 3
speecil Dec 12, 2024
0c9bfdf
make rich p not on by default, popup modal
speecil Dec 12, 2024
14548c0
Qwasyx review 4
speecil Dec 12, 2024
dfc0c17
Umbra review 1
speecil Dec 14, 2024
16dcb3b
rich disclaimer bug fix
speecil Dec 14, 2024
875fd86
use menu button, umbra review 2
speecil Dec 14, 2024
cd62533
Make header use scoresaber diff colours
speecil Dec 14, 2024
40ae61a
Http client rewrite + small changes
speecil Dec 15, 2024
256182e
remove static http client, only injection
speecil Dec 15, 2024
56968cd
Allow OST score uploading
speecil Dec 15, 2024
8711994
Null check levelAuthorName
speecil Dec 15, 2024
3f7ab10
Update to 1.40, fix arc replay
speecil Dec 17, 2024
17bf7f4
replay consistency
speecil Dec 22, 2024
020100a
yellow menu button
speecil Dec 22, 2024
448f951
Replay updates / bugfixes
speecil Dec 26, 2024
61d0f05
Qwasyx Review 4?
speecil Dec 27, 2024
15dba32
fix timescale text folding itself
speecil Dec 28, 2024
1c21390
handle end of song correctly
speecil Jan 3, 2025
7a69e2b
fix replay softlock
speecil Jan 3, 2025
7624715
More replay consistency
speecil Jan 5, 2025
2198fa7
update gitignore
speecil Jan 10, 2025
b17d21f
fix fpfc auto pause when hmd connected
speecil Mar 9, 2025
c4d28d3
properly dispose of web requests / pfp cancellation now works
RileyDevS15 Mar 19, 2025
5c675d6
update panel view to be simpler and hold map status
RileyDevS15 Mar 22, 2025
0f2253e
Add misses to the desktop imber timebar
RileyDevS15 May 5, 2025
c0fbc49
fix energy acting like you failed everytime you scrub the replay
RileyDevS15 May 5, 2025
7f3c88f
Fix "Coroutine couldnt be started" error when lb is not active
RileyDevS15 May 7, 2025
f6315b8
Ensure map info ranked text doesnt clip out
RileyDevS15 May 7, 2025
b1f680c
Noodle movement fixes, desktop imber small changes
RileyDevS15 May 12, 2025
c712618
Fix replay start unity exceptions
RileyDevS15 May 12, 2025
ec6c12d
Port Qwasyx scoringtype fixes + various fixes
RileyDevS15 May 12, 2025
430123f
Fix energy unity exceptions + desktop imber ui fixes
RileyDevS15 May 13, 2025
b93a63b
fpfc pose player fix, thread safety
RileyDevS15 May 14, 2025
43b34f7
global leaderboard fallback to country
RileyDevS15 May 14, 2025
05e3311
freecam prompt
RileyDevS15 May 14, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,4 @@ MigrationBackup/
ScoreSaber/ObfuscationSettings.cs
ScoreSaber/Core/ReplaySystem/Legacy/LegacyRecorder.cs
ScoreSaber/Core/ReplaySystem/Legacy/OldLegacyReplayPlayer.cs
/.idea
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# ScoreSaber-Plugin
Clone, and goodluck. The open source release of ScoreSaber was done hastily and quite a lot needs to be changed and plenty of "security through obscurity" practices covered by obfuscation can finally be removed, cleaning up the project.
ScoreSaber is Beat Saber's largest leaderboard system for custom songs, hosting 60 million scores across 170,000+ leaderboards, with more than 1 million users worldwide

Everything here is licensed under MIT and we'll be accepting contributors immediately, so if you're looking to help us out, that'd be greatly appreciated, we're going to need it.
![image](https://github.com/user-attachments/assets/f638f92b-d961-46e1-8277-d2628676128a)

Scores can also be viewed on [the website](https://scoresaber.com)

# Installation
## Method 1
- Install from your preferred mod manager
## Method 2
Comment thread
speecil marked this conversation as resolved.
- Download from the [releases](https://github.com/ScoreSaber/scoresaber-plugin/releases/latest)
102 changes: 102 additions & 0 deletions ScoreSaber/Core/AffinityPatches/MenuPresencePatches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using HarmonyLib;
using ScoreSaber.Core.Data.Models;
using ScoreSaber.Core.Services;
using ScoreSaber.Core.Utils;
using SiraUtil.Affinity;
using SiraUtil.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zenject;

namespace ScoreSaber.Core.AffinityPatches {
public class MenuPresencePatches : IAffinity {

[Inject] private readonly RichPresenceService _richPresenceService = null;

[AffinityPatch(typeof(SinglePlayerLevelSelectionFlowCoordinator), "HandleStandardLevelDidFinish")]
[AffinityPostfix]
public void HandleStopStandardLevelPostfix() {
var jsonObject = new SceneChangeEvent() {
Timestamp = _richPresenceService.TimeRightNow,
Scene = Scene.menu
};

_richPresenceService.SendUserProfileChannel("scene_change", jsonObject);
}

[AffinityPatch(typeof(SinglePlayerLevelSelectionFlowCoordinator), nameof(SinglePlayerLevelSelectionFlowCoordinator.StartLevel))]
[AffinityPostfix]
public void HandleStartLevelPostfix(SinglePlayerLevelSelectionFlowCoordinator __instance, Action beforeSceneSwitchCallback, bool practice) {

bool isPractice = practice;
GameMode gameMode = GameMode.solo;

if (isPractice) {
gameMode = GameMode.practice;

// this is to privatise the practice mode song, as it would be exposed in the rich presence, still not shown in UI though.
var songEventPrivate = new SongRichPresenceInfo(_richPresenceService.TimeRightNow, gameMode,
"PRACTICE",
string.Empty,
"PRACTICE AUTHOR",
"PRACTICE MAPPER",
"Standard",
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
(int)2000000000,
1,
0,
1);

_richPresenceService.SendUserProfileChannel("start_song", songEventPrivate);
return;
}

GameplayModifiers gameplayModifiers = __instance.gameplayModifiers;
int startTime = 0;
if (__instance.isInPracticeView) {
startTime = (int)__instance._practiceViewController._practiceSettings.startSongTime;
}

var songEvent = CreateSongStartEvent(__instance.selectedBeatmapLevel, __instance.selectedBeatmapKey, gameplayModifiers, startTime, gameMode);

_richPresenceService.SendUserProfileChannel("start_song", songEvent);
}

[AffinityPatch(typeof(MultiplayerLevelSelectionFlowCoordinator), nameof(MultiplayerLevelSelectionFlowCoordinator.HandleLobbyGameStateControllerGameStarted))]
[AffinityPostfix]
public void HandleMultiplayerGameStartPostfix(MultiplayerLevelSelectionFlowCoordinator __instance, ILevelGameplaySetupData levelGameplaySetupData) {
Comment thread
Umbranoxio marked this conversation as resolved.

// i dont like this, but i have to do it, just in case the users selected level doesnt match what the game started with
BeatmapLevel beatmapLevel = SongCore.Loader.GetLevelById(levelGameplaySetupData.beatmapKey.levelId);

GameplayModifiers gameplayModifiers = levelGameplaySetupData.gameplayModifiers;
int startTime = 0;

var songEvent = CreateSongStartEvent(beatmapLevel, levelGameplaySetupData.beatmapKey, gameplayModifiers, startTime, GameMode.multiplayer);

_richPresenceService.SendUserProfileChannel("start_song", songEvent);
}

private SongRichPresenceInfo CreateSongStartEvent(BeatmapLevel beatmapLevel, BeatmapKey beatmapKey, GameplayModifiers gameplayModifiers, int startTime, GameMode gameMode) {

string hash = BeatmapUtils.GetHashFromLevelID(beatmapLevel, out bool isOst);

var songEvent = new SongRichPresenceInfo(_richPresenceService.TimeRightNow, gameMode,
beatmapLevel.songName,
beatmapLevel.songSubName,
BeatmapUtils.FriendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters),
beatmapLevel.songAuthorName,
beatmapKey.beatmapCharacteristic.SerializedName(),
hash,
(int)beatmapLevel.songDuration,
((int)beatmapKey.difficulty * 2) + 1,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we still going with the (by the game long removed) numeric difficulty instead of string difficulty? (Sorry if this was already sufficiently resolved, but this big review is starting to get a bit out of hand and confusing.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numeric would be easier overall, but im fine with changing it if the backend changes it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what sense would numeric be easier? Don't you have to keep converting it everywhere?

startTime,
gameplayModifiers.songSpeedMul);

return songEvent;
}
}
}
5 changes: 5 additions & 0 deletions ScoreSaber/Core/AppInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using ScoreSaber.Core.Daemons;
using ScoreSaber.Core.Http;
using ScoreSaber.Core.Services;
using System.Reflection;
using Zenject;

Expand All @@ -7,8 +9,11 @@ internal class AppInstaller : Installer {

public override void InstallBindings() {
Plugin.Container = Container;
Container.Bind<PlayerService>().AsSingle();

Container.Bind<ReplayService>().AsSingle().NonLazy();
Container.BindInterfacesAndSelfTo<RichPresenceService>().AsSingle();
Container.Bind<ScoreSaberHttpClient>().FromInstance(new ScoreSaberHttpClient(new HttpClientOptions(applicationName: "ScoreSaber-PC", version: Plugin.Instance.LibVersion, defaultTimeout: 5, uploadTimeout: 120))).AsSingle();
}
}
}
67 changes: 35 additions & 32 deletions ScoreSaber/Core/Daemons/UploadDaemon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
using ScoreSaber.Core.Utils;
using static ScoreSaber.UI.Leaderboard.ScoreSaberLeaderboardViewController;
using System.Threading;
using ScoreSaber.Core.Http;
using ScoreSaber.Core.Http.Configuration;
using ScoreSaber.Core.Http.Endpoints.API;

namespace ScoreSaber.Core.Daemons {

Expand All @@ -28,18 +31,20 @@ internal class UploadDaemon : IDisposable, IUploadDaemon {
private readonly PlayerService _playerService = null;
private readonly ReplayService _replayService = null;
private readonly LeaderboardService _leaderboardService = null;
private readonly ScoreSaberHttpClient _scoreSaberHttpClient = null;

private readonly PlayerDataModel _playerDataModel = null;
private readonly MaxScoreCache _maxScoreCache = null;

private const string UPLOAD_SECRET = "f0b4a81c9bd3ded1081b365f7628781f";

public UploadDaemon(PlayerService playerService, LeaderboardService leaderboardService, ReplayService replayService, PlayerDataModel playerDataModel, MaxScoreCache maxScoreCache) {
public UploadDaemon(PlayerService playerService, LeaderboardService leaderboardService, ReplayService replayService, PlayerDataModel playerDataModel, MaxScoreCache maxScoreCache, ScoreSaberHttpClient scoreSaberHttpClient) {
_playerService = playerService;
_replayService = replayService;
_leaderboardService = leaderboardService;
_playerDataModel = playerDataModel;
_maxScoreCache = maxScoreCache;
_scoreSaberHttpClient = scoreSaberHttpClient;

SetupUploader();
Plugin.Log.Debug("Upload service setup!");
Expand Down Expand Up @@ -127,33 +132,31 @@ public void Five(string gameMode, BeatmapLevel beatmapLevel, BeatmapKey beatmapK
//This starts the upload processs
async void Six(BeatmapLevel beatmapLevel, BeatmapKey beatmapKey, LevelCompletionResults levelCompletionResults) {

if (!beatmapLevel.hasPrecalculatedData) {
int maxScore = await _maxScoreCache.GetMaxScore(beatmapLevel, beatmapKey);
int maxScore = await _maxScoreCache.GetMaxScore(beatmapLevel, beatmapKey);

if (levelCompletionResults.multipliedScore > maxScore) {
UploadStatusChanged?.Invoke(UploadStatus.Error, "Failed to upload (score was impossible)");
Plugin.Log.Debug($"Score was better than possible, not uploading!");
return;
}
if (levelCompletionResults.multipliedScore > maxScore) {
UploadStatusChanged?.Invoke(UploadStatus.Error, "Failed to upload (score was impossible)");
Plugin.Log.Debug($"Score was better than possible, not uploading!");
return;
}

try {
UploadStatusChanged?.Invoke(UploadStatus.Packaging, "Packaging score...");
ScoreSaberUploadData data = ScoreSaberUploadData.Create(beatmapLevel, beatmapKey, levelCompletionResults, _playerService.localPlayerInfo, GetVersionHash());
string scoreData = JsonConvert.SerializeObject(data);

// TODO: Simplify now that we're open source
byte[] encodedPassword = new UTF8Encoding().GetBytes($"{UPLOAD_SECRET}-{_playerService.localPlayerInfo.playerKey}-{_playerService.localPlayerInfo.playerId}-{UPLOAD_SECRET}");
byte[] keyHash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(encodedPassword);
string key = BitConverter.ToString(keyHash)
.Replace("-", string.Empty)
.ToLower();

string scoreDataHex = BitConverter.ToString(Swap(Encoding.UTF8.GetBytes(scoreData), Encoding.UTF8.GetBytes(key))).Replace("-", "");
Seven(data, scoreDataHex, beatmapLevel, beatmapKey, levelCompletionResults).RunTask();
} catch (Exception ex) {
UploadStatusChanged?.Invoke(UploadStatus.Error, "Failed to upload score, error written to log.");
Plugin.Log.Error($"Failed to upload score: {ex}");
}
try {
UploadStatusChanged?.Invoke(UploadStatus.Packaging, "Packaging score...");
ScoreSaberUploadData data = ScoreSaberUploadData.Create(beatmapLevel, beatmapKey, levelCompletionResults, _playerService.localPlayerInfo, GetVersionHash());
string scoreData = JsonConvert.SerializeObject(data);

// TODO: Simplify now that we're open source
byte[] encodedPassword = new UTF8Encoding().GetBytes($"{UPLOAD_SECRET}-{_playerService.localPlayerInfo.playerKey}-{_playerService.localPlayerInfo.playerId}-{UPLOAD_SECRET}");
byte[] keyHash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(encodedPassword);
string key = BitConverter.ToString(keyHash)
.Replace("-", string.Empty)
.ToLower();

string scoreDataHex = BitConverter.ToString(Swap(Encoding.UTF8.GetBytes(scoreData), Encoding.UTF8.GetBytes(key))).Replace("-", "");
Seven(data, scoreDataHex, beatmapLevel, beatmapKey, levelCompletionResults).RunTask();
} catch (Exception ex) {
UploadStatusChanged?.Invoke(UploadStatus.Error, "Failed to upload score, error written to log.");
Plugin.Log.Error($"Failed to upload score: {ex}");
}
}

Expand All @@ -165,7 +168,7 @@ public async Task Seven(ScoreSaberUploadData scoreSaberUploadData, string upload
UploadStatusChanged?.Invoke(UploadStatus.Packaging, "Checking leaderboard ranked status...");

bool ranked = true;
Leaderboard currentLeaderboard = await _leaderboardService.GetCurrentLeaderboard(beatmapKey);
Leaderboard currentLeaderboard = await _leaderboardService.GetCurrentLeaderboard(beatmapKey, beatmapLevel);

if (currentLeaderboard != null) {
ranked = currentLeaderboard.leaderboardInfo.ranked;
Expand Down Expand Up @@ -204,12 +207,12 @@ public async Task Seven(ScoreSaberUploadData scoreSaberUploadData, string upload
Plugin.Log.Info("Attempting score upload...");
UploadStatusChanged?.Invoke(UploadStatus.Uploading, "Uploading score...");
try {
response = await Plugin.HttpInstance.PostAsync("/game/upload", form);
} catch (HttpErrorException httpException) {
if (httpException.isScoreSaberError) {
Plugin.Log.Error($"Failed to upload score: {httpException.scoreSaberError.errorMessage}:{httpException}");
response = await _scoreSaberHttpClient.PostRawAsync(new UploadRequest().BuildUrl(), form); // this endpoint doesnt give back json, just a raw string, so we have to use PostRawAsync
} catch (HttpRequestException httpException) {
if (httpException.IsScoreSaberError) {
Plugin.Log.Error($"Failed to upload score: {httpException.ScoreSaberError.errorMessage}:{httpException}");
} else {
Plugin.Log.Error($"Failed to upload score: {httpException.isNetworkError}:{httpException.isHttpError}:{httpException}");
Plugin.Log.Error($"Failed to upload score: {httpException.IsNetworkError}:{httpException.IsHttpError}:{httpException}");
}
} catch (Exception ex) {
Plugin.Log.Error($"Failed to upload score: {ex.ToString()}");
Expand Down
43 changes: 41 additions & 2 deletions ScoreSaber/Core/Data/Internal/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ namespace ScoreSaber.Core.Data
{
internal class Settings
{
private static int _currentVersion => 8;
private static int _currentVersion => 9;

public bool hideReplayUI = false;

public int fileVersion { get; set; }
public bool disableScoreSaber { get; set; }
public bool disableScoreSaber { get; set; } // Unused
public bool showLocalPlayerRank { get; set; }
public bool showScorePP { get; set; }
public bool showStatusText { get; set; }
Expand All @@ -36,6 +36,13 @@ internal class Settings
public bool leftHandedReplayUI { get; set; }
public bool lockedReplayUIMode { get; set; }
public List<SpectatorPoseRoot> spectatorPositions { get; set; }
public Vec2 replayUIPosition { get; set; }
public float replayUISize { get; set; }
public bool startReplayUIHidden { get; set; }
public bool hideWatermarkIfUsersReplay { get; set; }
public bool enableRichPresence { get; set; }
public bool hasAcceptedRichPresenceDisclaimer { get; set; }
public bool showMainMenuButton { get; set; }

internal static string dataPath => "UserData";
internal static string configPath => dataPath + @"\ScoreSaber";
Expand Down Expand Up @@ -65,6 +72,13 @@ public void SetDefaults() {
hasOpenedReplayUI = false;
leftHandedReplayUI = false;
lockedReplayUIMode = false;
replayUIPosition = new Vec2(new Vector2(0.12f, 0.14f));
replayUISize = 1.25f;
startReplayUIHidden = false;
hideWatermarkIfUsersReplay = false;
enableRichPresence = false;
hasAcceptedRichPresenceDisclaimer = false;
showMainMenuButton = true;
SetDefaultSpectatorPositions();
}

Expand Down Expand Up @@ -113,6 +127,15 @@ internal static Settings LoadSettings() {
if(decoded.fileVersion < 8) {
decoded.replayCameraSmoothing = true;
}
if (decoded.fileVersion < 9) {
decoded.replayUIPosition = new Vec2(new Vector2(0.12f, 0.14f));
decoded.replayUISize = 1.25f;
decoded.startReplayUIHidden = false;
Comment thread
speecil marked this conversation as resolved.
decoded.enableRichPresence = false;
decoded.hasAcceptedRichPresenceDisclaimer = false;
decoded.hideWatermarkIfUsersReplay = false;
decoded.showMainMenuButton = true;
}
SaveSettings(decoded);
}
return decoded;
Expand All @@ -136,6 +159,22 @@ internal static void SaveSettings(Settings settings) {
}
}

internal struct Vec2 {
[JsonProperty("x")]
internal float x { get; set; }
[JsonProperty("y")]
internal float y { get; set; }

internal Vec2(Vector2 position) {
x = position.x;
y = position.y;
}

internal Vector2 ToVector2() {
return new Vector2(x, y);
}
}

internal struct SpectatorPoseRoot {
[JsonProperty("name")]
internal string name { get; set; }
Expand Down
17 changes: 17 additions & 0 deletions ScoreSaber/Core/Data/Models/GameMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ScoreSaber.Core.Data.Models {
public enum GameMode {
[JsonProperty("solo")]
solo,
[JsonProperty("multi")]
multiplayer,
[JsonProperty("practice")]
practice
}
}
15 changes: 2 additions & 13 deletions ScoreSaber/Core/Data/Models/LeaderboardUploadData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ internal static ScoreSaberUploadData Create(BeatmapLevel beatmapLevel, BeatmapKe
data.gameMode = $"Solo{beatmapKey.beatmapCharacteristic.serializedName}";
data.difficulty = BeatmapDifficultyMethods.DefaultRating(beatmapKey.difficulty);
data.infoHash = infoHash;
data.leaderboardId = levelInfo[2];
data.leaderboardId = BeatmapUtils.GetHashFromLevelID(beatmapKey, out bool isOst);
data.songName = beatmapLevel.songName;
data.songSubName = beatmapLevel.songSubName;
data.songAuthorName = beatmapLevel.songAuthorName;
data.levelAuthorName = friendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters);
data.levelAuthorName = BeatmapUtils.FriendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters) ?? "";
data.bpm = Convert.ToInt32(beatmapLevel.beatsPerMinute);

data.playerName = playerInfo.playerName;
Expand All @@ -83,16 +83,5 @@ internal static ScoreSaberUploadData Create(BeatmapLevel beatmapLevel, BeatmapKe
data.deviceControllerRightIdentifier = VRDevices.GetDeviceControllerRight();
return data;
}

static string friendlyLevelAuthorName(string[] mappers, string[] lighters) {
List<string> mappersAndLighters = new List<string>();
mappersAndLighters.AddRange(mappers);
mappersAndLighters.AddRange(lighters);

if(mappersAndLighters.Count <= 1) {
return mappersAndLighters.FirstOrDefault();
}
return $"{string.Join(", ", mappersAndLighters.Take(mappersAndLighters.Count - 1))} & {mappersAndLighters.Last()}";
}
}
}
Loading