Creator design (2026-11-05). A stack of red rows at the top of the left panel, above both mode grids so it shows while editing and while live, pushing the panel's content down. - Row 1 is the **Steam licence indicator** and nothing displaces it. It reads as status, not a warning: "Entitlement 30 fps · Maximum 60 fps", both numbers derived from QualityOptions and HasProOutput. While higher tiers are unavailable it shows a **pin** (standing indicator, no close control); once ownership is verified the pin becomes a **close control** and the dismissal is persisted (`Steam.LicenceIndicatorDismissed`), permanent while ownership holds. Losing ownership restores it as pinned — a change of state, not a preference. Click → About. - **Event rows stack beneath, newest first**, one row per episode (an episode advancing its state updates its own row rather than adding another). Each is dismissible. Click → the Logs tab opens with that event's sequence range painted red (`HighlightedLogFrom/To` + `LogInRangeConverter`; two numbers rather than a collection because the log list is virtualised and realised rows do not re-evaluate on membership changes). - Replaces the old red panel under the quality picker, which is removed. - The Steam diagnostic text loses "Contact the creator or support" for a neutral "Steam licensing is not configured in this build." Tests: `AttentionAlertStackTests` — pinned first with the status text; owned → closable and the close survives a reload but ownership loss restores it pinned; pinned cannot be dismissed; event rows LIFO with in-place episode updates; clicking an event opens Logs and sets its range. AboutHub + Entitlement tests updated for the removed panel and the new wording. FAST gate: 532 passed, 0 failed, 0 warnings.
106 lines
3.1 KiB
C#
106 lines
3.1 KiB
C#
using Steamworks;
|
|
|
|
namespace ytLive.Services.Entitlement;
|
|
|
|
/// <summary>
|
|
/// Reads ownership of the paid app from Steam. In the free Demo, this checks the associated paid
|
|
/// app ID; no key, server, or LlamaCasty account is involved. A missing ID, Steam client, or native
|
|
/// library fails closed so the app can continue with the free output profile.
|
|
/// </summary>
|
|
public sealed class SteamEntitlement : IEntitlement
|
|
{
|
|
private readonly uint _paidAppId;
|
|
private readonly Func<bool> _initializeSteam;
|
|
private readonly Func<uint, bool> _isAppSubscribed;
|
|
private readonly Action _shutdownSteam;
|
|
|
|
private bool _initAttempted;
|
|
private bool _available;
|
|
private bool? _cached;
|
|
private string? _errorMessage;
|
|
|
|
public SteamEntitlement(uint paidAppId)
|
|
: this(
|
|
paidAppId,
|
|
SteamAPI.Init,
|
|
appId => SteamApps.BIsSubscribedApp(new AppId_t(appId)),
|
|
SteamAPI.Shutdown)
|
|
{
|
|
}
|
|
|
|
internal SteamEntitlement(
|
|
uint paidAppId,
|
|
Func<bool> initializeSteam,
|
|
Func<uint, bool> isAppSubscribed,
|
|
Action shutdownSteam)
|
|
{
|
|
_paidAppId = paidAppId;
|
|
_initializeSteam = initializeSteam;
|
|
_isAppSubscribed = isAppSubscribed;
|
|
_shutdownSteam = shutdownSteam;
|
|
}
|
|
|
|
/// <summary>Configured with no paid app ID; ownership fails closed without touching Steam.</summary>
|
|
public static SteamEntitlement NotConfigured() => new(0);
|
|
|
|
public string? ErrorMessage => _errorMessage;
|
|
|
|
public bool IsAppOwned
|
|
{
|
|
get
|
|
{
|
|
if (_paidAppId == 0)
|
|
{
|
|
_cached = false;
|
|
_errorMessage = "Steam licensing is not configured in this build.";
|
|
return false;
|
|
}
|
|
if (_cached is { } known) return known;
|
|
|
|
if (!_initAttempted)
|
|
{
|
|
_initAttempted = true;
|
|
try
|
|
{
|
|
_available = _initializeSteam();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_available = false;
|
|
_errorMessage = $"Steam could not start ({ex.Message}). Open Steam, sign in, then restart LlamaCasty.";
|
|
}
|
|
}
|
|
|
|
if (!_available)
|
|
{
|
|
_cached = false;
|
|
_errorMessage ??= "Steam could not verify ownership. Open Steam, sign in, then restart LlamaCasty.";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var owned = _isAppSubscribed(_paidAppId);
|
|
_cached = owned;
|
|
_errorMessage = null;
|
|
return owned;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_cached = false;
|
|
_errorMessage = $"Steam could not check ownership ({ex.Message}). Open Steam, sign in, then restart LlamaCasty.";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
if (_available) _shutdownSteam();
|
|
_available = false;
|
|
_cached = null;
|
|
_initAttempted = false;
|
|
_errorMessage = null;
|
|
}
|
|
}
|