diff --git a/lib/app.dart b/lib/app.dart index 76a0fbd..56bc3c9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -22,7 +22,6 @@ import 'package:window_manager/window_manager.dart'; import 'api/analytics.dart'; import 'api/api.dart'; import 'common/conf/url_conf.dart'; -import 'common/helper/system_helper.dart'; import 'common/io/rs_http.dart'; import 'common/rust/frb_generated.dart'; import 'common/rust/api/win32_api.dart' as win32; @@ -148,16 +147,6 @@ class AppGlobalModel extends _$AppGlobalModel { exit(0); } - // init powershell - if (Platform.isWindows) { - try { - await SystemHelper.initPowershellPath(); - dPrint("---- Powershell init -----"); - } catch (e) { - dPrint("powershell init failed : $e"); - } - } - // get windows info WindowsDeviceInfo? windowsDeviceInfo; try { diff --git a/lib/common/helper/system_helper.dart b/lib/common/helper/system_helper.dart index 71aefcd..6696366 100644 --- a/lib/common/helper/system_helper.dart +++ b/lib/common/helper/system_helper.dart @@ -5,76 +5,31 @@ import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; class SystemHelper { - static String powershellPath = "powershell.exe"; - - static Future initPowershellPath() async { - try { - var result = await Process.run(powershellPath, ["echo", "pong"]); - if (!result.stdout.toString().startsWith("pong") && powershellPath == "powershell.exe") { - throw "powershell check failed"; - } - } catch (e) { - Map envVars = Platform.environment; - final systemRoot = envVars["SYSTEMROOT"]; - if (systemRoot != null) { - final autoSearchPath = "$systemRoot\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"; - dPrint("auto search powershell path === $autoSearchPath"); - powershellPath = autoSearchPath; - } - } - } - static Future checkNvmePatchStatus() async { try { - var result = await Process.run(SystemHelper.powershellPath, [ - "Get-ItemProperty", - "-Path", - "\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"", - "-Name", - "\"ForcedPhysicalSectorSizeInBytes\"", - ]); - dPrint("checkNvmePatchStatus result ==== ${result.stdout}"); - if (result.stderr == "" && result.stdout.toString().contains("{* 4095}")) { - return true; - } else { - return false; - } + return await win32.checkNvmePatchStatus(); } catch (e) { + dPrint("checkNvmePatchStatus error: $e"); return false; } } static Future addNvmePatch() async { - var result = await Process.run(powershellPath, [ - 'New-ItemProperty', - "-Path", - "\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"", - "-Name", - "ForcedPhysicalSectorSizeInBytes", - "-PropertyType MultiString", - "-Force -Value", - "\"* 4095\"", - ]); - dPrint("nvme_PhysicalBytes result == ${result.stdout}"); - return result.stderr; + try { + await win32.addNvmePatch(); + return ""; + } catch (e) { + dPrint("addNvmePatch error: $e"); + return e.toString(); + } } static Future doRemoveNvmePath() async { try { - var result = await Process.run(powershellPath, [ - "Clear-ItemProperty", - "-Path", - "\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"", - "-Name", - "\"ForcedPhysicalSectorSizeInBytes\"", - ]); - dPrint("doRemoveNvmePath result ==== ${result.stdout}"); - if (result.stderr == "") { - return true; - } else { - return false; - } + await win32.removeNvmePatch(); + return true; } catch (e) { + dPrint("doRemoveNvmePath error: $e"); return false; } } diff --git a/lib/common/rust/api/win32_api.dart b/lib/common/rust/api/win32_api.dart index 88a0284..cda3105 100644 --- a/lib/common/rust/api/win32_api.dart +++ b/lib/common/rust/api/win32_api.dart @@ -65,6 +65,55 @@ Future> getProcessListByName({required String processName}) => processName: processName, ); +/// Kill processes by name +Future killProcessByName({required String processName}) => RustLib + .instance + .api + .crateApiWin32ApiKillProcessByName(processName: processName); + +/// Get disk physical sector size for performance +Future getDiskPhysicalSectorSize({required String driveLetter}) => RustLib + .instance + .api + .crateApiWin32ApiGetDiskPhysicalSectorSize(driveLetter: driveLetter); + +/// Create a desktop shortcut +Future createDesktopShortcut({ + required String targetPath, + required String shortcutName, +}) => RustLib.instance.api.crateApiWin32ApiCreateDesktopShortcut( + targetPath: targetPath, + shortcutName: shortcutName, +); + +/// Run a program with admin privileges (UAC) +Future runAsAdmin({required String program, required String args}) => + RustLib.instance.api.crateApiWin32ApiRunAsAdmin( + program: program, + args: args, + ); + +/// Start a program (without waiting) +Future startProcess({ + required String program, + required List args, +}) => RustLib.instance.api.crateApiWin32ApiStartProcess( + program: program, + args: args, +); + +/// Check if NVME patch is applied +Future checkNvmePatchStatus() => + RustLib.instance.api.crateApiWin32ApiCheckNvmePatchStatus(); + +/// Add NVME patch to registry +Future addNvmePatch() => + RustLib.instance.api.crateApiWin32ApiAddNvmePatch(); + +/// Remove NVME patch from registry +Future removeNvmePatch() => + RustLib.instance.api.crateApiWin32ApiRemoveNvmePatch(); + class ProcessInfo { final int pid; final String name; diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index 0bea161..2a1d56c 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -71,7 +71,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => 1317751362; + int get rustContentHash => 1161621087; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -82,8 +82,17 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { + Future crateApiWin32ApiAddNvmePatch(); + + Future crateApiWin32ApiCheckNvmePatchStatus(); + Future crateApiOrtApiClearAllModels(); + Future crateApiWin32ApiCreateDesktopShortcut({ + required String targetPath, + required String shortcutName, + }); + Future> crateApiHttpApiDnsLookupIps({required String host}); Future> crateApiHttpApiDnsLookupTxt({required String host}); @@ -97,6 +106,10 @@ abstract class RustLibApi extends BaseApi { bool? withCustomDns, }); + Future crateApiWin32ApiGetDiskPhysicalSectorSize({ + required String driveLetter, + }); + Future crateApiHttpApiGetFasterUrl({ required List urls, String? pathSuffix, @@ -122,6 +135,8 @@ abstract class RustLibApi extends BaseApi { Future crateApiWin32ApiGetSystemMemorySizeGb(); + Future crateApiWin32ApiKillProcessByName({required String processName}); + Future crateApiOrtApiLoadTranslationModel({ required String modelPath, required String modelKey, @@ -151,6 +166,8 @@ abstract class RustLibApi extends BaseApi { Future crateApiUnp4KApiP4KOpen({required String p4KPath}); + Future crateApiWin32ApiRemoveNvmePatch(); + Future crateApiWin32ApiResolveShortcut({required String lnkPath}); Future crateApiAsarApiRsiLauncherAsarDataWriteMainJs({ @@ -158,6 +175,11 @@ abstract class RustLibApi extends BaseApi { required List content, }); + Future crateApiWin32ApiRunAsAdmin({ + required String program, + required String args, + }); + Future crateApiWin32ApiSendNotify({ String? summary, String? body, @@ -179,6 +201,11 @@ abstract class RustLibApi extends BaseApi { required String workingDirectory, }); + Future crateApiWin32ApiStartProcess({ + required String program, + required List args, + }); + Future crateApiOrtApiTranslateText({ required String modelKey, required String text, @@ -261,6 +288,50 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); + @override + Future crateApiWin32ApiAddNvmePatch() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire.wire__crate__api__win32_api__add_nvme_patch(port_); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiAddNvmePatchConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiAddNvmePatchConstMeta => + const TaskConstMeta(debugName: "add_nvme_patch", argNames: []); + + @override + Future crateApiWin32ApiCheckNvmePatchStatus() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire.wire__crate__api__win32_api__check_nvme_patch_status( + port_, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_bool, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiCheckNvmePatchStatusConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiCheckNvmePatchStatusConstMeta => + const TaskConstMeta(debugName: "check_nvme_patch_status", argNames: []); + @override Future crateApiOrtApiClearAllModels() { return handler.executeNormal( @@ -282,6 +353,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiOrtApiClearAllModelsConstMeta => const TaskConstMeta(debugName: "clear_all_models", argNames: []); + @override + Future crateApiWin32ApiCreateDesktopShortcut({ + required String targetPath, + required String shortcutName, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(targetPath); + var arg1 = cst_encode_String(shortcutName); + return wire.wire__crate__api__win32_api__create_desktop_shortcut( + port_, + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiCreateDesktopShortcutConstMeta, + argValues: [targetPath, shortcutName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiCreateDesktopShortcutConstMeta => + const TaskConstMeta( + debugName: "create_desktop_shortcut", + argNames: ["targetPath", "shortcutName"], + ); + @override Future> crateApiHttpApiDnsLookupIps({required String host}) { return handler.executeNormal( @@ -384,6 +488,37 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ], ); + @override + Future crateApiWin32ApiGetDiskPhysicalSectorSize({ + required String driveLetter, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(driveLetter); + return wire + .wire__crate__api__win32_api__get_disk_physical_sector_size( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_u_32, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiGetDiskPhysicalSectorSizeConstMeta, + argValues: [driveLetter], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiGetDiskPhysicalSectorSizeConstMeta => + const TaskConstMeta( + debugName: "get_disk_physical_sector_size", + argNames: ["driveLetter"], + ); + @override Future crateApiHttpApiGetFasterUrl({ required List urls, @@ -604,6 +739,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWin32ApiGetSystemMemorySizeGbConstMeta => const TaskConstMeta(debugName: "get_system_memory_size_gb", argNames: []); + @override + Future crateApiWin32ApiKillProcessByName({required String processName}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(processName); + return wire.wire__crate__api__win32_api__kill_process_by_name( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_u_32, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiKillProcessByNameConstMeta, + argValues: [processName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiKillProcessByNameConstMeta => + const TaskConstMeta( + debugName: "kill_process_by_name", + argNames: ["processName"], + ); + @override Future crateApiOrtApiLoadTranslationModel({ required String modelPath, @@ -824,6 +987,27 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiUnp4KApiP4KOpenConstMeta => const TaskConstMeta(debugName: "p4k_open", argNames: ["p4KPath"]); + @override + Future crateApiWin32ApiRemoveNvmePatch() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + return wire.wire__crate__api__win32_api__remove_nvme_patch(port_); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiRemoveNvmePatchConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiRemoveNvmePatchConstMeta => + const TaskConstMeta(debugName: "remove_nvme_patch", argNames: []); + @override Future crateApiWin32ApiResolveShortcut({required String lnkPath}) { return handler.executeNormal( @@ -883,6 +1067,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["that", "content"], ); + @override + Future crateApiWin32ApiRunAsAdmin({ + required String program, + required String args, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(program); + var arg1 = cst_encode_String(args); + return wire.wire__crate__api__win32_api__run_as_admin( + port_, + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiRunAsAdminConstMeta, + argValues: [program, args], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiRunAsAdminConstMeta => const TaskConstMeta( + debugName: "run_as_admin", + argNames: ["program", "args"], + ); + @override Future crateApiWin32ApiSendNotify({ String? summary, @@ -1024,6 +1240,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["executable", "arguments", "workingDirectory", "streamSink"], ); + @override + Future crateApiWin32ApiStartProcess({ + required String program, + required List args, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(program); + var arg1 = cst_encode_list_String(args); + return wire.wire__crate__api__win32_api__start_process( + port_, + arg0, + arg1, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_unit, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiStartProcessConstMeta, + argValues: [program, args], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiStartProcessConstMeta => + const TaskConstMeta( + debugName: "start_process", + argNames: ["program", "args"], + ); + @override Future crateApiOrtApiTranslateText({ required String modelKey, diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index 500914c..b87ec5e 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -920,6 +920,30 @@ class RustLibWire implements BaseWire { late final _store_dart_post_cobject = _store_dart_post_cobjectPtr .asFunction(); + void wire__crate__api__win32_api__add_nvme_patch(int port_) { + return _wire__crate__api__win32_api__add_nvme_patch(port_); + } + + late final _wire__crate__api__win32_api__add_nvme_patchPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__add_nvme_patch', + ); + late final _wire__crate__api__win32_api__add_nvme_patch = + _wire__crate__api__win32_api__add_nvme_patchPtr + .asFunction(); + + void wire__crate__api__win32_api__check_nvme_patch_status(int port_) { + return _wire__crate__api__win32_api__check_nvme_patch_status(port_); + } + + late final _wire__crate__api__win32_api__check_nvme_patch_statusPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__check_nvme_patch_status', + ); + late final _wire__crate__api__win32_api__check_nvme_patch_status = + _wire__crate__api__win32_api__check_nvme_patch_statusPtr + .asFunction(); + void wire__crate__api__ort_api__clear_all_models(int port_) { return _wire__crate__api__ort_api__clear_all_models(port_); } @@ -932,6 +956,40 @@ class RustLibWire implements BaseWire { _wire__crate__api__ort_api__clear_all_modelsPtr .asFunction(); + void wire__crate__api__win32_api__create_desktop_shortcut( + int port_, + ffi.Pointer target_path, + ffi.Pointer shortcut_name, + ) { + return _wire__crate__api__win32_api__create_desktop_shortcut( + port_, + target_path, + shortcut_name, + ); + } + + late final _wire__crate__api__win32_api__create_desktop_shortcutPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__create_desktop_shortcut', + ); + late final _wire__crate__api__win32_api__create_desktop_shortcut = + _wire__crate__api__win32_api__create_desktop_shortcutPtr + .asFunction< + void Function( + int, + ffi.Pointer, + ffi.Pointer, + ) + >(); + void wire__crate__api__http_api__dns_lookup_ips( int port_, ffi.Pointer host, @@ -1024,6 +1082,33 @@ class RustLibWire implements BaseWire { ) >(); + void wire__crate__api__win32_api__get_disk_physical_sector_size( + int port_, + ffi.Pointer drive_letter, + ) { + return _wire__crate__api__win32_api__get_disk_physical_sector_size( + port_, + drive_letter, + ); + } + + late final _wire__crate__api__win32_api__get_disk_physical_sector_sizePtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_disk_physical_sector_size', + ); + late final _wire__crate__api__win32_api__get_disk_physical_sector_size = + _wire__crate__api__win32_api__get_disk_physical_sector_sizePtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + void wire__crate__api__http_api__get_faster_url( int port_, ffi.Pointer urls, @@ -1189,6 +1274,33 @@ class RustLibWire implements BaseWire { _wire__crate__api__win32_api__get_system_memory_size_gbPtr .asFunction(); + void wire__crate__api__win32_api__kill_process_by_name( + int port_, + ffi.Pointer process_name, + ) { + return _wire__crate__api__win32_api__kill_process_by_name( + port_, + process_name, + ); + } + + late final _wire__crate__api__win32_api__kill_process_by_namePtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__kill_process_by_name', + ); + late final _wire__crate__api__win32_api__kill_process_by_name = + _wire__crate__api__win32_api__kill_process_by_namePtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + void wire__crate__api__ort_api__load_translation_model( int port_, ffi.Pointer model_path, @@ -1380,6 +1492,18 @@ class RustLibWire implements BaseWire { void Function(int, ffi.Pointer) >(); + void wire__crate__api__win32_api__remove_nvme_patch(int port_) { + return _wire__crate__api__win32_api__remove_nvme_patch(port_); + } + + late final _wire__crate__api__win32_api__remove_nvme_patchPtr = + _lookup>( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__remove_nvme_patch', + ); + late final _wire__crate__api__win32_api__remove_nvme_patch = + _wire__crate__api__win32_api__remove_nvme_patchPtr + .asFunction(); + void wire__crate__api__win32_api__resolve_shortcut( int port_, ffi.Pointer lnk_path, @@ -1438,6 +1562,34 @@ class RustLibWire implements BaseWire { ) >(); + void wire__crate__api__win32_api__run_as_admin( + int port_, + ffi.Pointer program, + ffi.Pointer args, + ) { + return _wire__crate__api__win32_api__run_as_admin(port_, program, args); + } + + late final _wire__crate__api__win32_api__run_as_adminPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ) + > + >('frbgen_starcitizen_doctor_wire__crate__api__win32_api__run_as_admin'); + late final _wire__crate__api__win32_api__run_as_admin = + _wire__crate__api__win32_api__run_as_adminPtr + .asFunction< + void Function( + int, + ffi.Pointer, + ffi.Pointer, + ) + >(); + void wire__crate__api__win32_api__send_notify( int port_, ffi.Pointer summary, @@ -1569,6 +1721,34 @@ class RustLibWire implements BaseWire { ) >(); + void wire__crate__api__win32_api__start_process( + int port_, + ffi.Pointer program, + ffi.Pointer args, + ) { + return _wire__crate__api__win32_api__start_process(port_, program, args); + } + + late final _wire__crate__api__win32_api__start_processPtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ffi.Pointer, + ) + > + >('frbgen_starcitizen_doctor_wire__crate__api__win32_api__start_process'); + late final _wire__crate__api__win32_api__start_process = + _wire__crate__api__win32_api__start_processPtr + .asFunction< + void Function( + int, + ffi.Pointer, + ffi.Pointer, + ) + >(); + void wire__crate__api__ort_api__translate_text( int port_, ffi.Pointer model_key, diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.dart index 45e6580..f4385f4 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:starcitizen_doctor/common/helper/log_helper.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; +import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/utils/base_utils.dart'; import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/ui/home/home_ui_model.dart'; @@ -35,11 +36,11 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { } Future doFix( - // ignore: avoid_build_context_in_providers - BuildContext context, - MapEntry item) async { - final checkResult = - List>.from(state.checkResult ?? []); + // ignore: avoid_build_context_in_providers + BuildContext context, + MapEntry item, + ) async { + final checkResult = List>.from(state.checkResult ?? []); state = state.copyWith(isFixing: true, isFixingString: ""); switch (item.key) { case "unSupport_system": @@ -49,13 +50,11 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { try { await Directory(item.value).create(recursive: true); if (!context.mounted) break; - showToast( - context, S.current.doctor_action_result_create_folder_success); + showToast(context, S.current.doctor_action_result_create_folder_success); checkResult.remove(item); state = state.copyWith(checkResult: checkResult); } catch (e) { - showToast(context, - S.current.doctor_action_result_create_folder_fail(item.value, e)); + showToast(context, S.current.doctor_action_result_create_folder_fail(item.value, e)); } break; case "nvme_PhysicalBytes": @@ -71,8 +70,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { } break; case "eac_file_miss": - showToast(context, - S.current.doctor_info_result_verify_files_with_rsi_launcher); + showToast(context, S.current.doctor_info_result_verify_files_with_rsi_launcher); break; case "eac_not_install": final eacJsonPath = "${item.value}\\Settings.json"; @@ -80,19 +78,16 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { final Map eacJson = json.decode(utf8.decode(eacJsonData)); final eacID = eacJson["productid"]; try { - var result = await Process.run( - "${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]); + var result = await Process.run("${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]); dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID"); if (result.stderr == "") { if (!context.mounted) break; - showToast( - context, S.current.doctor_action_result_game_start_success); + showToast(context, S.current.doctor_action_result_game_start_success); checkResult.remove(item); state = state.copyWith(checkResult: checkResult); } else { if (!context.mounted) break; - showToast(context, - S.current.doctor_action_result_fix_fail(result.stderr)); + showToast(context, S.current.doctor_action_result_fix_fail(result.stderr)); } } catch (e) { if (!context.mounted) break; @@ -102,8 +97,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { case "cn_user_name": showToast(context, S.current.doctor_action_result_redirect_warning); await Future.delayed(const Duration(milliseconds: 300)); - launchUrlString( - "https://jingyan.baidu.com/article/59703552a318a08fc0074021.html"); + launchUrlString("https://jingyan.baidu.com/article/59703552a318a08fc0074021.html"); break; default: showToast(context, S.current.doctor_action_result_issue_not_supported); @@ -115,8 +109,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { // ignore: avoid_build_context_in_providers Future doCheck(BuildContext context) async { if (state.isChecking) return; - state = state.copyWith( - isChecking: true, lastScreenInfo: S.current.doctor_action_analyzing); + state = state.copyWith(isChecking: true, lastScreenInfo: S.current.doctor_action_analyzing); dPrint("-------- start docker check -----"); if (!context.mounted) return; await _statCheck(context); @@ -144,11 +137,8 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { final lastScreenInfo = S.current.doctor_action_result_analysis_no_issue; state = state.copyWith(checkResult: null, lastScreenInfo: lastScreenInfo); } else { - final lastScreenInfo = S.current - .doctor_action_result_analysis_issues_found( - checkResult.length.toString()); - state = state.copyWith( - checkResult: checkResult, lastScreenInfo: lastScreenInfo); + final lastScreenInfo = S.current.doctor_action_result_analysis_issues_found(checkResult.length.toString()); + state = state.copyWith(checkResult: checkResult, lastScreenInfo: lastScreenInfo); } if (scInstalledPath == "not_install" && (checkResult.isEmpty)) { @@ -158,8 +148,11 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { } // ignore: avoid_build_context_in_providers - Future _checkGameRunningLog(BuildContext context, String scInstalledPath, - List> checkResult) async { + Future _checkGameRunningLog( + BuildContext context, + String scInstalledPath, + List> checkResult, + ) async { if (scInstalledPath == "not_install") return; final lastScreenInfo = S.current.doctor_action_tip_checking_game_log; state = state.copyWith(lastScreenInfo: lastScreenInfo); @@ -168,28 +161,27 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { final info = SCLoggerHelper.getGameRunningLogInfo(logs); if (info != null) { if (info.key != "_") { - checkResult.add(MapEntry( - S.current.doctor_action_info_game_abnormal_exit(info.key), - info.value)); + checkResult.add(MapEntry(S.current.doctor_action_info_game_abnormal_exit(info.key), info.value)); } else { - checkResult.add(MapEntry( + checkResult.add( + MapEntry( S.current.doctor_action_info_game_abnormal_exit_unknown, - S.current.doctor_action_info_info_feedback(info.value))); + S.current.doctor_action_info_info_feedback(info.value), + ), + ); } } } // ignore: avoid_build_context_in_providers - Future _checkEAC(BuildContext context, String scInstalledPath, - List> checkResult) async { + Future _checkEAC(BuildContext context, String scInstalledPath, List> checkResult) async { if (scInstalledPath == "not_install") return; final lastScreenInfo = S.current.doctor_action_info_checking_eac; state = state.copyWith(lastScreenInfo: lastScreenInfo); final eacPath = "$scInstalledPath\\EasyAntiCheat"; final eacJsonPath = "$eacPath\\Settings.json"; - if (!await Directory(eacPath).exists() || - !await File(eacJsonPath).exists()) { + if (!await Directory(eacPath).exists() || !await File(eacJsonPath).exists()) { checkResult.add(const MapEntry("eac_file_miss", "")); return; } @@ -212,17 +204,18 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { final _cnExp = RegExp(r"[^\x00-\xff]"); // ignore: avoid_build_context_in_providers - Future _checkPreInstall(BuildContext context, String scInstalledPath, - List> checkResult) async { + Future _checkPreInstall( + BuildContext context, + String scInstalledPath, + List> checkResult, + ) async { final lastScreenInfo = S.current.doctor_action_info_checking_runtime; state = state.copyWith(lastScreenInfo: lastScreenInfo); if (!(Platform.operatingSystemVersion.contains("Windows 10") || Platform.operatingSystemVersion.contains("Windows 11"))) { - checkResult - .add(MapEntry("unSupport_system", Platform.operatingSystemVersion)); - final lastScreenInfo = S.current.doctor_action_result_info_unsupported_os( - Platform.operatingSystemVersion); + checkResult.add(MapEntry("unSupport_system", Platform.operatingSystemVersion)); + final lastScreenInfo = S.current.doctor_action_result_info_unsupported_os(Platform.operatingSystemVersion); state = state.copyWith(lastScreenInfo: lastScreenInfo); await showToast(context, lastScreenInfo); } @@ -236,12 +229,10 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { if (ramSize < 16) { checkResult.add(MapEntry("low_ram", "$ramSize")); } - state = state.copyWith( - lastScreenInfo: S.current.doctor_action_info_checking_install_info); + state = state.copyWith(lastScreenInfo: S.current.doctor_action_info_checking_install_info); // 检查安装分区 try { - final listData = await SCLoggerHelper.getGameInstallPath( - await SCLoggerHelper.getLauncherLogList() ?? []); + final listData = await SCLoggerHelper.getGameInstallPath(await SCLoggerHelper.getLauncherLogList() ?? []); final p = []; final checkedPath = []; for (var installPath in listData) { @@ -262,19 +253,16 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { } } - // call check + // call check using Rust API for (var element in p) { - var result = await Process.run('powershell', [ - "(fsutil fsinfo sectorinfo $element: | Select-String 'PhysicalBytesPerSectorForPerformance').ToString().Split(':')[1].Trim()" - ]); - dPrint( - "fsutil info sector info: ->>> ${result.stdout.toString().trim()}"); - if (result.stderr == "") { - final rs = result.stdout.toString().trim(); - final physicalBytesPerSectorForPerformance = (int.tryParse(rs) ?? 0); + try { + final physicalBytesPerSectorForPerformance = await win32.getDiskPhysicalSectorSize(driveLetter: element); + dPrint("disk sector info for $element: -> $physicalBytesPerSectorForPerformance"); if (physicalBytesPerSectorForPerformance > 4096) { checkResult.add(MapEntry("nvme_PhysicalBytes", element)); } + } catch (e) { + dPrint("getDiskPhysicalSectorSize error: $e"); } } } catch (e) { diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart index 3a5e722..89dafb4 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart @@ -42,7 +42,7 @@ final class HomeGameDoctorUIModelProvider } String _$homeGameDoctorUIModelHash() => - r'7035b501860e9d8c3fdfb91370311760120af115'; + r'8226e88797ecc68920f684a1dc072a340bca783a'; abstract class _$HomeGameDoctorUIModel extends $Notifier { HomeGameDoctorState build(); diff --git a/lib/ui/home/home_ui_model.dart b/lib/ui/home/home_ui_model.dart index 2b5938d..3d1eb05 100644 --- a/lib/ui/home/home_ui_model.dart +++ b/lib/ui/home/home_ui_model.dart @@ -12,7 +12,6 @@ import 'package:starcitizen_doctor/api/news_api.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/helper/log_helper.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/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/utils/async.dart'; @@ -323,7 +322,7 @@ class HomeUIModel extends _$HomeUIModel { if (ConstConf.isMSE) { if (state.isCurGameRunning) { - await Process.run(SystemHelper.powershellPath, ["ps \"StarCitizen\" | kill"]); + await win32.killProcessByName(processName: "StarCitizen"); return; } AnalyticsApi.touch("gameLaunch"); diff --git a/lib/ui/home/home_ui_model.g.dart b/lib/ui/home/home_ui_model.g.dart index 2b283cf..f0faba6 100644 --- a/lib/ui/home/home_ui_model.g.dart +++ b/lib/ui/home/home_ui_model.g.dart @@ -41,7 +41,7 @@ final class HomeUIModelProvider } } -String _$homeUIModelHash() => r'cc795e27213d02993459dd711de4a897c8491575'; +String _$homeUIModelHash() => r'c3affcfc99a0cd7e3587cc58f02dbdc24c4f8ae7'; abstract class _$HomeUIModel extends $Notifier { HomeUIModelState build(); diff --git a/lib/ui/settings/settings_ui_model.dart b/lib/ui/settings/settings_ui_model.dart index 95389ad..09970e3 100644 --- a/lib/ui/settings/settings_ui_model.dart +++ b/lib/ui/settings/settings_ui_model.dart @@ -9,6 +9,7 @@ import 'package:hive_ce/hive.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; +import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/provider.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart'; @@ -50,14 +51,15 @@ class SettingsUIModel extends _$SettingsUIModel { Future setGameLaunchECore(BuildContext context) async { final userBox = await Hive.openBox("app_conf"); - final defaultInput = - userBox.get("gameLaunch_eCore_count", defaultValue: "0"); + final defaultInput = userBox.get("gameLaunch_eCore_count", defaultValue: "0"); if (!context.mounted) return; - final input = await showInputDialogs(context, - title: S.current.setting_action_info_enter_cpu_core_to_ignore, - content: S.current.setting_action_info_cpu_core_tip, - initialValue: defaultInput, - inputFormatters: [FilteringTextInputFormatter.digitsOnly]); + final input = await showInputDialogs( + context, + title: S.current.setting_action_info_enter_cpu_core_to_ignore, + content: S.current.setting_action_info_cpu_core_tip, + initialValue: defaultInput, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ); if (input == null) return; userBox.put("gameLaunch_eCore_count", input); _initState(); @@ -65,8 +67,7 @@ class SettingsUIModel extends _$SettingsUIModel { Future _updateGameLaunchECore() async { final userBox = await Hive.openBox("app_conf"); - final inputGameLaunchECore = - userBox.get("gameLaunch_eCore_count", defaultValue: "0"); + final inputGameLaunchECore = userBox.get("gameLaunch_eCore_count", defaultValue: "0"); state = state.copyWith(inputGameLaunchECore: inputGameLaunchECore); } @@ -102,9 +103,7 @@ class SettingsUIModel extends _$SettingsUIModel { if (r == null || r.files.firstOrNull?.path == null) return; final fileName = r.files.first.path!; dPrint(fileName); - final fileNameRegExp = RegExp( - r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$", - caseSensitive: false); + final fileNameRegExp = RegExp(r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$", caseSensitive: false); if (fileNameRegExp.hasMatch(fileName)) { RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$"); String extractedPath = fileName.replaceFirst(pathRegex, ''); @@ -127,8 +126,7 @@ class SettingsUIModel extends _$SettingsUIModel { final confBox = await Hive.openBox("app_conf"); final customLauncherPath = confBox.get("custom_launcher_path"); final customGamePath = confBox.get("custom_game_path"); - state = state.copyWith( - customLauncherPath: customLauncherPath, customGamePath: customGamePath); + state = state.copyWith(customLauncherPath: customLauncherPath, customGamePath: customGamePath); } Future delName(String key) async { @@ -138,24 +136,21 @@ class SettingsUIModel extends _$SettingsUIModel { } Future _loadLocationCacheSize() async { - final len1 = await SystemHelper.getDirLen( - "${appGlobalState.applicationSupportDir}/Localizations"); - final len2 = await SystemHelper.getDirLen( - "${appGlobalState.applicationSupportDir}/launcher_enhance_data"); + final len1 = await SystemHelper.getDirLen("${appGlobalState.applicationSupportDir}/Localizations"); + final len2 = await SystemHelper.getDirLen("${appGlobalState.applicationSupportDir}/launcher_enhance_data"); final locationCacheSize = len1 + len2; state = state.copyWith(locationCacheSize: locationCacheSize); } Future cleanLocationCache(BuildContext context) async { final ok = await showConfirmDialogs( - context, - S.current.setting_action_info_confirm_clear_cache, - Text(S.current.setting_action_info_clear_cache_warning)); + context, + S.current.setting_action_info_confirm_clear_cache, + Text(S.current.setting_action_info_clear_cache_warning), + ); if (ok == true) { - final dir1 = - Directory("${appGlobalState.applicationSupportDir}/Localizations"); - final dir2 = Directory( - "${appGlobalState.applicationSupportDir}/launcher_enhance_data"); + final dir1 = Directory("${appGlobalState.applicationSupportDir}/Localizations"); + final dir2 = Directory("${appGlobalState.applicationSupportDir}/launcher_enhance_data"); if (!context.mounted) return; if (await dir1.exists()) { if (!context.mounted) return; @@ -172,36 +167,27 @@ class SettingsUIModel extends _$SettingsUIModel { Future addShortCut(BuildContext context) async { if (ConstConf.isMSE) { - showToast( - context, S.current.setting_action_info_microsoft_version_limitation); + showToast(context, S.current.setting_action_info_microsoft_version_limitation); await Future.delayed(const Duration(seconds: 1)); Process.run("explorer.exe", ["shell:AppsFolder"]); return; } dPrint(Platform.resolvedExecutable); - final shortCuntName = S.current.app_shortcut_name; - final script = """ - \$targetPath = "${Platform.resolvedExecutable}"; - \$shortcutPath = [System.IO.Path]::Combine([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::DesktopDirectory), "$shortCuntName"); - \$shell = New-Object -ComObject WScript.Shell - \$shortcut = \$shell.CreateShortcut(\$shortcutPath) - if (\$shortcut -eq \$null) { - Write-Host "Failed to create shortcut." - } else { - \$shortcut.TargetPath = \$targetPath - \$shortcut.Save() - Write-Host "Shortcut created successfully." + final shortcutName = S.current.app_shortcut_name; + try { + await win32.createDesktopShortcut(targetPath: Platform.resolvedExecutable, shortcutName: shortcutName); + if (!context.mounted) return; + showToast(context, S.current.setting_action_info_shortcut_created); + } catch (e) { + dPrint("createDesktopShortcut error: $e"); + if (!context.mounted) return; + showToast(context, "Failed to create shortcut: $e"); } -"""; - await Process.run(SystemHelper.powershellPath, [script]); - if (!context.mounted) return; - showToast(context, S.current.setting_action_info_shortcut_created); } Future _loadToolSiteMirrorState() async { final userBox = await Hive.openBox("app_conf"); - final isEnableToolSiteMirrors = - userBox.get("isEnableToolSiteMirrors", defaultValue: false); + final isEnableToolSiteMirrors = userBox.get("isEnableToolSiteMirrors", defaultValue: false); state = state.copyWith(isEnableToolSiteMirrors: isEnableToolSiteMirrors); } @@ -213,8 +199,7 @@ class SettingsUIModel extends _$SettingsUIModel { } Future showLogs() async { - SystemHelper.openDir(getDPrintFile()?.absolute.path.replaceAll("/", "\\"), - isFile: true); + SystemHelper.openDir(getDPrintFile()?.absolute.path.replaceAll("/", "\\"), isFile: true); } void onChangeUseInternalDNS(bool? b) { @@ -225,8 +210,7 @@ class SettingsUIModel extends _$SettingsUIModel { Future _loadUseInternalDNS() async { final userBox = await Hive.openBox("app_conf"); - final isUseInternalDNS = - userBox.get("isUseInternalDNS", defaultValue: false); + final isUseInternalDNS = userBox.get("isUseInternalDNS", defaultValue: false); state = state.copyWith(isUseInternalDNS: isUseInternalDNS); } @@ -238,8 +222,7 @@ class SettingsUIModel extends _$SettingsUIModel { Future _loadOnnxXnnPackState() async { final userBox = await Hive.openBox("app_conf"); - final isEnableOnnxXnnPack = - userBox.get("isEnableOnnxXnnPack", defaultValue: true); + final isEnableOnnxXnnPack = userBox.get("isEnableOnnxXnnPack", defaultValue: true); state = state.copyWith(isEnableOnnxXnnPack: isEnableOnnxXnnPack); } } diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart index 3fd560d..a95376b 100644 --- a/lib/ui/settings/settings_ui_model.g.dart +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -41,7 +41,7 @@ final class SettingsUIModelProvider } } -String _$settingsUIModelHash() => r'72947d5ed36290df865cb010b056dc632f5dccec'; +String _$settingsUIModelHash() => r'd34b1a2fac69d10f560d9a2e1a7431dd5a7954ca'; abstract class _$SettingsUIModel extends $Notifier { SettingsUIState build(); diff --git a/lib/ui/settings/upgrade_dialog.dart b/lib/ui/settings/upgrade_dialog.dart index 3c7f376..1a85d58 100644 --- a/lib/ui/settings/upgrade_dialog.dart +++ b/lib/ui/settings/upgrade_dialog.dart @@ -11,6 +11,7 @@ import 'package:starcitizen_doctor/app.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; +import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:html/parser.dart' as html_parser; @@ -44,39 +45,36 @@ class UpgradeDialogUI extends HookConsumerWidget { return Material( child: ContentDialog( - title: - Text(S.current.app_upgrade_title_new_version_found(targetVersion)), - constraints: - BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55), + title: Text(S.current.app_upgrade_title_new_version_found(targetVersion)), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55), content: Column( mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only(left: 24, right: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (description.value == null) ...[ - Center( - child: Column( - children: [ - const ProgressRing(), - const SizedBox(height: 16), - Text(S.current - .app_upgrade_info_getting_new_version_details) - ], + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(left: 24, right: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (description.value == null) ...[ + Center( + child: Column( + children: [ + const ProgressRing(), + const SizedBox(height: 16), + Text(S.current.app_upgrade_info_getting_new_version_details), + ], + ), ), - ) - ] else - ...makeMarkdownView(description.value!, - attachmentsUrl: URLConf.giteaAttachmentsUrl), - ], + ] else + ...makeMarkdownView(description.value!, attachmentsUrl: URLConf.giteaAttachmentsUrl), + ], + ), ), ), - )), + ), if (isUsingDiversion.value) ...[ const SizedBox(height: 24), GestureDetector( @@ -84,13 +82,12 @@ class UpgradeDialogUI extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white.withValues(alpha: .1), - borderRadius: BorderRadius.circular(7)), + color: Colors.white.withValues(alpha: .1), + borderRadius: BorderRadius.circular(7), + ), child: Text( S.current.app_upgrade_info_update_server_tip, - style: TextStyle( - fontSize: 14, - color: Colors.white.withValues(alpha: .7)), + style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .7)), ), ), ), @@ -99,14 +96,12 @@ class UpgradeDialogUI extends HookConsumerWidget { const SizedBox(height: 24), Row( children: [ - Text(progress.value == 100 - ? S.current.app_upgrade_info_installing - : S.current.app_upgrade_info_downloading( - progress.value.toStringAsFixed(2))), - Expanded( - child: ProgressBar( - value: progress.value == 100 ? null : progress.value, - )), + Text( + progress.value == 100 + ? S.current.app_upgrade_info_installing + : S.current.app_upgrade_info_downloading(progress.value.toStringAsFixed(2)), + ), + Expanded(child: ProgressBar(value: progress.value == 100 ? null : progress.value)), ], ), ], @@ -117,38 +112,40 @@ class UpgradeDialogUI extends HookConsumerWidget { : [ if (downloadUrl.value.isNotEmpty) FilledButton( - onPressed: () => _doUpgrade( - context, - appState, - isUpgrading, - appModel, - downloadUrl, - description, - isUsingDiversion, - progress), - child: Padding( - padding: const EdgeInsets.only( - top: 4, bottom: 4, left: 8, right: 8), - child: Text(S.current.app_upgrade_action_update_now), - )), + onPressed: () => _doUpgrade( + context, + appState, + isUpgrading, + appModel, + downloadUrl, + description, + isUsingDiversion, + progress, + ), + child: Padding( + padding: const EdgeInsets.only(top: 4, bottom: 4, left: 8, right: 8), + child: Text(S.current.app_upgrade_action_update_now), + ), + ), if (ConstConf.appVersionCode >= (minVersionCode ?? 0)) Button( - onPressed: () => _doCancel(context), - child: Padding( - padding: const EdgeInsets.only( - top: 4, bottom: 4, left: 8, right: 8), - child: Text(S.current.app_upgrade_action_next_time), - )), + onPressed: () => _doCancel(context), + child: Padding( + padding: const EdgeInsets.only(top: 4, bottom: 4, left: 8, right: 8), + child: Text(S.current.app_upgrade_action_next_time), + ), + ), ], ), ); } Future _getUpdateInfo( - BuildContext context, - String targetVersion, - ValueNotifier description, - ValueNotifier downloadUrl) async { + BuildContext context, + String targetVersion, + ValueNotifier description, + ValueNotifier downloadUrl, + ) async { try { final r = await Api.getAppReleaseDataByVersionName(targetVersion); description.value = r["body"]; @@ -193,19 +190,19 @@ class UpgradeDialogUI extends HookConsumerWidget { } Future _doUpgrade( - BuildContext context, - AppGlobalState appState, - ValueNotifier isUpgrading, - AppGlobalModel appModel, - ValueNotifier downloadUrl, - ValueNotifier description, - ValueNotifier isUsingDiversion, - ValueNotifier progress) async { + BuildContext context, + AppGlobalState appState, + ValueNotifier isUpgrading, + AppGlobalModel appModel, + ValueNotifier downloadUrl, + ValueNotifier description, + ValueNotifier isUsingDiversion, + ValueNotifier progress, + ) async { if (ConstConf.isMSE) { launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1"); await Future.delayed(const Duration(seconds: 3)); - if (ConstConf.appVersionCode < - (appState.networkVersionData?.minVersionCode ?? 0)) { + if (ConstConf.appVersionCode < (appState.networkVersionData?.minVersionCode ?? 0)) { exit(0); } if (!context.mounted) return; @@ -221,10 +218,10 @@ class UpgradeDialogUI extends HookConsumerWidget { final dio = Dio(); if (diversionDownloadUrl.isNotEmpty) { try { - final resp = await dio.head(diversionDownloadUrl, - options: Options( - sendTimeout: const Duration(seconds: 10), - receiveTimeout: const Duration(seconds: 10))); + final resp = await dio.head( + diversionDownloadUrl, + options: Options(sendTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10)), + ); if (resp.statusCode == 200) { isUsingDiversion.value = true; url = diversionDownloadUrl; @@ -236,10 +233,13 @@ class UpgradeDialogUI extends HookConsumerWidget { dPrint("diversionDownloadUrl err:$e"); } } - await dio.download(url, fileName, - onReceiveProgress: (int count, int total) { - progress.value = (count / total) * 100; - }); + await dio.download( + url, + fileName, + onReceiveProgress: (int count, int total) { + progress.value = (count / total) * 100; + }, + ); } catch (_) { isUpgrading.value = false; progress.value = 0; @@ -249,11 +249,7 @@ class UpgradeDialogUI extends HookConsumerWidget { } try { - final r = await (Process.run( - SystemHelper.powershellPath, ["start", fileName, "/SILENT"])); - if (r.stderr.toString().isNotEmpty) { - throw r.stderr; - } + await win32.startProcess(program: fileName, args: ["/SILENT"]); exit(0); } catch (_) { isUpgrading.value = false; diff --git a/lib/ui/tools/dialogs/hosts_booster_dialog_ui.dart b/lib/ui/tools/dialogs/hosts_booster_dialog_ui.dart index 3177476..a2781bb 100644 --- a/lib/ui/tools/dialogs/hosts_booster_dialog_ui.dart +++ b/lib/ui/tools/dialogs/hosts_booster_dialog_ui.dart @@ -7,7 +7,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:starcitizen_doctor/api/analytics.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/win32_api.dart' as win32; import 'package:starcitizen_doctor/common/utils/async.dart'; +import 'package:starcitizen_doctor/common/utils/base_utils.dart'; import 'package:starcitizen_doctor/common/utils/log.dart'; class HostsBoosterDialogUI extends HookConsumerWidget { @@ -15,12 +17,8 @@ class HostsBoosterDialogUI extends HookConsumerWidget { static final _hostsMap = { "Recaptcha": ["www.recaptcha.net", "recaptcha.net"], - S.current.tools_hosts_info_rsi_official_website: [ - "robertsspaceindustries.com" - ], - S.current.tools_hosts_info_rsi_customer_service: [ - "support.robertsspaceindustries.com" - ], + S.current.tools_hosts_info_rsi_official_website: ["robertsspaceindustries.com"], + S.current.tools_hosts_info_rsi_customer_service: ["support.robertsspaceindustries.com"], }; @override @@ -31,9 +29,7 @@ class HostsBoosterDialogUI extends HookConsumerWidget { doHost(BuildContext context) async { if (workingMap.value.isEmpty) { - final hasTrue = - checkedMap.value.values.where((element) => element).firstOrNull != - null; + final hasTrue = checkedMap.value.values.where((element) => element).firstOrNull != null; if (!hasTrue) { for (var k in _hostsMap.keys) { checkedMap.value[k] = true; @@ -59,32 +55,29 @@ class HostsBoosterDialogUI extends HookConsumerWidget { }, []); return ContentDialog( - constraints: - BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55), title: Row( children: [ IconButton( - icon: const Icon( - FluentIcons.back, - size: 22, - ), - onPressed: - workingText.value.isEmpty ? Navigator.of(context).pop : null), + icon: const Icon(FluentIcons.back, size: 22), + onPressed: workingText.value.isEmpty ? Navigator.of(context).pop : null, + ), const SizedBox(width: 12), Text(S.current.tools_hosts_info_hosts_acceleration), const Spacer(), Button( - onPressed: () => _openHostsFile(context), - child: Padding( - padding: const EdgeInsets.all(3), - child: Row( - children: [ - const Icon(FluentIcons.open_file), - const SizedBox(width: 6), - Text(S.current.tools_hosts_info_open_hosts_file), - ], - ), - )) + onPressed: () => _openHostsFile(context), + child: Padding( + padding: const EdgeInsets.all(3), + child: Row( + children: [ + const Icon(FluentIcons.open_file), + const SizedBox(width: 6), + Text(S.current.tools_hosts_info_open_hosts_file), + ], + ), + ), + ), ], ), content: AnimatedSize( @@ -111,10 +104,8 @@ class HostsBoosterDialogUI extends HookConsumerWidget { padding: const EdgeInsets.all(6), physics: const NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) { - final isEnable = - checkedMap.value[_hostsMap.keys.elementAt(index)] ?? false; - final workingState = - workingMap.value[_hostsMap.keys.elementAt(index)]; + final isEnable = checkedMap.value[_hostsMap.keys.elementAt(index)] ?? false; + final workingState = workingMap.value[_hostsMap.keys.elementAt(index)]; return Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 12), @@ -124,23 +115,16 @@ class HostsBoosterDialogUI extends HookConsumerWidget { ), child: Row( children: [ - if (workingState == null) - Icon(FontAwesomeIcons.xmark, - size: 24, color: Colors.red), - if (workingState == 0) - const SizedBox( - width: 24, height: 24, child: ProgressRing()), - if (workingState == 1) - Icon(FontAwesomeIcons.check, - size: 24, color: Colors.green), + if (workingState == null) Icon(FontAwesomeIcons.xmark, size: 24, color: Colors.red), + if (workingState == 0) const SizedBox(width: 24, height: 24, child: ProgressRing()), + if (workingState == 1) Icon(FontAwesomeIcons.check, size: 24, color: Colors.green), const SizedBox(width: 24), const SizedBox(width: 12), Text(_hostsMap.keys.elementAt(index)), const Spacer(), ToggleSwitch( onChanged: (value) { - checkedMap.value[_hostsMap.keys.elementAt(index)] = - value; + checkedMap.value[_hostsMap.keys.elementAt(index)] = value; checkedMap.value = Map.from(checkedMap.value); }, checked: isEnable, @@ -156,8 +140,7 @@ class HostsBoosterDialogUI extends HookConsumerWidget { height: 86, child: Column( children: [ - const SizedBox( - height: 42, width: 42, child: ProgressRing()), + const SizedBox(height: 42, width: 42, child: ProgressRing()), const SizedBox(height: 12), Text(workingText.value), ], @@ -169,10 +152,8 @@ class HostsBoosterDialogUI extends HookConsumerWidget { child: FilledButton( onPressed: () => doHost(context), child: Padding( - padding: const EdgeInsets.only( - top: 3, bottom: 3, left: 12, right: 12), - child: Text( - S.current.tools_hosts_action_one_click_acceleration), + padding: const EdgeInsets.only(top: 3, bottom: 3, left: 12, right: 12), + child: Text(S.current.tools_hosts_action_one_click_acceleration), ), ), ), @@ -183,17 +164,19 @@ class HostsBoosterDialogUI extends HookConsumerWidget { } Future _openHostsFile(BuildContext context) async { - // 使用管理员权限调用记事本${S.current.tools_hosts_info_open_hosts_file} - Process.run(SystemHelper.powershellPath, [ - "-Command", - "Start-Process notepad.exe -Verb runAs -ArgumentList ${SystemHelper.getHostsFilePath()}" - // ignore: use_build_context_synchronously - ]).unwrap(context: context); + // 使用管理员权限调用记事本 + try { + await win32.runAsAdmin(program: "notepad.exe", args: SystemHelper.getHostsFilePath()); + } catch (e) { + if (!context.mounted) return; + showToast(context, "Failed to open hosts file: $e"); + } } Future> _doCheckDns( - ValueNotifier> workingMap, - ValueNotifier> checkedMap) async { + ValueNotifier> workingMap, + ValueNotifier> checkedMap, + ) async { Map result = {}; final trueLen = checkedMap.value.values.where((element) => element).length; if (trueLen == 0) { @@ -207,36 +190,37 @@ class HostsBoosterDialogUI extends HookConsumerWidget { } workingMap.value[siteName] = 0; workingMap.value = Map.from(workingMap.value); - RSHttp.dnsLookupIps(siteHost).then((ips) async { - int tryCount = ips.length; - try { - for (var ip in ips) { - final resp = - await RSHttp.head("https://$siteHost", withIpAddress: ip); - dPrint( - "[HostsBooster] host== $siteHost ip== $ip resp== ${resp.headers}"); - if (resp.headers.isNotEmpty) { - if (result[siteName] == null) { - result[siteName] = ip; - workingMap.value[siteName] = 1; - workingMap.value = Map.from(workingMap.value); - break; + RSHttp.dnsLookupIps(siteHost).then( + (ips) async { + int tryCount = ips.length; + try { + for (var ip in ips) { + final resp = await RSHttp.head("https://$siteHost", withIpAddress: ip); + dPrint("[HostsBooster] host== $siteHost ip== $ip resp== ${resp.headers}"); + if (resp.headers.isNotEmpty) { + if (result[siteName] == null) { + result[siteName] = ip; + workingMap.value[siteName] = 1; + workingMap.value = Map.from(workingMap.value); + break; + } } } + } catch (e) { + tryCount--; + if (tryCount == 0) { + workingMap.value[siteName] = null; + workingMap.value = Map.from(workingMap.value); + result[siteName] = ""; + } } - } catch (e) { - tryCount--; - if (tryCount == 0) { - workingMap.value[siteName] = null; - workingMap.value = Map.from(workingMap.value); - result[siteName] = ""; - } - } - }, onError: (e) { - workingMap.value[siteName] = null; - workingMap.value = Map.from(workingMap.value); - result[siteName] = ""; - }); + }, + onError: (e) { + workingMap.value[siteName] = null; + workingMap.value = Map.from(workingMap.value); + result[siteName] = ""; + }, + ); } while (true) { await Future.delayed(const Duration(milliseconds: 100)); @@ -265,16 +249,17 @@ class HostsBoosterDialogUI extends HookConsumerWidget { final domains = _hostsMap[kv.key] ?? []; for (var domain in domains) { if (kv.value != "") { - newHostsFileLines - .add("${kv.value} $domain #StarCitizenToolBox"); + newHostsFileLines.add("${kv.value} $domain #StarCitizenToolBox"); } } } await hostsFile.writeAsString(newHostsFileLines.join("\n"), flush: true); } - Future _readHostsState(ValueNotifier> workingMap, - ValueNotifier> checkedMap) async { + Future _readHostsState( + ValueNotifier> workingMap, + ValueNotifier> checkedMap, + ) async { workingMap.value.clear(); final hostsFile = File(SystemHelper.getHostsFilePath()); final hostsFileString = await hostsFile.readAsString(); diff --git a/rust/Cargo.toml b/rust/Cargo.toml index cad68f5..d0a23ab 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -54,7 +54,10 @@ windows = { version = "0.62.2", features = [ "Win32_UI_Shell", "Win32_System_Com", "Win32_System_Ole", - "Win32_System_Variant" + "Win32_System_Variant", + "Win32_Security", + "Win32_System_IO", + "Win32_System_Ioctl" ] } win32job = "2.0.3" wmi = "0.15" diff --git a/rust/src/api/win32_api.rs b/rust/src/api/win32_api.rs index 8a1b05f..09c4b3e 100644 --- a/rust/src/api/win32_api.rs +++ b/rust/src/api/win32_api.rs @@ -527,4 +527,392 @@ fn get_process_path(pid: u32) -> Option { pub fn get_process_list_by_name(process_name: &str) -> anyhow::Result> { println!("get_process_list_by_name (unix): {}", process_name); Ok(Vec::new()) +} + +/// Kill processes by name +#[cfg(target_os = "windows")] +pub fn kill_process_by_name(process_name: &str) -> anyhow::Result { + use windows::Win32::Foundation::CloseHandle; + use windows::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE}; + + let processes = get_process_list_by_name(process_name)?; + let mut killed_count = 0u32; + + for process in processes { + unsafe { + if let Ok(h_process) = OpenProcess(PROCESS_TERMINATE, false, process.pid) { + if !h_process.is_invalid() { + if TerminateProcess(h_process, 0).is_ok() { + killed_count += 1; + } + let _ = CloseHandle(h_process); + } + } + } + } + + Ok(killed_count) +} + +#[cfg(not(target_os = "windows"))] +pub fn kill_process_by_name(process_name: &str) -> anyhow::Result { + println!("kill_process_by_name (unix): {}", process_name); + Ok(0) +} + +/// Get disk physical sector size for performance +#[cfg(target_os = "windows")] +pub fn get_disk_physical_sector_size(drive_letter: &str) -> anyhow::Result { + use windows::Win32::Storage::FileSystem::{ + CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }; + use windows::Win32::System::IO::DeviceIoControl; + use windows::Win32::System::Ioctl::IOCTL_STORAGE_QUERY_PROPERTY; + use windows::Win32::Foundation::CloseHandle; + use windows::core::HSTRING; + use std::mem; + + // STORAGE_PROPERTY_QUERY structure + #[repr(C)] + struct StoragePropertyQuery { + property_id: u32, + query_type: u32, + additional_parameters: [u8; 1], + } + + // STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR structure + #[repr(C)] + struct StorageAccessAlignmentDescriptor { + version: u32, + size: u32, + bytes_per_cache_line: u32, + bytes_offset_for_cache_alignment: u32, + bytes_per_logical_sector: u32, + bytes_per_physical_sector: u32, + bytes_offset_for_sector_alignment: u32, + } + + let drive_path = format!(r"\\.\{}:", drive_letter.chars().next().unwrap_or('C')); + let drive_path_w = HSTRING::from(&drive_path); + + unsafe { + let handle = CreateFileW( + &drive_path_w, + 0, // No access needed, just query + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + windows::Win32::Storage::FileSystem::FILE_FLAGS_AND_ATTRIBUTES(0), + None, + )?; + + if handle.is_invalid() { + return Err(anyhow::anyhow!("Failed to open drive")); + } + + // StorageAccessAlignmentProperty = 6 + let query = StoragePropertyQuery { + property_id: 6, + query_type: 0, // PropertyStandardQuery + additional_parameters: [0], + }; + + let mut descriptor: StorageAccessAlignmentDescriptor = mem::zeroed(); + let mut bytes_returned: u32 = 0; + + let result = DeviceIoControl( + handle, + IOCTL_STORAGE_QUERY_PROPERTY, + Some(&query as *const _ as *const std::ffi::c_void), + mem::size_of::() as u32, + Some(&mut descriptor as *mut _ as *mut std::ffi::c_void), + mem::size_of::() as u32, + Some(&mut bytes_returned), + None, + ); + + let _ = CloseHandle(handle); + + if result.is_ok() { + Ok(descriptor.bytes_per_physical_sector) + } else { + Err(anyhow::anyhow!("DeviceIoControl failed")) + } + } +} + +#[cfg(not(target_os = "windows"))] +pub fn get_disk_physical_sector_size(drive_letter: &str) -> anyhow::Result { + println!("get_disk_physical_sector_size (unix): {}", drive_letter); + Ok(0) +} + +/// Create a desktop shortcut +#[cfg(target_os = "windows")] +pub fn create_desktop_shortcut(target_path: &str, shortcut_name: &str) -> anyhow::Result<()> { + use windows::core::{HSTRING, Interface, BSTR}; + use windows::Win32::System::Com::{ + CoCreateInstance, CoInitializeEx, CoUninitialize, + CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED, + }; + use windows::Win32::UI::Shell::{IShellLinkW, ShellLink, SHGetKnownFolderPath, FOLDERID_Desktop}; + use windows::Win32::System::Com::IPersistFile; + + unsafe { + let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED); + + let result = (|| -> anyhow::Result<()> { + // Get desktop path + let desktop_path = SHGetKnownFolderPath(&FOLDERID_Desktop, windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0), None)?; + let desktop_str = desktop_path.to_string()?; + + // Create ShellLink instance + let shell_link: IShellLinkW = CoCreateInstance( + &ShellLink, + None, + CLSCTX_INPROC_SERVER, + )?; + + // Set target path + let target_w = HSTRING::from(target_path); + shell_link.SetPath(&target_w)?; + + // Get IPersistFile interface + let persist_file: IPersistFile = shell_link.cast()?; + + // Create shortcut file path + let shortcut_path = format!("{}\\{}", desktop_str, shortcut_name); + let shortcut_w = BSTR::from(&shortcut_path); + + // Save shortcut + persist_file.Save(windows::core::PCWSTR(shortcut_w.as_ptr()), true)?; + + Ok(()) + })(); + + CoUninitialize(); + result + } +} + +#[cfg(not(target_os = "windows"))] +pub fn create_desktop_shortcut(target_path: &str, shortcut_name: &str) -> anyhow::Result<()> { + println!("create_desktop_shortcut (unix): {} -> {}", target_path, shortcut_name); + Ok(()) +} + +/// Run a program with admin privileges (UAC) +#[cfg(target_os = "windows")] +pub fn run_as_admin(program: &str, args: &str) -> anyhow::Result<()> { + use windows::core::HSTRING; + use windows::Win32::UI::Shell::ShellExecuteW; + use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL; + + let operation = HSTRING::from("runas"); + let file = HSTRING::from(program); + let parameters = HSTRING::from(args); + + unsafe { + let result = ShellExecuteW( + None, + &operation, + &file, + ¶meters, + None, + SW_SHOWNORMAL, + ); + + // ShellExecuteW returns a value > 32 on success + if result.0 as usize > 32 { + Ok(()) + } else { + Err(anyhow::anyhow!("ShellExecuteW failed with code: {}", result.0 as usize)) + } + } +} + +#[cfg(not(target_os = "windows"))] +pub fn run_as_admin(program: &str, args: &str) -> anyhow::Result<()> { + println!("run_as_admin (unix): {} {}", program, args); + Ok(()) +} + +/// Start a program (without waiting) +#[cfg(target_os = "windows")] +pub fn start_process(program: &str, args: Vec) -> anyhow::Result<()> { + use std::process::Command; + + Command::new(program) + .args(&args) + .spawn()?; + + Ok(()) +} + +#[cfg(not(target_os = "windows"))] +pub fn start_process(program: &str, args: Vec) -> anyhow::Result<()> { + println!("start_process (unix): {} {:?}", program, args); + Ok(()) +} + +// ============== NVME Patch Functions ============== + +const NVME_REGISTRY_PATH: &str = r"SYSTEM\CurrentControlSet\Services\stornvme\Parameters\Device"; +const NVME_VALUE_NAME: &str = "ForcedPhysicalSectorSizeInBytes"; + +/// Check if NVME patch is applied +#[cfg(target_os = "windows")] +pub fn check_nvme_patch_status() -> anyhow::Result { + use windows::Win32::System::Registry::{ + RegOpenKeyExW, RegQueryValueExW, RegCloseKey, + HKEY_LOCAL_MACHINE, KEY_READ, REG_VALUE_TYPE, + }; + use windows::core::{HSTRING, PCWSTR}; + + unsafe { + let path = HSTRING::from(NVME_REGISTRY_PATH); + let mut hkey = std::mem::zeroed(); + + // Try to open the registry key + if RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + PCWSTR(path.as_ptr()), + Some(0), + KEY_READ, + &mut hkey, + ).is_err() { + return Ok(false); + } + + // Query the value + let value_name = HSTRING::from(NVME_VALUE_NAME); + let mut buffer = [0u8; 1024]; + let mut size = buffer.len() as u32; + let mut value_type = REG_VALUE_TYPE::default(); + + let result = if RegQueryValueExW( + hkey, + PCWSTR(value_name.as_ptr()), + None, + Some(&mut value_type), + Some(buffer.as_mut_ptr()), + Some(&mut size), + ).is_ok() { + // Check if the value contains "* 4095" + // REG_MULTI_SZ is stored as null-terminated wide strings + let data = String::from_utf16_lossy( + std::slice::from_raw_parts(buffer.as_ptr() as *const u16, size as usize / 2) + ); + data.contains("* 4095") + } else { + false + }; + + let _ = RegCloseKey(hkey); + Ok(result) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn check_nvme_patch_status() -> anyhow::Result { + Ok(false) +} + +/// Add NVME patch to registry +#[cfg(target_os = "windows")] +pub fn add_nvme_patch() -> anyhow::Result<()> { + use windows::Win32::System::Registry::{ + RegOpenKeyExW, RegSetValueExW, RegCloseKey, + HKEY_LOCAL_MACHINE, KEY_WRITE, REG_MULTI_SZ, + }; + use windows::core::{HSTRING, PCWSTR}; + + unsafe { + let path = HSTRING::from(NVME_REGISTRY_PATH); + let mut hkey = std::mem::zeroed(); + + // Open the registry key with write access + let open_result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + PCWSTR(path.as_ptr()), + Some(0), + KEY_WRITE, + &mut hkey, + ); + if open_result.is_err() { + return Err(anyhow::anyhow!("Failed to open registry key: {:?}", open_result)); + } + + // Prepare the value: "* 4095" as REG_MULTI_SZ (double null terminated) + let value_str = "* 4095\0\0"; + let value_wide: Vec = value_str.encode_utf16().collect(); + let value_name = HSTRING::from(NVME_VALUE_NAME); + + let result = RegSetValueExW( + hkey, + PCWSTR(value_name.as_ptr()), + Some(0), + REG_MULTI_SZ, + Some(std::slice::from_raw_parts( + value_wide.as_ptr() as *const u8, + value_wide.len() * 2, + )), + ); + + let _ = RegCloseKey(hkey); + + if result.is_err() { + return Err(anyhow::anyhow!("Failed to set registry value: {:?}", result)); + } + Ok(()) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn add_nvme_patch() -> anyhow::Result<()> { + Err(anyhow::anyhow!("NVME patch is only supported on Windows")) +} + +/// Remove NVME patch from registry +#[cfg(target_os = "windows")] +pub fn remove_nvme_patch() -> anyhow::Result<()> { + use windows::Win32::System::Registry::{ + RegOpenKeyExW, RegDeleteValueW, RegCloseKey, + HKEY_LOCAL_MACHINE, KEY_WRITE, + }; + use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND; + use windows::core::{HSTRING, PCWSTR}; + + unsafe { + let path = HSTRING::from(NVME_REGISTRY_PATH); + let mut hkey = std::mem::zeroed(); + + // Open the registry key with write access + let open_result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + PCWSTR(path.as_ptr()), + Some(0), + KEY_WRITE, + &mut hkey, + ); + if open_result.is_err() { + return Err(anyhow::anyhow!("Failed to open registry key: {:?}", open_result)); + } + + let value_name = HSTRING::from(NVME_VALUE_NAME); + + let result = RegDeleteValueW( + hkey, + PCWSTR(value_name.as_ptr()), + ); + + let _ = RegCloseKey(hkey); + + // It's OK if the value doesn't exist + if result.is_err() && result != ERROR_FILE_NOT_FOUND { + return Err(anyhow::anyhow!("Failed to delete registry value: {:?}", result)); + } + + Ok(()) + } } \ No newline at end of file diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 9540266..e4d586e 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1317751362; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1161621087; // Section: executor @@ -45,6 +45,48 @@ flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs +fn wire__crate__api__win32_api__add_nvme_patch_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "add_nvme_patch", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::win32_api::add_nvme_patch()?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__win32_api__check_nvme_patch_status_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "check_nvme_patch_status", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::win32_api::check_nvme_patch_status()?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__ort_api__clear_all_models_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ) { @@ -66,6 +108,34 @@ fn wire__crate__api__ort_api__clear_all_models_impl( }, ) } +fn wire__crate__api__win32_api__create_desktop_shortcut_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + target_path: impl CstDecode, + shortcut_name: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "create_desktop_shortcut", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_target_path = target_path.cst_decode(); + let api_shortcut_name = shortcut_name.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::win32_api::create_desktop_shortcut( + &api_target_path, + &api_shortcut_name, + )?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__http_api__dns_lookup_ips_impl( port_: flutter_rust_bridge::for_generated::MessagePort, host: impl CstDecode, @@ -156,6 +226,31 @@ fn wire__crate__api__http_api__fetch_impl( }, ) } +fn wire__crate__api__win32_api__get_disk_physical_sector_size_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + drive_letter: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_disk_physical_sector_size", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_drive_letter = drive_letter.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::win32_api::get_disk_physical_sector_size( + &api_drive_letter, + )?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__http_api__get_faster_url_impl( port_: flutter_rust_bridge::for_generated::MessagePort, urls: impl CstDecode>, @@ -341,6 +436,30 @@ fn wire__crate__api__win32_api__get_system_memory_size_gb_impl( }, ) } +fn wire__crate__api__win32_api__kill_process_by_name_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + process_name: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "kill_process_by_name", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_process_name = process_name.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::win32_api::kill_process_by_name(&api_process_name)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__ort_api__load_translation_model_impl( port_: flutter_rust_bridge::for_generated::MessagePort, model_path: impl CstDecode, @@ -546,6 +665,27 @@ fn wire__crate__api__unp4k_api__p4k_open_impl( }, ) } +fn wire__crate__api__win32_api__remove_nvme_patch_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "remove_nvme_patch", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::win32_api::remove_nvme_patch()?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__win32_api__resolve_shortcut_impl( port_: flutter_rust_bridge::for_generated::MessagePort, lnk_path: impl CstDecode, @@ -599,6 +739,32 @@ fn wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js_impl( }, ) } +fn wire__crate__api__win32_api__run_as_admin_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + program: impl CstDecode, + args: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "run_as_admin", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_program = program.cst_decode(); + let api_args = args.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::win32_api::run_as_admin(&api_program, &api_args)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__win32_api__send_notify_impl( port_: flutter_rust_bridge::for_generated::MessagePort, summary: impl CstDecode>, @@ -723,6 +889,32 @@ fn wire__crate__api__rs_process__start_impl( }, ) } +fn wire__crate__api__win32_api__start_process_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + program: impl CstDecode, + args: impl CstDecode>, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "start_process", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_program = program.cst_decode(); + let api_args = args.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::win32_api::start_process(&api_program, api_args)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__ort_api__translate_text_impl( port_: flutter_rust_bridge::for_generated::MessagePort, model_key: impl CstDecode, @@ -2893,6 +3085,20 @@ mod io { } } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__add_nvme_patch( + port_: i64, + ) { + wire__crate__api__win32_api__add_nvme_patch_impl(port_) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__check_nvme_patch_status( + port_: i64, + ) { + wire__crate__api__win32_api__check_nvme_patch_status_impl(port_) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__clear_all_models( port_: i64, @@ -2900,6 +3106,15 @@ mod io { wire__crate__api__ort_api__clear_all_models_impl(port_) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__create_desktop_shortcut( + port_: i64, + target_path: *mut wire_cst_list_prim_u_8_strict, + shortcut_name: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__create_desktop_shortcut_impl(port_, target_path, shortcut_name) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__dns_lookup_ips( port_: i64, @@ -2937,6 +3152,14 @@ mod io { ) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_disk_physical_sector_size( + port_: i64, + drive_letter: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__get_disk_physical_sector_size_impl(port_, drive_letter) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__get_faster_url( port_: i64, @@ -2998,6 +3221,14 @@ mod io { wire__crate__api__win32_api__get_system_memory_size_gb_impl(port_) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__kill_process_by_name( + port_: i64, + process_name: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__kill_process_by_name_impl(port_, process_name) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__load_translation_model( port_: i64, @@ -3068,6 +3299,13 @@ mod io { wire__crate__api__unp4k_api__p4k_open_impl(port_, p4k_path) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__remove_nvme_patch( + port_: i64, + ) { + wire__crate__api__win32_api__remove_nvme_patch_impl(port_) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__resolve_shortcut( port_: i64, @@ -3085,6 +3323,15 @@ mod io { wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js_impl(port_, that, content) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__run_as_admin( + port_: i64, + program: *mut wire_cst_list_prim_u_8_strict, + args: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__run_as_admin_impl(port_, program, args) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__send_notify( port_: i64, @@ -3129,6 +3376,15 @@ mod io { ) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__start_process( + port_: i64, + program: *mut wire_cst_list_prim_u_8_strict, + args: *mut wire_cst_list_String, + ) { + wire__crate__api__win32_api__start_process_impl(port_, program, args) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__translate_text( port_: i64,