将诊断功能从首页拆分至独立页面

This commit is contained in:
xkeyC 2024-02-16 23:32:16 +08:00
parent 066bf11275
commit 27640ec6b7
7 changed files with 431 additions and 338 deletions

View File

@ -1,3 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:starcitizen_doctor/main.dart'; import 'package:starcitizen_doctor/main.dart';
@ -102,11 +103,13 @@ abstract class BaseUI<T extends BaseUIModel>
automaticallyImplyLeading: automaticallyImplyLeading, automaticallyImplyLeading: automaticallyImplyLeading,
title: DragToMoveArea( title: DragToMoveArea(
child: titleRow ?? child: titleRow ??
Row( Column(children: [Expanded(
child: Row(
children: [ children: [
Text(getUITitle(context, model)), Text(getUITitle(context, model)),
], ],
), ),
)],),
), ),
actions: Row( actions: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,

View File

@ -112,4 +112,12 @@ class SCLoggerHelper {
return scInstallPaths; return scInstallPaths;
} }
static String getGameLogs(String gameDir) {
return "";
}
static String getGameExitLogInfo(String logs) {
return "";
}
} }

View File

@ -119,7 +119,6 @@ class AboutUI extends BaseUI<AboutUIModel> {
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
makeRescueBanner(context),
const Spacer(), const Spacer(),
Row( Row(
children: [ children: [
@ -154,36 +153,6 @@ class AboutUI extends BaseUI<AboutUIModel> {
); );
} }
Widget makeRescueBanner(BuildContext context) {
return GestureDetector(
onTap: () async {
await showToast(context,
"您即将前往由 深空治疗中心QQ群号536454632 提供的游戏异常救援服务,主要解决游戏安装失败与频繁闪退,如游戏玩法问题,请勿加群。");
launchUrlString(
"https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=-M4wEme_bCXbUGT4LFKLH0bAYTFt70Ad&authKey=vHVr0TNgRmKu%2BHwywoJV6EiLa7La2VX74Vkyixr05KA0H9TqB6qWlCdY%2B9jLQ4Ha&noverify=0&group_code=536454632");
},
child: Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .2),
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/rescue.png", width: 24, height: 24),
const SizedBox(width: 12),
const Text("安装失败?游戏闪退?? 点我加群获取免费人工支援!"),
],
),
)),
),
);
}
static const tipTextEN = static const tipTextEN =
"This is an unofficial Star Citizen fan-made tools, not affiliated with the Cloud Imperium group of companies. All content on this Software not authored by its host or users are property of their respective owners. \nStar Citizen®, Roberts Space Industries® and Cloud Imperium® are registered trademarks of Cloud Imperium Rights LLC."; "This is an unofficial Star Citizen fan-made tools, not affiliated with the Cloud Imperium group of companies. All content on this Software not authored by its host or users are property of their respective owners. \nStar Citizen®, Roberts Space Industries® and Cloud Imperium® are registered trademarks of Cloud Imperium Rights LLC.";

View File

