From f1dda029e89e6ad92eebda44482201c0fce13471 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Wed, 16 Nov 2022 19:48:07 +0800 Subject: [PATCH] login by password --- .../Snap.Hutao/Core/CoreEnvironment.cs | 4 +- .../Snap.Hutao/Core/Json/InboxTextEncoder.cs | 49 +++++++++++++++ .../Snap.Hutao/ViewModel/SettingViewModel.cs | 12 ++-- .../Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs | 9 ++- .../Web/Hoyolab/HttpClientExtensions.cs | 24 +++++++ .../Web/Hoyolab/Passport/PassportClient.cs | 6 +- .../Web/Hoyolab/Passport/PassportClient2.cs | 62 +++++++++++++++++++ .../{RSAUtil.cs => RSAEncryptedString.cs} | 22 +++++-- ...erifyInformation.cs => UserInformation.cs} | 2 +- 9 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs rename src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/{RSAUtil.cs => RSAEncryptedString.cs} (57%) rename src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/{VerifyInformation.cs => UserInformation.cs} (98%) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs index da63244c..764fd973 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/CoreEnvironment.cs @@ -3,10 +3,12 @@ using Microsoft.Win32; using Snap.Hutao.Core.Convert; +using Snap.Hutao.Core.Json; using Snap.Hutao.Extension; using Snap.Hutao.Web.Hoyolab.DynamicSecret; using System.Collections.Immutable; using System.Text.Encodings.Web; +using System.Text.Unicode; using Windows.ApplicationModel; namespace Snap.Hutao.Core; @@ -70,7 +72,7 @@ internal static class CoreEnvironment public static readonly JsonSerializerOptions JsonOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Encoder = new InboxTextEncoder(), //JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNameCaseInsensitive = true, WriteIndented = true, }; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs new file mode 100644 index 00000000..7b30826d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/InboxTextEncoder.cs @@ -0,0 +1,49 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; + +namespace Snap.Hutao.Core.Json; + +/// +/// 替换 = +/// +internal class InboxTextEncoder : JavaScriptEncoder +{ + /// + public override int MaxOutputCharactersPerInputCharacter { get => 6; } + + /// + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + for (int i = 0; i < textLength; i++) + { + char c = text[i]; + if (c == '=') + { + return i; + } + } + + return -1; + } + + /// + public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) + { + string encoded = FormattableString.Invariant($"\\u{(uint)unicodeScalar:x4}"); + numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0; + return encoded.AsSpan().TryCopyTo(new Span(buffer, bufferLength)); + } + + /// + public override bool WillEncode(int unicodeScalar) + { + return true; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs index 09a31086..745b80ba 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/SettingViewModel.cs @@ -12,11 +12,12 @@ using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Game.Locator; +using Snap.Hutao.Web.Hoyolab.Passport; namespace Snap.Hutao.ViewModel; /// -/// 测试视图模型 +/// 设置视图模型 /// [Injection(InjectAs.Scoped)] internal class SettingViewModel : ObservableObject @@ -37,7 +38,7 @@ internal class SettingViewModel : ObservableObject private NamedValue selectedBackdropType; /// - /// 构造一个新的测试视图模型 + /// 构造一个新的设置视图模型 /// /// 数据库上下文 /// 游戏服务 @@ -153,10 +154,13 @@ internal class SettingViewModel : ObservableObject } } - private void DebugThrowException() + private async void DebugThrowException() { #if DEBUG - throw new InvalidOperationException("测试用异常"); + PassportClient2 passportClient2 = Ioc.Default.GetRequiredService(); + LoginResult? data = await passportClient2.LoginByPasswordAsync("phoneNunmber", "password", CancellationToken.None).ConfigureAwait(false); + + _ = data; #endif } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs index 716a3d2e..ed8ac99d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/ApiEndpoints.cs @@ -189,6 +189,11 @@ internal static class ApiEndpoints /// public const string AccountCookieAccountInfoBySToken = $"{PassportApi}/account/auth/api/getCookieAccountInfoBySToken"; + /// + /// 验证 Ltoken 有效性 + /// + public const string AccountLoginByPassword = $"{PassportApi}/account/ma-cn-passport/app/loginByPassword"; + /// /// 验证 Ltoken 有效性 /// @@ -244,8 +249,8 @@ internal static class ApiEndpoints private const string Hk4eApiAnnouncementApi = $"{Hk4eApi}/common/hk4e_cn/announcement/api"; private const string Hk4eApiGachaInfoApi = $"{Hk4eApi}/event/gacha_info/api"; - private const string PassportApi = "passport-api.mihoyo.com"; - private const string PassportApiV4 = "passport-api-v4.mihoyo.com"; + private const string PassportApi = "https://passport-api.mihoyo.com"; + private const string PassportApiV4 = "https://passport-api-v4.mihoyo.com"; private const string SdkStatic = "https://sdk-static.mihoyo.com"; private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api"; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs index b3eea862..8decccf5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs @@ -98,4 +98,28 @@ internal static class HttpClientExtensions httpClient.DefaultRequestHeaders.Set("Referer", referer); return httpClient; } + + /// + /// 设置AppId + /// + /// http客户端 + /// 客户端 + internal static HttpClient SetAppId(this HttpClient httpClient) + { + httpClient.DefaultRequestHeaders.Set("x-rpc-app_id", "bll8iq97cem8"); + return httpClient; + } + + /// + /// 设置头 + /// + /// http客户端 + /// 键 + /// 值 + /// 客户端 + internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value) + { + httpClient.DefaultRequestHeaders.Set(key, value); + return httpClient; + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs index bdfc225d..087b1c89 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient.cs @@ -40,11 +40,11 @@ internal class PassportClient /// 取消令牌 /// 验证信息 [ApiInformation(Cookie = CookieType.All)] - public async Task VerifyLtokenAsync(User user, CancellationToken token) + public async Task VerifyLtokenAsync(User user, CancellationToken token) { - Response? response = await httpClient + Response? response = await httpClient .SetUser(user, CookieType.All) - .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token) + .TryCatchPostAsJsonAsync>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token) .ConfigureAwait(false); return response?.Data; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index 0d059ef4..1c44c232 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -33,6 +33,34 @@ internal class PassportClient2 this.logger = logger; } + /// + /// 异步账密登录 + /// + /// 用户 + /// 密码 + /// 取消令牌 + /// 登录数据 + public async Task LoginByPasswordAsync(string account, string password, CancellationToken token) + { + Dictionary data = new() + { + { "account", RSAEncryptedString.Encrypt(account) }, + { "password", RSAEncryptedString.Encrypt(password) }, + }; + + Response? resp = await httpClient + .SetAppId() + .SetHeader("x-rpc-aigis", string.Empty) + .SetHeader("x-rpc-client_type", "2") + .SetHeader("x-rpc-game_biz", "bbs_cn") + .SetHeader("x-rpc-sdk_version", "1.3.1.2") + .UsingDynamicSecret2(SaltType.PROD, options, ApiEndpoints.AccountLoginByPassword, data) + .TryCatchPostAsJsonAsync>(logger, token) + .ConfigureAwait(false); + + return resp?.Data; + } + /// /// 异步获取 CookieToken /// @@ -50,4 +78,38 @@ internal class PassportClient2 return resp?.Data; } +} + +public class LoginResult +{ + [JsonPropertyName("token")] + public TokenWrapper Token { get; set; } + + [JsonPropertyName("user_info")] + public UserInformation UserInfo { get; set; } + + [JsonPropertyName("reactivate_info")] + public ReactivateInfo ReactivateInfo { get; set; } + + [JsonPropertyName("login_ticket")] + public string LoginTicket { get; set; } +} + +public class ReactivateInfo +{ + + [JsonPropertyName("required")] + public bool Required { get; set; } + + [JsonPropertyName("ticket")] + public string Ticket { get; set; } +} + +public class TokenWrapper +{ + [JsonPropertyName("token_type")] + public int TokenType { get; set; } + + [JsonPropertyName("token")] + public string Token { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAUtil.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAEncryptedString.cs similarity index 57% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAUtil.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAEncryptedString.cs index 44674ea3..0021f85b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAUtil.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/RSAEncryptedString.cs @@ -9,20 +9,34 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// /// 支持RSA转换 /// -internal static class RSAUtil +internal class RSAEncryptedString { private const string RsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7cI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs\n9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q\nCgGs52bFoYMtyi+xEQIDAQAB\n"; + private readonly string encryptedSource; + + private RSAEncryptedString(string encryptedSource) + { + this.encryptedSource = encryptedSource; + } + + public static implicit operator string(RSAEncryptedString encryptedString) + { + return encryptedString.encryptedSource.Replace("=", @"\u003d"); + } + /// /// 加密 /// /// 源 /// 字节数组 - public static string Encrypt(string source) + public static RSAEncryptedString Encrypt(string source) { RSA rsa = RSA.Create(); - rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}\n-----END PUBLIC KEY-----"); + rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}-----END PUBLIC KEY-----"); byte[] bytes = Encoding.UTF8.GetBytes(source); - return Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1)); + string encrypted = Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1)); + + return new(encrypted); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UserInformation.cs similarity index 98% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UserInformation.cs index 46a170b1..ce3872a8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/VerifyInformation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/UserInformation.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// /// 验证返回信息 /// -public class VerifyInformation +public class UserInformation { /// /// 账户Id