feat: Migrate more PowerShell calls to Rust implementation

This commit is contained in:
xkeyC 2025-12-05 11:06:54 +08:00
parent f6676ed3d8
commit 62b8718dbd
16 changed files with 1377 additions and 357 deletions

View File

@ -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 {

View File

@ -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<void> 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<String, String> 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<bool> 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<String> 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<bool> 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;
}
}

View File

@ -65,6 +65,55 @@ Future<List<ProcessInfo>> getProcessListByName({required String processName}) =>
processName: processName,
);
/// Kill processes by name
Future<int> killProcessByName({required String processName}) => RustLib
.instance
.api
.crateApiWin32ApiKillProcessByName(processName: processName);
/// Get disk physical sector size for performance
Future<int> getDiskPhysicalSectorSize({required String driveLetter}) => RustLib
.instance
.api
.crateApiWin32ApiGetDiskPhysicalSectorSize(driveLetter: driveLetter);
/// Create a desktop shortcut
Future<void> createDesktopShortcut({
required String targetPath,
required String shortcutName,
}) => RustLib.instance.api.crateApiWin32ApiCreateDesktopShortcut(
targetPath: targetPath,
shortcutName: shortcutName,
);
/// Run a program with admin privileges (UAC)
Future<void> runAsAdmin({required String program, required String args}) =>
RustLib.instance.api.crateApiWin32ApiRunAsAdmin(
program: program,
args: args,
);
/// Start a program (without waiting)
Future<void> startProcess({
required String program,
required List<String> args,
}) => RustLib.instance.api.crateApiWin32ApiStartProcess(
program: program,
args: args,
);
/// Check if NVME patch is applied
Future<bool> checkNvmePatchStatus() =>
RustLib.instance.api.crateApiWin32ApiCheckNvmePatchStatus();
/// Add NVME patch to registry
Future<void> addNvmePatch() =>
RustLib.instance.api.crateApiWin32ApiAddNvmePatch();
/// Remove NVME patch from registry
Future<void> removeNvmePatch() =>
RustLib.instance.api.crateApiWin32ApiRemoveNvmePatch();
class ProcessInfo {
final int pid;
final String name;

View File

@ -71,7 +71,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
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<RustLibApi, RustLibApiImpl, RustLibWire> {
}
abstract class RustLibApi extends BaseApi {
Future<void> crateApiWin32ApiAddNvmePatch();
Future<bool> crateApiWin32ApiCheckNvmePatchStatus();
Future<void> crateApiOrtApiClearAllModels();
Future<void> crateApiWin32ApiCreateDesktopShortcut({
required String targetPath,
required String shortcutName,
});
Future<List<String>> crateApiHttpApiDnsLookupIps({required String host});
Future<List<String>> crateApiHttpApiDnsLookupTxt({required String host});
@ -97,6 +106,10 @@ abstract class RustLibApi extends BaseApi {
bool? withCustomDns,
});
Future<int> crateApiWin32ApiGetDiskPhysicalSectorSize({
required String driveLetter,
});
Future<String?> crateApiHttpApiGetFasterUrl({
required List<String> urls,
String? pathSuffix,
@ -122,6 +135,8 @@ abstract class RustLibApi extends BaseApi {
Future<BigInt> crateApiWin32ApiGetSystemMemorySizeGb();
Future<int> crateApiWin32ApiKillProcessByName({required String processName});
Future<void> crateApiOrtApiLoadTranslationModel({
required String modelPath,
required String modelKey,
@ -151,6 +166,8 @@ abstract class RustLibApi extends BaseApi {
Future<void> crateApiUnp4KApiP4KOpen({required String p4KPath});
Future<void> crateApiWin32ApiRemoveNvmePatch();
Future<String> crateApiWin32ApiResolveShortcut({required String lnkPath});
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs({
@ -158,6 +175,11 @@ abstract class RustLibApi extends BaseApi {
required List<int> content,
});
Future<void> crateApiWin32ApiRunAsAdmin({
required String program,
required String args,
});
Future<void> crateApiWin32ApiSendNotify({
String? summary,
String? body,
@ -179,6 +201,11 @@ abstract class RustLibApi extends BaseApi {
required String workingDirectory,
});
Future<void> crateApiWin32ApiStartProcess({
required String program,
required List<String> args,
});
Future<String> crateApiOrtApiTranslateText({
required String modelKey,
required String text,
@ -261,6 +288,50 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required super.portManager,
});
@override
Future<void> 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<bool> 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<void> 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<void> 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<List<String>> crateApiHttpApiDnsLookupIps({required String host}) {
return handler.executeNormal(
@ -384,6 +488,37 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
],
);
@override
Future<int> 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<String?> crateApiHttpApiGetFasterUrl({
required List<String> 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<int> 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<void> 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<void> 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<String> crateApiWin32ApiResolveShortcut({required String lnkPath}) {
return handler.executeNormal(
@ -883,6 +1067,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["that", "content"],
);
@override
Future<void> 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<void> crateApiWin32ApiSendNotify({
String? summary,
@ -1024,6 +1240,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["executable", "arguments", "workingDirectory", "streamSink"],
);
@override
Future<void> crateApiWin32ApiStartProcess({
required String program,
required List<String> 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<String> crateApiOrtApiTranslateText({
required String modelKey,

View File

@ -920,6 +920,30 @@ class RustLibWire implements BaseWire {
late final _store_dart_post_cobject = _store_dart_post_cobjectPtr
.asFunction<void Function(DartPostCObjectFnType)>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
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 Function(int)>();
void wire__crate__api__win32_api__create_desktop_shortcut(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> target_path,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
void wire__crate__api__http_api__dns_lookup_ips(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> host,
@ -1024,6 +1082,33 @@ class RustLibWire implements BaseWire {
)
>();
void wire__crate__api__win32_api__get_disk_physical_sector_size(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__http_api__get_faster_url(
int port_,
ffi.Pointer<wire_cst_list_String> urls,
@ -1189,6 +1274,33 @@ class RustLibWire implements BaseWire {
_wire__crate__api__win32_api__get_system_memory_size_gbPtr
.asFunction<void Function(int)>();
void wire__crate__api__win32_api__kill_process_by_name(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
)
>
>(
'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<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__ort_api__load_translation_model(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_path,
@ -1380,6 +1492,18 @@ class RustLibWire implements BaseWire {
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
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<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'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 Function(int)>();
void wire__crate__api__win32_api__resolve_shortcut(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> lnk_path,
@ -1438,6 +1562,34 @@ class RustLibWire implements BaseWire {
)
>();
void wire__crate__api__win32_api__run_as_admin(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> program,
ffi.Pointer<wire_cst_list_prim_u_8_strict> 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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>('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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
void wire__crate__api__win32_api__send_notify(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> summary,
@ -1569,6 +1721,34 @@ class RustLibWire implements BaseWire {
)
>();
void wire__crate__api__win32_api__start_process(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> program,
ffi.Pointer<wire_cst_list_String> 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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
)
>
>('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<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
)
>();
void wire__crate__api__ort_api__translate_text(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_key,

View File

@ -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<void> doFix(
// ignore: avoid_build_context_in_providers
BuildContext context,
MapEntry<String, String> item) async {
final checkResult =
List<MapEntry<String, String>>.from(state.checkResult ?? []);
// ignore: avoid_build_context_in_providers
BuildContext context,
MapEntry<String, String> item,
) async {
final checkResult = List<MapEntry<String, String>>.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<void> 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<MapEntry<String, String>> checkResult) async {
Future _checkGameRunningLog(
BuildContext context,
String scInstalledPath,
List<MapEntry<String, String>> 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<MapEntry<String, String>> checkResult) async {
Future _checkEAC(BuildContext context, String scInstalledPath, List<MapEntry<String, String>> 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<MapEntry<String, String>> checkResult) async {
Future _checkPreInstall(
BuildContext context,
String scInstalledPath,
List<MapEntry<String, String>> 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) {

View File

@ -42,7 +42,7 @@ final class HomeGameDoctorUIModelProvider
}
String _$homeGameDoctorUIModelHash() =>
r'7035b501860e9d8c3fdfb91370311760120af115';
r'8226e88797ecc68920f684a1dc072a340bca783a';
abstract class _$HomeGameDoctorUIModel extends $Notifier<HomeGameDoctorState> {
HomeGameDoctorState build();

View File

@ -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");

View File

@ -41,7 +41,7 @@ final class HomeUIModelProvider
}
}
String _$homeUIModelHash() => r'cc795e27213d02993459dd711de4a897c8491575';
String _$homeUIModelHash() => r'c3affcfc99a0cd7e3587cc58f02dbdc24c4f8ae7';
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
HomeUIModelState build();

View File

@ -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<void> 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<void> 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<void> 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<void> 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<void> 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);
}
}

View File

@ -41,7 +41,7 @@ final class SettingsUIModelProvider
}
}
String _$settingsUIModelHash() => r'72947d5ed36290df865cb010b056dc632f5dccec';
String _$settingsUIModelHash() => r'd34b1a2fac69d10f560d9a2e1a7431dd5a7954ca';
abstract class _$SettingsUIModel extends $Notifier<SettingsUIState> {
SettingsUIState build();

View File

@ -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<void> _getUpdateInfo(
BuildContext context,
String targetVersion,
ValueNotifier<String?> description,
ValueNotifier<String> downloadUrl) async {
BuildContext context,
String targetVersion,
ValueNotifier<String?> description,
ValueNotifier<String> downloadUrl,
) async {
try {
final r = await Api.getAppReleaseDataByVersionName(targetVersion);
description.value = r["body"];
@ -193,19 +190,19 @@ class UpgradeDialogUI extends HookConsumerWidget {
}
Future<void> _doUpgrade(
BuildContext context,
AppGlobalState appState,
ValueNotifier<bool> isUpgrading,
AppGlobalModel appModel,
ValueNotifier<String> downloadUrl,
ValueNotifier<String?> description,
ValueNotifier<bool> isUsingDiversion,
ValueNotifier<double> progress) async {
BuildContext context,
AppGlobalState appState,
ValueNotifier<bool> isUpgrading,
AppGlobalModel appModel,
ValueNotifier<String> downloadUrl,
ValueNotifier<String?> description,
ValueNotifier<bool> isUsingDiversion,
ValueNotifier<double> 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;

View File

@ -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<void> _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<Map<String, String>> _doCheckDns(
ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap) async {
ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap,
) async {
Map<String, String> 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] ?? <String>[];
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<void> _readHostsState(ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap) async {
Future<void> _readHostsState(
ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap,
) async {
workingMap.value.clear();
final hostsFile = File(SystemHelper.getHostsFilePath());
final hostsFileString = await hostsFile.readAsString();

View File

@ -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"

View File

@ -527,4 +527,392 @@ fn get_process_path(pid: u32) -> Option<String> {
pub fn get_process_list_by_name(process_name: &str) -> anyhow::Result<Vec<ProcessInfo>> {
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<u32> {
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<u32> {
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<u32> {
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::<StoragePropertyQuery>() as u32,
Some(&mut descriptor as *mut _ as *mut std::ffi::c_void),
mem::size_of::<StorageAccessAlignmentDescriptor>() 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<u32> {
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,
&parameters,
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<String>) -> 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<String>) -> 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<bool> {
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<bool> {
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<u16> = 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(())
}
}

View File

@ -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::DcoCodec, _, _>(
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::DcoCodec, _, _>(
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<String>,
shortcut_name: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
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<String>,
@ -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<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
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<Vec<String>>,
@ -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<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
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<String>,
@ -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::DcoCodec, _, _>(
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<String>,
@ -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<String>,
args: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
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<Option<String>>,
@ -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<String>,
args: impl CstDecode<Vec<String>>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
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<String>,
@ -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,