Skip to content

Add a user-friendly OpenKH launcher - #1271

Open
Yokimitsuro wants to merge 5 commits into
OpenKH:masterfrom
Yokimitsuro:feature/user-friendly-experience
Open

Add a user-friendly OpenKH launcher#1271
Yokimitsuro wants to merge 5 commits into
OpenKH:masterfrom
Yokimitsuro:feature/user-friendly-experience

Conversation

@Yokimitsuro

@Yokimitsuro Yokimitsuro commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a dedicated WPF launcher as the main entry point for OpenKH.
  • Keeps Mod Manager unchanged and presents it as the recommended option for players.
  • Provides a searchable catalog for editors, viewers, map studios, and other creator tools.
  • Organizes releases into a clean root with Mod Manager dependencies under Apps\ModManager and specialist tools under AdvancedTools.
  • Automatically migrates existing flat installations when the updated launcher or an existing Mod Manager shortcut is opened.
  • Preserves Mod Manager settings, presets, mod lists, mods, and other user content while removing obsolete application files.
  • Keeps a permanent hidden compatibility entry point so existing Mod Manager shortcuts continue to work.
  • Checks for update availability silently when the launcher opens and displays Update Available when a new release exists.
  • Downloads and installs an update only after the user selects the update action and confirms it.
  • Applies updates from the launcher and restarts the launcher without opening the Mod Manager.
  • Adds a launcher action to create a Mod Manager desktop shortcut.
  • Uses the organized layout directly for subsequent updates.
  • Performs the migration in place without creating a backup.

Screenshots

Launcher

OpenKH Launcher home

Modding tools

OpenKH modding tools

Validation

  • Published a complete Windows release with .NET SDK 8.0.416.
  • Confirmed the release root contains no DLL files, one visible executable, and the hidden compatibility entry point.
  • Confirmed the launcher discovers 28 graphical modding tools.
  • Simulated migration through an existing Mod Manager shortcut and confirmed obsolete binaries and resource directories are removed.
  • Simulated migration by opening the new launcher directly over a legacy installation.
  • Confirmed both migration paths preserve user data and keep the compatibility executable hidden and functional.
  • Confirmed the launcher detects an available update automatically without downloading it or starting the Mod Manager.
  • Confirmed the update action runs in the launcher and starts downloading only after user interaction.
  • Verified the update ZIP contains the launcher, compatibility entry point, nested Mod Manager, and advanced tools.
  • Built both the launcher and Mod Manager in Release configuration.
  • Ran OpenKh.Tests.ModsManager: 13 tests passed.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds a Windows WPF launcher, installation-aware update services, legacy installation migration, desktop shortcut creation, and a release packaging workflow. The workflow validates and packages the launcher, Mod Manager, Panacea files, documentation, and advanced tools.

Changes

Launcher release integration