@ -0,0 +1,172 @@
import 'package:flutter_tilt/flutter_tilt.dart';
import 'package:starcitizen_doctor/base/ui.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'game_doctor_ui_model.dart';
class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
@override
Widget? buildBody(BuildContext context, GameDoctorUIModel model) {
return makeDefaultPage(context, model,
content: Stack(
children: [
Column(
children: [
if (model.isChecking)
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(model.lastScreenInfo)
],
),
))
else if (model.checkResult == null ||
model.checkResult!.isEmpty) ...[
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 12),
const Text("扫描完毕,没有找到问题!", maxLines: 1),
const SizedBox(height: 64),
makeRescueBanner(context),
],
),
))
] else ...[
...makeResult(context, model),
const SizedBox(height: 64),
makeRescueBanner(context),
],
],
),
if (model.isFixing)
Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(150),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(model.isFixingString.isNotEmpty
? model.isFixingString
: "正在处理..."),
],
),
),
)
],
));
}
List<Widget> makeResult(BuildContext context, GameDoctorUIModel model) {
return [
const SizedBox(height: 24),
const Text(
"检测结果",
style: TextStyle(fontSize: 24),
),
const SizedBox(height: 6),
Text(model.lastScreenInfo, maxLines: 1),
const SizedBox(height: 24),
ListView.builder(
itemCount: model.checkResult!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
final item = model.checkResult![index];
return makeResultItem(item, model);
},
),
const SizedBox(height: 24),
Text(
"注意:本工具检测结果仅供参考,若您不理解以上操作,请提供截图给有经验的玩家!",
style: TextStyle(color: Colors.red, fontSize: 16),
),
];
}
Widget makeRescueBanner(BuildContext context) {
return GestureDetector(
onTap: () async {
await showToast(context,
"您即将前往由 深空治疗中心QQ群号536454632 提供的游戏异常救援服务,主要解决游戏安装失败与频繁闪退,如游戏玩法问题,请勿加群。");
launchUrlString(
"https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=-M4wEme_bCXbUGT4LFKLH0bAYTFt70Ad&authKey=vHVr0TNgRmKu%2BHwywoJV6EiLa7La2VX74Vkyixr05KA0H9TqB6qWlCdY%2B9jLQ4Ha&noverify=0&group_code=536454632");
},
child: Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .2),
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/rescue.png", width: 24, height: 24),
const SizedBox(width: 12),
const Text("需要帮助? 点击加群获得免费人工支援!"),
],
),
)),
),
);
}
Widget makeResultItem(
MapEntry<String, String> item, GameDoctorUIModel model) {
final errorNames = {
"unSupport_system":
MapEntry("不支持的操作系统,游戏可能无法运行", "请升级您的系统 (${item.value})"),
"no_live_path": MapEntry("安装目录缺少LIVE文件夹可能导致安装失败",
"点击修复为您创建 LIVE 文件夹,完成后重试安装。(${item.value})"),
"nvme_PhysicalBytes": MapEntry("新型 NVME 设备,与 RSI 启动器暂不兼容,可能导致安装失败",
"为注册表项添加 ForcedPhysicalSectorSizeInBytes 值 模拟旧设备。硬盘分区(${item.value})"),
"eac_file_miss": const MapEntry("EasyAntiCheat 文件丢失",
"未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件"),
"eac_not_install": const MapEntry("EasyAntiCheat 未安装 或 未正常退出",
"EasyAntiCheat 未安装,请点击修复为您一键安装。(在游戏正常启动并结束前,该问题会一直出现,若您因为其他原因游戏闪退,可忽略此条目)"),
"cn_user_name":
const MapEntry("中文用户名!", "中文用户名可能会导致游戏启动/安装错误! 点击修复按钮查看修改教程!"),
"cn_install_path": MapEntry("中文安装路径!",
"中文安装路径!这可能会导致游戏 启动/安装 错误!(${item.value}请在RSI启动器更换安装路径。"),
"low_ram": MapEntry(
"物理内存过低", "您至少需要 16GB 的物理内存Memory才可运行此游戏。当前大小${item.value}"),
};
return ListTile(
title: Text(errorNames[item.key]?.key ?? item.key),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4, bottom: 4),
child: Text("修复建议: ${errorNames[item.key]?.value ?? "暂无解决方法,请截图反馈"}"),
),
trailing: Button(
onPressed: (errorNames[item.key]?.value == null || model.isFixing)
? null
: () async {
await model.doFix(item);
model.isFixing = false;
model.notifyListeners();
},
child: const Padding(
padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
child: Text("修复"),
),
),
);
}
@override
String getUITitle(BuildContext context, GameDoctorUIModel model) =>
"一键诊断 -> ${model.scInstalledPath}";
}

View File

