feat: Linux Path Basic Support

modified:   lib/ui/settings/settings_ui_model.dart
This commit is contained in:
xkeyC 2025-12-23 16:44:37 +08:00
parent 062014f24a
commit 1a1f72a596
6 changed files with 224 additions and 218 deletions

View File

@ -1,3 +1,5 @@
import 'dart:io';
class ConstConf { class ConstConf {
static const String appVersion = "3.0.0 Beta9"; static const String appVersion = "3.0.0 Beta9";
static const int appVersionCode = 79; static const int appVersionCode = 79;
@ -18,5 +20,12 @@ class AppConf {
_networkGameChannels = channels; _networkGameChannels = channels;
} }
static List<String> get gameChannels => _networkGameChannels ?? ConstConf._gameChannels; static List<String> get gameChannels {
final baseChannels = _networkGameChannels ?? ConstConf._gameChannels;
// On Linux, add lowercase variants for case-sensitive filesystem
if (Platform.isLinux) {
return [...baseChannels, ...baseChannels.map((c) => c.toLowerCase())];
}
return baseChannels;
}
} }

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:hive_ce/hive.dart'; import 'package:hive_ce/hive.dart';
import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/log.dart';
class SCLoggerHelper { class SCLoggerHelper {
@ -43,20 +44,23 @@ class SCLoggerHelper {
} }
} }
static Future<List<String>> getGameInstallPath(List listData, static Future<List<String>> getGameInstallPath(
{bool checkExists = true, List listData, {
List<String> withVersion = const ["LIVE"]}) async { bool checkExists = true,
List<String> withVersion = const ["LIVE"],
}) async {
List<String> scInstallPaths = []; List<String> scInstallPaths = [];
checkAndAddPath(String path, bool checkExists) async { checkAndAddPath(String path, bool checkExists) async {
// \\ \ // Normalize path separators to current platform format
path = path.replaceAll(RegExp(r'\\+'), "\\"); path = path.platformPath;
if (path.isNotEmpty && !scInstallPaths.contains(path)) {
// Case-insensitive check for existing paths
if (path.isNotEmpty && !scInstallPaths.any((p) => p.toLowerCase() == path.toLowerCase())) {
if (!checkExists) { if (!checkExists) {
dPrint("find installPath == $path"); dPrint("find installPath == $path");
scInstallPaths.add(path); scInstallPaths.add(path);
} else if (await File("$path/Bin64/StarCitizen.exe").exists() && } else if (await File("$path/Bin64/StarCitizen.exe").exists() && await File("$path/Data.p4k").exists()) {
await File("$path/Data.p4k").exists()) {
dPrint("find installPath == $path"); dPrint("find installPath == $path");
scInstallPaths.add(path); scInstallPaths.add(path);
} }
@ -67,14 +71,14 @@ class SCLoggerHelper {
final path = confBox.get("custom_game_path"); final path = confBox.get("custom_game_path");
if (path != null && path != "") { if (path != null && path != "") {
for (var v in withVersion) { for (var v in withVersion) {
await checkAndAddPath("$path\\$v", checkExists); await checkAndAddPath("$path\\$v".platformPath, checkExists);
} }
} }
try { try {
for (var v in withVersion) { for (var v in withVersion) {
String pattern = // Match both Windows (\\) and Unix (/) path separators in log entries, case-insensitive
r'([a-zA-Z]:\\\\[^\\\\]*\\\\[^\\\\]*\\\\StarCitizen\\\\' + v + r')'; String pattern = r'([a-zA-Z]:[\\/][^\\/]*[\\/][^\\/]*[\\/]StarCitizen[\\/]' + v + r')';
RegExp regExp = RegExp(pattern, caseSensitive: false); RegExp regExp = RegExp(pattern, caseSensitive: false);
for (var i = listData.length - 1; i > 0; i--) { for (var i = listData.length - 1; i > 0; i--) {
final line = listData[i]; final line = listData[i];
@ -89,10 +93,14 @@ class SCLoggerHelper {
// //
for (var fileName in List.from(scInstallPaths)) { for (var fileName in List.from(scInstallPaths)) {
for (var v in withVersion) { for (var v in withVersion) {
if (fileName.toString().endsWith(v)) { final suffix = '\\$v'.platformPath.toLowerCase();
if (fileName.toString().toLowerCase().endsWith(suffix)) {
for (var nv in withVersion) { for (var nv in withVersion) {
final nextName = final basePath = fileName.toString().replaceAll(
"${fileName.toString().replaceAll("\\$v", "")}\\$nv"; RegExp('${RegExp.escape(suffix)}\$', caseSensitive: false),
'',
);
final nextName = "$basePath\\$nv".platformPath;
await checkAndAddPath(nextName, true); await checkAndAddPath(nextName, true);
} }
} }
@ -108,9 +116,10 @@ class SCLoggerHelper {
} }
static String getGameChannelID(String installPath) { static String getGameChannelID(String installPath) {
final pathLower = installPath.platformPath.toLowerCase();
for (var value in AppConf.gameChannels) { for (var value in AppConf.gameChannels) {
if (installPath.endsWith("\\$value")) { if (pathLower.endsWith('\\${value.toLowerCase()}'.platformPath)) {
return value; return value.toUpperCase();
} }
} }
return "UNKNOWN"; return "UNKNOWN";
@ -121,8 +130,7 @@ class SCLoggerHelper {
if (!await logFile.exists()) { if (!await logFile.exists()) {
return null; return null;
} }
return await logFile.readAsLines( return await logFile.readAsLines(encoding: const Utf8Codec(allowMalformed: true));
encoding: const Utf8Codec(allowMalformed: true));
} }
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) { static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
@ -138,47 +146,47 @@ class SCLoggerHelper {
static MapEntry<String, String>? _checkRunningLine(String line) { static MapEntry<String, String>? _checkRunningLine(String line) {
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) { if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
return MapEntry(S.current.doctor_game_error_low_memory, return MapEntry(S.current.doctor_game_error_low_memory, S.current.doctor_game_error_low_memory_info);
S.current.doctor_game_error_low_memory_info);
} }
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) { if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
return MapEntry(S.current.doctor_game_error_generic_info, return MapEntry(S.current.doctor_game_error_generic_info, "https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
"https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
} }
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) { if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
return MapEntry(S.current.doctor_game_error_gpu_crash, return MapEntry(S.current.doctor_game_error_gpu_crash, "https://www.bilibili.com/read/cv19335199");
"https://www.bilibili.com/read/cv19335199");
} }
if (line.contains("Wakeup socket sendto error")) { if (line.contains("Wakeup socket sendto error")) {
return MapEntry(S.current.doctor_game_error_socket_error, return MapEntry(S.current.doctor_game_error_socket_error, S.current.doctor_game_error_socket_error_info);
S.current.doctor_game_error_socket_error_info);
} }
if (line.contains("The requested operation requires elevated")) { if (line.contains("The requested operation requires elevated")) {
return MapEntry(S.current.doctor_game_error_permissions_error, return MapEntry(
S.current.doctor_game_error_permissions_error_info); S.current.doctor_game_error_permissions_error,
S.current.doctor_game_error_permissions_error_info,
);
} }
if (line.contains( if (line.contains("The process cannot access the file because is is being used by another process")) {
"The process cannot access the file because is is being used by another process")) { return MapEntry(
return MapEntry(S.current.doctor_game_error_game_process_error, S.current.doctor_game_error_game_process_error,
S.current.doctor_game_error_game_process_error_info); S.current.doctor_game_error_game_process_error_info,
);
} }
if (line.contains("0xc0000043")) { if (line.contains("0xc0000043")) {
return MapEntry(S.current.doctor_game_error_game_damaged_file, return MapEntry(
S.current.doctor_game_error_game_damaged_file_info); S.current.doctor_game_error_game_damaged_file,
S.current.doctor_game_error_game_damaged_file_info,
);
} }
if (line.contains("option to verify the content of the Data.p4k file")) { if (line.contains("option to verify the content of the Data.p4k file")) {
return MapEntry(S.current.doctor_game_error_game_damaged_p4k_file, return MapEntry(
S.current.doctor_game_error_game_damaged_p4k_file_info); S.current.doctor_game_error_game_damaged_p4k_file,
S.current.doctor_game_error_game_damaged_p4k_file_info,
);
} }
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) { if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
return MapEntry(S.current.doctor_game_error_low_gpu_memory, return MapEntry(S.current.doctor_game_error_low_gpu_memory, S.current.doctor_game_error_low_gpu_memory_info);
S.current.doctor_game_error_low_gpu_memory_info);
} }
if (line.contains( if (line.contains("try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
"try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) { return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash, S.current.doctor_game_error_gpu_vulkan_crash_info);
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash,
S.current.doctor_game_error_gpu_vulkan_crash_info);
} }
/// Unknown /// Unknown

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -7,8 +8,22 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:starcitizen_doctor/generated/l10n.dart'; import 'package:starcitizen_doctor/generated/l10n.dart';
/// String extension for cross-platform path compatibility
extension PathStringExtension on String {
/// Converts path separators to the current platform's format.
/// On Windows: / -> \
/// On Linux/macOS: \ -> /
String get platformPath {
if (Platform.isWindows) {
return replaceAll('/', '\\');
}
return replaceAll('\\', '/');
}
}
Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async { Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async {
return showBaseDialog(context, return showBaseDialog(
context,
title: title ?? S.current.app_common_tip, title: title ?? S.current.app_common_tip,
content: Text(msg), content: Text(msg),
actions: [ actions: [
@ -20,50 +35,52 @@ Future showToast(BuildContext context, String msg, {BoxConstraints? constraints,
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
], ],
constraints: constraints); constraints: constraints,
);
} }
Future<bool> showConfirmDialogs(BuildContext context, String title, Widget content, Future<bool> showConfirmDialogs(
{String confirm = "", String cancel = "", BoxConstraints? constraints}) async { BuildContext context,
String title,
Widget content, {
String confirm = "",
String cancel = "",
BoxConstraints? constraints,
}) async {
if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm; if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm;
if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel; if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel;
final r = await showBaseDialog(context, final r = await showBaseDialog(
context,
title: title, title: title,
content: content, content: content,
actions: [ actions: [
if (confirm.isNotEmpty) if (confirm.isNotEmpty)
FilledButton( FilledButton(
child: Padding( child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(confirm)),
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(confirm),
),
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
), ),
if (cancel.isNotEmpty) if (cancel.isNotEmpty)
Button( Button(
child: Padding( child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(cancel)),
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(cancel),
),
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
), ),
], ],
constraints: constraints); constraints: constraints,
);
return r == true; return r == true;
} }
Future<String?> showInputDialogs(BuildContext context, Future<String?> showInputDialogs(
{required String title, BuildContext context, {
required String title,
required String content, required String content,
BoxConstraints? constraints, BoxConstraints? constraints,
String? initialValue, String? initialValue,
List<TextInputFormatter>? inputFormatters}) async { List<TextInputFormatter>? inputFormatters,
}) async {
String? userInput; String? userInput;
constraints ??= BoxConstraints(maxWidth: MediaQuery constraints ??= BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .38);
.of(context)
.size
.width * .38);
final ok = await showConfirmDialogs( final ok = await showConfirmDialogs(
context, context,
title, title,
@ -71,11 +88,7 @@ Future<String?> showInputDialogs(BuildContext context,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (content.isNotEmpty) if (content.isNotEmpty) Text(content, style: TextStyle(color: Colors.white.withValues(alpha: .6))),
Text(
content,
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
),
const SizedBox(height: 8), const SizedBox(height: 8),
TextFormBox( TextFormBox(
initialValue: initialValue, initialValue: initialValue,
@ -86,24 +99,25 @@ Future<String?> showInputDialogs(BuildContext context,
), ),
], ],
), ),
constraints: constraints); constraints: constraints,
);
if (ok == true) return userInput; if (ok == true) return userInput;
return null; return null;
} }
Future showBaseDialog(BuildContext context, Future showBaseDialog(
{required String title, required Widget content, List<Widget>? actions, BoxConstraints? constraints}) async { BuildContext context, {
required String title,
required Widget content,
List<Widget>? actions,
BoxConstraints? constraints,
}) async {
return await showDialog( return await showDialog(
context: context, context: context,
builder: (context) => builder: (context) => ContentDialog(
ContentDialog(
title: Text(title), title: Text(title),
content: content, content: content,
constraints: constraints ?? constraints: constraints ?? const BoxConstraints(maxWidth: 512, maxHeight: 756.0),
const BoxConstraints(
maxWidth: 512,
maxHeight: 756.0,
),
actions: actions, actions: actions,
), ),
); );

View File

@ -36,17 +36,9 @@ class GuideUI extends HookConsumerWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: Row( child: Row(
children: [ children: [
Image.asset( Image.asset("assets/app_logo_mini.png", width: 20, height: 20, fit: BoxFit.cover),
"assets/app_logo_mini.png",
width: 20,
height: 20,
fit: BoxFit.cover,
),
const SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
S.current.app_index_version_info(
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
)
], ],
), ),
), ),
@ -56,12 +48,7 @@ class GuideUI extends HookConsumerWidget {
children: [ children: [
Image.asset("assets/app_logo.png", width: 192, height: 192), Image.asset("assets/app_logo.png", width: 192, height: 192),
SizedBox(height: 12), SizedBox(height: 12),
Text( Text(S.current.guide_title_welcome, style: TextStyle(fontSize: 38)),
S.current.guide_title_welcome,
style: TextStyle(
fontSize: 38,
),
),
SizedBox(height: 24), SizedBox(height: 24),
Text(S.current.guide_info_check_settings), Text(S.current.guide_info_check_settings),
SizedBox(height: 32), SizedBox(height: 32),
@ -72,26 +59,23 @@ class GuideUI extends HookConsumerWidget {
Expanded( Expanded(
child: Column( child: Column(
children: [ children: [
makeGameLauncherPathSelect( makeGameLauncherPathSelect(context, toolsModel, toolsState, settingModel),
context, toolsModel, toolsState, settingModel),
const SizedBox(height: 12), const SizedBox(height: 12),
makeGamePathSelect( makeGamePathSelect(context, toolsModel, toolsState, settingModel),
context, toolsModel, toolsState, settingModel),
], ],
), ),
), ),
SizedBox(width: 12), SizedBox(width: 12),
Button( Button(
onPressed: () => toolsModel.reScanPath(context, onPressed: () => toolsModel.reScanPath(context, checkActive: true, skipToast: true),
checkActive: true, skipToast: true),
child: const Padding( child: const Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(top: 30, bottom: 30, left: 12, right: 12),
top: 30, bottom: 30, left: 12, right: 12),
child: Icon(FluentIcons.refresh), child: Icon(FluentIcons.refresh),
), ),
), ),
], ],
)), ),
),
SizedBox(height: 12), SizedBox(height: 12),
Padding( Padding(
padding: const EdgeInsets.only(right: 32, left: 32), padding: const EdgeInsets.only(right: 32, left: 32),
@ -100,9 +84,7 @@ class GuideUI extends HookConsumerWidget {
Expanded( Expanded(
child: Text( child: Text(
S.current.guide_info_game_download_note, S.current.guide_info_game_download_note,
style: TextStyle( style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
fontSize: 12,
color: Colors.white.withValues(alpha: .6)),
textAlign: TextAlign.end, textAlign: TextAlign.end,
), ),
), ),
@ -115,8 +97,7 @@ class GuideUI extends HookConsumerWidget {
Spacer(), Spacer(),
Button( Button(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(S.current.guide_action_get_help), child: Text(S.current.guide_action_get_help),
), ),
onPressed: () { onPressed: () {
@ -126,8 +107,7 @@ class GuideUI extends HookConsumerWidget {
SizedBox(width: 24), SizedBox(width: 24),
FilledButton( FilledButton(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(S.current.guide_action_complete_setup), child: Text(S.current.guide_action_complete_setup),
), ),
onPressed: () async { onPressed: () async {
@ -135,8 +115,8 @@ class GuideUI extends HookConsumerWidget {
final ok = await showConfirmDialogs( final ok = await showConfirmDialogs(
context, context,
S.current.guide_dialog_confirm_complete_setup, S.current.guide_dialog_confirm_complete_setup,
Text(S.current Text(S.current.guide_action_info_no_launcher_path_warning),
.guide_action_info_no_launcher_path_warning)); );
if (!ok) return; if (!ok) return;
} }
if (toolsState.scInstallPaths.isEmpty) { if (toolsState.scInstallPaths.isEmpty) {
@ -144,8 +124,8 @@ class GuideUI extends HookConsumerWidget {
final ok = await showConfirmDialogs( final ok = await showConfirmDialogs(
context, context,
S.current.guide_dialog_confirm_complete_setup, S.current.guide_dialog_confirm_complete_setup,
Text(S Text(S.current.guide_action_info_no_game_path_warning),
.current.guide_action_info_no_game_path_warning)); );
if (!ok) return; if (!ok) return;
} }
final appConf = await Hive.openBox("app_conf"); final appConf = await Hive.openBox("app_conf");
@ -164,8 +144,12 @@ class GuideUI extends HookConsumerWidget {
); );
} }
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model, Widget makeGameLauncherPathSelect(
ToolsUIState state, SettingsUIModel settingModel) { BuildContext context,
ToolsUIModel model,
ToolsUIState state,
SettingsUIModel settingModel,
) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -177,13 +161,7 @@ class GuideUI extends HookConsumerWidget {
child: ComboBox<String>( child: ComboBox<String>(
isExpanded: true, isExpanded: true,
value: state.rsiLauncherInstalledPath, value: state.rsiLauncherInstalledPath,
items: [ items: [for (final path in state.rsiLauncherInstallPaths) ComboBoxItem(value: path, child: Text(path))],
for (final path in state.rsiLauncherInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
onChanged: (v) { onChanged: (v) {
model.onChangeLauncherPath(v!); model.onChangeLauncherPath(v!);
}, },
@ -192,10 +170,7 @@ class GuideUI extends HookConsumerWidget {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Button( Button(
child: const Padding( child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_search),
),
onPressed: () async { onPressed: () async {
await settingModel.setLauncherPath(context); await settingModel.setLauncherPath(context);
if (!context.mounted) return; if (!context.mounted) return;
@ -206,8 +181,12 @@ class GuideUI extends HookConsumerWidget {
); );
} }
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model, Widget makeGamePathSelect(
ToolsUIState state, SettingsUIModel settingModel) { BuildContext context,
ToolsUIModel model,
ToolsUIState state,
SettingsUIModel settingModel,
) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -219,13 +198,7 @@ class GuideUI extends HookConsumerWidget {
child: ComboBox<String>( child: ComboBox<String>(
isExpanded: true, isExpanded: true,
value: state.scInstalledPath, value: state.scInstalledPath,
items: [ items: [for (final path in state.scInstallPaths) ComboBoxItem(value: path, child: Text(path))],
for (final path in state.scInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
onChanged: (v) { onChanged: (v) {
model.onChangeGamePath(v!); model.onChangeGamePath(v!);
}, },
@ -234,15 +207,13 @@ class GuideUI extends HookConsumerWidget {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Button( Button(
child: const Padding( child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_search),
),
onPressed: () async { onPressed: () async {
await settingModel.setGamePath(context); await settingModel.setGamePath(context);
if (!context.mounted) return; if (!context.mounted) return;
model.reScanPath(context, checkActive: true, skipToast: true); model.reScanPath(context, checkActive: true, skipToast: true);
}) },
),
], ],
); );
} }

View File

@ -216,7 +216,8 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
} }
String getChannelID(String installPath) { String getChannelID(String installPath) {
if (installPath.endsWith("\\LIVE")) { final pathLower = installPath.platformPath.toLowerCase();
if (pathLower.endsWith('\\live'.platformPath)) {
return "LIVE"; return "LIVE";
} }
return "PTU"; return "PTU";

View File

@ -80,8 +80,8 @@ class SettingsUIModel extends _$SettingsUIModel {
lockParentWindow: true, lockParentWindow: true,
); );
if (r == null || r.files.firstOrNull?.path == null) return; if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!; final fileName = r.files.first.path!.platformPath;
if (fileName.endsWith("\\RSI Launcher.exe")) { if (fileName.toLowerCase().endsWith('\\rsi launcher.exe'.platformPath)) {
await _saveCustomPath("custom_launcher_path", fileName); await _saveCustomPath("custom_launcher_path", fileName);
if (!context.mounted) return; if (!context.mounted) return;
showToast(context, S.current.setting_action_info_setting_success); showToast(context, S.current.setting_action_info_setting_success);
@ -101,11 +101,14 @@ class SettingsUIModel extends _$SettingsUIModel {
lockParentWindow: true, lockParentWindow: true,
); );
if (r == null || r.files.firstOrNull?.path == null) return; if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!; final fileName = r.files.first.path!.platformPath;
dPrint(fileName); dPrint(fileName);
final fileNameRegExp = RegExp(r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$", caseSensitive: false); final fileNameRegExp = RegExp(
r'^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$'.platformPath,
caseSensitive: false,
);
if (fileNameRegExp.hasMatch(fileName)) { if (fileNameRegExp.hasMatch(fileName)) {
RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$"); RegExp pathRegex = RegExp(r'\\[^\\]+\\Bin64\\StarCitizen\.exe$'.platformPath, caseSensitive: false);
String extractedPath = fileName.replaceFirst(pathRegex, ''); String extractedPath = fileName.replaceFirst(pathRegex, '');
await _saveCustomPath("custom_game_path", extractedPath); await _saveCustomPath("custom_game_path", extractedPath);
if (!context.mounted) return; if (!context.mounted) return;