launcher: refuse to start when another instance holds the install dir
Tetsu hit this running two Metin2Launcher.exe processes against the
same Wine install dir — the second instance blew up during blob
download with
The process cannot access the file '.../\.updates/staging/Metin2.exe'
because it is being used by another process.
because the first instance was already writing to the staging file.
There was no guard against multiple concurrent launchers, and the
symptoms (file-in-use IO exception during staging) are hard to
diagnose from the user's end.
Add a per-install-dir FileStream lock at `.updates/launcher.lock`
opened with FileShare.None + DeleteOnClose. If the lock is held, log a
clear error and exit with code 3. Released automatically when the
process exits. Works uniformly across Windows, Wine and native Linux;
a named Mutex would behave differently across Wine prefixes, so this
sticks to plain filesystem locking.
Also:
- launcher: switch main window to an image background + semi-
transparent column brushes so the Morion2 crystal gate branding art
shows through the existing dark-theme layout. First step toward the
art pack Jan dropped tonight; follow-up commits will redo the
layout per the full mockup.
- Assets/Branding/launcher-bg.png: initial background (downscaled
from the Gemini-generated crystal gate hero image, 1800x1120, ~2.5 MB).
- csproj: include Assets/**/*.png as AvaloniaResource.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BIN
src/Metin2Launcher/Assets/Branding/launcher-bg.png
Normal file
BIN
src/Metin2Launcher/Assets/Branding/launcher-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
@@ -26,4 +26,8 @@
|
||||
<EmbeddedResource Include="Localization\en.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**\*.png" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -29,6 +29,30 @@ public static class Program
|
||||
Log.SetLogFilePath(Path.Combine(stateDir, "launcher.log"));
|
||||
Log.Info($"metin2 launcher starting in {clientRoot}");
|
||||
|
||||
// Single-instance guard: two launchers running in the same install
|
||||
// dir race on the staging files and blow up with "The process cannot
|
||||
// access the file ... because it is being used by another process".
|
||||
// Use a per-install-dir file lock (not a named mutex — named mutexes
|
||||
// behave differently across Wine prefixes and Windows sessions). The
|
||||
// lock is released when the process exits.
|
||||
FileStream? instanceLock = null;
|
||||
try
|
||||
{
|
||||
var lockPath = Path.Combine(stateDir, "launcher.lock");
|
||||
instanceLock = new FileStream(
|
||||
lockPath,
|
||||
FileMode.OpenOrCreate,
|
||||
FileAccess.ReadWrite,
|
||||
FileShare.None,
|
||||
bufferSize: 1,
|
||||
FileOptions.DeleteOnClose);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
Log.Error($"another Metin2Launcher is already running in {clientRoot} — refusing to start a second instance");
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (args.Contains("--nogui"))
|
||||
{
|
||||
return RunHeadlessAsync(clientRoot, args).GetAwaiter().GetResult();
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
<GradientStop Offset="1.0" Color="#8B0000"/>
|
||||
</LinearGradientBrush>
|
||||
|
||||
<SolidColorBrush x:Key="LeftColumnBrush" Color="#17171B"/>
|
||||
<SolidColorBrush x:Key="RightColumnBrush" Color="#0F0F12"/>
|
||||
<!-- Column brushes are semi-transparent so the branding image bleeds -->
|
||||
<!-- through while still keeping text legible. The left column sits over -->
|
||||
<!-- the status widgets so it's a bit opaquer; the right column is the -->
|
||||
<!-- news/changelog and stays darker to avoid image-through-text. -->
|
||||
<SolidColorBrush x:Key="LeftColumnBrush" Color="#17171B" Opacity="0.78"/>
|
||||
<SolidColorBrush x:Key="RightColumnBrush" Color="#0F0F12" Opacity="0.68"/>
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#C41515"/>
|
||||
<SolidColorBrush x:Key="MutedTextBrush" Color="#888892"/>
|
||||
<SolidColorBrush x:Key="BodyTextBrush" Color="#E0E0E4"/>
|
||||
@@ -87,6 +91,18 @@
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Grid>
|
||||
<!-- Full-window branding image, hand-authored for the m2pack release. -->
|
||||
<!-- Sits behind the banner + body grid; the dark column brushes above it -->
|
||||
<!-- give the status and news columns their own legible background while -->
|
||||
<!-- the banner fades the top into the crimson header. -->
|
||||
<Image Source="avares://Metin2Launcher/Assets/Branding/launcher-bg.png"
|
||||
Stretch="UniformToFill"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"/>
|
||||
<!-- Dim overlay so text on the image reads reliably. -->
|
||||
<Rectangle Fill="#000000" Opacity="0.35"/>
|
||||
|
||||
<Grid RowDefinitions="110,*">
|
||||
<!-- Banner -->
|
||||
<Border Grid.Row="0" Background="{StaticResource BannerGradient}">
|
||||
@@ -232,4 +248,5 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user