From 3eca16a6618d106eed8f9b9826b41c9b1c44bee7 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Tue, 28 Apr 2026 20:19:17 +0800 Subject: [PATCH 01/11] upgrade to dotnet 10 --- Common/Common.csproj | 3 +-- GameServer/GameServer.csproj | 3 +-- MikuSB.Updater/MikuSB.Updater.csproj | 2 +- MikuSB/MikuSB.csproj | 2 +- .../PublishProfiles/MikuSB-Win64-Debug.pubxml | 2 +- .../MikuSB-Win64-MultiFile.pubxml | 2 +- .../MikuSB-Win64-OneFile.pubxml | 2 +- Proto/Proto.csproj | 2 +- Proxy/Proxy.csproj | 2 +- SdkServer/SdkServer.cs | 20 +++++++++---------- SdkServer/SdkServer.csproj | 2 +- TcpSharp/TcpSharp.csproj | 3 +-- 12 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Common/Common.csproj b/Common/Common.csproj index 024f2ef..64ce4a9 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false @@ -21,7 +21,6 @@ - diff --git a/GameServer/GameServer.csproj b/GameServer/GameServer.csproj index 6600304..ead8405 100644 --- a/GameServer/GameServer.csproj +++ b/GameServer/GameServer.csproj @@ -2,7 +2,7 @@ Library - net9.0 + net10.0 enable enable false @@ -22,7 +22,6 @@ - diff --git a/MikuSB.Updater/MikuSB.Updater.csproj b/MikuSB.Updater/MikuSB.Updater.csproj index 84f8afc..f4640d8 100644 --- a/MikuSB.Updater/MikuSB.Updater.csproj +++ b/MikuSB.Updater/MikuSB.Updater.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 win-x64 enable enable diff --git a/MikuSB/MikuSB.csproj b/MikuSB/MikuSB.csproj index bfcdc01..19c3b36 100644 --- a/MikuSB/MikuSB.csproj +++ b/MikuSB/MikuSB.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable false diff --git a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-Debug.pubxml b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-Debug.pubxml index b46a1e0..d1148d7 100644 --- a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-Debug.pubxml +++ b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-Debug.pubxml @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. bin\MikuSB-Win64-Debug FileSystem <_TargetId>Folder - net9.0 + net10.0 win-x64 false false diff --git a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-MultiFile.pubxml b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-MultiFile.pubxml index d51f9a6..ab03f5c 100644 --- a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-MultiFile.pubxml +++ b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-MultiFile.pubxml @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. bin\MikuSB-MultiFile\ FileSystem <_TargetId>Folder - net9.0 + net10.0 win-x64 false false diff --git a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-OneFile.pubxml b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-OneFile.pubxml index 9ac6ab9..d55e2e3 100644 --- a/MikuSB/Properties/PublishProfiles/MikuSB-Win64-OneFile.pubxml +++ b/MikuSB/Properties/PublishProfiles/MikuSB-Win64-OneFile.pubxml @@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. bin\MikuSB-OneFile\ FileSystem <_TargetId>Folder - net9.0 + net10.0 win-x64 true true diff --git a/Proto/Proto.csproj b/Proto/Proto.csproj index 4fe010f..7ff33df 100644 --- a/Proto/Proto.csproj +++ b/Proto/Proto.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false diff --git a/Proxy/Proxy.csproj b/Proxy/Proxy.csproj index c33e878..73aa14a 100644 --- a/Proxy/Proxy.csproj +++ b/Proxy/Proxy.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 enable enable MikuSB.Proxy diff --git a/SdkServer/SdkServer.cs b/SdkServer/SdkServer.cs index 6955abe..9b7a453 100644 --- a/SdkServer/SdkServer.cs +++ b/SdkServer/SdkServer.cs @@ -17,17 +17,17 @@ public static class SdkServer { public static void Start(string[] args) { - BuildWebHost(args).RunAsync(); - } + var builder = Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .ConfigureLogging((_, logging) => { logging.ClearProviders(); }) + .UseUrls(ConfigManager.Config.HttpServer.GetDisplayAddress()); + }); - private static IWebHost BuildWebHost(string[] args) - { - var builder = WebHost.CreateDefaultBuilder(args) - .UseStartup() - .ConfigureLogging((_, logging) => { logging.ClearProviders(); }) - .UseUrls(ConfigManager.Config.HttpServer.GetDisplayAddress()); - - return builder.Build(); + var host = builder.Build(); + host.RunAsync(); } } diff --git a/SdkServer/SdkServer.csproj b/SdkServer/SdkServer.csproj index d7d4a54..effc4cb 100644 --- a/SdkServer/SdkServer.csproj +++ b/SdkServer/SdkServer.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable MikuSB.SdkServer diff --git a/TcpSharp/TcpSharp.csproj b/TcpSharp/TcpSharp.csproj index 68eb1cf..004e7a3 100644 --- a/TcpSharp/TcpSharp.csproj +++ b/TcpSharp/TcpSharp.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false @@ -11,7 +11,6 @@ - From 34b93ad55df68b4f1204220f45341f04265fc9bb Mon Sep 17 00:00:00 2001 From: cs8425 Date: Tue, 28 Apr 2026 21:09:14 +0800 Subject: [PATCH 02/11] fix infinite loop when pressed ctrl+c --- Common/Util/IConsole.cs | 36 ++++++++++++++++++++++++--------- MikuSB/Program/LoaderManager.cs | 4 ++-- MikuSB/Program/MikuSB.cs | 28 ++++++++++++++++++------- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/Common/Util/IConsole.cs b/Common/Util/IConsole.cs index ac0c0e9..395561e 100644 --- a/Common/Util/IConsole.cs +++ b/Common/Util/IConsole.cs @@ -53,7 +53,7 @@ public class IConsole Console.WriteLine(); Input = []; CursorIndex = 0; - if (InputHistory.Count >= HistoryMaxCount) + if (InputHistory.Count >= HistoryMaxCount) InputHistory.RemoveAt(0); InputHistory.Add(input); HistoryIndex = InputHistory.Count; @@ -80,7 +80,7 @@ public class IConsole public static void HandleUpArrow() { if (InputHistory.Count == 0) return; - + if (HistoryIndex > 0) { HistoryIndex--; @@ -94,7 +94,7 @@ public class IConsole public static void HandleDownArrow() { if (HistoryIndex >= InputHistory.Count) return; - + HistoryIndex++; if (HistoryIndex >= InputHistory.Count) { @@ -102,7 +102,7 @@ public class IConsole Input = []; CursorIndex = 0; } - else + else { var history = InputHistory[HistoryIndex]; Input = [.. history]; @@ -114,7 +114,7 @@ public class IConsole public static void HandleLeftArrow() { if (CursorIndex <= 0) return; - + var (left, _) = Console.GetCursorPosition(); CursorIndex--; Console.SetCursorPosition(left - GetWidth(Input[CursorIndex].ToString()), Console.CursorTop); @@ -123,7 +123,7 @@ public class IConsole public static void HandleRightArrow() { if (CursorIndex >= Input.Count) return; - + var (left, _) = Console.GetCursorPosition(); CursorIndex++; Console.SetCursorPosition(left + GetWidth(Input[CursorIndex - 1].ToString()), Console.CursorTop); @@ -148,13 +148,29 @@ public class IConsole #endregion - public static string ListenConsole() + public static async Task ListenConsole(CancellationToken exitToken) { - while (true) + while (!exitToken.IsCancellationRequested) { ConsoleKeyInfo keyInfo; - try { keyInfo = Console.ReadKey(true); } - catch (InvalidOperationException) { continue; } + try + { + if (!Console.KeyAvailable) + { + await Task.Delay(10, exitToken); + continue; + } + keyInfo = Console.ReadKey(true); + } + catch (OperationCanceledException) + { + break; + } + catch (InvalidOperationException) + { + await Task.Delay(50, exitToken); + continue; + } switch (keyInfo.Key) { diff --git a/MikuSB/Program/LoaderManager.cs b/MikuSB/Program/LoaderManager.cs index 5d60ce0..04bfc29 100644 --- a/MikuSB/Program/LoaderManager.cs +++ b/MikuSB/Program/LoaderManager.cs @@ -160,7 +160,7 @@ public class LoaderManager : MikuSB } } - public static void InitCommand() + public static async Task InitCommand(CancellationToken exitToken) { // Register the command handlers try @@ -178,6 +178,6 @@ public class LoaderManager : MikuSB IConsole.OnConsoleExcuteCommand += CommandExecutor.ConsoleExcuteCommand; CommandExecutor.OnRunCommand += (sender, e) => { CommandManager.HandleCommand(e, sender); }; - IConsole.ListenConsole(); + await IConsole.ListenConsole(exitToken); } } \ No newline at end of file diff --git a/MikuSB/Program/MikuSB.cs b/MikuSB/Program/MikuSB.cs index d20e124..edd0778 100644 --- a/MikuSB/Program/MikuSB.cs +++ b/MikuSB/Program/MikuSB.cs @@ -18,6 +18,10 @@ public class MikuSB public static readonly Listener Listener = new(); public static readonly CommandManager CommandManager = new(); + // for exit signal + private static readonly CancellationTokenSource _cts = new(); + private static int _exitCode = 0; + public static async Task Main() { var time = DateTime.Now; @@ -50,44 +54,54 @@ public class MikuSB ResourceManager.IsLoaded = true; HandbookGenerator.GenerateAll(); - LoaderManager.InitCommand(); + var consoleTask = Task.Run(() => LoaderManager.InitCommand(_cts.Token), _cts.Token); var elapsed = DateTime.Now - time; Logger.Info(I18NManager.Translate("Server.ServerInfo.ServerStarted", Math.Round(elapsed.TotalSeconds, 2).ToString(CultureInfo.InvariantCulture))); + + await consoleTask; + + ProcessExit(Volatile.Read(ref _exitCode)); } - # region Exit + #region Exit private static void RegisterExitEvent() { AppDomain.CurrentDomain.ProcessExit += (_, _) => { Logger.Info(I18NManager.Translate("Server.ServerInfo.Shutdown")); - ProcessExit(); + RequestShutdown(0); }; AppDomain.CurrentDomain.UnhandledException += (obj, arg) => { Logger.Error(I18NManager.Translate("Server.ServerInfo.UnhandledException", obj.GetType().Name), (Exception)arg.ExceptionObject); Logger.Info(I18NManager.Translate("Server.ServerInfo.Shutdown")); - ProcessExit(); - Environment.Exit(1); + RequestShutdown(1); }; Console.CancelKeyPress += (_, eventArgs) => { Logger.Info(I18NManager.Translate("Server.ServerInfo.CancelKeyPressed")); eventArgs.Cancel = true; - Environment.Exit(0); + RequestShutdown(0); }; } - private static void ProcessExit() + private static void RequestShutdown(int exitCode) + { + Interlocked.Exchange(ref _exitCode, exitCode); + _cts.Cancel(); + } + + private static void ProcessExit(int exitCode) { SocketListener.Connections.Values.ToList().ForEach(x => x.Stop(true)); DatabaseHelper.SaveThread?.Interrupt(); DatabaseHelper.SaveDatabase(); + Environment.Exit(exitCode); } # endregion From e93dadafe8ffb3db629b4dc38b03a55476ec83ad Mon Sep 17 00:00:00 2001 From: cs8425 Date: Tue, 28 Apr 2026 22:49:58 +0800 Subject: [PATCH 03/11] fix infinite loop for auto SaveDatabase --- Common/Database/DatabaseHelper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Common/Database/DatabaseHelper.cs b/Common/Database/DatabaseHelper.cs index 1a9dc5f..235789e 100644 --- a/Common/Database/DatabaseHelper.cs +++ b/Common/Database/DatabaseHelper.cs @@ -77,13 +77,17 @@ public class DatabaseHelper while (!res.IsCompleted) { + Thread.Sleep(100); } LastSaveTick = DateTime.UtcNow.Ticks; - SaveThread = new Thread(() => + SaveThread = new Thread(async () => { - while (true) CalcSaveDatabase(); + while (true) { + CalcSaveDatabase(); + await Task.Delay(100); + } }); SaveThread.Start(); From b256fe7b01f5f574bc64b6b8e35edfd865affea2 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Tue, 28 Apr 2026 23:16:13 +0800 Subject: [PATCH 04/11] fix proxy log and proxy https connection always return 404 --- Proxy/ProxyServer.cs | 22 ++++++++++++++-------- SdkServer/SdkServer.cs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Proxy/ProxyServer.cs b/Proxy/ProxyServer.cs index e27f995..109c13e 100644 --- a/Proxy/ProxyServer.cs +++ b/Proxy/ProxyServer.cs @@ -5,6 +5,7 @@ using System.Net.Sockets; using System.Security.Authentication; using System.Text; using MikuSB.Configuration; +using MikuSB.Util; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -15,7 +16,7 @@ public sealed class ProxyServer( IOptions options, ProxyCertificateAuthority certificateAuthority, HttpClient httpClient, - ILogger logger) : BackgroundService + Logger logger) : BackgroundService { private const string ListenAddress = "127.0.0.1"; private const string ServerHost = "127.0.0.1"; @@ -53,14 +54,14 @@ public sealed class ProxyServer( { if (!_options.Enabled) { - logger.LogInformation("MikuSB proxy is disabled"); + logger.Info("MikuSB proxy is disabled"); return; } var address = IPAddress.Parse(ListenAddress); _listener = new TcpListener(address, _options.Port); _listener.Start(); - logger.LogInformation("MikuSB proxy listening on {Address}:{Port}", ListenAddress, _options.Port); + logger.Info($"MikuSB proxy listening on {ListenAddress}:{_options.Port}"); try { @@ -85,6 +86,7 @@ public sealed class ProxyServer( { using (client) { + logger.Info($"Proxy New client: {client.Client.RemoteEndPoint}"); try { await HandleClientCoreAsync(client, cancellationToken); @@ -100,12 +102,13 @@ public sealed class ProxyServer( } catch (AuthenticationException ex) { - logger.LogWarning(ex, "Proxy TLS authentication failed"); + logger.Warn($"Proxy TLS authentication failed: {ex}"); } catch (Exception ex) { - logger.LogWarning(ex, "Proxy client failed"); + logger.Warn($"Proxy client failed {ex}"); } + logger.Info($"Proxy client close: {client.Client.RemoteEndPoint}"); } } @@ -187,7 +190,7 @@ public sealed class ProxyServer( { var pathAndQuery = request.GetPathAndQuery(); var uri = new Uri($"http://{ServerHost}:{_options.ServerHttpPort}{pathAndQuery}"); - logger.LogInformation("[Proxy] Redirect: {Method} {Host}{Path} -> {Uri}", request.Method, request.HostOverride ?? request.Host, pathAndQuery, uri); + logger.Info($"Redirect: {request.Method} {request.HostOverride ?? request.Host}{pathAndQuery} -> {uri}"); await SendHttpRequestAsync(clientStream, request, uri, true, cancellationToken); } @@ -202,7 +205,7 @@ public sealed class ProxyServer( if (IsSelfReference(uri)) { - logger.LogWarning("[Proxy] Self-reference blocked: {Method} {Uri}", request.Method, uri); + logger.Info($"Self-reference blocked: {request.Method} {uri}"); await WriteSimpleResponseAsync(clientStream, HttpStatusCode.LoopDetected, "Proxy self-reference detected", cancellationToken); return; } @@ -351,7 +354,10 @@ public sealed class ProxyServer( public string GetPathAndQuery() { - if (Uri.TryCreate(Target, UriKind.Absolute, out var uri)) + // "/query?version=a.b.c&platform=PC" + // => Uri.TryCreate() return true && uri.Scheme == "file" + // => will return "/query%3Fversion=a.b.c&platform=PC" cause 404 + if (Uri.TryCreate(Target, UriKind.Absolute, out var uri) && uri.IsAbsoluteUri && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) return uri.PathAndQuery; if (string.IsNullOrEmpty(Target)) diff --git a/SdkServer/SdkServer.cs b/SdkServer/SdkServer.cs index 9b7a453..bc9fed6 100644 --- a/SdkServer/SdkServer.cs +++ b/SdkServer/SdkServer.cs @@ -91,7 +91,7 @@ public class Startup { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; }); - services.AddSingleton(_ => new Logger("HttpServer")); + services.AddSingleton(_ => new Logger("Proxy")); services.AddMikuSbProxy(ConfigManager.Config.Proxy); } } From 2fe795d29a28d7c4cba24af3aed88dd4da2e081a Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 02:52:48 +0800 Subject: [PATCH 05/11] also export CA cert in pem format --- Proxy/ProxyCertificateAuthority.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Proxy/ProxyCertificateAuthority.cs b/Proxy/ProxyCertificateAuthority.cs index 1c92295..cf8df7a 100644 --- a/Proxy/ProxyCertificateAuthority.cs +++ b/Proxy/ProxyCertificateAuthority.cs @@ -30,7 +30,8 @@ public sealed class ProxyCertificateAuthority RootCerPath); } - public string RootCerPath => Path.Combine(CertificateDirectory, "MikuSB.Proxy.Root.cer"); + public string RootCerPath => Path.Join(CertificateDirectory, "MikuSB.Proxy.Root.cer"); + public string RootCerPemPath => Path.Join(CertificateDirectory, "MikuSB.Proxy.Root.pem"); private static string CertificateDirectory => Path.Combine(AppContext.BaseDirectory, "proxy-certs"); @@ -55,6 +56,12 @@ public sealed class ProxyCertificateAuthority if (!File.Exists(RootCerPath)) File.WriteAllBytes(RootCerPath, existing.Export(X509ContentType.Cert)); + if (!File.Exists(RootCerPemPath)) + { + string pemString = existing.ExportCertificatePem(); + File.WriteAllText(RootCerPemPath, pemString); + } + return existing; } From 5f3fc0c746ad60ee5b14f806f5a76518b280be5c Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 02:53:08 +0800 Subject: [PATCH 06/11] add docs for running on Linux --- README_linux.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 README_linux.md diff --git a/README_linux.md b/README_linux.md new file mode 100644 index 0000000..f5af6c7 --- /dev/null +++ b/README_linux.md @@ -0,0 +1,60 @@ +# MikuSB on Linux + + +## Config + +### setup steam launch options as following + +`HTTP_PROXY="http://127.0.0.1:8888" HTTPS_PROXY="http://127.0.0.1:8888" ALL_PROXY="http://127.0.0.1:8888" %command%` + +### start local server and keep it running + +``` +./MikuSB +``` + +### find root CA cert, and create ca bundle + +root CA cert, should in the path: `proxy-certs/MikuSB.Proxy.Root.pem` + + +### setup root CA for proton/wine + +not sure, even I remove Proton PFX (Wine prefix) folder, without redo this step, still no cert issue. + +`Proton Hotfix` is the proton version which selected in steam `Force the use of a specific Steam Play compatibility tool` + +```bash +APPID= +STEAM_COMPAT_DATA_PATH=~/.steam/steam/steamapps/compatdata/$APPID/pfx +STEAM_WINE_PATH="$HOME/.steam/steam/steamapps/common/Proton Hotfix/files/bin/wine" +WINEPREFIX=$STEAM_COMPAT_DATA_PATH $STEAM_WINE_PATH certutil -addstore -f Root proxy-certs/MikuSB.Proxy.Root.pem +``` + +### start the game and enjoy + + +## development + +1. Restore dependencies and build. + +```bash +dotnet build +``` + +2. run it + +```bash +dotnet run --project ./MikuSB +``` + +## release build + +```bash +LANG=C time dotnet build -c Release +``` + +## TODO: + +* [ ] tool/script for CA cert create and install to proton/wine +* [ ] automatic done in main program From 7e83d2ccd21e80eee4807839f2df8d2a10a95124 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 19:30:57 +0800 Subject: [PATCH 07/11] fix racing between AutoSave and process exit --- Common/Database/DatabaseHelper.cs | 57 +++++++++++++++++++++++++------ MikuSB/Program/MikuSB.cs | 12 ++++--- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/Common/Database/DatabaseHelper.cs b/Common/Database/DatabaseHelper.cs index 235789e..cbaf4b0 100644 --- a/Common/Database/DatabaseHelper.cs +++ b/Common/Database/DatabaseHelper.cs @@ -14,7 +14,10 @@ public class DatabaseHelper public static readonly ConcurrentDictionary> UidInstanceMap = []; public static readonly List ToSaveUidList = []; public static long LastSaveTick = DateTime.UtcNow.Ticks; - public static Thread? SaveThread; + + private static int _saving = 0; + private static Task? _saveTask; + private static CancellationTokenSource? _cts; public static bool LoadAccount; public static bool LoadAllData; @@ -80,16 +83,8 @@ public class DatabaseHelper Thread.Sleep(100); } - LastSaveTick = DateTime.UtcNow.Ticks; - - SaveThread = new Thread(async () => - { - while (true) { - CalcSaveDatabase(); - await Task.Delay(100); - } - }); - SaveThread.Start(); + _cts = new CancellationTokenSource(); + _saveTask = RunAutoSave(_cts.Token); LoadAllData = true; } @@ -252,6 +247,35 @@ public class DatabaseHelper ToSaveUidList.RemoveAll(x => x == key); } + public static void Stop() + { + _cts?.Cancel(); + } + + public static async Task WaitAsync() + { + if (_saveTask != null) + await _saveTask; + } + + private static async Task RunAutoSave(CancellationToken token) + { + LastSaveTick = DateTime.UtcNow.Ticks; + try + { + while (!token.IsCancellationRequested) + { + CalcSaveDatabase(); + await Task.Delay(100, token); + } + } + catch (OperationCanceledException) + { + // exit normally + // Console.WriteLine($"RunAutoSave exit! - OperationCanceledException"); + } + } + // Auto save per 5 min public static void CalcSaveDatabase() { @@ -261,6 +285,10 @@ public class DatabaseHelper public static void SaveDatabase() { + // ensure only one SaveDatabase() runnig + if (Interlocked.Exchange(ref _saving, 1) == 1) + return; + try { var prev = DateTime.Now; @@ -285,11 +313,18 @@ public class DatabaseHelper Math.Round(t, 2).ToString(CultureInfo.InvariantCulture))); ToSaveUidList.Clear(); + + // Thread.Sleep(5000); // for test if saving process taking too long } catch (Exception e) { logger.Error("An error occurred while saving the database", e); } + finally + { + // release lock + Volatile.Write(ref _saving, 0); + } LastSaveTick = DateTime.UtcNow.Ticks; } diff --git a/MikuSB/Program/MikuSB.cs b/MikuSB/Program/MikuSB.cs index edd0778..ff779d5 100644 --- a/MikuSB/Program/MikuSB.cs +++ b/MikuSB/Program/MikuSB.cs @@ -62,7 +62,7 @@ public class MikuSB await consoleTask; - ProcessExit(Volatile.Read(ref _exitCode)); + await ProcessExit(Volatile.Read(ref _exitCode)); } #region Exit @@ -96,11 +96,15 @@ public class MikuSB _cts.Cancel(); } - private static void ProcessExit(int exitCode) + private static async Task ProcessExit(int exitCode) { SocketListener.Connections.Values.ToList().ForEach(x => x.Stop(true)); - DatabaseHelper.SaveThread?.Interrupt(); - DatabaseHelper.SaveDatabase(); + + DatabaseHelper.Stop(); // notify stop + await DatabaseHelper.WaitAsync(); // wait AutoSave thread exit + + DatabaseHelper.SaveDatabase(); // final flush + Environment.Exit(exitCode); } From 8e3323dd94139620292d1d25d9512ef270ec6195 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 19:34:25 +0800 Subject: [PATCH 08/11] fix pem format not export when first create CA cert --- Proxy/ProxyCertificateAuthority.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Proxy/ProxyCertificateAuthority.cs b/Proxy/ProxyCertificateAuthority.cs index cf8df7a..2e4f68a 100644 --- a/Proxy/ProxyCertificateAuthority.cs +++ b/Proxy/ProxyCertificateAuthority.cs @@ -85,6 +85,9 @@ public sealed class ProxyCertificateAuthority File.WriteAllBytes(pfxPath, exportable.Export(X509ContentType.Pfx, Password)); File.WriteAllBytes(RootCerPath, exportable.Export(X509ContentType.Cert)); _logger.LogInformation("Created MikuSB proxy root certificate at {CertificatePath}", RootCerPath); + + File.WriteAllText(RootCerPemPath, exportable.ExportCertificatePem()); + _logger.LogInformation("Created MikuSB proxy root certificate (PEM) at {CertificatePath}", RootCerPemPath); return exportable; } From 391879a483ba857d12cc53b8b5c68eb78dd49ed9 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 20:28:11 +0800 Subject: [PATCH 09/11] bump github workflow to dotnet 10 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1321af2..92bb5eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Read version id: version From 04121eb357b0c6a779a2ff763a1f88528cd5ca1c Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 21:39:39 +0800 Subject: [PATCH 10/11] try workflow build for linux --- .github/workflows/release.yml | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92bb5eb..7a36c27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ on: permissions: contents: write +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + jobs: build-release: runs-on: windows-latest @@ -54,3 +57,55 @@ jobs: files: | artifacts/MikuSB-win-x64.zip artifacts/MikuSB-win-x64.zip.sha256 + + build-release-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Publish server + run: | + dotnet publish ./MikuSB/MikuSB.csproj -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true --property:PublishDir=../artifacts/dist + + - name: Read version + id: version + shell: bash + run: | + line=$(head -n 1 version.txt | xargs) + VERSION=${line#v=} + + SHORT_HASH="$(git rev-parse --short=7 HEAD)" + if [ -z "$VERSION" ]; then + echo "version.txt is empty." + VERSION=${SHORT_HASH} + fi + if [[ "${{ env.BRANCH_NAME }}" == "main" ]]; then + echo "tag=v${VERSION}" >> $GITHUB_OUTPUT + else + SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-') + echo "tag=${SAFE_NAME}-v${VERSION}-${SHORT_HASH}" >> $GITHUB_OUTPUT + fi + + - name: Assemble release package + run: | + packageDir="./artifacts/dist/" + zipFile="MikuSB-linux-x64.zip" + cp ./version.txt ${packageDir} + pushd ${packageDir} + zip -r ../${zipFile} . + popd + sha256sum ./artifacts/${zipFile} > ./artifacts/${zipFile}.sha256 + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag }} + name: ${{ steps.version.outputs.tag }} + generate_release_notes: true + files: | + ./artifacts/MikuSB-linux-x64.zip + ./artifacts/MikuSB-linux-x64.zip.sha256 From d97eae6f5bab1a8f89c3c5dbfc9ec697d17f5f61 Mon Sep 17 00:00:00 2001 From: cs8425 Date: Wed, 29 Apr 2026 22:38:13 +0800 Subject: [PATCH 11/11] update README_linux.md --- README_linux.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README_linux.md b/README_linux.md index f5af6c7..36b910b 100644 --- a/README_linux.md +++ b/README_linux.md @@ -51,7 +51,13 @@ dotnet run --project ./MikuSB ## release build ```bash -LANG=C time dotnet build -c Release +DOTNET_CLI_UI_LANGUAGE=en time dotnet publish ./MikuSB/MikuSB.csproj -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true --property:PublishDir=../publish + +# output will in ./publish/* +cd ./publish + +# start server +./MikuSB ``` ## TODO: