feat: auto fetch remote hotfix.

This commit is contained in:
初心浮梦
2025-12-01 03:51:12 +08:00
parent c66443d669
commit fd97cf49dd
6 changed files with 168 additions and 20 deletions

View File

@@ -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()
{

View File

@@ -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/";

View File

@@ -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);

View File

@@ -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<ContentResult> QueryGateway([FromQuery] string version)
public async ValueTask<ContentResult> 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"
};

View File

@@ -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<bool> FetchRemoteHotfix(GateWayRequest req, BaseRegionEnum region, GateServer gateServer)
{
try
{
var gatewayUrl = GetGatewayUrlByVersion(req.version);
// build query params
var queryParams = new Dictionary<string, string>
{
["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();
}

View File

@@ -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; } = "";
}