Layer / File(s) Summary
Installation and update service contracts
OpenKh.Tools.ModsManager/Services/*, OpenKh.Tools.ModsManager/App.xaml.cs, OpenKh.Tests.ModsManager/*
Installation paths, configuration storage, crash logs, release metadata, and update restart behavior now use the packaged installation directory. Tests cover installation-root resolution.
Launcher application and project wiring
OpenKh.Tools.Launcher/*, OpenKh.sln
Added the WPF launcher, its interface, tool discovery, Mod Manager actions, shortcut actions, update handling, project configuration, and solution mappings.
Legacy installation migration and shortcuts
OpenKh.Tools.Launcher/LegacyInstallationMigration.cs, OpenKh.Tools.Launcher/DesktopShortcutService.cs
The launcher starts the packaged Mod Manager for legacy launches, forwards arguments, schedules cleanup, and creates desktop shortcuts.
Release packaging and documentation
.github/scripts/prepare-release.ps1, .github/workflows/dotnet.yml, .gitignore, README.md, distribution/README-FIRST.txt
The release workflow organizes published binaries, copies required runtime files and documentation, validates archive entries, and documents the release layout and migration behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Launcher
  participant ModManager
  participant UpdateService
  participant ReleaseArchive

  User->>Launcher: Start launcher
  Launcher->>ModManager: Start packaged Mod Manager
  Launcher->>UpdateService: Check and install update
  UpdateService->>Launcher: Report progress and restart target
  ReleaseArchive->>User: Provide organized release package
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding a user-friendly OpenKH launcher.
Description check ✅ Passed The description directly explains the launcher, migration, update, packaging, shortcut, and validation changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@osdanova

osdanova commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Looks good to me

@Yokimitsuro

Copy link
Copy Markdown
Contributor Author

Addressed the update and shortcut concerns in fab19110:

  • The root OpenKh.Tools.ModsManager.exe is now kept as a permanent hidden compatibility entry point, so existing desktop shortcuts continue to work.
  • Legacy cleanup runs whether the user opens that existing shortcut or opens the new launcher directly after updating.
  • The cleanup uses the release manifest and preserves settings, presets, mods, and other user data.
  • The launcher now has a Check for Updates action that delegates to the existing Mod Manager update workflow.
  • The launcher now has a Create Desktop Shortcut action targeting the stable compatibility entry point.
  • Subsequent updates keep the organized layout and retain shortcut compatibility.

I validated both legacy migration paths, the organized update archive, a complete Release build, and the Mod Manager test suite.

@Yokimitsuro

Copy link
Copy Markdown
Contributor Author

Follow-up update in 2df76552:

  • Update availability is now checked silently when the launcher opens.
  • The footer changes to Update Available when a new release is found.
  • Nothing is downloaded or installed automatically.
  • Selecting the update action shows the version and asks for confirmation.
  • The launcher handles the download and installation itself, then restarts the launcher.
  • The Mod Manager is not opened by the launcher update flow.

The new behavior was validated in the packaged Release build. Automatic detection showed the indicator with no download and zero Mod Manager processes started.

@Yokimitsuro

Copy link
Copy Markdown
Contributor Author

Final CI status for 2df76552:

  • build: passed, including the full test suite, Panacea build, organized release packaging, ZIP creation, and update archive validation.
  • Analyze (csharp): passed.
  • CodeQL: passed.

The PR is now green.

@kenjiuno

kenjiuno commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Hi

Thanks for your contribution to OpenKh.

I have tested the new OpenKh.Launcher.exe you have developed.

Currently, I cannot reproduce the new folder structure of openkh.zip that this pull request introduces.

So I have tested the behaviour of OpenKh.Launcher.exe using the following PowerShell commands:

PS V:\OpenKh-review-1271> .\pre-build.ps1
PS V:\OpenKh-review-1271> .\build.ps1
PS V:\OpenKh-review-1271> cd .\bin\
PS V:\OpenKh-review-1271\bin> .\OpenKh.Launcher.exe
2026-08-05_17h37_07

Open Mod Manager works.
Create Desktop Shortcut works.
Browse Modding Tools works.

2026-08-05_17h38_21

The list is empty.
Probably it is due to the unexpected folder structure I used.
However, this is clearly stated in the dialog, and so I don't see this as a concern.

And the problem is that when I close the launcher, the majority of OpenKh files are removed.

before closing

before

after closing

2026-08-05_17h43_58

I feel that this behavior is neither clearly documented nor easily predictable, and it feels kind of dangerous.
Some users may change the folder structure before they launch OpenKh.Launcher.exe.
Is it possible to add a safeguard for this feature?

And also, please allow me to turn on the coderabbitai review

@kenjiuno

kenjiuno commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
OpenKh.Tools.Launcher/MainWindow.xaml.cs (1)

43-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse OpenkhInstallation instead of duplicating the path resolution.

Lines 48-57 repeat the logic of OpenkhInstallation.GetModManagerExecutable. The launcher already compiles OpenkhInstallation.cs (csproj line 28), and line 189 already calls OpenkhInstallation.Directory. Two copies can drift when the packaged layout changes.

♻️ Proposed refactor
-    private string BaseDirectory => AppContext.BaseDirectory;
-    private string ModManagerPath
-    {
-        get
-        {
-            var packagedPath = Path.Combine(
-                BaseDirectory,
-                ApplicationsDirectory,
-                ModManagerDirectory,
-                ModManagerExecutable
-            );
-
-            return File.Exists(packagedPath)
-                ? packagedPath
-                : Path.Combine(BaseDirectory, ModManagerExecutable);
-        }
-    }
+    private string BaseDirectory => OpenkhInstallation.Directory;
+    private string ModManagerPath => OpenkhInstallation.GetModManagerExecutable(BaseDirectory);
     private string AdvancedToolsPath => Path.Combine(BaseDirectory, AdvancedToolsDirectory);
     private string CompatibilityModManagerPath => Path.Combine(BaseDirectory, ModManagerExecutable);

The constants ApplicationsDirectory and ModManagerDirectory at lines 17-18 then become unused and can be removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.Launcher/MainWindow.xaml.cs` around lines 43 - 61, Replace the
duplicated path resolution in the ModManagerPath property with the existing
OpenkhInstallation.GetModManagerExecutable logic, preserving the launcher’s
resolved executable behavior. Remove ApplicationsDirectory and
ModManagerDirectory if they become unused, and continue using
OpenkhInstallation.Directory where applicable.
OpenKh.Tools.Launcher/OpenKh.Tools.Launcher.csproj (1)

23-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the shared update services into a library instead of linking source.

Lines 28-30 compile three ModsManager source files into the launcher assembly, creating independently compiled OpenKh.Tools.ModsManager.Services.* types in each executable. If update state is later shared between assemblies, source linking will not share the same type identity. A small shared library referenced by both projects removes this duplication.

Add support for Nullable in the ModsManager services if the shared types leave external contract members nullable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.Launcher/OpenKh.Tools.Launcher.csproj` around lines 23 - 31,
Extract the shared update service types OpenkhInstallation,
OpenkhUpdateCheckerService, and OpenkhUpdateProceederService into a small
library referenced by both OpenKh.Tools.Launcher and OpenKh.Tools.ModsManager,
then remove the linked Compile entries from the launcher project. Enable
nullable support in the shared library if any externally visible service
contract members are nullable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@OpenKh.Tools.Launcher/App.xaml.cs`:
- Around line 11-18: Update OnStartup to schedule migration cleanup only when
the legacy layout exists and Apps/ModManager, AdvancedTools, and openkh-release
are absent. Wrap TryStartModManager and migration cleanup in exception handling
that records errors, while ensuring failures do not prevent MainWindow from
being created and shown. Preserve the existing early shutdown only when
TryStartModManager successfully starts the manager.

In `@OpenKh.Tools.Launcher/LegacyInstallationMigration.cs`:
- Around line 100-108: Update ScheduleCleanupIfNeeded to require a persisted
completed-migration marker and verified legacy-layout state before scheduling
cleanup. Restrict the collected legacyFiles and legacyDirectories to artifacts
confirmed as created by the old package, rather than deleting every matching
manifest or fallback path; otherwise skip cleanup entirely.

In `@OpenKh.Tools.Launcher/MainWindow.xaml.cs`:
- Around line 189-196: Update the launcherPath initialization in the update flow
to use Environment.ProcessPath instead of constructing the executable name with
the hardcoded "OpenKh.Launcher.exe" literal. Pass this running-process path
unchanged to OpenkhUpdateProceederService.UpdateAsync so both termination and
restart target the current launcher.

In `@OpenKh.Tools.ModsManager/Services/OpenkhUpdateProceederService.cs`:
- Around line 56-67: Update the OpenkhUpdateProceederService restart flow and
CreateBatchFileAsync invocation to terminate both the restartExecutable process
and the Mod Manager process before copying files. Preserve the existing restart
target while adding the executable name resolved by modManagerExecutable to the
batch generator’s process-stop list.

---

Nitpick comments:
In `@OpenKh.Tools.Launcher/MainWindow.xaml.cs`:
- Around line 43-61: Replace the duplicated path resolution in the
ModManagerPath property with the existing
OpenkhInstallation.GetModManagerExecutable logic, preserving the launcher’s
resolved executable behavior. Remove ApplicationsDirectory and
ModManagerDirectory if they become unused, and continue using
OpenkhInstallation.Directory where applicable.

In `@OpenKh.Tools.Launcher/OpenKh.Tools.Launcher.csproj`:
- Around line 23-31: Extract the shared update service types OpenkhInstallation,
OpenkhUpdateCheckerService, and OpenkhUpdateProceederService into a small
library referenced by both OpenKh.Tools.Launcher and OpenKh.Tools.ModsManager,
then remove the linked Compile entries from the launcher project. Enable
nullable support in the shared library if any externally visible service
contract members are nullable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f4ec05b-5fd4-4fd6-8abc-ec6fdc9cb5d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5153c67 and 2df7655.

⛔ Files ignored due to path filters (3)
  • images/openKH_Old.ico is excluded by !**/*.ico
  • images/openkh-launcher-home.png is excluded by !**/*.png
  • images/openkh-launcher-tools.png is excluded by !**/*.png
