Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/scripts/prepare-release.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
param (
[string] $BuildDirectory = "bin",
[string] $ReleaseDirectory = "openkh",
[string] $Configuration = "Release"
)

$ErrorActionPreference = "Stop"

if (-not (Test-Path -LiteralPath $BuildDirectory -PathType Container)) {
throw "Build directory '$BuildDirectory' does not exist."
}

if (Test-Path -LiteralPath $ReleaseDirectory) {
throw "Release directory '$ReleaseDirectory' already exists."
}

New-Item -ItemType Directory -Path $ReleaseDirectory | Out-Null

$applicationsDirectory = Join-Path $ReleaseDirectory "Apps"
$modManagerDirectory = Join-Path $applicationsDirectory "ModManager"
New-Item -ItemType Directory -Path $modManagerDirectory -Force | Out-Null

$legacyFileManifest = Join-Path $applicationsDirectory "legacy-release-files.txt"
$legacyDirectoryManifest = Join-Path $applicationsDirectory "legacy-release-directories.txt"
Get-ChildItem -LiteralPath $BuildDirectory -File |
Select-Object -ExpandProperty Name |
Sort-Object |
Set-Content -LiteralPath $legacyFileManifest -Encoding UTF8
Get-ChildItem -LiteralPath $BuildDirectory -Directory |
Select-Object -ExpandProperty Name |
Sort-Object |
Set-Content -LiteralPath $legacyDirectoryManifest -Encoding UTF8

dotnet publish `
"OpenKh.Tools.Launcher/OpenKh.Tools.Launcher.csproj" `
--configuration $Configuration `
--runtime win-x64 `
--self-contained false `
--output $ReleaseDirectory `
/p:PublishSingleFile=true `
/p:DebugType=None `
/p:DebugSymbols=false

if ($LASTEXITCODE -ne 0) {
throw "Publishing OpenKH Launcher failed with exit code $LASTEXITCODE."
}

$compatibilityExecutable = Join-Path $ReleaseDirectory "OpenKh.Tools.ModsManager.exe"
Copy-Item `
-LiteralPath (Join-Path $ReleaseDirectory "OpenKh.Launcher.exe") `
-Destination $compatibilityExecutable
(Get-Item -LiteralPath $compatibilityExecutable).Attributes += "Hidden"

dotnet publish `
"OpenKh.Tools.ModsManager/OpenKh.Tools.ModsManager.csproj" `
--configuration $Configuration `
--output $modManagerDirectory `
/p:DebugType=None `
/p:DebugSymbols=false

if ($LASTEXITCODE -ne 0) {
throw "Publishing Mods Manager failed with exit code $LASTEXITCODE."
}

$referencedCommandArtifacts = Get-ChildItem -LiteralPath $modManagerDirectory -File | Where-Object {
$_.Name -like "OpenKh.Command.*" -and $_.Extension -ne ".dll"
}

foreach ($referencedCommandArtifact in $referencedCommandArtifacts) {
Remove-Item -LiteralPath $referencedCommandArtifact.FullName
}

$panaceaFiles = @(
"OpenKH.Panacea.dll",
"avcodec-vgmstream-59.dll",
"avformat-vgmstream-59.dll",
"avutil-vgmstream-57.dll",
"bass.dll",
"bass_vgmstream.dll",
"libatrac9.dll",
"libcelt-0061.dll",
"libcelt-0110.dll",
"libg719_decode.dll",
"libmpg123-0.dll",
"libspeex-1.dll",
"libvorbis.dll",
"swresample-vgmstream-4.dll"
)

foreach ($fileName in $panaceaFiles) {
$sourcePath = Join-Path $BuildDirectory $fileName
if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
throw "Required Panacea file '$sourcePath' does not exist."
}

Copy-Item -LiteralPath $sourcePath -Destination $modManagerDirectory
}

Copy-Item -LiteralPath "distribution/README-FIRST.txt" -Destination $ReleaseDirectory
Copy-Item -LiteralPath "LICENSE" -Destination $ReleaseDirectory
Copy-Item -LiteralPath "NOTICE" -Destination $ReleaseDirectory

$advancedToolsDirectory = Join-Path $ReleaseDirectory "AdvancedTools"
Move-Item -LiteralPath $BuildDirectory -Destination $advancedToolsDirectory

$duplicateApplicationFiles = Get-ChildItem -LiteralPath $advancedToolsDirectory -File | Where-Object {
$_.Name -like "OpenKh.Launcher.*" -or
$_.Name -like "OpenKh.Tools.ModsManager.*"
}

foreach ($duplicateFile in $duplicateApplicationFiles) {
Remove-Item -LiteralPath $duplicateFile.FullName
}
30 changes: 23 additions & 7 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,47 @@ jobs:
- name: build.ps1
run: powershell -ExecutionPolicy Unrestricted ./build.ps1
shell: pwsh

- name: setup-msbuild
uses: microsoft/setup-msbuild@v1.1.3
- name: msbuild panacea
run: |
msbuild OpenKh.Research.Panacea\OpenKh.Research.Panacea.vcxproj /p:Configuration=Release /p:Platform=x64
xcopy "OpenKh.Research.Panacea\Release\*.dll" bin\
xcopy "OpenKh.Research.Panacea\Dependencies\*.dll" bin\


- name: Organize release for mod users
run: powershell -ExecutionPolicy Unrestricted ./.github/scripts/prepare-release.ps1
shell: pwsh

- name: create openkh-release
shell: bash
env:
RELEASE_TAG: "release2-${{github.run_number}}"
run: |
echo $RELEASE_TAG > bin/openkh-release

- name: bin → openkh
run: ren bin openkh
shell: pwsh
echo $RELEASE_TAG > openkh/openkh-release

- name: zip
uses: TheDoctor0/zip-release@0.6.2
with:
filename: openkh.zip
path: openkh

- name: validate update archive
shell: pwsh
run: |
$archiveListing = (7z l openkh.zip) -join "`n"
$requiredEntries = @(
"openkh\OpenKh.Launcher.exe",
"openkh\OpenKh.Tools.ModsManager.exe",
"openkh\Apps\ModManager\OpenKh.Tools.ModsManager.exe"
)
foreach ($entry in $requiredEntries) {
if ($archiveListing -notmatch [regex]::Escape($entry)) {
throw "Required update entry '$entry' is missing from openkh.zip."
}
}

