From fee0f77971ab414b9d4cad3c305823b26f4a1da2 Mon Sep 17 00:00:00 2001 From: UlisseWolf <88635118+UlisseWolf@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:57:59 +0200 Subject: [PATCH] Update 2.0 --- .github/workflows/ci.yml | 33 ++ .github/workflows/release.yml | 178 ++++++++ App.axaml | 111 +++++ App.axaml.cs | 23 + App.xaml | 6 - App.xaml.cs | 7 - Dialogs/MessageDialog.axaml | 20 + Dialogs/MessageDialog.axaml.cs | 60 +++ LogEntryView.cs | 4 +- MainWindow.axaml | 136 ++++++ MainWindow.axaml.cs | 609 +++++++++++++++++++++++++++ MainWindow.xaml | 312 -------------- MainWindow.xaml.cs | 442 ------------------- Models/LogColor.cs | 2 +- Models/Sc4pacExplicitPackages.cs | 15 + Models/TgiKey.cs | 6 +- Program.cs | 20 + README.md | 135 +++++- SC4ModMigrationAssistant.csproj | 31 +- Services/CatalogDatabaseService.cs | 11 +- Services/DbpfScanService.cs | 35 +- Services/Sc4pacLookupService.cs | 25 +- Services/Sc4pacPluginsFileService.cs | 157 +++++++ 23 files changed, 1562 insertions(+), 816 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 App.axaml create mode 100644 App.axaml.cs delete mode 100644 App.xaml delete mode 100644 App.xaml.cs create mode 100644 Dialogs/MessageDialog.axaml create mode 100644 Dialogs/MessageDialog.axaml.cs create mode 100644 MainWindow.axaml create mode 100644 MainWindow.axaml.cs delete mode 100644 MainWindow.xaml delete mode 100644 MainWindow.xaml.cs create mode 100644 Models/Sc4pacExplicitPackages.cs create mode 100644 Program.cs create mode 100644 Services/Sc4pacPluginsFileService.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f28b299 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Restore + run: dotnet restore SC4ModMigrationAssistant.csproj + + - name: Build + run: dotnet build SC4ModMigrationAssistant.csproj -c Release --no-restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..470c623 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,178 @@ +name: Release + +on: + push: + branches: [master] + tags: ["v*.*.*"] + +permissions: + contents: write + +jobs: + prepare: + name: Prepare version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set.outputs.version }} + file_version: ${{ steps.set.outputs.file_version }} + release_tag: ${{ steps.set.outputs.release_tag }} + prerelease: ${{ steps.set.outputs.prerelease }} + + steps: + - name: Determine version and release tag + id: set + shell: bash + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + TAG="${{ github.ref_name }}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Release tags must match vMAJOR.MINOR.PATCH, for example v0.4.0. Got: $TAG" + exit 1 + fi + echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + echo "file_version=${TAG#v}" >> "$GITHUB_OUTPUT" + echo "release_tag=$TAG" >> "$GITHUB_OUTPUT" + echo "prerelease=false" >> "$GITHUB_OUTPUT" + else + SHORT_SHA="${GITHUB_SHA:0:7}" + echo "version=0.0.0-dev.${{ github.run_number }}+$SHORT_SHA" >> "$GITHUB_OUTPUT" + echo "file_version=0.0.0-dev.${{ github.run_number }}-$SHORT_SHA" >> "$GITHUB_OUTPUT" + echo "release_tag=latest" >> "$GITHUB_OUTPUT" + echo "prerelease=true" >> "$GITHUB_OUTPUT" + fi + + package: + name: Package ${{ matrix.platform.name }} + runs-on: ${{ matrix.platform.os }} + needs: prepare + continue-on-error: ${{ matrix.platform.experimental }} + strategy: + fail-fast: false + matrix: + platform: + - name: Windows + os: windows-latest + rid: win-x64 + suffix: windows-x64 + experimental: false + - name: Linux + os: ubuntu-latest + rid: linux-x64 + suffix: linux-x64 + experimental: true + - name: macOS + os: macos-latest + rid: osx-arm64 + suffix: macos-arm64 + experimental: true + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Restore + run: dotnet restore SC4ModMigrationAssistant.csproj + + - name: Set archive name + shell: bash + run: echo "ARCHIVE=SC4ModMigrationAssistant-${{ needs.prepare.outputs.file_version }}-${{ matrix.platform.suffix }}.zip" >> "$GITHUB_ENV" + + - name: Publish + run: > + dotnet publish SC4ModMigrationAssistant.csproj -c Release + -r ${{ matrix.platform.rid }} + --self-contained true + -p:Version=${{ needs.prepare.outputs.version }} + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:IncludeAllContentForSelfExtract=true + -p:DebugType=None + -o publish/${{ matrix.platform.rid }} + + - name: Ad-hoc sign macOS executable + if: runner.os == 'macOS' + run: codesign --force --sign - publish/${{ matrix.platform.rid }}/SC4ModMigrationAssistant + + - name: Mark Unix executable bit + if: runner.os != 'Windows' + run: chmod +x publish/${{ matrix.platform.rid }}/SC4ModMigrationAssistant + + - name: Create Windows archive + if: runner.os == 'Windows' + shell: pwsh + run: Compress-Archive -Path publish/${{ matrix.platform.rid }}/* -DestinationPath $env:ARCHIVE + + - name: Create Unix archive + if: runner.os != 'Windows' + shell: bash + run: (cd publish/${{ matrix.platform.rid }} && zip -r -y "$OLDPWD/$ARCHIVE" .) + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ env.ARCHIVE }} + path: ${{ env.ARCHIVE }} + if-no-files-found: error + + publish: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: [prepare, package] + + steps: + - name: Download artifacts + uses: actions/download-artifact@v7 + with: + pattern: SC4ModMigrationAssistant-* + path: release-artifacts + merge-multiple: true + + - name: Write release notes + shell: bash + run: | + cat > release-notes.md << 'EOF' + ## Windows + + Unzip and run `SC4ModMigrationAssistant.exe`. Single self-contained executable, no + other files needed. + + ## Linux + + Unzip and run `./SC4ModMigrationAssistant` (executable bit already set in the + archive). Single self-contained executable, no other files needed. + + ## macOS + + Apple Silicon (arm64) only, ad-hoc signed and unnotarized. After unzipping, clear the + quarantine flag before launching: + + ```sh + xattr -rd com.apple.quarantine SC4ModMigrationAssistant + ``` + EOF + + - name: Replace rolling release + if: needs.prepare.outputs.release_tag == 'latest' + env: + GH_TOKEN: ${{ github.token }} + run: gh release delete latest --repo "${{ github.repository }}" --yes --cleanup-tag || true + + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + run: | + PRERELEASE_FLAG=() + if [[ "${{ needs.prepare.outputs.prerelease }}" == "true" ]]; then + PRERELEASE_FLAG=(--prerelease) + fi + gh release create "${{ needs.prepare.outputs.release_tag }}" release-artifacts/* \ + --repo "${{ github.repository }}" \ + --title "${{ needs.prepare.outputs.release_tag == 'latest' && 'Latest (master)' || needs.prepare.outputs.release_tag }}" \ + --notes-file release-notes.md \ + --target "${{ github.sha }}" \ + "${PRERELEASE_FLAG[@]}" diff --git a/App.axaml b/App.axaml new file mode 100644 index 0000000..0d7d893 --- /dev/null +++ b/App.axaml @@ -0,0 +1,111 @@ + + + + + + + #FF7B2FF7 + + #FF1B1330 + #FF241B3A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App.axaml.cs b/App.axaml.cs new file mode 100644 index 0000000..bf96e58 --- /dev/null +++ b/App.axaml.cs @@ -0,0 +1,23 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace SC4ModMigrationAssistant; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/App.xaml b/App.xaml deleted file mode 100644 index aba1568..0000000 --- a/App.xaml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/App.xaml.cs b/App.xaml.cs deleted file mode 100644 index af0da97..0000000 --- a/App.xaml.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Windows; - -namespace SC4ModMigrationAssistant; - -public partial class App : Application -{ -} diff --git a/Dialogs/MessageDialog.axaml b/Dialogs/MessageDialog.axaml new file mode 100644 index 0000000..cfec756 --- /dev/null +++ b/Dialogs/MessageDialog.axaml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/Dialogs/MessageDialog.axaml.cs b/Dialogs/MessageDialog.axaml.cs new file mode 100644 index 0000000..653a372 --- /dev/null +++ b/Dialogs/MessageDialog.axaml.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace SC4ModMigrationAssistant.Dialogs; + +/// +/// Minimal, self-contained modal dialog used in place of WPF's MessageBox, which +/// Avalonia does not provide out of the box. Supports a single "OK" button (informational / +/// error messages) or "Yes" / "No" buttons (confirmations). +/// +public partial class MessageDialog : Window +{ + public MessageDialog() + { + InitializeComponent(); + } + + /// Shows an informational/error/warning message with a single OK button. + public static Task ShowInfo(Window owner, string title, string message) => + ShowCore(owner, title, message, new[] { "OK" }); + + /// Shows a Yes/No confirmation; returns true if the user chose "Yes". + public static async Task ShowConfirm(Window owner, string title, string message) + { + string result = await ShowCore(owner, title, message, new[] { "Yes", "No" }); + return result == "Yes"; + } + + private static async Task ShowCore(Window owner, string title, string message, string[] buttonLabels) + { + var dialog = new MessageDialog { Title = title }; + dialog.MessageText.Text = message; + + var tcs = new TaskCompletionSource(); + bool resultSet = false; + + foreach (string label in buttonLabels) + { + var button = new Button { Content = label, MinWidth = 80 }; + button.Click += (_, _) => + { + resultSet = true; + tcs.TrySetResult(label); + dialog.Close(); + }; + dialog.ButtonPanel.Children.Add(button); + } + + dialog.Closed += (_, _) => + { + if (!resultSet) + { + tcs.TrySetResult(string.Empty); + } + }; + + await dialog.ShowDialog(owner); + return await tcs.Task; + } +} diff --git a/LogEntryView.cs b/LogEntryView.cs index 794ef38..ede0ab6 100644 --- a/LogEntryView.cs +++ b/LogEntryView.cs @@ -1,4 +1,4 @@ -using System.Windows.Media; +using Avalonia.Media; namespace SC4ModMigrationAssistant; @@ -9,5 +9,5 @@ namespace SC4ModMigrationAssistant; public sealed class LogEntryView { public required string Text { get; init; } - public required Brush Brush { get; init; } + public required IBrush Brush { get; init; } } diff --git a/MainWindow.axaml b/MainWindow.axaml new file mode 100644 index 0000000..b83471f --- /dev/null +++ b/MainWindow.axaml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + +