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; } }