using Steamworks;
namespace ytLive.Services.Entitlement;
///
/// The lifetime SKU: a Steam **DLC** on the free app, read locally from the Steam client.
///
/// This is the whole purchase story. There is no key, no server and no lease — the
/// customer buys the DLC on the store page and Steam already knows. The app asks once and caches
/// the answer; a refunded DLC stops reporting ownership the next time the app starts.
///
/// ⛔ Never sell the monthly plan through this. Steamworks requires in-app purchases
/// to use the Steam Wallet, so anything sold from inside the app would be a policy violation. The
/// monthly lives on the website and is billed by Polar ( when it
/// lands); this class only ever reads what Valve already sold.
///
/// Steam absent is normal, not exceptional. A copied folder, a shortcut run without
/// the client, or a CI machine has no Steam — returns false and the
/// app runs as the free tier. It must never refuse to start over an entitlement lookup.
///
public sealed class SteamEntitlement : IEntitlement
{
/// The DLC's own app id, not the base app's. Zero = not configured yet, which reads
/// as "no entitlement" rather than "try anyway".
private readonly uint _dlcAppId;
private bool _initAttempted;
private bool _available;
private bool? _cached;
public SteamEntitlement(uint dlcAppId) => _dlcAppId = dlcAppId;
/// Configured with no product id — the state before the Steamworks app exists.
public static SteamEntitlement NotConfigured() => new(0);
public bool IsPro
{
get
{
if (_dlcAppId == 0) return false;
if (_cached is { } known) return known;
if (!_initAttempted)
{
_initAttempted = true;
try
{
// No steam_appid.txt and no client → false. That is the documented free path.
_available = SteamAPI.Init();
}
catch (Exception)
{
// A missing steam_api64.dll throws on first touch. Free tier, never a crash.
_available = false;
}
}
if (!_available) { _cached = false; return false; }
try
{
var owned = SteamApps.BIsDlcInstalled(new AppId_t(_dlcAppId));
_cached = owned;
return owned;
}
catch (Exception)
{
return false;
}
}
}
/// Matches Steam's own lifecycle callbacks; called from the frame pump's pump thread
/// would be wrong, so nothing here schedules it automatically.
public void Shutdown()
{
if (_available) SteamAPI.Shutdown();
_available = false;
_cached = null;
_initAttempted = false;
}
}