📒 Files selected for processing (19)
  • .github/scripts/prepare-release.ps1
  • .github/workflows/dotnet.yml
  • .gitignore
  • OpenKh.Tests.ModsManager/OpenkhInstallationTest.cs
  • OpenKh.Tools.Launcher/App.xaml
  • OpenKh.Tools.Launcher/App.xaml.cs
  • OpenKh.Tools.Launcher/DesktopShortcutService.cs
  • OpenKh.Tools.Launcher/LegacyInstallationMigration.cs
  • OpenKh.Tools.Launcher/MainWindow.xaml
  • OpenKh.Tools.Launcher/MainWindow.xaml.cs
  • OpenKh.Tools.Launcher/OpenKh.Tools.Launcher.csproj
  • OpenKh.Tools.ModsManager/App.xaml.cs
  • OpenKh.Tools.ModsManager/Services/ConfigurationService.cs
  • OpenKh.Tools.ModsManager/Services/OpenkhInstallation.cs
  • OpenKh.Tools.ModsManager/Services/OpenkhUpdateCheckerService.cs
  • OpenKh.Tools.ModsManager/Services/OpenkhUpdateProceederService.cs
  • OpenKh.sln
  • README.md
  • distribution/README-FIRST.txt

Comment on lines +11 to +18
if (LegacyInstallationMigration.TryStartModManager())
{
Shutdown();
return;
}

