Check Steam ownership of the paid app with BIsSubscribedApp; keep Debug fake ownership out of Release. Ref: https://partner.steamgames.com/doc/api/ISteamApps#BIsSubscribedApp
8.5 KiB
Distribution & release mechanics
How the build becomes an install on someone's PC. Policy — prices, entitlement, why Steam is the
only channel, support — is docs/ai/commercial.md. This file is the how.
Superseded twice on 2026-10-31. The 736-line Polar/MSIX/obfuscation plan is in
git log -- Distribution.md; the zip-plus-winget plan written earlier the same day is in the same place. The reasoning that survives lives incommercial.mdanddocs/ai/decisions.md.
1. The artifact
A self-contained win-x64 publish plus the bundled ffmpeg\ folder, uploaded to a Steam depot. No
installer, no admin rights, no zip for anyone to mirror.
dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:PublishReadyToRun=true -o artifacts/steam-stage
PublishTrimmedstays OFF. Trimming breaks WPF's XAML reflection (Binding,DataTemplate,StaticResource). Expect ~110–150 MB in the depot; that is the cost of "no .NET install".- ffmpeg stays outside the exe —
ffmpeg\beside it (the app's locator contract, seeoutput.md→ "FFmpeg locator"). steam_api64.dllships beside the exe and is loaded at startup; without it the app cannot read DLC ownership. Its absence must degrade to the free profile, never to a crash.- WebView2 is the one machine dependency. Valve's Common Redistributables configure the Evergreen bootstrapper as a prerequisite, so the depot declares it instead of us shipping it.
AssemblyNameisLlamaCasty(renamed 2026-11-01, before the first depot because Steamworks warns about renaming a live exe) → the artifact isLlamaCasty.exeplus itsffmpeg\folder.scripts/publish.shstages both and refuses to finish if either is missing.- ⛔
ExcludeFromSingleFile="true"on the ffmpeg content. Without it,PublishSingleFile+IncludeNativeLibrariesForSelfExtractswallow the libav*.dll family into the exe — a 182 MB binary with noffmpeg\folder beside it, so the locator finds nothing and every stream fails at runtime. The publish output is ~257 MB: 127 MB exe + 129 MB ffmpeg. - Portable mode (
portable.txt→.\data\) survives as a feature for a copied install; it is not a delivery vector.
2. Steamworks setup, once
The SDK zip is the only download. It is ~24 MB and contains the runtime
(sdk/redistributable_bin/win64/steam_api64.dll) and the release tooling
(sdk/tools/ContentBuilder/ → steamcmd.exe for Windows, steamcmd.sh for Linux/macOS, plus the
app_build_*.vdf / depot_build_*.vdf templates). There is no separate "ContentBuilder download"
on the partner dashboard — the dashboard's Downloads page only re-offers the SDK.
Reference links, so nobody hunts again:
| What | Where |
|---|---|
| SDK zip (sign in to partner first) | partner.steamgames.com/downloads/steamworks_sdk.zip |
| Docs home | partner.steamgames.com/doc/home |
| SteamPipe upload (steamcmd + VDF fields) | partner.steamgames.com/doc/sdk/uploading |
| Demo configuration | partner.steamgames.com/doc/store/application/demos |
Paid app ownership (BIsSubscribedApp) |
partner.steamgames.com/doc/api/ISteamApps#BIsSubscribedApp |
| Store artwork sizes | partner.steamgames.com/doc/store/assets/standard |
| Coming Soon / 2-week public window | partner.steamgames.com/doc/store/coming_soon |
| Wallet rule (in-app purchases) | partner.steamgames.com/doc/features/microtransactions |
| Recurring subscriptions | partner.steamgames.com/doc/store/pricing/subscriptions |
| Partner support | help.steampowered.com/en/wizard/HelpWithPublishing |
| Steamworks.NET (managed wrapper) | nuget.org/packages/Steamworks.NET |
- Steamworks account → onboarding (NDA, identity, company details, tax interview, payout bank, then the $100 Steam Direct fee per app). The store page cannot be configured before the fee.
- App IDs. One paid app and its associated free Demo. The Demo has its own App ID; it checks
ownership of the paid app with
BIsSubscribedApp(paidAppId). - Builds. Keep one codebase. Steam gives the Demo a separate release checklist/build setup; shared depots can avoid duplicating common files once the paid app is released.
- Steamworks SDK.
./scripts/steam-sdk.sh ~/Downloads/steamworks_sdk_165.zipextractssteam_api64.dllfromsdk/redistributable_bin/win64/intosteam/(gitignored) and stagessteam_appid.txt= 480 — Valve's Spacewar app, soSteamAPI.Init()works locally before LlamaCasty has an App ID. Add--contentbuilderon the machine that uploads a depot. The csproj copies the DLL beside the exe (Link) withExcludeFromSingleFile, and copiessteam_appid.txtin Debug only — never into a depot. The managed side is theSteamworks.NETpackage (SteamApps.BIsSubscribedApp). - Store page configured and made public two weeks before release — Valve's rule, and it is the window where wishlists accumulate.
3. Release
publish → steam-stage → steamcmd + app_build.vdf → build upload → set live → announcement
- Upload the depot with
steamcmd +login <user> +run_app_build ..\scripts\app_build_<appid>.vdf +quit. The VDF names the app, depot, content root and branch. - Set the build live in Steamworks → Builds → the new build → Set Build Live on the default branch. That is the release; there is no artifact to hash and no manifest to submit.
- Announce with Steam Events (release notes appear in the library and on the store page).
- Verify: install the Demo from a clean Steam account, confirm paid-app ownership is false, then test with an account that owns the paid app and confirm the higher tiers unlock.
Updates are the same upload with a higher build ID. The app keeps no self-updater and no version banner — Steam is the update path, and that policy has not changed.
4. Entitlement wiring
IEntitlementreportsIsAppOwned. Debug uses a fake owned result; Release checks the paid app ID throughBIsSubscribedApp.MainViewModel.IsAppOwnedis the app-wide binary ownership flag.- The paid app ID is not yet configured. Until it is assigned, the Release path fails closed without touching the native library.
- Fail closed on every path: no id, no client, missing DLL, thrown load, not owned.
- The EULA should expressly allow commercial/monetized streaming use; confirm with counsel or Valve that this satisfies the Steam Subscriber Agreement's software terms.
- Never gate a feature. The only delta is the output profile.
5. Release checklist
- Build clean, 0 warnings (
./scripts/verify.sh), tests green. steam_api64.dllpresent; app starts without Steam and falls back to the free profile.- Paid-app ownership false → free profile; true → higher output tiers offered.
LlamaCasty.exerename landed (never rename a live exe).- Clean Steam install on a second machine, then an update pushed and observed.
- Store page public for the full two weeks; support contact and announcements configured.
- Portable mode:
portable.txtpresent → data lands in.\data\.
6. Legal and takedown
LICENSE(EULA) forbids redistribution, resale, reverse-engineering and circumvention, and disclaims warranty.PRIVACY.mdmust name any third party the app contacts — Google/YouTube. Steam itself is named by Valve's own policy, not ours.THIRD-PARTY-NOTICES.txtstays shipped and shown in About; the ffmpeg pin stayslgpl-shared; no GPL component may be linked (seeproduct.md→ "Licensing").- DMCA: a takedown needs only the notice — host, link, good-faith statement, signature. Statutory damages need a registration ($65). Worth doing once there is something to pirate.
- What actually protects the product is in
commercial.md→ "Anti-piracy posture": a useful free tier, Steam's own delivery, and Steam ownership of the paid app. Not obfuscation, not a native DLL, not hardware locking.
7. Effort, once
| Step | Effort |
|---|---|
| Steamworks onboarding + $100 fee | 0.5–1 day, mostly waiting on verification |
Publish profile + steam_api64.dll + IEntitlement Steam path |
1 day |
Depot + first steamcmd upload |
0.5 day, then ~15 min per release |
| Store page + assets (screenshots, capsule art) | 1–2 days |
| Clean-account Demo install + paid-app ownership test | 0.5 day |