app/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.dart
2024-05-06 20:29:32 +08:00

459 lines
18 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/rust/api/asar_api.dart' as asar_api;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/generated/no_l10n_strings.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
part 'rsi_launcher_enhance_dialog_ui.freezed.dart';
@freezed
class RSILauncherStateData with _$RSILauncherStateData {
const factory RSILauncherStateData({
required String version,
required asar_api.RsiLauncherAsarData data,
required String serverData,
@Default(false) bool isPatchInstalled,
String? enabledLocalization,
bool? enableDownloaderBoost,
}) = _RSILauncherStateData;
}
class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
const RsiLauncherEnhanceDialogUI({super.key});
static const supportLocalizationMap = {
"en": NoL10n.langEn,
"zh_CN": NoL10n.langZHS,
"fr": NoL10n.langFR,
};
@override
Widget build(BuildContext context, WidgetRef ref) {
final workingText = useState("");
final assarState = useState<RSILauncherStateData?>(null);
final expandEnhance = useState(false);
Future<void> readState() async {
workingText.value = S.current.tools_rsi_launcher_enhance_init_msg1;
assarState.value = await _readState(context).unwrap(context: context);
if (assarState.value == null) {
workingText.value = "";
return;
}
workingText.value = S.current.tools_rsi_launcher_enhance_init_msg2;
if (!context.mounted) return;
await _loadEnhanceData(context, ref, assarState)
.unwrap(context: context)
.unwrap(context: context);
workingText.value = "";
}
void doInstall() async {
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .35));
return;
}
if (!context.mounted) return;
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg1;
final newScript =
await _genNewScript(assarState).unwrap(context: context);
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg2;
if (!context.mounted) return;
await assarState.value?.data
.writeMainJs(content: utf8.encode(newScript))
.unwrap(context: context);
await readState();
}
useEffect(() {
readState();
return null;
}, const []);
return ContentDialog(
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .48),
title: Row(children: [
IconButton(
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed:
workingText.value.isEmpty ? Navigator.of(context).pop : null),
const SizedBox(width: 12),
Text(S.current.tools_rsi_launcher_enhance_title),
]),
content: AnimatedSize(
duration: const Duration(milliseconds: 130),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (workingText.value.isNotEmpty) ...[
Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Row(),
const SizedBox(height: 12),
const ProgressRing(),
const SizedBox(height: 12),
Text(workingText.value),
const SizedBox(height: 12),
],
),
),
] else ...[
Row(
children: [
Expanded(
child: Text(
S.current.tools_rsi_launcher_enhance_msg_version(
assarState.value?.version ?? ""),
style: TextStyle(
color: Colors.white.withOpacity(.6),
),
),
),
Text(
S.current.tools_rsi_launcher_enhance_msg_patch_status(
(assarState.value?.isPatchInstalled ?? false)
? S.current.localization_info_installed
: S.current.tools_action_info_not_installed),
style: TextStyle(
color: Colors.white.withOpacity(.6),
),
)
],
),
if (assarState.value?.serverData.isEmpty ?? true) ...[
Text(S.current.tools_rsi_launcher_enhance_msg_error),
] else ...[
const SizedBox(height: 24),
if (assarState.value?.enabledLocalization != null)
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.withOpacity(.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),
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.withOpacity(.6),
),
),
],
)),
ToggleSwitch(
onChanged: (value) {
assarState.value = assarState.value
?.copyWith(enableDownloaderBoost: value);
},
checked: assarState.value?.enableDownloaderBoost ??
false,
)
])),
],
const SizedBox(height: 12),
Center(
child: FilledButton(
onPressed: doInstall,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
child: Text(S.current
.tools_rsi_launcher_enhance_action_install),
))),
],
const SizedBox(height: 16),
Text(
S.current.tools_rsi_launcher_enhance_msg_uninstall,
style: TextStyle(
color: Colors.white.withOpacity(.6), fontSize: 13),
),
],
],
),
),
);
}
Future<RSILauncherStateData?> _readState(BuildContext context) async {
final lPath = await SystemHelper.getRSILauncherPath(skipEXE: true);
if (lPath.isEmpty) {
if (!context.mounted) return null;
showToast(context,
S.current.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
return null;
}
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherPath ==== $lPath");
final dataPath = "${lPath}resources\\app.asar";
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherDataPath ==== $dataPath");
try {
final data = await asar_api.getRsiLauncherAsarData(asarPath: dataPath);
dPrint(
"[RsiLauncherEnhanceDialogUI] rsiLauncherPath main.js path == ${data.mainJsPath}");
final version =
RegExp(r"main\.(\w+)\.js").firstMatch(data.mainJsPath)?.group(1);
if (version == null) {
if (!context.mounted) return null;
showToast(
context,
S.current
.tools_rsi_launcher_enhance_msg_error_get_launcher_info_error);
return null;
}
dPrint(
"[RsiLauncherEnhanceDialogUI] rsiLauncherPath main.js version == $version");
final mainJsString = String.fromCharCodes(data.mainJsContent);
final (enabledLocalization, enableDownloaderBoost) =
_readScriptState(mainJsString);
return RSILauncherStateData(
version: version,
data: data,
serverData: "",
isPatchInstalled: mainJsString.contains("SC_TOOLBOX"),
enabledLocalization: enabledLocalization,
enableDownloaderBoost: enableDownloaderBoost,
);
} catch (e) {
if (!context.mounted) return null;
showToast(
context,
S.current
.tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args(
e));
return null;
}
}
Future<String> _loadEnhanceData(BuildContext context, WidgetRef ref,
ValueNotifier<RSILauncherStateData?> assarState) async {
final globalModel = ref.read(appGlobalModelProvider);
final enhancePath =
"${globalModel.applicationSupportDir}/launcher_enhance_data";
final enhanceFile =
File("$enhancePath/${assarState.value?.version}.tar.gz");
if (!await enhanceFile.exists()) {
final downloadUrl =
"${URLConf.gitApiRSILauncherEnhanceUrl}/archive/${assarState.value?.version}.tar.gz";
final r = await RSHttp.get(downloadUrl).unwrap();
if (r.statusCode != 200 || r.data == null) {
return "";
}
await enhanceFile.create(recursive: true);
await enhanceFile.writeAsBytes(r.data!, flush: true);
}
final severMainJS =
await compute(_readArchive, (enhanceFile.path, "main.js"));
final serverMainJSString = severMainJS.toString();
final scriptState = _readScriptState(serverMainJSString);
if (assarState.value?.enabledLocalization == null) {
assarState.value =
assarState.value?.copyWith(enabledLocalization: scriptState.$1);
dPrint(
"[RsiLauncherEnhanceDialogUI] _loadEnhanceData enabledLocalization == ${scriptState.$1}");
}
if (assarState.value?.enableDownloaderBoost == null) {
assarState.value =
assarState.value?.copyWith(enableDownloaderBoost: scriptState.$2);
dPrint(
"[RsiLauncherEnhanceDialogUI] _loadEnhanceData enableDownloaderBoost == ${scriptState.$2}");
}
assarState.value =
assarState.value?.copyWith(serverData: serverMainJSString);
return serverMainJSString;
}
static StringBuffer _readArchive((String savePath, String fileName) data) {
final inputStream = InputFileStream(data.$1);
final archive =
TarDecoder().decodeBytes(GZipDecoder().decodeBuffer(inputStream));
StringBuffer dataBuffer = StringBuffer("");
for (var element in archive.files) {
if (element.name.endsWith(data.$2)) {
for (var value
in (element.rawContent?.readString() ?? "").split("\n")) {
final tv = value;
if (tv.isNotEmpty) dataBuffer.writeln(tv);
}
}
}
archive.clear();
return dataBuffer;
}
// ignore: constant_identifier_names
static const SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START =
"const SC_TOOLBOX_ENABLED_LOCALIZATION = ";
// ignore: constant_identifier_names
static const SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START =
"const SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST = ";
(String?, bool?) _readScriptState(String mainJsString) {
String? enabledLocalization;
bool? enableDownloaderBoost;
for (final line in mainJsString.split("\n")) {
final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
enabledLocalization = lineTrim
.substring(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START.length)
.replaceAll("\"", "")
.replaceAll(";", "");
} else if (lineTrim
.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
enableDownloaderBoost = lineTrim
.substring(
SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START.length)
.toLowerCase() ==
"true;";
}
}
return (enabledLocalization, enableDownloaderBoost);
}
Future<String> _genNewScript(
ValueNotifier<RSILauncherStateData?> assarState) async {
final serverScriptLines = assarState.value!.serverData.split("\n");
final StringBuffer scriptBuffer = StringBuffer("");
for (final line in serverScriptLines) {
final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";");
} else if (lineTrim
.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};");
} else {
scriptBuffer.writeln(line);
}
}
return scriptBuffer.toString();
}
}