LegacyInstallationMigration.ScheduleCleanupIfNeeded();
new MainWindow().Show();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the migration and cleanup logic that is not included in this review context.
set -euo pipefail

fd -t f 'LegacyInstallationMigration.cs' --exec cat -n {}
fd -t f 'DesktopShortcutService.cs' --exec cat -n {}
rg -n -C3 'Directory\.Delete|File\.Delete|rd /s|del /' --iglob '*.cs' --iglob '*.ps1'

Repository: OpenKH/OpenKh

Length of output: 11456


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)LegacyInstallationMigration.cs$|(^|/)App\.xaml\.cs$|legacy-release-(files|directories)\.txt$|OpenKh\.Tools\.ModsManager\.exe$'

echo
echo "App.xaml.cs:"
fd -t f 'App.xaml.cs' --exec cat -n {}

echo
echo "Migration manifests:"
while IFS= read -r file; do
  echo "--- ${file#"$PWD/"}"
  cat -n "$file" | sed -n '1,220p'
done < <(fd -t f 'legacy-release-(files|directories)\.txt')

echo
echo "Search file deletions/scripts:"
rg -n -C3 'Directory\.Delete|File\.Delete|rm -rf|rmdir /s|rd /s|del /f /q|Remove-Item|legacy-release-(files|directories)' --iglob '*.cs' --iglob '*.ps1' --iglob '*.bat' --iglob '*.txt'

Repository: OpenKH/OpenKh

Length of output: 13616


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Launcher App.xaml.cs:"
cat -n OpenKh.Tools.Launcher/App.xaml.cs | sed -n '1,40p'

echo
echo "LegacyInstallationMigration.cs:"
cat -n OpenKh.Tools.Launcher/LegacyInstallationMigration.cs | sed -n '1,80p;80,220p'

echo
echo "Manifest files:"
find . -name 'legacy-release-files.txt' -o -name 'legacy-release-directories.txt' | while IFS= read -r file; do
  echo "--- ${file#"$PWD/"}"
  cat -n "$file" | sed -n '1,260p'
