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