@ -0,0 +1,224 @@
import 'dart:convert';
import 'dart:io';
import 'package:starcitizen_doctor/base/ui_model.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:url_launcher/url_launcher_string.dart';
class GameDoctorUIModel extends BaseUIModel {
String scInstalledPath = "";
GameDoctorUIModel(this.scInstalledPath);
String _lastScreenInfo = "";
String get lastScreenInfo => _lastScreenInfo;
List<MapEntry<String, String>>? checkResult;
set lastScreenInfo(String info) {
_lastScreenInfo = info;
notifyListeners();
}
bool isChecking = false;
bool isFixing = false;
String isFixingString = "";
final cnExp = RegExp(r"[^\x00-\xff]");
@override
void initModel() {
doCheck()?.call();
super.initModel();
}
VoidCallback? doCheck() {
if (isChecking) return null;
return () async {
isChecking = true;
lastScreenInfo = "正在分析...";
await _statCheck();
isChecking = false;
notifyListeners();
};
}
Future _statCheck() async {
checkResult = [];
await _checkPreInstall();
await _checkEAC();
// TODO for debug
// checkResult?.add(const MapEntry("unSupport_system", "android"));
// checkResult?.add(const MapEntry("nvme_PhysicalBytes", "C"));
// checkResult?.add(const MapEntry("no_live_path", ""));
if (checkResult!.isEmpty) {
checkResult = null;
lastScreenInfo = "分析完毕,没有发现问题";
} else {
lastScreenInfo = "分析完毕,发现 ${checkResult!.length} 个问题";
}
if (scInstalledPath == "not_install" && (checkResult?.isEmpty ?? true)) {
showToast(context!, "扫描完毕,没有发现问题,若仍然安装失败,请尝试使用工具箱中的 RSI启动器管理员模式。");
}
}
Future _checkEAC() async {
if (scInstalledPath == "not_install") return;
lastScreenInfo = "正在检查EAC";
final eacPath = "$scInstalledPath\\EasyAntiCheat";
final eacJsonPath = "$eacPath\\Settings.json";
if (!await Directory(eacPath).exists() ||
!await File(eacJsonPath).exists()) {
checkResult?.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
final eacDeploymentId = eacJson["deploymentid"];
if (eacID == null || eacDeploymentId == null) {
checkResult?.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacFilePath =
"${Platform.environment["appdata"]}\\EasyAntiCheat\\$eacID\\$eacDeploymentId\\anticheatlauncher.log";
if (!await File(eacFilePath).exists()) {
checkResult?.add(MapEntry("eac_not_install", eacPath));
return;
}
}
Future _checkPreInstall() async {
lastScreenInfo = "正在检查:运行环境";
if (!(Platform.operatingSystemVersion.contains("Windows 10") ||
Platform.operatingSystemVersion.contains("Windows 11"))) {
checkResult
?.add(MapEntry("unSupport_system", Platform.operatingSystemVersion));
lastScreenInfo = "不支持的操作系统:${Platform.operatingSystemVersion}";
await showToast(context!, lastScreenInfo);
}
if (cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) {
checkResult?.add(const MapEntry("cn_user_name", ""));
}
// RAM
final ramSize = await SystemHelper.getSystemMemorySizeGB();
if (ramSize < 16) {
checkResult?.add(MapEntry("low_ram", "$ramSize"));
}
lastScreenInfo = "正在检查:安装信息";
//
try {
final listData = await SCLoggerHelper.getGameInstallPath(
await SCLoggerHelper.getLauncherLogList() ?? []);
final p = [];
final checkedPath = [];
for (var installPath in listData) {
if (!checkedPath.contains(installPath)) {
if (cnExp.hasMatch(installPath)) {
checkResult?.add(MapEntry("cn_install_path", installPath));
}
if (scInstalledPath == "not_install") {
checkedPath.add(installPath);
if (!await Directory(installPath).exists()) {
checkResult?.add(MapEntry("no_live_path", installPath));
}
}
final tp = installPath.split(":")[0];
if (!p.contains(tp)) {
p.add(tp);
}
}
}
// call check
for (var element in p) {
var result = await Process.run('powershell', [
"(fsutil fsinfo sectorinfo $element: | Select-String 'PhysicalBytesPerSectorForPerformance').ToString().Split(':')[1].Trim()"
]);
dPrint(result.stdout);
if (result.stderr == "") {
final rs = result.stdout.toString();
final physicalBytesPerSectorForPerformance = (int.tryParse(rs) ?? 0);
if (physicalBytesPerSectorForPerformance > 4096) {
checkResult?.add(MapEntry("nvme_PhysicalBytes", element));
}
}
}
} catch (e) {
dPrint(e);
}
}
Future<void> doFix(MapEntry<String, String> item) async {
isFixing = true;
notifyListeners();
switch (item.key) {
case "unSupport_system":
showToast(context!, "若您的硬件达标,请尝试安装最新的 Windows 系统。");
return;
case "no_live_path":
try {
await Directory(item.value).create(recursive: true);
showToast(context!, "创建文件夹成功,请尝试继续下载游戏!");
checkResult?.remove(item);
notifyListeners();
} catch (e) {
showToast(context!, "创建文件夹失败,请尝试手动创建。\n目录:${item.value} \n错误:$e");
}
return;
case "nvme_PhysicalBytes":
final r = await SystemHelper.addNvmePatch();
if (r == "") {
showToast(context!,
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
checkResult?.remove(item);
notifyListeners();
} else {
showToast(context!, "修复失败,$r");
}
return;
case "eac_file_miss":
showToast(
context!, "未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件");
return;
case "eac_not_install":
final eacJsonPath = "${item.value}\\Settings.json";
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
try {
var result = await Process.run(
"${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]);
dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID");
if (result.stderr == "") {
showToast(context!, "修复成功,请尝试启动游戏。(若问题无法解决,请使用工具箱的 《重装 EAC》");
checkResult?.remove(item);
notifyListeners();
} else {
showToast(context!, "修复失败,${result.stderr}");
}
} catch (e) {
showToast(context!, "修复失败,$e");
}
return;
case "cn_user_name":
showToast(context!, "即将跳转,教程来自互联网,请谨慎操作...");
await Future.delayed(const Duration(milliseconds: 300));
launchUrlString(
"https://btfy.eu.org/?q=5L+u5pS5d2luZG93c+eUqOaIt+WQjeS7juS4reaWh+WIsOiLseaWhw==");
return;
default:
showToast(context!, "该问题暂不支持自动处理,请提供截图寻求帮助");
return;
}
}
}

View File

@ -39,11 +39,6 @@ class HomeUI extends BaseUI<HomeUIModel> {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
], ],
if (!model.isChecking &&
model.checkResult != null &&
model.checkResult!.isNotEmpty)
...makeResult(context, model)
else
...makeIndex(context, model) ...makeIndex(context, model)
], ],
), ),
@ -167,7 +162,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
onPressed: () => model.openDir(model.scInstalledPath)), onPressed: () => model.openDir(model.scInstalledPath)),
const SizedBox(width: 12), const SizedBox(width: 12),
Button( Button(
onPressed: model.isChecking ? null : model.reScanPath, onPressed: model.reScanPath,
child: const Padding( child: const Padding(
padding: EdgeInsets.all(6), padding: EdgeInsets.all(6),
child: Icon(FluentIcons.refresh), child: Icon(FluentIcons.refresh),
@ -423,9 +418,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = items.elementAt(index); final item = items.elementAt(index);
return HoverButton( return HoverButton(
onPressed: item.key == "auto_check" && model.isChecking onPressed: () => model.onMenuTap(item.key),
? null
: () => model.onMenuTap(item.key),
builder: (BuildContext context, Set<ButtonStates> states) { builder: (BuildContext context, Set<ButtonStates> states) {
return Container( return Container(
width: 300, width: 300,
@ -478,13 +471,10 @@ class HomeUI extends BaseUI<HomeUIModel> {
Text(model.localizationUpdateInfo?.key ?? " "), Text(model.localizationUpdateInfo?.key ?? " "),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
if (item.key == "auto_check" && model.isChecking)
const ProgressRing()
else
const Icon( const Icon(
FluentIcons.chevron_right, FluentIcons.chevron_right,
size: 16, size: 16,
), )
], ],
), ),
), ),
@ -495,84 +485,9 @@ class HomeUI extends BaseUI<HomeUIModel> {
); );
} }
List<Widget> makeResult(BuildContext context, HomeUIModel model) {
return [
const SizedBox(height: 24),
const Text(
"检测结果",
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 6),
Text(model.lastScreenInfo, maxLines: 1),
const SizedBox(height: 24),
ListView.builder(
itemCount: model.checkResult!.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final item = model.checkResult![index];
return makeResultItem(item, model);
},
),
Text(
"注意:本工具检测结果仅供参考,若您不理解以上操作,请提供截图给有经验的玩家!",
style: TextStyle(color: Colors.red, fontSize: 16),
),
const SizedBox(height: 64),
FilledButton(
onPressed: model.resetCheck,
child: const Padding(
padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
child: Text('返回'),
),
),
const SizedBox(height: 38),
];
}
@override @override
String getUITitle(BuildContext context, HomeUIModel model) => "HOME"; String getUITitle(BuildContext context, HomeUIModel model) => "HOME";
Widget makeResultItem(MapEntry<String, String> item, HomeUIModel model) {
final errorNames = {
"unSupport_system":
MapEntry("不支持的操作系统,游戏可能无法运行", "请升级您的系统 (${item.value})"),
"no_live_path": MapEntry("安装目录缺少LIVE文件夹可能导致安装失败",
"点击修复为您创建 LIVE 文件夹,完成后重试安装。(${item.value})"),
"nvme_PhysicalBytes": MapEntry("新型 NVME 设备,与 RSI 启动器暂不兼容,可能导致安装失败",
"为注册表项添加 ForcedPhysicalSectorSizeInBytes 值 模拟旧设备。硬盘分区(${item.value})"),
"eac_file_miss": const MapEntry("EasyAntiCheat 文件丢失",
"未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件"),
"eac_not_install": const MapEntry("EasyAntiCheat 未安装 或 未正常退出",
"EasyAntiCheat 未安装,请点击修复为您一键安装。(在游戏正常启动并结束前,该问题会一直出现,若您因为其他原因游戏闪退,可忽略此条目)"),
"cn_user_name":
const MapEntry("中文用户名!", "中文用户名可能会导致游戏启动/安装错误! 点击修复按钮查看修改教程!"),
"cn_install_path": MapEntry("中文安装路径!",
"中文安装路径!这可能会导致游戏 启动/安装 错误!(${item.value}请在RSI启动器更换安装路径。"),
"low_ram": MapEntry(
"物理内存过低", "您至少需要 16GB 的物理内存Memory才可运行此游戏。当前大小${item.value}"),
};
return ListTile(
title: Text(errorNames[item.key]?.key ?? item.key),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4, bottom: 4),
child: Text("修复建议: ${errorNames[item.key]?.value ?? "暂无解决方法,请截图反馈"}"),
),
trailing: Button(
onPressed: (errorNames[item.key]?.value == null || model.isFixing)
? null
: () async {
await model.doFix(item);
model.isFixing = false;
model.notifyListeners();
},
child: const Padding(
padding: EdgeInsets.only(left: 8, right: 8),
child: Text("修复"),
),
),
);
}
Widget makeWebViewButton(HomeUIModel model, Widget makeWebViewButton(HomeUIModel model,
{required Widget icon, {required Widget icon,
required String name, required String name,

View File

@ -33,6 +33,8 @@ import 'package:html/dom.dart' as html_dom;
import 'package:windows_ui/windows_ui.dart'; import 'package:windows_ui/windows_ui.dart';
import 'countdown/countdown_dialog_ui.dart'; import 'countdown/countdown_dialog_ui.dart';
import 'game_doctor/game_doctor_ui.dart';
import 'game_doctor/game_doctor_ui_model.dart';
import 'localization/localization_ui.dart'; import 'localization/localization_ui.dart';
import 'performance/performance_ui.dart'; import 'performance/performance_ui.dart';
import 'webview/webview_localization_capture_ui.dart'; import 'webview/webview_localization_capture_ui.dart';
@ -42,11 +44,7 @@ class HomeUIModel extends BaseUIModel {
List<String> scInstallPaths = []; List<String> scInstallPaths = [];
String _lastScreenInfo = ""; String lastScreenInfo = "";
String get lastScreenInfo => _lastScreenInfo;
bool isChecking = false;
bool isFixing = false; bool isFixing = false;
String isFixingString = ""; String isFixingString = "";
@ -58,13 +56,6 @@ class HomeUIModel extends BaseUIModel {
List<RssItem>? rssVideoItems; List<RssItem>? rssVideoItems;
List<RssItem>? rssTextItems; List<RssItem>? rssTextItems;
set lastScreenInfo(String info) {
_lastScreenInfo = info;
notifyListeners();
}
List<MapEntry<String, String>>? checkResult;
AppWebLocalizationVersionsData? appWebLocalizationVersionsData; AppWebLocalizationVersionsData? appWebLocalizationVersionsData;
List<CountdownFestivalItemData>? countdownFestivalListData; List<CountdownFestivalItemData>? countdownFestivalListData;
@ -73,8 +64,6 @@ class HomeUIModel extends BaseUIModel {
bool _isSendLocalizationUpdateNotification = false; bool _isSendLocalizationUpdateNotification = false;
final cnExp = RegExp(r"[^\x00-\xff]");
AppPlacardData? appPlacardData; AppPlacardData? appPlacardData;
List? scServerStatus; List? scServerStatus;
@ -194,211 +183,24 @@ class HomeUIModel extends BaseUIModel {
} }
} }
VoidCallback? doCheck() {
if (isChecking) return null;
return () async {
isChecking = true;
lastScreenInfo = "正在分析...";
await _statCheck();
isChecking = false;
notifyListeners();
};
}
Future _statCheck() async {
checkResult = [];
await _checkPreInstall();
await _checkEAC();
// TODO for debug
// checkResult?.add(const MapEntry("unSupport_system", "android"));
// checkResult?.add(const MapEntry("nvme_PhysicalBytes", "c"));
// checkResult?.add(const MapEntry("no_live_path", ""));
if (checkResult!.isEmpty) {
checkResult = null;
lastScreenInfo = "分析完毕,没有发现问题";
} else {
lastScreenInfo = "分析完毕,发现 ${checkResult!.length} 个问题";
}
if (scInstalledPath == "not_install" && (checkResult?.isEmpty ?? true)) {
showToast(context!, "扫描完毕,没有发现问题,若仍然安装失败,请尝试使用工具箱中的 RSI启动器管理员模式。");
}
}
Future _checkEAC() async {
if (scInstalledPath == "not_install") return;
lastScreenInfo = "正在检查EAC";
final eacPath = "$scInstalledPath\\EasyAntiCheat";
final eacJsonPath = "$eacPath\\Settings.json";
if (!await Directory(eacPath).exists() ||
!await File(eacJsonPath).exists()) {
checkResult?.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
final eacDeploymentId = eacJson["deploymentid"];
if (eacID == null || eacDeploymentId == null) {
checkResult?.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacFilePath =
"${Platform.environment["appdata"]}\\EasyAntiCheat\\$eacID\\$eacDeploymentId\\anticheatlauncher.log";
if (!await File(eacFilePath).exists()) {
checkResult?.add(MapEntry("eac_not_install", eacPath));
return;
}
}
Future _checkPreInstall() async {
lastScreenInfo = "正在检查:运行环境";
if (!(Platform.operatingSystemVersion.contains("Windows 10") ||
Platform.operatingSystemVersion.contains("Windows 11"))) {
checkResult
?.add(MapEntry("unSupport_system", Platform.operatingSystemVersion));
lastScreenInfo = "不支持的操作系统:${Platform.operatingSystemVersion}";
await showToast(context!, lastScreenInfo);
}
if (cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) {
checkResult?.add(const MapEntry("cn_user_name", ""));
}
// RAM
final ramSize = await SystemHelper.getSystemMemorySizeGB();
if (ramSize < 16) {
checkResult?.add(MapEntry("low_ram", "$ramSize"));
}
lastScreenInfo = "正在检查:安装信息";
//
try {
final listData = await SCLoggerHelper.getGameInstallPath(
await SCLoggerHelper.getLauncherLogList() ?? []);
final p = [];
final checkedPath = [];
for (var installPath in listData) {
if (!checkedPath.contains(installPath)) {
if (cnExp.hasMatch(installPath)) {
checkResult?.add(MapEntry("cn_install_path", installPath));
}
if (scInstalledPath == "not_install") {
checkedPath.add(installPath);
if (!await Directory(installPath).exists()) {
checkResult?.add(MapEntry("no_live_path", installPath));
}
}
final tp = installPath.split(":")[0];
if (!p.contains(tp)) {
p.add(tp);
}
}
}
// call check
for (var element in p) {
var result = await Process.run('powershell', [
"(fsutil fsinfo sectorinfo $element: | Select-String 'PhysicalBytesPerSectorForPerformance').ToString().Split(':')[1].Trim()"
]);
dPrint(result.stdout);
if (result.stderr == "") {
final rs = result.stdout.toString();
final physicalBytesPerSectorForPerformance = (int.tryParse(rs) ?? 0);
if (physicalBytesPerSectorForPerformance > 4096) {
checkResult?.add(MapEntry("nvme_PhysicalBytes", element));
}
}
}
} catch (e) {
dPrint(e);
}
}
void resetCheck() {
checkResult = null;
reScanPath();
notifyListeners();
}
Future<void> doFix(MapEntry<String, String> item) async {
isFixing = true;
notifyListeners();
switch (item.key) {
case "unSupport_system":
showToast(context!, "若您的硬件达标,请尝试安装最新的 Windows 系统。");
return;
case "no_live_path":
try {
await Directory(item.value).create(recursive: true);
showToast(context!, "创建文件夹成功,请尝试继续下载游戏!");
checkResult?.remove(item);
notifyListeners();
} catch (e) {
showToast(context!, "创建文件夹失败,请尝试手动创建。\n目录:${item.value} \n错误:$e");
}
return;
case "nvme_PhysicalBytes":
final r = await SystemHelper.addNvmePatch();
if (r == "") {
showToast(context!,
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
checkResult?.remove(item);
notifyListeners();
} else {
showToast(context!, "修复失败,$r");
}
return;
case "eac_file_miss":
showToast(
context!, "未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件");
return;
case "eac_not_install":
final eacJsonPath = "${item.value}\\Settings.json";
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
try {
var result = await Process.run(
"${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]);
dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID");
if (result.stderr == "") {
showToast(context!, "修复成功,请尝试启动游戏。(若问题无法解决,请使用工具箱的 《重装 EAC》");
checkResult?.remove(item);
notifyListeners();
} else {
showToast(context!, "修复失败,${result.stderr}");
}
} catch (e) {
showToast(context!, "修复失败,$e");
}
return;
case "cn_user_name":
showToast(context!, "即将跳转,教程来自互联网,请谨慎操作...");
await Future.delayed(const Duration(milliseconds: 300));
launchUrlString(
"https://btfy.eu.org/?q=5L+u5pS5d2luZG93c+eUqOaIt+WQjeS7juS4reaWh+WIsOiLseaWhw==");
return;
default:
showToast(context!, "该问题暂不支持自动处理,请提供截图寻求帮助");
return;
}
}
openDir(rsiLauncherInstalledPath) async { openDir(rsiLauncherInstalledPath) async {
await Process.run(SystemHelper.powershellPath, await Process.run(SystemHelper.powershellPath,
["explorer.exe", "/select,\"$rsiLauncherInstalledPath\""]); ["explorer.exe", "/select,\"$rsiLauncherInstalledPath\""]);
} }
onMenuTap(String key) async { onMenuTap(String key) async {
const String gameInstallReqInfo =
"该功能需要一个有效的安装位置\n\n如果您的游戏未下载完成,请等待下载完毕后使用此功能。\n\n如果您的游戏已下载完毕但未识别,请启动一次游戏后重新打开盒子 或 在设置选项中手动设置安装位置。";
switch (key) { switch (key) {
case "auto_check": case "auto_check":
doCheck()?.call(); BaseUIContainer(
uiCreate: () => GameDoctorUI(),
modelCreate: () => GameDoctorUIModel(scInstalledPath))
.push(context!);
return; return;
case "localization": case "localization":
if (scInstalledPath == "not_install") { if (scInstalledPath == "not_install") {
showToast(context!, "该功能需要一个有效的安装位置"); showToast(context!, gameInstallReqInfo);
return; return;
} }
await showDialog( await showDialog(
@ -413,7 +215,7 @@ class HomeUIModel extends BaseUIModel {
return; return;
case "performance": case "performance":
if (scInstalledPath == "not_install") { if (scInstalledPath == "not_install") {
showToast(context!, "该功能需要一个有效的安装位置"); showToast(context!, gameInstallReqInfo);
return; return;
} }
AnalyticsApi.touch("performance_launch"); AnalyticsApi.touch("performance_launch");
@ -589,7 +391,7 @@ class HomeUIModel extends BaseUIModel {
dPrint('stderr: ${result.stderr}'); dPrint('stderr: ${result.stderr}');
if (result.exitCode != 0) { if (result.exitCode != 0) {
showToast(context!, showToast(context!,
"游戏非正常退出\nexitCode=${result.exitCode}\nstdout=${result.stdout}\nstderr=${result.stderr}"); "游戏非正常退出\nexitCode=${result.exitCode}\nstdout=${result.stdout}\nstderr=${result.stderr}\n\n诊断信息:");
} }
final launchFile = File("$installPath\\loginData.json"); final launchFile = File("$installPath\\loginData.json");