mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-23 00:33:42 +08:00
一键诊断:支持闪退诊断
This commit is contained in:
parent
27640ec6b7
commit
3ef5df8a80
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,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) {
|
||||
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,29 +141,90 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
"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}";
|
||||
}
|
||||
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user