diff --git a/lib/common/helper/log_helper.dart b/lib/common/helper/log_helper.dart index 6743d9e..995b1ca 100644 --- a/lib/common/helper/log_helper.dart +++ b/lib/common/helper/log_helper.dart @@ -113,11 +113,69 @@ class SCLoggerHelper { return scInstallPaths; } - static String getGameLogs(String gameDir) { - return ""; + static Future?> 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? getGameRunningLogInfo(List 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? _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; } } diff --git a/lib/ui/home/game_doctor/game_doctor_ui.dart b/lib/ui/home/game_doctor/game_doctor_ui.dart index 161faad..bfaa1fb 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui.dart @@ -26,23 +26,19 @@ class GameDoctorUI extends BaseUI { )) 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,7 +58,12 @@ class GameDoctorUI extends BaseUI { ], ), ), - ) + ), + Positioned( + bottom: 20, + right: 20, + child: makeRescueBanner(context), + ) ], )); } @@ -70,12 +71,12 @@ class GameDoctorUI extends BaseUI { List 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 { 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 { children: [ Image.asset("assets/rescue.png", width: 24, height: 24), const SizedBox(width: 12), - const Text("需要帮助? 点击加群获得免费人工支援!"), + const Text("需要帮助? 点击加群寻求免费人工支援!"), ], ), )), @@ -144,29 +141,90 @@ class GameDoctorUI extends BaseUI { "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("修复"), + 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: 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 String getUITitle(BuildContext context, GameDoctorUIModel model) => - "一键诊断 -> ${model.scInstalledPath}"; + "一键诊断 > ${model.scInstalledPath}"; } diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.dart index ea7cf0d..52789bc 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.dart @@ -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";