The SDK zip is 24 MB of headers, examples and tools we never build. The runtime dependency is one native library, so scripts/steam-sdk.sh extracts exactly sdk/redistributable_bin/win64/steam_api64.dll into steam/ (gitignored, like ffmpeg/ — the script is the audit trail) and writes steam_appid.txt = 480, Valve's Spacewar app, so SteamAPI.Init() resolves an app id locally before LlamaCasty has one of its own. Two csproj details that would have cost a debugging session: - Link="steam_api64.dll" puts it BESIDE the exe; without Link, None items keep their relative path and it lands in steam/, where the loader does not look. - ExcludeFromSingleFile, for the same reason ffmpeg needed it: a native DLL otherwise gets swallowed into the single-file bundle and cannot be loaded at runtime. steam_appid.txt is Debug-only — Valve's guidance is not to ship it — and the publish output confirms it: dll present at the root, no appid file, exe still 127 MB (proof the DLL was not bundled). Also queued, as asked: preview undo (Ctrl+Z / Ctrl+Shift+Z) in edit mode, noted as the partner to the defaults-vs-current split — undo covers the slip, defaults cover the mess.
7.0 KiB
7.0 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
- 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 base app (free) and one DLC app (the lifetime Pro). Note both numbers:
the DLC app ID is what the app passes to
BIsDlcInstalled. - Depot. One depot holding the publish output; the base app and the DLC share the install directory, and the DLC package grants no files of its own — it is entitlement only.
- 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. 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.BIsDlcInstalled). - 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 from a clean Steam account, confirm
BIsDlcInstalledreturns false, buy the DLC (or use a test licence), confirm the high tiers unlock, then refund the test purchase.
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
IEntitlementhas two implementations: Steam DLC (lifetime, local) and Polar (monthly, network + 7–14 day lease). Resolution isPro = steamDlc || polarValid; anything unresolved is the free profile.- Never sell the monthly inside the Steam build. Steamworks requires in-app purchases to use the Steam Wallet; the app may state that monthly plans exist on the site, with no checkout, no price and no payment field. Confirm the wording with Steamworks support before shipping it.
- 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.- DLC ownership false → free profile; true → every tier 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 today, Polar the day licence validation ships. 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 free tier good enough that a crack buys nothing, Steam's own delivery, short Polar leases, and a licence ID in About. 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 install + DLC purchase/refund test | 0.5 day |