# 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`](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 in `commercial.md` and `docs/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. ```bash 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 ``` - **`PublishTrimmed` stays 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, see [`output.md`](output.md) → "FFmpeg locator"). - **`steam_api64.dll` ships 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. - **`AssemblyName` is `LlamaCasty`** (renamed 2026-11-01, before the first depot because Steamworks warns about renaming a live exe) → the artifact is `LlamaCasty.exe` plus its `ffmpeg\` folder. `scripts/publish.sh` stages both and refuses to finish if either is missing. - **⛔ `ExcludeFromSingleFile="true"` on the ffmpeg content.** Without it, `PublishSingleFile` + `IncludeNativeLibrariesForSelfExtract` swallow the libav\*.dll family **into the exe** — a 182 MB binary with no `ffmpeg\` 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` | 1. **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. 2. **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)`. 3. **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. 4. **Steamworks SDK.** `./scripts/steam-sdk.sh ~/Downloads/steamworks_sdk_165.zip` extracts `steam_api64.dll` from `sdk/redistributable_bin/win64/` into `steam/` (gitignored) and stages `steam_appid.txt` = 480 — Valve's Spacewar app, so `SteamAPI.Init()` works locally before LlamaCasty has an App ID. Add `--contentbuilder` on the machine that uploads a depot. The csproj copies the DLL **beside the exe** (`Link`) with `ExcludeFromSingleFile`, and copies `steam_appid.txt` in Debug only — never into a depot. The managed side is the `Steamworks.NET` package (`SteamApps.BIsSubscribedApp`). 5. **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 ``` 1. **Upload the depot** with `steamcmd +login +run_app_build ..\scripts\app_build_.vdf +quit`. The VDF names the app, depot, content root and branch. 2. **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. 3. **Announce** with Steam Events (release notes appear in the library and on the store page). 4. **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 - `IEntitlement` reports `IsAppOwned`. Debug uses a fake owned result; Release checks the paid app ID through `BIsSubscribedApp`. `MainViewModel.IsAppOwned` is 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.dll` present; app starts **without** Steam and falls back to the free profile. - Paid-app ownership false → free profile; true → higher output tiers offered. - `LlamaCasty.exe` rename 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.txt` present → data lands in `.\data\`. ## 6. Legal and takedown - **`LICENSE`** (EULA) forbids redistribution, resale, reverse-engineering and circumvention, and disclaims warranty. - **`PRIVACY.md`** must name any third party the app contacts — Google/YouTube. Steam itself is named by Valve's own policy, not ours. - **`THIRD-PARTY-NOTICES.txt`** stays shipped and shown in About; the ffmpeg pin stays `lgpl-shared`; no GPL component may be linked (see `product.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 |