feature(updater): Implement auto-updating system using AppCast protocol

This commit is contained in:
2026-06-22 02:30:50 +04:00
parent c23b4bd0da
commit 602d294da4
18 changed files with 524 additions and 9 deletions
+4 -1
View File
@@ -45,4 +45,7 @@ app.*.map.json
/android/app/release
# FVM Version Cache
.fvm/
.fvm/
# Local launcher update signing material
.moonwell_signing/
+14 -2
View File
@@ -46,8 +46,8 @@ dart run build_runner build --delete-conflicting-outputs
dart format --set-exit-if-changed .
flutter analyze
flutter test
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host --dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host --dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
See `docs/linting.md` for the required format, analyze, test, and Lefthook
@@ -61,6 +61,15 @@ The launcher API base URL is compile-time configuration:
--dart-define=MOONWELL_API_BASE_URL=https://host
```
The optional Windows self-update feed is also compile-time configuration:
```powershell
--dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
Production AppCast feeds must be public HTTPS URLs. Omit the setting to disable
self-update in local builds.
Do not commit private credentials, production secrets, temporary bearer tokens,
or local-only API URLs.
@@ -71,6 +80,9 @@ or local-only API URLs.
Do not store sessions or other secrets in `shared_preferences`.
- File sync behavior should stay in `ClientSyncUseCase` and
`GameInstallationService`.
- Launcher self-update behavior should stay behind `LauncherUpdateService`.
Windows updates use WinSparkle/AppCast and the signed Inno Setup artifact;
they are separate from game-client synchronization.
- The server manifest is the source of truth for client files.
- Downloads must write to `*.moonwell.part`, verify size and SHA-256, then
replace the destination file only after successful verification.
+14 -2
View File
@@ -41,10 +41,19 @@ Set the launcher Web API base URL at compile time:
--dart-define=MOONWELL_API_BASE_URL=https://host
```
Set the public AppCast feed URL to enable Windows launcher self-updates:
```powershell
--dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
The updater is disabled when `MOONWELL_APPCAST_URL` is omitted. Production
feeds must use HTTPS.
Example run command:
```powershell
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host --dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
## Development
@@ -67,10 +76,12 @@ pre-commit workflow.
Build the Windows launcher:
```powershell
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host --dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
The Inno Setup installer script lives at `installer/moonwell_launcher.iss`.
See `docs/launcher_self_update.md` for the AppCast format, signing keys, and
manual release procedure.
## Architecture
@@ -99,3 +110,4 @@ See `docs/launcher_web_api_spec.md` for the API and sync contract.
- `docs/project_review.md`: project review with strengths, weaknesses, and
upgrade feasibility notes
- `docs/launcher_web_api_spec.md`: current launcher Web API and sync behavior
- `docs/launcher_self_update.md`: Windows self-update and release operations
+134
View File
@@ -0,0 +1,134 @@
# Launcher Self-Update
MoonWell Launcher uses `auto_updater` 1.0.0, backed by WinSparkle 0.8.1, to
update installed Windows launchers from an AppCast feed. This is independent of
the manifest-based World of Warcraft client synchronization.
The launcher performs one background update check after its window is visible.
WinSparkle shows its native prompt only when a newer version is available.
Missing configuration, network failures, and malformed feeds do not block the
launcher. Periodic WinSparkle checks are disabled.
## Build Configuration
Pass the public feed URL at build time:
```powershell
flutter build windows `
--dart-define=MOONWELL_API_BASE_URL=https://host `
--dart-define=MOONWELL_APPCAST_URL=https://host/launcher/updates/appcast.xml
```
The feed URL must be an absolute HTTPS URL. Omit
`MOONWELL_APPCAST_URL` to disable self-update, including for ordinary local
development.
## Signing Keys
WinSparkle verifies installer artifacts with a DSA signature. The public key is
committed at `windows/runner/resources/dsa_pub.pem` and embedded as the
`DSAPub` resource. The corresponding private key is local signing material at
`.moonwell_signing/dsa_priv.pem`; it is ignored by Git and must be backed up to
the project secret store before publishing the first update.
Never commit or upload the private key. Losing it prevents existing
installations from accepting future updates. Rotating it requires shipping an
ordinary installer containing the replacement public key before publishing
artifacts signed by the replacement private key.
The AppCast signature is not Authenticode. Publicly trusted Authenticode
certificates and managed signing services are paid products, so MoonWell does
not currently Authenticode-sign the installer. Windows can therefore display
an unknown-publisher or SmartScreen warning. A free self-signed Authenticode
certificate would not establish trust on player machines.
## Version and AppCast Contract
Increase the `X.Y.Z` version in `pubspec.yaml` for every launcher release. A
build-number-only change such as `1.0.0+2` is not sufficient because WinSparkle
compares the Windows product version. Pass the same `X.Y.Z` value to Inno Setup:
```powershell
ISCC.exe /DMyAppVersion=1.1.0 installer\moonwell_launcher.iss
```
The resulting artifact is
`build/installer/moonwell_launcher_1.1.0_windows_setup.exe`.
Use a public AppCast with absolute URLs:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>MoonWell Launcher</title>
<description>MoonWell Launcher updates</description>
<language>ru</language>
<item>
<title>MoonWell Launcher 1.1.0</title>
<sparkle:releaseNotesLink>https://host/launcher/updates/1.1.0/release-notes.html</sparkle:releaseNotesLink>
<pubDate>Sun, 21 Jun 2026 12:00:00 +0400</pubDate>
<enclosure
url="https://host/launcher/updates/1.1.0/moonwell_launcher_1.1.0_windows_setup.exe"
sparkle:version="1.1.0"
sparkle:os="windows"
sparkle:dsaSignature="SIGNATURE_FROM_SIGN_UPDATE"
length="ACTUAL_INSTALLER_SIZE_IN_BYTES"
type="application/octet-stream" />
</item>
</channel>
</rss>
```
The installer URL and release-notes URL must be absolute. The publication host
must serve the feed, notes, and installer without authentication or redirects
to an authenticated endpoint.
## Manual Release
Perform Windows signing on the Windows release machine because the package
invokes WinSparkle's Windows signing utility.
1. Update `pubspec.yaml` to the new `X.Y.Z+N` version.
2. Run `flutter pub get`, formatting, analysis, and all tests.
3. Build Windows with both production `--dart-define` values.
4. Compile the installer with `ISCC.exe /DMyAppVersion=X.Y.Z`.
5. Make the private key available outside the repository and sign the artifact:
```powershell
dart run auto_updater:sign_update `
build\installer\moonwell_launcher_X.Y.Z_windows_setup.exe `
C:\secure-path\dsa_priv.pem
```
6. Copy the emitted `sparkle:dsaSignature` into the enclosure. The package's
Windows helper reports `length="0"`; replace it with the actual value from:
```powershell
(Get-Item build\installer\moonwell_launcher_X.Y.Z_windows_setup.exe).Length
```
7. Upload the versioned installer and release notes first.
8. Download the hosted installer, verify its byte length, and test its DSA
signature before changing the feed.
9. Publish `appcast.xml` last so clients never observe an incomplete release.
Keep the preceding installer available for rollback and support. Rollback is a
new release with a product version higher than the faulty release; WinSparkle
does not install a lower version as an update.
## Release Verification
Test from an installed older version on a clean Windows x64 machine:
- offline or invalid feed: launcher remains usable and writes a log entry
- same or older feed version: no update prompt
- valid newer release: prompt, download, launcher exit, install, and relaunch
- modified installer or invalid DSA signature: WinSparkle rejects the update
- launcher session and selected game directory remain after installation
Watch the debug output for Flutter platform-channel thread warnings during the
test. `auto_updater` has an open Windows callback-thread issue; do not release
if it reproduces. Patch and pin `auto_updater_windows` so native callbacks are
marshalled onto Flutter's platform thread, then repeat the complete update test.
+4 -2
View File
@@ -1,6 +1,8 @@
#define MyAppName "MoonWell Launcher"
#define MyAppExeName "MoonWell.exe"
#define MyAppVersion "1.0.0"
#ifndef MyAppVersion
#define MyAppVersion "1.0.0"
#endif
#define MyAppPublisher "MoonWell"
#define MyAppURL "https://moon-well.online"
#define MyAppAssocName MyAppName + " Launcher"
@@ -19,7 +21,7 @@ DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
PrivilegesRequired=lowest
OutputDir=..\build\installer
OutputBaseFilename=moonwell_launcher_setup
OutputBaseFilename=moonwell_launcher_{#MyAppVersion}_windows_setup
SetupIconFile=..\windows\runner\resources\app_icon.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma
+3
View File
@@ -2,6 +2,9 @@ final class Config {
static const launcherApiBaseUrl = String.fromEnvironment(
'MOONWELL_API_BASE_URL',
);
static const launcherAppcastUrl = String.fromEnvironment(
'MOONWELL_APPCAST_URL',
);
static const launcherLogFileName = 'launcher.log';
static const gameExecutableName = 'Wow.exe';
static const cacheDirectoryName = 'Cache';
@@ -0,0 +1,99 @@
import 'dart:io';
import 'package:auto_updater/auto_updater.dart';
import 'package:injectable/injectable.dart';
import 'package:moonwell_launcher/config.dart';
import 'package:moonwell_launcher/features/launcher/data/launcher_log_service.dart';
abstract interface class LauncherUpdateClient {
Future<void> setFeedUrl(String feedUrl);
Future<void> disableScheduledChecks();
Future<void> checkInBackground();
}
final class AutoUpdaterLauncherUpdateClient implements LauncherUpdateClient {
@override
Future<void> setFeedUrl(String feedUrl) => autoUpdater.setFeedURL(feedUrl);
@override
Future<void> disableScheduledChecks() =>
autoUpdater.setScheduledCheckInterval(0);
@override
Future<void> checkInBackground() =>
autoUpdater.checkForUpdates(inBackground: true);
}
@lazySingleton
class LauncherUpdateService {
LauncherUpdateService(LauncherLogService launcherLogService)
: this.forTesting(
launcherLogService: launcherLogService,
updateClient: AutoUpdaterLauncherUpdateClient(),
isWindows: Platform.isWindows,
);
LauncherUpdateService.forTesting({
required LauncherLogService launcherLogService,
required LauncherUpdateClient updateClient,
required bool isWindows,
}) : _launcherLogService = launcherLogService,
_updateClient = updateClient,
_isWindows = isWindows;
final LauncherLogService _launcherLogService;
final LauncherUpdateClient _updateClient;
final bool _isWindows;
bool _hasChecked = false;
Future<void> checkForUpdates({
String feedUrl = Config.launcherAppcastUrl,
}) async {
if (!_isWindows || _hasChecked) {
return;
}
final normalizedFeedUrl = feedUrl.trim();
if (normalizedFeedUrl.isEmpty) {
await _launcherLogService.info(
'Launcher self-update is disabled because no AppCast URL is configured.',
);
return;
}
final uri = Uri.tryParse(normalizedFeedUrl);
if (uri == null ||
!uri.isAbsolute ||
uri.scheme != 'https' ||
!uri.hasAuthority ||
uri.host.isEmpty) {
await _launcherLogService.error(
'Launcher self-update AppCast URL is invalid.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
);
return;
}
_hasChecked = true;
try {
await _updateClient.setFeedUrl(normalizedFeedUrl);
await _updateClient.disableScheduledChecks();
await _updateClient.checkInBackground();
await _launcherLogService.info(
'Launcher self-update check started.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
);
} catch (error, stackTrace) {
await _launcherLogService.error(
'Launcher self-update check failed.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
error: error,
stackTrace: stackTrace,
);
}
}
}
+5 -2
View File
@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/mw_app.dart';
import 'package:moonwell_launcher/features/launcher/data/launcher_update_service.dart';
import 'package:moonwell_launcher/service_container.dart';
import 'package:window_manager/window_manager.dart';
@@ -10,6 +11,9 @@ Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized();
await configureDependencies(env: 'flutter');
final launcherUpdateService = getIt<LauncherUpdateService>();
WindowOptions windowOptions = WindowOptions(
size: Size(960, 540),
center: true,
@@ -22,10 +26,9 @@ Future<void> main() async {
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
await launcherUpdateService.checkForUpdates();
}),
);
await configureDependencies(env: 'flutter');
runApp(const MoonWellApp());
}
+4
View File
@@ -18,6 +18,7 @@ import 'features/launcher/application/client_sync_use_case.dart' as _i757;
import 'features/launcher/data/game_installation_service.dart' as _i315;
import 'features/launcher/data/launcher_api_client.dart' as _i703;
import 'features/launcher/data/launcher_log_service.dart' as _i43;
import 'features/launcher/data/launcher_update_service.dart' as _i605;
import 'features/preferences/data/repositories/local_preferences_repository.dart'
as _i658;
import 'features/preferences/domain/repositories/preferences_repository.dart'
@@ -47,6 +48,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.lazySingleton<_i558.FlutterSecureStorage>(
() => flutterSecureStorageModule.storage,
);
gh.lazySingleton<_i605.LauncherUpdateService>(
() => _i605.LauncherUpdateService(gh<_i43.LauncherLogService>()),
);
gh.lazySingleton<_i703.LauncherApiClient>(
() => _i703.LauncherApiClient(
launcherLogService: gh<_i43.LauncherLogService>(),
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import auto_updater_macos
import file_picker
import flutter_secure_storage_darwin
import screen_retriever_macos
@@ -12,6 +13,7 @@ import shared_preferences_foundation
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AutoUpdaterMacosPlugin.register(with: registry.registrar(forPlugin: "AutoUpdaterMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
+13
View File
@@ -1,4 +1,7 @@
PODS:
- auto_updater_macos (0.0.1):
- FlutterMacOS
- Sparkle
- file_picker (0.0.1):
- FlutterMacOS
- flutter_secure_storage_darwin (10.0.0):
@@ -10,10 +13,12 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- Sparkle (2.9.3)
- window_manager (0.5.0):
- FlutterMacOS
DEPENDENCIES:
- auto_updater_macos (from `Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- FlutterMacOS (from `Flutter/ephemeral`)
@@ -21,7 +26,13 @@ DEPENDENCIES:
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
SPEC REPOS:
trunk:
- Sparkle
EXTERNAL SOURCES:
auto_updater_macos:
:path: Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
flutter_secure_storage_darwin:
@@ -36,11 +47,13 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
screen_retriever_macos: c5508cc3c66ff0d4db650480cf0ab691e220d933
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
Sparkle: 39dbdf28637f6056fbf5ee6494bdbfae54f49f41
window_manager: b729e31d38fb04905235df9ea896128991cad99e
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
+32
View File
@@ -41,6 +41,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.1"
auto_updater:
dependency: "direct main"
description:
name: auto_updater
sha256: "74fd008b021d15e4fc50fb5f1923870e6374ae831bb1168241c23bc35a4e898b"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
auto_updater_macos:
dependency: transitive
description:
name: auto_updater_macos
sha256: ef9de0daf38d782d2243fe131503a9404d62df762f5386e5360020e43c01867f
url: "https://pub.dev"
source: hosted
version: "1.0.0"
auto_updater_platform_interface:
dependency: transitive
description:
name: auto_updater_platform_interface
sha256: ed5dff8cea18c58bc5ac787ff9122fedd1644272e02015d28c9b592f8078c5dc
url: "https://pub.dev"
source: hosted
version: "1.0.0"
auto_updater_windows:
dependency: transitive
description:
name: auto_updater_windows
sha256: "2bba20a71eee072f49b7267fedd5c4f1406c4b1b1e5b83932c634dbab75b80c9"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
bloc:
dependency: transitive
description:
+1
View File
@@ -45,6 +45,7 @@ dependencies:
flutter_secure_storage: ^10.3.1
shared_preferences: ^2.5.3
window_manager: ^0.5.1
auto_updater: 1.0.0
dev_dependencies:
flutter_test:
@@ -0,0 +1,163 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:moonwell_launcher/features/launcher/data/launcher_log_service.dart';
import 'package:moonwell_launcher/features/launcher/data/launcher_update_service.dart';
void main() {
group('LauncherUpdateService', () {
test('does nothing on unsupported platforms', () async {
final client = _FakeLauncherUpdateClient();
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: false,
);
await service.checkForUpdates(
feedUrl: 'https://moon-well.online/launcher/updates/appcast.xml',
);
expect(client.calls, isEmpty);
expect(logger.entries, isEmpty);
});
test('logs disabled state when the feed URL is empty', () async {
final client = _FakeLauncherUpdateClient();
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: true,
);
await service.checkForUpdates(feedUrl: ' ');
expect(client.calls, isEmpty);
expect(logger.entries.single, startsWith('INFO:'));
});
test('rejects a non-HTTPS feed URL without invoking the updater', () async {
final client = _FakeLauncherUpdateClient();
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: true,
);
await service.checkForUpdates(
feedUrl: 'http://moon-well.online/appcast.xml',
);
expect(client.calls, isEmpty);
expect(logger.entries.single, startsWith('ERROR:'));
});
test('rejects an HTTPS URL without a host', () async {
final client = _FakeLauncherUpdateClient();
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: true,
);
await service.checkForUpdates(feedUrl: 'https:appcast.xml');
expect(client.calls, isEmpty);
expect(logger.entries.single, startsWith('ERROR:'));
});
test(
'configures WinSparkle before starting one background check',
() async {
final client = _FakeLauncherUpdateClient();
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: true,
);
const feedUrl = 'https://moon-well.online/launcher/updates/appcast.xml';
await service.checkForUpdates(feedUrl: feedUrl);
await service.checkForUpdates(feedUrl: feedUrl);
expect(client.calls, <String>[
'setFeedUrl:$feedUrl',
'disableScheduledChecks',
'checkInBackground',
]);
expect(logger.entries.single, startsWith('INFO:'));
},
);
test('logs updater failures without propagating them', () async {
final client = _FakeLauncherUpdateClient(failure: Exception('offline'));
final logger = _FakeLauncherLogService();
final service = LauncherUpdateService.forTesting(
launcherLogService: logger,
updateClient: client,
isWindows: true,
);
await expectLater(
service.checkForUpdates(
feedUrl: 'https://moon-well.online/launcher/updates/appcast.xml',
),
completes,
);
expect(logger.entries.single, contains('offline'));
});
});
}
class _FakeLauncherUpdateClient implements LauncherUpdateClient {
_FakeLauncherUpdateClient({this.failure});
final Object? failure;
final List<String> calls = <String>[];
@override
Future<void> setFeedUrl(String feedUrl) async {
calls.add('setFeedUrl:$feedUrl');
if (failure case final error?) {
throw error;
}
}
@override
Future<void> disableScheduledChecks() async {
calls.add('disableScheduledChecks');
}
@override
Future<void> checkInBackground() async {
calls.add('checkInBackground');
}
}
class _FakeLauncherLogService extends LauncherLogService {
final List<String> entries = <String>[];
@override
Future<void> info(
String message, {
String? installationDir,
Map<String, Object?> details = const <String, Object?>{},
}) async {
entries.add('INFO:$message');
}
@override
Future<void> error(
String message, {
String? installationDir,
Map<String, Object?> details = const <String, Object?>{},
Object? error,
StackTrace? stackTrace,
}) async {
entries.add('ERROR:$message:$error');
}
}
@@ -6,11 +6,14 @@
#include "generated_plugin_registrant.h"
#include <auto_updater_windows/auto_updater_windows_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AutoUpdaterWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AutoUpdaterWindowsPluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
+1
View File
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
auto_updater_windows
flutter_secure_storage_windows
screen_retriever_windows
window_manager
+8
View File
@@ -55,6 +55,14 @@ END
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// WinSparkle update signature verification
//
DSAPub DSAPEM "resources\\dsa_pub.pem"
/////////////////////////////////////////////////////////////////////////////
//
// Version
+20
View File
@@ -0,0 +1,20 @@
-----BEGIN PUBLIC KEY-----
MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC5rT00uqOtUBNAtY/Kzep4xFXX34Xh
ArYMNMsc0lSCm7WzwklN1GTqKwp18gUxU1WX4YepePBwb4Mtm22VdYW76xKUMXpP
fZ/d2e+l2jwo/urixdQ9vdGrYTkPJLS9Mn6q/7/XQnIx/MTroOcYNh1QJj3Gynxy
y2C95Ty4yaxpjz9BP4TzT8XSvAy4ItdE0Xv2DqvruAP8VLR7HLAYMZFxvCuC65EC
3nLIRByL8s7CGq8w2Wn7x8WKI/PQ/RvngMoc0xrK58qV+I2hFefXSYG4T7/aDvOb
MTnIHlBZcgSEGCFDhtRqStKcBVW+ybk9SRpePU8rWL6QTtZTaBRlGlyRAh0A9A66
ZMG2sKJqs2w6X+dUCXzeNUMSrYpQit6UjwKCAQBJShETJlN90ZvrtYSOUnTrXKlH
rka5s6d1xFUNyqVNlI84oRC2gfw+eVAzbjtwFXv342uSHgC+kwQwK5UClxBTDxcm
YK4yC3288a4BoCf76SkLcUOdEp2/Ef1MjMSWKorcX9SAKY98RihrRXoHLrmDN3vk
/SVqgLv1Auu5X7yHMO2IDWvI29Sh6W13i2joXFt2O5ufsMY1k6sQtnT6R7sLphVw
ifE6ulDwRNjSkPMFCrKyKpnqXmC1TvtAdkSd0DsWEfQuqp3878PZoVTJzHxrl1Y0
3M7yrbVj5OojdGA9kX0zelrNCIPuH+2vmfeFis8y5XFFEOxc9yWezFYJ3TfEA4IB
BQACggEAfoxCOU4ofKti7T7QVV3d5Oh6fIi2eTLG4utP6Fvtb6AktF6OPJWeDmDe
g3ffLkKyx8/uA+KuZJO3/t/pHvzUvWNmc487bFZYd0dhlObQEu6Qg5uqDVWG4nqy
K1zNmhVO97SqzBjUklyaxQre65w3XI4a1XSk8RAkcMzu5rze4P9BodEChaG/kEih
qCJZivBjhMjaKi5cmE/S9G0zMrdTf152V8WaN0EjbFThC1izxk2j+76t+ALAe1Tt
I1bY9RT8pzjdfRwWHja0cooPILCWL82pcolOAvrNzhkbR9sIcw5t9F517EQch4VD
0W9SMpZl5uKcw3RS0oiFWyyKNWavvQ==
-----END PUBLIC KEY-----