done

echo
echo "Delete/delete-related references:"
rg -n -C2 'Directory\.Delete|File\.Delete|rmdir /s /q|del /f /q|Remove-Item|rd /s /q|legacy-release-(files|directories)' --iglob '*.cs' --iglob '*.ps1' --iglob '*.bat' --iglob '*.txt'

Repository: OpenKH/OpenKh

Length of output: 9568


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Manifest files:"
find . -type f \( -name 'legacy-release-files.txt' -o -name 'legacy-release-directories.txt' \) -print | while IFS= read -r file; do
  echo "--- ${file#"$PWD/"}"
  wc -l "$file"
  cat -n "$file" | sed -n '1,280p'
done

echo
echo "Delete-related references:"
rg -n -C2 'Directory\.Delete|File\.Delete|rmdir /s /q|rd /s /q|del /f /q|Remove-Item|legacy-release-(files|directories)' --iglob '*.cs' --iglob '*.ps1' --iglob '*.bat' --iglob '*.txt' --iglob '*.md'

echo
echo "Relevant files in OpenKh.Tools.Launcher:"
git ls-files OpenKh.Tools.Launcher | sort

Repository: OpenKH/OpenKh

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Manifest files:"
git ls-files | rg 'legacy-release-(files|directories)\.txt$' || true
find . -type f \( -name 'legacy-release-files.txt' -o -name 'legacy-release-directories.txt' \) -print || true
while IFS= read -r file; do
  echo "--- ${file#"$PWD/"}"
  wc -l "$file" || true
  sed -n '1,320p' "$file" || true
done < <(git ls-files | rg 'legacy-release-(files|directories)\.txt$' || true)

echo
echo "Delete-related references in cs/txt/ps1/bat:"
git ls-files | rg '\.(cs|txt|ps1|bat|md)$' | while IFS= read -r file; do
  rg -n -C2 'Directory\.Delete|File\.Delete|rmdir /s /q|rd /s /q|del /f /q|Remove-Item|legacy-release-(files|directories)' "$file" || true
done

echo
echo "Relevant launcher files:"
git ls-files OpenKh.Tools.Launcher | sort

Repository: OpenKH/OpenKh

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Manifest files from git:"
git ls-files | rg 'legacy-release-(files|directories)\.txt$' || true

echo "Manifest files from find:"
find . -type f \( -name 'legacy-release-files.txt' -o -name 'legacy-release-directories.txt' \) -print || true

echo "Read manifest files:"
git ls-files | rg 'legacy-release-(files|directories)\.txt$' | while IFS= read -r file; do
  echo "--- ${file#"$PWD/"}"
  wc -l "$file"
  sed -n '1,320p' "$file"
done

echo
echo "Delete-related refs in tracked text/coding files:"
git ls-files | rg '\.(cs|txt|ps1|bat|md)$' | while IFS= read -r file; do
  rg -n -C2 'Directory\.Delete|File\.Delete|rmdir /s /q|rd /s /q|del /f /q|Remove-Item|legacy-release-(files|directories)' "$file" || true
done

echo
echo "Launcher file list:"
git ls-files OpenKh.Tools.Launcher | sort

Repository: OpenKH/OpenKh

Length of output: 223


