一键诊断:支持闪退诊断

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

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@ -48,14 +49,15 @@ class GameDoctorUIModel extends BaseUIModel {
Future _statCheck() async { Future _statCheck() async {
checkResult = []; checkResult = [];
await _checkPreInstall();
await _checkEAC();
// TODO for debug // TODO for debug
// checkResult?.add(const MapEntry("unSupport_system", "android")); // checkResult?.add(const MapEntry("unSupport_system", "android"));
// checkResult?.add(const MapEntry("nvme_PhysicalBytes", "C")); // checkResult?.add(const MapEntry("nvme_PhysicalBytes", "C"));
// checkResult?.add(const MapEntry("no_live_path", "")); // checkResult?.add(const MapEntry("no_live_path", ""));
await _checkPreInstall();
await _checkEAC();
await _checkGameRunningLog();
if (checkResult!.isEmpty) { if (checkResult!.isEmpty) {
checkResult = null; checkResult = null;
lastScreenInfo = "分析完毕,没有发现问题"; 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 { Future _checkEAC() async {
if (scInstalledPath == "not_install") return; if (scInstalledPath == "not_install") return;
lastScreenInfo = "正在检查EAC"; lastScreenInfo = "正在检查EAC";