Skip to content

Add a user-friendly OpenKH launcher - #1271

Merged
kenjiuno merged 6 commits into
OpenKH:masterfrom
Yokimitsuro:feature/user-friendly-experience
Aug 14, 2026
Merged

Add a user-friendly OpenKH launcher#1271
kenjiuno merged 6 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.
  • Lets users mark modding tools as favorites, keeps favorites at the top, and preserves them between launcher sessions.
  • Stores Mod Manager, graphical tools, command-line programs, and their shared dependencies together under Apps.
  • Leaves the release root with one visible executable, documentation, user data, and a hidden compatibility entry point.
  • Automatically migrates existing flat installations when the updated launcher or an existing Mod Manager shortcut is opened.
  • Removes the previous AdvancedTools and Apps\ModManager directories when migrating an earlier preview layout.
  • Runs cleanup only after verifying that the organized launcher and Mod Manager installation is present.
  • Preserves Mod Manager settings, presets, mod lists, mods, favorites, and other user content while removing obsolete application files.
  • Keeps the 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.
  • Stops the launcher and Mod Manager before copying update files, then restarts the requested application.
  • Adds a launcher action to create a Mod Manager desktop shortcut.
  • Performs migration in place without creating a backup.

Screenshots

Launcher

OpenKH Launcher home

Modding tools and favorites

OpenKH modding tools

Validation

  • Published and validated a complete Windows release archive.
  • Confirmed the release root contains no DLL files, one visible executable, and the hidden compatibility entry point.
  • Confirmed Apps contains the Mod Manager, 28 graphical modding tools, command-line programs, and shared dependencies.
  • Confirmed the archive contains neither AdvancedTools nor the previous Apps\ModManager directory.
  • Launched the launcher, Mod Manager, and Bar Editor from the packaged layout.
  • Marked a tool as a favorite, confirmed it moved to the top, and confirmed it remained there after restarting the launcher.
  • Simulated migration through the launcher and confirmed legacy root files and previous application directories are removed.
  • Confirmed migration preserves the current Apps\OpenKh.Tools.ModsManager.exe and user files.
  • 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.
  • Built the launcher in Release configuration with no errors.
  • Ran OpenKh.Tests.ModsManager: 16 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
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.
Title check ✅ Passed The title clearly summarizes the primary change: adding a user-friendly OpenKH launcher.
Description check ✅ Passed The description directly explains the launcher, migration, packaging, update, shortcut, testing, and validation changes.

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.

shananas
shananas previously approved these changes Aug 7, 2026

@shananas shananas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Didnt review in great detail all the xaml stuff. From what i can tell looks good.

Question I did mention in discord why is mod manager in its own folder. From my testing all the created files/folder from mod manager are in the base folder. Which is still much a cleaner base folder and imo should stay there (idk if you were planning on changing that in the future or anything) If this is the case to keep them there why not just have one subfolder with all tools mods manager included. any standard user is going to either use the launcher or shortcut anyways. Mods manager does refrence some other programs in the toolkit for some of its features and im sure its all linked properly I just dont see any reason for not jsut having 1 subfolder with everything (at least with the PR as it is from a few suggested changes already).

My only real request then just have all apps in 1 subfolder and keep the base fodler as it is currently (Launcher, Mods Manager compate exe and the created files and folders from mods manager)

As for my discord comment about some files possibly not being cleaned up I was using the latest OpenKh release when testing but I went ahead and did a proper test on my fork and all files were cleaned up properly so disregard that comment. Not sure why that happened.

@shananas

shananas commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@kenjiuno not sure why all your stuff here is empty I set up 2 proper releases on my fork one before this PR and one after and all the tools are there and cleaned up properly. Also see some leftover files in your images I did have in a test I did without using Github releases so not sure exactly how that happens but at least going through github on my fork downloading release 1 then updating to release 2 it removes all extra files and has tools in the proper folders.

image

Defenitely understand your thought on warning popups or documentation about the deletion of the old files/folders

@Vladabdf

Vladabdf commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

This would certainly be helpful and slightly less intimidating for everyday people to use. I approve of it.

@shananas
shananas dismissed their stale review August 8, 2026 08:47

Meant to just mark as review as I did kind of request a change.

@Yokimitsuro

Copy link
Copy Markdown
Contributor Author

I have addressed the latest feedback in commit 692c3efa.

All applications now share a single Apps folder, including Mod Manager, graphical tools, command-line utilities, and their dependencies. The release root only contains the launcher, documentation, user data, and the hidden compatibility executable used by existing Mod Manager shortcuts.

The migration also removes the previous AdvancedTools and Apps\ModManager folders. Cleanup only runs after confirming that the complete organized installation exists, preventing accidental cleanup when running a development build.

I also added persistent tool favorites. Each modding tool now has a star button. Favorites are moved to the top of the list and remain selected after restarting or updating OpenKH.

Validation completed:

  • Launcher builds successfully in Release configuration.
  • All 16 Mod Manager tests pass.
  • Launcher, Mod Manager, and Bar Editor start correctly from the packaged layout.
  • Migration removes the old folders while preserving the current Mod Manager and user files.
  • The release archive contains neither AdvancedTools nor Apps\ModManager.

Updated screenshot:

Modding tools and favorites

@kenjiuno

Copy link
Copy Markdown
Collaborator

Thanks for updating Launcher.

The problem I encountered is now fixed.
@shananas the cleanup process I have encountered isn't invoked now with 692c3ef, when I started OpenKh.Launcher.exe in the bin dir.

Also, the update feature works well in my test environment.

@kenjiuno
kenjiuno merged commit 3ec21c8 into OpenKH:master Aug 14, 2026
4 checks passed
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.

5 participants