diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 53465122..ec0bf863 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -27,19 +27,19 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Publish win-x64 - run: dotnet publish Program\Program.csproj -o Release\win-x64 -r win-x64 --framework net8.0 -p:PublishSingleFile=true + run: dotnet publish Program\Program.csproj -o Release\win-x64 -r win-x64 --framework net9.0 -p:PublishSingleFile=true - name: Publish linux-x64 - run: dotnet publish Program\Program.csproj -o Release\linux-x64 -r linux-x64 --framework net8.0 -p:PublishSingleFile=true + run: dotnet publish Program\Program.csproj -o Release\linux-x64 -r linux-x64 --framework net9.0 -p:PublishSingleFile=true - name: Publish linux-arm64 - run: dotnet publish Program\Program.csproj -o Release\linux-arm64 -r linux-arm64 --framework net8.0 -p:PublishSingleFile=true + run: dotnet publish Program\Program.csproj -o Release\linux-arm64 -r linux-arm64 --framework net9.0 -p:PublishSingleFile=true - name: Upload win-x64 Build Artifact uses: actions/upload-artifact@v4.3.1 diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs index 7577fb85..466cf3b9 100644 --- a/Common/Configuration/ConfigContainer.cs +++ b/Common/Configuration/ConfigContainer.cs @@ -17,6 +17,7 @@ public class HttpServerConfig public string PublicAddress { get; set; } = "127.0.0.1"; public int Port { get; set; } = 443; public bool UseSSL { get; set; } = false; + public bool UseFetchRemoteHotfix { get; set; } = false; public string GetDisplayAddress() { diff --git a/Common/Configuration/HotfixContainer.cs b/Common/Configuration/HotfixContainer.cs index 152a7584..c6b1e067 100644 --- a/Common/Configuration/HotfixContainer.cs +++ b/Common/Configuration/HotfixContainer.cs @@ -21,6 +21,14 @@ public class DownloadUrlConfig public string IfixUrl { get; set; } = ""; } +public static class GateWayBaseUrl +{ + public const string CNBETA = "https://beta-release01-cn.bhsr.com/query_gateway"; + public const string CNPROD = "https://prod-gf-cn-dp01.bhsr.com/query_gateway"; + public const string OSBETA = "https://beta-release01-asia.starrails.com/query_gateway"; + public const string OSPROD = "https://prod-official-asia-dp01.starrails.com/query_gateway"; +} + public static class BaseUrl { public const string CN = "https://autopatchcn.bhsr.com/"; diff --git a/Common/Util/HttpNetwork.cs b/Common/Util/HttpNetwork.cs index c859e3b0..f21d0714 100644 --- a/Common/Util/HttpNetwork.cs +++ b/Common/Util/HttpNetwork.cs @@ -2,11 +2,12 @@ namespace EggLink.DanhengServer.Util; public static class HttpNetwork { - public static async ValueTask<(int, string?)> SendGetRequest(string url) + public static async ValueTask<(int, string?)> SendGetRequest(string url, int timeout = 30) { try { using var client = new HttpClient(); + client.Timeout = TimeSpan.FromSeconds(timeout); var response = await client.GetAsync(url); var content = await response.Content.ReadAsStringAsync(); return ((int)response.StatusCode, content); diff --git a/WebServer/Controllers/GateServerRoutes.cs b/WebServer/Controllers/GateServerRoutes.cs index 0dba6fa7..ba826068 100644 --- a/WebServer/Controllers/GateServerRoutes.cs +++ b/WebServer/Controllers/GateServerRoutes.cs @@ -1,7 +1,7 @@ using EggLink.DanhengServer.Util; using EggLink.DanhengServer.WebServer.Handler; using Microsoft.AspNetCore.Mvc; - +using EggLink.DanhengServer.WebServer.Request; namespace EggLink.DanhengServer.WebServer.Controllers; [ApiController] @@ -9,7 +9,7 @@ namespace EggLink.DanhengServer.WebServer.Controllers; public class GateServerRoutes { [HttpGet("/query_gateway")] - public async ValueTask QueryGateway([FromQuery] string version) + public async ValueTask QueryGateway([FromQuery] GateWayRequest req) { if (!ConfigManager.Config.ServerOption.ServerConfig.RunGateway) return new ContentResult @@ -20,7 +20,7 @@ public class GateServerRoutes await ValueTask.CompletedTask; return new ContentResult { - Content = new QueryGatewayHandler(version).Data, + Content = new QueryGatewayHandler(req).Data, StatusCode = 200, ContentType = "plain/text; charset=utf-8" }; diff --git a/WebServer/Handler/QueryGatewayHandler.cs b/WebServer/Handler/QueryGatewayHandler.cs index 27b75b5d..d5907402 100644 --- a/WebServer/Handler/QueryGatewayHandler.cs +++ b/WebServer/Handler/QueryGatewayHandler.cs @@ -5,6 +5,7 @@ using EggLink.DanhengServer.Enums; using EggLink.DanhengServer.Internationalization; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; +using EggLink.DanhengServer.WebServer.Request; using Google.Protobuf; namespace EggLink.DanhengServer.WebServer.Handler; @@ -14,7 +15,7 @@ internal partial class QueryGatewayHandler public static Logger Logger = new("GatewayServer"); public string Data; - public QueryGatewayHandler(string version) + public QueryGatewayHandler(GateWayRequest req) { var config = ConfigManager.Config; @@ -39,7 +40,7 @@ internal partial class QueryGatewayHandler // Auto separate CN/OS prefix var region = ConfigManager.Hotfix.Region; - if (region == BaseRegionEnum.None) _ = Enum.TryParse(version[..2], out region); + if (region == BaseRegionEnum.None) _ = Enum.TryParse(req.version[..2], out region); var baseUrl = region switch { BaseRegionEnum.CN => BaseUrl.CN, @@ -47,21 +48,15 @@ internal partial class QueryGatewayHandler _ => BaseUrl.OS }; - // Separate CN/OS hotfix by client - var ver = VersionRegex().Replace(version, ""); - ConfigManager.Hotfix.HotfixData.TryGetValue(ver, out var urls); - if (urls != null) + var remoteHotfixSuccess = false; + if (ConfigManager.Config.HttpServer.UseFetchRemoteHotfix) { - if (urls.AssetBundleUrl != "") - gateServer.AssetBundleUrl = baseUrl + urls.AssetBundleUrl; - if (urls.ExAssetBundleUrl != "") - gateServer.ExAssetBundleUrl = baseUrl + urls.ExAssetBundleUrl; - if (urls.ExResourceUrl != "") - gateServer.ExResourceUrl = baseUrl + urls.ExResourceUrl; - if (urls.LuaUrl != "") - gateServer.LuaUrl = baseUrl + urls.LuaUrl; - if (urls.IfixUrl != "") - gateServer.IfixUrl = baseUrl + urls.IfixUrl; + remoteHotfixSuccess = FetchRemoteHotfix(req, region, gateServer).GetAwaiter().GetResult(); + } + + if (!remoteHotfixSuccess) + { + UseLocalHotfix(req, region, baseUrl, gateServer); } if (!ResourceManager.IsLoaded) gateServer.Retcode = 2; @@ -70,6 +65,132 @@ internal partial class QueryGatewayHandler Data = Convert.ToBase64String(gateServer.ToByteArray()); } + private async Task FetchRemoteHotfix(GateWayRequest req, BaseRegionEnum region, GateServer gateServer) + { + try + { + var gatewayUrl = GetGatewayUrlByVersion(req.version); + // build query params + var queryParams = new Dictionary + { + ["version"] = req.version, + ["t"] = req.t, + ["uid"] = req.uid, + ["language_type"] = req.language_type, + ["platform_type"] = req.platform_type, + ["dispatch_seed"] = req.dispatch_seed, + ["channel_id"] = req.channel_id, + ["sub_channel_id"] = req.sub_channel_id, + ["is_need_url"] = req.is_need_url, + ["game_version"] = req.game_version, + ["account_type"] = req.account_type, + ["account_uid"] = req.account_uid + }; + + var queryString = string.Join("&", queryParams.Select(kv => $"{kv.Key}={kv.Value}")); + var fullUrl = $"{gatewayUrl}?{queryString}"; + + var (statusCode, response) = await HttpNetwork.SendGetRequest(fullUrl, 5); + + if (statusCode == 200 && !string.IsNullOrEmpty(response)) + { + try + { + // parse base64 response + var bytes = Convert.FromBase64String(response); + var remoteGateServer = GateServer.Parser.ParseFrom(bytes); + + // check if remote hotfix urls are valid, if not use local configuration + if (!string.IsNullOrEmpty(remoteGateServer.AssetBundleUrl)) + { + gateServer.AssetBundleUrl = remoteGateServer.AssetBundleUrl; + gateServer.ExAssetBundleUrl = remoteGateServer.ExAssetBundleUrl; + gateServer.ExResourceUrl = remoteGateServer.ExResourceUrl; + gateServer.LuaUrl = remoteGateServer.LuaUrl; + gateServer.IfixUrl = remoteGateServer.IfixUrl; + + return true; + } + else + { + Logger.Warn("Remote hotfix return empty, fall back to local hotfix"); + } + } + catch (Exception ex) + { + Logger.Warn($"Failed to parse remote hotfix response: {ex.Message}"); + } + } + else + { + Logger.Warn($"Remote hotfix request failed with status: {statusCode}"); + } + } + catch (Exception ex) + { + Logger.Warn($"Remote hotfix fetch failed: {ex.Message}"); + } + + return false; + } + + private void UseLocalHotfix(GateWayRequest req, BaseRegionEnum region, string baseUrl, GateServer gateServer) + { + var ver = VersionRegex().Replace(req.version, ""); + ConfigManager.Hotfix.HotfixData.TryGetValue(ver, out var urls); + + if (urls != null) + { + if (!string.IsNullOrEmpty(urls.AssetBundleUrl)) + gateServer.AssetBundleUrl = baseUrl + urls.AssetBundleUrl; + if (!string.IsNullOrEmpty(urls.ExAssetBundleUrl)) + gateServer.ExAssetBundleUrl = baseUrl + urls.ExAssetBundleUrl; + if (!string.IsNullOrEmpty(urls.ExResourceUrl)) + gateServer.ExResourceUrl = baseUrl + urls.ExResourceUrl; + if (!string.IsNullOrEmpty(urls.LuaUrl)) + gateServer.LuaUrl = baseUrl + urls.LuaUrl; + if (!string.IsNullOrEmpty(urls.IfixUrl)) + gateServer.IfixUrl = baseUrl + urls.IfixUrl; + } + else + { + Logger.Warn($"No local hotfix found for version: {ver}"); + } + } + + private string GetGatewayUrlByVersion(string version) + { + if (version.Contains("CNPROD", StringComparison.OrdinalIgnoreCase)) + { + return GateWayBaseUrl.CNPROD; + } + else if (version.Contains("CNBETA", StringComparison.OrdinalIgnoreCase)) + { + return GateWayBaseUrl.CNBETA; + } + else if (version.Contains("OSPROD", StringComparison.OrdinalIgnoreCase)) + { + return GateWayBaseUrl.OSPROD; + } + else if (version.Contains("OSBETA", StringComparison.OrdinalIgnoreCase)) + { + return GateWayBaseUrl.OSBETA; + } + else + { + // default fallback based on region prefix + var region = version[..2]; + if (region.Equals("CN", StringComparison.OrdinalIgnoreCase)) + { + return GateWayBaseUrl.CNPROD; + } + else + { + return GateWayBaseUrl.OSPROD; + } + } + } + [GeneratedRegex(@"BETA|PROD|CECREATION|Android|Win|iOS")] private static partial Regex VersionRegex(); } \ No newline at end of file diff --git a/WebServer/Request/GateWayRequest.cs b/WebServer/Request/GateWayRequest.cs new file mode 100644 index 00000000..5864f58e --- /dev/null +++ b/WebServer/Request/GateWayRequest.cs @@ -0,0 +1,17 @@ +namespace EggLink.DanhengServer.WebServer.Request; + +public class GateWayRequest +{ + public string version { get; set; } = ""; + public string t { get; set; } = ""; + public string uid { get; set; } = ""; + public string language_type { get; set; } = ""; + public string platform_type { get; set; } = ""; + public string dispatch_seed { get; set; } = ""; + public string channel_id { get; set; } = ""; + public string sub_channel_id { get; set; } = ""; + public string is_need_url { get; set; } = ""; + public string game_version { get; set; } = ""; + public string account_type { get; set; } = ""; + public string account_uid { get; set; } = ""; +}