2023-10-28 18:19:18 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
2023-11-06 00:29:25 +08:00
|
|
|
|
import 'package:cryptography/cryptography.dart';
|
2023-10-28 18:19:18 +08:00
|
|
|
|
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
|
|
|
|
import 'package:hive/hive.dart';
|
|
|
|
|
import 'package:jwt_decode/jwt_decode.dart';
|
2023-11-06 00:29:25 +08:00
|
|
|
|
import 'package:local_auth/local_auth.dart';
|
2023-10-28 18:19:18 +08:00
|
|
|
|
import 'package:starcitizen_doctor/base/ui_model.dart';
|
2023-11-21 01:05:20 +08:00
|
|
|
|
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
2023-11-06 00:29:25 +08:00
|
|
|
|
import 'package:starcitizen_doctor/common/win32/credentials.dart';
|
2023-10-28 18:19:18 +08:00
|
|
|
|
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
|
|
|
|
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
|
|
|
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
|
|
|
|
|
|
class LoginDialogModel extends BaseUIModel {
|
|
|
|
|
int loginStatus = 0;
|
|
|
|
|
|
|
|
|
|
String nickname = "";
|
|
|
|
|
String? avatarUrl;
|
|
|
|
|
String? authToken;
|
|
|
|
|
String? webToken;
|
|
|
|
|
Map? releaseInfo;
|
|
|
|
|
|
|
|
|
|
final String installPath;
|
|
|
|
|
|
|
|
|
|
final HomeUIModel homeUIModel;
|
|
|
|
|
|
2023-10-28 19:15:13 +08:00
|
|
|
|
// TextEditingController emailCtrl = TextEditingController();
|
2023-10-28 18:19:18 +08:00
|
|
|
|
|
|
|
|
|
LoginDialogModel(this.installPath, this.homeUIModel);
|
|
|
|
|
|
2023-11-06 00:29:25 +08:00
|
|
|
|
final LocalAuthentication localAuth = LocalAuthentication();
|
|
|
|
|
|
2023-11-07 20:47:28 +08:00
|
|
|
|
var isDeviceSupportWinHello = false;
|
|
|
|
|
|
2023-10-28 18:19:18 +08:00
|
|
|
|
@override
|
|
|
|
|
void initModel() {
|
|
|
|
|
_launchWebLogin();
|
|
|
|
|
super.initModel();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-07 20:47:28 +08:00
|
|
|
|
Future<void> _launchWebLogin() async {
|
|
|
|
|
isDeviceSupportWinHello = await localAuth.isDeviceSupported();
|
|
|
|
|
notifyListeners();
|
2023-10-28 18:19:18 +08:00
|
|
|
|
goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect",
|
|
|
|
|
loginMode: true, rsiLoginCallback: (message, ok) async {
|
2023-11-07 20:47:28 +08:00
|
|
|
|
// dPrint(
|
|
|
|
|
// "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
|
2023-10-28 18:19:18 +08:00
|
|
|
|
if (message == null || !ok) {
|
|
|
|
|
Navigator.pop(context!);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-10-28 19:15:13 +08:00
|
|
|
|
// final emailBox = await Hive.openBox("quick_login_email");
|
2023-10-28 18:19:18 +08:00
|
|
|
|
final data = message["data"];
|
|
|
|
|
authToken = data["authToken"];
|
|
|
|
|
webToken = data["webToken"];
|
|
|
|
|
releaseInfo = data["releaseInfo"];
|
|
|
|
|
avatarUrl = data["avatar"]
|
|
|
|
|
?.toString()
|
|
|
|
|
.replaceAll("url(\"", "")
|
|
|
|
|
.replaceAll("\")", "");
|
|
|
|
|
Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
|
|
|
|
nickname = payload["nickname"] ?? "";
|
2023-11-06 00:29:25 +08:00
|
|
|
|
|
|
|
|
|
final inputEmail = data["inputEmail"];
|
|
|
|
|
final inputPassword = data["inputPassword"];
|
|
|
|
|
|
2023-11-07 20:47:28 +08:00
|
|
|
|
final userBox = await Hive.openBox("rsi_account_data");
|
2023-11-06 00:29:25 +08:00
|
|
|
|
if (inputEmail != null && inputEmail != "") {
|
|
|
|
|
await userBox.put("account_email", inputEmail);
|
|
|
|
|
}
|
2023-11-07 20:47:28 +08:00
|
|
|
|
if (isDeviceSupportWinHello) {
|
|
|
|
|
if (await userBox.get("enable", defaultValue: true)) {
|
|
|
|
|
if (inputEmail != null &&
|
|
|
|
|
inputEmail != "" &&
|
|
|
|
|
inputPassword != null &&
|
|
|
|
|
inputPassword != "") {
|
|
|
|
|
final ok = await showConfirmDialogs(
|
|
|
|
|
context!,
|
|
|
|
|
"是否开启自动密码填充?",
|
|
|
|
|
const Text(
|
|
|
|
|
"盒子将使用 PIN 与 Windows 凭据加密保存您的密码,密码只存储在您的设备中。\n\n当下次登录需要输入密码时,您只需授权PIN即可自动填充登录。"));
|
|
|
|
|
if (ok == true) {
|
|
|
|
|
if (await localAuth.authenticate(localizedReason: "输入PIN以启用加密") ==
|
|
|
|
|
true) {
|
|
|
|
|
await _savePwd(inputEmail, inputPassword);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await userBox.put("enable", false);
|
2023-11-06 00:29:25 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 19:58:26 +08:00
|
|
|
|
final buildInfoFile = File("$installPath\\build_manifest.id");
|
|
|
|
|
if (await buildInfoFile.exists()) {
|
|
|
|
|
final buildInfo =
|
|
|
|
|
json.decode(await buildInfoFile.readAsString())["Data"];
|
|
|
|
|
dPrint("buildInfo ======= $buildInfo");
|
|
|
|
|
|
|
|
|
|
if (releaseInfo?["versionLabel"] != null &&
|
|
|
|
|
buildInfo["RequestedP4ChangeNum"] != null) {
|
|
|
|
|
if (!(releaseInfo!["versionLabel"]!
|
|
|
|
|
.toString()
|
|
|
|
|
.endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) {
|
|
|
|
|
final ok = await showConfirmDialogs(
|
|
|
|
|
context!,
|
|
|
|
|
"游戏版本过期",
|
|
|
|
|
Text(
|
|
|
|
|
"RSI 服务器报告版本号:${releaseInfo?["versionLabel"]} \n\n本地版本号:${buildInfo["RequestedP4ChangeNum"]} \n\n建议使用 RSI Launcher 更新游戏!"),
|
|
|
|
|
constraints: BoxConstraints(
|
|
|
|
|
maxWidth: MediaQuery.of(context!).size.width * .4),
|
|
|
|
|
cancel: "忽略");
|
|
|
|
|
if (ok == true) {
|
|
|
|
|
Navigator.pop(context!);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 19:15:13 +08:00
|
|
|
|
_readyForLaunch();
|
2023-10-28 18:19:18 +08:00
|
|
|
|
}, useLocalization: true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goWebView(String title, String url,
|
|
|
|
|
{bool useLocalization = false,
|
|
|
|
|
bool loginMode = false,
|
|
|
|
|
RsiLoginCallback? rsiLoginCallback}) async {
|
|
|
|
|
if (useLocalization) {
|
|
|
|
|
const tipVersion = 2;
|
|
|
|
|
final box = await Hive.openBox("app_conf");
|
|
|
|
|
final skip = await box.get("skip_web_login_version", defaultValue: 0);
|
|
|
|
|
if (skip != tipVersion) {
|
|
|
|
|
final ok = await showConfirmDialogs(
|
|
|
|
|
context!,
|
2023-12-11 23:56:17 +08:00
|
|
|
|
"盒子一键启动",
|
2023-10-28 18:19:18 +08:00
|
|
|
|
const Text(
|
2023-12-11 23:56:17 +08:00
|
|
|
|
"本功能可以帮您更加便利的启动游戏。\n\n为确保账户安全 ,本功能使用汉化浏览器保留登录状态,且不会保存您的密码信息(除非你启用了自动填充功能)。"
|
|
|
|
|
"\n\n使用此功能登录账号时请确保您的 SC汉化盒子 是从可信任的来源下载。",
|
2023-10-28 18:19:18 +08:00
|
|
|
|
style: TextStyle(fontSize: 16),
|
|
|
|
|
),
|
|
|
|
|
constraints: BoxConstraints(
|
|
|
|
|
maxWidth: MediaQuery.of(context!).size.width * .6));
|
|
|
|
|
if (!ok) {
|
|
|
|
|
if (loginMode) {
|
|
|
|
|
rsiLoginCallback?.call(null, false);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await box.put("skip_web_login_version", tipVersion);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!await WebviewWindow.isWebviewAvailable()) {
|
|
|
|
|
await showToast(context!, "需要安装 WebView2 Runtime");
|
|
|
|
|
await launchUrlString(
|
|
|
|
|
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
|
|
|
|
Navigator.pop(context!);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final webViewModel = WebViewModel(context!,
|
|
|
|
|
loginMode: loginMode,
|
|
|
|
|
loginCallback: rsiLoginCallback,
|
|
|
|
|
loginChannel: getChannelID());
|
|
|
|
|
if (useLocalization) {
|
|
|
|
|
try {
|
2023-10-28 19:58:26 +08:00
|
|
|
|
await webViewModel
|
|
|
|
|
.initLocalization(homeUIModel.appWebLocalizationVersionsData!);
|
2023-10-28 18:19:18 +08:00
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
2023-10-28 20:37:45 +08:00
|
|
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
2023-10-28 18:19:18 +08:00
|
|
|
|
await webViewModel.initWebView(
|
|
|
|
|
title: title,
|
|
|
|
|
);
|
|
|
|
|
await webViewModel.launch(url);
|
|
|
|
|
notifyListeners();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 19:15:13 +08:00
|
|
|
|
// onSaveEmail() async {
|
|
|
|
|
// final RegExp emailRegex = RegExp(r'^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$');
|
|
|
|
|
// if (!emailRegex.hasMatch(emailCtrl.text.trim())) {
|
|
|
|
|
// showToast(context!, "邮箱输入有误!");
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// final emailBox = await Hive.openBox("quick_login_email");
|
|
|
|
|
// await emailBox.put(nickname, emailCtrl.text.trim());
|
|
|
|
|
// _readyForLaunch();
|
|
|
|
|
// notifyListeners();
|
|
|
|
|
// }
|
2023-10-28 18:19:18 +08:00
|
|
|
|
|
|
|
|
|
Future<void> _readyForLaunch() async {
|
2023-11-06 00:29:25 +08:00
|
|
|
|
final userBox = await Hive.openBox("rsi_account_data");
|
2023-10-28 18:19:18 +08:00
|
|
|
|
loginStatus = 2;
|
|
|
|
|
notifyListeners();
|
|
|
|
|
final launchData = {
|
2023-11-06 00:29:25 +08:00
|
|
|
|
"username": userBox.get("account_email", defaultValue: ""),
|
2023-10-28 18:19:18 +08:00
|
|
|
|
"token": webToken,
|
|
|
|
|
"auth_token": authToken,
|
|
|
|
|
"star_network": {
|
|
|
|
|
"services_endpoint": releaseInfo?["servicesEndpoint"],
|
|
|
|
|
"hostname": releaseInfo?["universeHost"],
|
|
|
|
|
"port": releaseInfo?["universePort"],
|
|
|
|
|
},
|
|
|
|
|
"TMid": const Uuid().v4(),
|
|
|
|
|
};
|
|
|
|
|
final executable = releaseInfo?["executable"];
|
|
|
|
|
final launchOptions = releaseInfo?["launchOptions"];
|
|
|
|
|
dPrint("----------launch data ====== -----------\n$launchData");
|
|
|
|
|
dPrint(
|
|
|
|
|
"----------executable data ====== -----------\n$installPath\\$executable $launchOptions");
|
|
|
|
|
final launchFile = File("$installPath\\loginData.json");
|
|
|
|
|
if (await launchFile.exists()) {
|
|
|
|
|
await launchFile.delete();
|
|
|
|
|
}
|
|
|
|
|
await launchFile.create();
|
|
|
|
|
await launchFile.writeAsString(json.encode(launchData));
|
|
|
|
|
notifyListeners();
|
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
2023-11-21 01:05:20 +08:00
|
|
|
|
|
|
|
|
|
await Future.delayed(const Duration(seconds: 3));
|
2023-12-11 23:56:17 +08:00
|
|
|
|
final processorAffinity = await SystemHelper.getCpuAffinity();
|
2023-11-21 01:05:20 +08:00
|
|
|
|
|
2023-10-28 18:19:18 +08:00
|
|
|
|
homeUIModel.doLaunchGame(
|
|
|
|
|
'$installPath\\$executable',
|
|
|
|
|
["-no_login_dialog", ...launchOptions.toString().split(" ")],
|
2023-11-21 21:25:59 +08:00
|
|
|
|
installPath,
|
|
|
|
|
processorAffinity);
|
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
|
Navigator.pop(context!);
|
2023-10-28 18:19:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String getChannelID() {
|
|
|
|
|
if (installPath.endsWith("\\LIVE")) {
|
|
|
|
|
return "LIVE";
|
|
|
|
|
} else if (installPath.endsWith("\\PTU")) {
|
|
|
|
|
return "PTU";
|
2023-10-28 20:57:38 +08:00
|
|
|
|
} else if (installPath.endsWith("\\EPTU")) {
|
|
|
|
|
return "EPTU";
|
2023-10-28 18:19:18 +08:00
|
|
|
|
}
|
|
|
|
|
return "LIVE";
|
|
|
|
|
}
|
2023-11-06 00:29:25 +08:00
|
|
|
|
|
|
|
|
|
_savePwd(String inputEmail, String inputPassword) async {
|
|
|
|
|
final algorithm = AesGcm.with256bits();
|
|
|
|
|
final secretKey = await algorithm.newSecretKey();
|
|
|
|
|
final nonce = algorithm.newNonce();
|
|
|
|
|
|
|
|
|
|
final secretBox = await algorithm.encrypt(utf8.encode(inputPassword),
|
|
|
|
|
secretKey: secretKey, nonce: nonce);
|
|
|
|
|
|
|
|
|
|
await algorithm.decrypt(
|
|
|
|
|
SecretBox(secretBox.cipherText,
|
|
|
|
|
nonce: secretBox.nonce, mac: secretBox.mac),
|
|
|
|
|
secretKey: secretKey);
|
|
|
|
|
|
|
|
|
|
final pwdEncrypted = base64.encode(secretBox.cipherText);
|
|
|
|
|
|
|
|
|
|
final userBox = await Hive.openBox("rsi_account_data");
|
|
|
|
|
await userBox.put("account_email", inputEmail);
|
|
|
|
|
await userBox.put("account_pwd_encrypted", pwdEncrypted);
|
|
|
|
|
await userBox.put("nonce", base64.encode(secretBox.nonce));
|
|
|
|
|
await userBox.put("mac", base64.encode(secretBox.mac.bytes));
|
|
|
|
|
|
|
|
|
|
final secretKeyStr = base64.encode((await secretKey.extractBytes()));
|
|
|
|
|
|
|
|
|
|
Win32Credentials.write(
|
|
|
|
|
credentialName: "SCToolbox_RSI_Account_secret",
|
|
|
|
|
userName: inputEmail,
|
|
|
|
|
password: secretKeyStr);
|
|
|
|
|
}
|
2023-10-28 18:19:18 +08:00
|
|
|
|
}
|