Guard startup cleanup and keep the launcher launchable.

  1. ScheduleCleanupIfNeeded() is called unconditionally and can generate a migration batch that deletes top-level files matching the legacy file selector, including .dll files, and the FallbackLegacyResourceDirectories before the launcher has checked the new packaged layout. Add a guard that runs cleanup only when the legacy layout is present and newer paths like Apps/ModManager, AdvancedTools, or openkh-release are absent.
  2. If TryStartModManager() throws outside the existing Process.Start() block, or if later startup throws, OnStartup exits before MainWindow opens. Catch migration-related exceptions, record an error, and continue to create MainWindow.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.Launcher/App.xaml.cs` around lines 11 - 18, Update OnStartup to
schedule migration cleanup only when the legacy layout exists and
Apps/ModManager, AdvancedTools, and openkh-release are absent. Wrap
TryStartModManager and migration cleanup in exception handling that records
errors, while ensuring failures do not prevent MainWindow from being created and
shown. Preserve the existing early shutdown only when TryStartModManager
successfully starts the manager.

Comment on lines +100 to +108
private static void ScheduleCleanupIfNeeded(string installationDirectory)
{
var legacyFiles = GetLegacyApplicationFiles(installationDirectory)
.Where(File.Exists)
.ToArray();
var legacyDirectories = GetLegacyResourceDirectories(installationDirectory)
.Select(directoryName => Path.Combine(installationDirectory, directoryName))
.Where(Directory.Exists)
.ToArray();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Require a verified migration before cleanup.

ScheduleCleanupIfNeeded() runs during normal launcher startup. This code deletes every root directory named by the manifest, or by the fallback list such as resources and runtimes. A user can lose files after moving content into one of these directories before closing the launcher.

Persist a completed-migration marker. Delete files only after verifying the legacy layout. Restrict deletion to artifacts that the old package created.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.Launcher/LegacyInstallationMigration.cs` around lines 100 - 108,
Update ScheduleCleanupIfNeeded to require a persisted completed-migration marker
and verified legacy-layout state before scheduling cleanup. Restrict the
collected legacyFiles and legacyDirectories to artifacts confirmed as created by
the old package, rather than deleting every matching manifest or fallback path;
otherwise skip cleanup entirely.

