diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs index 88effcb..7405076 100644 --- a/Common/Configuration/ConfigContainer.cs +++ b/Common/Configuration/ConfigContainer.cs @@ -13,7 +13,7 @@ public class ConfigContainer public class HttpServerConfig { - public string BindAddress { get; set; } = "0.0.0.0"; + public string BindAddress { get; set; } = "127.0.0.1"; public string PublicAddress { get; set; } = "127.0.0.1"; public int Port { get; set; } = 21500; public bool EnableLog { get; set; } = false; @@ -31,7 +31,7 @@ public string GetBindDisplayAddress() public class GameServerConfig { - public string BindAddress { get; set; } = "0.0.0.0"; + public string BindAddress { get; set; } = "127.0.0.1"; public string PublicAddress { get; set; } = "127.0.0.1"; public int Port { get; set; } = 21000; public int KcpAliveMs { get; set; } = 45000; @@ -86,7 +86,9 @@ public class ServerProfile public class ProxyOptions { public bool Enabled { get; set; } = true; + public string BindAddress { get; set; } = "127.0.0.1"; public int Port { get; set; } = 8888; + public string ServerHttpAddress { get; set; } = "127.0.0.1"; public int ServerHttpPort { get; set; } = 21500; public bool InstallRootCertificate { get; set; } = true; public bool ManageSystemProxy { get; set; } = true; diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 36db113..de38191 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -129,6 +129,7 @@ public class CommandTextCHS public GirlTextCHS Girl { get; } = new(); public GiveAllTextCHS GiveAll { get; } = new(); public DebugTextCHS Debug { get; } = new(); + public GmMenuTextCHS GmMenu { get; } = new(); } #endregion @@ -227,12 +228,14 @@ public class GirlTextCHS public string Usage => "用法: /girl add -p -l -s\n" + "用法: /girl level \n" + - "用法: /girl neuronic "; + "用法: /girl neuronic \n" + + "用法: /girl manifestation "; public string NotFound => "角色不存在!"; public string Added => "已为玩家添加 {0} 个角色!"; public string UpdateLevel => "已将 {1} 个角色等级设置为 {0}!"; public string UpdateNeuronicLevel => "已将 {1} 个角色的神经元等级设置为 {0}!"; + public string UpdateManifestationLevel => "已将 {1} 个角色的天启/武格等级设置为 {0}!"; } /// @@ -268,6 +271,17 @@ public class DebugTextCHS public string FileDisabled => "个人调试文件输出已禁用。"; } +/// +/// path: Game.Command.GmMenu +/// +public class GmMenuTextCHS +{ + public string Desc => "切换GM选单"; + public string Usage => "用法: /gm [on|off]"; + public string Enabled => "已启用GM选单。"; + public string Disabled => "已禁用GM选单。"; +} + #endregion #endregion \ No newline at end of file diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index 4a956d9..7289b79 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -129,6 +129,7 @@ public class CommandTextCHT public GirlTextCHT Girl { get; } = new(); public GiveAllTextCHT GiveAll { get; } = new(); public DebugTextCHT Debug { get; } = new(); + public GmMenuTextCHT GmMenu { get; } = new(); } #endregion @@ -227,12 +228,14 @@ public class GirlTextCHT public string Usage => "用法: /girl add -p -l -s\n" + "用法: /girl level \n" + - "用法: /girl neuronic "; + "用法: /girl neuronic \n" + + "用法: /girl manifestation "; public string NotFound => "角色不存在!"; public string Added => "已為玩家新增 {0} 個角色!"; public string UpdateLevel => "已將 {1} 個角色等級設為 {0}!"; public string UpdateNeuronicLevel => "已將 {1} 個角色的神經元等級設置為 {0}!"; + public string UpdateManifestationLevel => "已将 {1} 个角色的天啟/武格等级设置为 {0}!"; } /// @@ -268,6 +271,17 @@ public class DebugTextCHT public string FileDisabled => "個人調試檔案輸出已停用。"; } +/// +/// path: Game.Command.GmMenu +/// +public class GmMenuTextCHT +{ + public string Desc => "切換GM選單"; + public string Usage => "用法: /gm [on|off]"; + public string Enabled => "已啟用GM選單。"; + public string Disabled => "已停用GM選單。"; +} + #endregion #endregion \ No newline at end of file diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index 6e25631..cda88b0 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -88,6 +88,7 @@ public class CommandTextEN public GirlTextEN Girl { get; } = new(); public GiveAllTextEN GiveAll { get; } = new(); public DebugTextEN Debug { get; } = new(); + public GmMenuTextEN GmMenu { get; } = new(); } #endregion @@ -193,12 +194,15 @@ public class GirlTextEN public string Usage => "Usage: /girl add -p -l -s\n" + "Usage: /girl level \n" + - "Usage: /girl neuronic "; + "Usage: /girl neuronic \n" + + "Usage: /girl manifestation "; public string NotFound => "Character not found!"; public string Added => "Granted {0} character(s) to player!"; public string UpdateLevel => "Set {1} character(s) to level {0}!"; public string UpdateNeuronicLevel => "Set {1} character(s) Neuronic to level {0}!"; + + public string UpdateManifestationLevel => "Set {1} character(s) Manifestation to level {0}!"; } /// @@ -234,6 +238,17 @@ public class DebugTextEN public string FileDisabled => "Personal debug file output disabled."; } +/// +/// path: Game.Command.GmMenu +/// +public class GmMenuTextEN +{ + public string Desc => "Toggle GM menu"; + public string Usage => "Usage: /gm [on|off]"; + public string Enabled => "GM menu enabled."; + public string Disabled => "GM menu disabled."; +} + #endregion #endregion \ No newline at end of file diff --git a/GameServer/Command/Commands/CommandGirl.cs b/GameServer/Command/Commands/CommandGirl.cs index afeff25..24839d2 100644 --- a/GameServer/Command/Commands/CommandGirl.cs +++ b/GameServer/Command/Commands/CommandGirl.cs @@ -129,4 +129,41 @@ await arg.SendMsg(I18NManager.Translate("Game.Command.Girl.UpdateNeuronicLevel", level.ToString(), girls.Count.ToString())); } + + [CommandMethod("manifestation")] + public async ValueTask UpdateManifestationLevel(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (!await arg.CheckArgCnt(2)) return; + + var guid = arg.GetInt(0); + var level = Math.Clamp(arg.GetInt(1), 0, 45); + var player = arg.Target!.Player!; + List girls = []; + + if (guid == -1) + { + foreach (var girl in player.CharacterManager.CharacterData.Characters) + { + girl.Break = (uint)level; + girls.Add(girl); + } + } + else + { + var girl = player.CharacterManager.GetCharacterByGUID((uint)guid); + if (girl == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.Girl.NotFound")); + return; + } + girl.Break = (uint)level; + girls.Add(girl); + } + + if (girls.Count > 0) await player.SendPacket(new PacketNtfCallScript(girls)); + await arg.SendMsg(I18NManager.Translate("Game.Command.Girl.UpdateManifestationLevel", + level.ToString(), + girls.Count.ToString())); + } } \ No newline at end of file diff --git a/GameServer/Command/Commands/CommandGmMenu.cs b/GameServer/Command/Commands/CommandGmMenu.cs new file mode 100644 index 0000000..f58d842 --- /dev/null +++ b/GameServer/Command/Commands/CommandGmMenu.cs @@ -0,0 +1,50 @@ +using MikuSB.Configuration; +using MikuSB.Enums.Player; +using MikuSB.Util; +using MikuSB.Internationalization; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Command.Commands; + +[CommandInfo("gm", "Game.Command.GmMenu.Desc", "Game.Command.GmMenu.Usage", ["gm"], [PermEnum.Admin, PermEnum.Support])] +public class CommandGmMenu : ICommands +{ + private static readonly Logger Logger = new("CommandManager"); + + [CommandDefault] + public async ValueTask ToggleGmMenu(CommandArg arg) + { + var option = arg.Args.FirstOrDefault()?.ToLowerInvariant() ?? "on"; + var serverOption = ConfigManager.Config.ServerOption; + var player = arg.Target?.Player; + var message = option switch + { + "on" => await EnableGmMenu(serverOption, player), + "off" => await DisableGmMenu(serverOption, player), + _ => I18NManager.Translate("Game.Command.GmMenu.Usage") + }; + + Logger.Info(message); + await arg.SendMsg(message); + } + + private static async ValueTask EnableGmMenu(ServerOption serverOption, Game.Player.PlayerInstance? player) + { + serverOption.EnableGmMenu = true; + if (player != null) await player.SendScript("gm.notifylogin", new JsonObject + { + ["IsDebug"] = ConfigManager.Config.ServerOption.EnableGmMenu + }.ToJsonString()); + return I18NManager.Translate("Game.Command.GmMenu.Enabled"); + } + + private static async ValueTask DisableGmMenu(ServerOption serverOption, Game.Player.PlayerInstance? player) + { + serverOption.EnableGmMenu = false; + if (player != null) await player.SendScript("gm.notifylogin", new JsonObject + { + ["IsDebug"] = ConfigManager.Config.ServerOption.EnableGmMenu + }.ToJsonString()); + return I18NManager.Translate("Game.Command.GmMenu.Disabled"); + } +} diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 1b77b98..dcd5ad7 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -143,6 +143,13 @@ public async ValueTask SendPacket(int cmdId, IMessage msg) if (Connection?.IsOnline == true) await Connection.SendPacket(cmdId,msg); } + public async Task SendScript(string api, string arg, NtfSyncPlayer extra = null!) + { + if (Connection?.IsOnline != true) return; + var rsp = new NtfCallScript { Api = api, Arg = arg, ExtraSync = extra }; + await Connection.SendPacket(CmdIds.NtfScript, rsp); + } + #endregion #region Actions diff --git a/Proxy/ProxyServer.cs b/Proxy/ProxyServer.cs index 085702f..cea39d0 100644 --- a/Proxy/ProxyServer.cs +++ b/Proxy/ProxyServer.cs @@ -18,8 +18,6 @@ public sealed class ProxyServer( HttpClient httpClient, Logger logger) : BackgroundService { - private const string ListenAddress = "127.0.0.1"; - private const string ServerHost = "127.0.0.1"; private static readonly string[] TargetDomains = [ "amazingseasuncdn.com", @@ -58,27 +56,37 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) return; } - var address = IPAddress.Parse(ListenAddress); - _listener = new TcpListener(address, _options.Port); - _listener.Start(); - logger.Info($"MikuSB proxy listening on {ListenAddress}:{_options.Port}"); - - try + for (var i = 0; i < 20; i++) { - while (!stoppingToken.IsCancellationRequested) + try + { + var address = IPAddress.Parse(_options.BindAddress); + _listener = new TcpListener(address, _options.Port); + _listener.Start(); + logger.Info($"MikuSB proxy listening on {_listener.LocalEndpoint}"); + + while (!stoppingToken.IsCancellationRequested) + { + var client = await _listener.AcceptTcpClientAsync(stoppingToken); + _ = Task.Run(() => HandleClientAsync(client, stoppingToken), stoppingToken); + } + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + } + catch (SocketException) when (stoppingToken.IsCancellationRequested) + { + // ignore stop + } + catch (SocketException ex) + { + logger.Info($"MikuSB proxy listening error {ex}"); + _options.Port += Random.Shared.Next(1, 500); // try another port + await Task.Delay(100, stoppingToken); + } + catch (ObjectDisposedException) when (stoppingToken.IsCancellationRequested) { - var client = await _listener.AcceptTcpClientAsync(stoppingToken); - _ = Task.Run(() => HandleClientAsync(client, stoppingToken), stoppingToken); } - } - catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) - { - } - catch (SocketException) when (stoppingToken.IsCancellationRequested) - { - } - catch (ObjectDisposedException) when (stoppingToken.IsCancellationRequested) - { } } @@ -94,7 +102,11 @@ private async Task HandleClientAsync(TcpClient client, CancellationToken cancell { using (client) { - //logger.Debug($"Proxy New client: {client.Client.RemoteEndPoint}"); + // TODO: pass client.Client.LocalEndPoint to SDK server + // let SDK server return IP/address for client to connect with + // api: /api/serverlist, /query_version, /query + var cliAddr = client.Client.RemoteEndPoint; + // logger.Debug($"Proxy New client: {cliAddr}"); try { await HandleClientCoreAsync(client, cancellationToken); @@ -116,7 +128,7 @@ private async Task HandleClientAsync(TcpClient client, CancellationToken cancell { logger.Warn($"Proxy client failed {ex}"); } - logger.Info($"Proxy client close: {client.Client.RemoteEndPoint}"); + // logger.Debug($"Proxy client close: {cliAddr}"); } } @@ -197,7 +209,7 @@ private async Task HandleRedirectedHttpLoopAsync(Stream tlsStream, string origin private async Task ForwardToServerAsync(Stream clientStream, ProxyHttpRequest request, CancellationToken cancellationToken) { var pathAndQuery = request.GetPathAndQuery(); - var uri = new Uri($"http://{ServerHost}:{_options.ServerHttpPort}{pathAndQuery}"); + var uri = new Uri($"http://{_options.ServerHttpAddress}:{_options.ServerHttpPort}{pathAndQuery}"); if (ConfigManager.Config.HttpServer.EnableLog) logger.Info($"Redirect: {request.Method} {request.HostOverride ?? request.Host}{pathAndQuery} -> {uri}"); await SendHttpRequestAsync(clientStream, request, uri, true, cancellationToken); } @@ -227,7 +239,7 @@ private bool IsSelfReference(Uri uri) return false; return uri.Host is "127.0.0.1" or "localhost" or "::1" - || uri.Host.Equals(ListenAddress, StringComparison.OrdinalIgnoreCase); + || uri.Host.Equals(_options.BindAddress, StringComparison.OrdinalIgnoreCase); } private async Task SendHttpRequestAsync(Stream clientStream, ProxyHttpRequest request, Uri uri, bool addCors, CancellationToken cancellationToken) diff --git a/README_linux.md b/README_linux.md index 36b910b..bfad8ee 100644 --- a/README_linux.md +++ b/README_linux.md @@ -5,7 +5,7 @@ ### 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%` +`ALL_PROXY="http://127.0.0.1:8888" %command%` ### start local server and keep it running @@ -13,12 +13,12 @@ ./MikuSB ``` -### find root CA cert, and create ca bundle +### ~~find root CA cert, and create ca bundle~~ no neeeded root CA cert, should in the path: `proxy-certs/MikuSB.Proxy.Root.pem` -### setup root CA for proton/wine +### ~~setup root CA for proton/wine~~ no neeeded not sure, even I remove Proton PFX (Wine prefix) folder, without redo this step, still no cert issue. diff --git a/SdkServer/Handlers/RouteController.cs b/SdkServer/Handlers/RouteController.cs index a33db5c..409f545 100644 --- a/SdkServer/Handlers/RouteController.cs +++ b/SdkServer/Handlers/RouteController.cs @@ -11,6 +11,10 @@ namespace MikuSB.SdkServer.Handlers; public class RouteController : ControllerBase { public static ConfigContainer Config = ConfigManager.Config; + public static JsonSerializerOptions JsonOption = new() + { + PropertyNamingPolicy = null, // no snake_case + }; public static object BuildServerList(string version = "") { @@ -95,7 +99,9 @@ public IActionResult GetGameConfig() msg = "success" }; - return Ok(rsp); + // return Ok(rsp); + var json = JsonSerializer.Serialize(rsp, JsonOption); + return Content(json, "application/json"); } [HttpGet("/seasun/config")] @@ -105,48 +111,80 @@ public IActionResult GetSeasunConfig() object rsp = new { code = 0, + msg = "操作成功", data = new { - agreementUpdateTime = "1728552600000", - appDownLoadUrl = "", - enableReportDataToDouyin = false, - loginType = new[] { "channel" }, - openActivationCode = false, - qqGroup = (string?)null, - privacyUpdateTime = "1728552600000", - realNameAuth = false + platformPrivacyAgreement = "", // https url + payType = new[] + { + "mycard", + }, + loginType = new[] + { + "mail", + "google", + "twitter", + "guest", + "steam", + }, + closeGeetest = false, + userAgreement = "", // https url + privacyAgreement = "", // https url + initPrivacyUpdateTime = 0, + platformUserAgreement = "", // https url + // accountPublicKey = "", // pem string? + payChannel = Array.Empty(), + registerPrivacyUrl = "", // https url + loginPrivacyUrl = "", // https url + // agreementUpdateTime = "1728552600000", + // appDownLoadUrl = "", + // enableReportDataToDouyin = false, + // openActivationCode = false, + // qqGroup = (string?)null, + // privacyUpdateTime = "1728552600000", + // realNameAuth = false }, - msg = "success" }; - return Ok(rsp); + // return Ok(rsp); + var json = JsonSerializer.Serialize(rsp, JsonOption); + return Content(json, "application/json"); + } + + + public class LoginByTokenReq + { + public string? uid { get; set; } + public string? token { get; set; } } [HttpGet("/seasun/loginByToken")] [HttpPost("/seasun/loginByToken")] public IActionResult LoginByToken( - [FromQuery] string? uid, - [FromQuery] string? token, + // [FromQuery] string? uid, + // [FromQuery] string? token, [FromForm] string? form_uid, - [FromForm] string? form_token + [FromForm] string? form_token, + [FromBody] LoginByTokenReq? body ) { - string finalUid = uid ?? form_uid ?? "10001"; - string finalToken = token ?? form_token ?? Guid.NewGuid().ToString("N"); + string finalUid = body?.uid ?? form_uid ?? "10001"; + string finalToken = body?.token ?? form_token ?? Guid.NewGuid().ToString("N"); object rsp = new { code = 0, data = new { - associatedAccounts = new[] - { - new { bindStatus = false, nickname = "", thirdPartyType = "mail" }, - new { bindStatus = true, nickname = Config.GameServer.GameServerName, thirdPartyType = "google" }, - new { bindStatus = false, nickname = "", thirdPartyType = "twitter" }, - new { bindStatus = false, nickname = "", thirdPartyType = "guest" }, - new { bindStatus = false, nickname = "", thirdPartyType = "steam" } - }, + // associatedAccounts = new[] + // { + // new { bindStatus = false, nickname = "", thirdPartyType = "mail" }, + // new { bindStatus = true, nickname = Config.GameServer.GameServerName, thirdPartyType = "google" }, + // new { bindStatus = false, nickname = "", thirdPartyType = "twitter" }, + // new { bindStatus = false, nickname = "", thirdPartyType = "guest" }, + // new { bindStatus = false, nickname = "", thirdPartyType = "steam" } + // }, + associatedAccounts = Array.Empty(), isFirstLogin = false, isNeedKoreaSciAuth = false, ksOpenId = $"ks_{finalUid}", @@ -156,13 +194,15 @@ [FromForm] string? form_token status = 0, thirdPartyUid = "", token = finalToken, - type = "google", - uid = finalUid + type = "guest", // google + uid = long.Parse(finalUid), }, msg = "操作成功" }; - return Ok(rsp); + // return Ok(rsp); + var json = JsonSerializer.Serialize(rsp, JsonOption); + return Content(json, "application/json"); } [HttpGet("/seasun/getAccountInfoForGame")] diff --git a/SdkServer/SdkServer.cs b/SdkServer/SdkServer.cs index bc9fed6..342f2e8 100644 --- a/SdkServer/SdkServer.cs +++ b/SdkServer/SdkServer.cs @@ -23,7 +23,7 @@ public static void Start(string[] args) webBuilder .UseStartup() .ConfigureLogging((_, logging) => { logging.ClearProviders(); }) - .UseUrls(ConfigManager.Config.HttpServer.GetDisplayAddress()); + .UseUrls(ConfigManager.Config.HttpServer.GetBindDisplayAddress()); }); var host = builder.Build();