- name: "GitHub release latest"
if: ${{ github.ref_name == 'master' }}
uses: "marvinpinto/action-automatic-releases@latest"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

# Project specific files
.tests/
/openkh/
/openkh-*/
/OpenKh-*-release.zip

# User-specific files
*.suo
Expand Down
21 changes: 21 additions & 0 deletions OpenKh.Tests.ModsManager/OpenkhInstallationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using OpenKh.Tools.ModsManager.Services;
using Xunit;

namespace OpenKh.Tests.ModsManager
{
public class OpenkhInstallationTest
{
[Theory]
[InlineData(@"C:\OpenKh", @"C:\OpenKh")]
[InlineData(@"C:\OpenKh\Apps\ModManager", @"C:\OpenKh")]
[InlineData(@"C:\OpenKh\apps\modmanager", @"C:\OpenKh")]
public void GetDirectoryReturnsInstallationRoot(string applicationDirectory, string expectedDirectory)
{
Assert.Equal(
Path.GetFullPath(expectedDirectory),
OpenkhInstallation.GetDirectory(applicationDirectory),
ignoreCase: true
);
}
}
}
6 changes: 6 additions & 0 deletions OpenKh.Tools.Launcher/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Application x:Class="OpenKh.Tools.Launcher.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
20 changes: 20 additions & 0 deletions OpenKh.Tools.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Windows;

namespace OpenKh.Tools.Launcher;

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

if (LegacyInstallationMigration.TryStartModManager())
{
Shutdown();
return;
}

LegacyInstallationMigration.ScheduleCleanupIfNeeded();
new MainWindow().Show();
Comment on lines +11 to +18

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.

}
}
46 changes: 46 additions & 0 deletions OpenKh.Tools.Launcher/DesktopShortcutService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.IO;
using System.Runtime.InteropServices;

namespace OpenKh.Tools.Launcher;

internal static class DesktopShortcutService
{
public static string CreateModManagerShortcut(string targetPath, string? shortcutDirectory = null)
{
shortcutDirectory ??= Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
var shortcutPath = Path.Combine(shortcutDirectory, "OpenKH Mod Manager.lnk");
var shellType = Type.GetTypeFromProgID("WScript.Shell")
?? throw new InvalidOperationException("Windows Script Host is not available.");
object? shell = null;
object? shortcut = null;

try
{
shell = Activator.CreateInstance(shellType)
?? throw new InvalidOperationException("Windows Script Host could not be started.");
shortcut = shellType.InvokeMember(
"CreateShortcut",
System.Reflection.BindingFlags.InvokeMethod,
null,
shell,
new object[] { shortcutPath }
) ?? throw new InvalidOperationException("The shortcut could not be created.");

var shortcutType = shortcut.GetType();
shortcutType.InvokeMember("TargetPath", System.Reflection.BindingFlags.SetProperty, null, shortcut, new object[] { targetPath });
shortcutType.InvokeMember("WorkingDirectory", System.Reflection.BindingFlags.SetProperty, null, shortcut, new object[] { Path.GetDirectoryName(targetPath)! });
shortcutType.InvokeMember("Description", System.Reflection.BindingFlags.SetProperty, null, shortcut, new object[] { "Open OpenKH Mod Manager" });
shortcutType.InvokeMember("IconLocation", System.Reflection.BindingFlags.SetProperty, null, shortcut, new object[] { $"{targetPath},0" });
shortcutType.InvokeMember("Save", System.Reflection.BindingFlags.InvokeMethod, null, shortcut, null);
}
finally
{
if (shortcut != null && Marshal.IsComObject(shortcut))
Marshal.FinalReleaseComObject(shortcut);
if (shell != null && Marshal.IsComObject(shell))
Marshal.FinalReleaseComObject(shell);
}

return shortcutPath;
}
}
Loading
Loading