mirror of
https://github.com/MikuLeaks/MikuSB.git
synced 2026-06-04 04:03:58 +00:00
Completely offline (Thank you cs8425)
This commit is contained in:
@@ -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 = "操作成功"
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
97
SdkServer/Utils/CertHelper.cs
Normal file
97
SdkServer/Utils/CertHelper.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user