Update Setlist for Beat Saber 1.44.1 startup scan
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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,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
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user