Comment on lines +189 to +196
var launcherPath = Path.Combine(OpenkhInstallation.Directory, "OpenKh.Launcher.exe");
await new OpenkhUpdateProceederService().UpdateAsync(
checkResult.DownloadZipUrl,
rate => Dispatcher.Invoke(() =>
CheckForUpdatesButton.Content = $"Downloading {rate:P0}"),
CancellationToken.None,
launcherPath
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive the restart path from the running process.

Line 189 hardcodes "OpenKh.Launcher.exe". That literal duplicates <AssemblyName> in OpenKh.Tools.Launcher.csproj. If the assembly name changes, the batch file restarts a path that does not exist, and the user is left with no running application after the update. The update service also derives the process to terminate from this same value, so both actions break together.

Use Environment.ProcessPath, which always names the running launcher.

🐛 Proposed fix
             CheckForUpdatesButton.Content = "Downloading Update...";
-            var launcherPath = Path.Combine(OpenkhInstallation.Directory, "OpenKh.Launcher.exe");
+            var launcherPath = Environment.ProcessPath
+                ?? Path.Combine(OpenkhInstallation.Directory, "OpenKh.Launcher.exe");
             await new OpenkhUpdateProceederService().UpdateAsync(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var launcherPath = Path.Combine(OpenkhInstallation.Directory, "OpenKh.Launcher.exe");
await new OpenkhUpdateProceederService().UpdateAsync(
checkResult.DownloadZipUrl,
rate => Dispatcher.Invoke(() =>
CheckForUpdatesButton.Content = $"Downloading {rate:P0}"),
CancellationToken.None,
launcherPath
);
CheckForUpdatesButton.Content = "Downloading Update...";
var launcherPath = Environment.ProcessPath
?? Path.Combine(OpenkhInstallation.Directory, "OpenKh.Launcher.exe");
await new OpenkhUpdateProceederService().UpdateAsync(
checkResult.DownloadZipUrl,
rate => Dispatcher.Invoke(() =>
CheckForUpdatesButton.Content = $"Downloading {rate:P0}"),
CancellationToken.None,
launcherPath
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.Launcher/MainWindow.xaml.cs` around lines 189 - 196, Update the
launcherPath initialization in the update flow to use Environment.ProcessPath
instead of constructing the executable name with the hardcoded
"OpenKh.Launcher.exe" literal. Pass this running-process path unchanged to
OpenkhUpdateProceederService.UpdateAsync so both termination and restart target
the current launcher.

Comment on lines +56 to +67
var modManagerExecutable = File.Exists(packagedModManagerExecutable)
? Path.Combine(copyTo, "Apps", "ModManager", "OpenKh.Tools.ModsManager.exe")
: OpenkhInstallation.GetModManagerExecutable(copyTo);
var restartExecutable = string.IsNullOrWhiteSpace(executableToRestart)
? modManagerExecutable
: executableToRestart;
await CreateBatchFileAsync(
tempBatFile: tempBatFile,
copyFrom: copyFrom,
copyTo: copyTo,
processToStop: Path.GetFileName(restartExecutable),
execAfter: $"start \"\" \"{restartExecutable}\""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Terminate the Mod Manager as well as the restart executable.

Line 66 stops only the process named by restartExecutable. When the launcher starts the update, that name is OpenKh.Launcher.exe. A Mod Manager instance started earlier from the launcher (MainWindow.xaml.cs line 150) keeps running and holds locks on Apps/ModManager. Robocopy then fails to replace those binaries, if errorlevel 8 pause blocks the console, and the installation ends in a mixed-version state.

Pass both process names to the batch generator, or always add the Mod Manager process to the terminate list.

🛠️ Proposed fix to stop both processes
-            await CreateBatchFileAsync(
-                tempBatFile: tempBatFile,
-                copyFrom: copyFrom,
-                copyTo: copyTo,
-                processToStop: Path.GetFileName(restartExecutable),
-                execAfter: $"start \"\" \"{restartExecutable}\""
-            );
+            var processesToStop = new[]
+                {
+                    Path.GetFileName(restartExecutable),
+                    Path.GetFileName(modManagerExecutable),
+                }
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .ToArray();
+
+            await CreateBatchFileAsync(
+                tempBatFile: tempBatFile,
+                copyFrom: copyFrom,
+                copyTo: copyTo,
+                processesToStop: processesToStop,
+                execAfter: $"start \"\" \"{restartExecutable}\""
+            );

Update the generator accordingly:

         private async Task CreateBatchFileAsync(
             string tempBatFile,
             string copyFrom,
             string copyTo,
-            string processToStop,
+            IReadOnlyCollection<string> processesToStop,
             string execAfter
         )
         {
             var bat = new StringWriter();
             bat.WriteLine($"chcp 65001");
-            bat.WriteLine($"taskkill /im {EscapeRobocopyArg(processToStop)}");
+            foreach (var processToStop in processesToStop)
+                bat.WriteLine($"taskkill /im {EscapeRobocopyArg(processToStop)}");
             bat.WriteLine($"robocopy  {EscapeRobocopyArg(copyFrom)} {EscapeRobocopyArg(copyTo)} /e");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var modManagerExecutable = File.Exists(packagedModManagerExecutable)
? Path.Combine(copyTo, "Apps", "ModManager", "OpenKh.Tools.ModsManager.exe")
: OpenkhInstallation.GetModManagerExecutable(copyTo);
var restartExecutable = string.IsNullOrWhiteSpace(executableToRestart)
? modManagerExecutable
: executableToRestart;
await CreateBatchFileAsync(
tempBatFile: tempBatFile,
copyFrom: copyFrom,
copyTo: copyTo,
processToStop: Path.GetFileName(restartExecutable),
execAfter: $"start \"\" \"{restartExecutable}\""
var modManagerExecutable = File.Exists(packagedModManagerExecutable)
? Path.Combine(copyTo, "Apps", "ModManager", "OpenKh.Tools.ModsManager.exe")
: OpenkhInstallation.GetModManagerExecutable(copyTo);
var restartExecutable = string.IsNullOrWhiteSpace(executableToRestart)
? modManagerExecutable
: executableToRestart;
var processesToStop = new[]
{
Path.GetFileName(restartExecutable),
Path.GetFileName(modManagerExecutable),
}
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
await CreateBatchFileAsync(
tempBatFile: tempBatFile,
copyFrom: copyFrom,
copyTo: copyTo,
processesToStop: processesToStop,
execAfter: $"start \"\" \"{restartExecutable}\""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OpenKh.Tools.ModsManager/Services/OpenkhUpdateProceederService.cs` around
lines 56 - 67, Update the OpenkhUpdateProceederService restart flow and
CreateBatchFileAsync invocation to terminate both the restartExecutable process
and the Mod Manager process before copying files. Preserve the existing restart
target while adding the executable name resolved by modManagerExecutable to the
batch generator’s process-stop list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants