feat: linux support

This commit is contained in:
xkeyC 2025-12-23 22:40:05 +08:00
parent 66ead87d47
commit bba2dbd360
20 changed files with 514 additions and 472 deletions

View File

@ -8,7 +8,15 @@ import 'package:starcitizen_doctor/common/utils/log.dart';
class SCLoggerHelper { class SCLoggerHelper {
static Future<String?> getLogFilePath() async { static Future<String?> getLogFilePath() async {
if (!Platform.isWindows) return null; if (!Platform.isWindows) {
final wineUserPath = await getWineUserPath();
if (wineUserPath == null) return null;
// /home/xkeyc/Games/star-citizen/drive_c/users/xkeyc/AppData/Roaming/rsilauncher/
final rsiLauncherPath = "$wineUserPath/AppData/Roaming/rsilauncher";
dPrint("rsiLauncherPath Wine:$rsiLauncherPath");
final jsonLogPath = "$rsiLauncherPath/logs/log.log";
return jsonLogPath;
};
Map<String, String> envVars = Platform.environment; Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["appdata"]; final appDataPath = envVars["appdata"];
if (appDataPath == null) { if (appDataPath == null) {
@ -21,6 +29,14 @@ class SCLoggerHelper {
} }
static Future<String?> getShaderCachePath() async { static Future<String?> getShaderCachePath() async {
if (!Platform.isWindows) {
final wineUserPath = await getWineUserPath();
if (wineUserPath == null) return null;
// /home/xkeyc/Games/star-citizen/drive_c/users/xkeyc/AppData/Local/star citizen/
final scCachePath = "$wineUserPath/AppData/Local/star citizen";
dPrint("getShaderCachePath Wine === $scCachePath");
return scCachePath;
}
Map<String, String> envVars = Platform.environment; Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["LOCALAPPDATA"]; final appDataPath = envVars["LOCALAPPDATA"];
if (appDataPath == null) { if (appDataPath == null) {
@ -31,6 +47,23 @@ class SCLoggerHelper {
return scCachePath; return scCachePath;
} }
static Future<String?> getWineUserPath() async {
// get game path in hiveBox
final confBox = await Hive.openBox("app_conf");
final path = confBox.get("custom_game_path");
if (path?.isEmpty ?? true) return null;
// path eg: /home/xkeyc/Games/star-citizen/drive_c/Program Files/Roberts Space Industries/StarCitizen/LIVE/
// resolve wine c_drive path
final wineCDrivePath = path.toString().split('/drive_c/').first;
// scan wine user path == current_unix_user
final wineUserPath = "$wineCDrivePath/drive_c/users/${Platform.environment['USER']}";
// check exists
final wineUserDir = Directory(wineUserPath);
if (!await wineUserDir.exists()) return null;
dPrint("getWineUserPath === $wineUserPath");
return wineUserPath;
}
static Future<List?> getLauncherLogList() async { static Future<List?> getLauncherLogList() async {
if (!Platform.isWindows) return []; if (!Platform.isWindows) return [];
try { try {

View File

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:hive_ce/hive.dart'; import 'package:hive_ce/hive.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';
import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32;
@ -41,7 +42,7 @@ class SystemHelper {
if (path != null && path != "") { if (path != null && path != "") {
if (await File(path).exists()) { if (await File(path).exists()) {
if (skipEXE) { if (skipEXE) {
return "${path.toString().replaceAll("\\RSI Launcher.exe", "")}\\"; return "${path.toString().replaceAll("\\RSI Launcher.exe".platformPath, "")}\\".platformPath;
} }
return path; return path;
} }

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
@ -112,7 +113,7 @@ class MultiWindowManager {
await Window.initialize(); await Window.initialize();
if (windowAppState.windowsVersion >= 10) { if (Platform.isWindows && windowAppState.windowsVersion >= 10) {
await Window.setEffect(effect: WindowEffect.acrylic); await Window.setEffect(effect: WindowEffect.acrylic);
} }

View File

@ -10,6 +10,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart'; import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart'; import 'package:starcitizen_doctor/common/helper/log_helper.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';
import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart'; import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart'; import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
@ -80,7 +81,7 @@ class Unp4kCModel extends _$Unp4kCModel {
void _init() async { void _init() async {
final gamePath = getGamePath(); final gamePath = getGamePath();
final gameP4kPath = "$gamePath\\Data.p4k"; final gameP4kPath = "$gamePath\\Data.p4k".platformPath;
try { try {
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading); state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading);

View File

@ -30,13 +30,9 @@ class LocalizationDialogUI extends HookConsumerWidget {
return ContentDialog( return ContentDialog(
title: makeTitle(context, model, state), title: makeTitle(context, model, state),
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery maxWidth: MediaQuery.of(context).size.width * .7,
.of(context) minHeight: MediaQuery.of(context).size.height * .9,
.size ),
.width * .7, minHeight: MediaQuery
.of(context)
.size
.height * .9),
content: Padding( content: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
child: SingleChildScrollView( child: SingleChildScrollView(
@ -44,132 +40,128 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [ children: [
AnimatedSize( AnimatedSize(
duration: const Duration(milliseconds: 130), duration: const Duration(milliseconds: 130),
child: state.patchStatus?.key == true && child:
state.patchStatus?.value == S.current.home_action_info_game_built_in state.patchStatus?.key == true &&
state.patchStatus?.value == S.current.home_action_info_game_built_in
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: InfoBar( child: InfoBar(
title: Text(S.current.home_action_info_warning), title: Text(S.current.home_action_info_warning),
content: Text(S.current.localization_info_machine_translation_warning), content: Text(S.current.localization_info_machine_translation_warning),
severity: InfoBarSeverity.info, severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) { style: InfoBarThemeData(
return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0)); decoration: (severity) {
}, iconColor: (severity) { return const BoxDecoration(color: Color.fromRGBO(155, 7, 7, 1.0));
return Colors.white; },
}), iconColor: (severity) {
), return Colors.white;
) },
: SizedBox( ),
width: MediaQuery ),
.of(context) )
.size : SizedBox(width: MediaQuery.of(context).size.width),
.width,
),
), ),
makeToolsListContainer(context, model, state), makeToolsListContainer(context, model, state),
makeListContainer( makeListContainer(S.current.localization_info_translation, [
S.current.localization_info_translation, if (state.patchStatus == null)
[ makeLoading(context)
if (state.patchStatus == null) else ...[
makeLoading(context) const SizedBox(height: 6),
else Row(
...[ children: [
const SizedBox(height: 6), Center(
Row( child: Text(
children: [ S.current.localization_info_enabled(
Center( LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "",
child: Text(S.current.localization_info_enabled( ),
LocalizationUIModel.languageSupport[state.selectedLanguage] ?? "")),
),
const Spacer(),
ToggleSwitch(
checked: state.patchStatus?.key == true,
onChanged: model.updateLangCfg,
)
],
), ),
const SizedBox(height: 12), ),
Row( const Spacer(),
ToggleSwitch(checked: state.patchStatus?.key == true, onChanged: model.updateLangCfg),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Row(
children: [ children: [
Expanded( Text(
child: Row( S.current.localization_info_installed_version(
children: [ "${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}",
Text(S.current.localization_info_installed_version(
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S
.current.home_localization_msg_version_advanced : ""}")),
SizedBox(width: 24),
if (state.installedCommunityInputMethodSupportVersion != null)
Text(
S.current.input_method_community_input_method_support_version(
state.installedCommunityInputMethodSupportVersion ?? "?"),
)
],
), ),
), ),
if (state.patchStatus?.value != S.current.home_action_info_game_built_in) SizedBox(width: 24),
Row( if (state.installedCommunityInputMethodSupportVersion != null)
children: [ Text(
Button( S.current.input_method_community_input_method_support_version(
onPressed: model.goFeedback, state.installedCommunityInputMethodSupportVersion ?? "?",
child: Padding( ),
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.feedback),
const SizedBox(width: 6),
Text(S.current.localization_action_translation_feedback),
],
),
)),
const SizedBox(width: 16),
Button(
onPressed: model.doDelIniFile(),
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.delete),
const SizedBox(width: 6),
Text(S.current.localization_action_uninstall_translation),
],
),
)),
],
), ),
], ],
), ),
const SizedBox(height: 12), ),
Container( if (state.patchStatus?.value != S.current.home_action_info_game_built_in)
color: Colors.white.withValues(alpha: .1), Row(
height: 1, children: [
), Button(
const SizedBox(height: 12), onPressed: model.goFeedback,
if (state.apiLocalizationData == null) child: Padding(
makeLoading(context) padding: const EdgeInsets.all(4),
else child: Row(
if (state.apiLocalizationData!.isEmpty) children: [
Center( const Icon(FluentIcons.feedback),
child: Text( const SizedBox(width: 6),
S.current.localization_info_no_translation_available, Text(S.current.localization_action_translation_feedback),
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)), ],
),
), ),
) ),
else const SizedBox(width: 16),
AlignedGridView.count( Button(
crossAxisCount: 2, onPressed: model.doDelIniFile(),
crossAxisSpacing: 12, child: Padding(
mainAxisSpacing: 12, padding: const EdgeInsets.all(4),
itemBuilder: (BuildContext context, int index) { child: Row(
final item = state.apiLocalizationData!.entries.elementAt(index); children: [
return makeRemoteList(context, model, item, state, index); const Icon(FluentIcons.delete),
}, const SizedBox(width: 6),
shrinkWrap: true, Text(S.current.localization_action_uninstall_translation),
physics: const NeverScrollableScrollPhysics(), ],
itemCount: state.apiLocalizationData?.length ?? 0, ),
) ),
], ),
], ],
context), ),
],
),
const SizedBox(height: 12),
Container(color: Colors.white.withValues(alpha: .1), height: 1),
const SizedBox(height: 12),
if (state.apiLocalizationData == null)
makeLoading(context)
else if (state.apiLocalizationData!.isEmpty)
Center(
child: Text(
S.current.localization_info_no_translation_available,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .8)),
),
)
else
AlignedGridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
itemBuilder: (BuildContext context, int index) {
final item = state.apiLocalizationData!.entries.elementAt(index);
return makeRemoteList(context, model, item, state, index);
},
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: state.apiLocalizationData?.length ?? 0,
),
],
], context),
], ],
), ),
), ),
@ -177,8 +169,13 @@ class LocalizationDialogUI extends HookConsumerWidget {
); );
} }
Widget makeRemoteList(BuildContext context, LocalizationUIModel model, MapEntry<String, ScLocalizationData> item, Widget makeRemoteList(
LocalizationUIState state, int index) { BuildContext context,
LocalizationUIModel model,
MapEntry<String, ScLocalizationData> item,
LocalizationUIState state,
int index,
) {
final isWorking = state.workingVersion.isNotEmpty; final isWorking = state.workingVersion.isNotEmpty;
final isMineWorking = state.workingVersion == item.key; final isMineWorking = state.workingVersion == item.key;
final isInstalled = state.patchStatus?.value == item.key; final isInstalled = state.patchStatus?.value == item.key;
@ -207,10 +204,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text("${item.value.info}", style: const TextStyle(fontSize: 19)),
"${item.value.info}",
style: const TextStyle(fontSize: 19),
),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
S.current.localization_info_version_number(item.value.versionName ?? ""), S.current.localization_info_version_number(item.value.versionName ?? ""),
@ -230,40 +224,30 @@ class LocalizationDialogUI extends HookConsumerWidget {
), ),
), ),
if (isMineWorking) if (isMineWorking)
const Padding( const Padding(padding: EdgeInsets.only(right: 12), child: ProgressRing())
padding: EdgeInsets.only(right: 12), else ...[
child: ProgressRing(), Icon(
) isInstalled
else ? FluentIcons.check_mark
...[ : isItemEnabled
Icon( ? FluentIcons.download
isInstalled : FluentIcons.disable_updates,
? FluentIcons.check_mark color: Colors.white.withValues(alpha: .8),
: isItemEnabled size: 18,
? FluentIcons.download ),
: FluentIcons.disable_updates, const SizedBox(width: 6),
color: Colors.white.withValues(alpha: .8), Text(
size: 18, isInstalled
), ? S.current.localization_info_installed
const SizedBox(width: 6), : (isItemEnabled
Text( ? S.current.localization_action_install
isInstalled : S.current.localization_info_unavailable),
? S.current.localization_info_installed style: TextStyle(color: Colors.white.withValues(alpha: .8)),
: (isItemEnabled ),
? S.current.localization_action_install const SizedBox(width: 6),
: S.current.localization_info_unavailable), if ((!isInstalled) && isItemEnabled)
style: TextStyle( Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)),
color: Colors.white.withValues(alpha: .8), ],
),
),
const SizedBox(width: 6),
if ((!isInstalled) && isItemEnabled)
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
)
]
], ],
), ),
if (item.value.note != null) ...[ if (item.value.note != null) ...[
@ -272,10 +256,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
"${item.value.note}", "${item.value.note}",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(color: Colors.white.withValues(alpha: .4), fontSize: 13),
color: Colors.white.withValues(alpha: .4),
fontSize: 13,
),
), ),
], ],
], ],
@ -286,16 +267,20 @@ class LocalizationDialogUI extends HookConsumerWidget {
); );
} }
Widget makeListContainer(String title, List<Widget> children, BuildContext context, Widget makeListContainer(
{List<Widget> actions = const [], bool gridViewMode = false, int gridViewCrossAxisCount = 2}) { String title,
List<Widget> children,
BuildContext context, {
List<Widget> actions = const [],
bool gridViewMode = false,
int gridViewCrossAxisCount = 2,
}) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: AnimatedSize( child: AnimatedSize(
duration: const Duration(milliseconds: 130), duration: const Duration(milliseconds: 130),
child: Container( child: Container(
decoration: BoxDecoration(color: FluentTheme decoration: BoxDecoration(color: FluentTheme.of(context).cardColor, borderRadius: BorderRadius.circular(7)),
.of(context)
.cardColor, borderRadius: BorderRadius.circular(7)),
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24), padding: const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
child: Column( child: Column(
@ -303,21 +288,13 @@ class LocalizationDialogUI extends HookConsumerWidget {
children: [ children: [
Row( Row(
children: [ children: [
Text( Text(title, style: const TextStyle(fontSize: 22)),
title,
style: const TextStyle(fontSize: 22),
),
const Spacer(), const Spacer(),
if (actions.isNotEmpty) ...actions, if (actions.isNotEmpty) ...actions,
], ],
), ),
const SizedBox( const SizedBox(height: 6),
height: 6, Container(color: Colors.white.withValues(alpha: .1), height: 1),
),
Container(
color: Colors.white.withValues(alpha: .1),
height: 1,
),
const SizedBox(height: 12), const SizedBox(height: 12),
if (gridViewMode) if (gridViewMode)
AlignedGridView.count( AlignedGridView.count(
@ -332,7 +309,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
itemCount: children.length, itemCount: children.length,
) )
else else
...children ...children,
], ],
), ),
), ),
@ -344,55 +321,46 @@ class LocalizationDialogUI extends HookConsumerWidget {
Widget makeTitle(BuildContext context, LocalizationUIModel model, LocalizationUIState state) { Widget makeTitle(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
return Row( return Row(
children: [ children: [
IconButton( IconButton(icon: const Icon(FluentIcons.back, size: 22), onPressed: model.onBack(context)),
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed: model.onBack(context)),
const SizedBox(width: 12), const SizedBox(width: 12),
Text(S.current.home_action_localization_management), Text(S.current.home_action_localization_management),
const SizedBox(width: 24), const SizedBox(width: 24),
Text( Expanded(
"${model.getScInstallPath()}", child: Text(
style: const TextStyle(fontSize: 13), "${model.getScInstallPath()}",
style: const TextStyle(fontSize: 13),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
const Spacer(), SizedBox(width: 24),
SizedBox( SizedBox(
height: 36, height: 36,
child: Row( child: Row(
children: [ children: [
Text( Text(S.current.localization_info_language, style: const TextStyle(fontSize: 16)),
S.current.localization_info_language,
style: const TextStyle(fontSize: 16),
),
const SizedBox(width: 12), const SizedBox(width: 12),
ComboBox<String>( ComboBox<String>(
value: state.selectedLanguage, value: state.selectedLanguage,
items: [ items: [
for (final lang in LocalizationUIModel.languageSupport.entries) for (final lang in LocalizationUIModel.languageSupport.entries)
ComboBoxItem( ComboBoxItem(value: lang.key, child: Text(lang.value)),
value: lang.key,
child: Text(lang.value),
)
], ],
onChanged: state.workingVersion.isNotEmpty onChanged: state.workingVersion.isNotEmpty
? null ? null
: (v) { : (v) {
if (v == null) return; if (v == null) return;
model.selectLang(v); model.selectLang(v);
}, },
) ),
], ],
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Button( Button(
onPressed: model.doRefresh(), onPressed: model.doRefresh(),
child: const Padding( child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.refresh)),
padding: EdgeInsets.all(6), ),
child: Icon(FluentIcons.refresh),
)),
], ],
); );
} }
@ -400,85 +368,77 @@ class LocalizationDialogUI extends HookConsumerWidget {
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, LocalizationUIState state) { Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model, LocalizationUIState state) {
final toolsMenu = { final toolsMenu = {
"launcher_mod": ( "launcher_mod": (
const Icon(FluentIcons.c_plus_plus, size: 24), const Icon(FluentIcons.c_plus_plus, size: 24),
(S.current.home_localization_action_rsi_launcher_localization), (S.current.home_localization_action_rsi_launcher_localization),
),
"advanced": (
const Icon(FluentIcons.queue_advanced, size: 24),
(S.current.home_localization_action_advanced),
), ),
"advanced": (const Icon(FluentIcons.queue_advanced, size: 24), (S.current.home_localization_action_advanced)),
"custom_files": ( "custom_files": (
const Icon(FluentIcons.custom_activity, size: 24), const Icon(FluentIcons.custom_activity, size: 24),
(S.current.home_localization_action_install_customize), (S.current.home_localization_action_install_customize),
), ),
}; };
final enableTap = state.workingVersion.isEmpty; final enableTap = state.workingVersion.isEmpty;
return makeListContainer( return makeListContainer(
S.current.home_localization_title_localization_tools, S.current.home_localization_title_localization_tools,
[ [
for (final item in toolsMenu.entries) for (final item in toolsMenu.entries)
Tilt( Tilt(
disable: !enableTap, disable: !enableTap,
shadowConfig: const ShadowConfig(maxIntensity: .3), shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(7), borderRadius: BorderRadius.circular(7),
child: GestureDetector( child: GestureDetector(
onTap: enableTap onTap: enableTap
? () async { ? () async {
switch (item.key) { switch (item.key) {
case "launcher_mod": case "launcher_mod":
ToolsUIModel.rsiEnhance(context); ToolsUIModel.rsiEnhance(context);
break; break;
case "advanced": case "advanced":
context.push("/index/advanced_localization"); context.push("/index/advanced_localization");
break; break;
case "custom_files": case "custom_files":
final sb = await showDialog( final sb = await showDialog(
context: context, context: context,
builder: (BuildContext context) => const LocalizationFromFileDialogUI(), builder: (BuildContext context) => const LocalizationFromFileDialogUI(),
); );
if (sb is (StringBuffer, bool)) { if (sb is (StringBuffer, bool)) {
if (!context.mounted) return; if (!context.mounted) return;
await model.installFormString( await model.installFormString(
sb.$1, sb.$1,
S.current.localization_info_custom_files, S.current.localization_info_custom_files,
isEnableCommunityInputMethod: sb.$2, isEnableCommunityInputMethod: sb.$2,
context: context, context: context,
); );
}
break;
} }
break; }
} : null,
} child: Container(
: null, decoration: BoxDecoration(
child: Container( color: FluentTheme.of(context).cardColor,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(7),
color: FluentTheme ),
.of(context) padding: const EdgeInsets.all(12),
.cardColor, child: Row(
borderRadius: BorderRadius.circular(7), children: [
), item.value.$1,
padding: const EdgeInsets.all(12), const SizedBox(width: 12),
child: Row( Text(item.value.$2),
children: [ const SizedBox(width: 12),
item.value.$1, const Spacer(),
const SizedBox(width: 12), Icon(FluentIcons.chevron_right, size: 14, color: Colors.white.withValues(alpha: .6)),
Text(item.value.$2), ],
const SizedBox(width: 12),
const Spacer(),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
)
],
),
), ),
), ),
), ),
], ),
context, ],
gridViewMode: true, context,
gridViewCrossAxisCount: 3); gridViewMode: true,
gridViewCrossAxisCount: 3,
);
} }
} }

View File

@ -48,13 +48,12 @@ abstract class LocalizationUIState with _$LocalizationUIState {
class LocalizationUIModel extends _$LocalizationUIModel { class LocalizationUIModel extends _$LocalizationUIModel {
static const languageSupport = {"chinese_(simplified)": NoL10n.langZHS, "chinese_(traditional)": NoL10n.langZHT}; static const languageSupport = {"chinese_(simplified)": NoL10n.langZHS, "chinese_(traditional)": NoL10n.langZHT};
Directory get _downloadDir => Directory("${appGlobalState.applicationSupportDir}\\Localizations"); Directory get _downloadDir => Directory("${appGlobalState.applicationSupportDir}\\Localizations".platformPath);
Directory getDownloadDir() => _downloadDir; Directory getDownloadDir() => _downloadDir;
Directory get _scDataDir => Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data"); Directory get _scDataDir => Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data".platformPath);
File get _cfgFile => File("${_scDataDir.absolute.path}\\system.cfg".platformPath);
File get _cfgFile => File("${_scDataDir.absolute.path}\\system.cfg");
StreamSubscription? _customizeDirListenSub; StreamSubscription? _customizeDirListenSub;
@ -155,7 +154,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
void checkUserCfg(BuildContext context) async { void checkUserCfg(BuildContext context) async {
final userCfgFile = File("$_scInstallPath\\USER.cfg"); final userCfgFile = File("$_scInstallPath\\USER.cfg".platformPath);
if (await userCfgFile.exists()) { if (await userCfgFile.exists()) {
final cfgString = await userCfgFile.readAsString(); final cfgString = await userCfgFile.readAsString();
if (cfgString.contains("g_language") && !cfgString.contains("g_language=${state.selectedLanguage}")) { if (cfgString.contains("g_language") && !cfgString.contains("g_language=${state.selectedLanguage}")) {
@ -241,7 +240,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
VoidCallback? doDelIniFile() { VoidCallback? doDelIniFile() {
return () async { return () async {
final iniFile = File("${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"); final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath,
);
if (await iniFile.exists()) await iniFile.delete(); if (await iniFile.exists()) await iniFile.delete();
await updateLangCfg(false); await updateLangCfg(false);
await _updateStatus(); await _updateStatus();
@ -249,7 +250,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
String getCustomizeFileName(String path) { String getCustomizeFileName(String path) {
return path.split("\\").last; return path.split("\\".platformPath).last;
} }
Future<void> installFormString( Future<void> installFormString(
@ -261,7 +262,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
BuildContext? context, BuildContext? context,
}) async { }) async {
dPrint("LocalizationUIModel -> installFormString $versionName"); dPrint("LocalizationUIModel -> installFormString $versionName");
final iniFile = File("${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"); final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath,
);
if (versionName.isNotEmpty) { if (versionName.isNotEmpty) {
if (!globalIni.toString().endsWith("\n")) { if (!globalIni.toString().endsWith("\n")) {
globalIni.write("\n"); globalIni.write("\n");
@ -323,7 +326,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
Future<Map<String, String>?> getCommunityInputMethodSupportData() async { Future<Map<String, String>?> getCommunityInputMethodSupportData() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"; final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final iniFile = File(iniPath); final iniFile = File(iniPath);
if (!await iniFile.exists()) { if (!await iniFile.exists()) {
return {}; return {};
@ -351,7 +354,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
Future<(String, String)> getIniContentWithoutCommunityInputMethodSupportData() async { Future<(String, String)> getIniContentWithoutCommunityInputMethodSupportData() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"; final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final iniFile = File(iniPath); final iniFile = File(iniPath);
if (!await iniFile.exists()) { if (!await iniFile.exists()) {
return ("", ""); return ("", "");
@ -383,7 +386,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}) async { }) async {
AnalyticsApi.touch("install_localization"); AnalyticsApi.touch("install_localization");
final savePath = File("${_downloadDir.absolute.path}\\${value.versionName}.sclang"); final savePath = File("${_downloadDir.absolute.path}\\${value.versionName}.sclang".platformPath);
try { try {
state = state.copyWith(workingVersion: value.versionName!); state = state.copyWith(workingVersion: value.versionName!);
if (!await savePath.exists()) { if (!await savePath.exists()) {
@ -488,7 +491,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
Future<void> _updateStatus() async { Future<void> _updateStatus() async {
final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"; final iniPath = "${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini".platformPath;
final patchStatus = MapEntry( final patchStatus = MapEntry(
await _getLangCfgEnableLang(lang: state.selectedLanguage!), await _getLangCfgEnableLang(lang: state.selectedLanguage!),
await _getInstalledIniVersion(iniPath), await _getInstalledIniVersion(iniPath),
@ -535,7 +538,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (gamePath.isEmpty) { if (gamePath.isEmpty) {
gamePath = _scInstallPath; gamePath = _scInstallPath;
} }
final cfgFile = File("${_scDataDir.absolute.path}\\system.cfg"); final cfgFile = File("${_scDataDir.absolute.path}\\system.cfg".platformPath);
if (!await cfgFile.exists()) return false; if (!await cfgFile.exists()) return false;
final str = (await cfgFile.readAsString()).replaceAll(" ", ""); final str = (await cfgFile.readAsString()).replaceAll(" ", "");
return str.contains("sys_languages=$lang") && return str.contains("sys_languages=$lang") &&
@ -573,13 +576,13 @@ class LocalizationUIModel extends _$LocalizationUIModel {
for (var scInstallPath in homeState.scInstallPaths) { for (var scInstallPath in homeState.scInstallPaths) {
// //
final scDataDir = Directory("$scInstallPath\\data\\Localization"); final scDataDir = Directory("$scInstallPath\\data\\Localization".platformPath);
// //
final dirList = await scDataDir.list().toList(); final dirList = await scDataDir.list().toList();
for (var element in dirList) { for (var element in dirList) {
for (var lang in languageSupport.keys) { for (var lang in languageSupport.keys) {
if (element.path.contains(lang) && await _getLangCfgEnableLang(lang: lang, gamePath: scInstallPath)) { if (element.path.contains(lang) && await _getLangCfgEnableLang(lang: lang, gamePath: scInstallPath)) {
final installedVersion = await _getInstalledIniVersion("${element.path}\\global.ini"); final installedVersion = await _getInstalledIniVersion("${element.path}\\global.ini".platformPath);
if (installedVersion == S.current.home_action_info_game_built_in || if (installedVersion == S.current.home_action_info_game_built_in ||
installedVersion == S.current.localization_info_custom_files) { installedVersion == S.current.localization_info_custom_files) {
continue; continue;

View File

@ -104,11 +104,11 @@ class SettingsUIModel extends _$SettingsUIModel {
final fileName = r.files.first.path!.platformPath; final fileName = r.files.first.path!.platformPath;
dPrint(fileName); dPrint(fileName);
final fileNameRegExp = RegExp( final fileNameRegExp = RegExp(
r'^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$'.platformPath, r'^(.*[/\\]starcitizen[/\\].*[/\\])bin64[/\\]starcitizen\.exe$',
caseSensitive: false, caseSensitive: false,
); );
if (fileNameRegExp.hasMatch(fileName)) { if (fileNameRegExp.hasMatch(fileName)) {
RegExp pathRegex = RegExp(r'\\[^\\]+\\Bin64\\StarCitizen\.exe$'.platformPath, caseSensitive: false); RegExp pathRegex = RegExp(r'[/\\][^/\\]+[/\\]bin64[/\\]starcitizen\.exe$', 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;

View File

@ -70,8 +70,11 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg1; workingText.value = S.current.tools_rsi_launcher_enhance_working_msg1;
if ((await SystemHelper.getPID("RSI Launcher")).isNotEmpty) { if ((await SystemHelper.getPID("RSI Launcher")).isNotEmpty) {
if (!context.mounted) return; if (!context.mounted) return;
showToast(context, S.current.tools_action_info_rsi_launcher_running_warning, showToast(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35)); context,
S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35),
);
workingText.value = ""; workingText.value = "";
return; return;
} }
@ -92,16 +95,16 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
return ContentDialog( return ContentDialog(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .48), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .48),
title: Row(children: [ title: Row(
IconButton( children: [
icon: const Icon( IconButton(
FluentIcons.back, icon: const Icon(FluentIcons.back, size: 22),
size: 22, onPressed: workingText.value.isEmpty ? Navigator.of(context).pop : null,
), ),
onPressed: workingText.value.isEmpty ? Navigator.of(context).pop : null), const SizedBox(width: 12),
const SizedBox(width: 12), Text(S.current.tools_rsi_launcher_enhance_title),
Text(S.current.tools_rsi_launcher_enhance_title), ],
]), ),
content: AnimatedSize( content: AnimatedSize(
duration: const Duration(milliseconds: 130), duration: const Duration(milliseconds: 130),
child: Column( child: Column(
@ -112,17 +115,16 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
InfoBar( InfoBar(
title: const SizedBox(), title: const SizedBox(),
content: Text(S.current.home_localization_action_rsi_launcher_no_game_path_msg), content: Text(S.current.home_localization_action_rsi_launcher_no_game_path_msg),
style: InfoBarThemeData(decoration: (severity) { style: InfoBarThemeData(
return BoxDecoration( decoration: (severity) {
color: Colors.orange, return BoxDecoration(color: Colors.orange);
); },
}, iconColor: (severity) { iconColor: (severity) {
return Colors.white; return Colors.white;
}), },
), ),
const SizedBox(
height: 12,
), ),
const SizedBox(height: 12),
], ],
if (workingText.value.isNotEmpty) ...[ if (workingText.value.isNotEmpty) ...[
Center( Center(
@ -144,19 +146,17 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
Expanded( Expanded(
child: Text( child: Text(
S.current.tools_rsi_launcher_enhance_msg_version(assarState.value?.version ?? ""), S.current.tools_rsi_launcher_enhance_msg_version(assarState.value?.version ?? ""),
style: TextStyle( style: TextStyle(color: Colors.white.withValues(alpha: .6)),
color: Colors.white.withValues(alpha: .6),
),
), ),
), ),
Text( Text(
S.current.tools_rsi_launcher_enhance_msg_patch_status((assarState.value?.isPatchInstalled ?? false) S.current.tools_rsi_launcher_enhance_msg_patch_status(
? S.current.localization_info_installed (assarState.value?.isPatchInstalled ?? false)
: S.current.tools_action_info_not_installed), ? S.current.localization_info_installed
style: TextStyle( : S.current.tools_action_info_not_installed,
color: Colors.white.withValues(alpha: .6),
), ),
) style: TextStyle(color: Colors.white.withValues(alpha: .6)),
),
], ],
), ),
if (assarState.value?.serverData.isEmpty ?? true) ...[ if (assarState.value?.serverData.isEmpty ?? true) ...[
@ -165,6 +165,84 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
const SizedBox(height: 24), const SizedBox(height: 24),
if (assarState.value?.enabledLocalization != null) if (assarState.value?.enabledLocalization != null)
Container( Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_localization),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_localization,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
),
],
),
),
ComboBox(
items: [
for (final key in supportLocalizationMap.keys)
ComboBoxItem(value: key, child: Text(supportLocalizationMap[key]!)),
],
value: assarState.value?.enabledLocalization,
onChanged: (v) {
assarState.value = assarState.value!.copyWith(enabledLocalization: v);
},
),
],
),
),
const SizedBox(height: 3),
if (assarState.value?.enableDownloaderBoost != null) ...[
IconButton(
icon: Padding(
padding: const EdgeInsets.only(top: 3, bottom: 3),
child: Row(
children: [
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(expandEnhance.value ? FluentIcons.chevron_up : FluentIcons.chevron_down),
const SizedBox(width: 12),
Text(
expandEnhance.value
? S.current.tools_rsi_launcher_enhance_action_fold
: S.current.tools_rsi_launcher_enhance_action_expand,
),
],
),
),
),
],
),
),
onPressed: () async {
if (!expandEnhance.value) {
final userOK = await showConfirmDialogs(
context,
S.current.tools_rsi_launcher_enhance_note_title,
Column(
mainAxisSize: MainAxisSize.min,
children: [Text(S.current.tools_rsi_launcher_enhance_note_msg)],
),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
);
if (!userOK) return;
}
expandEnhance.value = !expandEnhance.value;
},
),
if (expandEnhance.value)
Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -174,111 +252,38 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_localization),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_localization,
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: .6),
),
),
],
)),
ComboBox(
items: [
for (final key in supportLocalizationMap.keys)
ComboBoxItem(value: key, child: Text(supportLocalizationMap[key]!))
],
value: assarState.value?.enabledLocalization,
onChanged: (v) {
assarState.value = assarState.value!.copyWith(enabledLocalization: v);
},
),
],
)),
const SizedBox(height: 3),
if (assarState.value?.enableDownloaderBoost != null) ...[
IconButton(
icon: Padding(
padding: const EdgeInsets.only(top: 3, bottom: 3),
child: Row(
children: [
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(expandEnhance.value ? FluentIcons.chevron_up : FluentIcons.chevron_down),
const SizedBox(width: 12),
Text(expandEnhance.value
? S.current.tools_rsi_launcher_enhance_action_fold
: S.current.tools_rsi_launcher_enhance_action_expand),
],
))),
],
),
),
onPressed: () async {
if (!expandEnhance.value) {
final userOK = await showConfirmDialogs(
context,
S.current.tools_rsi_launcher_enhance_note_title,
Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(S.current.tools_rsi_launcher_enhance_note_msg), Text(S.current.tools_rsi_launcher_enhance_title_download_booster),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_download_booster,
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
),
], ],
), ),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55)); ),
if (!userOK) return;
}
expandEnhance.value = !expandEnhance.value;
},
),
if (expandEnhance.value)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.tools_rsi_launcher_enhance_title_download_booster),
const SizedBox(height: 3),
Text(
S.current.tools_rsi_launcher_enhance_subtitle_download_booster,
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: .6),
),
),
],
)),
ToggleSwitch( ToggleSwitch(
onChanged: (value) { onChanged: (value) {
assarState.value = assarState.value?.copyWith(enableDownloaderBoost: value); assarState.value = assarState.value?.copyWith(enableDownloaderBoost: value);
}, },
checked: assarState.value?.enableDownloaderBoost ?? false, checked: assarState.value?.enableDownloaderBoost ?? false,
) ),
])), ],
),
),
], ],
const SizedBox(height: 12), const SizedBox(height: 12),
Center( Center(
child: FilledButton( child: FilledButton(
onPressed: doInstall, onPressed: doInstall,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(S.current.tools_rsi_launcher_enhance_action_install), child: Text(S.current.tools_rsi_launcher_enhance_action_install),
))), ),
),
),
], ],
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
@ -300,7 +305,7 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
return null; return null;
} }
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherPath ==== $lPath"); dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherPath ==== $lPath");
final dataPath = "${lPath}resources\\app.asar"; final dataPath = "${lPath}resources\\app.asar".platformPath;
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherDataPath ==== $dataPath"); dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherDataPath ==== $dataPath");
try { try {
final data = await asar_api.getRsiLauncherAsarData(asarPath: dataPath); final data = await asar_api.getRsiLauncherAsarData(asarPath: dataPath);
@ -333,7 +338,10 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
} }
Future<String> _loadEnhanceData( Future<String> _loadEnhanceData(
BuildContext context, WidgetRef ref, ValueNotifier<RSILauncherStateData?> assarState) async { BuildContext context,
WidgetRef ref,
ValueNotifier<RSILauncherStateData?> assarState,
) async {
final globalModel = ref.read(appGlobalModelProvider); final globalModel = ref.read(appGlobalModelProvider);
final enhancePath = "${globalModel.applicationSupportDir}/launcher_enhance_data"; final enhancePath = "${globalModel.applicationSupportDir}/launcher_enhance_data";
@ -418,11 +426,13 @@ class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
for (final line in serverScriptLines) { for (final line in serverScriptLines) {
final lineTrim = line.trim(); final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) { if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
scriptBuffer scriptBuffer.writeln(
.writeln("$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";"); "$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";",
);
} else if (lineTrim.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) { } else if (lineTrim.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
scriptBuffer scriptBuffer.writeln(
.writeln("$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};"); "$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};",
);
} else { } else {
scriptBuffer.writeln(line); scriptBuffer.writeln(line);
} }

View File

@ -72,13 +72,14 @@ class ToolsUIModel extends _$ToolsUIModel {
} }
try { try {
items = [ items = [
ToolsItemData( if (Platform.isWindows)
"systemnfo", ToolsItemData(
S.current.tools_action_view_system_info, "systemnfo",
S.current.tools_action_info_view_critical_system_info, S.current.tools_action_view_system_info,
const Icon(FluentIcons.system, size: 24), S.current.tools_action_info_view_critical_system_info,
onTap: () => _showSystemInfo(context), const Icon(FluentIcons.system, size: 24),
), onTap: () => _showSystemInfo(context),
),
]; ];
// logic // logic
@ -108,13 +109,14 @@ class ToolsUIModel extends _$ToolsUIModel {
if (!context.mounted) return; if (!context.mounted) return;
items.add(await _addP4kCard(context)); items.add(await _addP4kCard(context));
items.addAll([ items.addAll([
ToolsItemData( if (Platform.isWindows)
"hosts_booster", ToolsItemData(
S.current.tools_action_hosts_acceleration_experimental, "hosts_booster",
S.current.tools_action_info_hosts_acceleration_experimental_tip, S.current.tools_action_hosts_acceleration_experimental,
const Icon(FluentIcons.virtual_network, size: 24), S.current.tools_action_info_hosts_acceleration_experimental_tip,
onTap: () => _doHostsBooster(context), const Icon(FluentIcons.virtual_network, size: 24),
), onTap: () => _doHostsBooster(context),
),
ToolsItemData( ToolsItemData(
"log_analyze", "log_analyze",
S.current.log_analyzer_title, S.current.log_analyzer_title,
@ -129,20 +131,22 @@ class ToolsUIModel extends _$ToolsUIModel {
const Icon(FluentIcons.c_plus_plus, size: 24), const Icon(FluentIcons.c_plus_plus, size: 24),
onTap: () => rsiEnhance(context), onTap: () => rsiEnhance(context),
), ),
ToolsItemData( if (Platform.isWindows)
"reinstall_eac", ToolsItemData(
S.current.tools_action_reinstall_easyanticheat, "reinstall_eac",
S.current.tools_action_info_reinstall_eac, S.current.tools_action_reinstall_easyanticheat,
const Icon(FluentIcons.game, size: 24), S.current.tools_action_info_reinstall_eac,
onTap: () => _reinstallEAC(context), const Icon(FluentIcons.game, size: 24),
), onTap: () => _reinstallEAC(context),
ToolsItemData( ),
"rsilauncher_admin_mode", if (Platform.isWindows)
S.current.tools_action_rsi_launcher_admin_mode, ToolsItemData(
S.current.tools_action_info_run_rsi_as_admin, "rsilauncher_admin_mode",
const Icon(FluentIcons.admin, size: 24), S.current.tools_action_rsi_launcher_admin_mode,
onTap: () => _adminRSILauncher(context), S.current.tools_action_info_run_rsi_as_admin,
), const Icon(FluentIcons.admin, size: 24),
onTap: () => _adminRSILauncher(context),
),
ToolsItemData( ToolsItemData(
"unp4kc", "unp4kc",
S.current.tools_action_unp4k, S.current.tools_action_unp4k,
@ -169,8 +173,10 @@ class ToolsUIModel extends _$ToolsUIModel {
if (!context.mounted) return; if (!context.mounted) return;
items.add(await _addPhotographyCard(context)); items.add(await _addPhotographyCard(context));
state = state.copyWith(items: items); state = state.copyWith(items: items);
if (!context.mounted) return; if (Platform.isWindows) {
items.addAll(await _addNvmePatchCard(context)); if (!context.mounted) return;
items.addAll(await _addNvmePatchCard(context));
}
state = state.copyWith(items: items, isItemLoading: false); state = state.copyWith(items: items, isItemLoading: false);
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!context.mounted) return;
@ -250,7 +256,10 @@ class ToolsUIModel extends _$ToolsUIModel {
Future<ToolsItemData> _addShaderCard(BuildContext context) async { Future<ToolsItemData> _addShaderCard(BuildContext context) async {
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath(); final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final shaderSize = final shaderSize =
((await SystemHelper.getDirLen(gameShaderCachePath ?? "", skipPath: ["$gameShaderCachePath\\Crashes"])) / ((await SystemHelper.getDirLen(
gameShaderCachePath ?? "",
skipPath: ["$gameShaderCachePath\\Crashes".platformPath],
)) /
1024 / 1024 /
1024) 1024)
.toStringAsFixed(4); .toStringAsFixed(4);
@ -300,7 +309,8 @@ class ToolsUIModel extends _$ToolsUIModel {
// 使 // 使
final latestVersion = versions.first; final latestVersion = versions.first;
final settingsPath = "$gameShaderCachePath\\starcitizen_$latestVersion\\GraphicsSettings\\GraphicsSettings.json"; final settingsPath =
"$gameShaderCachePath\\starcitizen_$latestVersion\\GraphicsSettings\\GraphicsSettings.json".platformPath;
final file = File(settingsPath); final file = File(settingsPath);
if (!await file.exists()) return (-1, latestVersion); if (!await file.exists()) return (-1, latestVersion);
@ -627,6 +637,7 @@ class ToolsUIModel extends _$ToolsUIModel {
final result = await showDialog<String>( final result = await showDialog<String>(
context: context, context: context,
builder: (dialogContext) => ContentDialog( builder: (dialogContext) => ContentDialog(
constraints: BoxConstraints(maxWidth: 380),
title: Text(S.current.tools_shader_clean_dialog_title), title: Text(S.current.tools_shader_clean_dialog_title),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "app") set(BINARY_NAME "starcitizen_doctor")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.app") set(APPLICATION_ID "com.starcitizen_doctor")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.
@ -53,6 +53,7 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies. # System-level dependencies.
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GDK_PIXBUF REQUIRED IMPORTED_TARGET gdk-pixbuf-2.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
@ -73,6 +74,7 @@ apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here. # Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GDK_PIXBUF)
# Run the Flutter tool portions of the build. This must not be removed. # Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble) add_dependencies(${BINARY_NAME} flutter_assemble)
@ -143,3 +145,13 @@ if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endif() endif()
# Install application icons
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/icons/hicolor"
DESTINATION "share/icons"
COMPONENT Runtime)
# Install desktop file
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/com.starcitizen_doctor.desktop"
DESTINATION "share/applications"
COMPONENT Runtime)

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=SC
Comment=Star Citizen Tool Box for Chinese players
Exec=starcitizen_doctor
Icon=starcitizen_doctor
Categories=Utility;Game;
Terminal=false
StartupNotify=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -4,6 +4,7 @@
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#endif #endif
#include <glib.h>
#include "flutter/generated_plugin_registrant.h" #include "flutter/generated_plugin_registrant.h"
@ -42,11 +43,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "app"); gtk_header_bar_set_title(header_bar, "SC汉化盒子");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "app"); gtk_window_set_title(window, "SC汉化盒子");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

View File

@ -71,8 +71,8 @@ pub async fn get_rsi_launcher_asar_data(asar_path: &str) -> anyhow::Result<RsiLa
let mut main_js_content: Vec<u8> = Vec::new(); let mut main_js_content: Vec<u8> = Vec::new();
asar.files().iter().for_each(|v| { asar.files().iter().for_each(|v| {
let (path, file) = v; let (path, file) = v;
let path_string = path.clone().into_os_string().into_string().unwrap(); let path_string = path.clone().into_os_string().into_string().unwrap().replace("\\", "/");
if path_string.starts_with("app\\static\\js\\main.") && path_string.ends_with(".js") { if path_string.starts_with("app/static/js/main.") && path_string.ends_with(".js") {
main_js_path = path_string; main_js_path = path_string;
main_js_content = file.data().to_vec(); main_js_content = file.data().to_vec();
} }

View File

@ -12,12 +12,16 @@ use tao::dpi::{LogicalPosition, LogicalSize};
use tao::event::{Event, WindowEvent}; use tao::event::{Event, WindowEvent};
use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder}; use tao::event_loop::{ControlFlow, EventLoop, EventLoopBuilder};
use tao::platform::run_return::EventLoopExtRunReturn; use tao::platform::run_return::EventLoopExtRunReturn;
use tao::window::{Icon, Window, WindowBuilder}; use tao::window::{Icon, Window, WindowBuilder};
use wry::{NewWindowResponse, PageLoadEvent, WebView, WebViewBuilder}; use wry::{NewWindowResponse, PageLoadEvent, WebView, WebViewBuilder};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use tao::platform::windows::EventLoopBuilderExtWindows; use tao::platform::windows::EventLoopBuilderExtWindows;
#[cfg(target_os = "linux")]
use tao::platform::unix::EventLoopBuilderExtUnix;
use crate::api::webview_api::{ use crate::api::webview_api::{
WebViewCommand, WebViewConfiguration, WebViewEvent, WebViewNavigationState, WebViewCommand, WebViewConfiguration, WebViewEvent, WebViewNavigationState,
}; };
@ -263,14 +267,10 @@ fn run_webview_loop(
is_closed: Arc<AtomicBool>, is_closed: Arc<AtomicBool>,
) { ) {
// Create event loop with any_thread support for non-main thread execution // Create event loop with any_thread support for non-main thread execution
#[cfg(target_os = "windows")]
let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event() let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event()
.with_any_thread(true) .with_any_thread(true)
.build(); .build();
#[cfg(not(target_os = "windows"))]
let mut event_loop: EventLoop<UserEvent> = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy(); let proxy = event_loop.create_proxy();
// Load window icon from embedded ICO file // Load window icon from embedded ICO file