diff --git a/build.rs b/build.rs index 47526d639a..d332a43a22 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,11 @@ #[cfg(windows)] fn build_windows() { let file = "src/platform/windows.cc"; - cc::Build::new().file(file).compile("windows"); + let file2 = "src/platform/windows_delete_test_cert.cc"; + cc::Build::new().file(file).file(file2).compile("windows"); println!("cargo:rustc-link-lib=WtsApi32"); println!("cargo:rerun-if-changed={}", file); + println!("cargo:rerun-if-changed={}", file2); } #[cfg(target_os = "macos")] diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 8840fa474a..7011e722ef 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -137,11 +137,13 @@ class _PeerTabPageState extends State Widget _createSwitchBar(BuildContext context) { final model = Provider.of(context); - - return ListView( + var counter = -1; + return ReorderableListView( + buildDefaultDragHandles: false, + onReorder: model.reorder, scrollDirection: Axis.horizontal, physics: NeverScrollableScrollPhysics(), - children: model.visibleIndexs.map((t) { + children: model.visibleEnabledOrderedIndexs.map((t) { final selected = model.currentTab == t; final color = selected ? MyTheme.tabbar(context).selectedTextColor @@ -155,43 +157,47 @@ class _PeerTabPageState extends State border: Border( bottom: BorderSide(width: 2, color: color!), )); - return Obx(() => Tooltip( - preferBelow: false, - message: model.tabTooltip(t), - onTriggered: isMobile ? mobileShowTabVisibilityMenu : null, - child: InkWell( - child: Container( - decoration: (hover.value - ? (selected ? decoBorder : deco) - : (selected ? decoBorder : null)), - child: Icon(model.tabIcon(t), color: color) - .paddingSymmetric(horizontal: 4), - ).paddingSymmetric(horizontal: 4), - onTap: () async { - await handleTabSelection(t); - await bind.setLocalFlutterOption( - k: 'peer-tab-index', v: t.toString()); - }, - onHover: (value) => hover.value = value, - ), - )); + counter += 1; + return ReorderableDragStartListener( + key: ValueKey(t), + index: counter, + child: Obx(() => Tooltip( + preferBelow: false, + message: model.tabTooltip(t), + onTriggered: isMobile ? mobileShowTabVisibilityMenu : null, + child: InkWell( + child: Container( + decoration: (hover.value + ? (selected ? decoBorder : deco) + : (selected ? decoBorder : null)), + child: Icon(model.tabIcon(t), color: color) + .paddingSymmetric(horizontal: 4), + ).paddingSymmetric(horizontal: 4), + onTap: () async { + await handleTabSelection(t); + await bind.setLocalFlutterOption( + k: PeerTabModel.kPeerTabIndex, v: t.toString()); + }, + onHover: (value) => hover.value = value, + ), + ))); }).toList()); } Widget _createPeersView() { final model = Provider.of(context); Widget child; - if (model.visibleIndexs.isEmpty) { + if (model.visibleEnabledOrderedIndexs.isEmpty) { child = visibleContextMenuListener(Row( children: [Expanded(child: InkWell())], )); } else { - if (model.visibleIndexs.contains(model.currentTab)) { + if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) { child = entries[model.currentTab].widget; } else { debugPrint("should not happen! currentTab not in visibleIndexs"); Future.delayed(Duration.zero, () { - model.setCurrentTab(model.indexs[0]); + model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]); }); child = entries[0].widget; } @@ -255,16 +261,17 @@ class _PeerTabPageState extends State void mobileShowTabVisibilityMenu() { final model = gFFI.peerTabModel; final items = List.empty(growable: true); - for (int i = 0; i < model.tabNames.length; i++) { + for (int i = 0; i < PeerTabModel.maxTabCount; i++) { + if (!model.isEnabled[i]) continue; items.add(PopupMenuItem( height: kMinInteractiveDimension * 0.8, - onTap: () => model.setTabVisible(i, !model.isVisible[i]), + onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]), child: Row( children: [ Checkbox( - value: model.isVisible[i], + value: model.isVisibleEnabled[i], onChanged: (_) { - model.setTabVisible(i, !model.isVisible[i]); + model.setTabVisible(i, !model.isVisibleEnabled[i]); if (Navigator.canPop(context)) { Navigator.pop(context); } @@ -314,16 +321,17 @@ class _PeerTabPageState extends State Widget visibleContextMenu(CancelFunc cancelFunc) { final model = Provider.of(context); - final menu = List.empty(growable: true); - for (int i = 0; i < model.tabNames.length; i++) { - menu.add(MenuEntrySwitch( + final menu = List.empty(growable: true); + for (int i = 0; i < model.orders.length; i++) { + int tabIndex = model.orders[i]; + if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue; + if (!model.isEnabled[tabIndex]) continue; + menu.add(MenuEntrySwitchSync( switchType: SwitchType.scheckbox, - text: model.tabTooltip(i), - getter: () async { - return model.isVisible[i]; - }, + text: model.tabTooltip(tabIndex), + currentValue: model.isVisibleEnabled[tabIndex], setter: (show) async { - model.setTabVisible(i, show); + model.setTabVisible(tabIndex, show); cancelFunc(); })); } @@ -434,7 +442,7 @@ class _PeerTabPageState extends State model.setMultiSelectionMode(false); showToast(translate('Successful')); }, - child: Icon(model.icons[PeerTabIndex.fav.index]), + child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]), ).marginOnly(left: isMobile ? 11 : 6), ); } @@ -455,7 +463,7 @@ class _PeerTabPageState extends State addPeersToAbDialog(peers); model.setMultiSelectionMode(false); }, - child: Icon(model.icons[PeerTabIndex.ab.index]), + child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]), ).marginOnly(left: isMobile ? 11 : 6), ); } @@ -563,7 +571,7 @@ class _PeerTabPageState extends State final screenWidth = MediaQuery.of(context).size.width; final leftIconSize = Theme.of(context).iconTheme.size ?? 24; final leftActionsSize = - (leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length; + (leftIconSize + (4 + 4) * 2) * model.visibleEnabledOrderedIndexs.length; final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2; final searchWidth = 120; final otherActionWidth = 18 + 10; diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 9833dcbcac..086d7a6222 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -568,6 +568,47 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { } } +// Compatible with MenuEntrySwitch, it uses value instead of getter +class MenuEntrySwitchSync extends MenuEntrySwitchBase { + final SwitchSetter setter; + final RxBool _curOption = false.obs; + + MenuEntrySwitchSync({ + required SwitchType switchType, + required String text, + required bool currentValue, + required this.setter, + Rx? textStyle, + EdgeInsets? padding, + dismissOnClicked = false, + RxBool? enabled, + dismissCallback, + }) : super( + switchType: switchType, + text: text, + textStyle: textStyle, + padding: padding, + dismissOnClicked: dismissOnClicked, + enabled: enabled, + dismissCallback: dismissCallback, + ) { + _curOption.value = currentValue; + } + + @override + RxBool get curOption => _curOption; + @override + setOption(bool? option) async { + if (option != null) { + await setter(option); + // Notice: no ensure with getter, best used on menus that are destroyed on click + if (_curOption.value != option) { + _curOption.value = option; + } + } + } +} + typedef Switch2Getter = RxBool Function(); typedef Switch2Setter = Future Function(bool); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 583d0750bf..70a5b13a83 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1626,19 +1626,7 @@ class _KeyboardMenu extends StatelessWidget { Widget build(BuildContext context) { var ffiModel = Provider.of(context); if (!ffiModel.keyboard) return Offstage(); - // If use flutter to grab keys, we can only use one mode. - // Map mode and Legacy mode, at least one of them is supported. - String? modeOnly; - if (isInputSourceFlutter) { - if (bind.sessionIsKeyboardModeSupported( - sessionId: ffi.sessionId, mode: kKeyMapMode)) { - modeOnly = kKeyMapMode; - } else if (bind.sessionIsKeyboardModeSupported( - sessionId: ffi.sessionId, mode: kKeyLegacyMode)) { - modeOnly = kKeyLegacyMode; - } - } - final toolbarToggles = toolbarKeyboardToggles(ffi) + toolbarToggles() => toolbarKeyboardToggles(ffi) .map((e) => CkbMenuButton( value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi)) .toList(); @@ -1649,17 +1637,17 @@ class _KeyboardMenu extends StatelessWidget { color: _ToolbarTheme.blueColor, hoverColor: _ToolbarTheme.hoverBlueColor, menuChildrenGetter: () => [ - keyboardMode(modeOnly), + keyboardMode(), localKeyboardType(), inputSource(), Divider(), viewMode(), Divider(), - ...toolbarToggles, + ...toolbarToggles(), ]); } - keyboardMode(String? modeOnly) { + keyboardMode() { return futureBuilder(future: () async { return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ?? kKeyLegacyMode; @@ -1679,6 +1667,19 @@ class _KeyboardMenu extends StatelessWidget { await ffi.inputModel.updateKeyboardMode(); } + // If use flutter to grab keys, we can only use one mode. + // Map mode and Legacy mode, at least one of them is supported. + String? modeOnly; + if (isInputSourceFlutter) { + if (bind.sessionIsKeyboardModeSupported( + sessionId: ffi.sessionId, mode: kKeyMapMode)) { + modeOnly = kKeyMapMode; + } else if (bind.sessionIsKeyboardModeSupported( + sessionId: ffi.sessionId, mode: kKeyLegacyMode)) { + modeOnly = kKeyLegacyMode; + } + } + for (InputModeMenu mode in modes) { if (modeOnly != null && mode.key != modeOnly) { continue; diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart index 43bbfda723..52e43bfeab 100644 --- a/flutter/lib/models/peer_tab_model.dart +++ b/flutter/lib/models/peer_tab_model.dart @@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier { WeakReference parent; int get currentTab => _currentTab; int _currentTab = 0; // index in tabNames - List tabNames = [ + static const int maxTabCount = 5; + static const String kPeerTabIndex = 'peer-tab-index'; + static const String kPeerTabOrder = 'peer-tab-order'; + static const String kPeerTabVisible = 'peer-tab-visible'; + static const List tabNames = [ 'Recent sessions', 'Favorites', - if (!isWeb) 'Discovered', - if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book', - if (!bind.isDisableAccount()) 'Group', + 'Discovered', + 'Address book', + 'Group', ]; - final List icons = [ + static const List icons = [ Icons.access_time_filled, Icons.star, - if (!isWeb) Icons.explore, - if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook, - if (!bind.isDisableAccount()) Icons.group, + Icons.explore, + IconFont.addressBook, + Icons.group, ]; - final List _isVisible = List.filled(5, true, growable: false); - List get isVisible => _isVisible; - List get indexs => List.generate(tabNames.length, (index) => index); - List get visibleIndexs => indexs.where((e) => _isVisible[e]).toList(); + List isEnabled = List.from([ + true, + true, + !isWeb, + !(bind.isDisableAb() || bind.isDisableAccount()), + !bind.isDisableAccount(), + ]); + final List _isVisible = List.filled(maxTabCount, true, growable: false); + List get isVisibleEnabled => () { + final list = _isVisible.toList(); + for (int i = 0; i < maxTabCount; i++) { + list[i] = list[i] && isEnabled[i]; + } + return list; + }(); + final List orders = + List.generate(maxTabCount, (index) => index, growable: false); + List get visibleEnabledOrderedIndexs => + orders.where((e) => isVisibleEnabled[e]).toList(); List _selectedPeers = List.empty(growable: true); List get selectedPeers => _selectedPeers; bool _multiSelectionMode = false; @@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier { PeerTabModel(this.parent) { // visible try { - final option = bind.getLocalFlutterOption(k: 'peer-tab-visible'); + final option = bind.getLocalFlutterOption(k: kPeerTabVisible); if (option.isNotEmpty) { List decodeList = jsonDecode(option); if (decodeList.length == _isVisible.length) { @@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier { } catch (e) { debugPrint("failed to get peer tab visible list:$e"); } + // order + try { + final option = bind.getLocalFlutterOption(k: kPeerTabOrder); + if (option.isNotEmpty) { + List decodeList = jsonDecode(option); + if (decodeList.length == maxTabCount) { + var sortedList = decodeList.toList(); + sortedList.sort(); + bool valid = true; + for (int i = 0; i < maxTabCount; i++) { + if (sortedList[i] is! int || sortedList[i] != i) { + valid = false; + } + } + if (valid) { + for (int i = 0; i < orders.length; i++) { + orders[i] = decodeList[i]; + } + } + } + } + } catch (e) { + debugPrint("failed to get peer tab order list: $e"); + } // init currentTab _currentTab = - int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0; - if (_currentTab < 0 || _currentTab >= tabNames.length) { + int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0; + if (_currentTab < 0 || _currentTab >= maxTabCount) { _currentTab = 0; } - _trySetCurrentTabToFirstVisible(); + _trySetCurrentTabToFirstVisibleEnabled(); } setCurrentTab(int index) { @@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier { if (index >= 0 && index < tabNames.length) { return translate(tabNames[index]); } - assert(false); return index.toString(); } IconData tabIcon(int index) { - if (index >= 0 && index < tabNames.length) { + if (index >= 0 && index < icons.length) { return icons[index]; } - assert(false); return Icons.help; } @@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier { } setTabVisible(int index, bool visible) { - if (index >= 0 && index < _isVisible.length) { + if (index >= 0 && index < maxTabCount) { if (_isVisible[index] != visible) { _isVisible[index] = visible; if (index == _currentTab && !visible) { - _trySetCurrentTabToFirstVisible(); - } else if (visible && visibleIndexs.length == 1) { + _trySetCurrentTabToFirstVisibleEnabled(); + } else if (visible && visibleEnabledOrderedIndexs.length == 1) { _currentTab = index; } try { bind.setLocalFlutterOption( - k: 'peer-tab-visible', v: jsonEncode(_isVisible)); + k: kPeerTabVisible, v: jsonEncode(_isVisible)); } catch (_) {} notifyListeners(); } } } - _trySetCurrentTabToFirstVisible() { - if (!_isVisible[_currentTab]) { - int firstVisible = _isVisible.indexWhere((e) => e); - if (firstVisible >= 0) { - _currentTab = firstVisible; + _trySetCurrentTabToFirstVisibleEnabled() { + if (!visibleEnabledOrderedIndexs.contains(_currentTab)) { + if (visibleEnabledOrderedIndexs.isNotEmpty) { + _currentTab = visibleEnabledOrderedIndexs.first; + } + } + } + + reorder(int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + if (oldIndex < 0 || oldIndex >= visibleEnabledOrderedIndexs.length) { + return; + } + if (newIndex < 0 || newIndex >= visibleEnabledOrderedIndexs.length) { + return; + } + final oldTabValue = visibleEnabledOrderedIndexs[oldIndex]; + final newTabValue = visibleEnabledOrderedIndexs[newIndex]; + int oldValueIndex = orders.indexOf(oldTabValue); + int newValueIndex = orders.indexOf(newTabValue); + final list = orders.toList(); + if (oldIndex != -1 && newIndex != -1) { + list.removeAt(oldValueIndex); + list.insert(newValueIndex, oldTabValue); + for (int i = 0; i < list.length; i++) { + orders[i] = list[i]; } + bind.setLocalFlutterOption(k: kPeerTabOrder, v: jsonEncode(orders)); + notifyListeners(); } } } diff --git a/res/msi/CustomActions/CustomActions.cpp b/res/msi/CustomActions/CustomActions.cpp index a5c49410f6..f836edb3bb 100644 --- a/res/msi/CustomActions/CustomActions.cpp +++ b/res/msi/CustomActions/CustomActions.cpp @@ -1,5 +1,6 @@ // CustomAction.cpp : Defines the entry point for the custom action. #include "pch.h" +#include #include UINT __stdcall CustomActionHello( @@ -30,34 +31,40 @@ UINT __stdcall RemoveInstallFolder( DWORD er = ERROR_SUCCESS; int nResult = 0; - wchar_t szCustomActionData[256] = { 0 }; - DWORD cchCustomActionData = sizeof(szCustomActionData) / sizeof(szCustomActionData[0]); + LPWSTR installFolder = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzData = NULL; hr = WcaInitialize(hInstall, "RemoveInstallFolder"); ExitOnFailure(hr, "Failed to initialize"); - MsiGetPropertyW(hInstall, L"InstallFolder", szCustomActionData, &cchCustomActionData); - - WcaLog(LOGMSG_STANDARD, "================= Remove Install Folder: %ls", szCustomActionData); + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &installFolder); + ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz); SHFILEOPSTRUCTW fileOp; - ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT)); + ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT)); fileOp.wFunc = FO_DELETE; - fileOp.pFrom = szCustomActionData; + fileOp.pFrom = installFolder; fileOp.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; - nResult = SHFileOperationW(&fileOp); + nResult = SHFileOperation(&fileOp); if (nResult == 0) { - WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has been deleted.", szCustomActionData); + WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has been deleted.", installFolder); } else { - WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has not been deleted, error code: 0X%02X.", szCustomActionData, nResult); + WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has not been deleted, error code: 0X%02X. Please refer to https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationa for the error codes.", installFolder, nResult); } LExit: + ReleaseStr(installFolder); + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); } diff --git a/res/msi/CustomActions/CustomActions.vcxproj b/res/msi/CustomActions/CustomActions.vcxproj index 1d15211580..3667b3bc82 100644 --- a/res/msi/CustomActions/CustomActions.vcxproj +++ b/res/msi/CustomActions/CustomActions.vcxproj @@ -10,7 +10,7 @@ Win32Proj - {87e7c13b-ae0e-4048-95cf-4523d510a3cd} + {6b3647e0-b4a3-46ae-8757-a22ee51c1dac} CustomActions v143 10.0 diff --git a/res/msi/Package/Components/RustDesk.wxs b/res/msi/Package/Components/RustDesk.wxs index abb43358d9..297f35a77d 100644 --- a/res/msi/Package/Components/RustDesk.wxs +++ b/res/msi/Package/Components/RustDesk.wxs @@ -20,6 +20,7 @@ + diff --git a/res/msi/Package/Fragments/CustomActions.wxs b/res/msi/Package/Fragments/CustomActions.wxs index 79b49b49d8..09d9b56984 100644 --- a/res/msi/Package/Fragments/CustomActions.wxs +++ b/res/msi/Package/Fragments/CustomActions.wxs @@ -5,18 +5,15 @@ - - - - + - + - + diff --git a/res/msi/msi.sln b/res/msi/msi.sln index 8fb06eb51d..70d28fb861 100644 --- a/res/msi/msi.sln +++ b/res/msi/msi.sln @@ -5,38 +5,17 @@ VisualStudioVersion = 17.7.34003.232 MinimumVisualStudioVersion = 10.0.40219.1 Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Package", "Package\Package.wixproj", "{F403A403-CEFF-4399-B51C-CC646C8E98CF}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{87E7C13B-AE0E-4048-95CF-4523D510A3CD}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.ActiveCfg = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.Build.0 = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.ActiveCfg = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.Build.0 = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x86.ActiveCfg = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x86.Build.0 = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.ActiveCfg = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.Build.0 = Release|x64 {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64 {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x86.ActiveCfg = Release|x64 - {F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x86.Build.0 = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Debug|Any CPU.ActiveCfg = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Debug|x64.ActiveCfg = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Debug|x86.ActiveCfg = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Release|Any CPU.ActiveCfg = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Release|Any CPU.Build.0 = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Release|x64.ActiveCfg = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Release|x64.Build.0 = Release|x64 - {87E7C13B-AE0E-4048-95CF-4523D510A3CD}.Release|x86.ActiveCfg = Release|x64 + {6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64 + {6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/res/msi/preprocess.py b/res/msi/preprocess.py index fe9050b980..23f1e28ead 100644 --- a/res/msi/preprocess.py +++ b/res/msi/preprocess.py @@ -143,7 +143,7 @@ def func(lines, index_start): upgrade_id = uuid.uuid4() to_insert_lines = [ f'{indent}\n', - f'{indent}" ?>\n', + f'{indent}{g_indent_unit}\n', f"{indent}\n", ] diff --git a/src/core_main.rs b/src/core_main.rs index caac2ca995..2d36097eb0 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -208,31 +208,11 @@ pub fn core_main() -> Option> { .show() .ok(); return None; - } else if args[0] == "--install-cert" { - #[cfg(windows)] - hbb_common::allow_err!(crate::platform::windows::install_cert( - crate::platform::windows::DRIVER_CERT_FILE - )); - if args.len() > 1 && args[1] == "silent" { - return None; - } - #[cfg(all(windows, feature = "virtual_display_driver"))] - if crate::virtual_display_manager::is_virtual_display_supported() { - hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver()); - } - return None; } else if args[0] == "--uninstall-cert" { #[cfg(windows)] hbb_common::allow_err!(crate::platform::windows::uninstall_cert()); return None; } else if args[0] == "--install-idd" { - #[cfg(windows)] - { - // It's ok to install cert multiple times. - hbb_common::allow_err!(crate::platform::windows::install_cert( - crate::platform::windows::DRIVER_CERT_FILE - )); - } #[cfg(all(windows, feature = "virtual_display_driver"))] if crate::virtual_display_manager::is_virtual_display_supported() { hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver()); diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 0ac1981ecb..f6d649470b 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -588,7 +588,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("powered_by_me", "由 RustDesk 提供支持"), ("outgoing_only_desk_tip", "当前版本的软件是定制版本。\n您可以连接至其他设备,但是其他设备无法连接至您的设备。"), ("preset_password_warning", "此定制版本附有预设密码。 任何知晓此密码的人都能完全控制您的设备。如果这不是您所预期的,请立即卸载此软件。"), - ("Security Alert", ""), + ("Security Alert", "安全警告"), ("My address book", "我的地址簿"), ("Personal", "个人的"), ("Owner", "所有者"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 86d08d5476..6600a65be4 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -134,14 +134,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Insert Lock", "Встановити замок"), ("Refresh", "Оновити"), ("ID does not exist", "ID не існує"), - ("Failed to connect to rendezvous server", "Не вдалося підключитися до проміжного сервера"), + ("Failed to connect to rendezvous server", "Не вдалося підключитися до сервера рандеву"), ("Please try later", "Будь ласка, спробуйте пізніше"), ("Remote desktop is offline", "Віддалена стільниця не в мережі"), ("Key mismatch", "Невідповідність ключів"), ("Timeout", "Час очікування"), - ("Failed to connect to relay server", "Не вдалося підключитися до сервера реле"), - ("Failed to connect via rendezvous server", "Не вдалося підключитися через проміжний сервер"), - ("Failed to connect via relay server", "Не вдалося підключитися через сервер реле"), + ("Failed to connect to relay server", "Не вдалося підключитися до сервера ретрансляції"), + ("Failed to connect via rendezvous server", "Не вдалося підключитися через сервер рандеву"), + ("Failed to connect via relay server", "Не вдалося підключитися через сервер ретрансляції"), ("Failed to make direct connection to remote desktop", "Не вдалося встановити пряме підключення до віддаленої стільниці"), ("Set Password", "Встановити пароль"), ("OS Password", "Пароль ОС"), @@ -182,9 +182,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable file copy and paste", "Дозволити копіювання та вставку файлів"), ("Connected", "Підключено"), ("Direct and encrypted connection", "Пряме та зашифроване підключення"), - ("Relayed and encrypted connection", "Релейне та зашифроване підключення"), + ("Relayed and encrypted connection", "Ретрансльоване та зашифроване підключення"), ("Direct and unencrypted connection", "Пряме та незашифроване підключення"), - ("Relayed and unencrypted connection", "Релейне та незашифроване підключення"), + ("Relayed and unencrypted connection", "Ретрансльоване та незашифроване підключення"), ("Enter Remote ID", "Введіть віддалений ID"), ("Enter your password", "Введіть пароль"), ("Logging in...", "Вхід..."), @@ -210,8 +210,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "Завершено вручну з боку віддаленого пристрою"), ("Enable remote configuration modification", "Дозволити віддалену зміну конфігурації"), ("Run without install", "Запустити без встановлення"), - ("Connect via relay", "Підключитися через реле"), - ("Always connect via relay", "Завжди підключатися через реле"), + ("Connect via relay", "Підключитися через ретранслятор"), + ("Always connect via relay", "Завжди підключатися через ретранслятор"), ("whitelist_tip", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"), ("Login", "Увійти"), ("Verify", "Підтвердити"), @@ -318,7 +318,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Restart remote device", "Перезапустити віддалений пристрій"), ("Are you sure you want to restart", "Ви впевнені, що хочете виконати перезапуск?"), ("Restarting remote device", "Перезапуск віддаленого пристрою"), - ("remote_restarting_tip", "Віддалений пристрій перезапускається. Будь ласка, закрийте це повідомлення та через деякий час перепідʼєднайтесь, використовуючи постійний пароль."), + ("remote_restarting_tip", "Віддалений пристрій перезапускається. Будь ласка, закрийте це повідомлення та через деякий час перепідключіться, використовуючи постійний пароль."), ("Copied", "Скопійовано"), ("Exit Fullscreen", "Вийти з повноекранного режиму"), ("Fullscreen", "Повноекранний"), @@ -332,7 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show Toolbar", "Показати панель інструментів"), ("Hide Toolbar", "Приховати панель інструментів"), ("Direct Connection", "Пряме підключення"), - ("Relay Connection", "Релейне підключення"), + ("Relay Connection", "Ретрансльоване підключення"), ("Secure Connection", "Безпечне підключення"), ("Insecure Connection", "Небезпечне підключення"), ("Scale original", "Оригінал масштабу"), @@ -444,7 +444,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Voice call", "Голосовий виклик"), ("Text chat", "Текстовий чат"), ("Stop voice call", "Завершити голосовий виклик"), - ("relay_hint_tip", "Якщо відсутня можливості підключитись напряму, ви можете спробувати підключення по реле. \nТакож, якщо ви хочете відразу використовувати реле, можна додати суфікс \"/r\" до ID, або ж вибрати опцію \"Завжди підключатися через реле\" в картці нещодавніх сеансів."), + ("relay_hint_tip", "Якщо відсутня можливості підключитись напряму, ви можете спробувати підключення через ретранслятор. \nТакож, якщо ви хочете відразу використовувати ретранслятор, можна додати суфікс \"/r\" до ID, або ж вибрати опцію \"Завжди підключатися через ретранслятор\" в картці нещодавніх сеансів."), ("Reconnect", "Перепідключитися"), ("Codec", "Кодек"), ("Resolution", "Роздільна здатність"), @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("empty_favorite_tip", "Досі немає улюблених вузлів?\nДавайте організуємо нове підключення та додамо його до улюблених!"), ("empty_lan_tip", "О ні, схоже ми поки не виявили жодного віддаленого пристрою"), ("empty_address_book_tip", "Ой лишенько, схоже до вашої адресної книги немає жодного віддаленого пристрою"), - ("eg: admin", "напр. admin"), + ("eg: admin", "напр., admin"), ("Empty Username", "Незаповнене імʼя"), ("Empty Password", "Незаповнений пароль"), ("Me", "Я"), @@ -561,7 +561,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("List", "Список"), ("Virtual display", "Віртуальний дисплей"), ("Plug out all", "Відключити все"), - ("True color (4:4:4)", "Спражній колір (4:4:4)"), + ("True color (4:4:4)", "Справжній колір (4:4:4)"), ("Enable blocking user input", "Блокувати введення для користувача"), ("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", ключ для публічного сервера не потрібен."), ("privacy_mode_impl_mag_tip", "Режим 1"), @@ -585,20 +585,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("2FA code must be 6 digits.", "Код двофакторної автентифікації повинен складатися з 6 символів."), ("Multiple Windows sessions found", "Виявлено декілька сеансів Windows"), ("Please select the session you want to connect to", "Будь ласка, оберіть сеанс, до якого ви хочете підключитися"), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), - ("Security Alert", ""), - ("My address book", ""), - ("Personal", ""), - ("Owner", ""), - ("Set shared password", ""), - ("Exist in", ""), - ("Read-only", ""), - ("Read/Write", ""), - ("Full Control", ""), - ("share_warning_tip", ""), - ("Everyone", ""), - ("ab_web_console_tip", ""), + ("powered_by_me", "На основі Rustdesk"), + ("outgoing_only_desk_tip", "Це персоналізована версія.\nВи можете підключатися до інших пристроїв, але інші пристрої не можуть підключатися до вашого."), + ("preset_password_warning", "Ця персоналізована версія містить попередньо встановлений пароль. Будь-хто з цим паролем може отримати повний доступ до вашого пристрою. Якщо це неочікувано для вас, негайно видаліть цю програму."), + ("Security Alert", "Попередження щодо безпеки"), + ("My address book", "Моя адресна книга"), + ("Personal", "Особиста"), + ("Owner", "Власник"), + ("Set shared password", "Встановити спільний пароль"), + ("Exist in", "Існує у"), + ("Read-only", "Лише читання"), + ("Read/Write", "Читання/запис"), + ("Full Control", "Повний доступ"), + ("share_warning_tip", "Поля вище є спільними та видимі для інших."), + ("Everyone", "Всі"), + ("ab_web_console_tip", "Детальніше про веб-консоль"), ].iter().cloned().collect(); } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 0659422efd..05a9582d7f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1187,17 +1187,6 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name} ); let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_string(); - let install_cert = if options.contains("driverCert") { - let s = format!(r#""{}" --install-cert"#, src_exe); - if silent { - format!("{} silent", s) - } else { - s - } - } else { - "".to_owned() - }; - // potential bug here: if run_cmd cancelled, but config file is changed. if let Some(lic) = get_license() { Config::set_option("key".into(), lic.key); @@ -1241,11 +1230,10 @@ cscript \"{uninstall_shortcut}\" copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\" {dels} {import_config} -{install_cert} {after_install} {sleep} ", - version = crate::VERSION, + version = crate::VERSION.replace("-", "."), build_date = crate::BUILD_DATE, after_install = get_after_install(&exe), sleep = if debug { "timeout 300" } else { "" }, @@ -1958,251 +1946,20 @@ pub fn user_accessible_folder() -> ResultType { Ok(dir) } -#[inline] -pub fn install_cert(cert_file: &str) -> ResultType<()> { - let exe_file = std::env::current_exe()?; - if let Some(cur_dir) = exe_file.parent() { - allow_err!(cert::install_cert(cur_dir.join(cert_file))); - } else { - bail!( - "Invalid exe parent for {}", - exe_file.to_string_lossy().as_ref() - ); - } - Ok(()) -} - #[inline] pub fn uninstall_cert() -> ResultType<()> { cert::uninstall_cert() } mod cert { - use hbb_common::{bail, log, ResultType}; - use std::{ffi::OsStr, io::Error, os::windows::ffi::OsStrExt, path::Path, str::from_utf8}; - use winapi::{ - shared::{ - minwindef::{BYTE, DWORD, FALSE, TRUE}, - ntdef::NULL, - }, - um::{ - wincrypt::{ - CertAddEncodedCertificateToStore, CertCloseStore, CertDeleteCertificateFromStore, - CertEnumCertificatesInStore, CertNameToStrA, CertOpenStore, CryptHashCertificate, - ALG_ID, CALG_SHA1, CERT_ID_SHA1_HASH, CERT_STORE_ADD_REPLACE_EXISTING, - CERT_STORE_PROV_SYSTEM_W, CERT_SYSTEM_STORE_LOCAL_MACHINE, CERT_X500_NAME_STR, - PCCERT_CONTEXT, PKCS_7_ASN_ENCODING, X509_ASN_ENCODING, - }, - winreg::HKEY_LOCAL_MACHINE, - }, - }; - use winreg::{ - enums::{KEY_WRITE, REG_BINARY}, - RegKey, - }; - - const ROOT_CERT_STORE_PATH: &str = - "SOFTWARE\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\"; - const THUMBPRINT_ALG: ALG_ID = CALG_SHA1; - const THUMBPRINT_LEN: DWORD = 20; - const CERT_ISSUER_1: &str = "CN=\"WDKTestCert admin,133225435702113567\"\0"; - const CERT_ENCODING_TYPE: DWORD = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; - - lazy_static::lazy_static! { - static ref CERT_STORE_LOC: Vec = OsStr::new("ROOT\0").encode_wide().collect::>(); - } - - #[inline] - unsafe fn compute_thumbprint(pb_encoded: *const BYTE, cb_encoded: DWORD) -> (Vec, String) { - let mut size = THUMBPRINT_LEN; - let mut thumbprint = [0u8; THUMBPRINT_LEN as usize]; - if CryptHashCertificate( - 0, - THUMBPRINT_ALG, - 0, - pb_encoded, - cb_encoded, - thumbprint.as_mut_ptr(), - &mut size, - ) == TRUE - { - ( - thumbprint.to_vec(), - hex::encode(thumbprint).to_ascii_uppercase(), - ) - } else { - (thumbprint.to_vec(), "".to_owned()) - } - } - - #[inline] - unsafe fn open_reg_cert_store() -> ResultType { - let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); - Ok(hklm.open_subkey_with_flags(ROOT_CERT_STORE_PATH, KEY_WRITE)?) - } - - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpef/6a9e35fa-2ac7-4c10-81e1-eabe8d2472f1 - fn create_cert_blob(thumbprint: Vec, encoded: Vec) -> Vec { - let mut blob = Vec::new(); - - let mut property_id = (CERT_ID_SHA1_HASH as u32).to_le_bytes().to_vec(); - let mut pro_reserved = [0x01, 0x00, 0x00, 0x00].to_vec(); - let mut pro_length = (THUMBPRINT_LEN as u32).to_le_bytes().to_vec(); - let mut pro_val = thumbprint; - blob.append(&mut property_id); - blob.append(&mut pro_reserved); - blob.append(&mut pro_length); - blob.append(&mut pro_val); - - let mut blob_reserved = [0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00].to_vec(); - let mut blob_length = (encoded.len() as u32).to_le_bytes().to_vec(); - let mut blob_val = encoded; - blob.append(&mut blob_reserved); - blob.append(&mut blob_length); - blob.append(&mut blob_val); - - blob - } - - pub fn install_cert>(path: P) -> ResultType<()> { - let mut cert_bytes = std::fs::read(path)?; - install_cert_reg(&mut cert_bytes)?; - install_cert_add_cert_store(&mut cert_bytes)?; - Ok(()) - } - - fn install_cert_reg(cert_bytes: &mut [u8]) -> ResultType<()> { - unsafe { - let thumbprint = compute_thumbprint(cert_bytes.as_mut_ptr(), cert_bytes.len() as _); - log::debug!("Thumbprint of cert {}", &thumbprint.1); - - let reg_cert_key = open_reg_cert_store()?; - let (cert_key, _) = reg_cert_key.create_subkey(&thumbprint.1)?; - let data = winreg::RegValue { - vtype: REG_BINARY, - bytes: create_cert_blob(thumbprint.0, cert_bytes.to_vec()), - }; - cert_key.set_raw_value("Blob", &data)?; - } - Ok(()) - } - - fn install_cert_add_cert_store(cert_bytes: &mut [u8]) -> ResultType<()> { - unsafe { - let store_handle = CertOpenStore( - CERT_STORE_PROV_SYSTEM_W, - 0, - 0, - CERT_SYSTEM_STORE_LOCAL_MACHINE, - CERT_STORE_LOC.as_ptr() as _, - ); - if store_handle.is_null() { - bail!( - "Error opening certificate store: {}", - Error::last_os_error() - ); - } - - // Create the certificate context - let cert_context = winapi::um::wincrypt::CertCreateCertificateContext( - CERT_ENCODING_TYPE, - cert_bytes.as_ptr(), - cert_bytes.len() as DWORD, - ); - if cert_context.is_null() { - bail!( - "Error creating certificate context: {}", - Error::last_os_error() - ); - } - - if FALSE - == CertAddEncodedCertificateToStore( - store_handle, - CERT_ENCODING_TYPE, - (*cert_context).pbCertEncoded, - (*cert_context).cbCertEncoded, - CERT_STORE_ADD_REPLACE_EXISTING, - std::ptr::null_mut(), - ) - { - log::error!( - "Failed to call CertAddEncodedCertificateToStore: {}", - Error::last_os_error() - ); - } else { - log::info!("Add cert to store successfully"); - } - - CertCloseStore(store_handle, 0); - } - Ok(()) - } - - fn get_thumbprints_to_rm() -> ResultType> { - let issuers_to_rm = [CERT_ISSUER_1]; + use hbb_common::ResultType; - let mut thumbprints = Vec::new(); - let mut buf = [0u8; 1024]; - - unsafe { - let store_handle = CertOpenStore( - CERT_STORE_PROV_SYSTEM_W, - 0, - 0, - CERT_SYSTEM_STORE_LOCAL_MACHINE, - CERT_STORE_LOC.as_ptr() as _, - ); - if store_handle.is_null() { - bail!( - "Error opening certificate store: {}", - Error::last_os_error() - ); - } - - let mut cert_ctx: PCCERT_CONTEXT = CertEnumCertificatesInStore(store_handle, NULL as _); - while !cert_ctx.is_null() { - // https://stackoverflow.com/a/66432736 - let cb_size = CertNameToStrA( - (*cert_ctx).dwCertEncodingType, - &mut ((*(*cert_ctx).pCertInfo).Issuer) as _, - CERT_X500_NAME_STR, - buf.as_mut_ptr() as _, - buf.len() as _, - ); - if cb_size != 1 { - if let Ok(issuer) = from_utf8(&buf[..cb_size as _]) { - for iss in issuers_to_rm.iter() { - if issuer == *iss { - let (_, thumbprint) = compute_thumbprint( - (*cert_ctx).pbCertEncoded, - (*cert_ctx).cbCertEncoded, - ); - if !thumbprint.is_empty() { - thumbprints.push(thumbprint); - } - // Delete current cert context and re-enumerate. - CertDeleteCertificateFromStore(cert_ctx); - cert_ctx = CertEnumCertificatesInStore(store_handle, NULL as _); - } - } - } - } - cert_ctx = CertEnumCertificatesInStore(store_handle, cert_ctx); - } - CertCloseStore(store_handle, 0); - } - - Ok(thumbprints) + extern "C" { + fn DeleteRustDeskTestCertsW(); } - pub fn uninstall_cert() -> ResultType<()> { - let thumbprints = get_thumbprints_to_rm()?; - let reg_cert_key = unsafe { open_reg_cert_store()? }; - log::info!("Found {} certs to remove", thumbprints.len()); - for thumbprint in thumbprints.iter() { - // Deleting cert from registry may fail, because the CertDeleteCertificateFromStore() is called before. - let _ = reg_cert_key.delete_subkey(thumbprint); + unsafe { + DeleteRustDeskTestCertsW(); } Ok(()) } @@ -2449,14 +2206,6 @@ pub fn try_kill_broker() { #[cfg(test)] mod tests { use super::*; - #[test] - fn test_install_cert() { - println!( - "install driver cert: {:?}", - cert::install_cert("RustDeskIddDriver.cer") - ); - } - #[test] fn test_uninstall_cert() { println!("uninstall driver certs: {:?}", cert::uninstall_cert()); diff --git a/src/platform/windows_delete_test_cert.cc b/src/platform/windows_delete_test_cert.cc new file mode 100644 index 0000000000..94949a8272 --- /dev/null +++ b/src/platform/windows_delete_test_cert.cc @@ -0,0 +1,406 @@ +// https://github.com/rustdesk/rustdesk/discussions/6444#discussioncomment-9010062 + +#include +#include +#include + +BOOL IsCertWdkTestCert(char* lpBlobData, DWORD cchBlobData) { + DWORD cchIdxBlobData = 0; + DWORD cchIdxTestCertBlob = 0; + DWORD cchSizeTestCertBlob = 0; +#pragma warning(push) +#pragma warning(disable: 4838) +#pragma warning(disable: 4309) + const char TestCertBlob[] = { + 0X30, 0X82, 0X03, 0X0C, 0X30, 0X82, 0X01, 0XF4, 0XA0, 0X03, 0X02, 0X01, 0X02, 0X02, 0X10, 0X17, + 0X93, 0X62, 0X03, 0XFA, 0XCD, 0X37, 0X83, 0X49, 0XE3, 0X33, 0X82, 0XC3, 0X14, 0XEC, 0X83, 0X30, + 0X0D, 0X06, 0X09, 0X2A, 0X86, 0X48, 0X86, 0XF7, 0X0D, 0X01, 0X01, 0X05, 0X05, 0X00, 0X30, 0X2F, + 0X31, 0X2D, 0X30, 0X2B, 0X06, 0X03, 0X55, 0X04, 0X03, 0X13, 0X24, 0X57, 0X44, 0X4B, 0X54, 0X65, + 0X73, 0X74, 0X43, 0X65, 0X72, 0X74, 0X20, 0X61, 0X64, 0X6D, 0X69, 0X6E, 0X2C, 0X31, 0X33, 0X33, + 0X32, 0X32, 0X35, 0X34, 0X33, 0X35, 0X37, 0X30, 0X32, 0X31, 0X31, 0X33, 0X35, 0X36, 0X37, 0X30, + 0X1E, 0X17, 0X0D, 0X32, 0X33, 0X30, 0X33, 0X30, 0X36, 0X30, 0X32, 0X33, 0X32, 0X35, 0X31, 0X5A, + 0X17, 0X0D, 0X33, 0X33, 0X30, 0X33, 0X30, 0X36, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X5A, 0X30, + 0X2F, 0X31, 0X2D, 0X30, 0X2B, 0X06, 0X03, 0X55, 0X04, 0X03, 0X13, 0X24, 0X57, 0X44, 0X4B, 0X54, + 0X65, 0X73, 0X74, 0X43, 0X65, 0X72, 0X74, 0X20, 0X61, 0X64, 0X6D, 0X69, 0X6E, 0X2C, 0X31, 0X33, + 0X33, 0X32, 0X32, 0X35, 0X34, 0X33, 0X35, 0X37, 0X30, 0X32, 0X31, 0X31, 0X33, 0X35, 0X36, 0X37, + 0X30, 0X82, 0X01, 0X22, 0X30, 0X0D, 0X06, 0X09, 0X2A, 0X86, 0X48, 0X86, 0XF7, 0X0D, 0X01, 0X01, + 0X01, 0X05, 0X00, 0X03, 0X82, 0X01, 0X0F, 0X00, 0X30, 0X82, 0X01, 0X0A, 0X02, 0X82, 0X01, 0X01, + 0X00, 0XB8, 0X65, 0X75, 0XAC, 0XD1, 0X82, 0XFC, 0X3A, 0X08, 0XE4, 0X1D, 0XD9, 0X4D, 0X5A, 0XCD, + 0X88, 0X2B, 0XDC, 0X00, 0XFD, 0X6B, 0X43, 0X13, 0XED, 0XE2, 0XCB, 0XD1, 0X26, 0X11, 0X22, 0XBF, + 0X20, 0X31, 0X09, 0X9D, 0X06, 0X47, 0XF5, 0XAA, 0XCE, 0X7B, 0X13, 0X98, 0XE0, 0X76, 0X40, 0XDD, + 0X2C, 0XCA, 0X98, 0XD1, 0XBB, 0X7F, 0XE2, 0X25, 0XAF, 0X48, 0X3A, 0X4E, 0X9E, 0X24, 0X38, 0X4D, + 0X04, 0XF0, 0X68, 0XAD, 0X7C, 0X6F, 0XA6, 0XBB, 0XE4, 0X9B, 0XE3, 0X7C, 0X8E, 0X2E, 0X54, 0X7D, + 0X5E, 0X74, 0XE3, 0XA6, 0X3D, 0XD9, 0X04, 0X22, 0X0A, 0X3E, 0XC7, 0X5C, 0XAB, 0X1F, 0X4D, 0X10, + 0X06, 0X2A, 0X95, 0X1A, 0X1B, 0X03, 0X20, 0X75, 0X3E, 0X49, 0X36, 0X40, 0X06, 0X63, 0XDB, 0X54, + 0X74, 0X53, 0X3C, 0X2D, 0X47, 0XE0, 0X82, 0XDD, 0X14, 0X92, 0XCC, 0XF1, 0X1A, 0X5A, 0X7F, 0X5B, + 0X4F, 0X2E, 0X94, 0X1E, 0XCE, 0X5A, 0X73, 0XD4, 0X70, 0X47, 0XF3, 0X3E, 0X85, 0X5C, 0X62, 0XF5, + 0X79, 0X0F, 0X4B, 0XB9, 0X69, 0X51, 0X33, 0X05, 0XF1, 0XDF, 0XE5, 0X4E, 0X6E, 0X28, 0XC6, 0X88, + 0X89, 0X9A, 0XEF, 0X07, 0X62, 0X23, 0X53, 0X6A, 0X16, 0X2B, 0X3A, 0XF7, 0X10, 0X1B, 0X42, 0XCE, + 0XEE, 0X33, 0XB9, 0X01, 0X30, 0X8A, 0XAB, 0X14, 0X73, 0XC5, 0XC3, 0X94, 0X2D, 0XEB, 0X00, 0XAE, + 0X73, 0X7B, 0X78, 0X65, 0X8B, 0X8F, 0X44, 0XBD, 0XF8, 0XBC, 0XE8, 0XB3, 0X6A, 0X4E, 0XE3, 0X4F, + 0X92, 0XE3, 0X72, 0XD9, 0X6D, 0XD1, 0X88, 0X5E, 0X1C, 0XFF, 0X8D, 0XF1, 0X76, 0XBC, 0X37, 0X4B, + 0X11, 0X48, 0XB5, 0X8D, 0X1D, 0X1C, 0XEC, 0X82, 0X11, 0X50, 0XC6, 0XFF, 0X3A, 0X7E, 0X3A, 0X8C, + 0X18, 0XF7, 0XA6, 0XEB, 0XAA, 0X26, 0X8E, 0XC6, 0X01, 0X7B, 0X50, 0X6A, 0XFA, 0X33, 0X3C, 0XBE, + 0X29, 0X02, 0X03, 0X01, 0X00, 0X01, 0XA3, 0X24, 0X30, 0X22, 0X30, 0X0B, 0X06, 0X03, 0X55, 0X1D, + 0X0F, 0X04, 0X04, 0X03, 0X02, 0X04, 0X30, 0X30, 0X13, 0X06, 0X03, 0X55, 0X1D, 0X25, 0X04, 0X0C, + 0X30, 0X0A, 0X06, 0X08, 0X2B, 0X06, 0X01, 0X05, 0X05, 0X07, 0X03, 0X03, 0X30, 0X0D, 0X06, 0X09, + 0X2A, 0X86, 0X48, 0X86, 0XF7, 0X0D, 0X01, 0X01, 0X05, 0X05, 0X00, 0X03, 0X82, 0X01, 0X01, 0X00, + 0X00, 0X44, 0X78, 0XE3, 0XDB, 0X0C, 0X33, 0X2B, 0X57, 0X52, 0X91, 0XD0, 0X09, 0X80, 0X12, 0XB0, + 0X11, 0X7C, 0X32, 0XCF, 0X24, 0XA0, 0XA5, 0X47, 0X18, 0XDE, 0XAB, 0X9E, 0X0D, 0X4A, 0X50, 0X6B, + 0X7B, 0XD3, 0X23, 0X71, 0X32, 0XEE, 0X28, 0X1D, 0XE8, 0X2C, 0X0A, 0XDF, 0X89, 0X87, 0X9D, 0X7E, + 0XE3, 0X59, 0X05, 0XDD, 0XC2, 0X3C, 0X48, 0XC1, 0XD5, 0X88, 0X2D, 0X60, 0X29, 0XDE, 0XA1, 0X69, + 0XD8, 0X4E, 0X01, 0XF6, 0XBD, 0XCB, 0X41, 0XDF, 0XDF, 0X5B, 0X3D, 0X3D, 0X59, 0X93, 0X70, 0XD6, + 0XAC, 0X03, 0X84, 0X5E, 0X2B, 0XB6, 0X62, 0X10, 0X5B, 0XB2, 0X68, 0X97, 0XC7, 0XF9, 0X44, 0X68, + 0XBC, 0XC3, 0X26, 0XD7, 0XB5, 0X13, 0XBE, 0X0E, 0XE6, 0X7E, 0X74, 0XF0, 0XB9, 0X59, 0X63, 0XE8, + 0X6E, 0XE2, 0X96, 0X3C, 0XFE, 0X55, 0XB9, 0XAC, 0X1A, 0XB8, 0XC5, 0X98, 0XA9, 0XD3, 0XF5, 0X30, + 0XCB, 0X9E, 0X43, 0X89, 0X19, 0X9A, 0X5C, 0XB5, 0XFB, 0X76, 0XD5, 0X3B, 0XD4, 0X79, 0X02, 0X98, + 0XA0, 0XC7, 0X60, 0X96, 0X84, 0X66, 0X79, 0X25, 0XC9, 0XC2, 0X77, 0X54, 0X63, 0XA1, 0X0E, 0X27, + 0X7B, 0X2E, 0X37, 0XBE, 0X18, 0X99, 0XF6, 0X34, 0XE7, 0XCC, 0XE8, 0XE7, 0XEB, 0XE4, 0XB7, 0X37, + 0X05, 0X35, 0X77, 0XAD, 0X76, 0XAD, 0X35, 0X84, 0X62, 0XF7, 0X7F, 0X87, 0XAB, 0X29, 0X25, 0X10, + 0X73, 0XBF, 0X2C, 0X78, 0X93, 0XFF, 0XBF, 0X24, 0XD7, 0X49, 0X74, 0XC5, 0X07, 0X41, 0X17, 0XBA, + 0X87, 0XBB, 0X4E, 0XB3, 0X8F, 0XF3, 0X75, 0X77, 0X2B, 0X44, 0X7B, 0X0D, 0X18, 0X24, 0X8A, 0XCB, + 0XCC, 0X67, 0XB4, 0X00, 0XC6, 0X2A, 0XAC, 0XCD, 0X4C, 0X16, 0XF8, 0XB8, 0X61, 0X8D, 0XAF, 0X7B, + 0XF2, 0X45, 0XE2, 0X63, 0X02, 0X4C, 0XA8, 0XB9, 0XBD, 0XB2, 0X5E, 0XF2, 0X94, 0X8F, 0X30, 0X16 + }; +#pragma warning(pop) + + cchSizeTestCertBlob = sizeof(TestCertBlob) / sizeof(TestCertBlob[0]); + if (cchBlobData < cchSizeTestCertBlob) return FALSE; + cchIdxBlobData = cchBlobData - cchSizeTestCertBlob; + while (cchIdxTestCertBlob < cchSizeTestCertBlob) { + if (lpBlobData[cchIdxBlobData] != TestCertBlob[cchIdxTestCertBlob]) { + return FALSE; + } + ++cchIdxTestCertBlob; + ++cchIdxBlobData; + } + return TRUE; +} + +//************************************************************* +// +// RegDelTestCertW() +// +// Purpose: Compares and deletes a test cert. +// +// Parameters: hKeyRoot - Root key +// lpSubKey - SubKey to delete +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +//************************************************************* + +BOOL RegDelTestCertW(HKEY hKeyRoot, LPCWSTR lpSubKey) +{ + LONG lResult; + HKEY hKey; + DWORD dValueType; + DWORD cchBufferSize = 0; + BOOL bRes = FALSE; + + lResult = RegOpenKeyExW(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); + if (lResult != ERROR_SUCCESS) { + if (lResult == ERROR_FILE_NOT_FOUND) { + return TRUE; + } + else { + //printf("Error opening key.\n"); + return FALSE; + } + } + + do { + lResult = RegQueryValueExW(hKey, L"Blob", NULL, &dValueType, NULL, &cchBufferSize); + if (lResult == ERROR_SUCCESS) { + if (dValueType == REG_BINARY) { + LPSTR szBuffer = NULL; + LONG readResult = 0; + szBuffer = (LPSTR)malloc(cchBufferSize * sizeof(char)); + if (szBuffer == NULL) { + bRes = FALSE; + break; + } + + lResult = RegQueryValueExW(hKey, L"Blob", NULL, &dValueType, (LPBYTE)szBuffer, &cchBufferSize); + if (readResult == ERROR_SUCCESS) { + if (IsCertWdkTestCert(szBuffer, cchBufferSize)) { + free(szBuffer); + lResult = RegDeleteKeyW(hKeyRoot, lpSubKey); + if (lResult == ERROR_SUCCESS) { + bRes = TRUE; + } + else { + bRes = FALSE; + } + + break; + } + } + + free(szBuffer); + } + } + } while (FALSE); + RegCloseKey(hKey); + return bRes; +} + +//************************************************************* +// +// RegDelnodeRecurseW() +// +// Purpose: Deletes a registry key and all its subkeys / values. +// +// Parameters: hKeyRoot - Root key +// lpSubKey - SubKey to delete +// bOneLevel - Delete lpSubKey and its first level subdirectory +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +// Note: If bOneLevel is TRUE, only current key and its first level subkeys are deleted. +// The first level subkeys are deleted only if they do not have subkeys. +// +// If some subkeys have subkeys, but the previous empty subkeys are deleted. +// It's ok for the certificates, because the empty subkeys are not used +// and they can be created automatically. +// +//************************************************************* + +BOOL RegDelnodeRecurseW(HKEY hKeyRoot, LPWSTR lpSubKey, BOOL bOneLevel) +{ + LPWSTR lpEnd; + LONG lResult; + DWORD dwSize; + WCHAR szName[MAX_PATH]; + HKEY hKey; + FILETIME ftWrite; + + // First, see if we can delete the key without having + // to recurse. + + lResult = RegDeleteKeyW(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + lResult = RegOpenKeyExW(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); + + if (lResult != ERROR_SUCCESS) + { + if (lResult == ERROR_FILE_NOT_FOUND) { + //printf("Key not found.\n"); + return TRUE; + } + else { + //printf("Error opening key.\n"); + return FALSE; + } + } + + // Check for an ending slash and add one if it is missing. + + lpEnd = lpSubKey + lstrlenW(lpSubKey); + + if (*(lpEnd - 1) != L'\\') + { + *lpEnd = L'\\'; + lpEnd++; + *lpEnd = L'\0'; + } + + // Enumerate the keys + + dwSize = MAX_PATH; + lResult = RegEnumKeyExW(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + if (lResult == ERROR_SUCCESS) + { + do { + + *lpEnd = L'\0'; + StringCchCatW(lpSubKey, MAX_PATH * 2, szName); + + if (bOneLevel) { + lResult = RegDeleteKeyW(hKeyRoot, lpSubKey); + if (lResult != ERROR_SUCCESS) { + return FALSE; + } + } + else { + if (!RegDelnodeRecurseW(hKeyRoot, lpSubKey, bOneLevel)) { + break; + } + } + + dwSize = MAX_PATH; + + lResult = RegEnumKeyExW(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + } while (lResult == ERROR_SUCCESS); + } + + lpEnd--; + *lpEnd = L'\0'; + + RegCloseKey(hKey); + + // Try again to delete the key. + + lResult = RegDeleteKeyW(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + return FALSE; +} + +//************************************************************* +// +// RegDelnodeW() +// +// Purpose: Deletes a registry key and all its subkeys / values. +// +// Parameters: hKeyRoot - Root key +// lpSubKey - SubKey to delete +// bOneLevel - Delete lpSubKey and its first level subdirectory +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +//************************************************************* + +BOOL RegDelnodeW(HKEY hKeyRoot, LPCWSTR lpSubKey, BOOL bOneLevel) +{ + //return FALSE; // For Testing + + WCHAR szDelKey[MAX_PATH * 2]; + + StringCchCopyW(szDelKey, MAX_PATH * 2, lpSubKey); + return RegDelnodeRecurseW(hKeyRoot, szDelKey, bOneLevel); +} + +//************************************************************* +// +// DeleteRustDeskTestCertsW_SingleHive() +// +// Purpose: Deletes RustDesk Test certificates and wrong key stores +// +// Parameters: RootKey - Root key +// Prefix - SID if RootKey=HKEY_USERS +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +//************************************************************* + +BOOL DeleteRustDeskTestCertsW_SingleHive(HKEY RootKey, LPWSTR Prefix = NULL) { + // WDKTestCert to be removed from all stores + LPCWSTR lpCertFingerPrint = L"D1DBB672D5A500B9809689CAEA1CE49E799767F0"; + + // Wrong key stores to be removed completely + LPCSTR RootName = "ROOT"; + LPWSTR SubKeyPrefix = (LPWSTR)RootName; // sic! Convert of ANSI to UTF-16 + + LPWSTR lpSystemCertificatesPath = (LPWSTR)malloc(512 * sizeof(WCHAR)); + if (lpSystemCertificatesPath == 0) return FALSE; + if (Prefix == NULL) { + wsprintfW(lpSystemCertificatesPath, L"Software\\Microsoft\\SystemCertificates"); + } + else { + wsprintfW(lpSystemCertificatesPath, L"%s\\Software\\Microsoft\\SystemCertificates", Prefix); + } + + HKEY hRegSystemCertificates; + LONG res = RegOpenKeyExW(RootKey, lpSystemCertificatesPath, NULL, KEY_ALL_ACCESS, &hRegSystemCertificates); + if (res != ERROR_SUCCESS) + return FALSE; + + for (DWORD Index = 0; ; Index++) { + LPWSTR SubKeyName = (LPWSTR)malloc(255 * sizeof(WCHAR)); + if (SubKeyName == 0) break; + DWORD cName = 255; + LONG res = RegEnumKeyExW(hRegSystemCertificates, Index, SubKeyName, &cName, NULL, NULL, NULL, NULL); + if ((res != ERROR_SUCCESS) || (SubKeyName == NULL)) + break; + + // Remove test certificate + LPWSTR Complete = (LPWSTR)malloc(512 * sizeof(WCHAR)); + if (Complete == 0) break; + wsprintfW(Complete, L"%s\\%s\\Certificates\\%s", lpSystemCertificatesPath, SubKeyName, lpCertFingerPrint); + // std::wcout << "Try delete from: " << SubKeyName << std::endl; + RegDelTestCertW(RootKey, Complete); + free(Complete); + + // "佒呏..." key begins with "ROOT" encoded as UTF-16 + if ((SubKeyName[0] == SubKeyPrefix[0]) && (SubKeyName[1] == SubKeyPrefix[1])) { + // Remove wrong empty key store + { + LPWSTR Complete = (LPWSTR)malloc(512 * sizeof(WCHAR)); + if (Complete == 0) break; + wsprintfW(Complete, L"%s\\%s", lpSystemCertificatesPath, SubKeyName); + if (RegDelnodeW(RootKey, Complete, TRUE)) { + //std::wcout << "Rogue Key Deleted! \"" << Complete << "\"" << std::endl; // TODO: Why does this break the console? + std::cout << "Rogue key is deleted!" << std::endl; + Index--; // Because index has moved due to the deletion + } + else { + std::cout << "Rogue key deletion failed!" << std::endl; + } + free(Complete); + } + } + + free(SubKeyName); + } + RegCloseKey(hRegSystemCertificates); + return TRUE; +} + +//************************************************************* +// +// DeleteRustDeskTestCertsW() +// +// Purpose: Deletes RustDesk Test certificates and wrong key stores +// +// Parameters: None +// +// Return: None +// +//************************************************************* + +extern "C" void DeleteRustDeskTestCertsW() { + // Current user + std::wcout << "*** Current User" << std::endl; + DeleteRustDeskTestCertsW_SingleHive(HKEY_CURRENT_USER); + + // Local machine (requires admin rights) + std::wcout << "*** Local Machine" << std::endl; + DeleteRustDeskTestCertsW_SingleHive(HKEY_LOCAL_MACHINE); + + // Iterate through all users (requires admin rights) + LPCWSTR lpRoot = L""; + HKEY hRegUsers; + LONG res = RegOpenKeyExW(HKEY_USERS, lpRoot, NULL, KEY_READ, &hRegUsers); + if (res != ERROR_SUCCESS) return; + for (DWORD Index = 0; ; Index++) { + LPWSTR SubKeyName = (LPWSTR)malloc(255 * sizeof(WCHAR)); + if (SubKeyName == 0) break; + DWORD cName = 255; + LONG res = RegEnumKeyExW(hRegUsers, Index, SubKeyName, &cName, NULL, NULL, NULL, NULL); + if ((res != ERROR_SUCCESS) || (SubKeyName == NULL)) + break; + std::wcout << "*** User: " << SubKeyName << std::endl; + DeleteRustDeskTestCertsW_SingleHive(HKEY_USERS, SubKeyName); + } + RegCloseKey(hRegUsers); +} + +// int main() +// { +// DeleteRustDeskTestCertsW(); +// return 0; +// }