diff --git a/AscNet.Common/Config.cs b/AscNet.Common/Config.cs index 34889831..a247c3a1 100644 --- a/AscNet.Common/Config.cs +++ b/AscNet.Common/Config.cs @@ -19,6 +19,9 @@ namespace AscNet.Common interface IGameServer { + [Option(DefaultValue = nameof(AscNet))] + string RegionName { get; set; } + [Option(DefaultValue = "127.0.0.1")] string Host { get; set; } diff --git a/AscNet.Common/Util/Logger.cs b/AscNet.Common/Util/Logger.cs index 5d47fd5e..5321e9cf 100644 --- a/AscNet.Common/Util/Logger.cs +++ b/AscNet.Common/Util/Logger.cs @@ -4,7 +4,7 @@ namespace AscNet.Common.Util { public class Logger { - public static readonly Logger c = new("SF", ConsoleColor.DarkRed); + public static readonly Logger c = new(nameof(AscNet), ConsoleColor.DarkRed); private readonly string _name; private readonly bool TraceOnError; private readonly ConsoleColor _color; diff --git a/AscNet.Common/Util/TsvTool.cs b/AscNet.Common/Util/TsvTool.cs new file mode 100644 index 00000000..04e8388d --- /dev/null +++ b/AscNet.Common/Util/TsvTool.cs @@ -0,0 +1,37 @@ +using System.Reflection; + +namespace AscNet.Common.Util +{ + public static class TsvTool + { + public static string SerializeObject(IEnumerable objs) where T : new() + { + string res = string.Empty; + + Type type = typeof(T); + var properties = type.GetProperties() + .OrderBy(p => p.GetCustomAttribute()?.Order ?? int.MaxValue) + .ToList(); + + res += string.Join('\t', properties.Select(x => x.Name)) + '\n'; + + foreach (var obj in objs) + { + res += string.Join('\t', properties.Select(x => x.GetValue(obj))) + '\n'; + } + + return res; + } + } + + [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class PropertyOrderAttribute : Attribute + { + public int Order { get; } + + public PropertyOrderAttribute(int order) + { + Order = order; + } + } +} diff --git a/AscNet.SDKServer/AscNet.SDKServer.csproj b/AscNet.SDKServer/AscNet.SDKServer.csproj index 3badfc4b..f561c44a 100644 --- a/AscNet.SDKServer/AscNet.SDKServer.csproj +++ b/AscNet.SDKServer/AscNet.SDKServer.csproj @@ -16,7 +16,6 @@ - diff --git a/AscNet.SDKServer/Controllers/ConfigController.cs b/AscNet.SDKServer/Controllers/ConfigController.cs index 7ab32bb9..15a8cab4 100644 --- a/AscNet.SDKServer/Controllers/ConfigController.cs +++ b/AscNet.SDKServer/Controllers/ConfigController.cs @@ -1,10 +1,59 @@ -namespace AscNet.SDKServer.Controllers +using AscNet.Common.Util; +using AscNet.SDKServer.Models; +using Newtonsoft.Json; + +namespace AscNet.SDKServer.Controllers { internal class ConfigController : IRegisterable { + private static readonly Dictionary versions = new(); + + static ConfigController() + { + versions = JsonConvert.DeserializeObject>(File.ReadAllText("./Configs/version_config.json"))!; + } + public static void Register(WebApplication app) { + app.MapGet("/prod/client/config/com.kurogame.punishing.grayraven.en.pc/{version}/standalone/config.tab", (HttpContext ctx) => + { + List remoteConfigs = new(); + ServerVersionConfig versionConfig = versions.GetValueOrDefault((string)ctx.Request.RouteValues["version"]!) ?? versions.First().Value; + foreach (var property in typeof(ServerVersionConfig).GetProperties()) + remoteConfigs.AddConfig(property.Name, (string)property.GetValue(versionConfig)!); + + remoteConfigs.AddConfig("ApplicationVersion", (string)ctx.Request.RouteValues["version"]!); + remoteConfigs.AddConfig("Debug", true); + remoteConfigs.AddConfig("External", true); + remoteConfigs.AddConfig("Channel", 1); + remoteConfigs.AddConfig("PayCallbackUrl", "empty"); + remoteConfigs.AddConfig("PrimaryCdns", "http://prod-encdn-akamai.kurogame.net/prod|http://prod-encdn-aliyun.kurogame.net/prod"); + remoteConfigs.AddConfig("SecondaryCdns", "http://prod-encdn-aliyun.kurogame.net/prod"); + remoteConfigs.AddConfig("CdnInvalidTime", 600); + remoteConfigs.AddConfig("MtpEnabled", false); + remoteConfigs.AddConfig("MemoryLimit", 2048); + remoteConfigs.AddConfig("CloseMsgEncrypt", false); + remoteConfigs.AddConfig("ServerListStr", $"{Common.Common.config.GameServer.RegionName}#{Common.Common.config.GameServer.Host}/api/Login/Login"); + remoteConfigs.AddConfig("AndroidPayCallbackUrl", $"{Common.Common.config.GameServer.Host}/api/XPay/HeroHgAndroidPayResult"); // i just wanna know what this is + remoteConfigs.AddConfig("IosPayCallbackUrl", $"{Common.Common.config.GameServer.Host}/api/XPay/HeroHgIosPayResult"); // i just wanna know what this is + remoteConfigs.AddConfig("WatermarkEnabled", true); // i just wanna know what this is + remoteConfigs.AddConfig("PicComposition", "empty"); // i just wanna know what this is + remoteConfigs.AddConfig("DeepLinkEnabled", true); + remoteConfigs.AddConfig("DownloadMethod", 1); + remoteConfigs.AddConfig("PcPayCallbackList", $"{Common.Common.config.GameServer.Host}/api/XPay/KuroPayResult"); + + return TsvTool.SerializeObject(remoteConfigs); + }); + + app.MapPost("/feedback", (HttpContext ctx) => + { + return ctx.Response.WriteAsJsonAsync(new + { + code = 0, + msg = "ok" + }); + }); } } } diff --git a/AscNet.SDKServer/Models/RemoteConfig.cs b/AscNet.SDKServer/Models/RemoteConfig.cs new file mode 100644 index 00000000..4df414d2 --- /dev/null +++ b/AscNet.SDKServer/Models/RemoteConfig.cs @@ -0,0 +1,56 @@ +using AscNet.Common.Util; + +namespace AscNet.SDKServer.Models +{ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public class RemoteConfig + { + [PropertyOrder(0)] + public string Key { get; set; } + + [PropertyOrder(1)] + public string Type { get; set; } + + [PropertyOrder(2)] + public string Value { get; set; } + } + + public static class RemoteConfigExtension + { + public static void AddConfig(this List remoteConfigs, string key, T value) + { + if (!typeMap.TryGetValue(typeof(T), out string? typeStr) || value is null) + throw new ArgumentException("Unsupported value!", nameof(value)); + + RemoteConfig remoteConfig = new() + { + Key = key, + Type = typeStr + }; + + switch (typeStr) + { + case "int": + case "float": + case "string": + remoteConfig.Value = value.ToString() ?? ""; + break; + case "bool": + remoteConfig.Value = value.ToString() == "True" ? "1" : "0"; + break; + default: + break; + } + + remoteConfigs.Add(remoteConfig); + } + + private static Dictionary typeMap = new Dictionary + { + { typeof(string), "string" }, + { typeof(int), "int" }, + { typeof(bool), "bool" }, + { typeof(float), "float" }, + }; + } +} diff --git a/AscNet.SDKServer/Models/ServerVersionConfig.cs b/AscNet.SDKServer/Models/ServerVersionConfig.cs new file mode 100644 index 00000000..17162c82 --- /dev/null +++ b/AscNet.SDKServer/Models/ServerVersionConfig.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace AscNet.SDKServer.Models +{ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public class ServerVersionConfig + { + [JsonProperty("DocumentVersion", NullValueHandling = NullValueHandling.Ignore)] + public string DocumentVersion { get; set; } + + [JsonProperty("LaunchModuleVersion", NullValueHandling = NullValueHandling.Ignore)] + public string LaunchModuleVersion { get; set; } + + [JsonProperty("IndexMd5", NullValueHandling = NullValueHandling.Ignore)] + public string IndexMd5 { get; set; } + + [JsonProperty("IndexSha1", NullValueHandling = NullValueHandling.Ignore)] + public string IndexSha1 { get; set; } + + [JsonProperty("LaunchIndexSha1", NullValueHandling = NullValueHandling.Ignore)] + public string LaunchIndexSha1 { get; set; } + } +} diff --git a/Resources/Configs/version_config.json b/Resources/Configs/version_config.json new file mode 100644 index 00000000..756e2ccb --- /dev/null +++ b/Resources/Configs/version_config.json @@ -0,0 +1,9 @@ +{ + "1.30.0": { + "DocumentVersion": "1.30.3", + "LaunchModuleVersion": "1.30.3", + "IndexMd5": "c5d4baac85a6e37b8109ea43dc045d31", + "IndexSha1": "544254b3f611e4268c108c88a366f8743cc7045b", + "LaunchIndexSha1": "e5726b1edcaf486a95561eb01a72365ccb30dd3c" + } +} \ No newline at end of file diff --git a/proxy.py b/proxy.py new file mode 100644 index 00000000..9abc6b6d --- /dev/null +++ b/proxy.py @@ -0,0 +1,27 @@ +from mitmproxy import http +from mitmproxy import ctx +from mitmproxy.proxy import layer, layers + +def load(loader): + # ctx.options.web_open_browser = False + # We change the connection strategy to lazy so that next_layer happens before we actually connect upstream. + ctx.options.connection_strategy = "lazy" + ctx.options.upstream_cert = False + ctx.options.ssl_insecure = True + +def next_layer(nextlayer: layer.NextLayer): + # ctx.log( + # f"{nextlayer.context=}\n" + # f"{nextlayer.data_client()[:70]=}\n" + # ) + # this is HTTPS only + sni = nextlayer.context.client.sni + if sni and (sni.endswith("kurogame.net") or sni.endswith("kurogame-service.com")): + ctx.log('sni:' + sni) + nextlayer.context.server.address = ("127.0.0.1", 443) + +def request(flow: http.HTTPFlow) -> None: + if "kurogame.net" in flow.request.pretty_url or "kurogame-service.com" in flow.request.pretty_url: + flow.request.host = "localhost" + flow.request.headers["Host"] = "localhost" + pass