From c54231c09476cef6a9bdf69652360c92738fe1f0 Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:03:21 +0200 Subject: [PATCH 1/6] feat(store): add MSIX manifest template --- packaging/msix/AppxManifest.xml.in | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 packaging/msix/AppxManifest.xml.in diff --git a/packaging/msix/AppxManifest.xml.in b/packaging/msix/AppxManifest.xml.in new file mode 100644 index 0000000..60ebc06 --- /dev/null +++ b/packaging/msix/AppxManifest.xml.in @@ -0,0 +1,86 @@ + + + + + + PLAY. + Dev Jam Studio + Premium video player for Windows. + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + .3g2 + .3gp + .asf + .avi + .drc + .f4v + .flv + .m2ts + .m2v + .m4v + .mkv + .mov + .mp4 + .mpeg + .mpg + .mxf + .mts + .ogm + .ogv + .qt + .rm + .rmvb + .ts + .vob + .webm + .wmv + + + + + + + + + + + From b2c18045c6cdcba9e3ddbf968faaab44464abe03 Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:04:00 +0200 Subject: [PATCH 2/6] feat(store): add reproducible MSIX build script --- scripts/build-msix.ps1 | 208 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 scripts/build-msix.ps1 diff --git a/scripts/build-msix.ps1 b/scripts/build-msix.ps1 new file mode 100644 index 0000000..5ab6e3d --- /dev/null +++ b/scripts/build-msix.ps1 @@ -0,0 +1,208 @@ +[CmdletBinding()] +param( + [string]$Version, + [string]$OutputDirectory = (Join-Path $PSScriptRoot '..\artifacts\msix'), + [switch]$SkipBuild +) + +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +$projectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +Set-Location $projectRoot + +$identityName = 'DevJamStudio.622382E8743A9' +$publisher = 'CN=300C6CAC-B727-40B2-8FF0-4C00EBAF31D9' +$publisherDisplayName = 'Dev Jam Studio' + +$tauriConfigPath = Join-Path $projectRoot 'src-tauri\tauri.conf.json' +$tauriConfig = Get-Content -LiteralPath $tauriConfigPath -Raw | ConvertFrom-Json +$appVersion = [string]$tauriConfig.version + +if ([string]::IsNullOrWhiteSpace($Version)) { + $Version = $appVersion +} elseif ($Version -ne $appVersion) { + throw "Requested app version '$Version' does not match tauri.conf.json version '$appVersion'." +} + +if ($Version -notmatch '^(\d+)\.(\d+)\.(\d+)$') { + throw "Version '$Version' must contain exactly three numeric components, such as 0.16.0." +} + +$appMajor = [int]$Matches[1] +$appMinor = [int]$Matches[2] +$appPatch = [int]$Matches[3] +$packageMajor = $appMajor + 1 + +foreach ($component in @($packageMajor, $appMinor, $appPatch)) { + if ($component -lt 0 -or $component -gt 65535) { + throw "Version '$Version' cannot be mapped to a valid Microsoft Store package version." + } +} + +# The Store requires a four-part package version whose first component is non-zero +# and reserves the fourth component. Offset the semantic major by one so pre-1.0 +# PLAY. versions remain monotonic: 0.16.0 -> 1.16.0.0, 1.0.0 -> 2.0.0.0. +$packageVersion = "$packageMajor.$appMinor.$appPatch.0" + +if (-not $SkipBuild) { + & bun run setup-lib + if ($LASTEXITCODE -ne 0) { + throw "bun run setup-lib failed with exit code $LASTEXITCODE." + } + + & bun run tauri build --no-bundle + if ($LASTEXITCODE -ne 0) { + throw "Tauri build failed with exit code $LASTEXITCODE." + } +} + +$makeAppxCommand = Get-Command makeappx.exe -ErrorAction SilentlyContinue | Select-Object -First 1 +if ($makeAppxCommand) { + $makeAppx = $makeAppxCommand.Source +} else { + $makeAppx = Get-ChildItem ` + -Path (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin') ` + -Filter makeappx.exe ` + -Recurse ` + -File ` + -ErrorAction SilentlyContinue | + Where-Object { $_.Directory.Name -eq 'x64' } | + Sort-Object FullName | + Select-Object -Last 1 -ExpandProperty FullName +} + +if (-not $makeAppx) { + throw 'makeappx.exe was not found in the installed Windows SDK.' +} + +$binary = Join-Path $projectRoot 'src-tauri\target\release\playdot-player.exe' +$libDirectory = Join-Path $projectRoot 'src-tauri\lib' +$shaderDirectory = Join-Path $projectRoot 'shaders' +$icon = Join-Path $projectRoot 'src-tauri\icons\128x128@2x.png' +$manifestTemplate = Join-Path $projectRoot 'packaging\msix\AppxManifest.xml.in' + +foreach ($requiredFile in @($binary, $icon, $manifestTemplate)) { + if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) { + throw "Required MSIX input is missing: $requiredFile" + } +} +foreach ($requiredDirectory in @($libDirectory, $shaderDirectory)) { + if (-not (Test-Path -LiteralPath $requiredDirectory -PathType Container)) { + throw "Required MSIX input directory is missing: $requiredDirectory" + } +} + +$requiredRuntimeFiles = @( + (Join-Path $libDirectory 'libmpv-wrapper.dll'), + (Join-Path $libDirectory 'libmpv-2.dll'), + (Join-Path $shaderDirectory 'FSR.glsl'), + (Join-Path $shaderDirectory 'adaptive-luma-ultra.glsl') +) +foreach ($requiredRuntimeFile in $requiredRuntimeFiles) { + if (-not (Test-Path -LiteralPath $requiredRuntimeFile -PathType Leaf)) { + throw "Required packaged runtime file is missing: $requiredRuntimeFile" + } +} + +if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { + $outputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory) +} else { + $outputDirectory = [System.IO.Path]::GetFullPath((Join-Path $projectRoot $OutputDirectory)) +} +New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null +$packagePath = Join-Path $outputDirectory "PLAY_$Version`_x64.msix" + +$stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) "playdot-msix-$([guid]::NewGuid().ToString('N'))" +$verificationRoot = Join-Path ([System.IO.Path]::GetTempPath()) "playdot-msix-verify-$([guid]::NewGuid().ToString('N'))" +$stagingAssets = Join-Path $stagingRoot 'Assets' +$stagingLib = Join-Path $stagingRoot 'lib' +$stagingShaders = Join-Path $stagingRoot 'shaders' + +try { + New-Item -ItemType Directory -Path $stagingAssets, $stagingLib, $stagingShaders -Force | Out-Null + + Copy-Item -LiteralPath $binary -Destination (Join-Path $stagingRoot 'playdot-player.exe') -Force + Copy-Item -Path (Join-Path $libDirectory '*') -Destination $stagingLib -Recurse -Force + Copy-Item -Path (Join-Path $shaderDirectory '*') -Destination $stagingShaders -Recurse -Force + + Add-Type -AssemblyName System.Drawing + function Write-ScaledPng([string]$SourcePath, [string]$DestinationPath, [int]$Width, [int]$Height) { + $sourceImage = [System.Drawing.Image]::FromFile($SourcePath) + $bitmap = [System.Drawing.Bitmap]::new($Width, $Height) + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality + $graphics.DrawImage($sourceImage, 0, 0, $Width, $Height) + $bitmap.Save($DestinationPath, [System.Drawing.Imaging.ImageFormat]::Png) + } finally { + $graphics.Dispose() + $bitmap.Dispose() + $sourceImage.Dispose() + } + } + + Write-ScaledPng $icon (Join-Path $stagingAssets 'StoreLogo.png') 50 50 + Write-ScaledPng $icon (Join-Path $stagingAssets 'Square44x44Logo.png') 44 44 + Write-ScaledPng $icon (Join-Path $stagingAssets 'Square150x150Logo.png') 150 150 + + $manifest = Get-Content -LiteralPath $manifestTemplate -Raw + $manifest = $manifest.Replace('__PACKAGE_VERSION__', $packageVersion) + if ($manifest.Contains('__PACKAGE_VERSION__')) { + throw 'The MSIX manifest package version token was not replaced.' + } + + $manifestPath = Join-Path $stagingRoot 'AppxManifest.xml' + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($manifestPath, $manifest, $utf8NoBom) + + [xml]$manifestXml = $manifest + $identity = $manifestXml.Package.Identity + if ($identity.Name -ne $identityName -or $identity.Publisher -ne $publisher) { + throw 'The generated manifest identity does not match the reserved Microsoft Store identity.' + } + if ($manifestXml.Package.Properties.PublisherDisplayName -ne $publisherDisplayName) { + throw 'The generated manifest publisher display name does not match Partner Center.' + } + + if (Test-Path -LiteralPath $packagePath) { + Remove-Item -LiteralPath $packagePath -Force + } + + & $makeAppx pack /v /h SHA256 /d $stagingRoot /p $packagePath /o + if ($LASTEXITCODE -ne 0) { + throw "makeappx.exe failed with exit code $LASTEXITCODE." + } + + New-Item -ItemType Directory -Path $verificationRoot -Force | Out-Null + & $makeAppx unpack /p $packagePath /d $verificationRoot /o + if ($LASTEXITCODE -ne 0) { + throw "makeappx.exe could not unpack the generated package (exit code $LASTEXITCODE)." + } + + foreach ($packagedFile in @( + 'playdot-player.exe', + 'lib\libmpv-wrapper.dll', + 'lib\libmpv-2.dll', + 'shaders\FSR.glsl', + 'shaders\adaptive-luma-ultra.glsl', + 'AppxManifest.xml' + )) { + $verifiedPath = Join-Path $verificationRoot $packagedFile + if (-not (Test-Path -LiteralPath $verifiedPath -PathType Leaf)) { + throw "Generated MSIX is missing required file: $packagedFile" + } + } + + Write-Host "Unsigned Microsoft Store MSIX written to $packagePath" -ForegroundColor Green + Write-Host "PLAY. version: $Version" + Write-Host "Store package version: $packageVersion" + Write-Host "Identity: $identityName" + Write-Host "Publisher: $publisher" + Write-Host 'The Microsoft Store will sign the package after certification.' +} finally { + Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $verificationRoot -Recurse -Force -ErrorAction SilentlyContinue +} From 04f50594e3cd70bfa5464678203a7e53bb95d0ac Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:04:12 +0200 Subject: [PATCH 3/6] ci(store): build Microsoft Store MSIX --- .github/workflows/store-msix.yml | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/store-msix.yml diff --git a/.github/workflows/store-msix.yml b/.github/workflows/store-msix.yml new file mode 100644 index 0000000..c45fb8b --- /dev/null +++ b/.github/workflows/store-msix.yml @@ -0,0 +1,53 @@ +name: Microsoft Store MSIX + +on: + pull_request: + paths: + - ".github/workflows/store-msix.yml" + - "packaging/msix/**" + - "scripts/**" + - "src-tauri/**" + - "src/**" + - "shaders/**" + - "package.json" + - "bun.lock" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-store-msix: + runs-on: windows-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.10 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust build + uses: Swatinem/rust-cache@v2 + with: + workspaces: "src-tauri -> target" + + - name: Install JavaScript dependencies + shell: pwsh + run: bun install --frozen-lockfile + + - name: Build unsigned Store MSIX + shell: pwsh + run: ./scripts/build-msix.ps1 + + - name: Upload Store package + uses: actions/upload-artifact@v4 + with: + name: PLAY-Microsoft-Store-MSIX + path: artifacts/msix/*.msix + if-no-files-found: error + retention-days: 30 From 949fbe304dbb5fef364be6f087a8c1416f82bf7e Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:04:29 +0200 Subject: [PATCH 4/6] chore(store): expose MSIX release command --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 445bcd4..4128666 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "tauri:build": "tauri build", "setup-lib": "bun ./scripts/setup-lib.mjs && bun ./scripts/setup-third-party-notices.mjs", "release:win": "bun run setup-lib && tauri build --bundles nsis", + "release:store": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/build-msix.ps1", "release:artifacts": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/release-artifacts.ps1", "release": "bun run release:win && bun run release:artifacts" }, From 60f1a21a72cf884465e8c0ed89b83a9eac2156cf Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:04:48 +0200 Subject: [PATCH 5/6] docs(store): document MSIX release flow --- docs/releasing.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/releasing.md b/docs/releasing.md index 6c43757..38787c5 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -26,3 +26,27 @@ 8. Write curated release notes with Highlights, Fixes, Engineering, and Downloads sections as applicable. Release notes should explain user impact. Commit messages and generated diffs are supporting material, not substitutes for release notes. + +## Microsoft Store MSIX + +PLAY. has a separate MSIX packaging path for Microsoft Store distribution. It does not replace the NSIS/GitHub release flow. + +Partner Center identity used by the package: + +- Package/Identity/Name: `DevJamStudio.622382E8743A9` +- Package/Identity/Publisher: `CN=300C6CAC-B727-40B2-8FF0-4C00EBAF31D9` +- Package/Properties/PublisherDisplayName: `Dev Jam Studio` +- Store ID: `9NXXT91JCTHX` + +The Store package version is derived from PLAY.'s semantic version by adding one to the semantic major and reserving the fourth component for Store use. For example, PLAY. `0.16.0` produces package version `1.16.0.0`, and PLAY. `1.0.0` produces `2.0.0.0`. + +Build locally on Windows with the Windows SDK installed: + +```powershell +bun install --frozen-lockfile +bun run release:store +``` + +The unsigned package is written to `artifacts/msix/`. The build stages the Tauri executable, bundled libmpv runtime and licenses, shaders, generated Store logo assets, file associations, and the Partner Center identity into the MSIX, then unpacks the result to verify the required files are present. + +For CI, run the `Microsoft Store MSIX` workflow manually or use the artifact produced by its pull request validation run. Upload the resulting `.msix` to the Packages section of the Microsoft Store submission. The Store signs the package after certification; no private signing certificate is required for the Store submission path. From 7695313f6c3fc0a053f57741a0fc042edd770fa6 Mon Sep 17 00:00:00 2001 From: Stephan Orgiazzi Date: Tue, 25 Aug 2026 14:21:19 +0200 Subject: [PATCH 6/6] ci(store): build MSIX on master --- .github/workflows/store-msix.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/store-msix.yml b/.github/workflows/store-msix.yml index c45fb8b..132ae36 100644 --- a/.github/workflows/store-msix.yml +++ b/.github/workflows/store-msix.yml @@ -11,6 +11,18 @@ on: - "shaders/**" - "package.json" - "bun.lock" + push: + branches: + - master + paths: + - ".github/workflows/store-msix.yml" + - "packaging/msix/**" + - "scripts/**" + - "src-tauri/**" + - "src/**" + - "shaders/**" + - "package.json" + - "bun.lock" workflow_dispatch: permissions: