一键诊断:支持闪退诊断

This commit is contained in:
xkeyC 2024-02-17 00:35:21 +08:00
parent 27640ec6b7
commit 3ef5df8a80
3 changed files with 180 additions and 46 deletions

View File

@ -113,11 +113,69 @@ class SCLoggerHelper {
return scInstallPaths;
}
static String getGameLogs(String gameDir) {
return "";
static Future<List<String>?> getGameRunningLogs(String gameDir) async {
final logFile = File("$gameDir/Game.log");
if (!await logFile.exists()) {
return null;
}
return await logFile.readAsLines(
encoding: const Utf8Codec(allowMalformed: true));
}
static String getGameExitLogInfo(String logs) {
return "";
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
for (var i = logs.length - 1; i > 0; i--) {
final line = logs[i];
final r = _checkRunningLine(line);
if (r != null) {
return r;
}
}
return null;
}
static MapEntry<String, String>? _checkRunningLine(String line) {
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
return const MapEntry("可用内存不足", "请尝试增加虚拟内存( 1080p 下, 物理可用+虚拟内存需 > 64G ");
}
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
return const MapEntry("游戏触发了最为广泛的崩溃问题,请查看排障指南:",
"https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
}
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
return const MapEntry(
"您的显卡崩溃啦!,请查看排障指南:", "https://www.bilibili.com/read/cv19335199");
}
if (line.contains("Wakeup socket sendto error")) {
return const MapEntry("检测到 socket 异常", "如使用 X黑盒 加速器,请尝试更换加速模式");
}
if (line.contains("The requested operation requires elevated")) {
return const MapEntry("权限不足", "请尝试以管理员权限运行启动器,或使用盒子(微软商店版)启动。");
}
if (line.contains(
"The process cannot access the file because is is being used by another process")) {
return const MapEntry("游戏进程被占用", "请尝试重启启动器,或直接重启电脑");
}
if (line.contains("0xc0000043")) {
return const MapEntry("游戏程序文件损坏", "请尝试删除 Bin64 文件夹 并在启动器校验。");
}
if (line.contains("option to verify the content of the Data.p4k file")) {
return const MapEntry("P4K文件损坏", "请尝试删除 Data.p4k 文件 并在启动器校验 或 使用盒子分流。");
}
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
return const MapEntry("可用显存不足", "请不要在后台运行其他高显卡占用的 游戏/应用,或更换显卡。");
}
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
return const MapEntry("可用显存不足", "请不要在后台运行其他高显卡占用的 游戏/应用,或更换显卡。");
}
/// Unknown
if (line.contains("network.replicatedEntityHandle")) {
return const MapEntry("_", "network.replicatedEntityHandle");
}
if (line.contains("Exception Unknown")) {
return const MapEntry("_", "Exception Unknown");
}
return null;
}
}

View File

@ -26,23 +26,19 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
))
else if (model.checkResult == null ||
model.checkResult!.isEmpty) ...[
Expanded(
const Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 12),
const Text("扫描完毕,没有找到问题!", maxLines: 1),
const SizedBox(height: 64),
makeRescueBanner(context),
SizedBox(height: 12),
Text("扫描完毕,没有找到问题!", maxLines: 1),
SizedBox(height: 64),
],
),
))
] else ...[
] else
...makeResult(context, model),
const SizedBox(height: 64),
makeRescueBanner(context),
],
],
),
if (model.isFixing)
@ -62,6 +58,11 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
],
),
),
),
Positioned(
bottom: 20,
right: 20,
child: makeRescueBanner(context),
)
],
));
@ -70,12 +71,12 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
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: 12),
Text(
"注意:本工具检测结果仅供参考,若您不理解以下操作,请提供截图给有经验的玩家!",
style: TextStyle(color: Colors.red, fontSize: 16),
),
const SizedBox(height: 24),
ListView.builder(
itemCount: model.checkResult!.length,
@ -86,11 +87,7 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
return makeResultItem(item, model);
},
),
const SizedBox(height: 24),
Text(
"注意:本工具检测结果仅供参考,若您不理解以上操作,请提供截图给有经验的玩家!",
style: TextStyle(color: Colors.red, fontSize: 16),
),
const SizedBox(height: 64),
];
}
@ -116,7 +113,7 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
children: [
Image.asset("assets/rescue.png", width: 24, height: 24),
const SizedBox(width: 12),
const Text("需要帮助? 点击加群获得免费人工支援!"),
const Text("需要帮助? 点击加群寻求免费人工支援!"),
],
),
)),
@ -144,11 +141,31 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
"low_ram": MapEntry(
"物理内存过低", "您至少需要 16GB 的物理内存Memory才可运行此游戏。当前大小${item.value}"),
};
return ListTile(
title: Text(errorNames[item.key]?.key ?? item.key),
bool isCheckedError = errorNames.containsKey(item.key);
if (isCheckedError) {
return Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text(
errorNames[item.key]?.key ?? "",
style: const TextStyle(fontSize: 18),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4, bottom: 4),
child: Text("修复建议: ${errorNames[item.key]?.value ?? "暂无解决方法,请截图反馈"}"),
child: Column(
children: [
const SizedBox(height: 4),
Text(
"修复建议: ${errorNames[item.key]?.value ?? "暂无解决方法,请截图反馈"}",
style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.7)),
),
],
),
),
trailing: Button(
onPressed: (errorNames[item.key]?.value == null || model.isFixing)
@ -163,10 +180,51 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
child: Text("修复"),
),
),
),
);
}
final isSubTitleUrl = item.value.startsWith("https://");
return Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text(
item.key,
style: const TextStyle(fontSize: 18),
),
subtitle: isSubTitleUrl
? null
: Column(
children: [
const SizedBox(height: 4),
Text(
item.value,
style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.7)),
),
],
),
trailing: isSubTitleUrl
? Button(
onPressed: () {
launchUrlString(item.value);
},
child: const Padding(
padding:
EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
child: Text("查看解决方案"),
),
)
: null,
),
);
}
@override
String getUITitle(BuildContext context, GameDoctorUIModel model) =>
"一键诊断 -> ${model.scInstalledPath}";
"一键诊断 > ${model.scInstalledPath}";
}

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@ -48,14 +49,15 @@ class GameDoctorUIModel extends BaseUIModel {
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", ""));
await _checkPreInstall();
await _checkEAC();
await _checkGameRunningLog();
if (checkResult!.isEmpty) {
checkResult = null;
lastScreenInfo = "分析完毕,没有发现问题";
@ -68,6 +70,22 @@ class GameDoctorUIModel extends BaseUIModel {
}
}
Future _checkGameRunningLog() async {
if (scInstalledPath == "not_install") return;
lastScreenInfo = "正在检查Game.log";
final logs = await SCLoggerHelper.getGameRunningLogs(scInstalledPath);
if (logs == null) return;
final info = SCLoggerHelper.getGameRunningLogInfo(logs);
if (info != null) {
if (info.key != "_") {
checkResult?.add(MapEntry("游戏异常退出:${info.key}", info.value));
} else {
checkResult
?.add(MapEntry("游戏异常退出:未知异常", "info:${info.value},请点击右下角加群反馈。"));
}
}
}
Future _checkEAC() async {
if (scInstalledPath == "not_install") return;
lastScreenInfo = "正在检查EAC";