diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 0000000..b1d38b0 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,43 @@ +name: Update Integration Test + +on: + workflow_call: + workflow_dispatch: + +jobs: + update-cycle: + name: Update cycle (${{ matrix.scenario.name }}) + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + scenario: + - { name: single, script: ./test-local-update.ps1 } + - { name: dual, script: ./test-local-update-dual.ps1 } + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: recursive + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + + - name: Run ${{ matrix.scenario.name }}-channel update cycle + shell: pwsh + run: ${{ matrix.scenario.script }} + + - name: Upload diagnostics on failure + if: failure() + uses: actions/upload-artifact@v7 + with: + name: update-cycle-${{ matrix.scenario.name }}-logs + # Updater log: %TEMP%\extUpdateLog-*.txt; RawDevLauncher app log: %APPDATA%\RawDevLauncher\*.txt + path: | + ${{ runner.temp }}\extUpdateLog-*.txt + ${{ env.APPDATA }}\RawDevLauncher\*.txt + if-no-files-found: ignore + retention-days: 7 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 497ece0..5a9a987 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,84 +13,114 @@ on: env: TOOL_PROJ_PATH: ./src/DevLauncher/DevLauncher.csproj - CREATOR_PROJ_PATH: ./ModdingToolBase/src/AnakinApps/ApplicationManifestCreator/ApplicationManifestCreator.csproj - UPLOADER_PROJ_PATH: ./ModdingToolBase/src/AnakinApps/FtpUploader/FtpUploader.csproj + PUBLISH_SCRIPT: ./ModdingToolBase/scripts/Publish-Release.ps1 TOOL_EXE: RaW-DevLauncher.exe UPDATER_EXE: AnakinRaW.ExternalUpdater.exe - MANIFEST_CREATOR: AnakinRaW.ApplicationManifestCreator.dll - SFTP_UPLOADER: AnakinRaW.FtpUploader.dll + EMBEDDED_TRUST_CERT: src/DevLauncher/Resources/Certs/anakinraw-trust.cer ORIGIN_BASE: https://republicatwar.com/downloads/RawDevLauncher - ORIGIN_BASE_PART: downloads/RawDevLauncher/ + SFTP_BASE_PATH: downloads/RawDevLauncher/ BRANCH_NAME: ${{ github.event.inputs.branch || 'stable' }} + # Migration-release values. Leave empty for a normal release; populate to enable. + # + # Origin URL of the next-generation channel, written into the manifest's componentOriginInfo. + NEXT_ORIGIN_BASE: https://republicatwar.com/downloads/RawDevLauncher/v2 + + # SFTP path the next-generation channel uploads to. Set together with NEXT_ORIGIN_BASE. + NEXT_SFTP_BASE_PATH: downloads/RawDevLauncher/v2/ + + # Previously-deployed updater used in place of the build-output one for the primary deploy. + # Only the old-gen manifest lists this binary; the next-gen manifest still uses the build-output updater. + # Requires NEXT_ORIGIN_BASE + NEXT_SFTP_BASE_PATH to be set. + COMPAT_UPDATER: tools/v1/AnakinRaW.ExternalUpdater.exe + jobs: - build: - name: Build +# Builds and tests the solution. + test: + uses: ./.github/workflows/test.yml + + # End-to-end self-update test + integration-test: + needs: [test] + uses: ./.github/workflows/integration-test.yml + + pack: + name: Pack + needs: [test] runs-on: windows-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 9.0.x - - name: Build - run: dotnet build ${{ env.TOOL_PROJ_PATH }} --configuration Release --output ./binaries + uses: actions/setup-dotnet@v5 + - name: Create NetFramework Release + # use build for .NET Framework to enusre external updater exe is included + run: dotnet build ${{ env.TOOL_PROJ_PATH }} --configuration Release -f net481 --output ./releases/net481 /p:DebugType=None /p:DebugSymbols=false - name: Upload a Build Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: Build Artifacts - path: | - binaries/${{env.TOOL_EXE}} - binaries/${{env.UPDATER_EXE}} + name: Binary Releases + path: ./releases + if-no-files-found: error retention-days: 1 deploy: name: Deploy - # Only deploy on push to main or manual trigger + # Stable deploys are gated to 'main'. Non-stable channels (beta, canary, etc.) can be + # workflow_dispatched from any branch. if: | - (github.ref == 'refs/heads/main' && github.event_name == 'push') || github.event_name == 'workflow_dispatch' - needs: [build] + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'workflow_dispatch' && + (github.event.inputs.branch != 'stable' || github.ref == 'refs/heads/main')) + needs: [pack, integration-test] runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - - name: Setup .NET - uses: actions/setup-dotnet@v4 + - uses: actions/download-artifact@v8 with: - dotnet-version: 9.0.x - - uses: actions/download-artifact@v4 + name: Binary Releases + path: ./releases + - name: Setup .NET + uses: actions/setup-dotnet@v5 with: - name: Build Artifacts - path: binaries - - name: Build Creator - run: dotnet build ${{env.CREATOR_PROJ_PATH}} --configuration Release --output ./dev - - name: Build Uploader - run: dotnet build ${{env.UPLOADER_PROJ_PATH}} --configuration Release --output ./dev - - name: Create Manifest - run: dotnet ./dev/${{env.MANIFEST_CREATOR}} -a binaries/${{env.TOOL_EXE}} --appDataFiles binaries/${{env.UPDATER_EXE}} --origin ${{env.ORIGIN_BASE}} -o ./binaries -b ${{env.BRANCH_NAME}} - - name: Upload Build - run: dotnet ./dev/${{env.SFTP_UPLOADER}} ftp --host $host --port $port -u ${{secrets.SFTP_USER}} -p ${{secrets.SFTP_PASSWORD}} --base $base_path -s $source - env: - host: republicatwar.com - port: 1579 - base_path: ${{env.ORIGIN_BASE_PART}} - source: ./binaries + dotnet-version: 10.0.x + + + - name: Publish self-update release + shell: pwsh + run: | + & $env:PUBLISH_SCRIPT ` + -AppExePath "./releases/net481/$env:TOOL_EXE" ` + -UpdaterExePath "./releases/net481/$env:UPDATER_EXE" ` + -EmbeddedTrustCertPath "$env:EMBEDDED_TRUST_CERT" ` + -Origin "$env:ORIGIN_BASE" ` + -SftpBasePath "$env:SFTP_BASE_PATH" ` + -Branch "$env:BRANCH_NAME" ` + -SigningPfxBase64 "${{ secrets.UPDATER_SIGNING_PFX_B64 }}" ` + -SigningPfxPassword "${{ secrets.UPDATER_SIGNING_PFX_PASSWORD }}" ` + -SftpHost "republicatwar.com" ` + -SftpPort 1579 ` + -SftpUser "${{ secrets.SFTP_USER }}" ` + -SftpPassword "${{ secrets.SFTP_PASSWORD }}" ` + -NextOrigin "$env:NEXT_ORIGIN_BASE" ` + -NextSftpBasePath "$env:NEXT_SFTP_BASE_PATH" ` + -CompatibilityUpdaterExePath "$env:COMPAT_UPDATER" - - uses: dotnet/nbgv@v0.4.2 + - uses: dotnet/nbgv@v0.5.1 id: nbgv - name: Create GitHub release # Only create a release on push to main if: | github.ref == 'refs/heads/main' && github.event_name == 'push' - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: name: v${{ steps.nbgv.outputs.SemVer2 }} tag_name: v${{ steps.nbgv.outputs.SemVer2 }} diff --git a/.github/workflows/build.yml b/.github/workflows/test.yml similarity index 67% rename from .github/workflows/build.yml rename to .github/workflows/test.yml index 1996673..05f2035 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/test.yml @@ -14,13 +14,13 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Build in Release Mode - run: dotnet build --configuration Release \ No newline at end of file + run: dotnet test --configuration Release --report-github \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5af3e6f..95991ec 100644 --- a/.gitignore +++ b/.gitignore @@ -398,4 +398,6 @@ FodyWeavers.xsd *.sln.iml -dev/* \ No newline at end of file +dev/* + +.local_deploy/ \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index f9cd8ce..dacff77 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,10 @@  - - - all - 3.9.50 - - + + + + all + 3.9.50 + + \ No newline at end of file diff --git a/ModVerify b/ModVerify index 4eb6a1c..a3cb110 160000 --- a/ModVerify +++ b/ModVerify @@ -1 +1 @@ -Subproject commit 4eb6a1c0567b68c6a0f858426c478c7572f4d81c +Subproject commit a3cb110eaa861778284eddcef8806883eb4711b7 diff --git a/ModdingToolBase b/ModdingToolBase index e12f6ce..49dcf5f 160000 --- a/ModdingToolBase +++ b/ModdingToolBase @@ -1 +1 @@ -Subproject commit e12f6ceedb83fe9e3372dd89c68d508f8479cf92 +Subproject commit 49dcf5f0ca7bd4bdd1cc1e2d32655afa906342cb diff --git a/RawDevTools.sln b/RawDevTools.sln index 6f39e92..40f2000 100644 --- a/RawDevTools.sln +++ b/RawDevTools.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.4.11519.219 insiders +VisualStudioVersion = 18.4.11519.219 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevLauncher", "src\DevLauncher\DevLauncher.csproj", "{1BA491BC-2CD6-4270-9573-5FF9529D8D89}" EndProject @@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppUpdaterFramework.Attribu EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ApplicationBase.Shared", "ModdingToolBase\src\AnakinApps\ApplicationBase.Shared\ApplicationBase.Shared.shproj", "{B297A13A-8E3A-436C-BA97-8B5F57827FFE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Engine.FileSystem", "ModVerify\src\PetroglyphTools\PG.StarWarsGame.Engine.FileSystem\PG.StarWarsGame.Engine.FileSystem.csproj", "{679DA7CA-B23B-7160-B6CD-420BD9CD2312}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -139,6 +141,10 @@ Global {1AA3A6D5-5492-26C3-E36C-54AB5997D2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AA3A6D5-5492-26C3-E36C-54AB5997D2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {1AA3A6D5-5492-26C3-E36C-54AB5997D2CF}.Release|Any CPU.Build.0 = Release|Any CPU + {679DA7CA-B23B-7160-B6CD-420BD9CD2312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {679DA7CA-B23B-7160-B6CD-420BD9CD2312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {679DA7CA-B23B-7160-B6CD-420BD9CD2312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {679DA7CA-B23B-7160-B6CD-420BD9CD2312}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -164,6 +170,7 @@ Global {8768819D-B0C2-4487-3B3A-84A90F36BAB7} = {46DB413A-0F73-48A6-9071-9C38916BE6FC} {1AA3A6D5-5492-26C3-E36C-54AB5997D2CF} = {3E986062-E81F-4833-A127-24FA73FBCB1B} {B297A13A-8E3A-436C-BA97-8B5F57827FFE} = {46DB413A-0F73-48A6-9071-9C38916BE6FC} + {679DA7CA-B23B-7160-B6CD-420BD9CD2312} = {FF4C3704-0C74-40B5-A38D-AF29B6385D85} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC15751A-BB36-4EF1-BFF0-2DF1E419AC8F} diff --git a/deploy-local.ps1 b/deploy-local.ps1 new file mode 100644 index 0000000..ca2d090 --- /dev/null +++ b/deploy-local.ps1 @@ -0,0 +1,49 @@ +param( + [string]$InstalledVersion = "0.0.1-local", + [string]$ServerVersion = "99.99.99-local", + [switch]$DualPublish, + [string]$CompatibilityUpdater +) + +$ErrorActionPreference = "Stop" + +$root = $PSScriptRoot +if ([string]::IsNullOrEmpty($root)) { $root = Get-Location } + +. (Join-Path $root "ModdingToolBase\scripts\NbgvVersion.ps1") + +$deployRoot = Join-Path $root ".local_deploy" +$installBuildDir = Join-Path $deployRoot "bin\install" +$serverBuildDir = Join-Path $deployRoot "bin\tool" + +$toolProj = Join-Path $root "src\DevLauncher\DevLauncher.csproj" +$baseScript = Join-Path $root "ModdingToolBase\scripts\Publish-LocalRelease.ps1" + +if (Test-Path $deployRoot) { Remove-Item -Recurse -Force $deployRoot } +New-Item -ItemType Directory -Path $deployRoot | Out-Null + +$nbgv = Backup-NbgvVersion -RepoRoot $root +try { + Write-Host "--- Building DevLauncher (net481) @ installed v$InstalledVersion ---" -ForegroundColor Cyan + Set-NbgvVersion -Snapshot $nbgv -Version $InstalledVersion + dotnet build $toolProj --configuration Release -f net481 --output $installBuildDir /p:DebugType=None /p:DebugSymbols=false /p:LocalDeploy=true + + Write-Host "--- Building DevLauncher (net481) @ server v$ServerVersion ---" -ForegroundColor Cyan + Set-NbgvVersion -Snapshot $nbgv -Version $ServerVersion + dotnet build $toolProj --configuration Release -f net481 --output $serverBuildDir /p:DebugType=None /p:DebugSymbols=false /p:LocalDeploy=true + + $publishParams = @{ + AppExePath = Join-Path $serverBuildDir "RaW-DevLauncher.exe" + UpdaterExePath = Join-Path $serverBuildDir "AnakinRaW.ExternalUpdater.exe" + DeployRoot = $deployRoot + InstallBuildDir = $installBuildDir + Branch = "beta" + } + if ($DualPublish) { $publishParams.DualPublish = $true } + if ($CompatibilityUpdater) { $publishParams.CompatibilityUpdater = $CompatibilityUpdater } + + & $baseScript @publishParams +} +finally { + Restore-NbgvVersion -Snapshot $nbgv +} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..802ab21 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} \ No newline at end of file diff --git a/src/DevLauncher.Tests/DevLauncher.Tests.csproj b/src/DevLauncher.Tests/DevLauncher.Tests.csproj index a54378b..bec0165 100644 --- a/src/DevLauncher.Tests/DevLauncher.Tests.csproj +++ b/src/DevLauncher.Tests/DevLauncher.Tests.csproj @@ -1,23 +1,28 @@ - + - net9.0 + net481 disable enable + + false - true + true + Exe - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/DevLauncher/DevLauncher.csproj b/src/DevLauncher/DevLauncher.csproj index 6f7f992..e249ea2 100644 --- a/src/DevLauncher/DevLauncher.csproj +++ b/src/DevLauncher/DevLauncher.csproj @@ -18,27 +18,37 @@ true + + + $(DefineConstants);LOCAL_DEPLOY + + + + + + - - + + - + - + all - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -46,15 +56,15 @@ - + compile runtime; build; native; contentfiles; analyzers; buildtransitive - + compile runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -66,14 +76,6 @@ - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - diff --git a/src/DevLauncher/DevLauncherEnvironment.cs b/src/DevLauncher/DevLauncherEnvironment.cs index 76dc6d5..fbf8865 100644 --- a/src/DevLauncher/DevLauncherEnvironment.cs +++ b/src/DevLauncher/DevLauncherEnvironment.cs @@ -1,9 +1,12 @@ -using System; +using AnakinRaW.ApplicationBase.Environment; +using AnakinRaW.AppUpdaterFramework.Configuration; +using AnakinRaW.AppUpdaterFramework.Security; +using AnakinRaW.CommonUtilities.DownloadManager.Configuration; +using System; using System.Collections.Generic; using System.IO.Abstractions; +using System.Net; using System.Reflection; -using AnakinRaW.ApplicationBase.Environment; -using AnakinRaW.AppUpdaterFramework.Configuration; namespace RepublicAtWar.DevLauncher; @@ -16,12 +19,20 @@ internal class DevLauncherEnvironment(Assembly assembly, IFileSystem fileSystem) public override ICollection UpdateMirrors { get; } = new List { - new($"https://republicatwar.com/downloads/{ToolPathName}") + new($"https://republicatwar.com/downloads/{ToolPathName}/v2") }; public override string UpdateRegistryPath => $@"SOFTWARE\{ToolPathName}\Update"; protected override string ApplicationLocalDirectoryName => ToolPathName; + static DevLauncherEnvironment() + { + // For some unknown reason, packaging dependencies into the app, may alter the used security protocols... + // This reverts the changes and forces secure settings + if (ServicePointManager.SecurityProtocol != SecurityProtocolType.SystemDefault) + ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault | SecurityProtocolType.Tls12; + } + protected override UpdateConfiguration CreateUpdateConfiguration() { return new() @@ -34,6 +45,20 @@ protected override UpdateConfiguration CreateUpdateConfiguration() { SupportsRestart = true, PassCurrentArgumentsForRestart = true + }, + ManifestDownloadConfiguration = new ManifestDownloadConfiguration + { + DownloadRetryDelay = 500 + }, + ComponentDownloadConfiguration = new DownloadManagerConfiguration + { + ValidationPolicy = ValidationPolicy.Required + }, + ValidateInstallation = true, + ManifestSigningConfiguration = new SigningConfiguration + { + Policy = SignaturePolicy.Required, + SignatureAlgorithm = SignatureAlgorithm.ES256 } }; } diff --git a/src/DevLauncher/Pipelines/ReleaseRawPipeline.cs b/src/DevLauncher/Pipelines/ReleaseRawPipeline.cs index 1936d5b..cc82138 100644 --- a/src/DevLauncher/Pipelines/ReleaseRawPipeline.cs +++ b/src/DevLauncher/Pipelines/ReleaseRawPipeline.cs @@ -1,5 +1,4 @@ -using AET.Modinfo.Model; -using AnakinRaW.CommonUtilities.SimplePipeline; +using AnakinRaW.CommonUtilities.SimplePipeline; using AnakinRaW.CommonUtilities.SimplePipeline.Steps; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/DevLauncher/Program.cs b/src/DevLauncher/Program.cs index 50944ba..71199b2 100644 --- a/src/DevLauncher/Program.cs +++ b/src/DevLauncher/Program.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Logging; using PG.Commons; using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Engine.Xml.Parsers; using PG.StarWarsGame.Files.ALO; using PG.StarWarsGame.Files.DAT; using PG.StarWarsGame.Files.MEG; @@ -35,6 +34,7 @@ using System.IO.Abstractions; using System.Reflection; using System.Threading.Tasks; +using PG.StarWarsGame.Engine.Xml; using Testably.Abstractions; using ILogger = Serilog.ILogger; @@ -52,8 +52,10 @@ public static Task Main(string[] args) internal class Program : SelfUpdateableAppLifecycle { - private static readonly string EngineParserNamespace = typeof(XmlObjectParser<>).Namespace!; - private static readonly string ParserNamespace = typeof(PetroglyphXmlFileParser<>).Namespace!; + private const string EmbeddedTrustCertResource = "RaW-DevLauncher.Resources.Certs.anakinraw-trust.cer"; + + private static readonly string EngineParserNamespace = typeof(PetroglyphStarWarsGameXmlParser).Namespace!; + private static readonly string ParserNamespace = typeof(XmlFileParser<>).Namespace!; private static readonly string DevLauncherRootNamespace = typeof(Program).Namespace!; private static readonly string DevLauncherUpdateNamespace = typeof(RawDevLauncherUpdater).Namespace!; @@ -99,13 +101,32 @@ private async Task RunAppCoreAsync(string[] args, IServiceProvider appServi { Log.CloseAndFlush(); - Console.WriteLine(); - ConsoleUtilities.WriteHorizontalLine('-'); - Console.Write("Press ENTER to exit."); - Console.ReadLine(); + // Skip the interactive prompt in plain update mode: the host is being driven + // by the external updater / a test harness and there is no human at the console. + if (!RawDevLauncher.IsUpdateOnlyInvocation(args)) + { + Console.WriteLine(); + ConsoleUtilities.WriteHorizontalLine('-'); + Console.Write("Press ENTER to exit."); + Console.ReadLine(); + } } } + protected override void RegisterTrustedCertificates(IServiceProvider appServices) + { + if (!IsUpdateableApplication) + return; + + string? devCertPath = null; +#if DEBUG || LOCAL_DEPLOY + devCertPath = System.IO.Path.GetFullPath( + System.IO.Path.Combine(AppContext.BaseDirectory, "..", "dev-trust.cer")); +#endif + appServices.GetRequiredService() + .RegisterTrustedCertificates(typeof(Program).Assembly, [EmbeddedTrustCertResource], devCertPath); + } + protected override void ResetApp() { Logger?.LogDebug("Resetting Application..."); diff --git a/src/DevLauncher/Properties/launchSettings.json b/src/DevLauncher/Properties/launchSettings.json index e517f5d..cbe0893 100644 --- a/src/DevLauncher/Properties/launchSettings.json +++ b/src/DevLauncher/Properties/launchSettings.json @@ -5,11 +5,11 @@ "commandLineArgs": "--verboseBootstrapLogging -verbose", "workingDirectory": "C:\\Privat\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\\Mods\\republic-at-war" }, - "DAT2LocFile": { - "commandName": "Project", - "commandLineArgs": "initLoc --skipUpdate", - "workingDirectory": "C:\\Privat\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\\Mods\\republic-at-war" - }, + //"DAT2LocFile": { + // "commandName": "Project", + // "commandLineArgs": "initLoc --skipUpdate", + // "workingDirectory": "C:\\Privat\\Steam\\steamapps\\common\\Star Wars Empire at War\\corruption\\Mods\\republic-at-war" + //}, "Diff Localizations": { "commandName": "Project", "commandLineArgs": "prepareLoc --skipUpdate", diff --git a/src/DevLauncher/RawDevLauncher.cs b/src/DevLauncher/RawDevLauncher.cs index f74e6fb..fd7f3a1 100644 --- a/src/DevLauncher/RawDevLauncher.cs +++ b/src/DevLauncher/RawDevLauncher.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AnakinRaW.AppUpdaterFramework.Metadata.Product; namespace RepublicAtWar.DevLauncher; @@ -25,10 +26,25 @@ internal sealed class RawDevLauncher(UpdatableApplicationEnvironment application private readonly Parser _looseArgumentParser = new(c => { c.IgnoreUnknownArguments = true; }); private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(RawDevLauncher)); + // Verb name from ApplicationUpdateOptions in ModdingToolBase; kept as a literal to avoid + // coupling Program's finally block to a reference type just for this check. + internal const string UpdateApplicationVerb = "updateApplication"; + + internal static bool IsUpdateOnlyInvocation(IReadOnlyList args) + { + return args.Count > 0 && string.Equals(args[0], UpdateApplicationVerb, StringComparison.Ordinal); + } + public async Task RunAsync(IReadOnlyList args) { + if (IsUpdateOnlyInvocation(args)) + { + await UpdateLauncher(args).ConfigureAwait(false); + return 0; + } + var option = ParseArguments(args); - + if (option is null) return 0xA0; @@ -50,7 +66,16 @@ private async Task UpdateLauncher(IReadOnlyList args) var updater = new RawDevLauncherUpdater(applicationEnvironment, serviceProvider); var branchName = updater.GetBranchNameFromRegistry(options?.BranchName, true); - var branch = updater.CreateBranch(branchName, options?.ManifestUrl); + + ProductBranch branch; + if (options is not null) + { + branch = !string.IsNullOrEmpty(options.ServerUrl) + ? updater.CreateBranchFromServerUrl(options.ServerUrl!, branchName) + : updater.CreateBranch(branchName, options.ManifestUrl); + } + else + branch = updater.CreateBranch(branchName); await updater.AutoUpdateApplication(branch); } diff --git a/src/DevLauncher/Resources/Certs/anakinraw-trust.cer b/src/DevLauncher/Resources/Certs/anakinraw-trust.cer new file mode 100644 index 0000000..f6fcda4 Binary files /dev/null and b/src/DevLauncher/Resources/Certs/anakinraw-trust.cer differ diff --git a/src/MegCompile/MegCompile.csproj b/src/MegCompile/MegCompile.csproj index b319200..68bce03 100644 --- a/src/MegCompile/MegCompile.csproj +++ b/src/MegCompile/MegCompile.csproj @@ -16,18 +16,18 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/RawDevTools/RawDevTools.csproj b/src/RawDevTools/RawDevTools.csproj index b47feff..bfb86b4 100644 --- a/src/RawDevTools/RawDevTools.csproj +++ b/src/RawDevTools/RawDevTools.csproj @@ -43,13 +43,13 @@ - + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/TextCompile/TextCompile.csproj b/src/TextCompile/TextCompile.csproj index e060ac5..90c7ad5 100644 --- a/src/TextCompile/TextCompile.csproj +++ b/src/TextCompile/TextCompile.csproj @@ -15,18 +15,18 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/test-local-update-dual.ps1 b/test-local-update-dual.ps1 new file mode 100644 index 0000000..57ae3a8 --- /dev/null +++ b/test-local-update-dual.ps1 @@ -0,0 +1,39 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [string]$InstalledVersion = '0.0.1-local', + [string]$ServerVersion = '99.99.99-local', + [string]$Branch = 'beta', + [string]$CompatibilityUpdater +) + +$ErrorActionPreference = 'Stop' + +$root = $PSScriptRoot +if ([string]::IsNullOrEmpty($root)) { $root = Get-Location } + +$deployArgs = @{ + InstalledVersion = $InstalledVersion + ServerVersion = $ServerVersion + DualPublish = $true +} +if ($CompatibilityUpdater) { $deployArgs.CompatibilityUpdater = $CompatibilityUpdater } + +& (Join-Path $root 'deploy-local.ps1') @deployArgs +if ($LASTEXITCODE -ne 0) { throw "deploy-local.ps1 -DualPublish failed (exit $LASTEXITCODE)." } + +$nextServerDir = Join-Path $root '.local_deploy\server\v2' +if (-not (Test-Path $nextServerDir)) { + throw "Expected /v2/ server dir at '$nextServerDir' but it does not exist." +} +$nextServerUri = "file:///$(((Resolve-Path $nextServerDir).Path -replace '\\','/'))" + +& (Join-Path $root 'ModdingToolBase\scripts\Test-LocalUpdateCycle.ps1') ` + -AppExePath (Join-Path $root '.local_deploy\install\RaW-DevLauncher.exe') ` + -ServerUri $nextServerUri ` + -Branch $Branch ` + -NoUpdateMessage 'No update available.' ` + -ExpectedNewVersion $ServerVersion + +exit $LASTEXITCODE \ No newline at end of file diff --git a/test-local-update.ps1 b/test-local-update.ps1 new file mode 100644 index 0000000..5a5f69e --- /dev/null +++ b/test-local-update.ps1 @@ -0,0 +1,37 @@ +# ========================================================================================= +# Local bootstrap for the self-update integration test. +# +# Stages a local deploy via deploy-local.ps1, then runs the shared end-to-end test from +# ModdingToolBase against the staged install dir + signed local server. +# +# Windows-only. +# ========================================================================================= + +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [string]$InstalledVersion = '0.0.1-local', + [string]$ServerVersion = '99.99.99-local', + [string]$Branch = 'beta' +) + +$ErrorActionPreference = 'Stop' + +$root = $PSScriptRoot +if ([string]::IsNullOrEmpty($root)) { $root = Get-Location } + +& (Join-Path $root 'deploy-local.ps1') -InstalledVersion $InstalledVersion -ServerVersion $ServerVersion +if ($LASTEXITCODE -ne 0) { throw "deploy-local.ps1 failed (exit $LASTEXITCODE)." } + +$serverDir = Join-Path $root '.local_deploy\server' +$serverUri = "file:///$(((Resolve-Path $serverDir).Path -replace '\\','/'))" + +& (Join-Path $root 'ModdingToolBase\scripts\Test-LocalUpdateCycle.ps1') ` + -AppExePath (Join-Path $root '.local_deploy\install\RaW-DevLauncher.exe') ` + -ServerUri $serverUri ` + -Branch $Branch ` + -NoUpdateMessage 'No update available.' ` + -ExpectedNewVersion $ServerVersion + +exit $LASTEXITCODE \ No newline at end of file diff --git a/tools/v1/AnakinRaW.ExternalUpdater.exe b/tools/v1/AnakinRaW.ExternalUpdater.exe new file mode 100644 index 0000000..c03ac56 Binary files /dev/null and b/tools/v1/AnakinRaW.ExternalUpdater.exe differ diff --git a/version.json b/version.json index c3a55c9..0157ac1 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.0", + "version": "3.0", "publicReleaseRefSpec": [ "^refs/heads/main$" ],