Completely offline (Thank you cs8425)

This commit is contained in:
Kei-Luna
2026-05-13 06:36:26 +09:00
parent 6740b8ecf7
commit e92b214161
7 changed files with 371 additions and 388 deletions

View File

@@ -107,16 +107,20 @@ public class RouteController : ControllerBase
code = 0,
data = new
{
agreementUpdateTime = "1728552600000",
appDownLoadUrl = "",
enableReportDataToDouyin = false,
loginType = new[] { "channel" },
openActivationCode = false,
qqGroup = (string?)null,
privacyUpdateTime = "1728552600000",
realNameAuth = false
platformPrivacyAgreement = "https://www.amazingseasun.com/privacy.html?lang=zh-Hant&gamecode=200001086",
payType = new[] { "mycard" },
loginType = new[] { "mail", "google", "twitter", "guest", "steam" },
closeGeetest = false,
userAgreement = "https://www.amazingseasun.com/user.html?lang=zh-Hant&gamecode=111111680",
privacyAgreement = "https://www.amazingseasun.com/privacy.html?lang=zh-Hant&gamecode=111111680",
initPrivacyUpdateTime = 0,
platformUserAgreement = "https://www.amazingseasun.com/user.html?lang=zh-Hant&gamecode=200001086",
accountPublicKey = "",
payChannel = (string[]?)null,
registerPrivacyUrl = "https://xgsdk.xoyo.games:13443/seasun/privacy-agreement/200001086/register/privacy.html?language=zh-Hant",
loginPrivacyUrl = "https://xgsdk.xoyo.games:13443/seasun/privacy-agreement/111111680/login/privacy.html?language=zh-Hant"
},
msg = "success"
msg = "操作成功"
};
return Ok(rsp);
@@ -133,31 +137,64 @@ public class RouteController : ControllerBase
{
string finalUid = uid ?? form_uid ?? "10001";
string finalToken = token ?? form_token ?? Guid.NewGuid().ToString("N");
int parsedUid = int.TryParse(finalUid, out var numericUid) ? numericUid : 10001;
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 = Array.Empty<string>(),
isFirstLogin = false,
isNeedKoreaSciAuth = false,
ksOpenId = $"ks_{finalUid}",
nickname = Config.GameServer.GameServerName,
passportId = finalUid.Length > 10 ? finalUid[^10..] : finalUid,
passportId = finalUid,
playerFillAgeUrl = "",
status = 0,
thirdPartyUid = "",
token = finalToken,
type = "google",
uid = finalUid
type = "guest",
uid = parsedUid
},
msg = "操作成功"
};
return Ok(rsp);
}
[HttpGet("/seasun/login")]
[HttpPost("/seasun/login")]
public IActionResult Login(
[FromQuery] string? uid,
[FromQuery] string? token,
[FromQuery] string? email,
[FromForm] string? form_uid,
[FromForm] string? form_token,
[FromForm] string? form_email
)
{
string finalUid = uid ?? form_uid ?? "10001";
string finalToken = token ?? form_token ?? Guid.NewGuid().ToString("N");
int parsedUid = int.TryParse(finalUid, out var numericUid) ? numericUid : 10001;
object rsp = new
{
code = 0,
data = new
{
associatedAccounts = Array.Empty<string>(),
isFirstLogin = false,
isNeedKoreaSciAuth = false,
ksOpenId = $"ks_{finalUid}",
nickname = Config.GameServer.GameServerName,
passportId = finalUid,
playerFillAgeUrl = "",
status = 0,
thirdPartyUid = "",
token = finalToken,
type = "guest",
uid = parsedUid
},
msg = "操作成功"
};
@@ -173,7 +210,6 @@ public class RouteController : ControllerBase
)
{
string uidString = uid ?? form_uid ?? "10001";
var finalUid = int.TryParse(uidString, out int parsedUid) ? parsedUid : 10001;
object rsp = new
{
@@ -184,8 +220,8 @@ public class RouteController : ControllerBase
channelUid = uidString,
loginAccountType = "google",
nickName = Config.GameServer.GameServerName,
passportId = uidString.Length > 10 ? uidString[^10..] : uidString,
uid = $"seasun__{uid}"
passportId = uidString,
uid = $"seasun__{uidString}"
},
msg = "操作成功"
};

