diff --git a/lib/app.g.dart b/lib/app.g.dart index f73d8b9..600b4ff 100644 --- a/lib/app.g.dart +++ b/lib/app.g.dart @@ -20,7 +20,7 @@ final routerProvider = AutoDisposeProvider.internal( ); typedef RouterRef = AutoDisposeProviderRef; -String _$appGlobalModelHash() => r'a604c415d2d855ede8727dd75ef991ab2afcf234'; +String _$appGlobalModelHash() => r'11a172d5fb19ad6566f1025354c8f8d91fe85a84'; /// See also [AppGlobalModel]. @ProviderFor(AppGlobalModel) diff --git a/lib/common/conf/url_conf.dart b/lib/common/conf/url_conf.dart index 27d01fa..f50217a 100644 --- a/lib/common/conf/url_conf.dart +++ b/lib/common/conf/url_conf.dart @@ -16,6 +16,9 @@ class URLConf { static String get gitlabLocalizationUrl => "$gitApiHome/SCToolBox/LocalizationData"; + static String get gitApiRSILauncherEnhanceUrl => + "$gitApiHome/SCToolBox/RSILauncherEnhance"; + static String get apiRepoPath => "$gitApiHome/SCToolBox/api/raw/branch/main"; static String get gitlabApiPath => "$gitApiHome/api/v1/"; diff --git a/lib/common/helper/system_helper.dart b/lib/common/helper/system_helper.dart index 78ae102..152fc4e 100644 --- a/lib/common/helper/system_helper.dart +++ b/lib/common/helper/system_helper.dart @@ -82,7 +82,7 @@ class SystemHelper { } /// 获取 RSI 启动器 目录 - static Future getRSILauncherPath() async { + static Future getRSILauncherPath({bool skipEXE = false}) async { final confBox = await Hive.openBox("app_conf"); final path = confBox.get("custom_launcher_path"); if (path != null && path != "") { @@ -102,6 +102,9 @@ class SystemHelper { ]); if (r.stdout.toString().contains("RSI Launcher.exe")) { final start = r.stdout.toString().split("RSI Launcher.exe"); + if (skipEXE) { + return start[0]; + } return "${start[0]}RSI Launcher.exe"; } } @@ -257,8 +260,8 @@ foreach ($adapter in $adapterMemory) { static Future openDir(path, {bool isFile = false}) async { dPrint("SystemHelper.openDir path === $path"); - await Process.run( - SystemHelper.powershellPath, ["explorer.exe", isFile ? "/select,$path" : "\"/select,\"$path\"\""]); + await Process.run(SystemHelper.powershellPath, + ["explorer.exe", isFile ? "/select,$path" : "\"/select,\"$path\"\""]); } static String getHostsFilePath() { diff --git a/lib/common/rust/api/asar_api.dart b/lib/common/rust/api/asar_api.dart new file mode 100644 index 0000000..5f3f12f --- /dev/null +++ b/lib/common/rust/api/asar_api.dart @@ -0,0 +1,40 @@ +// This file is automatically generated, so please do not edit it. +// Generated by `flutter_rust_bridge`@ 2.0.0-dev.32. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +Future getRsiLauncherAsarData( + {required String asarPath, dynamic hint}) => + RustLib.instance.api.getRsiLauncherAsarData(asarPath: asarPath, hint: hint); + +class RsiLauncherAsarData { + final String asarPath; + final String mainJsPath; + final Uint8List mainJsContent; + + const RsiLauncherAsarData({ + required this.asarPath, + required this.mainJsPath, + required this.mainJsContent, + }); + + Future writeMainJs({required List content, dynamic hint}) => + RustLib.instance.api.rsiLauncherAsarDataWriteMainJs( + that: this, content: content, hint: hint); + + @override + int get hashCode => + asarPath.hashCode ^ mainJsPath.hashCode ^ mainJsContent.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RsiLauncherAsarData && + runtimeType == other.runtimeType && + asarPath == other.asarPath && + mainJsPath == other.mainJsPath && + mainJsContent == other.mainJsContent; +} diff --git a/lib/common/rust/api/rs_process.dart b/lib/common/rust/api/rs_process.dart index 12b38c4..f4ea458 100644 --- a/lib/common/rust/api/rs_process.dart +++ b/lib/common/rust/api/rs_process.dart @@ -6,7 +6,6 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; -// The type `RS_PROCESS_MAP` is not used by any `pub` functions, thus it is ignored. // The type `RsProcess` is not used by any `pub` functions, thus it is ignored. Stream start( diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index db6f958..144c33c 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/asar_api.dart'; import 'api/http_api.dart'; import 'api/rs_process.dart'; import 'api/win32_api.dart'; @@ -57,7 +58,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.0.0-dev.32'; @override - int get rustContentHash => 1453545208; + int get rustContentHash => 1832496273; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -68,6 +69,14 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { + Future getRsiLauncherAsarData( + {required String asarPath, dynamic hint}); + + Future rsiLauncherAsarDataWriteMainJs( + {required RsiLauncherAsarData that, + required List content, + dynamic hint}); + Future> dnsLookupIps({required String host, dynamic hint}); Future> dnsLookupTxt({required String host, dynamic hint}); @@ -109,6 +118,59 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); + @override + Future getRsiLauncherAsarData( + {required String asarPath, dynamic hint}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(asarPath); + return wire.wire_get_rsi_launcher_asar_data(port_, arg0); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_rsi_launcher_asar_data, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kGetRsiLauncherAsarDataConstMeta, + argValues: [asarPath], + apiImpl: this, + hint: hint, + )); + } + + TaskConstMeta get kGetRsiLauncherAsarDataConstMeta => const TaskConstMeta( + debugName: "get_rsi_launcher_asar_data", + argNames: ["asarPath"], + ); + + @override + Future rsiLauncherAsarDataWriteMainJs( + {required RsiLauncherAsarData that, + required List content, + dynamic hint}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_box_autoadd_rsi_launcher_asar_data(that); + var arg1 = cst_encode_list_prim_u_8_loose(content); + return wire.wire_rsi_launcher_asar_data_write_main_js( + port_, arg0, arg1); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kRsiLauncherAsarDataWriteMainJsConstMeta, + argValues: [that, content], + apiImpl: this, + hint: hint, + )); + } + + TaskConstMeta get kRsiLauncherAsarDataWriteMainJsConstMeta => + const TaskConstMeta( + debugName: "rsi_launcher_asar_data_write_main_js", + argNames: ["that", "content"], + ); + @override Future> dnsLookupIps({required String host, dynamic hint}) { return handler.executeNormal(NormalTask( @@ -354,6 +416,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as bool; } + @protected + RsiLauncherAsarData dco_decode_box_autoadd_rsi_launcher_asar_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_rsi_launcher_asar_data(raw); + } + @protected int dco_decode_box_autoadd_u_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -372,6 +441,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_String).toList(); } + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as List; + } + @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -452,6 +527,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return RsProcessStreamDataType.values[raw as int]; } + @protected + RsiLauncherAsarData dco_decode_rsi_launcher_asar_data(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return RsiLauncherAsarData( + asarPath: dco_decode_String(arr[0]), + mainJsPath: dco_decode_String(arr[1]), + mainJsContent: dco_decode_list_prim_u_8_strict(arr[2]), + ); + } + @protected RustHttpResponse dco_decode_rust_http_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -535,6 +623,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8() != 0; } + @protected + RsiLauncherAsarData sse_decode_box_autoadd_rsi_launcher_asar_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_rsi_launcher_asar_data(deserializer)); + } + @protected int sse_decode_box_autoadd_u_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -559,6 +654,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -666,6 +768,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return RsProcessStreamDataType.values[inner]; } + @protected + RsiLauncherAsarData sse_decode_rsi_launcher_asar_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_asarPath = sse_decode_String(deserializer); + var var_mainJsPath = sse_decode_String(deserializer); + var var_mainJsContent = sse_decode_list_prim_u_8_strict(deserializer); + return RsiLauncherAsarData( + asarPath: var_asarPath, + mainJsPath: var_mainJsPath, + mainJsContent: var_mainJsContent); + } + @protected RustHttpResponse sse_decode_rust_http_response(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -808,6 +923,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8(self ? 1 : 0); } + @protected + void sse_encode_box_autoadd_rsi_launcher_asar_data( + RsiLauncherAsarData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_rsi_launcher_asar_data(self, serializer); + } + @protected void sse_encode_box_autoadd_u_64(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -829,6 +951,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_prim_u_8_loose( + List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer + .putUint8List(self is Uint8List ? self : Uint8List.fromList(self)); + } + @protected void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer) { @@ -926,6 +1057,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_i_32(self.index, serializer); } + @protected + void sse_encode_rsi_launcher_asar_data( + RsiLauncherAsarData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.asarPath, serializer); + sse_encode_String(self.mainJsPath, serializer); + sse_encode_list_prim_u_8_strict(self.mainJsContent, serializer); + } + @protected void sse_encode_rust_http_response( RustHttpResponse self, SseSerializer serializer) { diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index c4eb437..e80913f 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/asar_api.dart'; import 'api/http_api.dart'; import 'api/rs_process.dart'; import 'api/win32_api.dart'; @@ -37,6 +38,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool dco_decode_bool(dynamic raw); + @protected + RsiLauncherAsarData dco_decode_box_autoadd_rsi_launcher_asar_data( + dynamic raw); + @protected int dco_decode_box_autoadd_u_64(dynamic raw); @@ -46,6 +51,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw); + @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); @@ -79,6 +87,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected RsProcessStreamDataType dco_decode_rs_process_stream_data_type(dynamic raw); + @protected + RsiLauncherAsarData dco_decode_rsi_launcher_asar_data(dynamic raw); + @protected RustHttpResponse dco_decode_rust_http_response(dynamic raw); @@ -115,6 +126,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool sse_decode_bool(SseDeserializer deserializer); + @protected + RsiLauncherAsarData sse_decode_box_autoadd_rsi_launcher_asar_data( + SseDeserializer deserializer); + @protected int sse_decode_box_autoadd_u_64(SseDeserializer deserializer); @@ -124,6 +139,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); + @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); @@ -162,6 +180,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { RsProcessStreamDataType sse_decode_rs_process_stream_data_type( SseDeserializer deserializer); + @protected + RsiLauncherAsarData sse_decode_rsi_launcher_asar_data( + SseDeserializer deserializer); + @protected RustHttpResponse sse_decode_rust_http_response(SseDeserializer deserializer); @@ -212,6 +234,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return cst_encode_list_prim_u_8_strict(utf8.encoder.convert(raw)); } + @protected + ffi.Pointer + cst_encode_box_autoadd_rsi_launcher_asar_data(RsiLauncherAsarData raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_rsi_launcher_asar_data(); + cst_api_fill_to_wire_rsi_launcher_asar_data(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer cst_encode_box_autoadd_u_64(int raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -228,6 +259,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ans; } + @protected + ffi.Pointer cst_encode_list_prim_u_8_loose( + List raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ans = wire.cst_new_list_prim_u_8_loose(raw.length); + ans.ref.ptr.asTypedList(raw.length).setAll(0, raw); + return ans; + } + @protected ffi.Pointer cst_encode_list_prim_u_8_strict( Uint8List raw) { @@ -281,6 +321,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw.toInt(); } + @protected + void cst_api_fill_to_wire_box_autoadd_rsi_launcher_asar_data( + RsiLauncherAsarData apiObj, + ffi.Pointer wireObj) { + cst_api_fill_to_wire_rsi_launcher_asar_data(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_record_string_string( (String, String) apiObj, wire_cst_record_string_string wireObj) { @@ -296,6 +343,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.rs_pid = cst_encode_u_32(apiObj.rsPid); } + @protected + void cst_api_fill_to_wire_rsi_launcher_asar_data( + RsiLauncherAsarData apiObj, wire_cst_rsi_launcher_asar_data wireObj) { + wireObj.asar_path = cst_encode_String(apiObj.asarPath); + wireObj.main_js_path = cst_encode_String(apiObj.mainJsPath); + wireObj.main_js_content = + cst_encode_list_prim_u_8_strict(apiObj.mainJsContent); + } + @protected void cst_api_fill_to_wire_rust_http_response( RustHttpResponse apiObj, wire_cst_rust_http_response wireObj) { @@ -354,6 +410,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_bool(bool self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_rsi_launcher_asar_data( + RsiLauncherAsarData self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_u_64(int self, SseSerializer serializer); @@ -363,6 +423,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); + @protected void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer); @@ -403,6 +466,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_rs_process_stream_data_type( RsProcessStreamDataType self, SseSerializer serializer); + @protected + void sse_encode_rsi_launcher_asar_data( + RsiLauncherAsarData self, SseSerializer serializer); + @protected void sse_encode_rust_http_response( RustHttpResponse self, SseSerializer serializer); @@ -464,6 +531,49 @@ class RustLibWire implements BaseWire { late final _store_dart_post_cobject = _store_dart_post_cobjectPtr .asFunction(); + void wire_get_rsi_launcher_asar_data( + int port_, + ffi.Pointer asar_path, + ) { + return _wire_get_rsi_launcher_asar_data( + port_, + asar_path, + ); + } + + late final _wire_get_rsi_launcher_asar_dataPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, ffi.Pointer)>>( + 'frbgen_starcitizen_doctor_wire_get_rsi_launcher_asar_data'); + late final _wire_get_rsi_launcher_asar_data = + _wire_get_rsi_launcher_asar_dataPtr.asFunction< + void Function(int, ffi.Pointer)>(); + + void wire_rsi_launcher_asar_data_write_main_js( + int port_, + ffi.Pointer that, + ffi.Pointer content, + ) { + return _wire_rsi_launcher_asar_data_write_main_js( + port_, + that, + content, + ); + } + + late final _wire_rsi_launcher_asar_data_write_main_jsPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer)>>( + 'frbgen_starcitizen_doctor_wire_rsi_launcher_asar_data_write_main_js'); + late final _wire_rsi_launcher_asar_data_write_main_js = + _wire_rsi_launcher_asar_data_write_main_jsPtr.asFunction< + void Function(int, ffi.Pointer, + ffi.Pointer)>(); + void wire_dns_lookup_ips( int port_, ffi.Pointer host, @@ -660,6 +770,19 @@ class RustLibWire implements BaseWire { _wire_set_foreground_windowPtr.asFunction< void Function(int, ffi.Pointer)>(); + ffi.Pointer + cst_new_box_autoadd_rsi_launcher_asar_data() { + return _cst_new_box_autoadd_rsi_launcher_asar_data(); + } + + late final _cst_new_box_autoadd_rsi_launcher_asar_dataPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function()>>( + 'frbgen_starcitizen_doctor_cst_new_box_autoadd_rsi_launcher_asar_data'); + late final _cst_new_box_autoadd_rsi_launcher_asar_data = + _cst_new_box_autoadd_rsi_launcher_asar_dataPtr.asFunction< + ffi.Pointer Function()>(); + ffi.Pointer cst_new_box_autoadd_u_64( int value, ) { @@ -689,6 +812,21 @@ class RustLibWire implements BaseWire { late final _cst_new_list_String = _cst_new_list_StringPtr .asFunction Function(int)>(); + ffi.Pointer cst_new_list_prim_u_8_loose( + int len, + ) { + return _cst_new_list_prim_u_8_loose( + len, + ); + } + + late final _cst_new_list_prim_u_8_loosePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Int32)>>( + 'frbgen_starcitizen_doctor_cst_new_list_prim_u_8_loose'); + late final _cst_new_list_prim_u_8_loose = _cst_new_list_prim_u_8_loosePtr + .asFunction Function(int)>(); + ffi.Pointer cst_new_list_prim_u_8_strict( int len, ) { @@ -749,6 +887,21 @@ final class wire_cst_list_prim_u_8_strict extends ffi.Struct { external int len; } +final class wire_cst_rsi_launcher_asar_data extends ffi.Struct { + external ffi.Pointer asar_path; + + external ffi.Pointer main_js_path; + + external ffi.Pointer main_js_content; +} + +final class wire_cst_list_prim_u_8_loose extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_cst_record_string_string extends ffi.Struct { external ffi.Pointer field0; diff --git a/lib/ui/home/home_ui_model.g.dart b/lib/ui/home/home_ui_model.g.dart index 4e893a6..1ce3987 100644 --- a/lib/ui/home/home_ui_model.g.dart +++ b/lib/ui/home/home_ui_model.g.dart @@ -6,7 +6,7 @@ part of 'home_ui_model.dart'; // RiverpodGenerator // ************************************************************************** -String _$homeUIModelHash() => r'8308b6e327b7eeda64c39bcd73e9f2d9e6470437'; +String _$homeUIModelHash() => r'85d3242abb4264a814768a2d5ce108df46df38d9'; /// See also [HomeUIModel]. @ProviderFor(HomeUIModel) diff --git a/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.dart b/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.dart new file mode 100644 index 0000000..f6e0112 --- /dev/null +++ b/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.dart @@ -0,0 +1,383 @@ +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, + }; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final workingText = useState(""); + + final assarState = useState(null); + + Future readState() async { + workingText.value = "读取启动器信息..."; + assarState.value = await _readState(context).unwrap(context: context); + if (assarState.value == null) { + workingText.value = ""; + return; + } + workingText.value = "正在从网络获取增强数据..."; + if (!context.mounted) return; + await _loadEnhanceData(context, ref, assarState) + .unwrap(context: context) + .unwrap(context: context); + workingText.value = ""; + } + + void doInstall() async { + workingText.value = "生成补丁 ..."; + final newScript = + await _genNewScript(assarState).unwrap(context: context); + workingText.value = "安装补丁,这需要一点时间,取决于您的计算机性能 ..."; + 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), + const Text("RSI 启动器增强"), + ]), + 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( + "启动器内部版本信息:${assarState.value?.version}", + style: TextStyle( + color: Colors.white.withOpacity(.6), + ), + ), + ), + Text( + "补丁状态:${(assarState.value?.isPatchInstalled ?? false) ? "已安装" : "未安装"}", + style: TextStyle( + color: Colors.white.withOpacity(.6), + ), + ) + ], + ), + if (assarState.value?.serverData.isEmpty ?? true) ...[ + const Text("获取增强数据失败,可能是网络问题或当前版本不支持"), + ] 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: [ + const Text("RSI 启动器本地化"), + const SizedBox(height: 3), + Text( + "为 RSI 启动器增加多语言支持。", + 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) + 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: [ + const Text("RSI 启动器下载增强"), + const SizedBox(height: 3), + Text( + "下载游戏时可使用更多线程以提升下载速度,启用后请在启动器设置修改线程数。", + 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: const Padding( + padding: + EdgeInsets.symmetric(vertical: 4, horizontal: 6), + child: Text("安装增强补丁"), + ))), + ], + const SizedBox(height: 16), + Text( + "* 如需卸载增强补丁,请覆盖安装 RSI 启动器。", + style: TextStyle( + color: Colors.white.withOpacity(.6), fontSize: 13), + ), + ], + ], + ), + ), + ); + } + + Future _readState(BuildContext context) async { + final lPath = await SystemHelper.getRSILauncherPath(skipEXE: true); + if (lPath.isEmpty) { + if (!context.mounted) return null; + showToast(context, "未找到 RSI 启动器"); + 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, "读取启动器信息失败!"); + 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, "读取启动器信息失败:$e"); + return null; + } + } + + Future _loadEnhanceData(BuildContext context, WidgetRef ref, + ValueNotifier 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 _genNewScript( + ValueNotifier 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(); + } +} diff --git a/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.freezed.dart b/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.freezed.dart new file mode 100644 index 0000000..a81bb5e --- /dev/null +++ b/lib/ui/tools/dialogs/rsi_launcher_enhance_dialog_ui.freezed.dart @@ -0,0 +1,245 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'rsi_launcher_enhance_dialog_ui.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$RSILauncherStateData { + String get version => throw _privateConstructorUsedError; + asar_api.RsiLauncherAsarData get data => throw _privateConstructorUsedError; + String get serverData => throw _privateConstructorUsedError; + bool get isPatchInstalled => throw _privateConstructorUsedError; + String? get enabledLocalization => throw _privateConstructorUsedError; + bool? get enableDownloaderBoost => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $RSILauncherStateDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RSILauncherStateDataCopyWith<$Res> { + factory $RSILauncherStateDataCopyWith(RSILauncherStateData value, + $Res Function(RSILauncherStateData) then) = + _$RSILauncherStateDataCopyWithImpl<$Res, RSILauncherStateData>; + @useResult + $Res call( + {String version, + asar_api.RsiLauncherAsarData data, + String serverData, + bool isPatchInstalled, + String? enabledLocalization, + bool? enableDownloaderBoost}); +} + +/// @nodoc +class _$RSILauncherStateDataCopyWithImpl<$Res, + $Val extends RSILauncherStateData> + implements $RSILauncherStateDataCopyWith<$Res> { + _$RSILauncherStateDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = null, + Object? data = null, + Object? serverData = null, + Object? isPatchInstalled = null, + Object? enabledLocalization = freezed, + Object? enableDownloaderBoost = freezed, + }) { + return _then(_value.copyWith( + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as asar_api.RsiLauncherAsarData, + serverData: null == serverData + ? _value.serverData + : serverData // ignore: cast_nullable_to_non_nullable + as String, + isPatchInstalled: null == isPatchInstalled + ? _value.isPatchInstalled + : isPatchInstalled // ignore: cast_nullable_to_non_nullable + as bool, + enabledLocalization: freezed == enabledLocalization + ? _value.enabledLocalization + : enabledLocalization // ignore: cast_nullable_to_non_nullable + as String?, + enableDownloaderBoost: freezed == enableDownloaderBoost + ? _value.enableDownloaderBoost + : enableDownloaderBoost // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RSILauncherStateDataImplCopyWith<$Res> + implements $RSILauncherStateDataCopyWith<$Res> { + factory _$$RSILauncherStateDataImplCopyWith(_$RSILauncherStateDataImpl value, + $Res Function(_$RSILauncherStateDataImpl) then) = + __$$RSILauncherStateDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String version, + asar_api.RsiLauncherAsarData data, + String serverData, + bool isPatchInstalled, + String? enabledLocalization, + bool? enableDownloaderBoost}); +} + +/// @nodoc +class __$$RSILauncherStateDataImplCopyWithImpl<$Res> + extends _$RSILauncherStateDataCopyWithImpl<$Res, _$RSILauncherStateDataImpl> + implements _$$RSILauncherStateDataImplCopyWith<$Res> { + __$$RSILauncherStateDataImplCopyWithImpl(_$RSILauncherStateDataImpl _value, + $Res Function(_$RSILauncherStateDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = null, + Object? data = null, + Object? serverData = null, + Object? isPatchInstalled = null, + Object? enabledLocalization = freezed, + Object? enableDownloaderBoost = freezed, + }) { + return _then(_$RSILauncherStateDataImpl( + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as asar_api.RsiLauncherAsarData, + serverData: null == serverData + ? _value.serverData + : serverData // ignore: cast_nullable_to_non_nullable + as String, + isPatchInstalled: null == isPatchInstalled + ? _value.isPatchInstalled + : isPatchInstalled // ignore: cast_nullable_to_non_nullable + as bool, + enabledLocalization: freezed == enabledLocalization + ? _value.enabledLocalization + : enabledLocalization // ignore: cast_nullable_to_non_nullable + as String?, + enableDownloaderBoost: freezed == enableDownloaderBoost + ? _value.enableDownloaderBoost + : enableDownloaderBoost // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc + +class _$RSILauncherStateDataImpl implements _RSILauncherStateData { + const _$RSILauncherStateDataImpl( + {required this.version, + required this.data, + required this.serverData, + this.isPatchInstalled = false, + this.enabledLocalization, + this.enableDownloaderBoost}); + + @override + final String version; + @override + final asar_api.RsiLauncherAsarData data; + @override + final String serverData; + @override + @JsonKey() + final bool isPatchInstalled; + @override + final String? enabledLocalization; + @override + final bool? enableDownloaderBoost; + + @override + String toString() { + return 'RSILauncherStateData(version: $version, data: $data, serverData: $serverData, isPatchInstalled: $isPatchInstalled, enabledLocalization: $enabledLocalization, enableDownloaderBoost: $enableDownloaderBoost)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RSILauncherStateDataImpl && + (identical(other.version, version) || other.version == version) && + (identical(other.data, data) || other.data == data) && + (identical(other.serverData, serverData) || + other.serverData == serverData) && + (identical(other.isPatchInstalled, isPatchInstalled) || + other.isPatchInstalled == isPatchInstalled) && + (identical(other.enabledLocalization, enabledLocalization) || + other.enabledLocalization == enabledLocalization) && + (identical(other.enableDownloaderBoost, enableDownloaderBoost) || + other.enableDownloaderBoost == enableDownloaderBoost)); + } + + @override + int get hashCode => Object.hash(runtimeType, version, data, serverData, + isPatchInstalled, enabledLocalization, enableDownloaderBoost); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RSILauncherStateDataImplCopyWith<_$RSILauncherStateDataImpl> + get copyWith => + __$$RSILauncherStateDataImplCopyWithImpl<_$RSILauncherStateDataImpl>( + this, _$identity); +} + +abstract class _RSILauncherStateData implements RSILauncherStateData { + const factory _RSILauncherStateData( + {required final String version, + required final asar_api.RsiLauncherAsarData data, + required final String serverData, + final bool isPatchInstalled, + final String? enabledLocalization, + final bool? enableDownloaderBoost}) = _$RSILauncherStateDataImpl; + + @override + String get version; + @override + asar_api.RsiLauncherAsarData get data; + @override + String get serverData; + @override + bool get isPatchInstalled; + @override + String? get enabledLocalization; + @override + bool? get enableDownloaderBoost; + @override + @JsonKey(ignore: true) + _$$RSILauncherStateDataImplCopyWith<_$RSILauncherStateDataImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/tools/tools_ui_model.dart b/lib/ui/tools/tools_ui_model.dart index e903263..0cb2283 100644 --- a/lib/ui/tools/tools_ui_model.dart +++ b/lib/ui/tools/tools_ui_model.dart @@ -23,6 +23,7 @@ import 'package:url_launcher/url_launcher_string.dart'; import 'package:xml/xml.dart'; import 'dialogs/hosts_booster_dialog_ui.dart'; +import 'dialogs/rsi_launcher_enhance_dialog_ui.dart'; part 'tools_ui_model.g.dart'; @@ -90,6 +91,13 @@ class ToolsUIModel extends _$ToolsUIModel { const Icon(FluentIcons.virtual_network, size: 24), onTap: () => _doHostsBooster(context), ), + ToolsItemData( + "rsilauncher_enhance_mod", + "RSI 启动器增强", + "启动器汉化,下载线程增强", + const Icon(FluentIcons.c_plus_plus, size: 24), + onTap: () => _rsiEnhance(context), + ), ToolsItemData( "reinstall_eac", S.current.tools_action_reinstall_easyanticheat, @@ -584,4 +592,10 @@ class ToolsUIModel extends _$ToolsUIModel { _unp4kc(BuildContext context) async { context.push("/tools/unp4kc"); } + + _rsiEnhance(BuildContext context) async { + showDialog( + context: context, + builder: (BuildContext context) => const RsiLauncherEnhanceDialogUI()); + } } diff --git a/lib/ui/tools/tools_ui_model.g.dart b/lib/ui/tools/tools_ui_model.g.dart index e0ae79d..59665ce 100644 --- a/lib/ui/tools/tools_ui_model.g.dart +++ b/lib/ui/tools/tools_ui_model.g.dart @@ -6,7 +6,7 @@ part of 'tools_ui_model.dart'; // RiverpodGenerator // ************************************************************************** -String _$toolsUIModelHash() => r'5753016dc2cbcf3fafd2fa561d5b91a3295ca04b'; +String _$toolsUIModelHash() => r'6cf170210fa7a7c2c63bde9d0920c64a20a81263'; /// See also [ToolsUIModel]. @ProviderFor(ToolsUIModel) diff --git a/pubspec.yaml b/pubspec.yaml index 6c0510e..abb06a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,8 +45,6 @@ dependencies: flutter_rust_bridge: ^2.0.0-dev.32 freezed_annotation: ^2.4.1 meta: ^1.9.1 - cryptography: ^2.7.0 - cryptography_flutter: ^2.3.2 hexcolor: ^3.0.1 dart_rss: ^3.0.1 html: ^0.15.4 diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 96a9a19..234f20a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -22,4 +22,5 @@ anyhow = "1.0" win32job = "2" scopeguard = "1.2" notify-rust = "4" -windows = { version = "0.56.0", features = ["Win32_UI_WindowsAndMessaging"] } \ No newline at end of file +windows = { version = "0.56.0", features = ["Win32_UI_WindowsAndMessaging"] } +asar = "0.3.0" \ No newline at end of file diff --git a/rust/src/api/asar_api.rs b/rust/src/api/asar_api.rs new file mode 100644 index 0000000..66be349 --- /dev/null +++ b/rust/src/api/asar_api.rs @@ -0,0 +1,52 @@ +use std::fs::File; + +use asar::{AsarReader, AsarWriter}; +use tokio::fs; + +pub struct RsiLauncherAsarData { + pub asar_path: String, + pub main_js_path: String, + pub main_js_content: Vec, +} + +impl RsiLauncherAsarData { + pub async fn write_main_js(&self, content: Vec) -> anyhow::Result<()> { + let mut asar_writer = AsarWriter::new(); + let asar_mem_file = fs::read(self.asar_path.clone()).await?; + let asar = AsarReader::new(&asar_mem_file, None)?; + asar.files().iter().for_each(|v| { + let (path, file) = v; + let path_string = path.clone().into_os_string().into_string().unwrap(); + if path_string == self.main_js_path { + asar_writer.write_file(path, &content, true).unwrap(); + } else { + asar_writer.write_file(path, file.data(), true).unwrap(); + } + }); + // rm old asar + fs::remove_file(&self.asar_path).await?; + // write new asar + asar_writer.finalize(File::create(&self.asar_path)?)?; + Ok(()) + } +} + +pub async fn get_rsi_launcher_asar_data(asar_path: &str) -> anyhow::Result { + let asar_mem_file = fs::read(asar_path).await?; + let asar = AsarReader::new(&asar_mem_file, None)?; + let mut main_js_path = String::from(""); + let mut main_js_content: Vec = Vec::new(); + asar.files().iter().for_each(|v| { + let (path, file) = v; + let path_string = path.clone().into_os_string().into_string().unwrap(); + if path_string.starts_with("app\\static\\js\\main.") && path_string.ends_with(".js") { + main_js_path = path_string; + main_js_content = file.data().to_vec(); + } + }); + Ok(RsiLauncherAsarData { + asar_path: asar_path.to_string(), + main_js_path, + main_js_content, + }) +} \ No newline at end of file diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 398d038..cb0e8f9 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -4,3 +4,4 @@ pub mod http_api; pub mod rs_process; pub mod win32_api; +pub mod asar_api; diff --git a/rust/src/frb_generated.io.rs b/rust/src/frb_generated.io.rs index c6f90e5..1ef0ac9 100644 --- a/rust/src/frb_generated.io.rs +++ b/rust/src/frb_generated.io.rs @@ -57,6 +57,13 @@ impl CstDecode for *mut wire_cst_list_prim_u_8_strict { String::from_utf8(vec).unwrap() } } +impl CstDecode for *mut wire_cst_rsi_launcher_asar_data { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::asar_api::RsiLauncherAsarData { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } +} impl CstDecode for *mut u64 { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> u64 { @@ -73,6 +80,15 @@ impl CstDecode> for *mut wire_cst_list_String { vec.into_iter().map(CstDecode::cst_decode).collect() } } +impl CstDecode> for *mut wire_cst_list_prim_u_8_loose { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> Vec { + unsafe { + let wrap = flutter_rust_bridge::for_generated::box_from_leak_ptr(self); + flutter_rust_bridge::for_generated::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} impl CstDecode> for *mut wire_cst_list_prim_u_8_strict { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> Vec { @@ -108,6 +124,16 @@ impl CstDecode for wire_cst_rs_proc } } } +impl CstDecode for wire_cst_rsi_launcher_asar_data { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::asar_api::RsiLauncherAsarData { + crate::api::asar_api::RsiLauncherAsarData { + asar_path: self.asar_path.cst_decode(), + main_js_path: self.main_js_path.cst_decode(), + main_js_content: self.main_js_content.cst_decode(), + } + } +} impl CstDecode for wire_cst_rust_http_response { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::http_package::RustHttpResponse { @@ -149,6 +175,20 @@ impl Default for wire_cst_rs_process_stream_data { Self::new_with_null_ptr() } } +impl NewWithNullPtr for wire_cst_rsi_launcher_asar_data { + fn new_with_null_ptr() -> Self { + Self { + asar_path: core::ptr::null_mut(), + main_js_path: core::ptr::null_mut(), + main_js_content: core::ptr::null_mut(), + } + } +} +impl Default for wire_cst_rsi_launcher_asar_data { + fn default() -> Self { + Self::new_with_null_ptr() + } +} impl NewWithNullPtr for wire_cst_rust_http_response { fn new_with_null_ptr() -> Self { Self { @@ -168,6 +208,23 @@ impl Default for wire_cst_rust_http_response { } } +#[no_mangle] +pub extern "C" fn frbgen_starcitizen_doctor_wire_get_rsi_launcher_asar_data( + port_: i64, + asar_path: *mut wire_cst_list_prim_u_8_strict, +) { + wire_get_rsi_launcher_asar_data_impl(port_, asar_path) +} + +#[no_mangle] +pub extern "C" fn frbgen_starcitizen_doctor_wire_rsi_launcher_asar_data_write_main_js( + port_: i64, + that: *mut wire_cst_rsi_launcher_asar_data, + content: *mut wire_cst_list_prim_u_8_loose, +) { + wire_rsi_launcher_asar_data_write_main_js_impl(port_, that, content) +} + #[no_mangle] pub extern "C" fn frbgen_starcitizen_doctor_wire_dns_lookup_ips( port_: i64, @@ -243,6 +300,14 @@ pub extern "C" fn frbgen_starcitizen_doctor_wire_set_foreground_window( wire_set_foreground_window_impl(port_, window_name) } +#[no_mangle] +pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_rsi_launcher_asar_data( +) -> *mut wire_cst_rsi_launcher_asar_data { + flutter_rust_bridge::for_generated::new_leak_box_ptr( + wire_cst_rsi_launcher_asar_data::new_with_null_ptr(), + ) +} + #[no_mangle] pub extern "C" fn frbgen_starcitizen_doctor_cst_new_box_autoadd_u_64(value: u64) -> *mut u64 { flutter_rust_bridge::for_generated::new_leak_box_ptr(value) @@ -262,6 +327,17 @@ pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_String( flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) } +#[no_mangle] +pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_prim_u_8_loose( + len: i32, +) -> *mut wire_cst_list_prim_u_8_loose { + let ans = wire_cst_list_prim_u_8_loose { + ptr: flutter_rust_bridge::for_generated::new_leak_vec_ptr(Default::default(), len), + len, + }; + flutter_rust_bridge::for_generated::new_leak_box_ptr(ans) +} + #[no_mangle] pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_prim_u_8_strict( len: i32, @@ -295,6 +371,12 @@ pub struct wire_cst_list_String { } #[repr(C)] #[derive(Clone, Copy)] +pub struct wire_cst_list_prim_u_8_loose { + ptr: *mut u8, + len: i32, +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct wire_cst_list_prim_u_8_strict { ptr: *mut u8, len: i32, @@ -320,6 +402,13 @@ pub struct wire_cst_rs_process_stream_data { } #[repr(C)] #[derive(Clone, Copy)] +pub struct wire_cst_rsi_launcher_asar_data { + asar_path: *mut wire_cst_list_prim_u_8_strict, + main_js_path: *mut wire_cst_list_prim_u_8_strict, + main_js_content: *mut wire_cst_list_prim_u_8_strict, +} +#[repr(C)] +#[derive(Clone, Copy)] pub struct wire_cst_rust_http_response { status_code: u16, headers: *mut wire_cst_list_record_string_string, diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index f70fe87..ebdd20f 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -31,7 +31,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.0.0-dev.32"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1453545208; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1832496273; // Section: executor @@ -39,6 +39,58 @@ flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs +fn wire_get_rsi_launcher_asar_data_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + asar_path: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_rsi_launcher_asar_data", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_asar_path = asar_path.cst_decode(); + move |context| async move { + transform_result_dco( + (move || async move { + crate::api::asar_api::get_rsi_launcher_asar_data(&api_asar_path).await + })() + .await, + ) + } + }, + ) +} +fn wire_rsi_launcher_asar_data_write_main_js_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + that: impl CstDecode, + content: impl CstDecode>, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rsi_launcher_asar_data_write_main_js", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_that = that.cst_decode(); + let api_content = content.cst_decode(); + move |context| async move { + transform_result_dco( + (move || async move { + crate::api::asar_api::RsiLauncherAsarData::write_main_js( + &api_that, + api_content, + ) + .await + })() + .await, + ) + } + }, + ) +} fn wire_dns_lookup_ips_impl( port_: flutter_rust_bridge::for_generated::MessagePort, host: impl CstDecode, @@ -544,6 +596,20 @@ impl SseDecode for crate::api::rs_process::RsProcessStreamDataType { } } +impl SseDecode for crate::api::asar_api::RsiLauncherAsarData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_asarPath = ::sse_decode(deserializer); + let mut var_mainJsPath = ::sse_decode(deserializer); + let mut var_mainJsContent = >::sse_decode(deserializer); + return crate::api::asar_api::RsiLauncherAsarData { + asar_path: var_asarPath, + main_js_path: var_mainJsPath, + main_js_content: var_mainJsContent, + }; + } +} + impl SseDecode for crate::http_package::RustHttpResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -721,6 +787,28 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.asar_path.into_into_dart().into_dart(), + self.main_js_path.into_into_dart().into_dart(), + self.main_js_content.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::asar_api::RsiLauncherAsarData +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::asar_api::RsiLauncherAsarData +{ + fn into_into_dart(self) -> crate::api::asar_api::RsiLauncherAsarData { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::http_package::RustHttpResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -941,6 +1029,15 @@ impl SseEncode for crate::api::rs_process::RsProcessStreamDataType { } } +impl SseEncode for crate::api::asar_api::RsiLauncherAsarData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.asar_path, serializer); + ::sse_encode(self.main_js_path, serializer); + >::sse_encode(self.main_js_content, serializer); + } +} + impl SseEncode for crate::http_package::RustHttpResponse { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {