From 4f308e210ab3991e19773253c160f0d876207625 Mon Sep 17 00:00:00 2001
From: FaulMit <48646918+FaulMit@users.noreply.github.com>
Date: Mon, 3 Aug 2026 12:55:22 +0200
Subject: [PATCH] Add Microsoft Store packaging and privacy policy
---
PRIVACY.md | 117 ++++++++++++++
README.md | 2 +
docs/RELEASING.md | 15 ++
packaging/msix/AppxManifest.xml.template | 61 +++++++
packaging/msix/README.md | 64 ++++++++
src/Captail/App.xaml.cs | 16 +-
src/Captail/AppDistribution.cs | 47 ++++++
src/Captail/Autostart.cs | 48 +++++-
src/Captail/Captail.csproj | 1 +
src/Captail/Languages/Strings.en.xaml | 3 +
src/Captail/Languages/Strings.ru.xaml | 3 +
src/Captail/SettingsWindow.xaml | 1 +
src/Captail/SettingsWindow.xaml.cs | 39 ++++-
src/Captail/UpdateService.cs | 16 ++
tools/BuildStorePackage.ps1 | 194 +++++++++++++++++++++++
15 files changed, 616 insertions(+), 11 deletions(-)
create mode 100644 PRIVACY.md
create mode 100644 packaging/msix/AppxManifest.xml.template
create mode 100644 packaging/msix/README.md
create mode 100644 src/Captail/AppDistribution.cs
create mode 100644 tools/BuildStorePackage.ps1
diff --git a/PRIVACY.md b/PRIVACY.md
new file mode 100644
index 0000000..6db4b8b
--- /dev/null
+++ b/PRIVACY.md
@@ -0,0 +1,117 @@
+# Captail Privacy Policy
+
+Effective date: August 3, 2026
+
+This policy applies to the Captail desktop application and its official
+Microsoft Store and GitHub distributions.
+
+## Summary
+
+Captail records instant replays locally on your Windows PC. Captail does not
+sell personal information, include advertising or analytics, automatically
+upload recordings, or provide the developer with access to your captures.
+
+## Information processed locally
+
+Depending on the features you enable, Captail may process:
+
+- desktop, application, or game video frames;
+- desktop or game audio;
+- microphone audio;
+- monitor, audio-device, GPU, codec, and game-process information needed to
+ configure capture;
+- replay files and their technical metadata;
+- settings such as hotkeys, selected devices, and output folders.
+
+This information is processed on your device to maintain the replay buffer,
+save clips, organize recordings, show previews, and trim saved videos. Captail
+does not transmit captured video or audio to the developer or to a Captail
+cloud service.
+
+Captail does not attempt to bypass DRM or protected-media restrictions.
+Protected content may appear black or unavailable in recordings.
+
+## Local storage
+
+Saved replays are written to the folder you select. The rolling replay buffer
+is held locally and only becomes a normal video file when you save a replay.
+
+Captail stores configuration and diagnostic logs under its local application
+data. The logical log location is:
+
+```text
+%LOCALAPPDATA%\Captail\log.txt
+```
+
+Windows may map Store-package application data to a package-specific location.
+Logs can contain technical errors, device and GPU names, codec settings, game
+or process names, and local file paths. Logs do not intentionally contain raw
+video frames or recorded audio. Logs are not uploaded automatically. Review
+them and remove sensitive paths or names before attaching them to a public bug
+report.
+
+## Network access
+
+Captail has no telemetry, analytics, advertising, account, or cloud-storage
+service.
+
+The Microsoft Store build relies on Microsoft Store for application updates
+and does not check GitHub Releases for updates. Microsoft Store may process
+installation, licensing, update, and diagnostic information under Microsoft's
+own privacy terms.
+
+Installer and Portable builds distributed through GitHub may contact the
+GitHub Releases API to check for and download updates. Those requests identify
+the Captail version and necessarily expose network information such as the IP
+address to GitHub. GitHub processes that information under the
+[GitHub Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).
+
+Opening a GitHub, support, or repository link from Captail launches your
+default web browser. The destination website then applies its own privacy
+policy.
+
+## Permissions
+
+Captail uses only permissions needed for its recording workflow, including:
+
+- screen and game capture;
+- system-audio and optional microphone capture;
+- global replay hotkeys;
+- access to folders selected for replay storage;
+- hardware encoder detection;
+- optional startup with Windows.
+
+Microphone recording and startup with Windows can be disabled in Captail's
+settings. Capture can be stopped at any time by turning Instant Replay off or
+exiting Captail.
+
+## Sharing and disclosure
+
+Captail does not sell or share local recordings, settings, or diagnostic logs.
+Information leaves your device only when you choose to share a replay or log,
+or when a non-Store build performs the GitHub update request described above.
+
+## Retention and deletion
+
+You control saved replay retention. Replays can be deleted from Captail or
+Windows File Explorer. Configuration and logs remain locally until you delete
+them or Windows removes the application's data. Uninstalling Captail does not
+guarantee removal of replay files stored in your chosen output folder.
+
+## Children's privacy
+
+Captail is a general-purpose recording utility and is not directed to children.
+The developer does not knowingly collect personal information from children.
+
+## Changes to this policy
+
+Material changes will be published in this file and recorded in the
+repository's Git history. The effective date above will be updated when the
+policy changes.
+
+## Contact
+
+For privacy questions, open an issue in the
+[Captail repository](https://github.com/FaulMit/captail/issues). Do not include
+recordings, logs, local paths, or other sensitive information in a public
+issue.
diff --git a/README.md b/README.md
index 00064e4..6454e56 100644
--- a/README.md
+++ b/README.md
@@ -266,6 +266,8 @@ Read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting changes. Release maint
Captail is licensed under [GNU GPL-2.0-or-later](LICENSE).
+Privacy details are documented in [PRIVACY.md](PRIVACY.md).
+
Captail uses libobs and selected OBS Studio runtime components. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
Captail is not affiliated with or endorsed by NVIDIA or OBS Project. NVIDIA and ShadowPlay are trademarks of NVIDIA Corporation.
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index 3d7b561..1361a4e 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -76,3 +76,18 @@ Output is written to `artifacts\release\0.1.1`.
- Major: breaking change (`0.x` → `1.0.0`)
Never replace published binaries under an existing tag. Publish a new version.
+
+## Microsoft Store release
+
+Microsoft Store is a separate package channel. It must not be attached to a
+GitHub Release because Store installs, signs, and updates the package.
+
+Build an upload-ready package locally:
+
+```powershell
+.\tools\BuildStorePackage.ps1 -Version 0.1.5
+```
+
+Upload `artifacts\store\0.1.5\Captail-0.1.5.0-x64.msixupload` in Partner
+Center. Full identity, validation, update-channel, and submission instructions
+are in [`packaging/msix/README.md`](../packaging/msix/README.md).
diff --git a/packaging/msix/AppxManifest.xml.template b/packaging/msix/AppxManifest.xml.template
new file mode 100644
index 0000000..386dfec
--- /dev/null
+++ b/packaging/msix/AppxManifest.xml.template
@@ -0,0 +1,61 @@
+
+
+
+
+ Captail
+ faulmit
+ Lightweight instant replay recorder for Windows.
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packaging/msix/README.md b/packaging/msix/README.md
new file mode 100644
index 0000000..d7b80b1
--- /dev/null
+++ b/packaging/msix/README.md
@@ -0,0 +1,64 @@
+# Microsoft Store package
+
+This package channel is separate from Captail's GitHub Installer and Portable
+releases. Microsoft Store installs and updates this build. Captail's GitHub
+self-updater is disabled at compile time and guarded again at runtime whenever
+Windows reports package identity.
+
+## Partner Center identity
+
+- Package name: `faulmit.Captail`
+- Publisher: `CN=1BD9448E-C83F-401D-B530-ED5258C6319A`
+- Publisher display name: `faulmit`
+- Package family name: `faulmit.Captail_ryepxmzt2jqew`
+- Store product ID: `9PKVNVLKPTPS`
+
+Do not change package name or publisher. Partner Center rejects packages whose
+manifest identity does not match the reserved product.
+
+## Build
+
+From repository root:
+
+```powershell
+.\tools\BuildStorePackage.ps1 -Version 0.1.5
+```
+
+Output:
+
+```text
+artifacts\store\0.1.5\Captail-0.1.5.0-x64.msix
+artifacts\store\0.1.5\Captail-0.1.5.0-x64.msixupload
+artifacts\store\0.1.5\SHA256SUMS.txt
+```
+
+Upload `.msixupload` on Partner Center's **Packages** page. Do not publish this
+artifact as a GitHub Release. Microsoft Store signs the accepted package for
+distribution.
+
+Generated package is intentionally unsigned. It is ready for Partner Center,
+but Windows will not allow direct sideloading until a trusted test certificate
+is applied. Store customers receive Microsoft's signed package after
+certification.
+
+## Versioning
+
+MSIX requires four numeric components. Captail `0.1.5` becomes package version
+`0.1.5.0`. Every Store submission must use a strictly higher package version.
+Never reuse a version already submitted to Partner Center.
+
+## Store-specific behavior
+
+- Updates are delivered only by Microsoft Store.
+- Footer shows `Microsoft Store` instead of an update action.
+- Startup uses the package `windows.startupTask` extension.
+- Installer and Portable builds continue using GitHub Releases and HKCU Run.
+
+## Before submission
+
+1. Build package from clean `main` commit.
+2. Check `SHA256SUMS.txt` and inspect generated manifest identity.
+3. Test install, first launch, tray, capture, replay save, microphone permission,
+ startup toggle, uninstall, and Store update from previous package version.
+4. Complete Partner Center privacy, age rating, properties, screenshots, and
+ certification notes.
diff --git a/src/Captail/App.xaml.cs b/src/Captail/App.xaml.cs
index 3667124..e2d18ca 100644
--- a/src/Captail/App.xaml.cs
+++ b/src/Captail/App.xaml.cs
@@ -107,8 +107,9 @@ protected override async void OnStartup(StartupEventArgs e)
const bool previewGeometryTest = false;
#endif
bool backgroundLaunch = e.Args.Contains(
- "--background",
- StringComparer.OrdinalIgnoreCase);
+ "--background",
+ StringComparer.OrdinalIgnoreCase) ||
+ AppDistribution.IsStartupTaskActivation();
bool shutdownExisting = e.Args.Contains(
"--shutdown-existing",
StringComparer.OrdinalIgnoreCase);
@@ -136,7 +137,7 @@ protected override async void OnStartup(StartupEventArgs e)
{
try
{
- Autostart.SetEnabled(true);
+ await Autostart.SetEnabledAsync(true);
}
catch (Exception exception)
{
@@ -1307,6 +1308,9 @@ private void OpenSettings()
bool force,
CancellationToken cancellationToken)
{
+ if (AppDistribution.IsMicrosoftStore)
+ return Task.FromResult(null);
+
#if DEBUG
if (_qaUpdateAvailable)
{
@@ -1568,7 +1572,7 @@ private async Task ApplySettingsAsync(
await _pipelineGate.WaitAsync();
Config previous = _config!.Clone();
- bool previousAutostart = Autostart.IsEnabled();
+ bool previousAutostart = await Autostart.IsEnabledAsync();
bool wasRunning = IsReplayRunning;
bool pipelineChanged = !previous.PipelineEquals(candidate);
bool pipelineTouched = false;
@@ -1595,7 +1599,7 @@ private async Task ApplySettingsAsync(
Localization.Text("L.Engine.BufferStartFailed"));
}
- Autostart.SetEnabled(autostartEnabled);
+ await Autostart.SetEnabledAsync(autostartEnabled);
_config.Save();
UpdateUiState();
@@ -1628,7 +1632,7 @@ private async Task ApplySettingsAsync(
}
try
{
- Autostart.SetEnabled(previousAutostart);
+ await Autostart.SetEnabledAsync(previousAutostart);
}
catch (Exception rollbackException)
{
diff --git a/src/Captail/AppDistribution.cs b/src/Captail/AppDistribution.cs
new file mode 100644
index 0000000..ead6b54
--- /dev/null
+++ b/src/Captail/AppDistribution.cs
@@ -0,0 +1,47 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using Windows.ApplicationModel.Activation;
+
+namespace Captail;
+
+internal static class AppDistribution
+{
+ internal const string StoreProductId = "9PKVNVLKPTPS";
+
+#if MICROSOFT_STORE
+ internal static bool IsMicrosoftStore { get; } = true;
+#else
+ internal static bool IsMicrosoftStore { get; } = HasPackageIdentity();
+#endif
+
+ internal static bool IsStartupTaskActivation()
+ {
+ if (!IsMicrosoftStore)
+ return false;
+
+ try
+ {
+ return Windows.ApplicationModel.AppInstance
+ .GetActivatedEventArgs()?.Kind == ActivationKind.StartupTask;
+ }
+ catch (Exception exception)
+ {
+ Log.Write($"Could not read package activation: {exception.Message}");
+ return false;
+ }
+ }
+
+ private static bool HasPackageIdentity()
+ {
+ int length = 0;
+ return GetCurrentPackageFullName(ref length, null) ==
+ ErrorInsufficientBuffer;
+ }
+
+ private const int ErrorInsufficientBuffer = 122;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ private static extern int GetCurrentPackageFullName(
+ ref int packageFullNameLength,
+ StringBuilder? packageFullName);
+}
diff --git a/src/Captail/Autostart.cs b/src/Captail/Autostart.cs
index 4123c53..ea7ee26 100644
--- a/src/Captail/Autostart.cs
+++ b/src/Captail/Autostart.cs
@@ -1,15 +1,25 @@
using Microsoft.Win32;
+using Windows.ApplicationModel;
namespace Captail;
-/// Per-user startup through HKCU\...\Run; administrator rights are not required.
+/// Per-user startup through MSIX StartupTask or HKCU Run.
public static class Autostart
{
private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string ValueName = "Captail";
+ private const string StoreTaskId = "CaptailStartup";
- public static bool IsEnabled()
+ public static async Task IsEnabledAsync()
{
+ if (AppDistribution.IsMicrosoftStore)
+ {
+ StartupTask task = await StartupTask.GetAsync(StoreTaskId);
+ return task.State is
+ StartupTaskState.Enabled or
+ StartupTaskState.EnabledByPolicy;
+ }
+
using var key = Registry.CurrentUser.OpenSubKey(RunKey);
return string.Equals(
ReadCommand(key),
@@ -19,12 +29,44 @@ public static bool IsEnabled()
internal static bool HasEntry()
{
+ if (AppDistribution.IsMicrosoftStore)
+ return false;
+
using var key = Registry.CurrentUser.OpenSubKey(RunKey);
return !string.IsNullOrWhiteSpace(ReadCommand(key));
}
- public static void SetEnabled(bool enabled)
+ public static async Task SetEnabledAsync(bool enabled)
{
+ if (AppDistribution.IsMicrosoftStore)
+ {
+ StartupTask task = await StartupTask.GetAsync(StoreTaskId);
+ if (!enabled)
+ {
+ if (task.State is
+ StartupTaskState.Enabled or
+ StartupTaskState.EnabledByPolicy)
+ {
+ task.Disable();
+ }
+ return;
+ }
+
+ StartupTaskState state = task.State is
+ StartupTaskState.Enabled or
+ StartupTaskState.EnabledByPolicy
+ ? task.State
+ : await task.RequestEnableAsync();
+ if (state is not
+ (StartupTaskState.Enabled or
+ StartupTaskState.EnabledByPolicy))
+ {
+ throw new InvalidOperationException(
+ Localization.Text("L.Error.AutostartDenied"));
+ }
+ return;
+ }
+
using var key = Registry.CurrentUser.CreateSubKey(RunKey);
if (enabled)
{
diff --git a/src/Captail/Captail.csproj b/src/Captail/Captail.csproj
index 111a60e..45f84fd 100644
--- a/src/Captail/Captail.csproj
+++ b/src/Captail/Captail.csproj
@@ -16,6 +16,7 @@
Assets\Captail.ico0.1.5true
+ $(DefineConstants);MICROSOFT_STORE
diff --git a/src/Captail/Languages/Strings.en.xaml b/src/Captail/Languages/Strings.en.xaml
index b45913d..7fca013 100644
--- a/src/Captail/Languages/Strings.en.xaml
+++ b/src/Captail/Languages/Strings.en.xaml
@@ -24,6 +24,8 @@
Could not check for updates · click to retryUpdate check failedUpdate failed
+ v{0} · Microsoft Store
+ Updates are delivered by Microsoft StoreInstant Replay is onInstant Replay is off
@@ -195,6 +197,7 @@
Codec unavailableChoose a codec supported by current GPU and driver.Could not update startup
+ Windows did not allow Captail to start automatically. Check Startup apps in Windows Settings.Audio source unavailableChange cancelled. Previous audio configuration restored.Could not change source
diff --git a/src/Captail/Languages/Strings.ru.xaml b/src/Captail/Languages/Strings.ru.xaml
index 975dc29..d736894 100644
--- a/src/Captail/Languages/Strings.ru.xaml
+++ b/src/Captail/Languages/Strings.ru.xaml
@@ -24,6 +24,8 @@
Не удалось проверить обновления · нажмите, чтобы повторитьОшибка проверки обновленийОшибка обновления
+ v{0} · Microsoft Store
+ Обновления устанавливает Microsoft StoreМгновенный повтор включёнМгновенный повтор выключен
@@ -195,6 +197,7 @@
Кодек недоступенВыберите кодек, который поддерживается текущим GPU и драйвером.Не удалось изменить автозапуск
+ Windows не разрешила автозапуск Captail. Проверьте раздел «Автозагрузка» в параметрах Windows.Источник звука недоступенНастройка отменена. Предыдущая конфигурация восстановлена.Не удалось изменить источник
diff --git a/src/Captail/SettingsWindow.xaml b/src/Captail/SettingsWindow.xaml
index 6fab3b9..f83b170 100644
--- a/src/Captail/SettingsWindow.xaml
+++ b/src/Captail/SettingsWindow.xaml
@@ -855,6 +855,7 @@