View File

@@ -23,7 +23,24 @@ public static class SdkServer
webBuilder
.UseStartup<Startup>()
.ConfigureLogging((_, logging) => { logging.ClearProviders(); })
.UseUrls(ConfigManager.Config.HttpServer.GetDisplayAddress());
.ConfigureKestrel(serverOptions =>
{
// Pre-warm cert before first TLS handshake
_ = Utils.CertHelper.GetOrCreate(null);
var bindAddr = System.Net.IPAddress.Parse(ConfigManager.Config.HttpServer.BindAddress);
foreach (var port in new[] { ConfigManager.Config.HttpServer.Port, 13443, 18443, 31443 })
{
serverOptions.Listen(bindAddr, port, o =>
{
o.UseHttps(https =>
{
https.ServerCertificateSelector = (_, sni) =>
Utils.CertHelper.GetOrCreate(sni);
});
});
}
});
});
var host = builder.Build();

View File

@@ -0,0 +1,97 @@
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace MikuSB.SdkServer.Utils;
public static class CertHelper
{
private const string Password = "MikuSB.SdkServer.LocalTLS";
private static readonly ConcurrentDictionary<string, X509Certificate2> Certificates =
new(StringComparer.OrdinalIgnoreCase);
private static readonly Lazy<X509Certificate2> WildcardCert =
new(() => LoadOrCreatePersisted("wildcard.xoyo.games", "*.xoyo.games"));
private static string CertificateDirectory => Path.Combine(AppContext.BaseDirectory, "sdk-certs");
public static X509Certificate2 GetOrCreate(string? serverName)
{
if (string.IsNullOrEmpty(serverName) ||
serverName.EndsWith(".xoyo.games", StringComparison.OrdinalIgnoreCase))
return WildcardCert.Value;
var normalized = serverName.Trim().TrimEnd('.').ToLowerInvariant();
return Certificates.GetOrAdd(normalized, host => LoadOrCreatePersisted(host, host));
}
private static X509Certificate2 LoadOrCreatePersisted(string fileHost, string subjectHost)
{
Directory.CreateDirectory(CertificateDirectory);
var pfxPath = Path.Combine(CertificateDirectory, $"{SanitizeFileName(fileHost)}.pfx");
if (File.Exists(pfxPath))
return LoadPkcs12(File.ReadAllBytes(pfxPath));
var certificate = CreateSelfSigned(subjectHost);
File.WriteAllBytes(pfxPath, certificate.Export(X509ContentType.Pfx, Password));
return LoadPkcs12(File.ReadAllBytes(pfxPath));
}
private static X509Certificate2 LoadPkcs12(byte[] pfx)
{
return X509CertificateLoader.LoadPkcs12(
pfx,
Password,
X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
}
private static X509Certificate2 CreateSelfSigned(string host)
{
// CNG key must have AllowPlainTextExport so the private key is included in PFX export.
// Without this, Export(Pfx) produces a cert-only PFX, and EphemeralKeySet loads a
// keyless cert that Kestrel cannot use for TLS.
var cngParams = new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyUsage = CngKeyUsages.AllUsages
};
cngParams.Parameters.Add(new CngProperty("Length",
BitConverter.GetBytes(2048), CngPropertyOptions.None));
using var cngKey = CngKey.Create(CngAlgorithm.Rsa, null, cngParams);
using var rsa = new RSACng(cngKey);
var req = new CertificateRequest(
new X500DistinguishedName($"CN={host}"),
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
var san = new SubjectAlternativeNameBuilder();
san.AddDnsName(host);
req.CertificateExtensions.Add(san.Build());
req.CertificateExtensions.Add(new X509KeyUsageExtension(
X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature,
critical: false));
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") },
critical: false));
var cert = req.CreateSelfSigned(
DateTimeOffset.UtcNow.AddHours(-1),
DateTimeOffset.UtcNow.AddYears(10));
// Private key is now exportable — PFX includes key material
var pfx = cert.Export(X509ContentType.Pfx, Password);
return LoadPkcs12(pfx);
}
private static string SanitizeFileName(string host)
{
var invalidChars = Path.GetInvalidFileNameChars();
return string.Concat(host.Select(ch => invalidChars.Contains(ch) ? '_' : ch));
}
}