Update Setlist for Beat Saber 1.44.1 startup scan

This commit is contained in:
pleb
2026-07-19 20:25:22 -07:00
parent 14d21ad6a3
commit a3b8169fd6
7 changed files with 79 additions and 87 deletions
+2 -2
View File
@@ -26,8 +26,8 @@ device that consumes them, without alt-tabbing or running a sync script.
## Requirements ## Requirements
- Beat Saber `1.40.8` (PC), via BSIPA. - Beat Saber `1.44.1` (PC), via BSIPA.
- [BeatLeader](https://github.com/BeatLeader/beatleader-mod) `^0.9.0` — must be - [BeatLeader](https://github.com/BeatLeader/beatleader-mod) `>=0.9.0` — must be
installed **and signed in** for the sync to authenticate. installed **and signed in** for the sync to authenticate.
- [PlaylistManager](https://github.com/rithik-b/PlaylistManager) `^1.7.0`. - [PlaylistManager](https://github.com/rithik-b/PlaylistManager) `^1.7.0`.
- [BeatSaberPlaylistsLib](https://github.com/Zingabopp/BeatSaberPlaylistsLib) - [BeatSaberPlaylistsLib](https://github.com/Zingabopp/BeatSaberPlaylistsLib)
+24 -54
View File
@@ -3,8 +3,6 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BeatSaberPlaylistsLib.Types; using BeatSaberPlaylistsLib.Types;
using UnityEngine; using UnityEngine;
using IPALogger = IPA.Logging.Logger; using IPALogger = IPA.Logging.Logger;
@@ -20,7 +18,6 @@ namespace Setlist
private const float PlatformUserPollStepSeconds = 0.5f; private const float PlatformUserPollStepSeconds = 0.5f;
/// <summary>How long to wait for <see cref="PlatformLeaderboardsModel"/> to appear and populate <c>playerId</c> (plugin runs before menu init).</summary> /// <summary>How long to wait for <see cref="PlatformLeaderboardsModel"/> to appear and populate <c>playerId</c> (plugin runs before menu init).</summary>
private const float PlatformUserWaitTimeoutSeconds = 30f; private const float PlatformUserWaitTimeoutSeconds = 30f;
private const float PlatformUserGetUserInfoRetrySeconds = 3f;
internal static bool TryExtractBeatLeaderPlaylistGuid(string syncUrl, out string guid) internal static bool TryExtractBeatLeaderPlaylistGuid(string syncUrl, out string guid)
{ {
@@ -111,55 +108,18 @@ namespace Setlist
internal static IEnumerator FetchPlatformUserIdCoroutine(Action<string> onDone) internal static IEnumerator FetchPlatformUserIdCoroutine(Action<string> onDone)
{ {
var waited = 0f; var waited = 0f;
var lastGetUserInfoAttempt = -PlatformUserGetUserInfoRetrySeconds;
while (waited < PlatformUserWaitTimeoutSeconds) while (waited < PlatformUserWaitTimeoutSeconds)
{ {
foreach (var plm in EnumerateLeaderboardModels()) foreach (var plm in EnumerateLeaderboardModels())
{ {
var pid = plm.playerId; var pid = ReadPlatformUserId(plm);
if (!string.IsNullOrEmpty(pid)) if (!string.IsNullOrEmpty(pid))
{ {
onDone(pid.Trim()); onDone(pid);
yield break; yield break;
} }
} }
if (waited - lastGetUserInfoAttempt >= PlatformUserGetUserInfoRetrySeconds)
{
lastGetUserInfoAttempt = waited;
var model = ResolvePlatformUserModel();
if (model != null)
{
Task<UserInfo> task;
try
{
task = model.GetUserInfo(CancellationToken.None);
}
catch (Exception)
{
task = null;
}
if (task != null)
{
while (!task.IsCompleted)
{
yield return null;
}
if (!task.IsFaulted)
{
var uid = task.Result?.platformUserId;
if (!string.IsNullOrEmpty(uid))
{
onDone(uid.Trim());
yield break;
}
}
}
}
}
waited += PlatformUserPollStepSeconds; waited += PlatformUserPollStepSeconds;
yield return new WaitForSeconds(PlatformUserPollStepSeconds); yield return new WaitForSeconds(PlatformUserPollStepSeconds);
} }
@@ -188,26 +148,36 @@ namespace Setlist
} }
} }
private static IPlatformUserModel ResolvePlatformUserModel() private static string ReadPlatformUserId(PlatformLeaderboardsModel model)
{ {
var field = typeof(PlatformLeaderboardsModel).GetField( if (model == null)
"_platformUserModel",
BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{ {
return null; return null;
} }
IPlatformUserModel last = null; var id = NormalizeUserId(model.playerId);
foreach (var plm in EnumerateLeaderboardModels()) if (!string.IsNullOrEmpty(id))
{ {
if (field.GetValue(plm) is IPlatformUserModel m) return id;
{
last = m;
}
} }
return last; var platformField = typeof(PlatformLeaderboardsModel).GetField(
"_platform",
BindingFlags.Instance | BindingFlags.NonPublic);
var platform = platformField?.GetValue(model);
var user = platform?.GetType().GetProperty("user")?.GetValue(platform, null);
return NormalizeUserId(user?.GetType().GetProperty("userId")?.GetValue(user, null));
}
private static string NormalizeUserId(object raw)
{
if (raw == null)
{
return null;
}
var value = raw.ToString()?.Trim();
return string.IsNullOrEmpty(value) || value == "0" ? null : value;
} }
/// <summary> /// <summary>
+39 -22
View File
@@ -1,8 +1,10 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using BeatSaberPlaylistsLib.Types; using BeatSaberPlaylistsLib.Types;
using IPA; using IPA;
using PlaylistManager.Utilities; using PlaylistManager.Utilities;
using UnityEngine;
using IPALogger = IPA.Logging.Logger; using IPALogger = IPA.Logging.Logger;
namespace Setlist namespace Setlist
@@ -20,6 +22,9 @@ namespace Setlist
/// <summary>Set after the ownership scan resolves the platform user id; used when syncing after PlaylistManager adds a song.</summary> /// <summary>Set after the ownership scan resolves the platform user id; used when syncing after PlaylistManager adds a song.</summary>
internal static string CachedPlatformUserId { get; set; } internal static string CachedPlatformUserId { get; set; }
private const float PlaylistScanPollStepSeconds = 0.5f;
private const float PlaylistScanWaitTimeoutSeconds = 10f;
[Init] [Init]
public Plugin(IPALogger logger) public Plugin(IPALogger logger)
{ {
@@ -36,43 +41,55 @@ namespace Setlist
Events.playlistSongAdded += OnPlaylistSongAdded; Events.playlistSongAdded += OnPlaylistSongAdded;
SetlistSyncHost.Instance.StartPlaylistOwnershipScan(Log);
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
}
internal static IEnumerator CoScanAndLogPlaylistOwnership(IPALogger log)
{
var waited = 0f;
while (waited <= PlaylistScanWaitTimeoutSeconds)
{
var playlists = BeatSaberPlaylistsLib.PlaylistManager.DefaultManager.GetAllPlaylists( var playlists = BeatSaberPlaylistsLib.PlaylistManager.DefaultManager.GetAllPlaylists(
includeChildren: true, includeChildren: true,
out AggregateException loadErrors); out AggregateException loadErrors);
if (loadErrors != null) if (loadErrors != null)
{ {
Log.Error(loadErrors.Message); log.Error(loadErrors.Message);
foreach (var inner in loadErrors.InnerExceptions) foreach (var inner in loadErrors.InnerExceptions)
{ {
Log.Error(inner.ToString()); log.Error(inner.ToString());
} }
} }
if (playlists == null || playlists.Length == 0) if (playlists != null && playlists.Length > 0)
{ {
Log.Info("No playlists loaded (or playlist library not initialized yet)."); var entries = new List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>();
return; foreach (var playlist in playlists)
{
BeatLeaderPlaylistOwnership.TryReadBeatLeaderMetadata(
playlist,
out var hasSyncUrl,
out var blGuid,
out var ownerId);
entries.Add((playlist, playlist.Title, hasSyncUrl, blGuid, ownerId));
}
BeatLeaderPlaylistOwnership.ScheduleVerifyAndLog(entries, log);
yield break;
} }
var entries = new List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>(); waited += PlaylistScanPollStepSeconds;
foreach (var playlist in playlists) yield return new WaitForSeconds(PlaylistScanPollStepSeconds);
{
BeatLeaderPlaylistOwnership.TryReadBeatLeaderMetadata(
playlist,
out var hasSyncUrl,
out var blGuid,
out var ownerId);
entries.Add((playlist, playlist.Title, hasSyncUrl, blGuid, ownerId));
}
BeatLeaderPlaylistOwnership.ScheduleVerifyAndLog(entries, Log);
}
catch (Exception ex)
{
Log.Error(ex.ToString());
} }
log.Info("No playlists loaded after startup wait.");
} }
[OnExit] [OnExit]
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")] [assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
[assembly: AssemblyVersion("0.1.0.0")] [assembly: AssemblyVersion("0.1.1.0")]
[assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyFileVersion("0.1.1.0")]
+5
View File
@@ -28,5 +28,10 @@ namespace Setlist
{ {
StartCoroutine(BeatLeaderPlaylistSync.CoPostOwnedPlaylistToBeatLeader(playlist, log)); StartCoroutine(BeatLeaderPlaylistSync.CoPostOwnedPlaylistToBeatLeader(playlist, log));
} }
internal void StartPlaylistOwnershipScan(IPALogger log)
{
StartCoroutine(Plugin.CoScanAndLogPlaylistOwnership(log));
}
} }
} }
+3 -3
View File
@@ -3,13 +3,13 @@
"id": "Setlist", "id": "Setlist",
"name": "Setlist", "name": "Setlist",
"author": "", "author": "",
"version": "0.1.0", "version": "0.1.1",
"description": "Syncs playlists with external sources.", "description": "Syncs playlists with external sources.",
"gameVersion": "1.40.8", "gameVersion": "1.44.1",
"dependsOn": { "dependsOn": {
"BSIPA": "^4.3.0", "BSIPA": "^4.3.0",
"BeatSaberPlaylistsLib": "^1.7.0", "BeatSaberPlaylistsLib": "^1.7.0",
"BeatLeader": "^0.9.0", "BeatLeader": ">=0.9.0",
"PlaylistManager": "^1.7.0" "PlaylistManager": "^1.7.0"
} }
} }
+4 -4
View File
@@ -3,13 +3,13 @@
Gamedir Gamedir
```sh ```sh
cd ~/.local/share/BSManager/BSInstances/1.40.8 cd ~/.local/share/BSManager/BSInstances/1.44.1
``` ```
## Logs ## Logs
```sh ```sh
tail -f Logs/_latest.log tail -F Logs/_latest.log
``` ```
## FPFC Mode ## FPFC Mode
@@ -23,7 +23,7 @@ Mouse input is broken because Wayland is not yet supported.
To run beat saber, you can mimic the launch execution that bs-manager performs with the following: To run beat saber, you can mimic the launch execution that bs-manager performs with the following:
```sh ```sh
cd "/home/pleb/.local/share/BSManager/BSInstances/1.40.8" cd "/home/pleb/.local/share/BSManager/BSInstances/1.44.1"
export SteamAppId=620980 SteamOverlayGameId=620980 SteamGameId=620980 export SteamAppId=620980 SteamOverlayGameId=620980 SteamGameId=620980
export WINEDLLOVERRIDES='winhttp=n,b' export WINEDLLOVERRIDES='winhttp=n,b'
@@ -40,7 +40,7 @@ steam-run "/home/pleb/.local/share/Steam/steamapps/common/Proton - Experimental/
Then, after a few seconds you can read the log messages after BS starts up Then, after a few seconds you can read the log messages after BS starts up
``` ```
cat ~/.local/share/BSManager/BSInstances/1.40.8/Logs/_latest.log | grep Setlist cat ~/.local/share/BSManager/BSInstances/1.44.1/Logs/_latest.log | grep Setlist
``` ```
Thereafter, kill the process. Thereafter, kill the process.