From f9370e002e078e557ffcdb96ba4e7e63913b8d5f Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sun, 2 Jul 2023 13:15:51 +0100 Subject: [PATCH 01/53] [iso] fix a crash when parsing Windows ISOs with MinGW x86_32 * MinGW32's delay loading functionality is not yet up to par with MSVC's and especially, for some libraries (wininet, virtdisk) attempting to delay load them simply crashes the runtime. * This results in the MinGW32 version of the app crashing when selecting a Windows ISO, as we will then try to mount the ISO using virtdisk to poke the build version. Note that this crash does not happen with the MinGW64 version or with MSVC. * Closes #2272. * Also fix a Coverity warning in SaveImageThread() and improve the VHD saving code. --- .mingw/Makefile.am | 2 +- .mingw/Makefile.in | 2 +- .mingw/virtdisk.def | 7 ------- .vs/rufus.vcxproj | 32 ++++++++++++++++---------------- src/Makefile.am | 5 ++++- src/Makefile.in | 5 ++++- src/iso.c | 23 +++++++++++++++++++---- src/rufus.rc | 10 +++++----- src/vhd.c | 30 ++++++++++++++++++++++-------- 9 files changed, 72 insertions(+), 44 deletions(-) delete mode 100644 .mingw/virtdisk.def diff --git a/.mingw/Makefile.am b/.mingw/Makefile.am index 32c0b48b..eb979f9f 100644 --- a/.mingw/Makefile.am +++ b/.mingw/Makefile.am @@ -19,7 +19,7 @@ TARGET := $(word 1,$(subst -, ,$(TUPLE))) DEF_SUFFIX := $(if $(TARGET:x86_64=),.def,.def64) .PHONY: all -all: dwmapi-delaylib.lib virtdisk-delaylib.lib wintrust-delaylib.lib +all: dwmapi-delaylib.lib wintrust-delaylib.lib %.def64: %.def $(AM_V_SED) "s/@.*//" $< >$@ diff --git a/.mingw/Makefile.in b/.mingw/Makefile.in index fbdc2ce6..0af7e49c 100644 --- a/.mingw/Makefile.in +++ b/.mingw/Makefile.in @@ -367,7 +367,7 @@ uninstall-am: .PHONY: all -all: dwmapi-delaylib.lib virtdisk-delaylib.lib wintrust-delaylib.lib +all: dwmapi-delaylib.lib wintrust-delaylib.lib %.def64: %.def $(AM_V_SED) "s/@.*//" $< >$@ diff --git a/.mingw/virtdisk.def b/.mingw/virtdisk.def deleted file mode 100644 index 4476016c..00000000 --- a/.mingw/virtdisk.def +++ /dev/null @@ -1,7 +0,0 @@ -EXPORTS - AttachVirtualDisk@24 - GetVirtualDiskPhysicalPath@12 - DetachVirtualDisk@12 - OpenVirtualDisk@24 - CreateVirtualDisk@36 - GetVirtualDiskOperationProgress@12 diff --git a/.vs/rufus.vcxproj b/.vs/rufus.vcxproj index a4128ac7..6693b2f2 100644 --- a/.vs/rufus.vcxproj +++ b/.vs/rufus.vcxproj @@ -133,12 +133,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator true Windows MachineX86 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -162,12 +162,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator true Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\arm - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -193,12 +193,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator true Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.16299.0\um\arm64 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -229,12 +229,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator true Windows MachineX64 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -260,13 +260,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator false Windows MachineX86 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -292,13 +292,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator false Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\arm /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -326,13 +326,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator false Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.16299.0\um\arm64 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -363,13 +363,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;virtdisk.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator false Windows MachineX64 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;virtdisk.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) diff --git a/src/Makefile.am b/src/Makefile.am index ef95f391..59dfabd3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,10 @@ SUBDIRS = ../.mingw bled ext2fs ms-sys syslinux/libfat syslinux/libinstaller sys # As far as I can tell, the following libraries are *not* vulnerable to side-loading, so we link using their regular version: NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomdlg32 -lcomctl32 -luuid # The following libraries are vulnerable (or have an unknown vulnerability status), so we link using our delay-loaded replacement: -VULNERABLE_LIBS = -ldwmapi-delaylib -lvirtdisk-delaylib -lwintrust-delaylib +# Ideally there would also be virtdisk and wininet as delaylib's below, but the MinGW folks haven't quite sorted out delay-loading +# for x86_32 so as soon as you try to call APIs from these, the application will crash! +# See https://github.com/pbatard/rufus/issues/1877#issuecomment-1109683039 as well as https://github.com/pbatard/rufus/issues/2272 +VULNERABLE_LIBS = -ldwmapi-delaylib -lwintrust-delaylib noinst_PROGRAMS = rufus diff --git a/src/Makefile.in b/src/Makefile.in index e83fd54a..0fe9fa5a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -277,7 +277,10 @@ SUBDIRS = ../.mingw bled ext2fs ms-sys syslinux/libfat syslinux/libinstaller sys # As far as I can tell, the following libraries are *not* vulnerable to side-loading, so we link using their regular version: NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomdlg32 -lcomctl32 -luuid # The following libraries are vulnerable (or have an unknown vulnerability status), so we link using our delay-loaded replacement: -VULNERABLE_LIBS = -ldwmapi-delaylib -lvirtdisk-delaylib -lwintrust-delaylib +# Ideally there would also be virtdisk and wininet as delaylib's below, but the MinGW folks haven't quite sorted out delay-loading +# for x86_32 so as soon as you try to call APIs from these, the application will crash! +# See https://github.com/pbatard/rufus/issues/1877#issuecomment-1109683039 as well as https://github.com/pbatard/rufus/issues/2272 +VULNERABLE_LIBS = -ldwmapi-delaylib -lwintrust-delaylib AM_V_WINDRES_0 = @echo " RC $@";$(WINDRES) AM_V_WINDRES_1 = $(WINDRES) AM_V_WINDRES_ = $(AM_V_WINDRES_$(AM_DEFAULT_VERBOSITY)) diff --git a/src/iso.c b/src/iso.c index 4372a249..a8db0e68 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1710,6 +1710,15 @@ out: return ret; } +// VirtDisk API Prototypes since we can't use delay-loading because of MinGW +// See https://github.com/pbatard/rufus/issues/2272#issuecomment-1615976013 +PF_TYPE_DECL(WINAPI, DWORD, OpenVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR, + VIRTUAL_DISK_ACCESS_MASK, OPEN_VIRTUAL_DISK_FLAG, POPEN_VIRTUAL_DISK_PARAMETERS, PHANDLE)); +PF_TYPE_DECL(WINAPI, DWORD, AttachVirtualDisk, (HANDLE, PSECURITY_DESCRIPTOR, + ATTACH_VIRTUAL_DISK_FLAG, ULONG, PATTACH_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED)); +PF_TYPE_DECL(WINAPI, DWORD, DetachVirtualDisk, (HANDLE, DETACH_VIRTUAL_DISK_FLAG, ULONG)); +PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskPhysicalPath, (HANDLE, PULONG, PWSTR)); + static char physical_path[128] = ""; static HANDLE mounted_handle = INVALID_HANDLE_VALUE; @@ -1723,10 +1732,14 @@ char* MountISO(const char* path) wconvert(path); char* ret = NULL; + PF_INIT_OR_OUT(OpenVirtualDisk, VirtDisk); + PF_INIT_OR_OUT(AttachVirtualDisk, VirtDisk); + PF_INIT_OR_OUT(GetVirtualDiskPhysicalPath, VirtDisk); + if ((mounted_handle != NULL) && (mounted_handle != INVALID_HANDLE_VALUE)) UnMountISO(); - r = OpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO, + r = pfOpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO, OPEN_VIRTUAL_DISK_FLAG_NONE, NULL, &mounted_handle); if (r != ERROR_SUCCESS) { SetLastError(r); @@ -1735,7 +1748,7 @@ char* MountISO(const char* path) } vparams.Version = ATTACH_VIRTUAL_DISK_VERSION_1; - r = AttachVirtualDisk(mounted_handle, NULL, ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY | + r = pfAttachVirtualDisk(mounted_handle, NULL, ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY | ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER, 0, &vparams, NULL); if (r != ERROR_SUCCESS) { SetLastError(r); @@ -1743,7 +1756,7 @@ char* MountISO(const char* path) goto out; } - r = GetVirtualDiskPhysicalPath(mounted_handle, &size, wtmp); + r = pfGetVirtualDiskPhysicalPath(mounted_handle, &size, wtmp); if (r != ERROR_SUCCESS) { SetLastError(r); uprintf("Could not obtain physical path for mounted ISO '%s': %s", path, WindowsErrorString()); @@ -1761,10 +1774,12 @@ out: void UnMountISO(void) { + PF_INIT_OR_OUT(DetachVirtualDisk, VirtDisk); + if ((mounted_handle == NULL) || (mounted_handle == INVALID_HANDLE_VALUE)) goto out; - DetachVirtualDisk(mounted_handle, DETACH_VIRTUAL_DISK_FLAG_NONE, 0); + pfDetachVirtualDisk(mounted_handle, DETACH_VIRTUAL_DISK_FLAG_NONE, 0); safe_closehandle(mounted_handle); out: physical_path[0] = 0; diff --git a/src/rufus.rc b/src/rufus.rc index 60c3802b..1c81b27e 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2060" +CAPTION "Rufus 4.2.2061" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2060,0 - PRODUCTVERSION 4,2,2060,0 + FILEVERSION 4,2,2061,0 + PRODUCTVERSION 4,2,2061,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2060" + VALUE "FileVersion", "4.2.2061" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2060" + VALUE "ProductVersion", "4.2.2061" END END BLOCK "VarFileInfo" diff --git a/src/vhd.c b/src/vhd.c index 94c63188..9ef94647 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -885,6 +885,13 @@ BOOL WimApplyImage(const char* image, int index, const char* dst) return dw; } +// VirtDisk API Prototypes since we can't use delay-loading because of MinGW +PF_TYPE_DECL(WINAPI, DWORD, CreateVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR, + VIRTUAL_DISK_ACCESS_MASK, PSECURITY_DESCRIPTOR, CREATE_VIRTUAL_DISK_FLAG, ULONG, + PCREATE_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED, PHANDLE)); +PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskOperationProgress, (HANDLE, LPOVERLAPPED, + PVIRTUAL_DISK_PROGRESS)); + // Since we no longer have to deal with Windows 7, we can call on CreateVirtualDisk() // to backup a physical disk to VHD/VHDX. Now if this could also be used to create an // ISO from optical media that would be swell, but no matter what I tried, it didn't @@ -893,14 +900,17 @@ static DWORD WINAPI SaveImageThread(void* param) { IMG_SAVE* img_save = (IMG_SAVE*)param; HANDLE handle = INVALID_HANDLE_VALUE; + WCHAR* wSrc = utf8_to_wchar(img_save->DevicePath); WCHAR* wDst = utf8_to_wchar(img_save->ImagePath); - WCHAR* wSrc = utf8_to_wchar(GetPhysicalName(img_save->DeviceNum)); VIRTUAL_STORAGE_TYPE vtype = { img_save->Type, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT }; STOPGAP_CREATE_VIRTUAL_DISK_PARAMETERS vparams = { 0 }; VIRTUAL_DISK_PROGRESS vprogress = { 0 }; OVERLAPPED overlapped = { 0 }; DWORD result, flags; + PF_INIT_OR_OUT(CreateVirtualDisk, VirtDisk); + PF_INIT_OR_OUT(GetVirtualDiskOperationProgress, VirtDisk); + assert(img_save->Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHD || img_save->Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHDX); @@ -919,7 +929,7 @@ static DWORD WINAPI SaveImageThread(void* param) flags = CREATE_VIRTUAL_DISK_FLAG_CREATE_BACKING_STORAGE; // The following ensures that VHD images are stored uncompressed and can // be used as DD images. - if (img_save->Type != VIRTUAL_STORAGE_TYPE_DEVICE_VHDX) + if (img_save->Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHD) flags |= CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION; // TODO: Use CREATE_VIRTUAL_DISK_FLAG_PREVENT_WRITES_TO_SOURCE_DISK? @@ -928,7 +938,7 @@ static DWORD WINAPI SaveImageThread(void* param) // CreateVirtualDisk() does not have an overwrite flag... DeleteFileW(wDst); - result = CreateVirtualDisk(&vtype, wDst, VIRTUAL_DISK_ACCESS_NONE, NULL, + result = pfCreateVirtualDisk(&vtype, wDst, VIRTUAL_DISK_ACCESS_NONE, NULL, flags, 0, (PCREATE_VIRTUAL_DISK_PARAMETERS)&vparams, &overlapped, &handle); if (result != ERROR_SUCCESS && result != ERROR_IO_PENDING) { SetLastError(result); @@ -942,7 +952,7 @@ static DWORD WINAPI SaveImageThread(void* param) CancelIoEx(handle, &overlapped); goto out; } - if (GetVirtualDiskOperationProgress(handle, &overlapped, &vprogress) == ERROR_SUCCESS) { + if (pfGetVirtualDiskOperationProgress(handle, &overlapped, &vprogress) == ERROR_SUCCESS) { if (vprogress.OperationStatus == ERROR_IO_PENDING) UpdateProgressWithInfo(OP_FORMAT, MSG_261, vprogress.CurrentValue, vprogress.CompletionValue); } @@ -957,10 +967,11 @@ static DWORD WINAPI SaveImageThread(void* param) uprintf("Operation complete."); out: - safe_closehandle(handle); safe_closehandle(overlapped.hEvent); - safe_free(wDst); + safe_closehandle(handle); safe_free(wSrc); + safe_free(wDst); + safe_free(img_save->DevicePath); safe_free(img_save->ImagePath); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)TRUE, 0); ExitThread(0); @@ -980,12 +991,13 @@ void SaveVHD(void) static_sprintf(filename, "%s.vhdx", rufus_drive[DriveIndex].label); img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex); + img_save.DevicePath = GetPhysicalName(img_save.DeviceNum); img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); img_save.Type = safe_strstr(img_save.ImagePath, ".vhdx") != NULL ? VIRTUAL_STORAGE_TYPE_DEVICE_VHDX : VIRTUAL_STORAGE_TYPE_DEVICE_VHD; img_save.BufSize = DD_BUFFER_SIZE; img_save.DeviceSize = SelectedDrive.DiskSize; - if (img_save.ImagePath != NULL) { + if (img_save.DevicePath != NULL && img_save.ImagePath != NULL) { // Reset all progress bars SendMessage(hMainDialog, UM_PROGRESS_INIT, 0, 0); FormatStatus = 0; @@ -993,7 +1005,7 @@ void SaveVHD(void) if ((GetVolumePathNameA(img_save.ImagePath, path, sizeof(path))) && (GetDiskFreeSpaceExA(path, &free_space, NULL, NULL)) && ((LONGLONG)free_space.QuadPart > (SelectedDrive.DiskSize + 512))) { - // Disable all controls except cancel + // Disable all controls except Cancel EnableControls(FALSE, FALSE); FormatStatus = 0; InitProgress(TRUE); @@ -1005,6 +1017,7 @@ void SaveVHD(void) } else { uprintf("Unable to start VHD save thread"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_START_THREAD); + safe_free(img_save.DevicePath); safe_free(img_save.ImagePath); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); } @@ -1017,6 +1030,7 @@ void SaveVHD(void) uprintf("The VHD size is too large for the target drive"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_FILE_TOO_LARGE; } + safe_free(img_save.DevicePath); safe_free(img_save.ImagePath); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); } From 0b1c68635ad61a6bdf24b010feab0a0982dd323a Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 3 Jul 2023 23:57:02 +0100 Subject: [PATCH 02/53] [vhd] add experimental save to Full Flash Update (FFU) image support * Full Flash Update (FFU) image support was added to dism with Windows 10 1709 and is an alternate way to save a virtual hard disk for restoration. * While more modern than VHD/VHDX, FFU creation only works for drives with file systems that Windows natively recognizes (FAT, NTFS) and that look like Windows installation media, so you can forget about FFU'ing a Linux disk. * The other *intentional* drawback that Microsoft added is that they don't want anybody but themselves being able to create and restore FFU images, so, even as they have nice FfuApplyImage()/FfuCaptureImage() calls in FfuProvider.dll they have decided not to make these public. * This means that, since we don't have time to spend on figuring and direct hooking internal DLL calls for x86_32, x86_64, ARM and ARM64 (and worrying that Microsoft may ever so slightly change their DLL between revs to break our hooks), we just call on dism.exe behind the scenes to create the FFU. --- res/loc/rufus.loc | 1 + src/dev.c | 2 +- src/drive.c | 6 ++--- src/drive.h | 2 +- src/iso.c | 4 +-- src/rufus.h | 5 ++-- src/rufus.rc | 10 +++---- src/stdfn.c | 61 ++++++++++++++++++++++++++++++++++++++----- src/vhd.c | 66 +++++++++++++++++++++++++++++++++++++---------- src/vhd.h | 3 +++ 10 files changed, 126 insertions(+), 34 deletions(-) diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 50295d9e..c24d45c0 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -605,6 +605,7 @@ t MSG_340 "a \"Security Violation\" screen" t MSG_341 "a Windows Recovery Screen (BSOD) with '%s'" t MSG_342 "Compressed VHDX Image" t MSG_343 "Uncompressed VHD Image" +t MSG_344 "Full Flash Update Image" # The following messages are for the Windows Store listing only and are not used by the application t MSG_900 "Rufus is a utility that helps format and create bootable USB flash drives, such as USB keys/pendrives, memory sticks, etc." t MSG_901 "Official site: %s" diff --git a/src/dev.c b/src/dev.c index 4a5d4e2c..856883f5 100644 --- a/src/dev.c +++ b/src/dev.c @@ -897,7 +897,7 @@ BOOL GetDevices(DWORD devnum) break; } - if (GetDriveLabel(drive_index, drive_letters, &label)) { + if (GetDriveLabel(drive_index, drive_letters, &label, FALSE)) { if ((props.is_SCSI) && (!props.is_UASP) && (!props.is_VHD)) { if (!props.is_Removable) { // Non removables should have been eliminated above, but since we diff --git a/src/drive.c b/src/drive.c index 13d21d96..f9f20bb0 100644 --- a/src/drive.c +++ b/src/drive.c @@ -1314,7 +1314,7 @@ BOOL IsDriveLetterInUse(const char drive_letter) * Return the drive letter and volume label * If the drive doesn't have a volume assigned, space is returned for the letter */ -BOOL GetDriveLabel(DWORD DriveIndex, char* letters, char** label) +BOOL GetDriveLabel(DWORD DriveIndex, char* letters, char** label, BOOL bSilent) { HANDLE hPhysical; DWORD size, error; @@ -1350,10 +1350,10 @@ BOOL GetDriveLabel(DWORD DriveIndex, char* letters, char** label) if (DeviceIoControl(hPhysical, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &size, NULL)) AutorunLabel = get_token_data_file("label", AutorunPath); else if (GetLastError() == ERROR_NOT_READY) - uprintf("Ignoring 'autorun.inf' label for drive %c: No media", toupper(letters[0])); + suprintf("Ignoring 'autorun.inf' label for drive %c: No media", toupper(letters[0])); safe_closehandle(hPhysical); if (AutorunLabel != NULL) { - uprintf("Using 'autorun.inf' label for drive %c: '%s'", toupper(letters[0]), AutorunLabel); + suprintf("Using 'autorun.inf' label for drive %c: '%s'", toupper(letters[0]), AutorunLabel); static_strcpy(VolumeLabel, AutorunLabel); safe_free(AutorunLabel); *label = VolumeLabel; diff --git a/src/drive.h b/src/drive.h index 16caa367..170408d5 100644 --- a/src/drive.h +++ b/src/drive.h @@ -396,7 +396,7 @@ UINT GetDriveTypeFromIndex(DWORD DriveIndex); char GetUnusedDriveLetter(void); BOOL IsDriveLetterInUse(const char drive_letter); char RemoveDriveLetters(DWORD DriveIndex, BOOL bUseLast, BOOL bSilent); -BOOL GetDriveLabel(DWORD DriveIndex, char* letter, char** label); +BOOL GetDriveLabel(DWORD DriveIndex, char* letters, char** label, BOOL bSilent); uint64_t GetDriveSize(DWORD DriveIndex); BOOL IsMediaPresent(DWORD DriveIndex); BOOL AnalyzeMBR(HANDLE hPhysicalDrive, const char* TargetName, BOOL bSilent); diff --git a/src/iso.c b/src/iso.c index a8db0e68..185af3f6 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1670,7 +1670,7 @@ BOOL DumpFatDir(const char* path, int32_t cluster) buf = libfat_get_sector(lf_fs, s); if (buf == NULL) FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_SECTOR_NOT_FOUND; - if (FormatStatus) + if (IS_ERROR(FormatStatus)) goto out; size = MIN(LIBFAT_SECTOR_SIZE, diritem.size - written); if (!WriteFileWithRetry(handle, buf, size, &size, WRITE_RETRIES) || @@ -1724,7 +1724,7 @@ static HANDLE mounted_handle = INVALID_HANDLE_VALUE; char* MountISO(const char* path) { - VIRTUAL_STORAGE_TYPE vtype = { 1, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT }; + VIRTUAL_STORAGE_TYPE vtype = { VIRTUAL_STORAGE_TYPE_DEVICE_ISO, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT }; ATTACH_VIRTUAL_DISK_PARAMETERS vparams = { 0 }; DWORD r; wchar_t wtmp[128]; diff --git a/src/rufus.h b/src/rufus.h index b5c24ca2..7bee5c06 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -450,7 +450,7 @@ typedef struct { * to define an 'ext_t my_extensions' variable initialized with the relevant attributes. */ typedef struct ext_t { - const size_t count; + size_t count; const char* filename; const char** extension; const char** description; @@ -652,7 +652,8 @@ extern char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options); extern BOOL FileIO(enum file_io_type io_type, char* path, char** buffer, DWORD* size); extern unsigned char* GetResource(HMODULE module, char* name, char* type, const char* desc, DWORD* len, BOOL duplicate); extern DWORD GetResourceSize(HMODULE module, char* name, char* type, const char* desc); -extern DWORD RunCommand(const char* cmdline, const char* dir, BOOL log); +extern DWORD RunCommandWithProgress(const char* cmdline, const char* dir, BOOL log, int msg); +#define RunCommand(cmd, dir, log) RunCommandWithProgress(cmd, dir, log, 0) extern BOOL CompareGUID(const GUID *guid1, const GUID *guid2); extern BOOL MountRegistryHive(const HKEY key, const char* pszHiveName, const char* pszHivePath); extern BOOL UnmountRegistryHive(const HKEY key, const char* pszHiveName); diff --git a/src/rufus.rc b/src/rufus.rc index 1c81b27e..4cb53e3c 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2061" +CAPTION "Rufus 4.2.2062" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2061,0 - PRODUCTVERSION 4,2,2061,0 + FILEVERSION 4,2,2062,0 + PRODUCTVERSION 4,2,2062,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2061" + VALUE "FileVersion", "4.2.2062" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2061" + VALUE "ProductVersion", "4.2.2062" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index 04727917..20fd172f 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -26,6 +26,7 @@ #include #include +#include "re.h" #include "rufus.h" #include "missing.h" #include "resource.h" @@ -706,14 +707,19 @@ DWORD GetResourceSize(HMODULE module, char* name, char* type, const char* desc) } // Run a console command, with optional redirection of stdout and stderr to our log -DWORD RunCommand(const char* cmd, const char* dir, BOOL log) +// as well as optional progress reporting if msg is not 0. +DWORD RunCommandWithProgress(const char* cmd, const char* dir, BOOL log, int msg) { - DWORD ret, dwRead, dwAvail, dwPipeSize = 4096; - STARTUPINFOA si = {0}; - PROCESS_INFORMATION pi = {0}; + DWORD i, ret, dwRead, dwAvail, dwPipeSize = 4096; + STARTUPINFOA si = { 0 }; + PROCESS_INFORMATION pi = { 0 }; SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; HANDLE hOutputRead = INVALID_HANDLE_VALUE, hOutputWrite = INVALID_HANDLE_VALUE; + int match_length; static char* output; + // For detecting typical dism.exe commandline progress report of type: + // "\r[==== 8.0% ]\r\n" + re_t pattern = re_compile("\\s*\\[[= ]+[\\d\\.]+%[= ]+\\]\\s*"); si.cb = sizeof(si); if (log) { @@ -737,16 +743,56 @@ DWORD RunCommand(const char* cmd, const char* dir, BOOL log) goto out; } - if (log) { + if (log || msg != 0) { + if (msg != 0) + UpdateProgressWithInfoInit(NULL, FALSE); while (1) { + // Check for user cancel + if (IS_ERROR(FormatStatus) && (SCODE_CODE(FormatStatus) == ERROR_CANCELLED)) { + if (!TerminateProcess(pi.hProcess, ERROR_CANCELLED)) { + uprintf("Could not terminate command: %s", WindowsErrorString()); + } else switch (WaitForSingleObject(pi.hProcess, 5000)) { + case WAIT_TIMEOUT: + uprintf("Command did not terminate within timeout duration"); + break; + case WAIT_OBJECT_0: + uprintf("Command was terminated by user"); + break; + default: + uprintf("Error while waiting for command to be terminated: %s", WindowsErrorString()); + break; + } + ret = ERROR_CANCELLED; + goto out; + } // coverity[string_null] if (PeekNamedPipe(hOutputRead, NULL, dwPipeSize, NULL, &dwAvail, NULL)) { if (dwAvail != 0) { output = malloc(dwAvail + 1); if ((output != NULL) && (ReadFile(hOutputRead, output, dwAvail, &dwRead, NULL)) && (dwRead != 0)) { output[dwAvail] = 0; - // output may contain a '%' so don't feed it as a naked format string - uprintf("%s", output); + // Process a commandline progress bar into a percentage + if ((msg != 0) && (re_matchp(pattern, output, &match_length) != -1)) { + float f = 0.0f; + i = 0; +next_progress_line: + for (; (i < dwAvail) && (output[i] < '0' || output[i] > '9'); i++); + IGNORE_RETVAL(sscanf(&output[i], "%f*", &f)); + UpdateProgressWithInfo(OP_FORMAT, msg, (uint64_t)(f * 100.0f), 100 * 100ULL); + // Go to next line + while ((++i < dwAvail) && (output[i] != '\n') && (output[i] != '\r')); + while ((++i < dwAvail) && ((output[i] == '\n') || (output[i] == '\r'))); + // Print additional lines, if any + if (i < dwAvail) { + // Might have two consecutive progress lines in our buffer + if (re_matchp(pattern, &output[i], &match_length) != -1) + goto next_progress_line; + uprintf("%s", &output[i]); + } + } else if (log) { + // output may contain a '%' so don't feed it as a naked format string + uprintf("%s", output); + } } free(output); } @@ -756,6 +802,7 @@ DWORD RunCommand(const char* cmd, const char* dir, BOOL log) Sleep(100); }; } else { + // TODO: Detect user cancellation here? WaitForSingleObject(pi.hProcess, INFINITE); } diff --git a/src/vhd.c b/src/vhd.c index 9ef94647..844f8e92 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -896,7 +896,7 @@ PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskOperationProgress, (HANDLE, LPOVERLAPP // to backup a physical disk to VHD/VHDX. Now if this could also be used to create an // ISO from optical media that would be swell, but no matter what I tried, it didn't // seem possible... -static DWORD WINAPI SaveImageThread(void* param) +static DWORD WINAPI SaveVHDThread(void* param) { IMG_SAVE* img_save = (IMG_SAVE*)param; HANDLE handle = INVALID_HANDLE_VALUE; @@ -906,7 +906,7 @@ static DWORD WINAPI SaveImageThread(void* param) STOPGAP_CREATE_VIRTUAL_DISK_PARAMETERS vparams = { 0 }; VIRTUAL_DISK_PROGRESS vprogress = { 0 }; OVERLAPPED overlapped = { 0 }; - DWORD result, flags; + DWORD r = ERROR_NOT_FOUND, flags; PF_INIT_OR_OUT(CreateVirtualDisk, VirtDisk); PF_INIT_OR_OUT(GetVirtualDiskOperationProgress, VirtDisk); @@ -938,16 +938,16 @@ static DWORD WINAPI SaveImageThread(void* param) // CreateVirtualDisk() does not have an overwrite flag... DeleteFileW(wDst); - result = pfCreateVirtualDisk(&vtype, wDst, VIRTUAL_DISK_ACCESS_NONE, NULL, + r = pfCreateVirtualDisk(&vtype, wDst, VIRTUAL_DISK_ACCESS_NONE, NULL, flags, 0, (PCREATE_VIRTUAL_DISK_PARAMETERS)&vparams, &overlapped, &handle); - if (result != ERROR_SUCCESS && result != ERROR_IO_PENDING) { - SetLastError(result); + if (r != ERROR_SUCCESS && r != ERROR_IO_PENDING) { + SetLastError(r); uprintf("Could not create virtual disk: %s", WindowsErrorString()); goto out; } - if (result == ERROR_IO_PENDING) { - while ((result = WaitForSingleObject(overlapped.hEvent, 100)) == WAIT_TIMEOUT) { + if (r == ERROR_IO_PENDING) { + while ((r = WaitForSingleObject(overlapped.hEvent, 100)) == WAIT_TIMEOUT) { if (IS_ERROR(FormatStatus) && (SCODE_CODE(FormatStatus) == ERROR_CANCELLED)) { CancelIoEx(handle, &overlapped); goto out; @@ -957,12 +957,13 @@ static DWORD WINAPI SaveImageThread(void* param) UpdateProgressWithInfo(OP_FORMAT, MSG_261, vprogress.CurrentValue, vprogress.CompletionValue); } } - if (result != WAIT_OBJECT_0) { + if (r != WAIT_OBJECT_0) { uprintf("Could not save virtual disk: %s", WindowsErrorString()); goto out; } } + r = 0; UpdateProgressWithInfo(OP_FORMAT, MSG_261, SelectedDrive.DiskSize, SelectedDrive.DiskSize); uprintf("Operation complete."); @@ -974,7 +975,36 @@ out: safe_free(img_save->DevicePath); safe_free(img_save->ImagePath); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)TRUE, 0); - ExitThread(0); + ExitThread(r); +} + +// FfuProvider.dll has some nice FfuApplyImage()/FfuCaptureImage() calls... which +// Microsoft decided not make public! +// Considering that trying to both figure out how to use these internal function +// calls, as well as how to properly hook into the DLL for every arch/every release +// of Windows, would be a massive timesink, we just take a shortcut by calling dism +// directly, as imperfect as such a solution might be... +static DWORD WINAPI SaveFFUThread(void* param) +{ + DWORD r; + IMG_SAVE* img_save = (IMG_SAVE*)param; + char cmd[MAX_PATH + 128], *letter = "", *label; + + GetDriveLabel(SelectedDrive.DeviceNumber, letter, &label, TRUE); + static_sprintf(cmd, "dism /capture-ffu /capturedrive=%s /imagefile=\"%s\" " + "/name:\"%s\" /description:\"Created by %s (%s)\"", + img_save->DevicePath, img_save->ImagePath, label, APPLICATION_NAME, RUFUS_URL); + uprintf("Running command: '%s", cmd); + r = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261); + if (r != 0 && !IS_ERROR(FormatStatus)) { + SetLastError(r); + uprintf("Failed to create FFU image: %s", WindowsErrorString()); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_WINDOWS) | SCODE_CODE(r); + } + safe_free(img_save->DevicePath); + safe_free(img_save->ImagePath); + PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)TRUE, 0); + ExitThread(r); } void SaveVHD(void) @@ -983,7 +1013,8 @@ void SaveVHD(void) char filename[128]; char path[MAX_PATH]; int DriveIndex = ComboBox_GetCurSel(hDeviceList); - EXT_DECL(img_ext, filename, __VA_GROUP__("*.vhdx", "*.vhd"), __VA_GROUP__(lmprintf(MSG_342), lmprintf(MSG_343))); + EXT_DECL(img_ext, filename, __VA_GROUP__("*.vhdx", "*.vhd", "*.ffu"), + __VA_GROUP__(lmprintf(MSG_342), lmprintf(MSG_343), lmprintf(MSG_344))); ULARGE_INTEGER free_space; if ((DriveIndex < 0) || (format_thread != NULL)) @@ -992,9 +1023,17 @@ void SaveVHD(void) static_sprintf(filename, "%s.vhdx", rufus_drive[DriveIndex].label); img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex); img_save.DevicePath = GetPhysicalName(img_save.DeviceNum); + // FFU support started with Windows 10 1709 (build 16299) and requires GPT + // TODO: Better check for FFU compatibility + if (WindowsVersion.Major < 10 || WindowsVersion.BuildNumber < 16299 || + SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) + img_ext.count = 2; img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); - img_save.Type = safe_strstr(img_save.ImagePath, ".vhdx") != NULL ? - VIRTUAL_STORAGE_TYPE_DEVICE_VHDX : VIRTUAL_STORAGE_TYPE_DEVICE_VHD; + img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; + if (safe_strstr(img_save.ImagePath, ".vhdx") != NULL) + img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; + else if (safe_strstr(img_save.ImagePath, ".ffu") != NULL) + img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_FFU; img_save.BufSize = DD_BUFFER_SIZE; img_save.DeviceSize = SelectedDrive.DiskSize; if (img_save.DevicePath != NULL && img_save.ImagePath != NULL) { @@ -1009,7 +1048,8 @@ void SaveVHD(void) EnableControls(FALSE, FALSE); FormatStatus = 0; InitProgress(TRUE); - format_thread = CreateThread(NULL, 0, SaveImageThread, &img_save, 0, NULL); + format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? + SaveFFUThread : SaveVHDThread, &img_save, 0, NULL); if (format_thread != NULL) { uprintf("\r\nSave to VHD operation started"); PrintInfo(0, -1); diff --git a/src/vhd.h b/src/vhd.h index 4eee18a8..6609b4aa 100644 --- a/src/vhd.h +++ b/src/vhd.h @@ -72,7 +72,10 @@ #define MBR_SIZE 512 // Might need to review this once we see bootable 4k systems // TODO: Remove this once MinGW has been updated +#ifndef VIRTUAL_STORAGE_TYPE_DEVICE_VHDX #define VIRTUAL_STORAGE_TYPE_DEVICE_VHDX 3 +#endif +#define VIRTUAL_STORAGE_TYPE_DEVICE_FFU 99 #define CREATE_VIRTUAL_DISK_VERSION_2 2 #define CREATE_VIRTUAL_DISK_FLAG_CREATE_BACKING_STORAGE 8 From 10dbfdd7e110c1b7f1a6d5e4eea35159293c5383 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 4 Jul 2023 13:26:04 +0100 Subject: [PATCH 03/53] [core] make sure the main partition size is aligned to the cluster size * Having NTFS partitions properly aligned to the cluster size can drastically improve the speed of capturing a disk to an FFU image, as, when the size is aligned, the FFU capture uses the actual NTFS allocation map to only read and capture the clusters that are actually in use, whereas, if no aligned, FFU capture falls back to a sector by sector copy that is both much slower and also includes unwanted leftover garbage. * Also only enable FFU when we detect FFUProvider.dll as a system library. --- src/drive.c | 21 ++++++++++++++++----- src/rufus.rc | 10 +++++----- src/vhd.c | 7 +++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/drive.c b/src/drive.c index f9f20bb0..d51fb4d3 100644 --- a/src/drive.c +++ b/src/drive.c @@ -2269,8 +2269,8 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m const DWORD size_to_clear = MAX_SECTORS_TO_CLEAR * SelectedDrive.SectorSize; uint8_t* buffer; size_t uefi_ntfs_size = 0; - CREATE_DISK CreateDisk = {PARTITION_STYLE_RAW, {{0}}}; - DRIVE_LAYOUT_INFORMATION_EX4 DriveLayoutEx = {0}; + CREATE_DISK CreateDisk = { PARTITION_STYLE_RAW, { { 0 } } }; + DRIVE_LAYOUT_INFORMATION_EX4 DriveLayoutEx = { 0 }; BOOL r; DWORD i, size, bufsize, pn = 0; LONGLONG main_part_size_in_sectors, extra_part_size_in_tracks = 0; @@ -2278,9 +2278,13 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/configure-uefigpt-based-hard-drive-partitions // and folks using MacOS: https://github.com/pbatard/rufus/issues/979 LONGLONG esp_size = 260 * MB; + LONGLONG ClusterSize = (LONGLONG)ComboBox_GetCurItemData(hClusterSize); PrintInfoDebug(0, MSG_238, PartitionTypeName[partition_style]); + if (ClusterSize == 0) + ClusterSize = 0x200; + if (partition_style == PARTITION_STYLE_SFD) // Nothing to do return TRUE; @@ -2307,9 +2311,6 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // CHS sizes that IBM imparted upon us. Long story short, we now align to a // cylinder size that is itself aligned to the cluster size. // If this actually breaks old systems, please send your complaints to IBM. - LONGLONG ClusterSize = (LONGLONG)ComboBox_GetCurItemData(hClusterSize); - if (ClusterSize == 0) - ClusterSize = 0x200; DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = ((bytes_per_track + (ClusterSize - 1)) / ClusterSize) * ClusterSize; // GRUB2 no longer fits in the usual 31½ KB that the above computation provides @@ -2399,6 +2400,16 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m main_part_size_in_sectors = ((main_part_size_in_sectors / SelectedDrive.SectorsPerTrack) - extra_part_size_in_tracks) * SelectedDrive.SectorsPerTrack; } + // Try to make sure that the main partition size is a multiple of the cluster size + // This can be especially important when trying to capture an NTFS partition as FFU, as, when + // the NTFS partition is aligned to cluster size, the FFU capture parses the NTFS allocated + // map to only record clusters that are in use, whereas, if not aligned, the FFU capture uses + // a full sector by sector scan of the NTFS partition and records any non-zero garbage, which + // may include garbage leftover data from a previous reformat... + if (ClusterSize % SelectedDrive.SectorSize == 0) { + main_part_size_in_sectors = (((main_part_size_in_sectors * SelectedDrive.SectorSize) / + ClusterSize) * ClusterSize) / SelectedDrive.SectorSize; + } if (main_part_size_in_sectors <= 0) { uprintf("Error: Invalid %S size", main_part_name); return FALSE; diff --git a/src/rufus.rc b/src/rufus.rc index 4cb53e3c..43b44be4 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2062" +CAPTION "Rufus 4.2.2063" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2062,0 - PRODUCTVERSION 4,2,2062,0 + FILEVERSION 4,2,2063,0 + PRODUCTVERSION 4,2,2063,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2062" + VALUE "FileVersion", "4.2.2063" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2062" + VALUE "ProductVersion", "4.2.2063" END END BLOCK "VarFileInfo" diff --git a/src/vhd.c b/src/vhd.c index 844f8e92..6c6abfc0 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -1023,10 +1023,9 @@ void SaveVHD(void) static_sprintf(filename, "%s.vhdx", rufus_drive[DriveIndex].label); img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex); img_save.DevicePath = GetPhysicalName(img_save.DeviceNum); - // FFU support started with Windows 10 1709 (build 16299) and requires GPT - // TODO: Better check for FFU compatibility - if (WindowsVersion.Major < 10 || WindowsVersion.BuildNumber < 16299 || - SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) + // FFU support started with Windows 10 1709 (through FfuProvider.dll) and requires GPT + static_sprintf(path, "%s\\dism\\FfuProvider.dll", sysnative_dir); + if ((_accessU(path, 0) != 0) || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) img_ext.count = 2; img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; From bb66dfc4922b9335fd3ff240be23f08c89ec11f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98E=C3=98=5F=CE=99=CE=99=C3=98Z?= <22034115+Neo1102@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:56:18 +0800 Subject: [PATCH 04/53] [loc] update Traditional Chinese translation * Closes #2274. --- res/loc/po/zh-TW.po | 48 ++++++++++++++++++++++----------------------- res/loc/rufus.loc | 48 ++++++++++++++++++++++----------------------- src/rufus.rc | 10 +++++----- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/res/loc/po/zh-TW.po b/res/loc/po/zh-TW.po index 13d26736..6c5bef7c 100644 --- a/res/loc/po/zh-TW.po +++ b/res/loc/po/zh-TW.po @@ -1802,43 +1802,43 @@ msgstr "ç„¡æ³•é–‹å•Ÿæˆ–è®€å– '%s'" #. • MSG_325 msgid "Applying Windows customization: %s" -msgstr "正在應用 Windows 自定義設置:%s" +msgstr "正在套用 Windows 客製化設定:%s" #. • MSG_326 msgid "Applying user options..." -msgstr "正在應用用戶設置..." +msgstr "正在套用使用者設定..." #. • MSG_327 msgid "Windows User Experience" -msgstr "Windows 用戶體驗" +msgstr "Windows 使用者體驗" #. • MSG_328 msgid "Customize Windows installation?" -msgstr "自定義 Windows 安è£ï¼Ÿ" +msgstr "客製化 Windows 安è£ï¼Ÿ" #. • MSG_329 msgid "Remove requirement for 4GB+ RAM, Secure Boot and TPM 2.0" -msgstr "ç§»é™¤å° 4GB+ å…§å­˜ã€å®‰å…¨å¼•å°Žå’Œ TPM 2.0 çš„è¦æ±‚" +msgstr "ç§»é™¤å° 4GB+ 記憶體ã€å®‰å…¨é–‹æ©Ÿå’Œ TPM 2.0 çš„è¦æ±‚" #. • MSG_330 msgid "Remove requirement for an online Microsoft account" -msgstr "移除å°ç™»éŒ„å¾®è»Ÿè³¬æˆ¶çš„è¦æ±‚" +msgstr "å°‡ç™»å…¥å¾®è»Ÿå¸³è™Ÿçš„è¦æ±‚移除" #. • MSG_331 msgid "Disable data collection (Skip privacy questions)" -msgstr "關閉數據收集 (è·³éŽéš±ç§è¨­ç½®)" +msgstr "關閉資料收集 (è·³éŽéš±ç§è¨­å®š)" #. • MSG_332 msgid "Prevent Windows To Go from accessing internal disks" -msgstr "阻止 Windows To Go 訪å•內部ç£ç›¤" +msgstr "防止 Windows To Go å­˜å–內部ç£ç¢Ÿ" #. • MSG_333 msgid "Create a local account with username:" -msgstr "創建一個使用此用戶å的本地賬號:" +msgstr "使用此使用者å建立一個本機帳戶:" #. • MSG_334 msgid "Set regional options to the same values as this user's" -msgstr "使用當å‰ç”¨æˆ¶çš„å€åŸŸè¨­ç½®" +msgstr "使用目å‰ç”¨æˆ¶çš„å€åŸŸè¨­å®š" #. • MSG_335 msgid "Disable BitLocker automatic device encryption" @@ -1860,7 +1860,7 @@ msgstr "官方網站: %s" #. • MSG_902 msgid "Source Code: %s" -msgstr "æºä»£ç¢¼: %s" +msgstr "æºå§‹ç¢¼: %s" #. • MSG_903 msgid "ChangeLog: %s" @@ -1887,52 +1887,52 @@ msgstr "啟動" #. #. This and subsequent messages will be listed in the 'Features' section of the Windows Store page msgid "Format USB, flash card and virtual drives to FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3" -msgstr "å°‡ U 盤ã€å­˜å„²å¡æˆ–虛擬驅動器格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼" +msgstr "將隨身碟ã€è¨˜æ†¶å¡æˆ–虛擬光碟機格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼" #. • MSG_911 msgid "Create FreeDOS bootable USB drives" -msgstr "創建 FreeDOS å¯å•Ÿå‹•驅動器" +msgstr "建立 FreeDOS å¯å•Ÿéš¨èº«ç¢Ÿ" #. • MSG_912 msgid "Create bootable drives from bootable ISOs (Windows, Linux, etc.)" -msgstr "從å¯å•Ÿå‹• ISO 文件 (Windows å’Œ Linux ç­‰) 創建å¯å•Ÿå‹•驅動器" +msgstr "從å¯å•Ÿå‹• ISO 檔案 (Windows å’Œ Linux ç­‰) 建立å¯å•Ÿå‹•隨身碟" #. • MSG_913 msgid "Create bootable drives from bootable disk images, including compressed ones" -msgstr "從å¯å•Ÿå‹•硬盤é¡åƒ (包括壓縮é¡åƒ) 創建å¯å•Ÿå‹•驅動器" +msgstr "從å¯å•Ÿå‹•ç¡¬ç¢Ÿæ˜ åƒæª” (åŒ…æ‹¬å£“ç¸®æ˜ åƒæª”) 建立å¯å•Ÿå‹•隨身碟" #. • MSG_914 msgid "Create BIOS or UEFI bootable drives, including UEFI bootable NTFS" -msgstr "創建 BIOS 或 UEFI å¯å•Ÿå‹•驅動器,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 驅動器" +msgstr "建立 BIOS 或 UEFI å¯å•Ÿå‹•隨身碟,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 隨身碟" #. • MSG_915 msgid "Create 'Windows To Go' drives" -msgstr "創建 'Windows To Go' é©…å‹•" +msgstr "建立 'Windows To Go' 隨身碟" #. • MSG_916 msgid "Create Windows 11 installation drives for PCs that don't have TPM or Secure Boot" -msgstr "為沒有 TPM 或安全啟動功能的電腦創建 Windows 11 安è£é©…å‹•" +msgstr "為沒有 TPM 或安全開機功能的電腦建立 Windows 11 安è£éš¨èº«ç¢Ÿ" #. • MSG_917 msgid "Create persistent Linux partitions" -msgstr "創建æŒä¹… Linux 分å€" +msgstr "建立æŒçºŒæ€§ Linux ç£å€" #. • MSG_918 msgid "Create VHD/DD images of the selected drive" -msgstr "為é¸ä¸­çš„驅動創建 VHD/DD é¡åƒ" +msgstr "為é¸ä¸­çš„ç£ç¢Ÿæ©Ÿå»ºç«‹ VHD/DD æ˜ åƒæª”" #. • MSG_919 msgid "Compute MD5, SHA-1, SHA-256 and SHA-512 checksums of the selected image" -msgstr "計算被é¸ä¸­é¡åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 校驗碼" +msgstr "計算被é¸ä¸­æ˜ åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 檢查碼" #. • MSG_920 msgid "Perform bad blocks checks, including detection of \"fake\" flash drives" -msgstr "執行壞塊檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„æª¢æ¸¬" +msgstr "執行壞軌檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„嵿¸¬" #. • MSG_921 msgid "Download official Microsoft Windows retail ISOs" -msgstr "下載微軟官方 Windows é¡åƒ" +msgstr "下載微軟官方 Windows æ˜ åƒæª”" #. • MSG_922 msgid "Download UEFI Shell ISOs" -msgstr "下載 UEFI Shell é¡åƒ" +msgstr "下載 UEFI Shell æ˜ åƒæª”" diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index c24d45c0..73394841 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -2253,37 +2253,37 @@ t MSG_319 "忽略 Boot Marker" t MSG_320 "æ­£åœ¨é‡æ–°æ•´ç†ç£å€åˆ†å‰² (%s)..." t MSG_321 "你鏿“‡çš„æ˜ åƒæª”是 ISOHybrid,但是檔案作者沒有使之與 ISO/檔案複製模å¼ç›¸å®¹ã€‚\n因此強制使用 DD 映åƒå¯«å…¥æ¨¡å¼ã€‚" t MSG_322 "ç„¡æ³•é–‹å•Ÿæˆ–è®€å– '%s'" -t MSG_325 "正在應用 Windows 自定義設置:%s" -t MSG_326 "正在應用用戶設置..." -t MSG_327 "Windows 用戶體驗" -t MSG_328 "自定義 Windows 安è£ï¼Ÿ" -t MSG_329 "ç§»é™¤å° 4GB+ å…§å­˜ã€å®‰å…¨å¼•å°Žå’Œ TPM 2.0 çš„è¦æ±‚" -t MSG_330 "移除å°ç™»éŒ„å¾®è»Ÿè³¬æˆ¶çš„è¦æ±‚" -t MSG_331 "關閉數據收集 (è·³éŽéš±ç§è¨­ç½®)" -t MSG_332 "阻止 Windows To Go 訪å•內部ç£ç›¤" -t MSG_333 "創建一個使用此用戶å的本地賬號:" -t MSG_334 "使用當å‰ç”¨æˆ¶çš„å€åŸŸè¨­ç½®" +t MSG_325 "正在套用 Windows 客製化設定:%s" +t MSG_326 "正在套用使用者設定..." +t MSG_327 "Windows 使用者體驗" +t MSG_328 "客製化 Windows 安è£ï¼Ÿ" +t MSG_329 "ç§»é™¤å° 4GB+ 記憶體ã€å®‰å…¨é–‹æ©Ÿå’Œ TPM 2.0 çš„è¦æ±‚" +t MSG_330 "å°‡ç™»å…¥å¾®è»Ÿå¸³è™Ÿçš„è¦æ±‚移除" +t MSG_331 "關閉資料收集 (è·³éŽéš±ç§è¨­å®š)" +t MSG_332 "防止 Windows To Go å­˜å–內部ç£ç¢Ÿ" +t MSG_333 "使用此使用者å建立一個本機帳戶:" +t MSG_334 "使用目å‰ç”¨æˆ¶çš„å€åŸŸè¨­å®š" t MSG_335 "關閉 BitLocker 自動設備加密" t MSG_336 "æŒä¹…化日誌" t MSG_900 "Rufus 是個能格å¼åŒ–並製作å¯é–‹æ©Ÿ USB å¿«é–ƒç£ç¢Ÿæ©Ÿï¼ˆUSB 隨身碟ã€Memory Stick 等等)的工具。" t MSG_901 "官方網站: %s" -t MSG_902 "æºä»£ç¢¼: %s" +t MSG_902 "æºå§‹ç¢¼: %s" t MSG_903 "更新日誌: %s" t MSG_904 "本應用採用 GNU 通用公共許å¯è­‰ (GPL) 第三版。\n具體許å¯è­‰è¦‹ https://www.gnu.org/licenses/gpl-3.0.zh-tw.html。" t MSG_905 "啟動" -t MSG_910 "å°‡ U 盤ã€å­˜å„²å¡æˆ–虛擬驅動器格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼" -t MSG_911 "創建 FreeDOS å¯å•Ÿå‹•驅動器" -t MSG_912 "從å¯å•Ÿå‹• ISO 文件 (Windows å’Œ Linux ç­‰) 創建å¯å•Ÿå‹•驅動器" -t MSG_913 "從å¯å•Ÿå‹•硬盤é¡åƒ (包括壓縮é¡åƒ) 創建å¯å•Ÿå‹•驅動器" -t MSG_914 "創建 BIOS 或 UEFI å¯å•Ÿå‹•驅動器,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 驅動器" -t MSG_915 "創建 'Windows To Go' é©…å‹•" -t MSG_916 "為沒有 TPM 或安全啟動功能的電腦創建 Windows 11 安è£é©…å‹•" -t MSG_917 "創建æŒä¹… Linux 分å€" -t MSG_918 "為é¸ä¸­çš„驅動創建 VHD/DD é¡åƒ" -t MSG_919 "計算被é¸ä¸­é¡åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 校驗碼" -t MSG_920 "執行壞塊檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„æª¢æ¸¬" -t MSG_921 "下載微軟官方 Windows é¡åƒ" -t MSG_922 "下載 UEFI Shell é¡åƒ" +t MSG_910 "將隨身碟ã€è¨˜æ†¶å¡æˆ–虛擬光碟機格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼" +t MSG_911 "建立 FreeDOS å¯å•Ÿéš¨èº«ç¢Ÿ" +t MSG_912 "從å¯å•Ÿå‹• ISO 檔案 (Windows å’Œ Linux ç­‰) 建立å¯å•Ÿå‹•隨身碟" +t MSG_913 "從å¯å•Ÿå‹•ç¡¬ç¢Ÿæ˜ åƒæª” (åŒ…æ‹¬å£“ç¸®æ˜ åƒæª”) 建立å¯å•Ÿå‹•隨身碟" +t MSG_914 "建立 BIOS 或 UEFI å¯å•Ÿå‹•隨身碟,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 隨身碟" +t MSG_915 "建立 'Windows To Go' 隨身碟" +t MSG_916 "為沒有 TPM 或安全開機功能的電腦建立 Windows 11 安è£éš¨èº«ç¢Ÿ" +t MSG_917 "建立æŒçºŒæ€§ Linux ç£å€" +t MSG_918 "為é¸ä¸­çš„ç£ç¢Ÿæ©Ÿå»ºç«‹ VHD/DD æ˜ åƒæª”" +t MSG_919 "計算被é¸ä¸­æ˜ åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 檢查碼" +t MSG_920 "執行壞軌檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„嵿¸¬" +t MSG_921 "下載微軟官方 Windows æ˜ åƒæª”" +t MSG_922 "下載 UEFI Shell æ˜ åƒæª”" ######################################################################### l "hr-HR" "Croatian (Hrvatski)" 0x041a, 0x081a, 0x101a diff --git a/src/rufus.rc b/src/rufus.rc index 43b44be4..0c7d1092 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2063" +CAPTION "Rufus 4.2.2064" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2063,0 - PRODUCTVERSION 4,2,2063,0 + FILEVERSION 4,2,2064,0 + PRODUCTVERSION 4,2,2064,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2063" + VALUE "FileVersion", "4.2.2064" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2063" + VALUE "ProductVersion", "4.2.2064" END END BLOCK "VarFileInfo" From ef345bf106fc3d975a74997f6ef1532a48f02e14 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 4 Jul 2023 18:05:30 +0100 Subject: [PATCH 05/53] [uefi] fix UEFI bootloader extraction when case doesn't match * With ISO-9660 being case sensitive, we could end up in situations where trying to extract '/efi/boot/bootx64.efi' for revocation validation would fail if the file on the image was stored as '/EFI/boot/bootx64.efi' (e.g. Debian 12). * Fox this by storing the exact UEFI bootloader path we detected during ISO scan, and using this path for file extraction. * Also add a Cancel choice to the revocation dialog and harmonize whitespaces. --- src/iso.c | 24 ++++++++++++++++++++---- src/rufus.c | 37 +++++++++++++++++-------------------- src/rufus.h | 31 ++++++++++++++++--------------- src/rufus.rc | 10 +++++----- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/iso.c b/src/iso.c index 185af3f6..1b01d655 100644 --- a/src/iso.c +++ b/src/iso.c @@ -55,7 +55,6 @@ // How often should we update the progress bar (in 2K blocks) as updating // the progress bar for every block will bring extraction to a crawl #define PROGRESS_THRESHOLD 128 -#define FOUR_GIGABYTES 4294967296LL // Needed for UDF symbolic link testing #define S_IFLNK 0xA000 @@ -243,6 +242,15 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const img_report.has_bootmgr = TRUE; } if (safe_stricmp(psz_basename, bootmgr_efi_name) == 0) { + // We may extract the bootloaders for revocation validation later but + // to do so, since we're working with case sensitive file systems, we + // must store all found UEFI bootloader paths with the right case. + for (j = 0; j < ARRAYSIZE(img_report.efi_boot_path); j++) { + if (img_report.efi_boot_path[j][0] == 0) { + static_strcpy(img_report.efi_boot_path[j], psz_fullpath); + break; + } + } img_report.has_efi |= 1; img_report.has_bootmgr_efi = TRUE; } @@ -273,9 +281,17 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const // Check for the EFI boot entries if (safe_stricmp(psz_dirname, efi_dirname) == 0) { - for (i = 0; i < ARRAYSIZE(efi_bootname); i++) - if (safe_stricmp(psz_basename, efi_bootname[i]) == 0) + for (i = 0; i < ARRAYSIZE(efi_bootname); i++) { + if (safe_stricmp(psz_basename, efi_bootname[i]) == 0) { img_report.has_efi |= (2 << i); // start at 2 since "bootmgr.efi" is bit 0 + for (j = 0; j < ARRAYSIZE(img_report.efi_boot_path); j++) { + if (img_report.efi_boot_path[j][0] == 0) { + static_strcpy(img_report.efi_boot_path[j], psz_fullpath); + break; + } + } + } + } } if (psz_dirname != NULL) { @@ -312,7 +328,7 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const if (props->is_old_c32[i]) img_report.has_old_c32[i] = TRUE; } - if (file_length >= FOUR_GIGABYTES) + if (file_length >= 4 * GB) img_report.has_4GB_file = TRUE; // Compute projected size needed (NB: ISO_BLOCKSIZE = UDF_BLOCKSIZE) if (file_length != 0) diff --git a/src/rufus.c b/src/rufus.c index 7f5e70ca..7d79b564 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1429,7 +1429,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) const char* ldlinux = "ldlinux"; const char* syslinux = "syslinux"; const char* ldlinux_ext[3] = { "sys", "bss", "c32" }; - char tmp[MAX_PATH], tmp2[MAX_PATH], efi[MAX_PATH], c; + char tmp[MAX_PATH], tmp2[MAX_PATH], c; syslinux_ldlinux_len[0] = 0; syslinux_ldlinux_len[1] = 0; safe_free(grub2_buf); @@ -1449,7 +1449,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) goto out; if ((size_check) && (img_report.projected_size > (uint64_t)SelectedDrive.DiskSize)) { // This ISO image is too big for the selected target - MessageBoxExU(hMainDialog, lmprintf(MSG_089), lmprintf(MSG_088), MB_OK|MB_ICONERROR|MB_IS_RTL, selected_langid); + MessageBoxExU(hMainDialog, lmprintf(MSG_089), lmprintf(MSG_088), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid); goto out; } if (IS_DD_BOOTABLE(img_report)) { @@ -1489,14 +1489,14 @@ static DWORD WINAPI BootCheckThread(LPVOID param) if (is_windows_to_go) { if (fs_type != FS_NTFS) { // Windows To Go only works for NTFS - MessageBoxExU(hMainDialog, lmprintf(MSG_097, "Windows To Go"), lmprintf(MSG_092), MB_OK|MB_ICONERROR|MB_IS_RTL, selected_langid); + MessageBoxExU(hMainDialog, lmprintf(MSG_097, "Windows To Go"), lmprintf(MSG_092), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid); goto out; } if (SelectedDrive.MediaType != FixedMedia) { if ((target_type == TT_UEFI) && (partition_type == PARTITION_STYLE_GPT) && (WindowsVersion.BuildNumber < 15000)) { // Up to Windows 10 Creators Update (1703), we were screwed, since we need access to 2 partitions at the same time. // Thankfully, the newer Windows allow mounting multiple partitions on the same REMOVABLE drive. - MessageBoxExU(hMainDialog, lmprintf(MSG_198), lmprintf(MSG_190), MB_OK|MB_ICONERROR|MB_IS_RTL, selected_langid); + MessageBoxExU(hMainDialog, lmprintf(MSG_198), lmprintf(MSG_190), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid); goto out; } } @@ -1623,22 +1623,19 @@ static DWORD WINAPI BootCheckThread(LPVOID param) if (target_type == TT_UEFI) { // coverity[swapped_arguments] if (GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, tmp) != 0) { - for (i = 0; i < ARRAYSIZE(efi_bootname) + 1; i++) { - if ((img_report.has_efi & (1 << i)) == 0) - continue; - if (i == 0) - static_strcpy(efi, bootmgr_efi_name); - else - static_sprintf(efi, "%s/%s", efi_dirname, efi_bootname[i - 1]); - if (ExtractISOFile(image_path, efi, tmp, FILE_ATTRIBUTE_NORMAL) == 0) { - uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", efi); + // ExtractISOFile() is case sensitive, so we must use + for (i = 0; i < ARRAYSIZE(img_report.efi_boot_path) && img_report.efi_boot_path[i][0] != 0; i++) { + if (ExtractISOFile(image_path, img_report.efi_boot_path[i], tmp, FILE_ATTRIBUTE_NORMAL) == 0) { + uprintf("Warning: Failed to extract '%s' to check for UEFI revocation", img_report.efi_boot_path[i]); continue; } r = IsBootloaderRevoked(tmp); if (r > 0) { - MessageBoxExU(hMainDialog, lmprintf(MSG_339, + r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, (r == 1) ? lmprintf(MSG_340) : lmprintf(MSG_341, "Error code: 0xc0000428")), - lmprintf(MSG_338), MB_ICONWARNING | MB_IS_RTL, selected_langid); + lmprintf(MSG_338), MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); + if (r == IDCANCEL) + goto out; break; } } @@ -1677,7 +1674,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) fclose(fd); } else { r = MessageBoxExU(hMainDialog, lmprintf(MSG_116, img_report.grub2_version, GRUB2_PACKAGE_VERSION), - lmprintf(MSG_115), MB_YESNOCANCEL|MB_ICONWARNING|MB_IS_RTL, selected_langid); + lmprintf(MSG_115), MB_YESNOCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); if (r == IDCANCEL) goto out; else if (r == IDYES) { @@ -1735,7 +1732,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) } else { PrintInfo(0, MSG_204, old_c32_name[i]); if (MessageBoxExU(hMainDialog, lmprintf(MSG_084, old_c32_name[i], old_c32_name[i]), - lmprintf(MSG_083, old_c32_name[i]), MB_YESNO|MB_ICONWARNING|MB_IS_RTL, selected_langid) == IDYES) { + lmprintf(MSG_083, old_c32_name[i]), MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) == IDYES) { static_sprintf(tmp, "%s-%s", syslinux, embedded_sl_version_str[0]); IGNORE_RETVAL(_mkdir(tmp)); static_sprintf(tmp, "%s/%s-%s/%s", FILES_URL, syslinux, embedded_sl_version_str[0], old_c32_name[i]); @@ -1775,7 +1772,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) } else { r = MessageBoxExU(hMainDialog, lmprintf(MSG_114, img_report.sl_version_str, img_report.sl_version_ext, embedded_sl_version_str[1], embedded_sl_version_ext[1]), - lmprintf(MSG_115), MB_YESNO|MB_ICONWARNING|MB_IS_RTL, selected_langid); + lmprintf(MSG_115), MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid); if (r != IDYES) goto out; for (i=0; i<2; i++) { @@ -1834,7 +1831,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) PrintInfo(0, MSG_206, tmp); // MSG_104: "Syslinux v5.0 or later requires a '%s' file to be installed" r = MessageBoxExU(hMainDialog, lmprintf(MSG_104, "Syslinux v5.0", tmp, "Syslinux v5+", tmp), - lmprintf(MSG_103, tmp), MB_YESNOCANCEL|MB_ICONWARNING|MB_IS_RTL, selected_langid); + lmprintf(MSG_103, tmp), MB_YESNOCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); if (r == IDCANCEL) goto out; if (r == IDYES) { @@ -1883,7 +1880,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) static_sprintf(tmp, "grldr"); PrintInfo(0, MSG_206, tmp); r = MessageBoxExU(hMainDialog, lmprintf(MSG_104, "Grub4DOS 0.4", tmp, "Grub4DOS", tmp), - lmprintf(MSG_103, tmp), MB_YESNOCANCEL|MB_ICONWARNING|MB_IS_RTL, selected_langid); + lmprintf(MSG_103, tmp), MB_YESNOCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); if (r == IDCANCEL) goto out; if (r == IDYES) { diff --git a/src/rufus.h b/src/rufus.h index 7bee5c06..485a409e 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -364,12 +364,28 @@ typedef struct { uint16_t revision; } winver_t; +/* We can't use the Microsoft enums as we want to have RISC-V */ +enum ArchType { + ARCH_UNKNOWN = 0, + ARCH_X86_32, + ARCH_X86_64, + ARCH_ARM_32, + ARCH_ARM_64, + ARCH_IA_64, + ARCH_RISCV_32, + ARCH_RISCV_64, + ARCH_RISCV_128, + ARCH_EBC, + ARCH_MAX +}; + typedef struct { char label[192]; // 3*64 to account for UTF-8 char usb_label[192]; // converted USB label for workaround char cfg_path[128]; // path to the ISO's isolinux.cfg char reactos_path[128]; // path to the ISO's freeldr.sys or setupldr.sys char wininst_path[MAX_WININST][64]; // path to the Windows install image(s) + char efi_boot_path[ARCH_MAX][30]; // paths of detected UEFI bootloaders char efi_img_path[128]; // path to an efi.img file uint64_t image_size; uint64_t archive_size; @@ -476,21 +492,6 @@ typedef enum TASKBAR_PROGRESS_FLAGS TASKBAR_PAUSED = 0x8 } TASKBAR_PROGRESS_FLAGS; -/* We can't use the Microsoft enums as we want to have RISC-V */ -enum ArchType { - ARCH_UNKNOWN = 0, - ARCH_X86_32, - ARCH_X86_64, - ARCH_ARM_32, - ARCH_ARM_64, - ARCH_IA_64, - ARCH_RISCV_32, - ARCH_RISCV_64, - ARCH_RISCV_128, - ARCH_EBC, - ARCH_MAX -}; - static __inline USHORT GetApplicationArch(void) { #if defined(_M_AMD64) diff --git a/src/rufus.rc b/src/rufus.rc index 0c7d1092..9cb6ccb7 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2064" +CAPTION "Rufus 4.2.2065" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2064,0 - PRODUCTVERSION 4,2,2064,0 + FILEVERSION 4,2,2065,0 + PRODUCTVERSION 4,2,2065,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2064" + VALUE "FileVersion", "4.2.2065" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2064" + VALUE "ProductVersion", "4.2.2065" END END BLOCK "VarFileInfo" From 0363bfe503d76d6e1d9a22a61e850f8ccbf79f50 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 5 Jul 2023 18:36:58 +0100 Subject: [PATCH 06/53] [misc] add an address resolver for internal DLL function calls * Not sure if we'll use this to hook into FfuCaptureImage()/FfuApplyImage()/FfuMountImage() directly. But at least, if we ever need it, it's there... --- res/loc/rufus.loc | 3 ++ src/drive.c | 10 ++-- src/net.c | 4 +- src/pki.c | 2 +- src/rufus.c | 15 +++++- src/rufus.h | 13 ++++- src/rufus.rc | 10 ++-- src/stdio.c | 123 ++++++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 160 insertions(+), 20 deletions(-) diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 73394841..224c4ee3 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -606,6 +606,9 @@ t MSG_341 "a Windows Recovery Screen (BSOD) with '%s'" t MSG_342 "Compressed VHDX Image" t MSG_343 "Uncompressed VHD Image" t MSG_344 "Full Flash Update Image" +t MSG_345 "Some additional data must be downloaded from Microsoft to use this functionality:\n" + "- Select 'Yes' to connect to the Internet and download it\n" + "- Select 'No' to cancel the operation" # The following messages are for the Windows Store listing only and are not used by the application t MSG_900 "Rufus is a utility that helps format and create bootable USB flash drives, such as USB keys/pendrives, memory sticks, etc." t MSG_901 "Official site: %s" diff --git a/src/drive.c b/src/drive.c index d51fb4d3..e015a287 100644 --- a/src/drive.c +++ b/src/drive.c @@ -1541,7 +1541,7 @@ static BOOL StoreEspInfo(GUID* guid) static_sprintf(key_name[0], "ToggleEsp%02u", j); str = ReadSettingStr(key_name[0]); if ((str == NULL) || (str[0] == 0)) - return WriteSettingStr(key_name[0], GuidToString(guid)); + return WriteSettingStr(key_name[0], GuidToString(guid, TRUE)); } // All slots are used => Move every key down and add to last slot // NB: No, we don't care that the slot we remove may not be the oldest. @@ -1550,7 +1550,7 @@ static BOOL StoreEspInfo(GUID* guid) static_sprintf(key_name[1], "ToggleEsp%02u", j + 1); WriteSettingStr(key_name[0], ReadSettingStr(key_name[1])); } - return WriteSettingStr(key_name[1], GuidToString(guid)); + return WriteSettingStr(key_name[1], GuidToString(guid, TRUE)); } static GUID* GetEspGuid(uint8_t index) @@ -1990,7 +1990,7 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys case PARTITION_STYLE_GPT: SelectedDrive.PartitionStyle = PARTITION_STYLE_GPT; suprintf("Partition type: GPT, NB Partitions: %d", DriveLayout->PartitionCount); - suprintf("Disk GUID: %s", GuidToString(&DriveLayout->Gpt.DiskId)); + suprintf("Disk GUID: %s", GuidToString(&DriveLayout->Gpt.DiskId, TRUE)); suprintf("Max parts: %d, Start Offset: %" PRIi64 ", Usable = %" PRIi64 " bytes", DriveLayout->Gpt.MaxPartitionCount, DriveLayout->Gpt.StartingUsableOffset.QuadPart, DriveLayout->Gpt.UsableLength.QuadPart); for (i = 0; i < DriveLayout->PartitionCount; i++) { @@ -2006,7 +2006,7 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys suprintf(" Name: '%S'", DriveLayout->PartitionEntry[i].Gpt.Name); suprintf(" Detected File System: %s", GetFsName(hPhysical, DriveLayout->PartitionEntry[i].StartingOffset)); suprintf(" ID: %s\r\n Size: %s (%" PRIi64 " bytes)\r\n Start Sector: %" PRIi64 ", Attributes: 0x%016" PRIX64, - GuidToString(&DriveLayout->PartitionEntry[i].Gpt.PartitionId), + GuidToString(&DriveLayout->PartitionEntry[i].Gpt.PartitionId, TRUE), SizeToHumanReadable(DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, TRUE, FALSE), DriveLayout->PartitionEntry[i].PartitionLength, DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize, @@ -2646,5 +2646,5 @@ const char* GetGPTPartitionType(const GUID* guid) { int i; for (i = 0; (i < ARRAYSIZE(gpt_type)) && !CompareGUID(guid, gpt_type[i].guid); i++); - return (i < ARRAYSIZE(gpt_type)) ? gpt_type[i].name : GuidToString(guid); + return (i < ARRAYSIZE(gpt_type)) ? gpt_type[i].name : GuidToString(guid, TRUE); } diff --git a/src/net.c b/src/net.c index a6748cb7..ff394433 100644 --- a/src/net.c +++ b/src/net.c @@ -915,7 +915,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) // thinking that Rufus is doing something malicious... IGNORE_RETVAL(CoCreateGuid(&guid)); // coverity[fixed_size_dest] - strcpy(&pipe[9], GuidToString(&guid)); + strcpy(&pipe[9], GuidToString(&guid, TRUE)); static_sprintf(icon_path, "%s%s.ico", temp_dir, APPLICATION_NAME); ExtractAppIcon(icon_path, TRUE); @@ -973,7 +973,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) assert((fido_script != NULL) && (fido_len != 0)); - static_sprintf(script_path, "%s%s.ps1", temp_dir, GuidToString(&guid)); + static_sprintf(script_path, "%s%s.ps1", temp_dir, GuidToString(&guid, TRUE)); hFile = CreateFileU(script_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_READONLY, NULL); if (hFile == INVALID_HANDLE_VALUE) { uprintf("Unable to create download script '%s': %s", script_path, WindowsErrorString()); diff --git a/src/pki.c b/src/pki.c index 2b4cc2da..1d6a674d 100644 --- a/src/pki.c +++ b/src/pki.c @@ -860,7 +860,7 @@ BOOL ParseSKUSiPolicy(void) goto out; } if (!CompareGUID(&Header->PolicyTypeGUID, &SKU_CODE_INTEGRITY_POLICY)) { - uprintf("ParseSKUSiPolicy: Unexpected Policy Type GUID %s", GuidToString(&Header->PolicyTypeGUID)); + uprintf("ParseSKUSiPolicy: Unexpected Policy Type GUID %s", GuidToString(&Header->PolicyTypeGUID, TRUE)); goto out; } diff --git a/src/rufus.c b/src/rufus.c index 7d79b564..0855d345 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1861,7 +1861,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) IGNORE_RETVAL(_chdirU(app_data_dir)); IGNORE_RETVAL(_mkdir(FILES_DIR)); IGNORE_RETVAL(_chdir(FILES_DIR)); - if (DownloadToFileOrBufferEx(DISKCOPY_URL, tmp, DISKCOPY_USER_AGENT, NULL, hMainDialog, FALSE) != DISKCOPY_SIZE) { + if (DownloadToFileOrBufferEx(DISKCOPY_URL, tmp, SYMBOL_SERVER_USER_AGENT, NULL, hMainDialog, FALSE) != DISKCOPY_SIZE) { ret = BOOTCHECK_DOWNLOAD_ERROR; goto out; } @@ -3787,7 +3787,18 @@ extern int TestHashes(void); // Ctrl-T => Alternate Test mode that doesn't require a full rebuild if ((ctrl_without_focus || ((GetKeyState(VK_CONTROL) & 0x8000) && (msg.message == WM_KEYDOWN))) && (msg.wParam == 'T')) { - TestHashes(); +// TestHashes(); + dll_resolver_t resolver = { 0 }; + resolver.path = "C:\\Windows\\System32\\Dism\\FfuProvider.dll"; + resolver.count = 3; + resolver.name = calloc(resolver.count, sizeof(char*)); + resolver.address = calloc(resolver.count, sizeof(uint32_t)); + resolver.name[0] = "FfuCaptureImage"; + resolver.name[1] = "FfuApplyImage"; + resolver.name[2] = "FfuMountImage"; + uprintf("Got %d resolved addresses", ResolveDllAddress(&resolver)); + free(resolver.name); + free(resolver.address); continue; } #endif diff --git a/src/rufus.h b/src/rufus.h index 485a409e..84ec91a3 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -121,10 +121,10 @@ #define SEVENZIP_URL "https://7-zip.org/" // Generated by following https://randomascii.wordpress.com/2013/03/09/symbols-the-microsoft-way/ #define DISKCOPY_URL "https://msdl.microsoft.com/download/symbols/diskcopy.dll/54505118173000/diskcopy.dll" -#define DISKCOPY_USER_AGENT "Microsoft-Symbol-Server/10.0.22621.755" #define DISKCOPY_SIZE 0x16ee00 #define DISKCOPY_IMAGE_OFFSET 0x66d8 #define DISKCOPY_IMAGE_SIZE 0x168000 +#define SYMBOL_SERVER_USER_AGENT "Microsoft-Symbol-Server/10.0.22621.755" #define DEFAULT_ESP_MOUNT_POINT "S:\\" #define IS_POWER_OF_2(x) ((x != 0) && (((x) & ((x) - 1)) == 0)) #define IGNORE_RETVAL(expr) do { (void)(expr); } while(0) @@ -472,6 +472,14 @@ typedef struct ext_t { const char** description; } ext_t; +/* DLL address resolver */ +typedef struct { + char* path; + uint32_t count; + char** name; + uint32_t* address; // 32-bit will do, as we're not dealing with >4GB DLLs... +} dll_resolver_t; + #ifndef __VA_GROUP__ #define __VA_GROUP__(...) __VA_ARGS__ #endif @@ -613,7 +621,7 @@ extern void _UpdateProgressWithInfo(int op, int msg, uint64_t processed, uint64_ #define UpdateProgressWithInfoForce(op, msg, processed, total) _UpdateProgressWithInfo(op, msg, processed, total, TRUE) #define UpdateProgressWithInfoInit(hProgressDialog, bNoAltMode) UpdateProgressWithInfo(OP_INIT, (int)bNoAltMode, (uint64_t)(uintptr_t)hProgressDialog, 0); extern const char* StrError(DWORD error_code, BOOL use_default_locale); -extern char* GuidToString(const GUID* guid); +extern char* GuidToString(const GUID* guid, BOOL bDecorated); extern GUID* StringToGuid(const char* str); extern char* SizeToHumanReadable(uint64_t size, BOOL copy_to_log, BOOL fake_units); extern char* TimestampToHumanReadable(uint64_t ts); @@ -723,6 +731,7 @@ extern HICON CreateMirroredIcon(HICON hiconOrg); extern HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, LONGLONG fileSize); +extern uint32_t ResolveDllAddress(dll_resolver_t* resolver); #define GetTextWidth(hDlg, id) GetTextSize(GetDlgItem(hDlg, id), NULL).cx DWORD WINAPI HashThread(void* param); diff --git a/src/rufus.rc b/src/rufus.rc index 9cb6ccb7..0f3d4b52 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2065" +CAPTION "Rufus 4.2.2066" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2065,0 - PRODUCTVERSION 4,2,2065,0 + FILEVERSION 4,2,2066,0 + PRODUCTVERSION 4,2,2066,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2065" + VALUE "FileVersion", "4.2.2066" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2065" + VALUE "ProductVersion", "4.2.2066" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index 3a242042..f784683b 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -28,20 +28,25 @@ #include #include #include +#include #include #include #include #include "rufus.h" +#include "missing.h" +#include "settings.h" #include "resource.h" #include "msapi_utf8.h" #include "localization.h" -#define FACILITY_WIM 322 +#define FACILITY_WIM 322 +#define DEFAULT_BASE_ADDRESS 0x100000000ULL /* * Globals */ +const HANDLE hRufus = (HANDLE)0x0000005275667573ULL; // "\0\0\0Rufus" HWND hStatus; size_t ubuffer_pos = 0; char ubuffer[UBUFFER_SIZE]; // Buffer for ubpushf() messages we don't log right away @@ -683,12 +688,13 @@ const char *WindowsErrorString(void) return err_string; } -char* GuidToString(const GUID* guid) +char* GuidToString(const GUID* guid, BOOL bDecorated) { static char guid_string[MAX_GUID_STRING_LENGTH]; if (guid == NULL) return NULL; - sprintf(guid_string, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + sprintf(guid_string, bDecorated ? "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}" : + "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", (uint32_t)guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); @@ -1143,3 +1149,114 @@ HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAccess, return fileHandle; } + +// The following calls are used to resolve the addresses of DLL function calls +// that are not publicly exposed by Microsoft. This is accomplished by downloading +// the relevant .pdb and looking up the relevant address there. Once an address is +// found, it is stored in the Rufus settings so that it can be reused. + +PF_TYPE_DECL(WINAPI, BOOL, SymInitialize, (HANDLE, PCSTR, BOOL)); +PF_TYPE_DECL(WINAPI, DWORD64, SymLoadModuleEx, (HANDLE, HANDLE, PCSTR, PCSTR, DWORD64, DWORD, PMODLOAD_DATA, DWORD)); +PF_TYPE_DECL(WINAPI, BOOL, SymGetModuleInfo64, (HANDLE, DWORD64, PIMAGEHLP_MODULE64)); +PF_TYPE_DECL(WINAPI, BOOL, SymUnloadModule64, (HANDLE, DWORD64)); +PF_TYPE_DECL(WINAPI, BOOL, SymEnumSymbols, (HANDLE, ULONG64, PCSTR, PSYM_ENUMERATESYMBOLS_CALLBACK, PVOID)); +PF_TYPE_DECL(WINAPI, BOOL, SymCleanup, (HANDLE)); + +BOOL CALLBACK EnumSymProc(PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext) +{ + dll_resolver_t* resolver = (dll_resolver_t*)UserContext; + uint32_t i; + + for (i = 0; i < resolver->count; i++) { + if (safe_strcmp(pSymInfo->Name, resolver->name[i]) == 0) { + resolver->address[i] = (uint32_t)pSymInfo->Address; +#if defined(_DEBUG) + uprintf("%08x: %s", resolver->address[i], resolver->name[i]); +#endif + } + } + + return TRUE; +} + +uint32_t ResolveDllAddress(dll_resolver_t* resolver) +{ + uint32_t r = 0; + uint32_t i; + char url[MAX_PATH], saved_id[MAX_PATH], path[MAX_PATH]; + DWORD64 base_address; + IMAGEHLP_MODULE64 info = { sizeof(IMAGEHLP_MODULE64) }; + + PF_INIT(SymInitialize, DbgHelp); + PF_INIT(SymLoadModuleEx, DbgHelp); + PF_INIT(SymGetModuleInfo64, DbgHelp); + PF_INIT(SymUnloadModule64, DbgHelp); + PF_INIT(SymEnumSymbols, DbgHelp); + PF_INIT(SymCleanup, DbgHelp); + + if (pfSymInitialize == NULL || pfSymLoadModuleEx == NULL || pfSymGetModuleInfo64 == NULL || + pfSymUnloadModule64 == NULL || pfSymEnumSymbols == NULL || pfSymCleanup == NULL || + resolver->count == 0 || resolver->path == NULL || resolver->name == NULL || resolver->address == NULL) + return 0; + + if (!pfSymInitialize(hRufus, NULL, FALSE)) { + uprintf("Could not initialize DLL symbol handler"); + return 0; + } + + // Get the PDB unique address from the DLL + base_address = pfSymLoadModuleEx(hRufus, NULL, resolver->path, NULL, DEFAULT_BASE_ADDRESS, 0, NULL, 0); + if (base_address == 0ULL || !pfSymGetModuleInfo64(hRufus, base_address, &info)) { + uprintf("Could not obtain DLL symbol info"); + goto out; + } + assert(base_address == DEFAULT_BASE_ADDRESS); + pfSymUnloadModule64(hRufus, base_address); + + // Check settings to see if we have existing data for these DLL calls. + for (i = 0; i < resolver->count; i++) { + static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), + GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, resolver->name[i]); + resolver->address[i] = ReadSetting32(saved_id); + if (resolver->address[i] == 0) + break; + } + + if (i == resolver->count) { + // No need to download the PDB + r = resolver->count; + goto out; + } + + // Download the PDB from Microsoft's symbol servers + if (MessageBoxExU(hMainDialog, lmprintf(MSG_345), lmprintf(MSG_115), + MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES) + goto out; + static_sprintf(path, "%s\\%s.pdb", temp_dir, info.ModuleName); + static_sprintf(url, "http://msdl.microsoft.com/download/symbols/%s.pdb/%s%x/%s.pdb", + info.ModuleName, GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, info.ModuleName); + if (DownloadToFileOrBufferEx(url, path, SYMBOL_SERVER_USER_AGENT, NULL, hMainDialog, FALSE) < 200 * KB) + goto out; + + // NB: SymLoadModuleEx() does not load a PDB unless the file has an explicit '.pdb' extension + base_address = pfSymLoadModuleEx(hRufus, NULL, path, NULL, DEFAULT_BASE_ADDRESS, 0, NULL, 0); + assert(base_address == DEFAULT_BASE_ADDRESS); + pfSymEnumSymbols(hRufus, base_address, "*!*", EnumSymProc, resolver); + DeleteFileU(path); + + // Store the addresses + r = 0; + for (i = 0; i < resolver->count; i++) { + static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), + GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, resolver->name[i]); + if (resolver->address[i] != 0) { + WriteSetting32(saved_id, resolver->address[i]); + r++; + } + } + +out: + pfSymUnloadModule64(hRufus, base_address); + pfSymCleanup(hRufus); + return r; +} From f411d526d699a2c5e2334646b0641b888d1fc546 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 5 Jul 2023 22:25:25 +0100 Subject: [PATCH 07/53] [misc] attempt to fix DLL address resolver for ARM64 * SymLoadModuleEx() on the DLL fails (with ERROR_SUCCESS, sic) on ARM64, so we have to resort to loading the DLL in memory and look for the "RSDS" section to access the GUID and Age. * Except that, once you sort that, you end up with SymEnumSymbols() producing two separate addresses for each symbol, on account that the Windows 11 ARM64 DLLs are actually an unholy union of X64 and ARM64 *in the same binary*, under an abomination that Microsoft calls ARM64X (See https://learn.microsoft.com/en-us/windows/arm/arm64x-pe for details). * And, of course, Microsoft did not provide any means during PDB enumeration to tell which address is for which arch. Especially, the bloody pSymInfo->TypeIndex, that they could easily have used for this, is utterly pointless as it remains set to 0... * This means that, even after all this effort, we're still nowhere close to have a solution that can make DLL address resolution work on Windows 11 ARM64. --- src/rufus.rc | 10 +++---- src/stdio.c | 78 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/rufus.rc b/src/rufus.rc index 0f3d4b52..c468a3d4 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2066" +CAPTION "Rufus 4.2.2067" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2066,0 - PRODUCTVERSION 4,2,2066,0 + FILEVERSION 4,2,2067,0 + PRODUCTVERSION 4,2,2067,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2066" + VALUE "FileVersion", "4.2.2067" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2066" + VALUE "ProductVersion", "4.2.2067" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index f784683b..073636ce 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -42,6 +42,7 @@ #define FACILITY_WIM 322 #define DEFAULT_BASE_ADDRESS 0x100000000ULL +#define RSDS_SIG 0x53445352 /* * Globals @@ -51,6 +52,15 @@ HWND hStatus; size_t ubuffer_pos = 0; char ubuffer[UBUFFER_SIZE]; // Buffer for ubpushf() messages we don't log right away +#pragma pack(push, 1) +typedef struct { + DWORD Signature; // "RSDS" + GUID Guid; + DWORD Age; + CHAR PdbName[1]; +} debug_info_t; +#pragma pack(pop) + void uprintf(const char *format, ...) { static char buf[4096]; @@ -1157,7 +1167,6 @@ HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAccess, PF_TYPE_DECL(WINAPI, BOOL, SymInitialize, (HANDLE, PCSTR, BOOL)); PF_TYPE_DECL(WINAPI, DWORD64, SymLoadModuleEx, (HANDLE, HANDLE, PCSTR, PCSTR, DWORD64, DWORD, PMODLOAD_DATA, DWORD)); -PF_TYPE_DECL(WINAPI, BOOL, SymGetModuleInfo64, (HANDLE, DWORD64, PIMAGEHLP_MODULE64)); PF_TYPE_DECL(WINAPI, BOOL, SymUnloadModule64, (HANDLE, DWORD64)); PF_TYPE_DECL(WINAPI, BOOL, SymEnumSymbols, (HANDLE, ULONG64, PCSTR, PSYM_ENUMERATESYMBOLS_CALLBACK, PVOID)); PF_TYPE_DECL(WINAPI, BOOL, SymCleanup, (HANDLE)); @@ -1183,40 +1192,50 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) { uint32_t r = 0; uint32_t i; + debug_info_t* info = NULL; char url[MAX_PATH], saved_id[MAX_PATH], path[MAX_PATH]; - DWORD64 base_address; - IMAGEHLP_MODULE64 info = { sizeof(IMAGEHLP_MODULE64) }; + uint8_t* buf = NULL; + DWORD* dbuf; + DWORD64 base_address = 0ULL; PF_INIT(SymInitialize, DbgHelp); PF_INIT(SymLoadModuleEx, DbgHelp); - PF_INIT(SymGetModuleInfo64, DbgHelp); PF_INIT(SymUnloadModule64, DbgHelp); PF_INIT(SymEnumSymbols, DbgHelp); PF_INIT(SymCleanup, DbgHelp); - if (pfSymInitialize == NULL || pfSymLoadModuleEx == NULL || pfSymGetModuleInfo64 == NULL || - pfSymUnloadModule64 == NULL || pfSymEnumSymbols == NULL || pfSymCleanup == NULL || - resolver->count == 0 || resolver->path == NULL || resolver->name == NULL || resolver->address == NULL) + if (pfSymInitialize == NULL || pfSymLoadModuleEx == NULL || pfSymUnloadModule64 == NULL || + pfSymEnumSymbols == NULL || pfSymCleanup == NULL || resolver->count == 0 || + resolver->path == NULL || resolver->name == NULL || resolver->address == NULL) return 0; - if (!pfSymInitialize(hRufus, NULL, FALSE)) { - uprintf("Could not initialize DLL symbol handler"); + // Get the PDB unique address from the DLL. Note that we can *NOT* use SymGetModuleInfo64() to + // obtain the data we need because Microsoft either *BOTCHED* or *DELIBERATELY CRIPPLED* their + // SymLoadModuleEx()/SymLoadModule64() implementation on ARM64, so that the return value is always + // 0 with GetLastError() set to ERROR_SUCCESS, thereby *FALSELY* indicating that the module is + // already loaded... So we just load the whole DLL into a buffer and look for an "RSDS" section + // per https://www.godevtool.com/Other/pdb.htm + r = read_file(resolver->path, &buf); + if (r == 0) return 0; + + dbuf = (DWORD*)buf; + for (i = 0; i < (r - sizeof(debug_info_t)) / sizeof(DWORD); i++) { + if (dbuf[i] == RSDS_SIG) { + info = (debug_info_t*)&dbuf[i]; + if (safe_strstr(info->PdbName, ".pdb") != NULL) + break; + } } - - // Get the PDB unique address from the DLL - base_address = pfSymLoadModuleEx(hRufus, NULL, resolver->path, NULL, DEFAULT_BASE_ADDRESS, 0, NULL, 0); - if (base_address == 0ULL || !pfSymGetModuleInfo64(hRufus, base_address, &info)) { - uprintf("Could not obtain DLL symbol info"); + if (info == NULL) { + uprintf("Could not find debug info in '%s'", resolver->path); goto out; } - assert(base_address == DEFAULT_BASE_ADDRESS); - pfSymUnloadModule64(hRufus, base_address); // Check settings to see if we have existing data for these DLL calls. for (i = 0; i < resolver->count; i++) { static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), - GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, resolver->name[i]); + GuidToString(&info->Guid, FALSE), info->Age, resolver->name[i]); resolver->address[i] = ReadSetting32(saved_id); if (resolver->address[i] == 0) break; @@ -1232,15 +1251,28 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) if (MessageBoxExU(hMainDialog, lmprintf(MSG_345), lmprintf(MSG_115), MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES) goto out; - static_sprintf(path, "%s\\%s.pdb", temp_dir, info.ModuleName); - static_sprintf(url, "http://msdl.microsoft.com/download/symbols/%s.pdb/%s%x/%s.pdb", - info.ModuleName, GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, info.ModuleName); + static_sprintf(path, "%s\\%s", temp_dir, info->PdbName); + static_sprintf(url, "http://msdl.microsoft.com/download/symbols/%s/%s%x/%s", + info->PdbName, GuidToString(&info->Guid, FALSE), info->Age, info->PdbName); if (DownloadToFileOrBufferEx(url, path, SYMBOL_SERVER_USER_AGENT, NULL, hMainDialog, FALSE) < 200 * KB) goto out; + if (!pfSymInitialize(hRufus, NULL, FALSE)) { + uprintf("Could not initialize DLL symbol handler"); + goto out; + } + // NB: SymLoadModuleEx() does not load a PDB unless the file has an explicit '.pdb' extension base_address = pfSymLoadModuleEx(hRufus, NULL, path, NULL, DEFAULT_BASE_ADDRESS, 0, NULL, 0); assert(base_address == DEFAULT_BASE_ADDRESS); + // On Windows 11 ARM64 the following call will return *TWO* different addresses for the same + // call, because most Windows DLL's are ARM64X, which means that they are an unholy union of + // both X64 and ARM64 code in the same binary... + // See https://learn.microsoft.com/en-us/windows/arm/arm64x-pe + // Now this would be all swell and dandy if Microsoft's debugging/symbol APIs had followed + // and would give us a hint of the architecture behind each duplicate address, but of course, + // the SYMBOL_INFO passed to EnumSymProc contains no such data. So we currently don't have a + // way to tell which of the two addresses we get on ARM64 is for which architecture... :( pfSymEnumSymbols(hRufus, base_address, "*!*", EnumSymProc, resolver); DeleteFileU(path); @@ -1248,7 +1280,7 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) r = 0; for (i = 0; i < resolver->count; i++) { static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), - GuidToString(&info.PdbSig70, FALSE), (int)info.PdbAge, resolver->name[i]); + GuidToString(&info->Guid, FALSE), info->Age, resolver->name[i]); if (resolver->address[i] != 0) { WriteSetting32(saved_id, resolver->address[i]); r++; @@ -1256,7 +1288,9 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) } out: - pfSymUnloadModule64(hRufus, base_address); + free(buf); + if (base_address != 0) + pfSymUnloadModule64(hRufus, base_address); pfSymCleanup(hRufus); return r; } From 5bbcba8534ac847df3191f44bd2757a3911ff172 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 6 Jul 2023 19:47:26 +0100 Subject: [PATCH 08/53] [vhd] add write support for .vhdx and .ffu images * Also move VHD mounting function calls from iso.c to vhd.c and remove unused VHD footer elements. --- src/format.c | 42 ++++++++-- src/format.h | 6 +- src/iso.c | 81 +------------------ src/rufus.c | 14 +++- src/rufus.h | 2 - src/rufus.rc | 10 +-- src/stdio.c | 6 +- src/vhd.c | 215 ++++++++++++++++++++++++++++++++++++--------------- src/vhd.h | 59 ++------------ src/wue.c | 14 ++-- 10 files changed, 226 insertions(+), 223 deletions(-) diff --git a/src/format.c b/src/format.c index ea43ce8e..e955f40e 100644 --- a/src/format.c +++ b/src/format.c @@ -36,6 +36,7 @@ #endif #include "rufus.h" +#include "format.h" #include "missing.h" #include "resource.h" #include "settings.h" @@ -73,7 +74,7 @@ extern const int nb_steps[FS_MAX]; extern uint32_t dur_mins, dur_secs; extern uint32_t wim_nb_files, wim_proc_files, wim_extra_files; extern BOOL force_large_fat32, enable_ntfs_compression, lock_drive, zero_drive, fast_zeroing, enable_file_indexing; -extern BOOL write_as_image, use_vds, write_as_esp, is_vds_available; +extern BOOL write_as_image, use_vds, write_as_esp, is_vds_available, has_ffu_support; uint8_t *grub2_buf = NULL, *sec_buf = NULL; long grub2_len; @@ -1154,6 +1155,7 @@ static BOOL WriteDrive(HANDLE hPhysicalDrive, BOOL bZeroDrive) int64_t bled_ret; uint8_t* buffer = NULL; uint32_t zero_data, *cmp_buffer = NULL; + char* vhd_path = NULL; int throttle_fast_zeroing = 0, read_bufnum = 0, proc_bufnum = 1; if (SelectedDrive.SectorSize < 512) { @@ -1273,7 +1275,7 @@ static BOOL WriteDrive(HANDLE hPhysicalDrive, BOOL bZeroDrive) if (i > WRITE_RETRIES) goto out; } - } else if (img_report.compression_type != BLED_COMPRESSION_NONE) { + } else if (img_report.compression_type != BLED_COMPRESSION_NONE && img_report.compression_type < BLED_COMPRESSION_MAX) { uprintf("Writing compressed image:"); hSourceImage = CreateFileU(image_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); @@ -1310,8 +1312,17 @@ static BOOL WriteDrive(HANDLE hPhysicalDrive, BOOL bZeroDrive) goto out; } } else { - hSourceImage = CreateFileAsync(image_path, GENERIC_READ, FILE_SHARE_READ, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN); + assert(img_report.compression_type != IMG_COMPRESSION_FFU); + // VHD/VHDX require mounting the image first + if (img_report.compression_type == IMG_COMPRESSION_VHD || + img_report.compression_type == IMG_COMPRESSION_VHDX) { + vhd_path = VhdMountImage(image_path); + if (vhd_path == NULL) + goto out; + } + + hSourceImage = CreateFileAsync(vhd_path != NULL ? vhd_path : image_path, GENERIC_READ, + FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN); if (hSourceImage == NULL) { uprintf("Could not open image '%s': %s", image_path, WindowsErrorString()); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_OPEN_FAILED; @@ -1397,10 +1408,12 @@ static BOOL WriteDrive(HANDLE hPhysicalDrive, BOOL bZeroDrive) RefreshDriveLayout(hPhysicalDrive); ret = TRUE; out: - if (img_report.compression_type != BLED_COMPRESSION_NONE) + if (img_report.compression_type != BLED_COMPRESSION_NONE && img_report.compression_type < BLED_COMPRESSION_MAX) safe_closehandle(hSourceImage); else CloseFileAsync(hSourceImage); + if (vhd_path != NULL) + VhdUnmountImage(); safe_mm_free(buffer); safe_mm_free(cmp_buffer); return ret; @@ -1627,7 +1640,24 @@ DWORD WINAPI FormatThread(void* param) // Write an image file if ((boot_type == BT_IMAGE) && write_as_image) { - WriteDrive(hPhysicalDrive, FALSE); + // Special case for FFU images + if (img_report.compression_type == IMG_COMPRESSION_FFU) { + char cmd[MAX_PATH + 128], *physical; + // Should have been filtered out beforehand + assert(has_ffu_support); + safe_unlockclose(hPhysicalDrive); + physical = GetPhysicalName(SelectedDrive.DeviceNumber); + static_sprintf(cmd, "dism /Apply-Ffu /ApplyDrive:%s /ImageFile:\"%s\"", physical, image_path); + uprintf("Running command: '%s", cmd); + cr = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261); + if (cr != 0 && !IS_ERROR(FormatStatus)) { + SetLastError(cr); + uprintf("Failed to apply FFU image: %s", WindowsErrorString()); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_WINDOWS) | SCODE_CODE(cr); + } + } else { + WriteDrive(hPhysicalDrive, FALSE); + } goto out; } diff --git a/src/format.h b/src/format.h index 8bc514e8..c60b38d5 100644 --- a/src/format.h +++ b/src/format.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Formatting function calls - * Copyright © 2011-2020 Pete Batard + * Copyright © 2011-2023 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -111,6 +111,10 @@ typedef BOOLEAN (WINAPI* EnableVolumeCompression_t)( ULONG CompressionFlags // FILE_SYSTEM_PROP_FLAG ); +#define IMG_COMPRESSION_FFU (BLED_COMPRESSION_MAX) +#define IMG_COMPRESSION_VHD (BLED_COMPRESSION_MAX + 1) +#define IMG_COMPRESSION_VHDX (BLED_COMPRESSION_MAX + 2) + BOOL WritePBR(HANDLE hLogicalDrive); BOOL FormatLargeFAT32(DWORD DriveIndex, uint64_t PartitionOffset, DWORD ClusterSize, LPCSTR FSName, LPCSTR Label, DWORD Flags); BOOL FormatExtFs(DWORD DriveIndex, uint64_t PartitionOffset, DWORD BlockSize, LPCSTR FSName, LPCSTR Label, DWORD Flags); diff --git a/src/iso.c b/src/iso.c index 1b01d655..25c9b670 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1726,83 +1726,8 @@ out: return ret; } -// VirtDisk API Prototypes since we can't use delay-loading because of MinGW -// See https://github.com/pbatard/rufus/issues/2272#issuecomment-1615976013 -PF_TYPE_DECL(WINAPI, DWORD, OpenVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR, - VIRTUAL_DISK_ACCESS_MASK, OPEN_VIRTUAL_DISK_FLAG, POPEN_VIRTUAL_DISK_PARAMETERS, PHANDLE)); -PF_TYPE_DECL(WINAPI, DWORD, AttachVirtualDisk, (HANDLE, PSECURITY_DESCRIPTOR, - ATTACH_VIRTUAL_DISK_FLAG, ULONG, PATTACH_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED)); -PF_TYPE_DECL(WINAPI, DWORD, DetachVirtualDisk, (HANDLE, DETACH_VIRTUAL_DISK_FLAG, ULONG)); -PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskPhysicalPath, (HANDLE, PULONG, PWSTR)); - -static char physical_path[128] = ""; -static HANDLE mounted_handle = INVALID_HANDLE_VALUE; - -char* MountISO(const char* path) -{ - VIRTUAL_STORAGE_TYPE vtype = { VIRTUAL_STORAGE_TYPE_DEVICE_ISO, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT }; - ATTACH_VIRTUAL_DISK_PARAMETERS vparams = { 0 }; - DWORD r; - wchar_t wtmp[128]; - ULONG size = ARRAYSIZE(wtmp); - wconvert(path); - char* ret = NULL; - - PF_INIT_OR_OUT(OpenVirtualDisk, VirtDisk); - PF_INIT_OR_OUT(AttachVirtualDisk, VirtDisk); - PF_INIT_OR_OUT(GetVirtualDiskPhysicalPath, VirtDisk); - - if ((mounted_handle != NULL) && (mounted_handle != INVALID_HANDLE_VALUE)) - UnMountISO(); - - r = pfOpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO, - OPEN_VIRTUAL_DISK_FLAG_NONE, NULL, &mounted_handle); - if (r != ERROR_SUCCESS) { - SetLastError(r); - uprintf("Could not open ISO '%s': %s", path, WindowsErrorString()); - goto out; - } - - vparams.Version = ATTACH_VIRTUAL_DISK_VERSION_1; - r = pfAttachVirtualDisk(mounted_handle, NULL, ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY | - ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER, 0, &vparams, NULL); - if (r != ERROR_SUCCESS) { - SetLastError(r); - uprintf("Could not mount ISO '%s': %s", path, WindowsErrorString()); - goto out; - } - - r = pfGetVirtualDiskPhysicalPath(mounted_handle, &size, wtmp); - if (r != ERROR_SUCCESS) { - SetLastError(r); - uprintf("Could not obtain physical path for mounted ISO '%s': %s", path, WindowsErrorString()); - goto out; - } - wchar_to_utf8_no_alloc(wtmp, physical_path, sizeof(physical_path)); - ret = physical_path; - -out: - if (ret == NULL) - UnMountISO(); - wfree(path); - return ret; -} - -void UnMountISO(void) -{ - PF_INIT_OR_OUT(DetachVirtualDisk, VirtDisk); - - if ((mounted_handle == NULL) || (mounted_handle == INVALID_HANDLE_VALUE)) - goto out; - - pfDetachVirtualDisk(mounted_handle, DETACH_VIRTUAL_DISK_FLAG_NONE, 0); - safe_closehandle(mounted_handle); -out: - physical_path[0] = 0; -} - // TODO: If we can't get save to ISO from virtdisk, we might as well drop this -static DWORD WINAPI SaveISOThread(void* param) +static DWORD WINAPI IsoSaveImageThread(void* param) { BOOL s; DWORD rSize, wSize; @@ -1908,7 +1833,7 @@ out: ExitThread(0); } -void SaveISO(void) +void IsoSaveImage(void) { static IMG_SAVE img_save = { 0 }; char filename[33] = "disc_image.iso"; @@ -1939,7 +1864,7 @@ void SaveISO(void) // Disable all controls except cancel EnableControls(FALSE, FALSE); InitProgress(TRUE); - format_thread = CreateThread(NULL, 0, SaveISOThread, &img_save, 0, NULL); + format_thread = CreateThread(NULL, 0, IsoSaveImageThread, &img_save, 0, NULL); if (format_thread != NULL) { uprintf("\r\nSave to ISO operation started"); PrintInfo(0, -1); diff --git a/src/rufus.c b/src/rufus.c index 0855d345..3acf3ae2 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -128,7 +128,7 @@ BOOL advanced_mode_device, advanced_mode_format, allow_dual_uefi_bios, detect_fa BOOL usb_debug, use_fake_units, preserve_timestamps = FALSE, fast_zeroing = FALSE, app_changed_size = FALSE; BOOL zero_drive = FALSE, list_non_usb_removable_drives = FALSE, enable_file_indexing, large_drive = FALSE; BOOL write_as_image = FALSE, write_as_esp = FALSE, use_vds = FALSE, ignore_boot_marker = FALSE; -BOOL appstore_version = FALSE, is_vds_available = TRUE, persistent_log = FALSE; +BOOL appstore_version = FALSE, is_vds_available = TRUE, persistent_log = FALSE, has_ffu_support = FALSE; float fScale = 1.0f; int dialog_showing = 0, selection_default = BT_IMAGE, persistence_unit_selection = -1, imop_win_sel = 0; int default_fs, fs_type, boot_type, partition_type, target_type; @@ -2588,8 +2588,11 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA img_provided = FALSE; // One off thing... } else { char* old_image_path = image_path; + char extensions[128] = "*.iso;*.img;*.vhd;*.vhdx;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wim;*.esd;*.vtsi"; + if (has_ffu_support) + strcat(extensions, ";*.ffu"); // If declared globaly, lmprintf(MSG_036) would be called on each message... - EXT_DECL(img_ext, NULL, __VA_GROUP__("*.iso;*.img;*.vhd;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wim;*.esd;*.vtsi"), + EXT_DECL(img_ext, NULL, __VA_GROUP__(extensions), __VA_GROUP__(lmprintf(MSG_036))); image_path = FileDialog(FALSE, NULL, &img_ext, 0); if (image_path == NULL) { @@ -2684,7 +2687,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA } break; case IDC_SAVE: - SaveVHD(); + VhdSaveImage(); break; case IDM_SELECT: case IDM_DOWNLOAD: @@ -3695,6 +3698,9 @@ skip_args_processing: // Detect CPU acceleration for SHA-1/SHA-256 cpu_has_sha1_accel = DetectSHA1Acceleration(); cpu_has_sha256_accel = DetectSHA256Acceleration(); + // FFU support started with Windows 10 1709 (through FfuProvider.dll) + static_sprintf(tmp_path, "%s\\dism\\FfuProvider.dll", sysnative_dir); + has_ffu_support = (_accessU(tmp_path, 0) == 0); relaunch: ubprintf("Localization set to '%s'", selected_locale->txt[0]); @@ -3964,7 +3970,7 @@ extern int TestHashes(void); } // Alt-O => Save from Optical drive to ISO if ((msg.message == WM_SYSKEYDOWN) && (msg.wParam == 'O')) { - SaveISO(); + IsoSaveImage(); continue; } // Alt-P => Toggle GPT ESP to and from Basic Data type (Windows 10 or later) diff --git a/src/rufus.h b/src/rufus.h index 84ec91a3..53dc6251 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -652,8 +652,6 @@ extern int64_t ExtractISOFile(const char* iso, const char* iso_file, const char* extern BOOL CopySKUSiPolicy(const char* drive_name); extern BOOL HasEfiImgBootLoaders(void); extern BOOL DumpFatDir(const char* path, int32_t cluster); -extern char* MountISO(const char* path); -extern void UnMountISO(void); extern BOOL InstallSyslinux(DWORD drive_index, char drive_letter, int fs); extern uint16_t GetSyslinuxVersion(char* buf, size_t buf_size, char** ext); extern BOOL SetAutorun(const char* path); diff --git a/src/rufus.rc b/src/rufus.rc index c468a3d4..681afaa9 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2067" +CAPTION "Rufus 4.2.2068" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2067,0 - PRODUCTVERSION 4,2,2067,0 + FILEVERSION 4,2,2068,0 + PRODUCTVERSION 4,2,2068,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2067" + VALUE "FileVersion", "4.2.2068" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2067" + VALUE "ProductVersion", "4.2.2068" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index 073636ce..eaef1810 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -1235,7 +1235,7 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) // Check settings to see if we have existing data for these DLL calls. for (i = 0; i < resolver->count; i++) { static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), - GuidToString(&info->Guid, FALSE), info->Age, resolver->name[i]); + GuidToString(&info->Guid, FALSE), (int)info->Age, resolver->name[i]); resolver->address[i] = ReadSetting32(saved_id); if (resolver->address[i] == 0) break; @@ -1253,7 +1253,7 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) goto out; static_sprintf(path, "%s\\%s", temp_dir, info->PdbName); static_sprintf(url, "http://msdl.microsoft.com/download/symbols/%s/%s%x/%s", - info->PdbName, GuidToString(&info->Guid, FALSE), info->Age, info->PdbName); + info->PdbName, GuidToString(&info->Guid, FALSE), (int)info->Age, info->PdbName); if (DownloadToFileOrBufferEx(url, path, SYMBOL_SERVER_USER_AGENT, NULL, hMainDialog, FALSE) < 200 * KB) goto out; @@ -1280,7 +1280,7 @@ uint32_t ResolveDllAddress(dll_resolver_t* resolver) r = 0; for (i = 0; i < resolver->count; i++) { static_sprintf(saved_id, "%s@%s%x:%s", _filenameU(resolver->path), - GuidToString(&info->Guid, FALSE), info->Age, resolver->name[i]); + GuidToString(&info->Guid, FALSE), (int)info->Age, resolver->name[i]); if (resolver->address[i] != 0) { WriteSetting32(saved_id, resolver->address[i]); r++; diff --git a/src/vhd.c b/src/vhd.c index 6c6abfc0..1823fc84 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -58,7 +58,7 @@ typedef struct { uint32_t wim_nb_files, wim_proc_files, wim_extra_files; HANDLE wim_thread = NULL; extern int default_thread_priority; -extern BOOL ignore_boot_marker; +extern BOOL ignore_boot_marker, has_ffu_support; extern RUFUS_DRIVE rufus_drive[MAX_DRIVES]; extern HANDLE format_thread; @@ -67,7 +67,6 @@ static uint32_t progress_report_mask; static uint64_t progress_offset = 0, progress_total = 100; static wchar_t wmount_path[MAX_PATH] = { 0 }, wmount_track[MAX_PATH] = { 0 }; static char sevenzip_path[MAX_PATH]; -static const char conectix_str[] = VHD_FOOTER_COOKIE; static BOOL count_files; static int progress_op = OP_FILE_COPY, progress_msg = MSG_267; @@ -83,7 +82,7 @@ static BOOL Get7ZipPath(void) typedef struct { const char* ext; - bled_compression_type type; + uint8_t type; } comp_assoc; static comp_assoc file_assoc[] = { @@ -94,33 +93,75 @@ static comp_assoc file_assoc[] = { { ".bz2", BLED_COMPRESSION_BZIP2 }, { ".xz", BLED_COMPRESSION_XZ }, { ".vtsi", BLED_COMPRESSION_VTSI }, + { ".ffu", BLED_COMPRESSION_MAX }, + { ".vhd", BLED_COMPRESSION_MAX + 1 }, + { ".vhdx", BLED_COMPRESSION_MAX + 2 }, }; -// For now we consider that an image that matches a known extension is bootable +// Look for a boot marker in the MBR area of the image static BOOL IsCompressedBootableImage(const char* path) { - char *p; + char *ext = NULL, *physical_disk = NULL; unsigned char *buf = NULL; int i; + FILE* fd = NULL; BOOL r = FALSE; - int64_t dc; + int64_t dc = 0; img_report.compression_type = BLED_COMPRESSION_NONE; - for (p = (char*)&path[strlen(path)-1]; (*p != '.') && (p != path); p--); + if (safe_strlen(path) > 4) + for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--); - if (p == path) - return FALSE; - - for (i = 0; i= (512 + size))) { - footer = (vhd_footer*)malloc(size); - ptr.QuadPart = img_report.image_size - size; - if ( (footer == NULL) || (!SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN)) || - (!ReadFile(handle, footer, size, &size, NULL)) || (size != sizeof(vhd_footer)) ) { - uprintf(" Could not read VHD footer"); - is_bootable_img = -3; - goto out; - } - if (memcmp(footer->cookie, conectix_str, sizeof(footer->cookie)) == 0) { - img_report.image_size -= sizeof(vhd_footer); - if ( (bswap_uint32(footer->file_format_version) != VHD_FOOTER_FILE_FORMAT_V1_0) - || (bswap_uint32(footer->disk_type) != VHD_FOOTER_TYPE_FIXED_HARD_DISK)) { - uprintf(" Unsupported type of VHD image"); - is_bootable_img = 0; - goto out; - } - // Might as well validate the checksum while we're at it - old_checksum = bswap_uint32(footer->checksum); - footer->checksum = 0; - for (checksum = 0, i = 0; i < sizeof(vhd_footer); i++) - checksum += ((uint8_t*)footer)[i]; - checksum = ~checksum; - if (checksum != old_checksum) - uprintf(" Warning: VHD footer seems corrupted (checksum: %04X, expected: %04X)", old_checksum, checksum); - // Need to remove the footer from our payload - uprintf(" Image is a Fixed Hard Disk VHD file"); - img_report.is_vhd = TRUE; - } - } - out: - safe_free(footer); safe_closehandle(handle); return is_bootable_img; } @@ -405,7 +410,7 @@ char* WimMountImage(const char* image, int index) mp.index = index; // Try to unmount an existing stale image, if there is any - mount_path = GetExistingMountPoint(image, index); + mount_path = WimGetExistingMountPoint(image, index); if (mount_path != NULL) { uprintf("WARNING: Found stale '%s [%d]' image mounted on '%s' - Attempting to unmount it...", image, index, mount_path); @@ -499,7 +504,7 @@ BOOL WimUnmountImage(const char* image, int index, BOOL commit) // This basically parses HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WIMMount\Mounted Images // to see if an instance exists with the image/index passed as parameter and returns // the mount point of this image if found, or NULL otherwise. -char* GetExistingMountPoint(const char* image, int index) +char* WimGetExistingMountPoint(const char* image, int index) { static char path[MAX_PATH]; char class[MAX_PATH] = "", guid[40], key_name[MAX_PATH]; @@ -886,17 +891,102 @@ BOOL WimApplyImage(const char* image, int index, const char* dst) } // VirtDisk API Prototypes since we can't use delay-loading because of MinGW +// See https://github.com/pbatard/rufus/issues/2272#issuecomment-1615976013 PF_TYPE_DECL(WINAPI, DWORD, CreateVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR, VIRTUAL_DISK_ACCESS_MASK, PSECURITY_DESCRIPTOR, CREATE_VIRTUAL_DISK_FLAG, ULONG, PCREATE_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED, PHANDLE)); +PF_TYPE_DECL(WINAPI, DWORD, OpenVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR, + VIRTUAL_DISK_ACCESS_MASK, OPEN_VIRTUAL_DISK_FLAG, POPEN_VIRTUAL_DISK_PARAMETERS, PHANDLE)); +PF_TYPE_DECL(WINAPI, DWORD, AttachVirtualDisk, (HANDLE, PSECURITY_DESCRIPTOR, + ATTACH_VIRTUAL_DISK_FLAG, ULONG, PATTACH_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED)); +PF_TYPE_DECL(WINAPI, DWORD, DetachVirtualDisk, (HANDLE, DETACH_VIRTUAL_DISK_FLAG, ULONG)); +PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskPhysicalPath, (HANDLE, PULONG, PWSTR)); PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskOperationProgress, (HANDLE, LPOVERLAPPED, PVIRTUAL_DISK_PROGRESS)); +static char physical_path[128] = ""; +static HANDLE mounted_handle = INVALID_HANDLE_VALUE; + +// Mount an ISO or a VHD/VHDX image +// Returns the physical path of the mounted image or NULL on error. +char* VhdMountImage(const char* path) +{ + VIRTUAL_STORAGE_TYPE vtype = { VIRTUAL_STORAGE_TYPE_DEVICE_ISO, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT }; + ATTACH_VIRTUAL_DISK_PARAMETERS vparams = { 0 }; + DWORD r; + wchar_t wtmp[128]; + ULONG size = ARRAYSIZE(wtmp); + wconvert(path); + char *ret = NULL, *ext = NULL; + + PF_INIT_OR_OUT(OpenVirtualDisk, VirtDisk); + PF_INIT_OR_OUT(AttachVirtualDisk, VirtDisk); + PF_INIT_OR_OUT(GetVirtualDiskPhysicalPath, VirtDisk); + + if (wpath == NULL) + return NULL; + + if ((mounted_handle != NULL) && (mounted_handle != INVALID_HANDLE_VALUE)) + VhdUnmountImage(); + + if (safe_strlen(path) > 4) + for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--); + if (safe_stricmp(ext, ".vhdx") == 0) + vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX; + else if (safe_stricmp(ext, ".vhdx") == 0) + vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; + + r = pfOpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO, + OPEN_VIRTUAL_DISK_FLAG_NONE, NULL, &mounted_handle); + if (r != ERROR_SUCCESS) { + SetLastError(r); + uprintf("Could not open image '%s': %s", path, WindowsErrorString()); + goto out; + } + + vparams.Version = ATTACH_VIRTUAL_DISK_VERSION_1; + r = pfAttachVirtualDisk(mounted_handle, NULL, ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY | + ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER, 0, &vparams, NULL); + if (r != ERROR_SUCCESS) { + SetLastError(r); + uprintf("Could not mount image '%s': %s", path, WindowsErrorString()); + goto out; + } + + r = pfGetVirtualDiskPhysicalPath(mounted_handle, &size, wtmp); + if (r != ERROR_SUCCESS) { + SetLastError(r); + uprintf("Could not obtain physical path for mounted image '%s': %s", path, WindowsErrorString()); + goto out; + } + wchar_to_utf8_no_alloc(wtmp, physical_path, sizeof(physical_path)); + ret = physical_path; + +out: + if (ret == NULL) + VhdUnmountImage(); + wfree(path); + return ret; +} + +void VhdUnmountImage(void) +{ + PF_INIT_OR_OUT(DetachVirtualDisk, VirtDisk); + + if ((mounted_handle == NULL) || (mounted_handle == INVALID_HANDLE_VALUE)) + goto out; + + pfDetachVirtualDisk(mounted_handle, DETACH_VIRTUAL_DISK_FLAG_NONE, 0); + safe_closehandle(mounted_handle); +out: + physical_path[0] = 0; +} + // Since we no longer have to deal with Windows 7, we can call on CreateVirtualDisk() // to backup a physical disk to VHD/VHDX. Now if this could also be used to create an // ISO from optical media that would be swell, but no matter what I tried, it didn't // seem possible... -static DWORD WINAPI SaveVHDThread(void* param) +static DWORD WINAPI VhdSaveImageThread(void* param) { IMG_SAVE* img_save = (IMG_SAVE*)param; HANDLE handle = INVALID_HANDLE_VALUE; @@ -984,21 +1074,21 @@ out: // calls, as well as how to properly hook into the DLL for every arch/every release // of Windows, would be a massive timesink, we just take a shortcut by calling dism // directly, as imperfect as such a solution might be... -static DWORD WINAPI SaveFFUThread(void* param) +static DWORD WINAPI FfuSaveImageThread(void* param) { DWORD r; IMG_SAVE* img_save = (IMG_SAVE*)param; char cmd[MAX_PATH + 128], *letter = "", *label; GetDriveLabel(SelectedDrive.DeviceNumber, letter, &label, TRUE); - static_sprintf(cmd, "dism /capture-ffu /capturedrive=%s /imagefile=\"%s\" " - "/name:\"%s\" /description:\"Created by %s (%s)\"", + static_sprintf(cmd, "dism /Capture-Ffu /CaptureDrive:%s /ImageFile:\"%s\" " + "/Name:\"%s\" /Description:\"Created by %s (%s)\"", img_save->DevicePath, img_save->ImagePath, label, APPLICATION_NAME, RUFUS_URL); uprintf("Running command: '%s", cmd); r = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261); if (r != 0 && !IS_ERROR(FormatStatus)) { SetLastError(r); - uprintf("Failed to create FFU image: %s", WindowsErrorString()); + uprintf("Failed to capture FFU image: %s", WindowsErrorString()); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_WINDOWS) | SCODE_CODE(r); } safe_free(img_save->DevicePath); @@ -1007,7 +1097,7 @@ static DWORD WINAPI SaveFFUThread(void* param) ExitThread(r); } -void SaveVHD(void) +void VhdSaveImage(void) { static IMG_SAVE img_save = { 0 }; char filename[128]; @@ -1023,9 +1113,8 @@ void SaveVHD(void) static_sprintf(filename, "%s.vhdx", rufus_drive[DriveIndex].label); img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex); img_save.DevicePath = GetPhysicalName(img_save.DeviceNum); - // FFU support started with Windows 10 1709 (through FfuProvider.dll) and requires GPT - static_sprintf(path, "%s\\dism\\FfuProvider.dll", sysnative_dir); - if ((_accessU(path, 0) != 0) || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) + // FFU support requires GPT + if (!has_ffu_support || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) img_ext.count = 2; img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; @@ -1048,7 +1137,7 @@ void SaveVHD(void) FormatStatus = 0; InitProgress(TRUE); format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? - SaveFFUThread : SaveVHDThread, &img_save, 0, NULL); + FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL); if (format_thread != NULL) { uprintf("\r\nSave to VHD operation started"); PrintInfo(0, -1); diff --git a/src/vhd.h b/src/vhd.h index 6609b4aa..65ac06d9 100644 --- a/src/vhd.h +++ b/src/vhd.h @@ -23,23 +23,6 @@ #pragma once -#define VHD_FOOTER_COOKIE { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x' } - -#define VHD_FOOTER_FEATURES_NONE 0x00000000 -#define VHD_FOOTER_FEATURES_TEMPORARY 0x00000001 -#define VHD_FOOTER_FEATURES_RESERVED 0x00000002 - -#define VHD_FOOTER_FILE_FORMAT_V1_0 0x00010000 - -#define VHD_FOOTER_DATA_OFFSET_FIXED_DISK 0xFFFFFFFFFFFFFFFFULL - -#define VHD_FOOTER_CREATOR_HOST_OS_WINDOWS { 'W', 'i', '2', 'k' } -#define VHD_FOOTER_CREATOR_HOST_OS_MAC { 'M', 'a', 'c', ' ' } - -#define VHD_FOOTER_TYPE_FIXED_HARD_DISK 0x00000002 -#define VHD_FOOTER_TYPE_DYNAMIC_HARD_DISK 0x00000003 -#define VHD_FOOTER_TYPE_DIFFER_HARD_DISK 0x00000004 - #define WIM_MAGIC 0x0000004D4957534DULL // "MSWIM\0\0\0" #define WIM_HAS_API_EXTRACT 1 #define WIM_HAS_7Z_EXTRACT 2 @@ -142,40 +125,6 @@ enum WIMMessage { WIM_MSG_ABORT_IMAGE = -1 }; -/* - * VHD Fixed HD footer (Big Endian) - * http://download.microsoft.com/download/f/f/e/ffef50a5-07dd-4cf8-aaa3-442c0673a029/Virtual%20Hard%20Disk%20Format%20Spec_10_18_06.doc - * NB: If a dymamic implementation is needed, check the GPL v3 compatible C++ implementation from: - * https://sourceforge.net/p/urbackup/backend/ci/master/tree/fsimageplugin/ - */ -#pragma pack(push, 1) -typedef struct vhd_footer { - char cookie[8]; - uint32_t features; - uint32_t file_format_version; - uint64_t data_offset; - uint32_t timestamp; - char creator_app[4]; - uint32_t creator_version; - char creator_host_os[4]; - uint64_t original_size; - uint64_t current_size; - union { - uint32_t geometry; - struct { - uint16_t cylinders; - uint8_t heads; - uint8_t sectors; - } chs; - } disk_geometry; - uint32_t disk_type; - uint32_t checksum; - uuid_t unique_id; - uint8_t saved_state; - uint8_t reserved[427]; -} vhd_footer; -#pragma pack(pop) - extern uint8_t WimExtractCheck(BOOL bSilent); extern BOOL WimExtractFile(const char* wim_image, int index, const char* src, const char* dst, BOOL bSilent); extern BOOL WimExtractFile_API(const char* image, int index, const char* src, const char* dst, BOOL bSilent); @@ -183,8 +132,10 @@ extern BOOL WimExtractFile_7z(const char* image, int index, const char* src, con extern BOOL WimApplyImage(const char* image, int index, const char* dst); extern char* WimMountImage(const char* image, int index); extern BOOL WimUnmountImage(const char* image, int index, BOOL commit); -extern char* GetExistingMountPoint(const char* image, int index); +extern char* WimGetExistingMountPoint(const char* image, int index); extern BOOL WimIsValidIndex(const char* image, int index); extern int8_t IsBootableImage(const char* path); -extern void SaveVHD(void); -extern void SaveISO(void); +extern char* VhdMountImage(const char* path); +extern void VhdUnmountImage(void); +extern void VhdSaveImage(void); +extern void IsoSaveImage(void); diff --git a/src/wue.c b/src/wue.c index f0423312..62970dfa 100644 --- a/src/wue.c +++ b/src/wue.c @@ -440,7 +440,7 @@ BOOL PopulateWindowsVersion(void) // If we're not using a straight install.wim, we need to mount the ISO to access it if (!img_report.is_windows_img) { - mounted_iso = MountISO(image_path); + mounted_iso = VhdMountImage(image_path); if (mounted_iso == NULL) { uprintf("Could not mount Windows ISO for build number detection"); return FALSE; @@ -468,7 +468,7 @@ BOOL PopulateWindowsVersion(void) out: DeleteFileU(xml_file); if (!img_report.is_windows_img) - UnMountISO(); + VhdUnmountImage(); return ((img_report.win_version.major != 0) && (img_report.win_version.build != 0)); } @@ -532,7 +532,7 @@ int SetWinToGoIndex(void) // If we're not using a straight install.wim, we need to mount the ISO to access it if (!img_report.is_windows_img) { - mounted_iso = MountISO(image_path); + mounted_iso = VhdMountImage(image_path); if (mounted_iso == NULL) { uprintf("Could not mount ISO for Windows To Go selection"); return -1; @@ -613,7 +613,7 @@ int SetWinToGoIndex(void) out: DeleteFileU(xml_file); if (!img_report.is_windows_img) - UnMountISO(); + VhdUnmountImage(); return wintogo_index; } @@ -640,7 +640,7 @@ BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp) } if (!img_report.is_windows_img) { - mounted_iso = MountISO(image_path); + mounted_iso = VhdMountImage(image_path); if (mounted_iso == NULL) { uprintf("Could not mount ISO for Windows To Go installation"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_ISO_EXTRACT); @@ -656,11 +656,11 @@ BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp) if (!IS_ERROR(FormatStatus)) FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_ISO_EXTRACT); if (!img_report.is_windows_img) - UnMountISO(); + VhdUnmountImage(); return FALSE; } if (!img_report.is_windows_img) - UnMountISO(); + VhdUnmountImage(); if (use_esp) { uprintf("Setting up EFI System Partition"); From 5191c68337069ad0ffdaf17f88929162da78ec3b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 10 Jul 2023 11:34:50 +0200 Subject: [PATCH 09/53] [ui] keep user preferred image type when saving drive to VHD * Also fix a Coverity warning and use a better description for SELECT image types. --- src/format.c | 3 +- src/net.c | 2 +- src/rufus.c | 13 +-- src/rufus.h | 2 +- src/rufus.rc | 10 +-- src/settings.h | 4 +- src/stdlg.c | 239 ++++++++++++++++++------------------------------- src/vhd.c | 87 ++++++++++-------- 8 files changed, 156 insertions(+), 204 deletions(-) diff --git a/src/format.c b/src/format.c index e955f40e..b9017a37 100644 --- a/src/format.c +++ b/src/format.c @@ -1642,12 +1642,13 @@ DWORD WINAPI FormatThread(void* param) if ((boot_type == BT_IMAGE) && write_as_image) { // Special case for FFU images if (img_report.compression_type == IMG_COMPRESSION_FFU) { - char cmd[MAX_PATH + 128], *physical; + char cmd[MAX_PATH + 128], *physical = NULL; // Should have been filtered out beforehand assert(has_ffu_support); safe_unlockclose(hPhysicalDrive); physical = GetPhysicalName(SelectedDrive.DeviceNumber); static_sprintf(cmd, "dism /Apply-Ffu /ApplyDrive:%s /ImageFile:\"%s\"", physical, image_path); + safe_free(physical); uprintf("Running command: '%s", cmd); cr = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261); if (cr != 0 && !IS_ERROR(FormatStatus)) { diff --git a/src/net.c b/src/net.c index ff394433..bdbb9c7a 100644 --- a/src/net.c +++ b/src/net.c @@ -1040,7 +1040,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) #endif EXT_DECL(img_ext, GetShortName(url), __VA_GROUP__("*.iso"), __VA_GROUP__(lmprintf(MSG_036))); img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_ISO; - img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); + img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, NULL); if (img_save.ImagePath == NULL) { goto out; } diff --git a/src/rufus.c b/src/rufus.c index 3acf3ae2..ef248a72 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -139,7 +139,7 @@ char embedded_sl_version_str[2][12] = { "?.??", "?.??" }; char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; char msgbox[1024], msgbox_title[32], *ini_file = NULL, *image_path = NULL, *short_image_path; -char *archive_path = NULL, image_option_txt[128], *fido_url = NULL; +char *archive_path = NULL, image_option_txt[128], *fido_url = NULL, *save_image_type = NULL; StrArray BlockingProcess, ImageList; // Number of steps for each FS for FCC_STRUCTURE_PROGRESS const int nb_steps[FS_MAX] = { 5, 5, 12, 1, 10, 1, 1, 1, 1 }; @@ -1013,7 +1013,7 @@ BOOL CALLBACK LogCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) log_size = GetDlgItemTextU(hDlg, IDC_LOG_EDIT, log_buffer, log_size); if (log_size != 0) { log_size--; // remove NUL terminator - filepath = FileDialog(TRUE, user_dir, &log_ext, 0); + filepath = FileDialog(TRUE, user_dir, &log_ext, NULL); if (filepath != NULL) FileIO(FILE_IO_WRITE, filepath, &log_buffer, &log_size); safe_free(filepath); @@ -2567,7 +2567,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA EXT_DECL(arch_ext, NULL, __VA_GROUP__("*.zip"), __VA_GROUP__(lmprintf(MSG_309))); if (image_path == NULL) break; - archive_path = FileDialog(FALSE, NULL, &arch_ext, 0); + archive_path = FileDialog(FALSE, NULL, &arch_ext, NULL); if (archive_path != NULL) { struct __stat64 stat64 = { 0 }; _stat64U(archive_path, &stat64); @@ -2591,10 +2591,10 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA char extensions[128] = "*.iso;*.img;*.vhd;*.vhdx;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wim;*.esd;*.vtsi"; if (has_ffu_support) strcat(extensions, ";*.ffu"); - // If declared globaly, lmprintf(MSG_036) would be called on each message... + // If declared globaly, lmprintf(MSG_280) would be called on each message... EXT_DECL(img_ext, NULL, __VA_GROUP__(extensions), - __VA_GROUP__(lmprintf(MSG_036))); - image_path = FileDialog(FALSE, NULL, &img_ext, 0); + __VA_GROUP__(lmprintf(MSG_280))); + image_path = FileDialog(FALSE, NULL, &img_ext, NULL); if (image_path == NULL) { if (old_image_path != NULL) { // Reselect previous image @@ -3556,6 +3556,7 @@ skip_args_processing: enable_extra_hashes = ReadSettingBool(SETTING_ENABLE_EXTRA_HASHES); ignore_boot_marker = ReadSettingBool(SETTING_IGNORE_BOOT_MARKER); persistent_log = ReadSettingBool(SETTING_PERSISTENT_LOG); + save_image_type = ReadSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE); // This restores the Windows User Experience/unattend.xml mask from the saved user // settings, and is designed to work even if we add new options later. wue_options = ReadSetting32(SETTING_WUE_OPTIONS); diff --git a/src/rufus.h b/src/rufus.h index 53dc6251..4c485b92 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -655,7 +655,7 @@ extern BOOL DumpFatDir(const char* path, int32_t cluster); extern BOOL InstallSyslinux(DWORD drive_index, char drive_letter, int fs); extern uint16_t GetSyslinuxVersion(char* buf, size_t buf_size, char** ext); extern BOOL SetAutorun(const char* path); -extern char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options); +extern char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext); extern BOOL FileIO(enum file_io_type io_type, char* path, char** buffer, DWORD* size); extern unsigned char* GetResource(HMODULE module, char* name, char* type, const char* desc, DWORD* len, BOOL duplicate); extern DWORD GetResourceSize(HMODULE module, char* name, char* type, const char* desc); diff --git a/src/rufus.rc b/src/rufus.rc index 681afaa9..15de22a2 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2068" +CAPTION "Rufus 4.2.2069" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2068,0 - PRODUCTVERSION 4,2,2068,0 + FILEVERSION 4,2,2069,0 + PRODUCTVERSION 4,2,2069,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2068" + VALUE "FileVersion", "4.2.2069" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2068" + VALUE "ProductVersion", "4.2.2069" END END BLOCK "VarFileInfo" diff --git a/src/settings.h b/src/settings.h index 20f0f5ea..bf8c8ab2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Settings access, through either registry or INI file - * Copyright © 2015-2022 Pete Batard + * Copyright © 2015-2023 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include #include #include "rufus.h" +#include "msapi_utf8.h" #include "registry.h" #pragma once @@ -52,6 +53,7 @@ extern char* ini_file; #define SETTING_USE_UDF_VERSION "UseUdfVersion" #define SETTING_USE_VDS "UseVds" #define SETTING_PERSISTENT_LOG "PersistentLog" +#define SETTING_PREFERRED_SAVE_IMAGE_TYPE "PreferredSaveImageType" #define SETTING_PRESERVE_TIMESTAMPS "PreserveTimestamps" #define SETTING_VERBOSE_UPDATES "VerboseUpdateCheck" #define SETTING_WUE_OPTIONS "WindowsUserExperienceOptions" diff --git a/src/stdlg.c b/src/stdlg.c index 5316fb40..73839e49 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -90,14 +90,9 @@ void SetDialogFocus(HWND hDlg, HWND hCtrl) * *EACH* thread you invoke FileDialog from, as GetDisplayName() will * return error 0x8001010E otherwise. */ -char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options) +char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext) { - DWORD tmp; - OPENFILENAMEA ofn; - char selected_name[MAX_PATH]; - char *ext_string = NULL, *all_files = NULL; - size_t i, j, ext_strlen; - BOOL r; + size_t i; char* filepath = NULL; HRESULT hr = FALSE; IFileDialog *pfd = NULL; @@ -108,167 +103,107 @@ char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options) if ((ext == NULL) || (ext->count == 0) || (ext->extension == NULL) || (ext->description == NULL)) return NULL; - dialog_showing++; filter_spec = (COMDLG_FILTERSPEC*)calloc(ext->count + 1, sizeof(COMDLG_FILTERSPEC)); - if (filter_spec != NULL) { - // Setup the file extension filter table - for (i = 0; i < ext->count; i++) { - filter_spec[i].pszSpec = utf8_to_wchar(ext->extension[i]); - filter_spec[i].pszName = utf8_to_wchar(ext->description[i]); - } - filter_spec[i].pszSpec = L"*.*"; - filter_spec[i].pszName = utf8_to_wchar(lmprintf(MSG_107)); + if (filter_spec == NULL) + return NULL; - hr = CoCreateInstance(save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, - &IID_IFileDialog, (LPVOID)&pfd); + dialog_showing++; - if (FAILED(hr)) { - SetLastError(hr); - uprintf("CoCreateInstance for FileOpenDialog failed: %s\n", WindowsErrorString()); - if (pfd != NULL) { - IFileDialog_Release(pfd); - pfd = NULL; // Just in case - } - goto fallback; - } + // Setup the file extension filter table + for (i = 0; i < ext->count; i++) { + filter_spec[i].pszSpec = utf8_to_wchar(ext->extension[i]); + filter_spec[i].pszName = utf8_to_wchar(ext->description[i]); + } + filter_spec[i].pszSpec = L"*.*"; + filter_spec[i].pszName = utf8_to_wchar(lmprintf(MSG_107)); - // Set the file extension filters - IFileDialog_SetFileTypes(pfd, (UINT)ext->count + 1, filter_spec); + hr = CoCreateInstance(save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, + &IID_IFileDialog, (LPVOID)&pfd); - if (path == NULL) { - // Try to use the "Downloads" folder as the initial default directory - const GUID download_dir_guid = - { 0x374de290, 0x123f, 0x4565, { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } }; - hr = SHGetKnownFolderPath(&download_dir_guid, 0, 0, &wpath); - if (SUCCEEDED(hr)) { - hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); - if (SUCCEEDED(hr)) { - IFileDialog_SetDefaultFolder(pfd, si_path); - } - CoTaskMemFree(wpath); - } - } else { - wpath = utf8_to_wchar(path); + if (FAILED(hr)) { + SetLastError(hr); + uprintf("CoCreateInstance for FileOpenDialog failed: %s\n", WindowsErrorString()); + if (pfd != NULL) + goto out; + } + + // Set the file extension filters + IFileDialog_SetFileTypes(pfd, (UINT)ext->count + 1, filter_spec); + + if (path == NULL) { + // Try to use the "Downloads" folder as the initial default directory + const GUID download_dir_guid = + { 0x374de290, 0x123f, 0x4565, { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } }; + hr = SHGetKnownFolderPath(&download_dir_guid, 0, 0, &wpath); + if (SUCCEEDED(hr)) { hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); if (SUCCEEDED(hr)) { - IFileDialog_SetFolder(pfd, si_path); + IFileDialog_SetDefaultFolder(pfd, si_path); } - safe_free(wpath); + CoTaskMemFree(wpath); } - - // Set the default filename - wfilename = utf8_to_wchar((ext->filename == NULL) ? "" : ext->filename); - if (wfilename != NULL) { - IFileDialog_SetFileName(pfd, wfilename); - } - // Set a default extension so that when the user switches filters it gets - // automatically updated. Note that the IFileDialog::SetDefaultExtension() - // doc says the extension shouldn't be prefixed with unwanted characters - // but it appears to work regardless so we don't bother cleaning it. - wext = utf8_to_wchar((ext->extension == NULL) ? "" : ext->extension[0]); - if (wext != NULL) { - IFileDialog_SetDefaultExtension(pfd, wext); - } - - // Display the dialog - hr = IFileDialog_Show(pfd, hMainDialog); - - // Cleanup - safe_free(wext); - safe_free(wfilename); - for (i = 0; i < ext->count; i++) { - safe_free(filter_spec[i].pszSpec); - safe_free(filter_spec[i].pszName); - } - safe_free(filter_spec[i].pszName); - safe_free(filter_spec); - + } else { + wpath = utf8_to_wchar(path); + hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); if (SUCCEEDED(hr)) { - // Obtain the result of the user's interaction with the dialog. - hr = IFileDialog_GetResult(pfd, &psiResult); - if (SUCCEEDED(hr)) { - hr = IShellItem_GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); - if (SUCCEEDED(hr)) { - filepath = wchar_to_utf8(wpath); - CoTaskMemFree(wpath); - } else { - SetLastError(hr); - uprintf("Unable to access file path: %s\n", WindowsErrorString()); - } - IShellItem_Release(psiResult); - } - } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { - // If it's not a user cancel, assume the dialog didn't show and fallback - SetLastError(hr); - uprintf("Could not show FileOpenDialog: %s\n", WindowsErrorString()); - goto fallback; + IFileDialog_SetFolder(pfd, si_path); } - IFileDialog_Release(pfd); - dialog_showing--; - return filepath; + safe_free(wpath); } -fallback: + // Set the default filename + wfilename = utf8_to_wchar((ext->filename == NULL) ? "" : ext->filename); + if (wfilename != NULL) + IFileDialog_SetFileName(pfd, wfilename); + // Set a default extension so that when the user switches filters it gets + // automatically updated. Note that the IFileDialog::SetDefaultExtension() + // doc says the extension shouldn't be prefixed with unwanted characters + // but it appears to work regardless so we don't bother cleaning it. + wext = utf8_to_wchar((ext->extension == NULL) ? "" : ext->extension[0]); + if (wext != NULL) + IFileDialog_SetDefaultExtension(pfd, wext); + // Set the current selected extension + IFileDialog_SetFileTypeIndex(pfd, selected_ext == NULL ? 0 : *selected_ext); + + // Display the dialog and (optionally) get the selected extension index + hr = IFileDialog_Show(pfd, hMainDialog); + if (selected_ext != NULL) + IFileDialog_GetFileTypeIndex(pfd, selected_ext); + + // Cleanup + safe_free(wext); + safe_free(wfilename); + for (i = 0; i < ext->count; i++) { + safe_free(filter_spec[i].pszSpec); + safe_free(filter_spec[i].pszName); + } + safe_free(filter_spec[i].pszName); safe_free(filter_spec); - if (pfd != NULL) { - IFileDialog_Release(pfd); + + if (SUCCEEDED(hr)) { + // Obtain the result of the user's interaction with the dialog. + hr = IFileDialog_GetResult(pfd, &psiResult); + if (SUCCEEDED(hr)) { + hr = IShellItem_GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); + if (SUCCEEDED(hr)) { + filepath = wchar_to_utf8(wpath); + CoTaskMemFree(wpath); + } else { + SetLastError(hr); + uprintf("Unable to access file path: %s", WindowsErrorString()); + } + IShellItem_Release(psiResult); + } + } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { + // If it's not a user cancel, assume the dialog didn't show and fallback + SetLastError(hr); + uprintf("Could not show FileOpenDialog: %s", WindowsErrorString()); } - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = hMainDialog; - // Selected File name - static_sprintf(selected_name, "%s", (ext->filename == NULL)?"":ext->filename); - ofn.lpstrFile = selected_name; - ofn.nMaxFile = MAX_PATH; - // Set the file extension filters - all_files = lmprintf(MSG_107); - ext_strlen = 0; - for (i=0; icount; i++) { - ext_strlen += safe_strlen(ext->description[i]) + 2*safe_strlen(ext->extension[i]) + sizeof(" ()\r\r"); - } - ext_strlen += safe_strlen(all_files) + sizeof(" (*.*)\r*.*\r"); - ext_string = (char*)malloc(ext_strlen+1); - if (ext_string == NULL) - return NULL; - ext_string[0] = 0; - for (i=0, j=0; icount; i++) { - j += _snprintf(&ext_string[j], ext_strlen-j, "%s (%s)\r%s\r", ext->description[i], ext->extension[i], ext->extension[i]); - } - j = _snprintf(&ext_string[j], ext_strlen-j, "%s (*.*)\r*.*\r", all_files); - // Microsoft could really have picked a better delimiter! - for (i=0; i (UINT)img_ext.count) + i = 2; + img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, &i); + assert(i > 0 && i <= (UINT)img_ext.count); + save_image_type = (char*) &_img_ext_x[i - 1][2]; + WriteSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE, save_image_type); + switch (i) { + case 1: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; - else if (safe_strstr(img_save.ImagePath, ".ffu") != NULL) + break; + case 3: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_FFU; + break; + default: + img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX; + break; + } img_save.BufSize = DD_BUFFER_SIZE; img_save.DeviceSize = SelectedDrive.DiskSize; if (img_save.DevicePath != NULL && img_save.ImagePath != NULL) { // Reset all progress bars SendMessage(hMainDialog, UM_PROGRESS_INIT, 0, 0); FormatStatus = 0; - free_space.QuadPart = 0; - if ((GetVolumePathNameA(img_save.ImagePath, path, sizeof(path))) - && (GetDiskFreeSpaceExA(path, &free_space, NULL, NULL)) - && ((LONGLONG)free_space.QuadPart > (SelectedDrive.DiskSize + 512))) { - // Disable all controls except Cancel - EnableControls(FALSE, FALSE); - FormatStatus = 0; - InitProgress(TRUE); - format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? - FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL); - if (format_thread != NULL) { - uprintf("\r\nSave to VHD operation started"); - PrintInfo(0, -1); - SendMessage(hMainDialog, UM_TIMER_START, 0, 0); - } else { - uprintf("Unable to start VHD save thread"); - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_START_THREAD); - safe_free(img_save.DevicePath); - safe_free(img_save.ImagePath); - PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); - } - } else { - if (free_space.QuadPart == 0) { - uprintf("Unable to isolate drive name for VHD save"); - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_PATH_NOT_FOUND; - } else { - // TODO: Might be salvageable for compressed VHDX + if (img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHD) { + free_space.QuadPart = 0; + if ((GetVolumePathNameA(img_save.ImagePath, path, sizeof(path))) + && (GetDiskFreeSpaceExA(path, &free_space, NULL, NULL)) + && ((LONGLONG)free_space.QuadPart < (SelectedDrive.DiskSize + 512))) { uprintf("The VHD size is too large for the target drive"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_FILE_TOO_LARGE; + PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); + goto out; } - safe_free(img_save.DevicePath); - safe_free(img_save.ImagePath); + } + // Disable all controls except Cancel + EnableControls(FALSE, FALSE); + FormatStatus = 0; + InitProgress(TRUE); + format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? + FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL); + if (format_thread != NULL) { + uprintf("\r\nSave to VHD operation started"); + PrintInfo(0, -1); + SendMessage(hMainDialog, UM_TIMER_START, 0, 0); + } else { + uprintf("Unable to start VHD save thread"); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_START_THREAD); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); } } +out: + if (format_thread == NULL) { + safe_free(img_save.DevicePath); + safe_free(img_save.ImagePath); + } } From 65b84ea99d4ceabdfeb2662bbf7d896822ef3da3 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 10 Jul 2023 12:40:55 +0200 Subject: [PATCH 10/53] [loc] update French translation --- ChangeLog.txt | 9 ++++++++ res/loc/ChangeLog.txt | 8 +++++++ res/loc/po/fr-FR.po | 54 +++++++++++++++++++++++++++++++++++++++---- res/loc/rufus.loc | 8 +++++++ src/rufus.rc | 10 ++++---- 5 files changed, 80 insertions(+), 9 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index b96ebd73..df7d54a6 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,12 @@ +o Version 4.2 (2023.07.??) + Add detection and warning for UEFI revoked bootloaders (including ones revoked through SkuSiPolicy.p7b) + Add ZIP64 support, to extract .zip images that are larger than 4 GB + Add saving and restoring current drive to/from compressed VHDX image + Add saving and restoring current drive to/from compressed FFU (Full Flash Update) image [EXPERIMENTAL] + Fix a crash when trying to open Windows ISOs, with the x86 32-bit version + Increase ISO → ESP limit, for Debian 12 netinst images + Make sure the main partition size is aligned to the cluster size + o Version 4.1 (2023.05.31) Add timeouts on enumeration queries that may stall on some systems Restore MS-DOS drive creation through the download of binaries from Microsoft diff --git a/res/loc/ChangeLog.txt b/res/loc/ChangeLog.txt index 77f2d980..1e88bc04 100644 --- a/res/loc/ChangeLog.txt +++ b/res/loc/ChangeLog.txt @@ -7,6 +7,14 @@ Or simply download https://files.akeo.ie/pollock/pollock-1.5.exe and follow its o v4.?? (202?.??.??) - *NEW* MSG_337 "An additional file ('diskcopy.dll') must be downloaded from Microsoft to install MS-DOS (...)" + - *NEW* MSG_338 "Revoked UEFI bootloader detected" + - *NEW* MSG_339 "Rufus detected that the ISO you have selected contains a UEFI bootloader that has been revoked (...)" + - *NEW* MSG_340 "a \"Security Violation\" screen" + - *NEW* MSG_341 "a Windows Recovery Screen (BSOD) with '%s'" + - *NEW MSG_342 "Compressed VHDX Image" + - *NEW* MSG_343 "Uncompressed VHD Image" + - *NEW* MSG_344 "Full Flash Update Image" + - *NEW* MSG_345 "Some additional data must be downloaded from Microsoft to use this functionality (...)" o v3.22 (2023.??.??) // MSG_144 is aimed the the ISO download feature diff --git a/res/loc/po/fr-FR.po b/res/loc/po/fr-FR.po index c06bd232..482c8141 100644 --- a/res/loc/po/fr-FR.po +++ b/res/loc/po/fr-FR.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: 3.22\n" "Report-Msgid-Bugs-To: pete@akeo.ie\n" -"POT-Creation-Date: 2023-05-26 12:25+0100\n" -"PO-Revision-Date: 2023-05-26 12:25+0100\n" +"POT-Creation-Date: 2023-07-10 11:13+0200\n" +"PO-Revision-Date: 2023-07-10 11:25+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr_FR\n" @@ -13,7 +13,7 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "X-Rufus-LanguageName: French (Français)\n" "X-Rufus-LCID: 0x040c, 0x080c, 0x0c0c, 0x100c, 0x140c, 0x180c, 0x1c0c, 0x200c, 0x240c, 0x280c, 0x2c0c, 0x300c, 0x340c, 0x380c, 0xe40c\n" -"X-Generator: Poedit 3.3.1\n" +"X-Generator: Poedit 3.3.2\n" #. • IDD_DIALOG → IDS_DRIVE_PROPERTIES_TXT msgid "Drive Properties" @@ -1090,7 +1090,7 @@ msgid "Version %d.%d (Build %d)" msgstr "" #. • MSG_176 -msgid "English translation: Pete Batard " +msgid "mailto:support@akeo.ie" msgstr "Traduction Française : Pete Batard " #. • MSG_177 @@ -1862,6 +1862,52 @@ msgstr "" "\n" "Note : Ce fichier sera téléchargé dans le répertoire de l'application et réutilisé automatiquement s'il est présent." +#. • MSG_338 +msgid "Revoked UEFI bootloader detected" +msgstr "Bootloader UEFI révoqué détecté" + +#. • MSG_339 +msgid "" +"Rufus detected that the ISO you have selected contains a UEFI bootloader that has been revoked and that will produce %s, when Secure Boot is enabled on a fully up to date UEFI system.\n" +"\n" +"- If you obtained this ISO image from a non reputable source, you should consider the possibility that it might contain UEFI malware and avoid booting from it.\n" +"- If you obtained it from a trusted source, you should try to locate a more up to date version, that will not produce this warning." +msgstr "" +"Rufus a détecté que l’ISO que vous avez sélectionnée contient un bootloader UEFI révoqué, qui devrait produire %s sur un système UEFI à jour, lorsque 'Secure Boot' est activé.\n" +"\n" +"- Si vous avez obtenu cette image ISO à partir d’une source douteuse, vous devriez envisager la possibilité qu’elle puisse contenir un logiciel malveillant, et éviter de démarrer à partir de celle-ci.\n" +"- Si vous l’avez obtenu à partir d’une source fiable, vous devriez essayer de trouver une version plus récente, où cette notification ne se produit pas." + +#. • MSG_340 +msgid "a \"Security Violation\" screen" +msgstr "un écran « Violation de sécurité »" + +#. • MSG_341 +msgid "a Windows Recovery Screen (BSOD) with '%s'" +msgstr "un écran de récupération Windows (BSOD) avec '%s'" + +#. • MSG_342 +msgid "Compressed VHDX Image" +msgstr "Image VHDX compressée" + +#. • MSG_343 +msgid "Uncompressed VHD Image" +msgstr "Image VHD non compressée" + +#. • MSG_344 +msgid "Full Flash Update Image" +msgstr "Image 'Full Flash Update'" + +#. • MSG_345 +msgid "" +"Some additional data must be downloaded from Microsoft to use this functionality:\n" +"- Select 'Yes' to connect to the Internet and download it\n" +"- Select 'No' to cancel the operation" +msgstr "" +"Des données complémentaires doivent être téléchargées à partir du site Microsoft avant d'utiliser cette fonctionnalité :\n" +"- Sélectionnez 'Oui' pour vous connecter à Internet et le télécharger ces données\n" +"- Sélectionnez 'Non' pour annuler l’opération" + #. • MSG_900 #. #. The following messages are for the Windows Store listing only and are not used by the application diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 224c4ee3..3d99cea8 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -4698,6 +4698,14 @@ t MSG_334 "Définir les options régionales avec les mêmes valeurs que celles d t MSG_335 "Désactiver l'encryption automatique BitLocker" t MSG_336 "Log persistent" t MSG_337 "Un fichier supplémentaire ('diskcopy.dll') doit être téléchargé depuis Microsoft pour installer MS-DOS :\n- Sélectionnez 'Oui' pour vous connecter à Internet et le télécharger\n- Sélectionnez 'Non' pour annuler l’opération\n\nNote : Ce fichier sera téléchargé dans le répertoire de l'application et réutilisé automatiquement s'il est présent." +t MSG_338 "Bootloader UEFI révoqué détecté" +t MSG_339 "Rufus a détecté que l’ISO que vous avez sélectionnée contient un bootloader UEFI révoqué, qui devrait produire %s sur un système UEFI à jour, lorsque 'Secure Boot' est activé.\n\n- Si vous avez obtenu cette image ISO à partir d’une source douteuse, vous devriez envisager la possibilité qu’elle puisse contenir un logiciel malveillant, et éviter de démarrer à partir de celle-ci.\n- Si vous l’avez obtenu à partir d’une source fiable, vous devriez essayer de trouver une version plus récente, où cette notification ne se produit pas." +t MSG_340 "un écran « Violation de sécurité »" +t MSG_341 "un écran de récupération Windows (BSOD) avec '%s'" +t MSG_342 "Image VHDX compressée" +t MSG_343 "Image VHD non compressée" +t MSG_344 "Image 'Full Flash Update'" +t MSG_345 "Des données complémentaires doivent être téléchargées à partir du site Microsoft avant d'utiliser cette fonctionnalité :\n- Sélectionnez 'Oui' pour vous connecter à Internet et le télécharger ces données\n- Sélectionnez 'Non' pour annuler l’opération" t MSG_900 "Rufus est un utilitaire permettant de formater et de créer des média USB amorçables, tels que clés USB, mémoire flash, etc." t MSG_901 "Site officiel : %s" t MSG_902 "Code source: %s" diff --git a/src/rufus.rc b/src/rufus.rc index 15de22a2..247a02ab 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2069" +CAPTION "Rufus 4.2.2070" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2069,0 - PRODUCTVERSION 4,2,2069,0 + FILEVERSION 4,2,2070,0 + PRODUCTVERSION 4,2,2070,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2069" + VALUE "FileVersion", "4.2.2070" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2069" + VALUE "ProductVersion", "4.2.2070" END END BLOCK "VarFileInfo" From 3329304e673fb8f63edcdb23c7b1babc04c8437d Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 13 Jul 2023 10:11:52 +0200 Subject: [PATCH 11/53] [grub] update DB for GRUB 2.12~rc1 * Also fix some Coverity warnings in stdlg.c. --- src/db.h | 1 + src/rufus.rc | 10 +++++----- src/stdlg.c | 10 ++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/db.h b/src/db.h index 384c01b1..686980f6 100644 --- a/src/db.h +++ b/src/db.h @@ -80,6 +80,7 @@ static uint8_t sha256db[] = { 0x78, 0x64, 0x8e, 0xf0, 0xc5, 0x00, 0x41, 0x75, 0xb9, 0xa8, 0xea, 0x33, 0x30, 0x14, 0xea, 0x02, 0xc9, 0x17, 0xf8, 0x23, 0xe7, 0x7a, 0x3e, 0xc9, 0xac, 0xd9, 0xd2, 0x2b, 0x46, 0x02, 0xf3, 0x6d, // syslinux-6.03/pre13/ldlinux.sys 0x7d, 0xa9, 0xc5, 0x21, 0x76, 0xb8, 0xaf, 0x01, 0x64, 0xea, 0x39, 0x21, 0x22, 0x44, 0xb1, 0x0a, 0xa0, 0xc7, 0x97, 0xe7, 0x65, 0xbb, 0x6b, 0x92, 0x69, 0xb5, 0x8b, 0xc9, 0xe5, 0x0a, 0x9f, 0x18, // syslinux-5.01/ldlinux.bss 0x82, 0x11, 0xfa, 0xe8, 0xaf, 0xf0, 0x23, 0x3f, 0x05, 0xa8, 0xb7, 0x8c, 0x58, 0x15, 0x25, 0xe2, 0x81, 0xac, 0x98, 0x23, 0x54, 0xa8, 0xc4, 0x3b, 0xb4, 0x96, 0x5e, 0x61, 0xdc, 0x98, 0xb4, 0x62, // syslinux-6.03/pre8/ldlinux.bss + 0x83, 0x57, 0xaa, 0xd3, 0x6a, 0xec, 0x68, 0x21, 0xcb, 0xf2, 0x17, 0x4d, 0xb5, 0xd2, 0x09, 0xef, 0x2c, 0xd2, 0x62, 0x88, 0x12, 0x39, 0xeb, 0xc3, 0xf4, 0xc1, 0xcf, 0x55, 0xab, 0x10, 0xee, 0x55, // grub-2.12~rc1/core.img 0x83, 0x9b, 0xd0, 0x8a, 0xcb, 0x68, 0x47, 0xd6, 0x55, 0x07, 0xf1, 0x4e, 0x7a, 0x55, 0x6e, 0x91, 0xe6, 0x12, 0x9c, 0x47, 0x86, 0x3f, 0x7d, 0x61, 0xe2, 0xce, 0x6d, 0xb7, 0x8d, 0xf3, 0xd2, 0x3f, // syslinux-6.03/pre9/ldlinux.bss 0x87, 0xaa, 0x91, 0xf8, 0x7f, 0xba, 0x5f, 0x31, 0x79, 0x43, 0x08, 0xda, 0xa4, 0xa4, 0x8d, 0xad, 0x6c, 0xf6, 0xfa, 0x34, 0x26, 0x4d, 0x66, 0xb8, 0x84, 0xb8, 0xb9, 0xdc, 0x96, 0x42, 0xed, 0x86, // syslinux-5.02/ldlinux.sys 0x88, 0x14, 0xe5, 0x76, 0xab, 0xc1, 0xaa, 0x44, 0xdd, 0xe9, 0x43, 0xb0, 0xca, 0xae, 0xe8, 0x33, 0xa5, 0x81, 0x01, 0x42, 0x61, 0x4a, 0xde, 0xeb, 0x4c, 0xc7, 0x25, 0xe7, 0x8a, 0x50, 0x45, 0xb7, // syslinux-6.03/ldlinux.bss diff --git a/src/rufus.rc b/src/rufus.rc index 247a02ab..1eb885ff 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2070" +CAPTION "Rufus 4.2.2071" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2070,0 - PRODUCTVERSION 4,2,2070,0 + FILEVERSION 4,2,2071,0 + PRODUCTVERSION 4,2,2071,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2070" + VALUE "FileVersion", "4.2.2071" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2070" + VALUE "ProductVersion", "4.2.2071" END END BLOCK "VarFileInfo" diff --git a/src/stdlg.c b/src/stdlg.c index 73839e49..012a38b6 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -120,12 +120,13 @@ char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext) hr = CoCreateInstance(save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, &IID_IFileDialog, (LPVOID)&pfd); + if (SUCCEEDED(hr) && (pfd == NULL)) // Never trust Microsoft APIs to do the right thing + hr = ERROR_SEVERITY_ERROR | FAC(FACILITY_WINDOWS) | ERROR_API_UNAVAILABLE; if (FAILED(hr)) { SetLastError(hr); - uprintf("CoCreateInstance for FileOpenDialog failed: %s\n", WindowsErrorString()); - if (pfd != NULL) - goto out; + uprintf("CoCreateInstance for FileOpenDialog failed: %s", WindowsErrorString()); + goto out; } // Set the file extension filters @@ -195,13 +196,14 @@ char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext) } IShellItem_Release(psiResult); } - } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { + } else if (HRESULT_CODE(hr) != ERROR_CANCELLED) { // If it's not a user cancel, assume the dialog didn't show and fallback SetLastError(hr); uprintf("Could not show FileOpenDialog: %s", WindowsErrorString()); } out: + safe_free(filter_spec); if (pfd != NULL) IFileDialog_Release(pfd); dialog_showing--; From 64e85ed09ab30fcf7c3b11429ecedd23ecfe3814 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sat, 15 Jul 2023 23:20:59 +0200 Subject: [PATCH 12/53] [uefi] don't revoke Windows 11 or post Windows 10 20H1 boot media yet * As opposed to what we originally asserted, Microsoft did enact a blanket revocation in SkuSiPolicy.p7b for all post 1703 up to 2305 Windows UEFI bootloaders. * As a result, unconditionally copying SkuSiPolicy.p7b will result in media as recent as Windows 11 22H2 (v1) being flagged as revoked, which we don't want to enforce as long as Microsoft themselves haven't entered the enforcing phase of their Black Lotus mitigation (currently planned for early 2024). * Because of this, while we add some revocation detection for post 1703 bootloaders, we set it to only go as far as 20H1 for now, which means that all post 20H1 Windows 10 media and all Windows 11 media will not yet be flagged by Rufus as revoked and will still boot in a Secure Boot environment due to lack of an SkuSiPolicy.p7b. * Ultimately, per #2244 we may look for a BOOTMGRSECURITYVERSIONNUMBER resource to blanket revoke all post 1703 - pre 2305 Windows UEFI bootloaders. * Also remove the now unused comdlg32 library from the linker. --- .mingw/Makefile.am | 2 +- .mingw/Makefile.in | 2 +- .mingw/version.def | 4 ++++ .vs/rufus.vcxproj | 32 ++++++++++++++++---------------- src/Makefile.am | 4 ++-- src/Makefile.in | 4 ++-- src/hash.c | 8 ++++++++ src/rufus.c | 4 +++- src/rufus.h | 8 ++++++++ src/rufus.rc | 10 +++++----- src/stdfn.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/wue.c | 5 ++++- 12 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 .mingw/version.def diff --git a/.mingw/Makefile.am b/.mingw/Makefile.am index eb979f9f..cb131d61 100644 --- a/.mingw/Makefile.am +++ b/.mingw/Makefile.am @@ -19,7 +19,7 @@ TARGET := $(word 1,$(subst -, ,$(TUPLE))) DEF_SUFFIX := $(if $(TARGET:x86_64=),.def,.def64) .PHONY: all -all: dwmapi-delaylib.lib wintrust-delaylib.lib +all: dwmapi-delaylib.lib version-delaylib.lib wintrust-delaylib.lib %.def64: %.def $(AM_V_SED) "s/@.*//" $< >$@ diff --git a/.mingw/Makefile.in b/.mingw/Makefile.in index 0af7e49c..0dda8fb1 100644 --- a/.mingw/Makefile.in +++ b/.mingw/Makefile.in @@ -367,7 +367,7 @@ uninstall-am: .PHONY: all -all: dwmapi-delaylib.lib wintrust-delaylib.lib +all: dwmapi-delaylib.lib version-delaylib.lib wintrust-delaylib.lib %.def64: %.def $(AM_V_SED) "s/@.*//" $< >$@ diff --git a/.mingw/version.def b/.mingw/version.def new file mode 100644 index 00000000..3ee9c727 --- /dev/null +++ b/.mingw/version.def @@ -0,0 +1,4 @@ +EXPORTS + GetFileVersionInfoSizeW@8 + GetFileVersionInfoW@16 + VerQueryValueA@16 diff --git a/.vs/rufus.vcxproj b/.vs/rufus.vcxproj index 6693b2f2..7266623b 100644 --- a/.vs/rufus.vcxproj +++ b/.vs/rufus.vcxproj @@ -133,12 +133,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator true Windows MachineX86 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -162,12 +162,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator true Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\arm - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -193,12 +193,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator true Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.16299.0\um\arm64 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -229,12 +229,12 @@ /utf-8 $(ExternalCompilerOptions) %(AdditionalOptions) - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator true Windows MachineX64 - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -260,13 +260,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator false Windows MachineX86 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -292,13 +292,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator false Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\arm /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -326,13 +326,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;ole32.lib;advapi32.lib;gdi32.lib;shell32.lib;comdlg32.lib;%(AdditionalDependencies) RequireAdministrator false Windows C:\Program Files (x86)\Windows Kits\10\Lib\10.0.16299.0\um\arm64 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;ole32.dll;advapi32.dll;gdi32.dll;shell32.dll;comdlg32.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -363,13 +363,13 @@ true - advapi32.lib;comctl32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;wintrust.lib;%(AdditionalDependencies) + advapi32.lib;comctl32.lib;crypt32.lib;gdi32.lib;ole32.lib;dwmapi.lib;setupapi.lib;shell32.lib;shlwapi.lib;version.lib;wintrust.lib;%(AdditionalDependencies) RequireAdministrator false Windows MachineX64 /BREPRO %(AdditionalOptions) - advapi32.dll;comctl32.dll;comdlg32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;wintrust.dll;%(DelayLoadDLLs) + advapi32.dll;comctl32.dll;crypt32.dll;gdi32.dll;ole32.dll;dwmapi.dll;setupapi.dll;shell32.dll;shlwapi.dll;version.dll;wintrust.dll;%(DelayLoadDLLs) _UNICODE;UNICODE;%(PreprocessorDefinitions) diff --git a/src/Makefile.am b/src/Makefile.am index 59dfabd3..ef0d0436 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,11 +1,11 @@ SUBDIRS = ../.mingw bled ext2fs ms-sys syslinux/libfat syslinux/libinstaller syslinux/win libcdio/iso9660 libcdio/udf libcdio/driver ../res/loc # As far as I can tell, the following libraries are *not* vulnerable to side-loading, so we link using their regular version: -NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomdlg32 -lcomctl32 -luuid +NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomctl32 -luuid # The following libraries are vulnerable (or have an unknown vulnerability status), so we link using our delay-loaded replacement: # Ideally there would also be virtdisk and wininet as delaylib's below, but the MinGW folks haven't quite sorted out delay-loading # for x86_32 so as soon as you try to call APIs from these, the application will crash! # See https://github.com/pbatard/rufus/issues/1877#issuecomment-1109683039 as well as https://github.com/pbatard/rufus/issues/2272 -VULNERABLE_LIBS = -ldwmapi-delaylib -lwintrust-delaylib +VULNERABLE_LIBS = -ldwmapi-delaylib -lversion-delaylib -lwintrust-delaylib noinst_PROGRAMS = rufus diff --git a/src/Makefile.in b/src/Makefile.in index 0fe9fa5a..70d31a54 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -275,12 +275,12 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ SUBDIRS = ../.mingw bled ext2fs ms-sys syslinux/libfat syslinux/libinstaller syslinux/win libcdio/iso9660 libcdio/udf libcdio/driver ../res/loc # As far as I can tell, the following libraries are *not* vulnerable to side-loading, so we link using their regular version: -NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomdlg32 -lcomctl32 -luuid +NONVULNERABLE_LIBS = -lsetupapi -lole32 -lgdi32 -lshlwapi -lcrypt32 -lcomctl32 -luuid # The following libraries are vulnerable (or have an unknown vulnerability status), so we link using our delay-loaded replacement: # Ideally there would also be virtdisk and wininet as delaylib's below, but the MinGW folks haven't quite sorted out delay-loading # for x86_32 so as soon as you try to call APIs from these, the application will crash! # See https://github.com/pbatard/rufus/issues/1877#issuecomment-1109683039 as well as https://github.com/pbatard/rufus/issues/2272 -VULNERABLE_LIBS = -ldwmapi-delaylib -lwintrust-delaylib +VULNERABLE_LIBS = -ldwmapi-delaylib -lversion-delaylib -lwintrust-delaylib AM_V_WINDRES_0 = @echo " RC $@";$(WINDRES) AM_V_WINDRES_1 = $(WINDRES) AM_V_WINDRES_ = $(AM_V_WINDRES_$(AM_DEFAULT_VERBOSITY)) diff --git a/src/hash.c b/src/hash.c index 79d84316..3f2c0df6 100644 --- a/src/hash.c +++ b/src/hash.c @@ -2119,8 +2119,10 @@ BOOL IsFileInDB(const char* path) int IsBootloaderRevoked(const char* path) { + version_t* ver; uint32_t i; uint8_t hash[SHA256_HASHSIZE]; + if (!PE256File(path, hash)) return -1; for (i = 0; i < ARRAYSIZE(pe256dbx); i += SHA256_HASHSIZE) @@ -2129,6 +2131,12 @@ int IsBootloaderRevoked(const char* path) for (i = 0; i < pe256ssp_size * SHA256_HASHSIZE; i += SHA256_HASHSIZE) if (memcmp(hash, &pe256ssp[i], SHA256_HASHSIZE) == 0) return 2; + ver = GetExecutableVersion(path); + // Blanket filter for Windows 10 1607 (excluded) to Windows 10 20H1 (excluded) + // TODO: Revoke all bootloaders prior to 2023.05 once Microsoft does +// uprintf("Found UEFI bootloader version: %d.%d.%d.%d", ver->Major, ver->Minor, ver->Micro, ver->Nano); + if (ver != NULL && ver->Major == 10 && ver->Minor == 0 && ver->Micro > 14393 && ver->Micro < 19041) + return 3; return 0; } diff --git a/src/rufus.c b/src/rufus.c index ef248a72..79ba301d 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -93,7 +93,7 @@ static unsigned int timer; static char uppercase_select[2][64], uppercase_start[64], uppercase_close[64], uppercase_cancel[64]; extern HANDLE update_check_thread, wim_thread; -extern BOOL enable_iso, enable_joliet, enable_rockridge, enable_extra_hashes; +extern BOOL enable_iso, enable_joliet, enable_rockridge, enable_extra_hashes, is_bootloader_revoked; extern BYTE* fido_script; extern HWND hFidoDlg; extern uint8_t* grub2_buf; @@ -1432,6 +1432,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) char tmp[MAX_PATH], tmp2[MAX_PATH], c; syslinux_ldlinux_len[0] = 0; syslinux_ldlinux_len[1] = 0; + is_bootloader_revoked = FALSE; safe_free(grub2_buf); if (ComboBox_GetCurSel(hDeviceList) == CB_ERR) @@ -1631,6 +1632,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) } r = IsBootloaderRevoked(tmp); if (r > 0) { + is_bootloader_revoked = TRUE; r = MessageBoxExU(hMainDialog, lmprintf(MSG_339, (r == 1) ? lmprintf(MSG_340) : lmprintf(MSG_341, "Error code: 0xc0000428")), lmprintf(MSG_338), MB_OKCANCEL | MB_ICONWARNING | MB_IS_RTL, selected_langid); diff --git a/src/rufus.h b/src/rufus.h index 4c485b92..012530a6 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -546,6 +546,13 @@ enum WindowsVersion { WINDOWS_MAX = 0xFFFF, }; +typedef struct { + DWORD Major; + DWORD Minor; + DWORD Micro; + DWORD Nano; +} version_t; + typedef struct { DWORD Version; DWORD Major; @@ -608,6 +615,7 @@ extern char sysnative_dir[MAX_PATH], app_data_dir[MAX_PATH], *image_path, *fido_ * Shared prototypes */ extern void GetWindowsVersion(windows_version_t* WindowsVersion); +extern version_t* GetExecutableVersion(const char* path); extern const char* WindowsErrorString(void); extern void DumpBufferHex(void *buf, size_t size); extern void PrintStatusInfo(BOOL info, BOOL debug, unsigned int duration, int msg_id, ...); diff --git a/src/rufus.rc b/src/rufus.rc index 1eb885ff..652ed4eb 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2071" +CAPTION "Rufus 4.2.2072" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2071,0 - PRODUCTVERSION 4,2,2071,0 + FILEVERSION 4,2,2072,0 + PRODUCTVERSION 4,2,2072,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2071" + VALUE "FileVersion", "4.2.2072" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2071" + VALUE "ProductVersion", "4.2.2072" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index 20fd172f..6d4d148a 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -453,6 +453,46 @@ void GetWindowsVersion(windows_version_t* windows_version) safe_sprintf(vptr, vlen, " (Build %lu)", windows_version->BuildNumber); } +/* + * Why oh why does Microsoft make it so convoluted to retrieve a measly executable's version number ? + */ +version_t* GetExecutableVersion(const char* path) +{ + static version_t version, *r = NULL; + uint8_t* buf = NULL; + UINT uLen; + DWORD dwSize, dwHandle; + VS_FIXEDFILEINFO* version_info; + + memset(&version, 0, sizeof(version)); + + dwSize = GetFileVersionInfoSizeU(path, &dwHandle); + if (dwSize == 0) + goto out; + + buf = malloc(dwSize); + if (buf == NULL) + goto out;; + if (!GetFileVersionInfoU(path, dwHandle, dwSize, buf)) + goto out; + + if (!VerQueryValueA(buf, "\\", (LPVOID*)&version_info, &uLen) || uLen == 0) + goto out; + + if (version_info->dwSignature != 0xfeef04bd) + goto out; + + version.Major = (version_info->dwFileVersionMS >> 16) & 0xffff; + version.Minor = (version_info->dwFileVersionMS >> 0) & 0xffff; + version.Micro = (version_info->dwFileVersionLS >> 16) & 0xffff; + version.Nano = (version_info->dwFileVersionLS >> 0) & 0xffff; + r = &version; + +out: + free(buf); + return r; +} + /* * String array manipulation */ diff --git a/src/wue.c b/src/wue.c index 62970dfa..5e76609b 100644 --- a/src/wue.c +++ b/src/wue.c @@ -44,6 +44,7 @@ const char* bypass_name[] = { "BypassTPMCheck", "BypassSecureBootCheck", "Bypass int unattend_xml_flags = 0, wintogo_index = -1, wininst_index = 0; int unattend_xml_mask = UNATTEND_DEFAULT_SELECTION_MASK; char *unattend_xml_path = NULL, unattend_username[MAX_USERNAME_LENGTH]; +BOOL is_bootloader_revoked = FALSE; extern uint32_t wim_nb_files, wim_proc_files, wim_extra_files; @@ -481,7 +482,9 @@ BOOL CopySKUSiPolicy(const char* drive_name) char src[MAX_PATH], dst[MAX_PATH]; struct __stat64 stat64 = { 0 }; - if ((target_type != TT_UEFI) || !IS_WINDOWS_1X(img_report) || pe256ssp_size == 0) + // Only copy SkuPolicy if we warned about the bootloader being revoked. + if ((target_type != TT_UEFI) || !IS_WINDOWS_1X(img_report) || + (pe256ssp_size == 0) || !is_bootloader_revoked) return r; static_sprintf(src, "%s\\SecureBootUpdates\\SKUSiPolicy.p7b", system_dir); From b81b440a20b79b348232901d3583ef9beadf6fe7 Mon Sep 17 00:00:00 2001 From: AJIOB Date: Mon, 24 Jul 2023 16:26:59 +0300 Subject: [PATCH 13/53] [uefi] fix search for bootloaders in FAT images that have an 'EFI' volume label * Per https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Directory_entry it is possible to have a FAT directory entry with a 'EFI' volume label alongside with a 'EFI' subdirectory. * If that happens, then the current Syslinux libfat_searchdir() code may treat the 'EFI' volume label as an empty subdirectory and say that there are no bootloaders, even if the 'EFI\Boot\Boot###.efi' binaries really do exist. * Fix this by filtering out entries with the 'volume label' attribute (0x08). * For good measure, also filter out entries with the 'device' attribute (0x40), as it is technically possible to create a 'EFI' device leading to the same issue. * Closes #2288. * Closes #2289. --- src/rufus.rc | 10 +++++----- src/syslinux/libfat/searchdir.c | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rufus.rc b/src/rufus.rc index 652ed4eb..525436a8 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2072" +CAPTION "Rufus 4.2.2073" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2072,0 - PRODUCTVERSION 4,2,2072,0 + FILEVERSION 4,2,2073,0 + PRODUCTVERSION 4,2,2073,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2072" + VALUE "FileVersion", "4.2.2073" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2072" + VALUE "ProductVersion", "4.2.2073" END END BLOCK "VarFileInfo" diff --git a/src/syslinux/libfat/searchdir.c b/src/syslinux/libfat/searchdir.c index 4964120b..f863a13f 100644 --- a/src/syslinux/libfat/searchdir.c +++ b/src/syslinux/libfat/searchdir.c @@ -40,7 +40,8 @@ int32_t libfat_searchdir(struct libfat_filesystem *fs, int32_t dirclust, for (nent = 0; nent < LIBFAT_SECTOR_SIZE; nent += sizeof(struct fat_dirent)) { - if (!memcmp(dep->name, name, 11)) { + /* Filter out volume labels (0x08) and devices (0x40) from the search */ + if (!(dep->attribute & 0x48) && !memcmp(dep->name, name, 11)) { if (direntry) { memcpy(direntry->entry, dep, sizeof(*dep)); direntry->sector = s; From 5084317dd7fc58fb62d302b8f9239f6834e60714 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 26 Jul 2023 12:54:49 +0100 Subject: [PATCH 14/53] Rufus 4.2 (Build 2074) * Also update UEFI:NTFS's NTFS driver to v1.7 --- ChangeLog.txt | 9 ++++--- README.md | 2 +- res/appstore/listing/listing.csv | 41 +++++++++++++++++-------------- res/uefi/readme.txt | 2 +- res/uefi/uefi-ntfs.img | Bin 1048576 -> 1048576 bytes src/rufus.rc | 10 ++++---- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index df7d54a6..d947f451 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,11 +1,12 @@ -o Version 4.2 (2023.07.??) +o Version 4.2 (2023.07.26) Add detection and warning for UEFI revoked bootloaders (including ones revoked through SkuSiPolicy.p7b) Add ZIP64 support, to extract .zip images that are larger than 4 GB Add saving and restoring current drive to/from compressed VHDX image Add saving and restoring current drive to/from compressed FFU (Full Flash Update) image [EXPERIMENTAL] - Fix a crash when trying to open Windows ISOs, with the x86 32-bit version - Increase ISO → ESP limit, for Debian 12 netinst images - Make sure the main partition size is aligned to the cluster size + Fix a crash when trying to open Windows ISOs, with the MinGW compiled x86 32-bit version + Fix an issue where ISOs that contain a boot image with an 'EFI' label are not be detected bootable + Increase the ISO → ESP limit for Debian 12 netinst images + Ensure that the main partition size is aligned to the cluster size o Version 4.1 (2023.05.31) Add timeouts on enumeration queries that may stall on some systems diff --git a/README.md b/README.md index 13de2629..bca5045a 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Features * Create bootable drives from bootable disk images, including compressed ones * Create Windows 11 installation drives for PCs that don't have TPM or Secure Boot * Create [Windows To Go](https://en.wikipedia.org/wiki/Windows_To_Go) drives +* Create VHD/DD, VHDX and FFU images of an existing drive * Create persistent Linux partitions -* Create VHD/DD images of a drive * Compute MD5, SHA-1, SHA-256 and SHA-512 checksums of the selected image * Improve Windows installation experience by automatically setting up OOBE parameters (local account, privacy options, etc.) * Perform bad blocks checks, including detection of "fake" flash drives diff --git a/res/appstore/listing/listing.csv b/res/appstore/listing/listing.csv index 090b45fc..737b0c90 100644 --- a/res/appstore/listing/listing.csv +++ b/res/appstore/listing/listing.csv @@ -13,7 +13,7 @@ • æºä»£ç : https://github.com/pbatard/rufus • 更新日志: https://github.com/pbatard/rufus/blob/master/ChangeLog.txt","Rufus 是個能格å¼åŒ–並製作å¯é–‹æ©Ÿ USB å¿«é–ƒç£ç¢Ÿæ©Ÿï¼ˆUSB 隨身碟ã€Memory Stick 等等)的工具。 • 官方網站: https://rufus.ie -• æºä»£ç¢¼: https://github.com/pbatard/rufus +• æºå§‹ç¢¼: https://github.com/pbatard/rufus • 更新日誌: https://github.com/pbatard/rufus/blob/master/ChangeLog.txt","Rufus je aplikacija koja olakÅ¡ava formatiranje i stvaranje USB pokretaÄkih jedinica. • Službena stranica: https://rufus.ie • Å ifra izvora: https://github.com/pbatard/rufus @@ -114,11 +114,14 @@ • Trang web chính thức: https://rufus.ie • Mã nguồn: https://github.com/pbatard/rufus • Nhật ký thay đổi: https://github.com/pbatard/rufus/blob/master/ChangeLog.txt" -"ReleaseNotes","3","Text","• Add timeouts on enumeration queries that may stall on some systems -• Restore MS-DOS drive creation through the download of binaries from Microsoft -• Update the log button icon -• Fix UEFI:NTFS incompatibility with Windows Dev Kit 2023 platform -• Fix more out of range pointer errors with Ubuntu/Fedora when booting in BIOS mode",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"ReleaseNotes","3","Text","• Add detection and warning for UEFI revoked bootloaders (including ones revoked through SkuSiPolicy.p7b) +• Add ZIP64 support, to extract .zip images that are larger than 4 GB +• Add saving and restoring current drive to/from compressed VHDX image +• Add saving and restoring current drive to/from compressed FFU (Full Flash Update) image [EXPERIMENTAL] +• Fix a crash when trying to open Windows ISOs, with the MinGW compiled x86 32-bit version +• Fix an issue where ISOs that contain a boot image with an 'EFI' label are not be detected bootable +• Increase the ISO → ESP limit for Debian 12 netinst images +• Ensure that the main partition size is aligned to the cluster size",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "Title","4","Text","Rufus",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "ShortTitle","5","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "SortTitle","6","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -259,19 +262,19 @@ Xem tại https://www.gnu.org/licenses/gpl-3.0.html để biết thêm chi tiế "OptionalPromo358x358","611","Relative path (or URL to file in Partner Center)","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "OptionalPromo1000x800","612","Relative path (or URL to file in Partner Center)","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "OptionalPromo414x180","613","Relative path (or URL to file in Partner Center)","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -"Feature1","700","Text","","Format USB, flash card and virtual drives to FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","تهيئة USB وبطاقة الÙلاش ومحركات الأقراص Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© إلى FAT / FAT32 / NTFS / UDF / exFAT / ReFS / ext2 / ext3","Форматиране на USB, карти памет и виртуални уÑтройÑтва Ñ FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","å°† U 盘ã€å­˜å‚¨å¡æˆ–虚拟驱动器格å¼åŒ–为 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼","å°‡ U 盤ã€å­˜å„²å¡æˆ–虛擬驅動器格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼","Formatirajte USB, flash karticu i virtualne pogone na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formátování USB, flash karet a virtuálních jednotek na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formater USB, flash kort og virtuelle drev til FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flashkaart en virtuele schijven formatteren naar FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Alusta USB-asemia, muistikortteja ja virtuaalisia asemia muotoon FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatez des périphériques USB, des cartes flash et des disques virtuels en FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatieren von USB, Flash-Karte und virtuellen Laufwerken in FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ΜοÏφοποίηση USB, κάÏτας flash και εικονικών μονάδων δίσκου σε FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","×תחול USB, כרטיסי זיכרון ×•×›×•× × ×™× ×•×™×¨×˜×•××œ×™×™× ×œÖ¾FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flash memóriakártyák és virtuális meghajtók formázása FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 fájlrendszerre","Format USB, kartu flash dan penyimpanan maya ke FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatta USB, flash card e unità virtuali in FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USBメモリやSDカードã€ä»®æƒ³ãƒ‰ãƒ©ã‚¤ãƒ–ã‚’FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3ã§ãƒ•ォーマットã—ã¾ã™ã€‚","USB, 플래시 카드 ë° ê°€ìƒ ë“œë¼ì´ë¸Œë¥¼ FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3로 í¬ë§·","FormatÄ“ USB, atmiņas kartes un virtuÄlos diskus formÄtos FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Suformatuokite USB, flash kortelÄ™ ir virtualius diskus į FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatkan USB, kad flash dan pemacu maya kepada FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formater USB, minnekort og virtuelle disker til FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ÙØ±Ù…ت USBØŒ Ùلش کارت Ùˆ درایوهای مجازی به FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Sformatuj noÅ›nik używajÄ…c: FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatar dispositivos USB, cartões flash e discos virtuais com FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatar dispositivos USB, cartão de memória e drives virtuais em FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatează USB, card flash si drive-uri virtuale la FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Форматировать USB, флешки и виртуальные диÑки в FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatiraj USB, flash kartice, i virtualne diskove u FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Naformátujte USB, kartu a virtuálne disky do FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","FormatIRANJE USB, bliskavice in virtualnih pogonov na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatee USB, tarjetas flash y unidades virtuales a FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatera USB-enheter, flash-kort och virtuella enheter till FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ฟอร์à¹à¸¡à¸• USB, à¹à¸Ÿà¸¥à¸Šà¸à¸²à¸£à¹Œà¸” หรือไดร์ฟจำลองให้อยู่ในรูปà¹à¸šà¸šà¸‚อง FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flash kart ve sanal sürücüleri FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 olarak biçimlendirin","Ð¤Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ USB-накопичувачів, флешок, карток пам'Ñті у FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Äịnh dạng USB, thẻ nhá»› hoặc ổ nhá»› ảo vá»›i FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3" -"Feature2","701","Text","","Create FreeDOS bootable USB drives","إنشاء محركات أقراص USB قابلة للتشغيل من FreeDOS","Създаване на FreeDOS Ñтартиращ USB уÑтройÑтва","创建 FreeDOS å¯å¯åŠ¨é©±åŠ¨å™¨","創建 FreeDOS å¯å•Ÿå‹•驅動器","Stvaranje FreeDOS USB pogona za pokretanje","VytvoÅ™ení bootovacích disků USB se systémem FreeDOS","Lav FreeDOS opstartsbare USB drev","FreeDOS opstartbare USB-schijven aanmaken","Luo boottaavia FreeDOS USB-asemia","Créez des disques amorçable FreeDOS","FreeDOS-bootfähige USB-Laufwerke erstellen","ΔημιουÏγήστε μονάδες USB με δυνατότητα εκκίνησης FreeDOS","יצירת כונני USB ×”× ×™×ª× ×™× ×œ×תחול של FreeDOS","Bootolható FreeDOS USB meghajtó készítése","Buat perangkat USB FreeDOS yang dapat di boot","Crea unità USB avviabili FreeDOS","FreeDOSã®èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","FreeDOS 부팅 가능한 USB 드ë¼ì´ë¸Œ 만들기","Izveido FreeDOS ielÄdes USB ierÄ«ces","Sukurkite FreeDOS įkrovos USB diskus","Buat pemacu USB boleh boot FreeDOS","Lag FreeDos oppstartbar USB stick","درایوهای USB قابل بوت FreeDOS را ایجاد کنید","Stwórz bootowalny noÅ›nik USB FreeDOS","Criar discos USB inicializáveis FreeDOS","Criar unidades USB inicializáveis FreeDOS","Crează drive USB bootabil FreeDOS","Создать загрузочные USB-диÑки FreeDOS","Kreiraj FreeDOS butabilni USB disk","Vytvorte bootovacie usb zariadenia FreeDOS","Ustvarite FreeDOS zagonske USB pogone","Crear unidades USB de arranque FreeDOS","Skapa FreeDOS startbara USB-enheter","สร้าง USB ไดร์ฟที่บูตได้สำหรับระบบ FreeDOS","FreeDOS önyüklenebilir USB sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв FreeDOS","Tạo USB có thể khởi động vá»›i FreeDOS" -"Feature3","702","Text","","Create bootable drives from bootable ISOs (Windows, Linux, etc.)","إنشاء محركات أقراص قابلة للتمهيد من Ù…Ù„ÙØ§Øª ISO القابلة للتمهيد (Windows Ùˆ Linux وما إلى ذلك)","Създаване на Ñтартиращи уÑтройÑтва от ISO образи (Windows, Linux и др.)","从å¯å¯åЍ ISO 文件 (Windows å’Œ Linux ç­‰) 创建å¯å¯åŠ¨é©±åŠ¨å™¨","從å¯å•Ÿå‹• ISO 文件 (Windows å’Œ Linux ç­‰) 創建å¯å•Ÿå‹•驅動器","Stvaranje pogona za pokretanje iz ISO-ova za pokretanje (Windows, Linux itd.)","Vytváření bootovacích jednotek ze zavádÄ›cích ISO (Windows, Linux atd.)","Lav opstartsbarer drev fra opstartsbarer ISOer (Window, Linux, osv.)","Opstartbare schijven aanmaken via opstartbare ISO's (Windows, Linux, enz)","Luo käynnistysasemia boottaavista ISO-kuvista (Windows, Linux jne.)","Créez des disques amorçables à partir d'images ISOs (Windows, Linux, etc.)","Erstellen bootfähiger Laufwerke aus bootfähigen ISOs (Windows, Linux, etc.)","ΔημιουÏγήστε εκκινήσιμες μονάδες από ISO με δυνατότητα εκκίνησης (Windows, Linux, κ.λπ.)","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מקובצי ISO ×”× ×™×ª× ×™× ×œ×תחול (Windowsâ€, Linux וכו')","Bootolható meghajtók készítése bootolható ISO képfájlokból (Windows, Linux, stb.)","Buat perangkat yang dapat di boot dari ISO (Windows, Linux, dll.)","Crea unità avviabili da ISO avviabili (Windows, Linux, ecc.)","Windowsã‚„Linuxãªã©ã®ISOファイルã‹ã‚‰èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","부팅 가능한 ISO (Windows, Linux 등)ì—서 부팅 가능한 드ë¼ì´ë¸Œ 만들기","Izveido ielÄdes ierÄ«ces no ISO failiem (Windows, Linux, u.c.)","Sukurkite įkrovos diskus iÅ¡ įkrovos ISO (Windows, Linux ir kt.)","Buat pemacu boleh boot daripada ISO boleh boot (Windows, Linux, dll.)","Lag oppstartabar enhet/disk fra ISOer (Windows, Linux, etc.)","ایجاد درایوهای قابل بوت از ISOهای قابل بوت (ویندوز، لینوکس Ùˆ غیره)","Twórz dyski rozruchowe z obrazów ISO (Windows, Linux itp.)","Criar discos inicializáveis a partir de ISOs inicializáveis (Windows, Linux, etc.)","Criar unidades inicializáveis a partir de ISOs inicializáveis (Windows, Linux, etc.)","Crează drive-uri bootabile de la ISO-uri bootabile (Windows, Linux, etc.)","Создать загрузочные диÑки из загрузочных ISO-образов (Windows, Linux и Ñ‚.д.)","Kreirajte disk jedinice za pokretanje sistema od ISO-a koji se mogu pokretanja sistema (Windows, Linux itd.)","Vytvorte bootovacie jednotky z ISO súborov (Windows, Linux atÄ.)","Ustvarite zagonske pogone iz zagonskih ISO-jev (Windows, Linux itd.)","Cree unidades de arranque desde ISO de arranque (Windows, Linux, etc.)","Skapa startbara enheter frÃ¥n startbara ISO-filer (Windows, Linux, etc.)","สร้าง USB ไดร์ฟที่บูตได้จาà¸à¹„ฟล์อิมเมจที่บูตได้ (เช่น ไฟล์ติดตั้ง Windows หรือ Linux เป็นต้น)","Önyüklenebilir ISO'lardan önyüklenebilir sürücüler oluÅŸturun (Windows, Linux, vb.)","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв з завантажувальних образів (Windows, Linux, тощо)","Tạo ổ đĩa có thể khởi động từ các file ISO có thể khởi động (Windows, Linux, v.v...)" -"Feature4","703","Text","","Create bootable drives from bootable disk images, including compressed ones","إنشاء محركات أقراص قابلة للتمهيد من صور الأقراص القابلة للتمهيد ØŒ بما ÙÙŠ ذلك الأقراص المضغوطة","Създаване на Ñтартиращи уÑтройÑтва от образи, включително компреÑирани такива","从å¯å¯åŠ¨ç¡¬ç›˜é•œåƒ (包括压缩镜åƒ) 创建å¯å¯åŠ¨é©±åŠ¨å™¨","從å¯å•Ÿå‹•硬盤é¡åƒ (包括壓縮é¡åƒ) 創建å¯å•Ÿå‹•驅動器","Stvaranje pogona za pokretanje iz slika diska za pokretanje, ukljuÄujući komprimirane","Vytváření zavádÄ›cích jednotek ze zavádÄ›cích obrazů disků, vÄetnÄ› komprimovaných","Lav opstartsbarer drev fra opstartsbarer disk billeder, inklusiv komprimerede billeder","Opstartbare schijven aanmaken van opstartbare schijf-images, inclusief gecomprimeerde images","Luo käynnistysasemia boottaavista levykuvista, pakatut kuvat mukaanlukien","Créez des disques amorçables à partir d'images disque, y compris à partir d'images compressées","Erstellen bootfähiger Laufwerke aus bootfähigen Festplatten-Images, einschließlich komprimierter Images","ΔημιουÏγήστε μονάδες εκκίνησης από εικόνες δίσκου με δυνατότητα εκκίνησης, συμπεÏιλαμβανομένων συμπιεσμένων","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מקובצי תמונת דיסק ×”× ×™×ª× ×™× ×œ×תחול, כולל ×§×‘×¦×™× ×“×—×•×¡×™×","Bootolható meghajtók készítése bootolható lemez képfájlokból, beleértve a tömörítetteket is","Buat perangkat yang dapat di boot dari Disk Image, termasuk yang Disk Image yang terkompresi","Crea unità avviabili da immagini disco avviabili, incluse quelle compresse","圧縮済ã¿ã®ã‚‚ã®ã‚’å«ã‚€ãƒ‡ã‚£ã‚¹ã‚¯ã‚¤ãƒ¡ãƒ¼ã‚¸ã‹ã‚‰èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","ì••ì¶•ëœ ì´ë¯¸ì§€ë¥¼ í¬í•¨í•˜ì—¬ 부팅 가능한 ë””ìŠ¤í¬ ì´ë¯¸ì§€ì—서 부팅 가능한 드ë¼ì´ë¸Œ 만들기","Izveido ielÄdes ierÄ«ces no ielÄdes disku virtuÄlajiem attÄ“liem, tai skaitÄ arÄ« no saspiestajiem","Sukurkite įkrovos diskus iÅ¡ įkrovos disko vaizdų, įskaitant suspaustus","Buat pemacu boleh boot daripada imej cakera boleh boot, termasuk yang dimampatkan","Lag oppstartbare disker fra images, inkludert komprimerte sÃ¥dan","درایوهای قابل بوت را از تصاویر دیسک قابل بوت، از جمله موارد ÙØ´Ø±Ø¯Ù‡ØŒ ایجاد کنید","Twórz dyski rozruchowe z obrazów dysków, włączajÄ…c skompresowane","Criar discos inicializáveis a partir de imagens de disco inicializáveis, inclusive de imagens compactadas","Criar unidades inicializáveis a partir de imagens de disco inicializáveis, inclusive de imagens compactadas","Crează drive-uri de la imagini de disc bootabile, incluzând cele compresate","Создать загрузочные диÑки из образов загрузочных диÑков, в том чиÑле Ñжатых","Kreiranje disk jedinica za pokretanje sistema sa slika diska koji se može pokretanja, ukljuÄujući kompresovane","Vytvorte bootovacie jednotky z diskových obrazov, vrátane tých komprimovaných","Ustvarite zagonske pogone iz slik diska, ki jih je mogoÄe zagnati, vkljuÄno s stisnjenimi","Cree unidades de arranque a partir de imágenes de disco de arranque, incluidas las comprimidas","Skapa startbara enheter frÃ¥n startbara diskavbildningar, inklusive komprimerade","สร้างไดร์ฟที่บูตได้จาà¸à¸”ิสà¸à¹Œà¸­à¸´à¸¡à¹€à¸¡à¸ˆà¸—ี่บูตได้ รวมไปถึงอันที่ถูà¸à¸šà¸µà¸šà¸­à¸±à¸”","Sıkıştırılmış olanlar da dahil olmak üzere önyüklenebilir disk yansısından önyüklenebilir sürücüler oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв з завантажувальних образів, у тому чиÑлі ÑтиÑнутих","Tạo ổ đĩa có thể khởi động từ các tệp đĩa có thể khởi động, bao gồm cả tệp nén" -"Feature5","704","Text","","Create BIOS or UEFI bootable drives, including UEFI bootable NTFS","إنشاء BIOS أو محركات أقراص UEFI قابلة للتمهيد ØŒ بما ÙÙŠ ذلك NTFS القابل للتشغيل من UEFI","Създаване на BIOS или UEFI Ñтартиращи уÑтройÑтва, включително UEFI Ñтартиращ NTFS","创建 BIOS 或 UEFI å¯å¯åŠ¨é©±åŠ¨å™¨ï¼ŒåŒ…æ‹¬ UEFI å¯å¯åŠ¨çš„ NTFS 驱动器","創建 BIOS 或 UEFI å¯å•Ÿå‹•驅動器,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 驅動器","Stvaranje BIOS ili UEFI pogona za pokretanje, ukljuÄujući UEFI NTFS za pokretanje","VytvoÅ™te zavádÄ›cí jednotky BIOS nebo UEFI, vÄetnÄ› zavádÄ›cích souborů NTFS UEFI","Lav BIOS eller UEFI opstartsbarer drev, inklusiv UEFI opstartsbarer NTFS","BIOS of UEFI opstartbare schijven aanmaken, inclusief UEFI opstartbare NTFS","Luo BIOS- tai UEFI-boottaavia asemia, mukaanlukien UEFI-boottaavat NTFS-asemat","Créez des disques amorçables BIOS ou UEFI, y compris des disques UEFI amorçables utilisant NTFS","Erstellen von BIOS- oder UEFI-bootfähigen Laufwerken, einschließlich UEFI-bootfähigem NTFS","ΔημιουÏγήστε μονάδες δίσκου με δυνατότητα εκκίνησης BIOS ή UEFI, συμπεÏιλαμβανομένων NTFS με δυνατότητα εκκίνησης UEFI","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול ×ž×ž×—×©×‘×™× ×”×ª×•×ž×›×™× ×‘Ö¾BIOS ×ו UEFI, לרבות ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מ־UEFI, ×”×ž×©×ª×ž×©×™× ×‘×ž×¢×¨×›×ª ×”×§×‘×¦×™× NTFS","BIOS-ból vagy UEFI-bÅ‘l bootolható meghajtók készítése, beleértve az UEFI-bÅ‘l bootolható NTFS meghajtókat is","Buat perangkat BIOS atau UEFI yang dapat di boot, termasuk perangkat NTFS yang dapat di boot oleh UEFI","Crea unità avviabili BIOS o UEFI, incluso NTFS avviabile UEFI","UEFI:NTFSã‚’å«ã‚€BIOSåŠã³UEFIã§èµ·å‹•å¯èƒ½ãªãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","UEFI 부팅 가능한 NTFS를 í¬í•¨í•˜ì—¬ BIOS ë˜ëŠ” UEFI 부팅 가능 드ë¼ì´ë¸Œ 만들기","Izveido BIOS vai UEFI ielÄdes ierÄ«ces, ieskaitot UEFI ielÄdi no NTFS","Sukurkite BIOS arba UEFI įkrovos diskus, įskaitant UEFI įkrovos NTFS","Buat pemacu boleh boot BIOS atau UEFI, termasuk NTFS boleh boot UEFI","Lag BIOS eller UEFI oppstartbare disker, inkludert UEFI oppstartbar NTFS","درایوهای قابل بوت بایوس یا UEFI از جمله NTFS قابل بوت UEFI ایجاد کنید","Stwórz dysk rozruchowy BIOS lub UEFI, włączajÄ…c bootowalny dysk UEFI NTFS","Criar discos inicializáveis ​​BIOS ou UEFI, inclusive discos UEFI inicializáveis ​​usando NTFS","Criar discos inicializáveis ​​BIOS ou UEFI, inclusive discos UEFI inicializáveis ​​usando NTFS","Crează discuri bootabile BIOS sau UEFI, incluzând NTFS-uri bootabile de UEFI","Создать загрузочные диÑки BIOS или UEFI, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¾Ñ‡Ð½Ñ‹Ð¹ UEFI NTFS","Kreiranje BIOS ili UEFI disk jedinica za pokretanje sistema, ukljuÄujući NTFS sa UEFI pokretanjem sistema","Vytvorte bootovacie jednotky systému BIOS alebo UEFI vrátane UEFI bootovateľnej jednotky NTFS","Ustvarite zagonske pogone BIOS ali UEFI, vkljuÄno z UEFI bootable NTFS","Cree unidades de arranque BIOS o UEFI, incluido NTFS de arranque UEFI","Skapa BIOS- eller UEFI-startbara enheter, inklusive UEFI-startbar NTFS","สร้างไดร์ฟที่บูตได้จาภBIOS หรือ UEFI รวมไปถึง NTFS ที่บูตได้จาภUEFI","UEFI önyüklenebilir NTFS dahil BIOS ya da UEFI önyüklenebilir sürücüler oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв BIOS чи UEFI, включаючи завантажувальний UEFI NTFS","Tạo ổ đĩa có thể khởi động vá»›i hệ thống BIOS hoặc UEFI, bao gồm cả NTFS có thể khởi động vá»›i UEFI" -"Feature6","705","Text","","Create 'Windows To Go' drives","إنشاء محركات أقراص ""Windows To Go\","Създаване на 'Windows To Go' уÑтройÑтва","创建 'Windows To Go' 驱动器","創建 'Windows To Go' é©…å‹•","Stvaranje pogona ""Windows To Go\","VytvoÅ™ení jednotek ""Windows To Go","Lav 'Windows To Go' drev","'Windows To Go'-schijven aanmaken","Luo 'Windows To Go' -asemia","Créez des disques 'Windows To Go'","Erstellen von ""Windows To Go""-Laufwerken","ΔημιουÏγήστε μονάδες δίσκου ""Windows To Go\","יצירת כונני Windows To Go","'Windows To Go' meghajtók készítése","Buat perangkat Windows To Go","Crea unità 'Windows To Go'","Windows To Goドライブを作æˆã—ã¾ã™ã€‚","'Windows To Go' 드ë¼ì´ë¸Œ 만들기","Izveido 'Windows To Go' ierÄ«ces","Sukurkite ""Windows To Go"" diskus","Buat pemacu 'Windows To Go'","Lag 'Windows To Go' disker","درایوهای ""Windows To Go"" را ایجاد کنید","Stwórz dysk 'Windows To Go'","Criar discos 'Windows To Go'","Criar discos 'Windows To Go'","Crează discuri 'Windows To Go'","Создание диÑков Windows To Go","Kreiranje disk jedinica ""Windows to Go\","Vytvorte jednotky „Windows To Go\","Ustvarjanje pogonov »Windows To Go'","Cree unidades 'Windows To Go'","Skapa 'Windows To Go'-enheter","สร้างไดร์ฟของ 'Windows To Go'","'Windows To Go' sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ñтроїв 'Windows To Go'","Tạo ổ đĩa 'Windows To Go'" -"Feature7","706","Text","","Create Windows 11 installation drives for PCs that don't have TPM or Secure Boot","قم بإنشاء محركات تثبيت ويندوز 11 لأجهزة الكمبيوتر التي لا تحتوي على TPM أو التمهيد الآمن","Създаване на Windows 11 инÑталационно уÑтройÑтво за компютри, които нÑмат TPM или Secure Boot","为没有 TPM 或安全å¯åŠ¨åŠŸèƒ½çš„ç”µè„‘åˆ›å»º Windows 11 安装驱动器","為沒有 TPM 或安全啟動功能的電腦創建 Windows 11 安è£é©…å‹•","Stvaranje instalacijskih pogona sustava Windows 11 za PC-jeve koji nemaju TPM ili Sigurno pokretanje","VytvoÅ™ení instalaÄních jednotek systému Windows 11 pro poÄítaÄe bez Äipu TPM nebo Secure Boot","Lav Windows 11 installations drev for PCer der ikker har TPM eller Secure Boot","Windows 11 installatieschijven aanmaken voor pc's die geen TPM of Secure Boot hebben","Luo Windows 11 -asennusasemia tietokoneille, jotka eivät tue TPM- tai Secure Boot -ominaisuuksia","Créez des disques d'installation Windows 11 pour des PCs qui ne disposent pas de TPM ou Secure Boot","Erstellen von Windows 11-Installationslaufwerken für PCs ohne TPM oder Secure Boot","ΔημιουÏγήστε μονάδες εγκατάστασης των Windows 11 για υπολογιστές που δεν διαθέτουν TPM ή Ασφαλή Εκκίνηση","יצירת כונני התקנה של Windows 11 עבור ×ž×—×©×‘×™× ×©×ין ×œ×”× TPM ×ו Secure Boot","Windows 11 telepítési meghajtók készítése olyan PC-k számára, amelyek nem rendelkeznek TPM vagy Secure Boot funkciókkal","Buat perangkat pemasang Windows 11 untuk PC yang tidak mempunyai TPM atau Secure Boot","Crea unità di installazione di Windows 11 per PC che non dispongono di TPM o avvio protetto","TPMåŠã³ã‚»ã‚­ãƒ¥ã‚¢ãƒ–ートéžå¯¾å¿œã®PCå‘ã‘ã®Windows 11インストールドライブを作æˆã—ã¾ã™","TPM ë˜ëŠ” 보안 ë¶€íŒ…ì´ ì—†ëŠ” PCìš© Windows 11 설치 드ë¼ì´ë¸Œ 만들기","Izveido Windows 11 instalÄcijas ierÄ«ces datoriem, kam nav TPM vai Secure Boot","Sukurkite Windows 11 diegimo diskus kompiuteriams, kuriuose nÄ—ra TPM arba saugaus įkrovimo","Buat pemacu pemasangan Windows 11 untuk PC yang tidak mempunyai TPM atau But Selamat","Lag oppstartsmedia for Windows 11 som ikke krever TPM eller Secure Boot","درایوهای نصب ویندوز 11 را برای رایانه هایی Ú©Ù‡ TPM یا Secure Boot ندارند ایجاد کنید","Twórz dyski instalacyjne systemu Windows 11 dla komputerów, które nie posiadajÄ… moduÅ‚u TPM ani Secure Boot","Criar discos de instalação do Windows 11 para PCs que não possuem TPM ou Secure Boot","Criar discos de instalação do Windows 11 para PCs que não possuem TPM ou Secure Boot","Crează drive de instalare Windows 11 pe computere care nu au TPM sau Bootare Securizată","Создать уÑтановочные диÑки Windows 11 Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð¾Ð² без TPM или безопаÑной загрузки","Kreiranje windows 11 instalacionih disk jedinica za raÄunare koji nemaju TPM ili bezbedno pokretanje sistema","Vytvorte inÅ¡talaÄné jednotky Windows 11 pre poÄítaÄe, ktoré nemajú modul TPM, ani Secure Boot","Ustvarjanje namestitvenih pogonov za Windows 11 za raÄunalnike, ki nimate TPM ali Varnega zagona","Cree unidades de instalación de Windows 11 para PC que no tienen TPM o Arranque seguro","Skapa installationsenheter till Windows 11 för datorer som inte har TPM eller säker start","สร้างไดร์ฟติดตั้ง Windows 11 สำหรับคอมพิวเตอร์ที่ไม่มี TPM หรือ SecureBoot","TPM ya da Güvenli Önyüklemeye sahip olmayan bilgisayarlar için Windows 11 kurulum sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ–Ð½ÑталÑційних диÑків Windows 11 Ð´Ð»Ñ ÐŸÐš, Ñкі не мають TPM чи Secure Boot","Tạo ổ đĩa cài đặt Windows 11 cho các máy tính không có TPM hoặc Secure Boot" -"Feature8","707","Text","","Create persistent Linux partitions","إنشاء Persistent Linux partitions","Създаване на уÑтойчиви Linux дÑлове","创建æŒä¹… Linux 分区","創建æŒä¹… Linux 分å€","Stvaranje trajnih Linux particija","VytvoÅ™ení trvalých oddílů systému Linux","Lav vedvarende Linux adskillelser","Persistent Linux partities aanmaken","Luo pysyviä Linux-osioita","Créez des partitions persistentes pour Linux","Persistente Linux-Partitionen erstellen","ΔημιουÏγήστε μόνιμα διαμεÏίσματα Linux","יצירת מחיצות Linux קבועות","Tartós Linux partíciók készítése","Buat partisi Linux yang tetap/persistent","Crea partizioni persistenti Linux","記録用Linuxパーティションを作æˆã—ã¾ã™ã€‚","ì˜êµ¬ 리눅스 파티션 만들기","Izveido pastÄvÄ«gas Linux partÄ«cijas","Sukurkite nuolatinius Linux skaidinius","Buat partition Linux berterusan","Lag persistente Linux partisjoner","ایجاد پارتیشن های لینوکس دائمی","Stwórz particje persistent Linuxa","Criar partições persistentes para Linux","Crie partições persistentes para Linux","Crează partiÈ›ie de Linux persistentă","Создать поÑтоÑнные разделы Linux","Kreiranje upornih Linux particija","Vytvorte trvalé oblasti systému Linux","Ustvarjanje trajnih Linux particij","Crear particiones persistentes de Linux","Skapa beständiga Linux-partitioner","สร้างพาร์ทิชั่นของ Linux à¹à¸šà¸šà¸–าวร","Kalıcı Linux bölümleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»Ñƒ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Linux","Tạo phân vùng Linux liên tục" -"Feature9","708","Text","","Create VHD/DD images of the selected drive","إنشاء صور VHD / DD لمحرك الأقراص المحدد","Създаване на VHD/DD образи на избраното уÑтройÑтво","为选择的驱动器创建 VHD/DD 镜åƒ","為é¸ä¸­çš„驅動創建 VHD/DD é¡åƒ","Stvaranje VHD/DD slika odabranog pogona","VytvoÅ™ení obrazů VHD/DD z vybrané jednotky","Lav VHD/DD billeder af det valgte drev","VHD/DD-images van de geselecteerde schijf aanmaken","Luo VHD/DD-kuvia valitusta asemasta","Créez des images VHD/DD du périphérique sélectionné","VHD/DD-Images des ausgewählten Laufwerks erstellen","ΔημιουÏγήστε εικόνες VHD/DD της επιλεγμένης μονάδας δίσκου","יצירת קובצי תמונה מסוג VHD/DD של הכונן שנבחר","VHD/DD képfájl készítése a kiválasztott meghajtóról","Buat image VHD/DD dari penyimpanan yang dipilih","Crea immagini VHD/DD dell'unità selezionata","é¸æŠžã•れãŸãƒ‰ãƒ©ã‚¤ãƒ–ã®VHD/DDイメージを作æˆã—ã¾ã™ã€‚","ì„ íƒí•œ 드ë¼ì´ë¸Œì˜ VHD/DD ì´ë¯¸ì§€ 만들기","Izveido izvÄ“lÄ“tÄ diska VHD/DD virtuÄlos attÄ“lus","Sukurkite pasirinkto disko VHD / DD vaizdus","Buat imej VHD/DD bagi pemacu yang dipilih","Lag VHD/DD speilinger av valgt disk","تصاویر VHD/DD از درایو انتخاب شده ایجاد کنید","Stwórz obraz VHD/DD z wybranego dysku","Criar imagens VHD/DD do dispositivo selecionado","Criar imagens VHD/DD do dispositivo selecionado","Crează imagini VHD/DD de pe drive-ul selectat","Создать образы VHD/DD выбранного диÑка","Kreiranje VHD/DD slika izabrane disk jedinice","Vytvorte obrazy VHD/DD z vybratej jednotky","Ustvarjanje VHD/DD slik izbranega pogona","Cree imágenes VHD/DD de la unidad seleccionada","Skapa VHD/DD-avbilder av den valda enheten","สร้างอิมเมจไฟล์à¹à¸šà¸š VHD/DD ของไดร์ฟที่เลือà¸","Seçilen sürücünün VHD/DD yansılarını oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ñ€Ð°Ð·Ñ–Ð² VHD/DD з вибраних диÑків","Tạo tệp VHD/DD từ ổ đĩa đã chá»n" -"Feature10","709","Text","","Compute MD5, SHA-1, SHA-256 and SHA-512 checksums of the selected image","حساب المجموع الاختباري MD5 Ùˆ SHA-1 Ùˆ SHA-256 Ùˆ SHA-512 للصورة المحددة","ИзчиÑлÑване на MD5, SHA-1, SHA-256 и SHA-512 чекÑуми на избраниÑÑ‚ образ","计算被选择镜åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 校验ç ","計算被é¸ä¸­é¡åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 校驗碼","IzraÄunajte kontrolne zbrojeve odabrane slike MD5, SHA-1, SHA-256 i SHA-512","VýpoÄet kontrolních souÄtů MD5, SHA-1, SHA-256 a SHA-512 vybraného obrazu","Beregn MD5, SHA-1, SHA-256 og SHA-512 checksums af det valgte billede","MD5, SHA-1, SHA-256 en SHA-512 controlesommen berekenen van de geselecteerde image","Laske MD5, SHA-1, SHA-256 ja SHA-512 tarkistussummia valitusta levykuvasta","Calculez les sommes de contrôle MD5, SHA-1, SHA-256 et SHA-512 de l'image sélectionnée","Berechnung von MD5-, SHA-1-, SHA-256- und SHA-512-Prüfsummen für das ausgewählte Bild","Υπολογίστε τα αθÏοίσματα ελέγχου MD5, SHA-1, SHA-256 και SHA-512 της επιλεγμένης εικόνας","חישוב סיכומי ביקורת מסוג MD5,†SHA-1,†SHA-256 ו־SHA-512 של קובץ התמונה שנבחרה","A kiválasztott képfájl MD5, SHA-1, SHA-256 és SHA-512 ellenÅ‘rzÅ‘ összegének kiszámítása","Hitung checksum MD5, SHA-1, SHA-256 dan SHA-512 dari image yang dipilih","Calcola i checksum MD5, SHA-1, SHA-256 e SHA-512 dell'immagine selezionata","é¸æŠžã•れãŸã‚¤ãƒ¡ãƒ¼ã‚¸ã®MD5ã€SHA-1ã€SHA-256åŠã³SHA-512ãƒã‚§ãƒƒã‚¯ã‚µãƒ ã‚’計算ã—ã¾ã™ã€‚","ì„ íƒí•œ ì´ë¯¸ì§€ì˜ MD5, SHA-1, SHA-256 ë° SHA-512 ì²´í¬ì„¬ 계산","Izskaitļo izvÄ“lÄ“tÄ virtuÄlÄ attÄ“la MD5, SHA-1, SHA-256 un SHA-512 kontrolsummas","ApskaiÄiuokite pasirinkto vaizdo MD5, SHA-1, SHA-256 ir SHA-512 kontrolines sumas","Kira MD5, SHA-1, SHA-256 dan SHA-512 checksum imej yang dipilih","Kalkuler MD5, SHA-1, SHA-256 og SHA-512 sjekksummer fra valgt speiling","MD5ØŒ SHA-1ØŒ SHA-256 Ùˆ SHA-512 را برای تصویر انتخابی محاسبه کنید","Oblicz sumy kontrolne MD5, SHA-1, SHA-256 i SHA-512 dla wybranego obrazu","Calcular somas de verificação MD5, SHA-1, SHA-256 e SHA-512 da imagem selecionada","Calcular checksums MD5, SHA-1, SHA-256 e SHA-512 da imagem selecionada","Compută MD5, SHA-1, SHA-256 È™i SHA-512 suma de control ale imaginilor selectate","ВычиÑлить контрольные Ñуммы MD5, SHA-1, SHA-256 и SHA-512 выбранного образа","IzraÄunajte MD5, SHA-1, SHA-256 i SHA-512 kontrolne preglede izabrane slike","VypoÄítajte kontrolné súÄty vybratého obrazu (MD5, SHA-1, SHA-256 a SHA-512)","RaÄun MD5, SHA-1, SHA-256 in SHA-512 kontrolni vsoti izbrane slike","Calcule las sumas de comprobación MD5, SHA-1, SHA-256 y SHA-512 de la imagen seleccionada","Beräkna kontrollsummor MD5, SHA-1, SHA-256 och SHA-512 för den valda avbilden","คำนวนรหัส MD5, SHA-1, SHA-256, SHA-512 ของไฟล์อิมเมจที่เลือà¸","Seçilen yansının MD5, SHA-1, SHA-256 ve SHA-512 saÄŸlama toplamlarını hesaplayın","ОбчиÑÐ»ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŒÐ½Ð¸Ñ… Ñум MD5, SHA-1, SHA-256 та SHA-512 Ð´Ð»Ñ Ð²Ð¸Ð±Ñ€Ð°Ð½Ð¸Ñ… образів","Tính tổng kiểm MD5, SHA-1, SHA-256 và SHA-512 cá»§a tệp đã chá»n" -"Feature11","710","Text","","Perform bad blocks checks, including detection of ""fake"" flash drives","إجراء ÙØ­ÙˆØµØ§Øª كتل ØªØ§Ù„ÙØ© ØŒ بما ÙÙŠ ذلك الكش٠عن محركات أقراص Ùلاش ""Ø²Ø§Ø¦ÙØ©\","ПроверÑване за лоши блокове, включително заÑичане на ""фалшиви"" уÑтройÑтва","执行åå—æ£€æŸ¥ï¼ŒåŒ…括对â€å‡â€œU盘的检测","執行壞塊檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„æª¢æ¸¬","IzvrÅ¡ite provjere loÅ¡ih blokova, ukljuÄujući otkrivanje ""lažnih"" flash pogona","Provést kontrolu vadných bloků, vÄetnÄ› detekce ""faleÅ¡ných"" bloků. flash disky","Udøv dÃ¥rlige blokke tjeks, inklusiv opdagelse af ""falske"" flashdrev","Controles uitvoeren op slechte blokken, inclusief detectie van ""valse"" flashdrives","Suorita viallisten lohkojen tarkistuksia, sisältäen ""valheellisten"" muistitikkujen tunnistamisen","Executez un test de mauvais secteurs avec detection des ""fake drives\","Durchführung von Prüfungen auf fehlerhafte Blöcke, einschließlich der Erkennung von ""gefälschten"" Flash-Laufwerken","Εκτελέστε ελέγχους εσφαλμένων block, συμπεÏιλαμβανομένου του ÎµÎ½Ï„Î¿Ï€Î¹ÏƒÎ¼Î¿Ï ""ψευδών"" μονάδων flash","ביצוע בדיקות ×חר ×‘×œ×•×§×™× (×זורי×) פגומי×, כולל זיהוי של כונני הבזק ""מזוייפי×\","Hibás blokkok ellenÅ‘rzése, beleértve a ""hamis"" flash meghajtók detektálását","Lakukan cek Blok yang buruk, termasuk deteksi USB Flash Disk ""PALSU\","Esegui controlli dei blocchi danneggiati, incluso il rilevamento di unità flash ""false\","ä¸è‰¯ãƒ–ロックãƒã‚§ãƒƒã‚¯åŠã³å®¹é‡è©æ¬ºãƒ‰ãƒ©ã‚¤ãƒ–ã®æ¤œçŸ¥ã‚’行ã„ã¾ã™ã€‚","""위조"" 플래시 드ë¼ì´ë¸Œ ê°ì§€ë¥¼ í¬í•¨í•˜ì—¬ 불량 ë¸”ë¡ ê²€ì‚¬ 수행","Izpilda bojÄto bloku pÄrbaudi ieskaitot ""falsificÄ“to"" nesÄ“ju noteikÅ¡anu","Atlikite blogų blokų patikrinimus, įskaitant ""netikrų"" flash diskų aptikimÄ…","Melakukan pemeriksaan blok buruk, termasuk pengesanan pemacu kilat ""palsu\","Utfør sjekk for dÃ¥rlige sektorer, inkludert sjekk for forfalskede flash disker","بررسی بلوک های بد، از جمله تشخیص درایوهای Ùلش ""جعلی"" را انجام دهید","Sprawdź dysk pod wzglÄ™dem spójnoÅ›ci danych lub wykryj ""nieorginalny"" pendrive","Executar verificações de blocos defeituosos, incluindo detecção de unidades flash ""falsificadas\","Executar verificações de blocos inválidos, incluindo a detecção de unidades flash ""falsas\","Verifică blocuri rele, incluzând detectarea de drive-uri flash ""false\","Проверить наличие плохих блоков, обнаружить ""поддельные"" флешки","IzvrÅ¡ite provere loÅ¡ih blokova, ukljuÄujući otkrivanje ""lažnih"" fleÅ¡ diskova","Vykonajte kontroly zlých blokov vrátane detekcie „faloÅ¡ných"" prenosných diskov","Izvajanje preverjanj slabih blokov, vkljuÄno z odkrivanjem »lažnih« bliskavic","Realice comprobaciones de bloques defectuosos, incluida la detección de unidades flash ""falsas\","Utför kontroll av trasiga block, inklusive upptäckt av ""falska"" USB-minnen","ดำเนินà¸à¸²à¸£à¸•รวจสอบบล็อà¸à¸‚้อมูลที่พัง รวมไปถึงà¸à¸²à¸£à¸—ดสอบว่าเป็นà¹à¸Ÿà¸¥à¸Šà¹„ดร์ฟ ""ปลอม"" หรือไม่","""Sahte"" flash sürücülerin tespiti de dahil olmak üzere hatalı blok kontrolleri gerçekleÅŸtirin","Перевірка диÑків (включаючи фальшиві диÑки)","Thá»±c hiện kiểm tra Ä‘iểm lá»—i, bao gồm phát hiện ổ đĩa ""giả\" -"Feature12","711","Text","","Download official Microsoft Windows retail ISOs","قم بتنزيل Ù…Ù„ÙØ§Øª ISO الرسمية الخاصة بـ Microsoft Windows","ИзтеглÑне на официални Microsoft Windows ISO образи","下载微软官方 Windows 镜åƒ","下載微軟官方 Windows é¡åƒ","Preuzimanje službenih ISO-ova za maloprodaju sustava Microsoft Windows","Stažení oficiálních souborů ISO systému Microsoft Windows","Hent officielle Microsoft Windows detail ISOer","Officiële Microsoft Windows retail ISO's downloaden","Lataa virallisia Microsoft Windowsin jälleenmyyntiversion ISO-levykuvia","Téléchargez des images ISOs commerciales officielles de Microsoft Windows","Offizielle Microsoft Windows-ISOs herunterladen","Κατεβάστε τα επίσημα retail ISO των Microsoft Windows","הורדת קובצי ×”Ö¾ISO הקמעונ××™×™× ×”×¨×©×ž×™×™× ×©×œ Microsoft Windows","Hivatalos Microsoft Windows kiskereskedelmi ISO képfájlok letöltése","Unduh ISO Microsoft Windows resmi","Scarica le ISO ufficiali di Microsoft Windows","マイクロソフト公å¼ã®Windows ISOをダウンロードã—ã¾ã™ã€‚","ê³µì‹ Microsoft Windows ë¦¬í…Œì¼ ISO 다운로드","LejupielÄdÄ“ oficiÄlos Microsoft ISO failus","Atsisiųskite oficialius Microsoft Windows mažmeninÄ—s prekybos ISO","Muat turun rasmi ISO runcit Microsoft Windows","Last ned offisielle Windows ISOer","ISO های رسمی Ù…Ø§Ú©Ø±ÙˆØ³Ø§ÙØª را Ø¯Ø±ÛŒØ§ÙØª کنید","Pobierz oficjalny obraz ISO systemu Microsoft Windows","Baixar ISOs oficiais do Microsoft Windows","Transferir ISOs oficiais do Microsoft Windows Retail","Descarcă un Microsoft Windows ISO oficial de vânzare","Загрузить официальные ISO-образы Windows","Preuzmite zvaniÄne Microsoft Windows maloprodajne ISO-ove","Stiahnite si oficiálne ISO pre Microsoft Windows","Prenos uradnih Microsoft Windows maloprodaja ISOs","Descargue los ISO oficiales de Microsoft Windows","Ladda ner officiella Microsoft Windows ISO-filer","ดาวน์โหลดอิมเมจไฟล์ของ Windows จาà¸à¹€à¸§à¹‡à¸šà¹„ซต์ทางà¸à¸²à¸£","Resmi Microsoft Windows Retail ISO'larını indirin","Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ„Ñ–Ñ†Ñ–Ð¹Ð½Ð¸Ñ… образів Microsoft Windows","Tải xuống các tệp Microsoft Windows ISO bán lẻ chính thức" -"Feature13","712","Text","","Download UEFI Shell ISOs","قم بتنزيل UEFI Shell ISOs","ИзтеглÑне на UEFI Shell образи","下载 UEFI Shell 镜åƒ","下載 UEFI Shell é¡åƒ","Preuzmite ISO-ove UEFI ljuske","Stažení souborů UEFI Shell ISO","Hent UEFI Shell ISOer","UEFI Shell ISO's downloaden","Lataa UEFI Shell ISO-levykuvia","Téléchargez des images ISOs du Shell UEFI","UEFI-Shell-ISOs herunterladen","Κατεβάστε τα ISO Shell UEFI","הורדת קובצי ISO של מעטפת UEFI","UEFI Shell ISO képfájlok letöltése","Unduh Shell ISO UEFI","Scarica le ISO della shell UEFI","UEFIシェルã®ISOをダウンロードã—ã¾ã™ã€‚","UEFI Shell ISO 다운로드","LejupielÄdÄ“ UEFI OS ISO failus","Atsisiųskite UEFI Shell ISO","Muat turun ISO Shell UEFI","Last ned UEFI kommandolinje ISOer","ISO های پوسته UEFI را دانلود کنید","ÅšciÄ…gnij obrazy ISO UEFI Shell","Baixar ISOs do Shell UEFI","Transferir ISOs de UEFI Shell","Descarcă UEFI Shell ISO-uri","Загрузить ISO-образы оболочки UEFI","Preuzmite UEFI Shell ISOs","Stiahnite UEFI Shell ISO","Prenos UEFI Shell ISOs","Descargar ISO de UEFI Shell","Ladda ner UEFI-shell ISO-filer","ดาวน์โหลดอิมเมจไฟล์ของ UEFI Shell","UEFI Shell ISO'larını indirin","Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ñ— оболонки UEFI ISO","Tải xuống các tệp UEFI Shell ISO" +"Feature1","700","Text","","Format USB, flash card and virtual drives to FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","تهيئة USB وبطاقة الÙلاش ومحركات الأقراص Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© إلى FAT / FAT32 / NTFS / UDF / exFAT / ReFS / ext2 / ext3","Форматиране на USB, карти памет и виртуални уÑтройÑтва Ñ FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","å°† U 盘ã€å­˜å‚¨å¡æˆ–虚拟驱动器格å¼åŒ–为 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼","將隨身碟ã€è¨˜æ†¶å¡æˆ–虛擬光碟機格å¼åŒ–為 FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 æ ¼å¼","Formatirajte USB, flash karticu i virtualne pogone na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formátování USB, flash karet a virtuálních jednotek na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formater USB, flash kort og virtuelle drev til FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flashkaart en virtuele schijven formatteren naar FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Alusta USB-asemia, muistikortteja ja virtuaalisia asemia muotoon FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatez des périphériques USB, des cartes flash et des disques virtuels en FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatieren von USB, Flash-Karte und virtuellen Laufwerken in FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ΜοÏφοποίηση USB, κάÏτας flash και εικονικών μονάδων δίσκου σε FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","×תחול USB, כרטיסי זיכרון ×•×›×•× × ×™× ×•×™×¨×˜×•××œ×™×™× ×œÖ¾FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flash memóriakártyák és virtuális meghajtók formázása FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 fájlrendszerre","Format USB, kartu flash dan penyimpanan maya ke FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatta USB, flash card e unità virtuali in FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USBメモリやSDカードã€ä»®æƒ³ãƒ‰ãƒ©ã‚¤ãƒ–ã‚’FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3ã§ãƒ•ォーマットã—ã¾ã™ã€‚","USB, 플래시 카드 ë° ê°€ìƒ ë“œë¼ì´ë¸Œë¥¼ FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3로 í¬ë§·","FormatÄ“ USB, atmiņas kartes un virtuÄlos diskus formÄtos FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Suformatuokite USB, flash kortelÄ™ ir virtualius diskus į FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatkan USB, kad flash dan pemacu maya kepada FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formater USB, minnekort og virtuelle disker til FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ÙØ±Ù…ت USBØŒ Ùلش کارت Ùˆ درایوهای مجازی به FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Sformatuj noÅ›nik używajÄ…c: FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatar dispositivos USB, cartões flash e discos virtuais com FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatar dispositivos USB, cartão de memória e drives virtuais em FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatează USB, card flash si drive-uri virtuale la FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Форматировать USB, флешки и виртуальные диÑки в FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatiraj USB, flash kartice, i virtualne diskove u FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Naformátujte USB, kartu a virtuálne disky do FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","FormatIRANJE USB, bliskavice in virtualnih pogonov na FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatee USB, tarjetas flash y unidades virtuales a FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Formatera USB-enheter, flash-kort och virtuella enheter till FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","ฟอร์à¹à¸¡à¸• USB, à¹à¸Ÿà¸¥à¸Šà¸à¸²à¸£à¹Œà¸” หรือไดร์ฟจำลองให้อยู่ในรูปà¹à¸šà¸šà¸‚อง FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","USB, flash kart ve sanal sürücüleri FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3 olarak biçimlendirin","Ð¤Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ USB-накопичувачів, флешок, карток пам'Ñті у FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3","Äịnh dạng USB, thẻ nhá»› hoặc ổ nhá»› ảo vá»›i FAT/FAT32/NTFS/UDF/exFAT/ReFS/ext2/ext3" +"Feature2","701","Text","","Create FreeDOS bootable USB drives","إنشاء محركات أقراص USB قابلة للتشغيل من FreeDOS","Създаване на FreeDOS Ñтартиращ USB уÑтройÑтва","创建 FreeDOS å¯å¯åŠ¨é©±åŠ¨å™¨","建立 FreeDOS å¯å•Ÿéš¨èº«ç¢Ÿ","Stvaranje FreeDOS USB pogona za pokretanje","VytvoÅ™ení bootovacích disků USB se systémem FreeDOS","Lav FreeDOS opstartsbare USB drev","FreeDOS opstartbare USB-schijven aanmaken","Luo boottaavia FreeDOS USB-asemia","Créez des disques amorçable FreeDOS","FreeDOS-bootfähige USB-Laufwerke erstellen","ΔημιουÏγήστε μονάδες USB με δυνατότητα εκκίνησης FreeDOS","יצירת כונני USB ×”× ×™×ª× ×™× ×œ×תחול של FreeDOS","Bootolható FreeDOS USB meghajtó készítése","Buat perangkat USB FreeDOS yang dapat di boot","Crea unità USB avviabili FreeDOS","FreeDOSã®èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","FreeDOS 부팅 가능한 USB 드ë¼ì´ë¸Œ 만들기","Izveido FreeDOS ielÄdes USB ierÄ«ces","Sukurkite FreeDOS įkrovos USB diskus","Buat pemacu USB boleh boot FreeDOS","Lag FreeDos oppstartbar USB stick","درایوهای USB قابل بوت FreeDOS را ایجاد کنید","Stwórz bootowalny noÅ›nik USB FreeDOS","Criar discos USB inicializáveis FreeDOS","Criar unidades USB inicializáveis FreeDOS","Crează drive USB bootabil FreeDOS","Создать загрузочные USB-диÑки FreeDOS","Kreiraj FreeDOS butabilni USB disk","Vytvorte bootovacie usb zariadenia FreeDOS","Ustvarite FreeDOS zagonske USB pogone","Crear unidades USB de arranque FreeDOS","Skapa FreeDOS startbara USB-enheter","สร้าง USB ไดร์ฟที่บูตได้สำหรับระบบ FreeDOS","FreeDOS önyüklenebilir USB sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв FreeDOS","Tạo USB có thể khởi động vá»›i FreeDOS" +"Feature3","702","Text","","Create bootable drives from bootable ISOs (Windows, Linux, etc.)","إنشاء محركات أقراص قابلة للتمهيد من Ù…Ù„ÙØ§Øª ISO القابلة للتمهيد (Windows Ùˆ Linux وما إلى ذلك)","Създаване на Ñтартиращи уÑтройÑтва от ISO образи (Windows, Linux и др.)","从å¯å¯åЍ ISO 文件 (Windows å’Œ Linux ç­‰) 创建å¯å¯åŠ¨é©±åŠ¨å™¨","從å¯å•Ÿå‹• ISO 檔案 (Windows å’Œ Linux ç­‰) 建立å¯å•Ÿå‹•隨身碟","Stvaranje pogona za pokretanje iz ISO-ova za pokretanje (Windows, Linux itd.)","Vytváření bootovacích jednotek ze zavádÄ›cích ISO (Windows, Linux atd.)","Lav opstartsbarer drev fra opstartsbarer ISOer (Window, Linux, osv.)","Opstartbare schijven aanmaken via opstartbare ISO's (Windows, Linux, enz)","Luo käynnistysasemia boottaavista ISO-kuvista (Windows, Linux jne.)","Créez des disques amorçables à partir d'images ISOs (Windows, Linux, etc.)","Erstellen bootfähiger Laufwerke aus bootfähigen ISOs (Windows, Linux, etc.)","ΔημιουÏγήστε εκκινήσιμες μονάδες από ISO με δυνατότητα εκκίνησης (Windows, Linux, κ.λπ.)","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מקובצי ISO ×”× ×™×ª× ×™× ×œ×תחול (Windowsâ€, Linux וכו')","Bootolható meghajtók készítése bootolható ISO képfájlokból (Windows, Linux, stb.)","Buat perangkat yang dapat di boot dari ISO (Windows, Linux, dll.)","Crea unità avviabili da ISO avviabili (Windows, Linux, ecc.)","Windowsã‚„Linuxãªã©ã®ISOファイルã‹ã‚‰èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","부팅 가능한 ISO (Windows, Linux 등)ì—서 부팅 가능한 드ë¼ì´ë¸Œ 만들기","Izveido ielÄdes ierÄ«ces no ISO failiem (Windows, Linux, u.c.)","Sukurkite įkrovos diskus iÅ¡ įkrovos ISO (Windows, Linux ir kt.)","Buat pemacu boleh boot daripada ISO boleh boot (Windows, Linux, dll.)","Lag oppstartabar enhet/disk fra ISOer (Windows, Linux, etc.)","ایجاد درایوهای قابل بوت از ISOهای قابل بوت (ویندوز، لینوکس Ùˆ غیره)","Twórz dyski rozruchowe z obrazów ISO (Windows, Linux itp.)","Criar discos inicializáveis a partir de ISOs inicializáveis (Windows, Linux, etc.)","Criar unidades inicializáveis a partir de ISOs inicializáveis (Windows, Linux, etc.)","Crează drive-uri bootabile de la ISO-uri bootabile (Windows, Linux, etc.)","Создать загрузочные диÑки из загрузочных ISO-образов (Windows, Linux и Ñ‚.д.)","Kreirajte disk jedinice za pokretanje sistema od ISO-a koji se mogu pokretanja sistema (Windows, Linux itd.)","Vytvorte bootovacie jednotky z ISO súborov (Windows, Linux atÄ.)","Ustvarite zagonske pogone iz zagonskih ISO-jev (Windows, Linux itd.)","Cree unidades de arranque desde ISO de arranque (Windows, Linux, etc.)","Skapa startbara enheter frÃ¥n startbara ISO-filer (Windows, Linux, etc.)","สร้าง USB ไดร์ฟที่บูตได้จาà¸à¹„ฟล์อิมเมจที่บูตได้ (เช่น ไฟล์ติดตั้ง Windows หรือ Linux เป็นต้น)","Önyüklenebilir ISO'lardan önyüklenebilir sürücüler oluÅŸturun (Windows, Linux, vb.)","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв з завантажувальних образів (Windows, Linux, тощо)","Tạo ổ đĩa có thể khởi động từ các file ISO có thể khởi động (Windows, Linux, v.v...)" +"Feature4","703","Text","","Create bootable drives from bootable disk images, including compressed ones","إنشاء محركات أقراص قابلة للتمهيد من صور الأقراص القابلة للتمهيد ØŒ بما ÙÙŠ ذلك الأقراص المضغوطة","Създаване на Ñтартиращи уÑтройÑтва от образи, включително компреÑирани такива","从å¯å¯åŠ¨ç¡¬ç›˜é•œåƒ (包括压缩镜åƒ) 创建å¯å¯åŠ¨é©±åŠ¨å™¨","從å¯å•Ÿå‹•ç¡¬ç¢Ÿæ˜ åƒæª” (åŒ…æ‹¬å£“ç¸®æ˜ åƒæª”) 建立å¯å•Ÿå‹•隨身碟","Stvaranje pogona za pokretanje iz slika diska za pokretanje, ukljuÄujući komprimirane","Vytváření zavádÄ›cích jednotek ze zavádÄ›cích obrazů disků, vÄetnÄ› komprimovaných","Lav opstartsbarer drev fra opstartsbarer disk billeder, inklusiv komprimerede billeder","Opstartbare schijven aanmaken van opstartbare schijf-images, inclusief gecomprimeerde images","Luo käynnistysasemia boottaavista levykuvista, pakatut kuvat mukaanlukien","Créez des disques amorçables à partir d'images disque, y compris à partir d'images compressées","Erstellen bootfähiger Laufwerke aus bootfähigen Festplatten-Images, einschließlich komprimierter Images","ΔημιουÏγήστε μονάδες εκκίνησης από εικόνες δίσκου με δυνατότητα εκκίνησης, συμπεÏιλαμβανομένων συμπιεσμένων","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מקובצי תמונת דיסק ×”× ×™×ª× ×™× ×œ×תחול, כולל ×§×‘×¦×™× ×“×—×•×¡×™×","Bootolható meghajtók készítése bootolható lemez képfájlokból, beleértve a tömörítetteket is","Buat perangkat yang dapat di boot dari Disk Image, termasuk yang Disk Image yang terkompresi","Crea unità avviabili da immagini disco avviabili, incluse quelle compresse","圧縮済ã¿ã®ã‚‚ã®ã‚’å«ã‚€ãƒ‡ã‚£ã‚¹ã‚¯ã‚¤ãƒ¡ãƒ¼ã‚¸ã‹ã‚‰èµ·å‹•å¯èƒ½ãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","ì••ì¶•ëœ ì´ë¯¸ì§€ë¥¼ í¬í•¨í•˜ì—¬ 부팅 가능한 ë””ìŠ¤í¬ ì´ë¯¸ì§€ì—서 부팅 가능한 드ë¼ì´ë¸Œ 만들기","Izveido ielÄdes ierÄ«ces no ielÄdes disku virtuÄlajiem attÄ“liem, tai skaitÄ arÄ« no saspiestajiem","Sukurkite įkrovos diskus iÅ¡ įkrovos disko vaizdų, įskaitant suspaustus","Buat pemacu boleh boot daripada imej cakera boleh boot, termasuk yang dimampatkan","Lag oppstartbare disker fra images, inkludert komprimerte sÃ¥dan","درایوهای قابل بوت را از تصاویر دیسک قابل بوت، از جمله موارد ÙØ´Ø±Ø¯Ù‡ØŒ ایجاد کنید","Twórz dyski rozruchowe z obrazów dysków, włączajÄ…c skompresowane","Criar discos inicializáveis a partir de imagens de disco inicializáveis, inclusive de imagens compactadas","Criar unidades inicializáveis a partir de imagens de disco inicializáveis, inclusive de imagens compactadas","Crează drive-uri de la imagini de disc bootabile, incluzând cele compresate","Создать загрузочные диÑки из образов загрузочных диÑков, в том чиÑле Ñжатых","Kreiranje disk jedinica za pokretanje sistema sa slika diska koji se može pokretanja, ukljuÄujući kompresovane","Vytvorte bootovacie jednotky z diskových obrazov, vrátane tých komprimovaných","Ustvarite zagonske pogone iz slik diska, ki jih je mogoÄe zagnati, vkljuÄno s stisnjenimi","Cree unidades de arranque a partir de imágenes de disco de arranque, incluidas las comprimidas","Skapa startbara enheter frÃ¥n startbara diskavbildningar, inklusive komprimerade","สร้างไดร์ฟที่บูตได้จาà¸à¸”ิสà¸à¹Œà¸­à¸´à¸¡à¹€à¸¡à¸ˆà¸—ี่บูตได้ รวมไปถึงอันที่ถูà¸à¸šà¸µà¸šà¸­à¸±à¸”","Sıkıştırılmış olanlar da dahil olmak üzere önyüklenebilir disk yansısından önyüklenebilir sürücüler oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв з завантажувальних образів, у тому чиÑлі ÑтиÑнутих","Tạo ổ đĩa có thể khởi động từ các tệp đĩa có thể khởi động, bao gồm cả tệp nén" +"Feature5","704","Text","","Create BIOS or UEFI bootable drives, including UEFI bootable NTFS","إنشاء BIOS أو محركات أقراص UEFI قابلة للتمهيد ØŒ بما ÙÙŠ ذلك NTFS القابل للتشغيل من UEFI","Създаване на BIOS или UEFI Ñтартиращи уÑтройÑтва, включително UEFI Ñтартиращ NTFS","创建 BIOS 或 UEFI å¯å¯åŠ¨é©±åŠ¨å™¨ï¼ŒåŒ…æ‹¬ UEFI å¯å¯åŠ¨çš„ NTFS 驱动器","建立 BIOS 或 UEFI å¯å•Ÿå‹•隨身碟,包括 UEFI å¯å•Ÿå‹•çš„ NTFS 隨身碟","Stvaranje BIOS ili UEFI pogona za pokretanje, ukljuÄujući UEFI NTFS za pokretanje","VytvoÅ™te zavádÄ›cí jednotky BIOS nebo UEFI, vÄetnÄ› zavádÄ›cích souborů NTFS UEFI","Lav BIOS eller UEFI opstartsbarer drev, inklusiv UEFI opstartsbarer NTFS","BIOS of UEFI opstartbare schijven aanmaken, inclusief UEFI opstartbare NTFS","Luo BIOS- tai UEFI-boottaavia asemia, mukaanlukien UEFI-boottaavat NTFS-asemat","Créez des disques amorçables BIOS ou UEFI, y compris des disques UEFI amorçables utilisant NTFS","Erstellen von BIOS- oder UEFI-bootfähigen Laufwerken, einschließlich UEFI-bootfähigem NTFS","ΔημιουÏγήστε μονάδες δίσκου με δυνατότητα εκκίνησης BIOS ή UEFI, συμπεÏιλαμβανομένων NTFS με δυνατότητα εκκίνησης UEFI","יצירת ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול ×ž×ž×—×©×‘×™× ×”×ª×•×ž×›×™× ×‘Ö¾BIOS ×ו UEFI, לרבות ×›×•× × ×™× ×”× ×™×ª× ×™× ×œ×תחול מ־UEFI, ×”×ž×©×ª×ž×©×™× ×‘×ž×¢×¨×›×ª ×”×§×‘×¦×™× NTFS","BIOS-ból vagy UEFI-bÅ‘l bootolható meghajtók készítése, beleértve az UEFI-bÅ‘l bootolható NTFS meghajtókat is","Buat perangkat BIOS atau UEFI yang dapat di boot, termasuk perangkat NTFS yang dapat di boot oleh UEFI","Crea unità avviabili BIOS o UEFI, incluso NTFS avviabile UEFI","UEFI:NTFSã‚’å«ã‚€BIOSåŠã³UEFIã§èµ·å‹•å¯èƒ½ãªãƒ‰ãƒ©ã‚¤ãƒ–を作æˆã—ã¾ã™ã€‚","UEFI 부팅 가능한 NTFS를 í¬í•¨í•˜ì—¬ BIOS ë˜ëŠ” UEFI 부팅 가능 드ë¼ì´ë¸Œ 만들기","Izveido BIOS vai UEFI ielÄdes ierÄ«ces, ieskaitot UEFI ielÄdi no NTFS","Sukurkite BIOS arba UEFI įkrovos diskus, įskaitant UEFI įkrovos NTFS","Buat pemacu boleh boot BIOS atau UEFI, termasuk NTFS boleh boot UEFI","Lag BIOS eller UEFI oppstartbare disker, inkludert UEFI oppstartbar NTFS","درایوهای قابل بوت بایوس یا UEFI از جمله NTFS قابل بوت UEFI ایجاد کنید","Stwórz dysk rozruchowy BIOS lub UEFI, włączajÄ…c bootowalny dysk UEFI NTFS","Criar discos inicializáveis ​​BIOS ou UEFI, inclusive discos UEFI inicializáveis ​​usando NTFS","Criar discos inicializáveis ​​BIOS ou UEFI, inclusive discos UEFI inicializáveis ​​usando NTFS","Crează discuri bootabile BIOS sau UEFI, incluzând NTFS-uri bootabile de UEFI","Создать загрузочные диÑки BIOS или UEFI, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¾Ñ‡Ð½Ñ‹Ð¹ UEFI NTFS","Kreiranje BIOS ili UEFI disk jedinica za pokretanje sistema, ukljuÄujući NTFS sa UEFI pokretanjem sistema","Vytvorte bootovacie jednotky systému BIOS alebo UEFI vrátane UEFI bootovateľnej jednotky NTFS","Ustvarite zagonske pogone BIOS ali UEFI, vkljuÄno z UEFI bootable NTFS","Cree unidades de arranque BIOS o UEFI, incluido NTFS de arranque UEFI","Skapa BIOS- eller UEFI-startbara enheter, inklusive UEFI-startbar NTFS","สร้างไดร์ฟที่บูตได้จาภBIOS หรือ UEFI รวมไปถึง NTFS ที่บูตได้จาภUEFI","UEFI önyüklenebilir NTFS dahil BIOS ya da UEFI önyüklenebilir sürücüler oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÑƒÐ²Ð°Ð»ÑŒÐ½Ð¸Ñ… приÑтроїв BIOS чи UEFI, включаючи завантажувальний UEFI NTFS","Tạo ổ đĩa có thể khởi động vá»›i hệ thống BIOS hoặc UEFI, bao gồm cả NTFS có thể khởi động vá»›i UEFI" +"Feature6","705","Text","","Create 'Windows To Go' drives","إنشاء محركات أقراص ""Windows To Go\","Създаване на 'Windows To Go' уÑтройÑтва","创建 'Windows To Go' 驱动器","建立 'Windows To Go' 隨身碟","Stvaranje pogona ""Windows To Go\","VytvoÅ™ení jednotek ""Windows To Go","Lav 'Windows To Go' drev","'Windows To Go'-schijven aanmaken","Luo 'Windows To Go' -asemia","Créez des disques 'Windows To Go'","Erstellen von ""Windows To Go""-Laufwerken","ΔημιουÏγήστε μονάδες δίσκου ""Windows To Go\","יצירת כונני Windows To Go","'Windows To Go' meghajtók készítése","Buat perangkat Windows To Go","Crea unità 'Windows To Go'","Windows To Goドライブを作æˆã—ã¾ã™ã€‚","'Windows To Go' 드ë¼ì´ë¸Œ 만들기","Izveido 'Windows To Go' ierÄ«ces","Sukurkite ""Windows To Go"" diskus","Buat pemacu 'Windows To Go'","Lag 'Windows To Go' disker","درایوهای ""Windows To Go"" را ایجاد کنید","Stwórz dysk 'Windows To Go'","Criar discos 'Windows To Go'","Criar discos 'Windows To Go'","Crează discuri 'Windows To Go'","Создание диÑков Windows To Go","Kreiranje disk jedinica ""Windows to Go\","Vytvorte jednotky „Windows To Go\","Ustvarjanje pogonov »Windows To Go'","Cree unidades 'Windows To Go'","Skapa 'Windows To Go'-enheter","สร้างไดร์ฟของ 'Windows To Go'","'Windows To Go' sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ñтроїв 'Windows To Go'","Tạo ổ đĩa 'Windows To Go'" +"Feature7","706","Text","","Create Windows 11 installation drives for PCs that don't have TPM or Secure Boot","قم بإنشاء محركات تثبيت ويندوز 11 لأجهزة الكمبيوتر التي لا تحتوي على TPM أو التمهيد الآمن","Създаване на Windows 11 инÑталационно уÑтройÑтво за компютри, които нÑмат TPM или Secure Boot","为没有 TPM 或安全å¯åŠ¨åŠŸèƒ½çš„ç”µè„‘åˆ›å»º Windows 11 安装驱动器","為沒有 TPM 或安全開機功能的電腦建立 Windows 11 安è£éš¨èº«ç¢Ÿ","Stvaranje instalacijskih pogona sustava Windows 11 za PC-jeve koji nemaju TPM ili Sigurno pokretanje","VytvoÅ™ení instalaÄních jednotek systému Windows 11 pro poÄítaÄe bez Äipu TPM nebo Secure Boot","Lav Windows 11 installations drev for PCer der ikker har TPM eller Secure Boot","Windows 11 installatieschijven aanmaken voor pc's die geen TPM of Secure Boot hebben","Luo Windows 11 -asennusasemia tietokoneille, jotka eivät tue TPM- tai Secure Boot -ominaisuuksia","Créez des disques d'installation Windows 11 pour des PCs qui ne disposent pas de TPM ou Secure Boot","Erstellen von Windows 11-Installationslaufwerken für PCs ohne TPM oder Secure Boot","ΔημιουÏγήστε μονάδες εγκατάστασης των Windows 11 για υπολογιστές που δεν διαθέτουν TPM ή Ασφαλή Εκκίνηση","יצירת כונני התקנה של Windows 11 עבור ×ž×—×©×‘×™× ×©×ין ×œ×”× TPM ×ו Secure Boot","Windows 11 telepítési meghajtók készítése olyan PC-k számára, amelyek nem rendelkeznek TPM vagy Secure Boot funkciókkal","Buat perangkat pemasang Windows 11 untuk PC yang tidak mempunyai TPM atau Secure Boot","Crea unità di installazione di Windows 11 per PC che non dispongono di TPM o avvio protetto","TPMåŠã³ã‚»ã‚­ãƒ¥ã‚¢ãƒ–ートéžå¯¾å¿œã®PCå‘ã‘ã®Windows 11インストールドライブを作æˆã—ã¾ã™","TPM ë˜ëŠ” 보안 ë¶€íŒ…ì´ ì—†ëŠ” PCìš© Windows 11 설치 드ë¼ì´ë¸Œ 만들기","Izveido Windows 11 instalÄcijas ierÄ«ces datoriem, kam nav TPM vai Secure Boot","Sukurkite Windows 11 diegimo diskus kompiuteriams, kuriuose nÄ—ra TPM arba saugaus įkrovimo","Buat pemacu pemasangan Windows 11 untuk PC yang tidak mempunyai TPM atau But Selamat","Lag oppstartsmedia for Windows 11 som ikke krever TPM eller Secure Boot","درایوهای نصب ویندوز 11 را برای رایانه هایی Ú©Ù‡ TPM یا Secure Boot ندارند ایجاد کنید","Twórz dyski instalacyjne systemu Windows 11 dla komputerów, które nie posiadajÄ… moduÅ‚u TPM ani Secure Boot","Criar discos de instalação do Windows 11 para PCs que não possuem TPM ou Secure Boot","Criar discos de instalação do Windows 11 para PCs que não possuem TPM ou Secure Boot","Crează drive de instalare Windows 11 pe computere care nu au TPM sau Bootare Securizată","Создать уÑтановочные диÑки Windows 11 Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð¾Ð² без TPM или безопаÑной загрузки","Kreiranje windows 11 instalacionih disk jedinica za raÄunare koji nemaju TPM ili bezbedno pokretanje sistema","Vytvorte inÅ¡talaÄné jednotky Windows 11 pre poÄítaÄe, ktoré nemajú modul TPM, ani Secure Boot","Ustvarjanje namestitvenih pogonov za Windows 11 za raÄunalnike, ki nimate TPM ali Varnega zagona","Cree unidades de instalación de Windows 11 para PC que no tienen TPM o Arranque seguro","Skapa installationsenheter till Windows 11 för datorer som inte har TPM eller säker start","สร้างไดร์ฟติดตั้ง Windows 11 สำหรับคอมพิวเตอร์ที่ไม่มี TPM หรือ SecureBoot","TPM ya da Güvenli Önyüklemeye sahip olmayan bilgisayarlar için Windows 11 kurulum sürücüleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ–Ð½ÑталÑційних диÑків Windows 11 Ð´Ð»Ñ ÐŸÐš, Ñкі не мають TPM чи Secure Boot","Tạo ổ đĩa cài đặt Windows 11 cho các máy tính không có TPM hoặc Secure Boot" +"Feature8","707","Text","","Create persistent Linux partitions","إنشاء Persistent Linux partitions","Създаване на уÑтойчиви Linux дÑлове","创建æŒä¹… Linux 分区","建立æŒçºŒæ€§ Linux ç£å€","Stvaranje trajnih Linux particija","VytvoÅ™ení trvalých oddílů systému Linux","Lav vedvarende Linux adskillelser","Persistent Linux partities aanmaken","Luo pysyviä Linux-osioita","Créez des partitions persistentes pour Linux","Persistente Linux-Partitionen erstellen","ΔημιουÏγήστε μόνιμα διαμεÏίσματα Linux","יצירת מחיצות Linux קבועות","Tartós Linux partíciók készítése","Buat partisi Linux yang tetap/persistent","Crea partizioni persistenti Linux","記録用Linuxパーティションを作æˆã—ã¾ã™ã€‚","ì˜êµ¬ 리눅스 파티션 만들기","Izveido pastÄvÄ«gas Linux partÄ«cijas","Sukurkite nuolatinius Linux skaidinius","Buat partition Linux berterusan","Lag persistente Linux partisjoner","ایجاد پارتیشن های لینوکس دائمی","Stwórz particje persistent Linuxa","Criar partições persistentes para Linux","Crie partições persistentes para Linux","Crează partiÈ›ie de Linux persistentă","Создать поÑтоÑнные разделы Linux","Kreiranje upornih Linux particija","Vytvorte trvalé oblasti systému Linux","Ustvarjanje trajnih Linux particij","Crear particiones persistentes de Linux","Skapa beständiga Linux-partitioner","สร้างพาร์ทิชั่นของ Linux à¹à¸šà¸šà¸–าวร","Kalıcı Linux bölümleri oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»Ñƒ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Linux","Tạo phân vùng Linux liên tục" +"Feature9","708","Text","","Create VHD/DD images of the selected drive","إنشاء صور VHD / DD لمحرك الأقراص المحدد","Създаване на VHD/DD образи на избраното уÑтройÑтво","为选择的驱动器创建 VHD/DD 镜åƒ","為é¸ä¸­çš„ç£ç¢Ÿæ©Ÿå»ºç«‹ VHD/DD æ˜ åƒæª”","Stvaranje VHD/DD slika odabranog pogona","VytvoÅ™ení obrazů VHD/DD z vybrané jednotky","Lav VHD/DD billeder af det valgte drev","VHD/DD-images van de geselecteerde schijf aanmaken","Luo VHD/DD-kuvia valitusta asemasta","Créez des images VHD/DD du périphérique sélectionné","VHD/DD-Images des ausgewählten Laufwerks erstellen","ΔημιουÏγήστε εικόνες VHD/DD της επιλεγμένης μονάδας δίσκου","יצירת קובצי תמונה מסוג VHD/DD של הכונן שנבחר","VHD/DD képfájl készítése a kiválasztott meghajtóról","Buat image VHD/DD dari penyimpanan yang dipilih","Crea immagini VHD/DD dell'unità selezionata","é¸æŠžã•れãŸãƒ‰ãƒ©ã‚¤ãƒ–ã®VHD/DDイメージを作æˆã—ã¾ã™ã€‚","ì„ íƒí•œ 드ë¼ì´ë¸Œì˜ VHD/DD ì´ë¯¸ì§€ 만들기","Izveido izvÄ“lÄ“tÄ diska VHD/DD virtuÄlos attÄ“lus","Sukurkite pasirinkto disko VHD / DD vaizdus","Buat imej VHD/DD bagi pemacu yang dipilih","Lag VHD/DD speilinger av valgt disk","تصاویر VHD/DD از درایو انتخاب شده ایجاد کنید","Stwórz obraz VHD/DD z wybranego dysku","Criar imagens VHD/DD do dispositivo selecionado","Criar imagens VHD/DD do dispositivo selecionado","Crează imagini VHD/DD de pe drive-ul selectat","Создать образы VHD/DD выбранного диÑка","Kreiranje VHD/DD slika izabrane disk jedinice","Vytvorte obrazy VHD/DD z vybratej jednotky","Ustvarjanje VHD/DD slik izbranega pogona","Cree imágenes VHD/DD de la unidad seleccionada","Skapa VHD/DD-avbilder av den valda enheten","สร้างอิมเมจไฟล์à¹à¸šà¸š VHD/DD ของไดร์ฟที่เลือà¸","Seçilen sürücünün VHD/DD yansılarını oluÅŸturun","Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ñ€Ð°Ð·Ñ–Ð² VHD/DD з вибраних диÑків","Tạo tệp VHD/DD từ ổ đĩa đã chá»n" +"Feature10","709","Text","","Compute MD5, SHA-1, SHA-256 and SHA-512 checksums of the selected image","حساب المجموع الاختباري MD5 Ùˆ SHA-1 Ùˆ SHA-256 Ùˆ SHA-512 للصورة المحددة","ИзчиÑлÑване на MD5, SHA-1, SHA-256 и SHA-512 чекÑуми на избраниÑÑ‚ образ","计算被选择镜åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 校验ç ","計算被é¸ä¸­æ˜ åƒçš„ MD5ã€SHA-1ã€SHA-256 å’Œ SHA-512 檢查碼","IzraÄunajte kontrolne zbrojeve odabrane slike MD5, SHA-1, SHA-256 i SHA-512","VýpoÄet kontrolních souÄtů MD5, SHA-1, SHA-256 a SHA-512 vybraného obrazu","Beregn MD5, SHA-1, SHA-256 og SHA-512 checksums af det valgte billede","MD5, SHA-1, SHA-256 en SHA-512 controlesommen berekenen van de geselecteerde image","Laske MD5, SHA-1, SHA-256 ja SHA-512 tarkistussummia valitusta levykuvasta","Calculez les sommes de contrôle MD5, SHA-1, SHA-256 et SHA-512 de l'image sélectionnée","Berechnung von MD5-, SHA-1-, SHA-256- und SHA-512-Prüfsummen für das ausgewählte Bild","Υπολογίστε τα αθÏοίσματα ελέγχου MD5, SHA-1, SHA-256 και SHA-512 της επιλεγμένης εικόνας","חישוב סיכומי ביקורת מסוג MD5,†SHA-1,†SHA-256 ו־SHA-512 של קובץ התמונה שנבחרה","A kiválasztott képfájl MD5, SHA-1, SHA-256 és SHA-512 ellenÅ‘rzÅ‘ összegének kiszámítása","Hitung checksum MD5, SHA-1, SHA-256 dan SHA-512 dari image yang dipilih","Calcola i checksum MD5, SHA-1, SHA-256 e SHA-512 dell'immagine selezionata","é¸æŠžã•れãŸã‚¤ãƒ¡ãƒ¼ã‚¸ã®MD5ã€SHA-1ã€SHA-256åŠã³SHA-512ãƒã‚§ãƒƒã‚¯ã‚µãƒ ã‚’計算ã—ã¾ã™ã€‚","ì„ íƒí•œ ì´ë¯¸ì§€ì˜ MD5, SHA-1, SHA-256 ë° SHA-512 ì²´í¬ì„¬ 계산","Izskaitļo izvÄ“lÄ“tÄ virtuÄlÄ attÄ“la MD5, SHA-1, SHA-256 un SHA-512 kontrolsummas","ApskaiÄiuokite pasirinkto vaizdo MD5, SHA-1, SHA-256 ir SHA-512 kontrolines sumas","Kira MD5, SHA-1, SHA-256 dan SHA-512 checksum imej yang dipilih","Kalkuler MD5, SHA-1, SHA-256 og SHA-512 sjekksummer fra valgt speiling","MD5ØŒ SHA-1ØŒ SHA-256 Ùˆ SHA-512 را برای تصویر انتخابی محاسبه کنید","Oblicz sumy kontrolne MD5, SHA-1, SHA-256 i SHA-512 dla wybranego obrazu","Calcular somas de verificação MD5, SHA-1, SHA-256 e SHA-512 da imagem selecionada","Calcular checksums MD5, SHA-1, SHA-256 e SHA-512 da imagem selecionada","Compută MD5, SHA-1, SHA-256 È™i SHA-512 suma de control ale imaginilor selectate","ВычиÑлить контрольные Ñуммы MD5, SHA-1, SHA-256 и SHA-512 выбранного образа","IzraÄunajte MD5, SHA-1, SHA-256 i SHA-512 kontrolne preglede izabrane slike","VypoÄítajte kontrolné súÄty vybratého obrazu (MD5, SHA-1, SHA-256 a SHA-512)","RaÄun MD5, SHA-1, SHA-256 in SHA-512 kontrolni vsoti izbrane slike","Calcule las sumas de comprobación MD5, SHA-1, SHA-256 y SHA-512 de la imagen seleccionada","Beräkna kontrollsummor MD5, SHA-1, SHA-256 och SHA-512 för den valda avbilden","คำนวนรหัส MD5, SHA-1, SHA-256, SHA-512 ของไฟล์อิมเมจที่เลือà¸","Seçilen yansının MD5, SHA-1, SHA-256 ve SHA-512 saÄŸlama toplamlarını hesaplayın","ОбчиÑÐ»ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŒÐ½Ð¸Ñ… Ñум MD5, SHA-1, SHA-256 та SHA-512 Ð´Ð»Ñ Ð²Ð¸Ð±Ñ€Ð°Ð½Ð¸Ñ… образів","Tính tổng kiểm MD5, SHA-1, SHA-256 và SHA-512 cá»§a tệp đã chá»n" +"Feature11","710","Text","","Perform bad blocks checks, including detection of ""fake"" flash drives","إجراء ÙØ­ÙˆØµØ§Øª كتل ØªØ§Ù„ÙØ© ØŒ بما ÙÙŠ ذلك الكش٠عن محركات أقراص Ùلاش ""Ø²Ø§Ø¦ÙØ©\","ПроверÑване за лоши блокове, включително заÑичане на ""фалшиви"" уÑтройÑтва","执行åå—æ£€æŸ¥ï¼ŒåŒ…括对â€å‡â€œU盘的检测","執行壞軌檢查,包括å°â€å‡â€œUSB å¿«é–ƒç£ç¢Ÿæ©Ÿçš„嵿¸¬","IzvrÅ¡ite provjere loÅ¡ih blokova, ukljuÄujući otkrivanje ""lažnih"" flash pogona","Provést kontrolu vadných bloků, vÄetnÄ› detekce ""faleÅ¡ných"" bloků. flash disky","Udøv dÃ¥rlige blokke tjeks, inklusiv opdagelse af ""falske"" flashdrev","Controles uitvoeren op slechte blokken, inclusief detectie van ""valse"" flashdrives","Suorita viallisten lohkojen tarkistuksia, sisältäen ""valheellisten"" muistitikkujen tunnistamisen","Executez un test de mauvais secteurs avec detection des ""fake drives\","Durchführung von Prüfungen auf fehlerhafte Blöcke, einschließlich der Erkennung von ""gefälschten"" Flash-Laufwerken","Εκτελέστε ελέγχους εσφαλμένων block, συμπεÏιλαμβανομένου του ÎµÎ½Ï„Î¿Ï€Î¹ÏƒÎ¼Î¿Ï ""ψευδών"" μονάδων flash","ביצוע בדיקות ×חר ×‘×œ×•×§×™× (×זורי×) פגומי×, כולל זיהוי של כונני הבזק ""מזוייפי×\","Hibás blokkok ellenÅ‘rzése, beleértve a ""hamis"" flash meghajtók detektálását","Lakukan cek Blok yang buruk, termasuk deteksi USB Flash Disk ""PALSU\","Esegui controlli dei blocchi danneggiati, incluso il rilevamento di unità flash ""false\","ä¸è‰¯ãƒ–ロックãƒã‚§ãƒƒã‚¯åŠã³å®¹é‡è©æ¬ºãƒ‰ãƒ©ã‚¤ãƒ–ã®æ¤œçŸ¥ã‚’行ã„ã¾ã™ã€‚","""위조"" 플래시 드ë¼ì´ë¸Œ ê°ì§€ë¥¼ í¬í•¨í•˜ì—¬ 불량 ë¸”ë¡ ê²€ì‚¬ 수행","Izpilda bojÄto bloku pÄrbaudi ieskaitot ""falsificÄ“to"" nesÄ“ju noteikÅ¡anu","Atlikite blogų blokų patikrinimus, įskaitant ""netikrų"" flash diskų aptikimÄ…","Melakukan pemeriksaan blok buruk, termasuk pengesanan pemacu kilat ""palsu\","Utfør sjekk for dÃ¥rlige sektorer, inkludert sjekk for forfalskede flash disker","بررسی بلوک های بد، از جمله تشخیص درایوهای Ùلش ""جعلی"" را انجام دهید","Sprawdź dysk pod wzglÄ™dem spójnoÅ›ci danych lub wykryj ""nieorginalny"" pendrive","Executar verificações de blocos defeituosos, incluindo detecção de unidades flash ""falsificadas\","Executar verificações de blocos inválidos, incluindo a detecção de unidades flash ""falsas\","Verifică blocuri rele, incluzând detectarea de drive-uri flash ""false\","Проверить наличие плохих блоков, обнаружить ""поддельные"" флешки","IzvrÅ¡ite provere loÅ¡ih blokova, ukljuÄujući otkrivanje ""lažnih"" fleÅ¡ diskova","Vykonajte kontroly zlých blokov vrátane detekcie „faloÅ¡ných"" prenosných diskov","Izvajanje preverjanj slabih blokov, vkljuÄno z odkrivanjem »lažnih« bliskavic","Realice comprobaciones de bloques defectuosos, incluida la detección de unidades flash ""falsas\","Utför kontroll av trasiga block, inklusive upptäckt av ""falska"" USB-minnen","ดำเนินà¸à¸²à¸£à¸•รวจสอบบล็อà¸à¸‚้อมูลที่พัง รวมไปถึงà¸à¸²à¸£à¸—ดสอบว่าเป็นà¹à¸Ÿà¸¥à¸Šà¹„ดร์ฟ ""ปลอม"" หรือไม่","""Sahte"" flash sürücülerin tespiti de dahil olmak üzere hatalı blok kontrolleri gerçekleÅŸtirin","Перевірка диÑків (включаючи фальшиві диÑки)","Thá»±c hiện kiểm tra Ä‘iểm lá»—i, bao gồm phát hiện ổ đĩa ""giả\" +"Feature12","711","Text","","Download official Microsoft Windows retail ISOs","قم بتنزيل Ù…Ù„ÙØ§Øª ISO الرسمية الخاصة بـ Microsoft Windows","ИзтеглÑне на официални Microsoft Windows ISO образи","下载微软官方 Windows 镜åƒ","下載微軟官方 Windows æ˜ åƒæª”","Preuzimanje službenih ISO-ova za maloprodaju sustava Microsoft Windows","Stažení oficiálních souborů ISO systému Microsoft Windows","Hent officielle Microsoft Windows detail ISOer","Officiële Microsoft Windows retail ISO's downloaden","Lataa virallisia Microsoft Windowsin jälleenmyyntiversion ISO-levykuvia","Téléchargez des images ISOs commerciales officielles de Microsoft Windows","Offizielle Microsoft Windows-ISOs herunterladen","Κατεβάστε τα επίσημα retail ISO των Microsoft Windows","הורדת קובצי ×”Ö¾ISO הקמעונ××™×™× ×”×¨×©×ž×™×™× ×©×œ Microsoft Windows","Hivatalos Microsoft Windows kiskereskedelmi ISO képfájlok letöltése","Unduh ISO Microsoft Windows resmi","Scarica le ISO ufficiali di Microsoft Windows","マイクロソフト公å¼ã®Windows ISOをダウンロードã—ã¾ã™ã€‚","ê³µì‹ Microsoft Windows ë¦¬í…Œì¼ ISO 다운로드","LejupielÄdÄ“ oficiÄlos Microsoft ISO failus","Atsisiųskite oficialius Microsoft Windows mažmeninÄ—s prekybos ISO","Muat turun rasmi ISO runcit Microsoft Windows","Last ned offisielle Windows ISOer","ISO های رسمی Ù…Ø§Ú©Ø±ÙˆØ³Ø§ÙØª را Ø¯Ø±ÛŒØ§ÙØª کنید","Pobierz oficjalny obraz ISO systemu Microsoft Windows","Baixar ISOs oficiais do Microsoft Windows","Transferir ISOs oficiais do Microsoft Windows Retail","Descarcă un Microsoft Windows ISO oficial de vânzare","Загрузить официальные ISO-образы Windows","Preuzmite zvaniÄne Microsoft Windows maloprodajne ISO-ove","Stiahnite si oficiálne ISO pre Microsoft Windows","Prenos uradnih Microsoft Windows maloprodaja ISOs","Descargue los ISO oficiales de Microsoft Windows","Ladda ner officiella Microsoft Windows ISO-filer","ดาวน์โหลดอิมเมจไฟล์ของ Windows จาà¸à¹€à¸§à¹‡à¸šà¹„ซต์ทางà¸à¸²à¸£","Resmi Microsoft Windows Retail ISO'larını indirin","Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¾Ñ„Ñ–Ñ†Ñ–Ð¹Ð½Ð¸Ñ… образів Microsoft Windows","Tải xuống các tệp Microsoft Windows ISO bán lẻ chính thức" +"Feature13","712","Text","","Download UEFI Shell ISOs","قم بتنزيل UEFI Shell ISOs","ИзтеглÑне на UEFI Shell образи","下载 UEFI Shell 镜åƒ","下載 UEFI Shell æ˜ åƒæª”","Preuzmite ISO-ove UEFI ljuske","Stažení souborů UEFI Shell ISO","Hent UEFI Shell ISOer","UEFI Shell ISO's downloaden","Lataa UEFI Shell ISO-levykuvia","Téléchargez des images ISOs du Shell UEFI","UEFI-Shell-ISOs herunterladen","Κατεβάστε τα ISO Shell UEFI","הורדת קובצי ISO של מעטפת UEFI","UEFI Shell ISO képfájlok letöltése","Unduh Shell ISO UEFI","Scarica le ISO della shell UEFI","UEFIシェルã®ISOをダウンロードã—ã¾ã™ã€‚","UEFI Shell ISO 다운로드","LejupielÄdÄ“ UEFI OS ISO failus","Atsisiųskite UEFI Shell ISO","Muat turun ISO Shell UEFI","Last ned UEFI kommandolinje ISOer","ISO های پوسته UEFI را دانلود کنید","ÅšciÄ…gnij obrazy ISO UEFI Shell","Baixar ISOs do Shell UEFI","Transferir ISOs de UEFI Shell","Descarcă UEFI Shell ISO-uri","Загрузить ISO-образы оболочки UEFI","Preuzmite UEFI Shell ISOs","Stiahnite UEFI Shell ISO","Prenos UEFI Shell ISOs","Descargar ISO de UEFI Shell","Ladda ner UEFI-shell ISO-filer","ดาวน์โหลดอิมเมจไฟล์ของ UEFI Shell","UEFI Shell ISO'larını indirin","Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ñ— оболонки UEFI ISO","Tải xuống các tệp UEFI Shell ISO" "Feature14","713","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "Feature15","714","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "Feature16","715","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/res/uefi/readme.txt b/res/uefi/readme.txt index 5c84f2a2..5aab7420 100644 --- a/res/uefi/readme.txt +++ b/res/uefi/readme.txt @@ -7,7 +7,7 @@ This image, which can be mounted as a FAT file system or opened in 7-zip, contains the following data: o Secure Boot signed NTFS UEFI drivers, derived from ntfs-3g [1]. - These drivers are the exact same as the read-only binaries from release 1.6, + These drivers are the exact same as the read-only binaries from release 1.7, except for the addition of Microsoft's Secure Boot signature. Note that, per Microsoft's current Secure Boot signing policies, the 32-bit ARM driver (ntfs_arm.efi) is not Secure Boot signed. diff --git a/res/uefi/uefi-ntfs.img b/res/uefi/uefi-ntfs.img index f8981512f4ff1064a4ccd88f138ce1d8afa4975b..21765cb93ac43a0801a476fce2d33b8f8d175deb 100644 GIT binary patch delta 121092 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

NwT5WZSfBv+Mv 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

Zc}^Or0^Y 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 Date: Tue, 15 Aug 2023 11:15:16 +0100 Subject: [PATCH 15/53] [wue] add an expert feature to restrict a Windows installation to S Mode * This is placed behind an expert wall (Ctrl-Alt-E) on account that: - If you happen to boot a Windows To Go drive in S Mode on a computer, it may set any existing Windows installation there to S Mode as well, *even if their disk is offline!* - It can be *exceedingly* tricky to get out of S Mode, as the SkuPolicyRequired registry trick alone may not be enough (i.e. You can have very much a Windows install in S Mode *without* SkuPolicyRequired being set anywhere). * Also set version to rufus-next and fix a ChangeLog typo. --- ChangeLog.txt | 2 +- configure | 20 ++++++++++---------- configure.ac | 2 +- res/loc/rufus.loc | 2 ++ src/rufus.c | 38 ++++++++++++++++++++++++++++---------- src/rufus.h | 3 ++- src/rufus.rc | 12 ++++++------ src/settings.h | 1 + src/wue.c | 9 +++++++++ 9 files changed, 60 insertions(+), 29 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index d947f451..0cb2abaf 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -4,7 +4,7 @@ o Version 4.2 (2023.07.26) Add saving and restoring current drive to/from compressed VHDX image Add saving and restoring current drive to/from compressed FFU (Full Flash Update) image [EXPERIMENTAL] Fix a crash when trying to open Windows ISOs, with the MinGW compiled x86 32-bit version - Fix an issue where ISOs that contain a boot image with an 'EFI' label are not be detected bootable + Fix an issue where ISOs that contain a boot image with an 'EFI' label are not detected as bootable Increase the ISO → ESP limit for Debian 12 netinst images Ensure that the main partition size is aligned to the cluster size diff --git a/configure b/configure index d2115037..69835a42 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for rufus 4.2. +# Generated by GNU Autoconf 2.71 for rufus 4.3. # # Report bugs to . # @@ -611,8 +611,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='rufus' PACKAGE_TARNAME='rufus' -PACKAGE_VERSION='4.2' -PACKAGE_STRING='rufus 4.2' +PACKAGE_VERSION='4.3' +PACKAGE_STRING='rufus 4.3' PACKAGE_BUGREPORT='https://github.com/pbatard/rufus/issues' PACKAGE_URL='https://rufus.ie' @@ -1269,7 +1269,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures rufus 4.2 to adapt to many kinds of systems. +\`configure' configures rufus 4.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1336,7 +1336,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of rufus 4.2:";; + short | recursive ) echo "Configuration of rufus 4.3:";; esac cat <<\_ACEOF @@ -1428,7 +1428,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -rufus configure 4.2 +rufus configure 4.3 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1504,7 +1504,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by rufus $as_me 4.2, which was +It was created by rufus $as_me 4.3, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2767,7 +2767,7 @@ fi # Define the identity of the package. PACKAGE='rufus' - VERSION='4.2' + VERSION='4.3' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -5309,7 +5309,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by rufus $as_me 4.2, which was +This file was extended by rufus $as_me 4.3, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -5365,7 +5365,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -rufus config.status 4.2 +rufus config.status 4.3 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 2a531578..d1f2b960 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([rufus], [4.2], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) +AC_INIT([rufus], [4.3], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) AM_INIT_AUTOMAKE([-Wno-portability foreign no-dist no-dependencies]) AC_CONFIG_SRCDIR([src/rufus.c]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 3d99cea8..6456538f 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -609,6 +609,8 @@ t MSG_344 "Full Flash Update Image" t MSG_345 "Some additional data must be downloaded from Microsoft to use this functionality:\n" "- Select 'Yes' to connect to the Internet and download it\n" "- Select 'No' to cancel the operation" +t MSG_346 "Restrict Windows to S-Mode (INCOMPATIBLE with online account bypass)" +t MSG_347 "Expert Mode" # The following messages are for the Windows Store listing only and are not used by the application t MSG_900 "Rufus is a utility that helps format and create bootable USB flash drives, such as USB keys/pendrives, memory sticks, etc." t MSG_901 "Official site: %s" diff --git a/src/rufus.c b/src/rufus.c index 79ba301d..64b8a9ee 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -129,6 +129,7 @@ BOOL usb_debug, use_fake_units, preserve_timestamps = FALSE, fast_zeroing = FALS BOOL zero_drive = FALSE, list_non_usb_removable_drives = FALSE, enable_file_indexing, large_drive = FALSE; BOOL write_as_image = FALSE, write_as_esp = FALSE, use_vds = FALSE, ignore_boot_marker = FALSE; BOOL appstore_version = FALSE, is_vds_available = TRUE, persistent_log = FALSE, has_ffu_support = FALSE; +BOOL expert_mode = FALSE; float fScale = 1.0f; int dialog_showing = 0, selection_default = BT_IMAGE, persistence_unit_selection = -1, imop_win_sel = 0; int default_fs, fs_type, boot_type, partition_type, target_type; @@ -1514,8 +1515,8 @@ static DWORD WINAPI BootCheckThread(LPVOID param) if ((WindowsVersion.Version >= WINDOWS_8) && IS_WINDOWS_1X(img_report)) { StrArray options; int arch = _log2(img_report.has_efi >> 1); - uint8_t map[8] = { 0 }, b = 1; - StrArrayCreate(&options, 2); + uint16_t map[16] = { 0 }, b = 1; + StrArrayCreate(&options, 8); StrArrayAdd(&options, lmprintf(MSG_332), TRUE); MAP_BIT(UNATTEND_OFFLINE_INTERNAL_DRIVES); if (img_report.win_version.build >= 22500) { @@ -1529,16 +1530,20 @@ static DWORD WINAPI BootCheckThread(LPVOID param) MAP_BIT(UNATTEND_DUPLICATE_LOCALE); StrArrayAdd(&options, lmprintf(MSG_331), TRUE); MAP_BIT(UNATTEND_NO_DATA_COLLECTION); + if (expert_mode) { + StrArrayAdd(&options, lmprintf(MSG_346), TRUE); + MAP_BIT(UNATTEND_FORCE_S_MODE); + } i = CustomSelectionDialog(BS_AUTOCHECKBOX, lmprintf(MSG_327), lmprintf(MSG_328), - options.String, options.Index, remap8(unattend_xml_mask, map, FALSE), username_index); + options.String, options.Index, remap16(unattend_xml_mask, map, FALSE), username_index); StrArrayDestroy(&options); if (i < 0) goto out; // Remap i to the correct bit positions before calling CreateUnattendXml() - i = remap8(i, map, TRUE); + i = remap16(i, map, TRUE); unattend_xml_path = CreateUnattendXml(arch, i | UNATTEND_WINDOWS_TO_GO); // Keep the bits we didn't process - unattend_xml_mask &= ~(remap8(0xff, map, TRUE)); + unattend_xml_mask &= ~(remap16(0x1ff, map, TRUE)); // And add back the bits we did process unattend_xml_mask |= i; WriteSetting32(SETTING_WUE_OPTIONS, (UNATTEND_DEFAULT_MASK << 16) | unattend_xml_mask); @@ -1576,8 +1581,8 @@ static DWORD WINAPI BootCheckThread(LPVOID param) if ((WindowsVersion.Version >= WINDOWS_8) && IS_WINDOWS_1X(img_report) && (!is_windows_to_go)) { StrArray options; int arch = _log2(img_report.has_efi >> 1); - uint8_t map[8] = { 0 }, b = 1; - StrArrayCreate(&options, 4); + uint16_t map[16] = { 0 }, b = 1; + StrArrayCreate(&options, 10); if (IS_WINDOWS_11(img_report)) { StrArrayAdd(&options, lmprintf(MSG_329), TRUE); MAP_BIT(UNATTEND_SECUREBOOT_TPM_MINRAM); @@ -1595,15 +1600,19 @@ static DWORD WINAPI BootCheckThread(LPVOID param) MAP_BIT(UNATTEND_NO_DATA_COLLECTION); StrArrayAdd(&options, lmprintf(MSG_335), TRUE); MAP_BIT(UNATTEND_DISABLE_BITLOCKER); + if (expert_mode) { + StrArrayAdd(&options, lmprintf(MSG_346), TRUE); + MAP_BIT(UNATTEND_FORCE_S_MODE); + } i = CustomSelectionDialog(BS_AUTOCHECKBOX, lmprintf(MSG_327), lmprintf(MSG_328), - options.String, options.Index, remap8(unattend_xml_mask, map, FALSE), username_index); + options.String, options.Index, remap16(unattend_xml_mask, map, FALSE), username_index); StrArrayDestroy(&options); if (i < 0) goto out; - i = remap8(i, map, TRUE); + i = remap16(i, map, TRUE); unattend_xml_path = CreateUnattendXml(arch, i); // Remember the user preferences for the current session. - unattend_xml_mask &= ~(remap8(0xff, map, TRUE)); + unattend_xml_mask &= ~(remap16(0x1ff, map, TRUE)); unattend_xml_mask |= i; WriteSetting32(SETTING_WUE_OPTIONS, (UNATTEND_DEFAULT_MASK << 16) | unattend_xml_mask); } @@ -3556,6 +3565,7 @@ skip_args_processing: enable_file_indexing = ReadSettingBool(SETTING_ENABLE_FILE_INDEXING); enable_VHDs = !ReadSettingBool(SETTING_DISABLE_VHDS); enable_extra_hashes = ReadSettingBool(SETTING_ENABLE_EXTRA_HASHES); + expert_mode = ReadSettingBool(SETTING_EXPERT_MODE); ignore_boot_marker = ReadSettingBool(SETTING_IGNORE_BOOT_MARKER); persistent_log = ReadSettingBool(SETTING_PERSISTENT_LOG); save_image_type = ReadSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE); @@ -4074,6 +4084,14 @@ extern int TestHashes(void); } // Other hazardous cheat modes require Ctrl + Alt + // Ctrl-Alt-E => Expert Mode + if ((msg.message == WM_KEYDOWN) && (msg.wParam == 'E') && + (GetKeyState(VK_CONTROL) & 0x8000) && (GetKeyState(VK_MENU) & 0x8000)) { + expert_mode = !expert_mode; + WriteSettingBool(SETTING_EXPERT_MODE, expert_mode); + PrintStatusTimeout(lmprintf(MSG_347), expert_mode); + continue; + } // Ctrl-Alt-F => List non USB removable drives such as eSATA, etc - CAUTION!!! if ((msg.message == WM_KEYDOWN) && (msg.wParam == 'F') && (GetKeyState(VK_CONTROL) & 0x8000) && (GetKeyState(VK_MENU) & 0x8000)) { diff --git a/src/rufus.h b/src/rufus.h index 012530a6..e1fc1cf8 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -572,6 +572,7 @@ typedef struct { #define UNATTEND_DUPLICATE_LOCALE 0x00020 #define UNATTEND_SET_USER 0x00040 #define UNATTEND_DISABLE_BITLOCKER 0x00080 +#define UNATTEND_FORCE_S_MODE 0x00100 #define UNATTEND_DEFAULT_MASK 0x000FF #define UNATTEND_WINDOWS_TO_GO 0x10000 // Special flag for Windows To Go @@ -580,7 +581,7 @@ typedef struct { #define UNATTEND_OOBE_SHELL_SETUP_MASK (UNATTEND_NO_DATA_COLLECTION | UNATTEND_SET_USER) #define UNATTEND_OOBE_INTERNATIONAL_MASK (UNATTEND_DUPLICATE_LOCALE) #define UNATTEND_OOBE_MASK (UNATTEND_OOBE_SHELL_SETUP_MASK | UNATTEND_OOBE_INTERNATIONAL_MASK | UNATTEND_DISABLE_BITLOCKER) -#define UNATTEND_OFFLINE_SERVICING_MASK (UNATTEND_OFFLINE_INTERNAL_DRIVES) +#define UNATTEND_OFFLINE_SERVICING_MASK (UNATTEND_OFFLINE_INTERNAL_DRIVES | UNATTEND_FORCE_S_MODE) #define UNATTEND_DEFAULT_SELECTION_MASK (UNATTEND_SECUREBOOT_TPM_MINRAM | UNATTEND_NO_ONLINE_ACCOUNT | UNATTEND_OFFLINE_INTERNAL_DRIVES) /* diff --git a/src/rufus.rc b/src/rufus.rc index bf3084b4..32b1fed1 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2074" +CAPTION "Rufus 4.3.2075" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2074,0 - PRODUCTVERSION 4,2,2074,0 + FILEVERSION 4,3,2075,0 + PRODUCTVERSION 4,3,2075,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2074" + VALUE "FileVersion", "4.3.2075" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" - VALUE "OriginalFilename", "rufus-4.2.exe" + VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2074" + VALUE "ProductVersion", "4.3.2075" END END BLOCK "VarFileInfo" diff --git a/src/settings.h b/src/settings.h index bf8c8ab2..0a95d1f6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -42,6 +42,7 @@ extern char* ini_file; #define SETTING_ENABLE_USB_DEBUG "EnableUsbDebug" #define SETTING_ENABLE_VMDK_DETECTION "EnableVmdkDetection" #define SETTING_ENABLE_WIN_DUAL_EFI_BIOS "EnableWindowsDualUefiBiosMode" +#define SETTING_EXPERT_MODE "ExpertMode" #define SETTING_FORCE_LARGE_FAT32_FORMAT "ForceLargeFat32Formatting" #define SETTING_IGNORE_BOOT_MARKER "IgnoreBootMarker" #define SETTING_INCLUDE_BETAS "CheckForBetas" diff --git a/src/wue.c b/src/wue.c index 5e76609b..d81fb25a 100644 --- a/src/wue.c +++ b/src/wue.c @@ -117,6 +117,7 @@ char* CreateUnattendXml(int arch, int flags) "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); fprintf(fd, " \n"); // This part was picked from https://github.com/AveYo/MediaCreationTool.bat/blob/main/bypass11/AutoUnattend.xml + // NB: This is INCOMPATIBLE with S-Mode below if (flags & UNATTEND_NO_ONLINE_ACCOUNT) { uprintf("• Bypass online account requirement"); fprintf(fd, " \n"); @@ -226,6 +227,14 @@ char* CreateUnattendXml(int arch, int flags) fprintf(fd, " 4\n"); fprintf(fd, " \n"); } + if (flags & UNATTEND_FORCE_S_MODE) { + uprintf("• Enforce S Mode"); + fprintf(fd, " \n", xml_arch_names[arch]); + fprintf(fd, " 1\n"); + fprintf(fd, " \n"); + } fprintf(fd, " \n"); } From 5b6574d6f67e401779ea7a166d850c8dae8c6418 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 18 Aug 2023 17:37:16 +0100 Subject: [PATCH 16/53] [misc] add S Mode detection to Windows version reporting * With thanks to @Wack0 --- src/drive.h | 4 ++-- src/rufus.rc | 10 +++++----- src/stdfn.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/drive.h b/src/drive.h index 170408d5..840faf86 100644 --- a/src/drive.h +++ b/src/drive.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Drive access function calls - * Copyright © 2011-2022 Pete Batard + * Copyright © 2011-2023 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -57,7 +57,7 @@ #define VDS_RESCAN_REFRESH 0x00000001 #define VDS_RESCAN_REENUMERATE 0x00000002 -#define VDS_SET_ERROR(hr) do { if (hr != S_OK) { SetLastError(hr); FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_GEN_FAILURE; } } while(0) +#define VDS_SET_ERROR(hr) do { if (hr != S_OK) { SetLastError((DWORD)hr); FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_GEN_FAILURE; } } while(0) #if !defined(__MINGW32__) typedef enum _FSINFOCLASS { diff --git a/src/rufus.rc b/src/rufus.rc index 32b1fed1..35aac853 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2075" +CAPTION "Rufus 4.3.2076" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2075,0 - PRODUCTVERSION 4,3,2075,0 + FILEVERSION 4,3,2076,0 + PRODUCTVERSION 4,3,2076,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2075" + VALUE "FileVersion", "4.3.2076" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2075" + VALUE "ProductVersion", "4.3.2076" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index 6d4d148a..ea9953bf 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -35,6 +35,15 @@ #include "settings.h" +// MinGW doesn't yet know these (from wldp.h) +typedef enum WLDP_WINDOWS_LOCKDOWN_MODE +{ + WLDP_WINDOWS_LOCKDOWN_MODE_UNLOCKED = 0, + WLDP_WINDOWS_LOCKDOWN_MODE_TRIAL, + WLDP_WINDOWS_LOCKDOWN_MODE_LOCKED, + WLDP_WINDOWS_LOCKDOWN_MODE_MAX, +} WLDP_WINDOWS_LOCKDOWN_MODE, * PWLDP_WINDOWS_LOCKDOWN_MODE; + windows_version_t WindowsVersion = { 0 }; /* @@ -304,6 +313,25 @@ static const char* GetEdition(DWORD ProductType) } } +PF_TYPE_DECL(WINAPI, HRESULT, WldpQueryWindowsLockdownMode, (PWLDP_WINDOWS_LOCKDOWN_MODE)); +BOOL isSMode(void) +{ + BOOL r = FALSE; + WLDP_WINDOWS_LOCKDOWN_MODE mode; + PF_INIT_OR_OUT(WldpQueryWindowsLockdownMode, Wldp); + + HRESULT hr = pfWldpQueryWindowsLockdownMode(&mode); + if (hr != S_OK) { + SetLastError((DWORD)hr); + uprintf("Could not detect S Mode: %s", WindowsErrorString()); + } else { + r = (mode != WLDP_WINDOWS_LOCKDOWN_MODE_UNLOCKED); + } + +out: + return r; +} + /* * Modified from smartmontools' os_win32.cpp */ @@ -451,6 +479,10 @@ void GetWindowsVersion(windows_version_t* windows_version) safe_sprintf(vptr, vlen, " (Build %lu.%lu)", windows_version->BuildNumber, windows_version->Ubr); else safe_sprintf(vptr, vlen, " (Build %lu)", windows_version->BuildNumber); + vptr = &windows_version->VersionStr[safe_strlen(windows_version->VersionStr)]; + vlen = sizeof(windows_version->VersionStr) - safe_strlen(windows_version->VersionStr) - 1; + if (isSMode()) + safe_sprintf(vptr, vlen, " in S Mode"); } /* From 9c6b1ad97700cb834c7c4de96a84ed923758c034 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sun, 20 Aug 2023 13:12:38 +0100 Subject: [PATCH 17/53] [vhd] fix Rufus being unable to open .vhd images * Due to a typo in vhd.c where the second safe_stricmp() should be against ".vhd" and not ".vhdx" again. * Also enable the ignore boot marker bypass for VHD/VHDX/FFU and don't misreport those images as "compressed disk images". * Closes #2309. --- src/rufus.c | 3 ++- src/rufus.rc | 10 +++++----- src/vhd.c | 15 ++++++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/rufus.c b/src/rufus.c index 64b8a9ee..5e0a4112 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1338,7 +1338,8 @@ DWORD WINAPI ImageScanThread(LPVOID param) uprintf(" Image is a FORCED non-bootable image"); else uprintf(" Image is a %sbootable %s image", - (img_report.compression_type != BLED_COMPRESSION_NONE) ? "compressed " : "", img_report.is_vhd ? "VHD" : "disk"); + (img_report.compression_type != BLED_COMPRESSION_NONE && img_report.compression_type < BLED_COMPRESSION_MAX) ? + "compressed " : "", img_report.is_vhd ? "VHD" : "disk"); selection_default = BT_IMAGE; } diff --git a/src/rufus.rc b/src/rufus.rc index 35aac853..1900c5ad 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2076" +CAPTION "Rufus 4.3.2077" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2076,0 - PRODUCTVERSION 4,3,2076,0 + FILEVERSION 4,3,2077,0 + PRODUCTVERSION 4,3,2077,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2076" + VALUE "FileVersion", "4.3.2077" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2076" + VALUE "ProductVersion", "4.3.2077" END END BLOCK "VarFileInfo" diff --git a/src/vhd.c b/src/vhd.c index a87e84d3..410ed97c 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -101,13 +101,13 @@ static comp_assoc file_assoc[] = { }; // Look for a boot marker in the MBR area of the image -static BOOL IsCompressedBootableImage(const char* path) +static int8_t IsCompressedBootableImage(const char* path) { char *ext = NULL, *physical_disk = NULL; unsigned char *buf = NULL; int i; FILE* fd = NULL; - BOOL r = FALSE; + BOOL r = 0; int64_t dc = 0; img_report.compression_type = BLED_COMPRESSION_NONE; @@ -119,7 +119,7 @@ static BOOL IsCompressedBootableImage(const char* path) img_report.compression_type = file_assoc[i].type; buf = malloc(MBR_SIZE); if (buf == NULL) - return FALSE; + return 0; FormatStatus = 0; if (img_report.compression_type < BLED_COMPRESSION_MAX) { bled_init(0, uprintf, NULL, NULL, NULL, NULL, &FormatStatus); @@ -138,6 +138,7 @@ static BOOL IsCompressedBootableImage(const char* path) if (has_ffu_support) { fd = fopenU(path, "rb"); if (fd != NULL) { + img_report.is_vhd = TRUE; dc = fread(buf, 1, MBR_SIZE, fd); fclose(fd); // The signature may not be constant, but since the only game in town to @@ -156,6 +157,7 @@ static BOOL IsCompressedBootableImage(const char* path) } else { physical_disk = VhdMountImage(path); if (physical_disk != NULL) { + img_report.is_vhd = TRUE; fd = fopenU(physical_disk, "rb"); if (fd != NULL) { dc = fread(buf, 1, MBR_SIZE, fd); @@ -168,7 +170,10 @@ static BOOL IsCompressedBootableImage(const char* path) free(buf); return FALSE; } - r = (buf[0x1FE] == 0x55) && (buf[0x1FF] == 0xAA); + if ((buf[0x1FE] == 0x55) && (buf[0x1FF] == 0xAA)) + r = 1; + else if (ignore_boot_marker) + r = 2; free(buf); return r; } @@ -935,7 +940,7 @@ char* VhdMountImage(const char* path) for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--); if (safe_stricmp(ext, ".vhdx") == 0) vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX; - else if (safe_stricmp(ext, ".vhdx") == 0) + else if (safe_stricmp(ext, ".vhd") == 0) vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; r = pfOpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO, From 866aa22a8bbf55441007fa085c6d1bda29ae2e99 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sun, 27 Aug 2023 23:03:43 +0100 Subject: [PATCH 18/53] [iso] fix Debian 12 Live not using persistence in BIOS mode * The Isolinux/Syslinux append line has been moved to /isolinux/live.cfg so make sure we patch that file when persistence is enabled. --- src/iso.c | 2 +- src/rufus.rc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/iso.c b/src/iso.c index 25c9b670..405b4d16 100644 --- a/src/iso.c +++ b/src/iso.c @@ -107,7 +107,7 @@ static const char* grub_dirname[] = { "/boot/grub/i386-pc", "/boot/grub2/i386-pc static const char* grub_cfg[] = { "grub.cfg", "loopback.cfg" }; static const char* menu_cfg = "menu.cfg"; // NB: Do not alter the order of the array below without validating hardcoded indexes in check_iso_props -static const char* syslinux_cfg[] = { "isolinux.cfg", "syslinux.cfg", "extlinux.conf", "txt.cfg" }; +static const char* syslinux_cfg[] = { "isolinux.cfg", "syslinux.cfg", "extlinux.conf", "txt.cfg", "live.cfg" }; static const char* isolinux_bin[] = { "isolinux.bin", "boot.bin" }; static const char* pe_dirname[] = { "/i386", "/amd64", "/minint" }; static const char* pe_file[] = { "ntdetect.com", "setupldr.bin", "txtsetup.sif" }; diff --git a/src/rufus.rc b/src/rufus.rc index 1900c5ad..456e7a10 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2077" +CAPTION "Rufus 4.3.2078" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2077,0 - PRODUCTVERSION 4,3,2077,0 + FILEVERSION 4,3,2078,0 + PRODUCTVERSION 4,3,2078,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2077" + VALUE "FileVersion", "4.3.2078" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2077" + VALUE "ProductVersion", "4.3.2078" END END BLOCK "VarFileInfo" From d5bf53054b58a0f3b6ca5bed64ef9e60da5fff14 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 28 Aug 2023 11:08:03 +0100 Subject: [PATCH 19/53] [misc] improve retrieval of core directories * This *might* help with the issue reported in #2296. --- src/rufus.c | 76 +++++++++++++++++++++++++++++++++++----------------- src/rufus.rc | 10 +++---- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/rufus.c b/src/rufus.c index 5e0a4112..036b0cee 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -135,7 +135,7 @@ int dialog_showing = 0, selection_default = BT_IMAGE, persistence_unit_selection int default_fs, fs_type, boot_type, partition_type, target_type; int force_update = 0, default_thread_priority = THREAD_PRIORITY_ABOVE_NORMAL; char szFolderPath[MAX_PATH], app_dir[MAX_PATH], system_dir[MAX_PATH], temp_dir[MAX_PATH], sysnative_dir[MAX_PATH]; -char app_data_dir[MAX_PATH], user_dir[MAX_PATH]; +char app_data_dir[MAX_PATH], user_dir[MAX_PATH], cur_dir[MAX_PATH]; char embedded_sl_version_str[2][12] = { "?.??", "?.??" }; char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; @@ -3326,45 +3326,63 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine // Retrieve various app & system directories. if (GetCurrentDirectoryU(sizeof(app_dir), app_dir) == 0) { - uprintf("Could not get current directory: %s", WindowsErrorString()); - app_dir[0] = 0; + uprintf("Could not get application directory: %s", WindowsErrorString()); + static_strcpy(app_dir, ".\\"); + } else { + // Microsoft has a bad habit of making some of its APIs (_chdir/_wchdir) break + // when app_dir is a drive letter that doesn't have a trailing backslash. For + // instance _chdir("F:") does not change the directory, whereas _chdir("F:\\") + // does. So make sure we always have a trailing backslash. + static_strcat(app_dir, "\\"); } - // Microsoft has a bad habit of making some of its APIs (_chdir/_wchdir) break - // when app_dir is a drive letter that doesn't have a trailing backslash. For - // instance _chdir("F:") does not change the directory, whereas _chdir("F:\\") - // does. So make sure we add a trailing backslash if the app_dir is a drive. - if ((app_dir[1] == ':') && (app_dir[2] == 0)) { - app_dir[2] = '\\'; - app_dir[3] = 0; - } - if (GetSystemDirectoryU(system_dir, sizeof(system_dir)) == 0) { - uprintf("Could not get system directory: %s", WindowsErrorString()); - static_strcpy(system_dir, "C:\\Windows\\System32"); + + // In the wonderful world of Microsoft Windows, GetCurrentDirectory() returns the + // directory where the application resides, instead of the real current directory + // so we need another method to resolve the *ACTUAL* current directory. + static_strcpy(cur_dir, ".\\"); + hFile = CreateFileU(cur_dir, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if ((hFile == INVALID_HANDLE_VALUE) || + (GetFinalPathNameByHandleU(hFile, cur_dir, sizeof(cur_dir), FILE_NAME_OPENED) == 0) || + ((strstr(cur_dir, "\\\\?\\") != cur_dir) && (strstr(cur_dir, "\\\\.\\") != cur_dir))) { + uprintf("Could not get current directory from '%s': %s", cur_dir, WindowsErrorString()); + static_strcpy(cur_dir, ".\\"); + } else { + // Need to remove the '\\?\' prefix and reappend the trailing '\' + strcpy(cur_dir, &cur_dir[4]); + static_strcat(cur_dir, "\\"); } + safe_closehandle(hFile); + // Per documentation, the returned string ends with a backslash if (GetTempPathU(sizeof(temp_dir), temp_dir) == 0) { uprintf("Could not get temp directory: %s", WindowsErrorString()); - static_strcpy(temp_dir, ".\\"); + static_strcpy(temp_dir, cur_dir); } else { // Some folks have found nothing better than configure their Windows installation to use // a symlink for their temp dir, and it so happens that the Windows WIM mounting facility, // which we need for applying the WUE options, can't handle symlinked directories. So we // *attempt* to resolve the actual symlinked temp dir for this super limited number of // users, with the hope that doing so is not going to break stuff elsewhere... - HANDLE handle = CreateFileU(temp_dir, GENERIC_READ, FILE_SHARE_READ, NULL, + hFile = CreateFileU(temp_dir, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); // GetFinalPathNameByHandle returns a UNC path, which should be prefixed by '\\?\' or '\\.\' - if ((GetFinalPathNameByHandleU(handle, temp_dir, sizeof(temp_dir), FILE_NAME_OPENED) == 0) || + if ((hFile == INVALID_HANDLE_VALUE) || + (GetFinalPathNameByHandleU(hFile, temp_dir, sizeof(temp_dir), FILE_NAME_OPENED) == 0) || ((strstr(temp_dir, "\\\\?\\") != temp_dir) && (strstr(temp_dir, "\\\\.\\") != temp_dir))) { - uprintf("Could not get actual temp directory: %s", WindowsErrorString()); - static_strcpy(temp_dir, ".\\"); + uprintf("Could not get actual temp directory from '%s': %s", temp_dir, WindowsErrorString()); + static_strcpy(temp_dir, cur_dir); } else { // Need to remove the '\\?\' prefix or else we'll get issues with the Fido icon strcpy(temp_dir, &temp_dir[4]); // And me must re-append the '\' that gets removed by GetFinalPathNameByHandle() static_strcat(temp_dir, "\\"); } - CloseHandle(handle); + safe_closehandle(hFile); + } + if (GetSystemDirectoryU(system_dir, sizeof(system_dir)) == 0) { + uprintf("Could not get system directory: %s", WindowsErrorString()); + static_strcpy(system_dir, "C:\\Windows\\System32"); } if (!SHGetSpecialFolderPathU(NULL, app_data_dir, CSIDL_LOCAL_APPDATA, FALSE)) { uprintf("Could not get app data directory: %s", WindowsErrorString()); @@ -3392,13 +3410,19 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine } } #endif + uprintf("Cur dir: '%s'", cur_dir); + uprintf("App dir: '%s'", app_dir); + uprintf("Sys dir: '%s'", sysnative_dir); + uprintf("Usr dir: '%s'", user_dir); + uprintf("Dat dir: '%s'", app_data_dir); + uprintf("Tmp dir: '%s'", temp_dir); // Look for a rufus.app file in the current app directory // Since Microsoft makes it downright impossible to pass an arg in the app manifest // and the automated VS2019 package building process doesn't like renaming the .exe // right under its nose (else we would use the same trick as for portable vs regular) // we use yet another workaround to detect if we are running the AppStore version... - static_sprintf(ini_path, "%s\\rufus.app", app_dir); + static_sprintf(ini_path, "%srufus.app", app_dir); if (PathFileExistsU(ini_path)) { appstore_version = TRUE; goto skip_args_processing; @@ -3530,7 +3554,7 @@ skip_args_processing: uprintf("AppStore version detected"); // Look for a .ini file in the current app directory - static_sprintf(ini_path, "%s\\rufus.ini", app_dir); + static_sprintf(ini_path, "%srufus.ini", app_dir); fd = fopenU(ini_path, ini_flags); // Will create the file if portable mode is requested // Using the string directly in safe_strcmp() would call GetSignatureName() twice tmp = GetSignatureName(NULL, NULL, FALSE); @@ -3590,7 +3614,7 @@ skip_args_processing: init_localization(); // Seek for a loc file in the application directory - static_sprintf(loc_file, "%s\\%s", app_dir, rufus_loc); + static_sprintf(loc_file, "%s%s", app_dir, rufus_loc); if (GetFileAttributesU(loc_file) == INVALID_FILE_ATTRIBUTES) { uprintf("loc file not found in current directory - embedded one will be used"); @@ -4154,8 +4178,10 @@ out: // Kill the update check thread if running if (update_check_thread != NULL) TerminateThread(update_check_thread, 1); - if ((!external_loc_file) && (loc_file[0] != 0)) - DeleteFileU(loc_file); + if ((!external_loc_file) && (loc_file[0] != 0)) { + if (!DeleteFileU(loc_file)) + uprintf("Could not delete '%s': %s", loc_file, WindowsErrorString()); + } DestroyAllTooltips(); ClrAlertPromptHook(); exit_localization(); diff --git a/src/rufus.rc b/src/rufus.rc index 456e7a10..67e260e7 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2078" +CAPTION "Rufus 4.3.2079" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2078,0 - PRODUCTVERSION 4,3,2078,0 + FILEVERSION 4,3,2079,0 + PRODUCTVERSION 4,3,2079,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2078" + VALUE "FileVersion", "4.3.2079" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2078" + VALUE "ProductVersion", "4.3.2079" END END BLOCK "VarFileInfo" From 94ecf74c5f1f3a078d6d658a5c2f74f15d428ddb Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 4 Sep 2023 11:41:35 +0100 Subject: [PATCH 20/53] [misc] use FORMAT_MESSAGE_FROM_HMODULE where possible and drop our custom message tables --- src/net.c | 201 ++-------------------- src/rufus.rc | 10 +- src/stdio.c | 468 +++------------------------------------------------ 3 files changed, 41 insertions(+), 638 deletions(-) diff --git a/src/net.c b/src/net.c index bdbb9c7a..43f96d94 100644 --- a/src/net.c +++ b/src/net.c @@ -62,177 +62,6 @@ static const char* request_headers = "Accept-Encoding: gzip, deflate"; #define INetworkListManager_get_IsConnectedToInternet INetworkListManager_IsConnectedToInternet #endif -/* - * FormatMessage does not handle internet errors - * https://docs.microsoft.com/en-us/windows/desktop/wininet/wininet-errors - */ -const char* WinInetErrorString(void) -{ - static char error_string[256]; - DWORD size = sizeof(error_string); - PF_TYPE_DECL(WINAPI, BOOL, InternetGetLastResponseInfoA, (LPDWORD, LPSTR, LPDWORD)); - PF_INIT(InternetGetLastResponseInfoA, WinInet); - - error_code = HRESULT_CODE(GetLastError()); - - if ((error_code < INTERNET_ERROR_BASE) || (error_code > INTERNET_ERROR_LAST)) - return WindowsErrorString(); - - switch(error_code) { - case ERROR_INTERNET_OUT_OF_HANDLES: - return "No more handles could be generated at this time."; - case ERROR_INTERNET_TIMEOUT: - return "The request has timed out."; - case ERROR_INTERNET_INTERNAL_ERROR: - return "An internal error has occurred."; - case ERROR_INTERNET_INVALID_URL: - return "The URL is invalid."; - case ERROR_INTERNET_UNRECOGNIZED_SCHEME: - return "The URL scheme could not be recognized or is not supported."; - case ERROR_INTERNET_NAME_NOT_RESOLVED: - return "The server name could not be resolved."; - case ERROR_INTERNET_PROTOCOL_NOT_FOUND: - return "The requested protocol could not be located."; - case ERROR_INTERNET_INVALID_OPTION: - return "A request specified an invalid option value."; - case ERROR_INTERNET_BAD_OPTION_LENGTH: - return "The length of an option supplied is incorrect for the type of option specified."; - case ERROR_INTERNET_OPTION_NOT_SETTABLE: - return "The request option cannot be set, only queried."; - case ERROR_INTERNET_SHUTDOWN: - return "The Win32 Internet function support is being shut down or unloaded."; - case ERROR_INTERNET_INCORRECT_USER_NAME: - return "The request to connect and log on to an FTP server could not be completed because the supplied user name is incorrect."; - case ERROR_INTERNET_INCORRECT_PASSWORD: - return "The request to connect and log on to an FTP server could not be completed because the supplied password is incorrect."; - case ERROR_INTERNET_LOGIN_FAILURE: - return "The request to connect to and log on to an FTP server failed."; - case ERROR_INTERNET_INVALID_OPERATION: - return "The requested operation is invalid."; - case ERROR_INTERNET_OPERATION_CANCELLED: - return "The operation was cancelled, usually because the handle on which the request was operating was closed before the operation completed."; - case ERROR_INTERNET_INCORRECT_HANDLE_TYPE: - return "The type of handle supplied is incorrect for this operation."; - case ERROR_INTERNET_INCORRECT_HANDLE_STATE: - return "The requested operation cannot be carried out because the handle supplied is not in the correct state."; - case ERROR_INTERNET_NOT_PROXY_REQUEST: - return "The request cannot be made via a proxy."; - case ERROR_INTERNET_REGISTRY_VALUE_NOT_FOUND: - return "A required registry value could not be located."; - case ERROR_INTERNET_BAD_REGISTRY_PARAMETER: - return "A required registry value was located but is an incorrect type or has an invalid value."; - case ERROR_INTERNET_NO_DIRECT_ACCESS: - return "Direct network access cannot be made at this time."; - case ERROR_INTERNET_NO_CONTEXT: - return "An asynchronous request could not be made because a zero context value was supplied."; - case ERROR_INTERNET_NO_CALLBACK: - return "An asynchronous request could not be made because a callback function has not been set."; - case ERROR_INTERNET_REQUEST_PENDING: - return "The required operation could not be completed because one or more requests are pending."; - case ERROR_INTERNET_INCORRECT_FORMAT: - return "The format of the request is invalid."; - case ERROR_INTERNET_ITEM_NOT_FOUND: - return "The requested item could not be located."; - case ERROR_INTERNET_CANNOT_CONNECT: - return "The attempt to connect to the server failed."; - case ERROR_INTERNET_CONNECTION_ABORTED: - return "The connection with the server has been terminated."; - case ERROR_INTERNET_CONNECTION_RESET: - return "The connection with the server has been reset."; - case ERROR_INTERNET_FORCE_RETRY: - return "Calls for the Win32 Internet function to redo the request."; - case ERROR_INTERNET_INVALID_PROXY_REQUEST: - return "The request to the proxy was invalid."; - case ERROR_INTERNET_HANDLE_EXISTS: - return "The request failed because the handle already exists."; - case ERROR_INTERNET_SEC_INVALID_CERT: - return "The SSL certificate is invalid."; - case ERROR_INTERNET_SEC_CERT_DATE_INVALID: - return "SSL certificate date that was received from the server is bad. The certificate is expired."; - case ERROR_INTERNET_SEC_CERT_CN_INVALID: - return "SSL certificate common name (host name field) is incorrect."; - case ERROR_INTERNET_SEC_CERT_ERRORS: - return "The SSL certificate contains errors."; - case ERROR_INTERNET_SEC_CERT_NO_REV: - return "The SSL certificate was not revoked."; - case ERROR_INTERNET_SEC_CERT_REV_FAILED: - return "The revocation check of the SSL certificate failed."; - case ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR: - return "The application is moving from a non-SSL to an SSL connection because of a redirect."; - case ERROR_INTERNET_HTTPS_TO_HTTP_ON_REDIR: - return "The application is moving from an SSL to an non-SSL connection because of a redirect."; - case ERROR_INTERNET_MIXED_SECURITY: - return "Some of the content being viewed may have come from unsecured servers."; - case ERROR_INTERNET_CHG_POST_IS_NON_SECURE: - return "The application is posting and attempting to change multiple lines of text on a server that is not secure."; - case ERROR_INTERNET_POST_IS_NON_SECURE: - return "The application is posting data to a server that is not secure."; - case ERROR_FTP_TRANSFER_IN_PROGRESS: - return "The requested operation cannot be made on the FTP session handle because an operation is already in progress."; - case ERROR_FTP_DROPPED: - return "The FTP operation was not completed because the session was aborted."; - case ERROR_GOPHER_PROTOCOL_ERROR: - case ERROR_GOPHER_NOT_FILE: - case ERROR_GOPHER_DATA_ERROR: - case ERROR_GOPHER_END_OF_DATA: - case ERROR_GOPHER_INVALID_LOCATOR: - case ERROR_GOPHER_INCORRECT_LOCATOR_TYPE: - case ERROR_GOPHER_NOT_GOPHER_PLUS: - case ERROR_GOPHER_ATTRIBUTE_NOT_FOUND: - case ERROR_GOPHER_UNKNOWN_LOCATOR: - return "Gopher? Really??? What is this, 1994?"; - case ERROR_HTTP_HEADER_NOT_FOUND: - return "The requested header could not be located."; - case ERROR_HTTP_DOWNLEVEL_SERVER: - return "The server did not return any headers."; - case ERROR_HTTP_INVALID_SERVER_RESPONSE: - return "The server response could not be parsed."; - case ERROR_HTTP_INVALID_HEADER: - return "The supplied header is invalid."; - case ERROR_HTTP_INVALID_QUERY_REQUEST: - return "The request made to HttpQueryInfo is invalid."; - case ERROR_HTTP_HEADER_ALREADY_EXISTS: - return "The header could not be added because it already exists."; - case ERROR_HTTP_REDIRECT_FAILED: - return "The redirection failed because either the scheme changed or all attempts made to redirect failed."; - case ERROR_INTERNET_SECURITY_CHANNEL_ERROR: - return "This system's SSL library is too old to be able to access this website."; - case ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED: - return "Client Authentication certificate needed"; - case ERROR_INTERNET_BAD_AUTO_PROXY_SCRIPT: - return "Bad auto proxy script."; - case ERROR_INTERNET_UNABLE_TO_DOWNLOAD_SCRIPT: - return "Unable to download script."; - case ERROR_INTERNET_NOT_INITIALIZED: - return "Internet has not be initialized."; - case ERROR_INTERNET_UNABLE_TO_CACHE_FILE: - return "Unable to cache the file."; - case ERROR_INTERNET_TCPIP_NOT_INSTALLED: - return "TPC/IP not installed."; - case ERROR_INTERNET_DISCONNECTED: - return "Internet is disconnected."; - case ERROR_INTERNET_SERVER_UNREACHABLE: - return "Server could not be reached."; - case ERROR_INTERNET_PROXY_SERVER_UNREACHABLE: - return "Proxy server could not be reached."; - case ERROR_INTERNET_FAILED_DUETOSECURITYCHECK: - return "A security check prevented internet connection."; - case ERROR_INTERNET_NEED_MSN_SSPI_PKG: - return "This connection requires an MSN Security Support Provider Interface package."; - case ERROR_INTERNET_LOGIN_FAILURE_DISPLAY_ENTITY_BODY: - return "Please ask Microsoft about that one!"; - case ERROR_INTERNET_EXTENDED_ERROR: - if (pfInternetGetLastResponseInfoA != NULL) { - pfInternetGetLastResponseInfoA(&error_code, error_string, &size); - return error_string; - } - // fall through - default: - static_sprintf(error_string, "Unknown internet error 0x%08lX", error_code); - return error_string; - } -} - static char* GetShortName(const char* url) { static char short_name[128]; @@ -393,34 +222,34 @@ uint64_t DownloadToFileOrBufferEx(const char* url, const char* file, const char* if ( (!pfInternetCrackUrlA(url, (DWORD)safe_strlen(url), 0, &UrlParts)) || (UrlParts.lpszHostName == NULL) || (UrlParts.lpszUrlPath == NULL)) { - uprintf("Unable to decode URL: %s", WinInetErrorString()); + uprintf("Unable to decode URL: %s", WindowsErrorString()); goto out; } hostname[sizeof(hostname)-1] = 0; hSession = GetInternetSession(user_agent, TRUE); if (hSession == NULL) { - uprintf("Could not open Internet session: %s", WinInetErrorString()); + uprintf("Could not open Internet session: %s", WindowsErrorString()); goto out; } hConnection = pfInternetConnectA(hSession, UrlParts.lpszHostName, UrlParts.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, (DWORD_PTR)NULL); if (hConnection == NULL) { - uprintf("Could not connect to server %s:%d: %s", UrlParts.lpszHostName, UrlParts.nPort, WinInetErrorString()); + uprintf("Could not connect to server %s:%d: %s", UrlParts.lpszHostName, UrlParts.nPort, WindowsErrorString()); goto out; } hRequest = pfHttpOpenRequestA(hConnection, "GET", UrlParts.lpszUrlPath, NULL, NULL, accept_types, - INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP|INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS| - INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI|INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_HYPERLINK| + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | + INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_HYPERLINK | ((UrlParts.nScheme==INTERNET_SCHEME_HTTPS)?INTERNET_FLAG_SECURE:0), (DWORD_PTR)NULL); if (hRequest == NULL) { - uprintf("Could not open URL %s: %s", url, WinInetErrorString()); + uprintf("Could not open URL %s: %s", url, WindowsErrorString()); goto out; } if (!pfHttpSendRequestA(hRequest, request_headers, -1L, NULL, 0)) { - uprintf("Unable to send request: %s", WinInetErrorString()); + uprintf("Unable to send request: %s", WindowsErrorString()); goto out; } @@ -435,7 +264,7 @@ uint64_t DownloadToFileOrBufferEx(const char* url, const char* file, const char* } dwSize = sizeof(strsize); if (!pfHttpQueryInfoA(hRequest, HTTP_QUERY_CONTENT_LENGTH, (LPVOID)strsize, &dwSize, NULL)) { - uprintf("Unable to retrieve file length: %s", WinInetErrorString()); + uprintf("Unable to retrieve file length: %s", WindowsErrorString()); goto out; } total_size = strtoull(strsize, NULL, 10); @@ -452,7 +281,7 @@ uint64_t DownloadToFileOrBufferEx(const char* url, const char* file, const char* if (file != NULL) { hFile = CreateFileU(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { - uprintf("Unable to create file '%s': %s", short_name, WinInetErrorString()); + uprintf("Unable to create file '%s': %s", short_name, WindowsErrorString()); goto out; } } else { @@ -479,7 +308,7 @@ uint64_t DownloadToFileOrBufferEx(const char* url, const char* file, const char* UpdateProgressWithInfo(OP_NOOP, MSG_241, size, total_size); if (file != NULL) { if (!WriteFile(hFile, buf, dwDownloaded, &dwWritten, NULL)) { - uprintf("Error writing file '%s': %s", short_name, WinInetErrorString()); + uprintf("Error writing file '%s': %s", short_name, WindowsErrorString()); goto out; } else if (dwDownloaded != dwWritten) { uprintf("Error writing file '%s': Only %d/%d bytes written", short_name, dwWritten, dwDownloaded); @@ -564,11 +393,11 @@ DWORD DownloadSignedFile(const char* url, const char* file, HWND hProgressDialog DownloadStatus = 206; // Partial content hFile = CreateFileU(file, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { - uprintf("Unable to create file '%s': %s", PathFindFileNameU(file), WinInetErrorString()); + uprintf("Unable to create file '%s': %s", PathFindFileNameU(file), WindowsErrorString()); goto out; } if (!WriteFile(hFile, buf, buf_len, &ret, NULL)) { - uprintf("Error writing file '%s': %s", PathFindFileNameU(file), WinInetErrorString()); + uprintf("Error writing file '%s': %s", PathFindFileNameU(file), WindowsErrorString()); ret = 0; goto out; } else if (ret != buf_len) { @@ -584,7 +413,7 @@ out: if ((bPromptOnError) && (DownloadStatus != 200)) { PrintInfo(0, MSG_242); SetLastError(error_code); - MessageBoxExU(hMainDialog, IS_ERROR(FormatStatus) ? StrError(FormatStatus, FALSE) : WinInetErrorString(), + MessageBoxExU(hMainDialog, IS_ERROR(FormatStatus) ? StrError(FormatStatus, FALSE) : WindowsErrorString(), lmprintf(MSG_044), MB_OK | MB_ICONERROR | MB_IS_RTL, selected_langid); } safe_closehandle(hFile); @@ -758,7 +587,7 @@ static DWORD WINAPI CheckForUpdatesThread(LPVOID param) INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI|INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_HYPERLINK| ((UrlParts.nScheme == INTERNET_SCHEME_HTTPS)?INTERNET_FLAG_SECURE:0), (DWORD_PTR)NULL); if ((hRequest == NULL) || (!pfHttpSendRequestA(hRequest, request_headers, -1L, NULL, 0))) { - uprintf("Unable to send request: %s", WinInetErrorString()); + uprintf("Unable to send request: %s", WindowsErrorString()); goto out; } @@ -1055,7 +884,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) Notification(MSG_INFO, NULL, NULL, lmprintf(MSG_211), lmprintf(MSG_041)); PrintInfo(0, MSG_211); } else { - Notification(MSG_ERROR, NULL, NULL, lmprintf(MSG_194, GetShortName(url)), lmprintf(MSG_043, WinInetErrorString())); + Notification(MSG_ERROR, NULL, NULL, lmprintf(MSG_194, GetShortName(url)), lmprintf(MSG_043, WindowsErrorString())); PrintInfo(0, MSG_212); } } else { diff --git a/src/rufus.rc b/src/rufus.rc index 67e260e7..133e3e0f 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2079" +CAPTION "Rufus 4.3.2080" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2079,0 - PRODUCTVERSION 4,3,2079,0 + FILEVERSION 4,3,2080,0 + PRODUCTVERSION 4,3,2080,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2079" + VALUE "FileVersion", "4.3.2080" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2079" + VALUE "ProductVersion", "4.3.2080" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index eaef1810..fe80dc0f 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -219,472 +219,46 @@ void DumpBufferHex(void *buf, size_t size) uprintf("%s\n", line); } -// Count on Microsoft to add a new API while not bothering updating the existing error facilities, -// so that the new error messages have to be handled manually. Now, since I don't have all day: -// 1. Copy text from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-vds/5102cc53-3143-4268-ba4c-6ea39e999ab4 -// 2. awk '{l[NR%7]=$0} {if (NR%7==0) printf "\tcase %s:\t// %s\n\t\treturn \"%s\";\n", l[1], l[3], l[6]}' vds.txt -// 3. Filter out the crap we don't need. -static const char *GetVdsError(DWORD error_code) -{ - switch (error_code) { - case 0x80042400: // VDS_E_NOT_SUPPORTED - return "The operation is not supported by the object."; - case 0x80042401: // VDS_E_INITIALIZED_FAILED - return "VDS or the provider failed to initialize."; - case 0x80042402: // VDS_E_INITIALIZE_NOT_CALLED - return "VDS did not call the hardware provider's initialization method."; - case 0x80042403: // VDS_E_ALREADY_REGISTERED - return "The provider is already registered."; - case 0x80042404: // VDS_E_ANOTHER_CALL_IN_PROGRESS - return "A concurrent second call is made on an object before the first call is completed."; - case 0x80042405: // VDS_E_OBJECT_NOT_FOUND - return "The specified object was not found."; - case 0x80042406: // VDS_E_INVALID_SPACE - return "The specified space is neither free nor valid."; - case 0x80042407: // VDS_E_PARTITION_LIMIT_REACHED - return "No more partitions can be created on the specified disk."; - case 0x80042408: // VDS_E_PARTITION_NOT_EMPTY - return "The extended partition is not empty."; - case 0x80042409: // VDS_E_OPERATION_PENDING - return "The operation is still in progress."; - case 0x8004240A: // VDS_E_OPERATION_DENIED - return "The operation is not permitted on the specified disk, partition, or volume."; - case 0x8004240B: // VDS_E_OBJECT_DELETED - return "The object no longer exists."; - case 0x8004240C: // VDS_E_CANCEL_TOO_LATE - return "The operation can no longer be canceled."; - case 0x8004240D: // VDS_E_OPERATION_CANCELED - return "The operation has already been canceled."; - case 0x8004240E: // VDS_E_CANNOT_EXTEND - return "The file system does not support extending this volume."; - case 0x8004240F: // VDS_E_NOT_ENOUGH_SPACE - return "There is not enough space to complete the operation."; - case 0x80042410: // VDS_E_NOT_ENOUGH_DRIVE - return "There are not enough free disk drives in the subsystem to complete the operation."; - case 0x80042411: // VDS_E_BAD_COOKIE - return "The cookie was not found."; - case 0x80042412: // VDS_E_NO_MEDIA - return "There is no removable media in the drive."; - case 0x80042413: // VDS_E_DEVICE_IN_USE - return "The device is currently in use."; - case 0x80042414: // VDS_E_DISK_NOT_EMPTY - return "The disk contains partitions or volumes."; - case 0x80042415: // VDS_E_INVALID_OPERATION - return "The specified operation is not valid."; - case 0x80042416: // VDS_E_PATH_NOT_FOUND - return "The specified path was not found."; - case 0x80042417: // VDS_E_DISK_NOT_INITIALIZED - return "The specified disk has not been initialized."; - case 0x80042418: // VDS_E_NOT_AN_UNALLOCATED_DISK - return "The specified disk is not an unallocated disk."; - case 0x80042419: // VDS_E_UNRECOVERABLE_ERROR - return "An unrecoverable error occurred. The service MUST shut down."; - case 0x0004241A: // VDS_S_DISK_PARTIALLY_CLEANED - return "The clean operation was not a full clean or was canceled before it could be completed."; - case 0x8004241B: // VDS_E_DMADMIN_SERVICE_CONNECTION_FAILED - return "The provider failed to connect to the LDMA service."; - case 0x8004241C: // VDS_E_PROVIDER_INITIALIZATION_FAILED - return "The provider failed to initialize."; - case 0x8004241D: // VDS_E_OBJECT_EXISTS - return "The object already exists."; - case 0x8004241E: // VDS_E_NO_DISKS_FOUND - return "No disks were found on the target machine."; - case 0x8004241F: // VDS_E_PROVIDER_CACHE_CORRUPT - return "The cache for a provider is corrupt."; - case 0x80042420: // VDS_E_DMADMIN_METHOD_CALL_FAILED - return "A method call to the LDMA service failed."; - case 0x00042421: // VDS_S_PROVIDER_ERROR_LOADING_CACHE - return "The provider encountered errors while loading the cache."; - case 0x80042422: // VDS_E_PROVIDER_VOL_DEVICE_NAME_NOT_FOUND - return "The device form of the volume pathname could not be retrieved."; - case 0x80042423: // VDS_E_PROVIDER_VOL_OPEN - return "Failed to open the volume device"; - case 0x80042424: // VDS_E_DMADMIN_CORRUPT_NOTIFICATION - return "A corrupt notification was sent from the LDMA service."; - case 0x80042425: // VDS_E_INCOMPATIBLE_FILE_SYSTEM - return "The file system is incompatible with the specified operation."; - case 0x80042426: // VDS_E_INCOMPATIBLE_MEDIA - return "The media is incompatible with the specified operation."; - case 0x80042427: // VDS_E_ACCESS_DENIED - return "Access is denied. A VDS operation MUST run elevated."; - case 0x80042428: // VDS_E_MEDIA_WRITE_PROTECTED - return "The media is write-protected."; - case 0x80042429: // VDS_E_BAD_LABEL - return "The volume label is not valid."; - case 0x8004242A: // VDS_E_CANT_QUICK_FORMAT - return "The volume cannot be quick-formatted."; - case 0x8004242B: // VDS_E_IO_ERROR - return "An I/O error occurred during the operation."; - case 0x8004242C: // VDS_E_VOLUME_TOO_SMALL - return "The volume size is too small."; - case 0x8004242D: // VDS_E_VOLUME_TOO_BIG - return "The volume size is too large."; - case 0x8004242E: // VDS_E_CLUSTER_SIZE_TOO_SMALL - return "The cluster size is too small."; - case 0x8004242F: // VDS_E_CLUSTER_SIZE_TOO_BIG - return "The cluster size is too large."; - case 0x80042430: // VDS_E_CLUSTER_COUNT_BEYOND_32BITS - return "The number of clusters is too large to be represented as a 32-bit integer."; - case 0x80042431: // VDS_E_OBJECT_STATUS_FAILED - return "The component that the object represents has failed."; - case 0x80042432: // VDS_E_VOLUME_INCOMPLETE - return "The volume is incomplete."; - case 0x80042433: // VDS_E_EXTENT_SIZE_LESS_THAN_MIN - return "The specified extent size is too small."; - case 0x00042434: // VDS_S_UPDATE_BOOTFILE_FAILED - return "The operation was successful, but VDS failed to update the boot options."; - case 0x00042436: // VDS_S_BOOT_PARTITION_NUMBER_CHANGE - case 0x80042436: // VDS_E_BOOT_PARTITION_NUMBER_CHANGE - return "The boot partition's partition number will change as a result of the operation."; - case 0x80042437: // VDS_E_NO_FREE_SPACE - return "The specified disk does not have enough free space to complete the operation."; - case 0x80042438: // VDS_E_ACTIVE_PARTITION - return "An active partition was detected on the selected disk."; - case 0x80042439: // VDS_E_PARTITION_OF_UNKNOWN_TYPE - return "The partition information cannot be read."; - case 0x8004243A: // VDS_E_LEGACY_VOLUME_FORMAT - return "A partition with an unknown type was detected on the specified disk."; - case 0x8004243C: // VDS_E_MIGRATE_OPEN_VOLUME - return "A volume on the specified disk could not be opened."; - case 0x8004243D: // VDS_E_VOLUME_NOT_ONLINE - return "The volume is not online."; - case 0x8004243E: // VDS_E_VOLUME_NOT_HEALTHY - return "The volume is failing or has failed."; - case 0x8004243F: // VDS_E_VOLUME_SPANS_DISKS - return "The volume spans multiple disks."; - case 0x80042440: // VDS_E_REQUIRES_CONTIGUOUS_DISK_SPACE - return "The volume does not consist of a single disk extent."; - case 0x80042441: // VDS_E_BAD_PROVIDER_DATA - return "A provider returned bad data."; - case 0x80042442: // VDS_E_PROVIDER_FAILURE - return "A provider failed to complete an operation."; - case 0x00042443: // VDS_S_VOLUME_COMPRESS_FAILED - return "The file system was formatted successfully but could not be compressed."; - case 0x80042444: // VDS_E_PACK_OFFLINE - return "The pack is offline."; - case 0x80042445: // VDS_E_VOLUME_NOT_A_MIRROR - return "The volume is not a mirror."; - case 0x80042446: // VDS_E_NO_EXTENTS_FOR_VOLUME - return "No extents were found for the volume."; - case 0x80042447: // VDS_E_DISK_NOT_LOADED_TO_CACHE - return "The migrated disk failed to load to the cache."; - case 0x80042448: // VDS_E_INTERNAL_ERROR - return "VDS encountered an internal error."; - case 0x8004244A: // VDS_E_PROVIDER_TYPE_NOT_SUPPORTED - return "The method call is not supported for the specified provider type."; - case 0x8004244B: // VDS_E_DISK_NOT_ONLINE - return "One or more of the specified disks are not online."; - case 0x8004244C: // VDS_E_DISK_IN_USE_BY_VOLUME - return "One or more extents of the disk are already being used by the volume."; - case 0x0004244D: // VDS_S_IN_PROGRESS - return "The asynchronous operation is in progress."; - case 0x8004244E: // VDS_E_ASYNC_OBJECT_FAILURE - return "Failure initializing the asynchronous object."; - case 0x8004244F: // VDS_E_VOLUME_NOT_MOUNTED - return "The volume is not mounted."; - case 0x80042450: // VDS_E_PACK_NOT_FOUND - return "The pack was not found."; - case 0x80042453: // VDS_E_OBJECT_OUT_OF_SYNC - return "The reference to the object might be stale."; - case 0x80042454: // VDS_E_MISSING_DISK - return "The specified disk could not be found."; - case 0x80042455: // VDS_E_DISK_PNP_REG_CORRUPT - return "The provider's list of PnP registered disks has become corrupted."; - case 0x80042457: // VDS_E_NO_DRIVELETTER_FLAG - return "The provider does not support the VDS_VF_NO DRIVELETTER volume flag."; - case 0x80042459: // VDS_E_REVERT_ON_CLOSE_SET - return "Some volume flags are already set."; - case 0x0004245B: // VDS_S_UNABLE_TO_GET_GPT_ATTRIBUTES - return "Unable to retrieve the GPT attributes for this volume."; - case 0x8004245C: // VDS_E_VOLUME_TEMPORARILY_DISMOUNTED - return "The volume is already dismounted temporarily."; - case 0x8004245D: // VDS_E_VOLUME_PERMANENTLY_DISMOUNTED - return "The volume is already permanently dismounted."; - case 0x8004245E: // VDS_E_VOLUME_HAS_PATH - return "The volume cannot be dismounted permanently because it still has an access path."; - case 0x8004245F: // VDS_E_TIMEOUT - return "The operation timed out."; - case 0x80042461: // VDS_E_LDM_TIMEOUT - return "The operation timed out in the LDMA service. Retry the operation."; - case 0x80042462: // VDS_E_REVERT_ON_CLOSE_MISMATCH - return "The flags to be cleared do not match the flags that were set previously."; - case 0x80042463: // VDS_E_RETRY - return "The operation failed. Retry the operation."; - case 0x80042464: // VDS_E_ONLINE_PACK_EXISTS - return "The operation failed, because an online pack object already exists."; - case 0x80042468: // VDS_E_MAX_USABLE_MBR - return "Only the first 2TB are usable on large MBR disks."; - case 0x80042500: // VDS_E_NO_SOFTWARE_PROVIDERS_LOADED - return "There are no software providers loaded."; - case 0x80042501: // VDS_E_DISK_NOT_MISSING - return "The disk is not missing."; - case 0x80042502: // VDS_E_NO_VOLUME_LAYOUT - return "The volume's layout could not be retrieved."; - case 0x80042503: // VDS_E_CORRUPT_VOLUME_INFO - return "The volume's driver information is corrupted."; - case 0x80042504: // VDS_E_INVALID_ENUMERATOR - return "The enumerator is corrupted"; - case 0x80042505: // VDS_E_DRIVER_INTERNAL_ERROR - return "An internal error occurred in the volume management driver."; - case 0x80042507: // VDS_E_VOLUME_INVALID_NAME - return "The volume name is not valid."; - case 0x00042508: // VDS_S_DISK_IS_MISSING - return "The disk is missing and not all information could be returned."; - case 0x80042509: // VDS_E_CORRUPT_PARTITION_INFO - return "The disk's partition information is corrupted."; - case 0x0004250A: // VDS_S_NONCONFORMANT_PARTITION_INFO - return "The disk's partition information does not conform to what is expected on a dynamic disk."; - case 0x8004250B: // VDS_E_CORRUPT_EXTENT_INFO - return "The disk's extent information is corrupted."; - case 0x0004250E: // VDS_S_SYSTEM_PARTITION - return "Warning: There was a failure while checking for the system partition."; - case 0x8004250F: // VDS_E_BAD_PNP_MESSAGE - return "The PNP service sent a corrupted notification to the provider."; - case 0x80042510: // VDS_E_NO_PNP_DISK_ARRIVE - case 0x80042511: // VDS_E_NO_PNP_VOLUME_ARRIVE - return "No disk/volume arrival notification was received."; - case 0x80042512: // VDS_E_NO_PNP_DISK_REMOVE - case 0x80042513: // VDS_E_NO_PNP_VOLUME_REMOVE - return "No disk/volume removal notification was received."; - case 0x80042514: // VDS_E_PROVIDER_EXITING - return "The provider is exiting."; - case 0x00042517: // VDS_S_NO_NOTIFICATION - return "No volume arrival notification was received."; - case 0x80042519: // VDS_E_INVALID_DISK - return "The specified disk is not valid."; - case 0x8004251A: // VDS_E_INVALID_PACK - return "The specified disk pack is not valid."; - case 0x8004251B: // VDS_E_VOLUME_ON_DISK - return "This operation is not allowed on disks with volumes."; - case 0x8004251C: // VDS_E_DRIVER_INVALID_PARAM - return "The driver returned an invalid parameter error."; - case 0x8004253D: // VDS_E_DRIVER_OBJECT_NOT_FOUND - return "The object was not found in the driver cache."; - case 0x8004253E: // VDS_E_PARTITION_NOT_CYLINDER_ALIGNED - return "The disk layout contains partitions which are not cylinder aligned."; - case 0x8004253F: // VDS_E_DISK_LAYOUT_PARTITIONS_TOO_SMALL - return "The disk layout contains partitions which are less than the minimum required size."; - case 0x80042540: // VDS_E_DISK_IO_FAILING - return "The I/O to the disk is failing."; - case 0x80042543: // VDS_E_GPT_ATTRIBUTES_INVALID - return "Invalid GPT attributes were specified."; - case 0x8004254D: // VDS_E_UNEXPECTED_DISK_LAYOUT_CHANGE - return "An unexpected layout change occurred external to the volume manager."; - case 0x8004254E: // VDS_E_INVALID_VOLUME_LENGTH - return "The volume length is invalid."; - case 0x8004254F: // VDS_E_VOLUME_LENGTH_NOT_SECTOR_SIZE_MULTIPLE - return "The volume length is not a multiple of the sector size."; - case 0x80042550: // VDS_E_VOLUME_NOT_RETAINED - return "The volume does not have a retained partition association."; - case 0x80042551: // VDS_E_VOLUME_RETAINED - return "The volume already has a retained partition association."; - case 0x80042553: // VDS_E_ALIGN_BEYOND_FIRST_CYLINDER - return "The specified alignment is beyond the first cylinder."; - case 0x80042554: // VDS_E_ALIGN_NOT_SECTOR_SIZE_MULTIPLE - return "The specified alignment is not a multiple of the sector size."; - case 0x80042555: // VDS_E_ALIGN_NOT_ZERO - return "The specified partition type cannot be created with a non-zero alignment."; - case 0x80042556: // VDS_E_CACHE_CORRUPT - return "The service's cache has become corrupt."; - case 0x80042557: // VDS_E_CANNOT_CLEAR_VOLUME_FLAG - return "The specified volume flag cannot be cleared."; - case 0x80042558: // VDS_E_DISK_BEING_CLEANED - return "The operation is not allowed on a disk that is in the process of being cleaned."; - case 0x8004255A: // VDS_E_DISK_REMOVEABLE - return "The operation is not supported on removable media."; - case 0x8004255B: // VDS_E_DISK_REMOVEABLE_NOT_EMPTY - return "The operation is not supported on a non-empty removable disk."; - case 0x8004255C: // VDS_E_DRIVE_LETTER_NOT_FREE - return "The specified drive letter is not free to be assigned."; - case 0x8004255E: // VDS_E_INVALID_DRIVE_LETTER - return "The specified drive letter is not valid."; - case 0x8004255F: // VDS_E_INVALID_DRIVE_LETTER_COUNT - return "The specified number of drive letters to retrieve is not valid."; - case 0x80042560: // VDS_E_INVALID_FS_FLAG - return "The specified file system flag is not valid."; - case 0x80042561: // VDS_E_INVALID_FS_TYPE - return "The specified file system is not valid."; - case 0x80042562: // VDS_E_INVALID_OBJECT_TYPE - return "The specified object type is not valid."; - case 0x80042563: // VDS_E_INVALID_PARTITION_LAYOUT - return "The specified partition layout is invalid."; - case 0x80042564: // VDS_E_INVALID_PARTITION_STYLE - return "VDS only supports MBR or GPT partition style disks."; - case 0x80042565: // VDS_E_INVALID_PARTITION_TYPE - return "The specified partition type is not valid for this operation."; - case 0x80042566: // VDS_E_INVALID_PROVIDER_CLSID - case 0x80042567: // VDS_E_INVALID_PROVIDER_ID - case 0x8004256A: // VDS_E_INVALID_PROVIDER_VERSION_GUID - return "A NULL GUID was passed to the provider."; - case 0x80042568: // VDS_E_INVALID_PROVIDER_NAME - return "The specified provider name is invalid."; - case 0x80042569: // VDS_E_INVALID_PROVIDER_TYPE - return "The specified provider type is invalid."; - case 0x8004256B: // VDS_E_INVALID_PROVIDER_VERSION_STRING - return "The specified provider version string is invalid."; - case 0x8004256C: // VDS_E_INVALID_QUERY_PROVIDER_FLAG - return "The specified query provider flag is invalid."; - case 0x8004256D: // VDS_E_INVALID_SERVICE_FLAG - return "The specified service flag is invalid."; - case 0x8004256E: // VDS_E_INVALID_VOLUME_FLAG - return "The specified volume flag is invalid."; - case 0x8004256F: // VDS_E_PARTITION_NOT_OEM - return "The operation is only supported on an OEM, ESP, or unknown partition."; - case 0x80042570: // VDS_E_PARTITION_PROTECTED - return "Cannot delete a protected partition without the force protected parameter set."; - case 0x80042571: // VDS_E_PARTITION_STYLE_MISMATCH - return "The specified partition style is not the same as the disk's partition style."; - case 0x80042572: // VDS_E_PROVIDER_INTERNAL_ERROR - return "An internal error has occurred in the provider."; - case 0x80042575: // VDS_E_UNRECOVERABLE_PROVIDER_ERROR - return "An unrecoverable error occurred in the provider."; - case 0x80042576: // VDS_E_VOLUME_HIDDEN - return "Cannot assign a mount point to a hidden volume."; - case 0x00042577: // VDS_S_DISMOUNT_FAILED - case 0x00042578: // VDS_S_REMOUNT_FAILED - return "Failed to dismount/remount the volume after setting the volume flags."; - case 0x80042579: // VDS_E_FLAG_ALREADY_SET - return "Cannot set the specified flag as revert-on-close because it is already set."; - case 0x8004257B: // VDS_E_DISTINCT_VOLUME - return "The input volume id cannot be the id of the volume that is the target of the operation."; - case 0x00042583: // VDS_S_FS_LOCK - return "Failed to obtain a file system lock."; - case 0x80042584: // VDS_E_READONLY - return "The volume is read only."; - case 0x80042585: // VDS_E_INVALID_VOLUME_TYPE - return "The volume type is invalid for this operation."; - case 0x80042588: // VDS_E_VOLUME_MIRRORED - return "This operation is not supported on a mirrored volume."; - case 0x80042589: // VDS_E_VOLUME_SIMPLE_SPANNED - return "The operation is only supported on simple or spanned volumes."; - case 0x8004258C: // VDS_E_PARTITION_MSR - case 0x8004258D: // VDS_E_PARTITION_LDM - return "The operation is not supported on this type of partitions."; - case 0x0004258E: // VDS_S_WINPE_BOOTENTRY - return "The boot entries cannot be updated automatically on WinPE."; - case 0x8004258F: // VDS_E_ALIGN_NOT_A_POWER_OF_TWO - return "The alignment is not a power of two."; - case 0x80042590: // VDS_E_ALIGN_IS_ZERO - return "The alignment is zero."; - case 0x80042593: // VDS_E_FS_NOT_DETERMINED - return "The default file system could not be determined."; - case 0x80042595: // VDS_E_DISK_NOT_OFFLINE - return "This disk is already online."; - case 0x80042596: // VDS_E_FAILED_TO_ONLINE_DISK - return "The online operation failed."; - case 0x80042597: // VDS_E_FAILED_TO_OFFLINE_DISK - return "The offline operation failed."; - case 0x80042598: // VDS_E_BAD_REVISION_NUMBER - return "The operation could not be completed because the specified revision number is not supported."; - case 0x00042700: // VDS_S_NAME_TRUNCATED - return "The name was set successfully but had to be truncated."; - case 0x80042701: // VDS_E_NAME_NOT_UNIQUE - return "The specified name is not unique."; - case 0x8004270F: // VDS_E_NO_DISK_PATHNAME - return "The disk's path could not be retrieved. Some operations on the disk might fail."; - case 0x80042711: // VDS_E_NO_VOLUME_PATHNAME - return "The path could not be retrieved for one or more volumes."; - case 0x80042712: // VDS_E_PROVIDER_CACHE_OUTOFSYNC - return "The provider's cache is not in sync with the driver cache."; - case 0x80042713: // VDS_E_NO_IMPORT_TARGET - return "No import target was set for the subsystem."; - case 0x00042714: // VDS_S_ALREADY_EXISTS - return "The object already exists."; - case 0x00042715: // VDS_S_PROPERTIES_INCOMPLETE - return "Some, but not all, of the properties were successfully retrieved."; - case 0x80042803: // VDS_E_UNABLE_TO_FIND_BOOT_DISK - return "Volume disk extent information could not be retrieved for the boot volume."; - case 0x80042807: // VDS_E_BOOT_DISK - return "Disk attributes cannot be changed on the boot disk."; - case 0x00042808: // VDS_S_DISK_MOUNT_FAILED - case 0x00042809: // VDS_S_DISK_DISMOUNT_FAILED - return "One or more of the volumes on the disk could not be mounted/dismounted."; - case 0x8004280A: // VDS_E_DISK_IS_OFFLINE - case 0x8004280B: // VDS_E_DISK_IS_READ_ONLY - return "The operation cannot be performed on a disk that is offline or read-only."; - case 0x8004280C: // VDS_E_PAGEFILE_DISK - case 0x8004280D: // VDS_E_HIBERNATION_FILE_DISK - case 0x8004280E: // VDS_E_CRASHDUMP_DISK - return "The operation cannot be performed on a disk that contains a pagefile, hibernation or crashdump volume."; - case 0x8004280F: // VDS_E_UNABLE_TO_FIND_SYSTEM_DISK - return "A system error occurred while retrieving the system disk information."; - case 0x80042810: // VDS_E_INCORRECT_SYSTEM_VOLUME_EXTENT_INFO - return "Multiple disk extents reported for the system volume - system error."; - case 0x80042811: // VDS_E_SYSTEM_DISK - return "Disk attributes cannot be changed on the current system disk or BIOS disk 0."; - case 0x80042823: // VDS_E_SECTOR_SIZE_ERROR - return "The sector size MUST be non-zero, a power of 2, and less than the maximum sector size."; - case 0x80042907: // VDS_E_SUBSYSTEM_ID_IS_NULL - return "The provider returned a NULL subsystem identification string."; - case 0x8004290C: // VDS_E_REBOOT_REQUIRED - return "A reboot is required before any further operations are initiated."; - case 0x8004290D: // VDS_E_VOLUME_GUID_PATHNAME_NOT_ALLOWED - return "Volume GUID pathnames are not valid input to this method."; - case 0x8004290E: // VDS_E_BOOT_PAGEFILE_DRIVE_LETTER - return "Assigning or removing drive letters on the current boot or pagefile volume is not allowed."; - case 0x8004290F: // VDS_E_DELETE_WITH_CRITICAL - return "Delete is not allowed on a critical volume."; - case 0x80042910: // VDS_E_CLEAN_WITH_DATA - case 0x80042911: // VDS_E_CLEAN_WITH_OEM - return "The FORCE parameter MUST be set to TRUE in order to clean a disk that contains a data or OEM volume."; - case 0x80042912: // VDS_E_CLEAN_WITH_CRITICAL - return "Clean is not allowed on a critical disk."; - case 0x80042913: // VDS_E_FORMAT_CRITICAL - return "Format is not allowed on a critical volume."; - case 0x80042914: // VDS_E_NTFS_FORMAT_NOT_SUPPORTED - case 0x80042915: // VDS_E_FAT32_FORMAT_NOT_SUPPORTED - case 0x80042916: // VDS_E_FAT_FORMAT_NOT_SUPPORTED - return "The requested file system format is not supported on this volume."; - case 0x80042917: // VDS_E_FORMAT_NOT_SUPPORTED - return "The volume is not formattable."; - case 0x80042918: // VDS_E_COMPRESSION_NOT_SUPPORTED - return "The specified file system does not support compression."; - default: - return NULL; - } -} - -static const char* GetVimError(DWORD error_code) -{ - switch (error_code) { - case 0xC1420127: - return "The specified image in the specified wim is already mounted for read and write access."; - default: - return NULL; - } -} - -// Convert a windows error to human readable string +// Convert a Windows error to human readable string +// One really has to wonder why the hell FormatMessage() was designed not to +// handle FORMAT_MESSAGE_FROM_HMODULE automatically according to the facility... const char *WindowsErrorString(void) { static char err_string[256] = { 0 }; DWORD size, presize; DWORD error_code, format_error; + HANDLE hModule = NULL; error_code = GetLastError(); - // Check for VDS error codes - if ((HRESULT_FACILITY(error_code) == FACILITY_ITF) && (GetVdsError(error_code) != NULL)) { - static_sprintf(err_string, "[0x%08lX] %s", error_code, GetVdsError(error_code)); - return err_string; - } - if ((HRESULT_FACILITY(error_code) == FACILITY_WIM) && (GetVimError(error_code) != NULL)) { - static_sprintf(err_string, "[0x%08lX] %s", error_code, GetVimError(error_code)); - return err_string; + // Check for specific facility error codes + switch (HRESULT_FACILITY(error_code)) { + case FACILITY_HTTP: + hModule = GetModuleHandleA("wininet.dll"); + break; + case FACILITY_ITF: + hModule = GetModuleHandleA("vdsutil.dll"); + break; + case FACILITY_WIM: + hModule = GetModuleHandleA("wimgapi.dll"); + break; + default: + break; } static_sprintf(err_string, "[0x%08lX] ", error_code); presize = (DWORD)strlen(err_string); - size = FormatMessageU(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + size = FormatMessageU(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | + ((hModule != NULL) ? FORMAT_MESSAGE_FROM_HMODULE : 0), hModule, HRESULT_CODE(error_code), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), &err_string[presize], (DWORD)(sizeof(err_string)-strlen(err_string)), NULL); if (size == 0) { format_error = GetLastError(); if ((format_error) && (format_error != ERROR_MR_MID_NOT_FOUND) && (format_error != ERROR_MUI_FILE_NOT_LOADED)) - static_sprintf(err_string, "Windows error code 0x%08lX (FormatMessage error code 0x%08lX)", + static_sprintf(err_string, "[0x%08lX] (FormatMessage error code 0x%08lX)", error_code, format_error); else - static_sprintf(err_string, "Windows error code 0x%08lX", error_code); + static_sprintf(err_string, "[0x%08lX] (No Windows Error String)", error_code); } else { // Microsoft may suffix CRLF to error messages, which we need to remove... assert(presize > 2); From 98725a0d5f49ad36be1ddf89bd362fbaf2f025f4 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 8 Sep 2023 16:57:39 +0100 Subject: [PATCH 21/53] [misc] fix improper processing of net related errors * With thanks to @Wack0 * Also silence a Coverity warning --- src/rufus.rc | 10 +++++----- src/stdio.c | 9 +++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/rufus.rc b/src/rufus.rc index 133e3e0f..693165dd 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2080" +CAPTION "Rufus 4.3.2081" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2080,0 - PRODUCTVERSION 4,3,2080,0 + FILEVERSION 4,3,2081,0 + PRODUCTVERSION 4,3,2081,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2080" + VALUE "FileVersion", "4.3.2081" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2080" + VALUE "ProductVersion", "4.3.2081" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index fe80dc0f..95ba5634 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -233,8 +234,11 @@ const char *WindowsErrorString(void) error_code = GetLastError(); // Check for specific facility error codes switch (HRESULT_FACILITY(error_code)) { - case FACILITY_HTTP: - hModule = GetModuleHandleA("wininet.dll"); + case FACILITY_NULL: + // Special case for internet related errors, that don't actually have a facility + // set but still require a hModule into wininet to display the messages. + if ((error_code >= INTERNET_ERROR_BASE) && (error_code <= INTERNET_ERROR_LAST)) + hModule = GetModuleHandleA("wininet.dll"); break; case FACILITY_ITF: hModule = GetModuleHandleA("vdsutil.dll"); @@ -248,6 +252,7 @@ const char *WindowsErrorString(void) static_sprintf(err_string, "[0x%08lX] ", error_code); presize = (DWORD)strlen(err_string); + // coverity[var_deref_model] size = FormatMessageU(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | ((hModule != NULL) ? FORMAT_MESSAGE_FROM_HMODULE : 0), hModule, HRESULT_CODE(error_code), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), From 99bffe836424bec147972b3691898a4036083284 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 8 Sep 2023 17:13:30 +0100 Subject: [PATCH 22/53] [misc] add '.wic' to the list of default image extensions * '.wic' are DD images used by the Yocto project. * Why Yocto chose to use their own extension instead of using the de-facto '.img' is beyond me but hey... * Also update GitHub Actions dependencies to latest. * Closes #2319. --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/coverity.yml | 4 ++-- .github/workflows/mingw.yml | 2 +- .github/workflows/vs2022.yml | 2 +- src/rufus.c | 2 +- src/rufus.rc | 10 +++++----- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 758d00fe..6fc37b76 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 @@ -48,7 +48,7 @@ jobs: languages: cpp - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v1.1 with: msbuild-architecture: x64 diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index f2f93327..a04a4c74 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive @@ -44,7 +44,7 @@ jobs: run: echo "${{github.workspace}}/cov-analysis-win64/bin" >> $GITHUB_PATH - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v1.1 - name: Build with Coverity run: | diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index f6201387..39779c9c 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -53,7 +53,7 @@ jobs: upx - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive diff --git a/.github/workflows/vs2022.yml b/.github/workflows/vs2022.yml index 66f2d5bb..82ac2bde 100644 --- a/.github/workflows/vs2022.yml +++ b/.github/workflows/vs2022.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive diff --git a/src/rufus.c b/src/rufus.c index 036b0cee..f3eda853 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -2600,7 +2600,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA img_provided = FALSE; // One off thing... } else { char* old_image_path = image_path; - char extensions[128] = "*.iso;*.img;*.vhd;*.vhdx;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wim;*.esd;*.vtsi"; + char extensions[128] = "*.iso;*.img;*.vhd;*.vhdx;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wic;*.wim;*.esd;*.vtsi"; if (has_ffu_support) strcat(extensions, ";*.ffu"); // If declared globaly, lmprintf(MSG_280) would be called on each message... diff --git a/src/rufus.rc b/src/rufus.rc index 693165dd..787e29cb 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2081" +CAPTION "Rufus 4.3.2082" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2081,0 - PRODUCTVERSION 4,3,2081,0 + FILEVERSION 4,3,2082,0 + PRODUCTVERSION 4,3,2082,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2081" + VALUE "FileVersion", "4.3.2082" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2081" + VALUE "ProductVersion", "4.3.2082" END END BLOCK "VarFileInfo" From 1fc790295c32e1b8ca24e714da239a10f76938f6 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 8 Sep 2023 17:33:25 +0100 Subject: [PATCH 23/53] [efi] update UEFI:NTFS to latest * Add Windows bootmgr detection to report a more explicit error on security issues. --- res/uefi/readme.txt | 2 +- res/uefi/uefi-ntfs.img | Bin 1048576 -> 1048576 bytes src/rufus.rc | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/res/uefi/readme.txt b/res/uefi/readme.txt index 5aab7420..c3ae32d7 100644 --- a/res/uefi/readme.txt +++ b/res/uefi/readme.txt @@ -17,7 +17,7 @@ o Non Secure Boot signed exFAT UEFI drivers from EfiFs [2]. because they are licensed under GPLv3, cannot be Secure Boot signed. o Secure Boot signed UEFI:NTFS bootloader binaries [3]. - These drivers are the exact same as the binaries from release 2.2, except for + These drivers are the exact same as the binaries from release 2.3, except for the addition of Microsoft's Secure Boot signature. Note that, per Microsoft's current Secure Boot signing policies, the 32-bit ARM bootloader (bootarm.efi) is not Secure Boot signed. diff --git a/res/uefi/uefi-ntfs.img b/res/uefi/uefi-ntfs.img index 21765cb93ac43a0801a476fce2d33b8f8d175deb..a0d12e81b0d736603525a2b2d53c15d5c6032754 100644 GIT binary patch delta 45979 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 KK8o}_{XYO>%(o!` delta 47993 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%0DnevH4D&PKqQPZu52pcy9}4RbcL#19oF1 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 Sx1xw9UqmR&Kbzaw{r>>0X%o-@ diff --git a/src/rufus.rc b/src/rufus.rc index 787e29cb..66147440 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2082" +CAPTION "Rufus 4.3.2083" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2082,0 - PRODUCTVERSION 4,3,2082,0 + FILEVERSION 4,3,2083,0 + PRODUCTVERSION 4,3,2083,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2082" + VALUE "FileVersion", "4.3.2083" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2082" + VALUE "ProductVersion", "4.3.2083" END END BLOCK "VarFileInfo" From 8859c5954887e7e57fced63d4650c9c600b6e5f1 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sat, 23 Sep 2023 17:07:55 +0100 Subject: [PATCH 24/53] [iso] count Rock Ridge duplicated files into the total size to process * Duplicated symlinked Rock Ridge files were not counted into the total size needed to process the image and as a result, progress could go over 100% when extracting data (e.g. debian-live-12.1.0-amd64-lxqt.iso). * Fix this by adding the duplicated files twice in the total block size. --- src/iso.c | 13 +++++++++++-- src/rufus.rc | 10 +++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/iso.c b/src/iso.c index 405b4d16..75831516 100644 --- a/src/iso.c +++ b/src/iso.c @@ -708,7 +708,8 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) EXTRACT_PROPS props; BOOL is_symlink, is_identical, free_p_statbuf = FALSE; int length, r = 1; - char tmp[128], psz_fullpath[MAX_PATH], *psz_basename = NULL, *psz_sanpath = NULL; + char psz_fullpath[MAX_PATH], *psz_basename = NULL, *psz_sanpath = NULL; + char tmp[128], target_path[256]; const char *psz_iso_name = &psz_fullpath[strlen(psz_extract_dir)]; unsigned char buf[ISO_BLOCKSIZE]; CdioListNode_t* p_entnode; @@ -792,6 +793,15 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) } else { file_length = p_statbuf->total_size; if (check_iso_props(psz_path, file_length, psz_basename, psz_fullpath, &props)) { + // Add symlink duplicated files to total_size at scantime + if (is_symlink && (file_length == 0) && (strcmp(psz_path, "/firmware") == 0)) { + static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); + iso9660_stat_t *p_statbuf2 = iso9660_ifs_stat_translate(p_iso, target_path); + if (p_statbuf2 != NULL) { + total_blocks += (p_statbuf2->total_size + ISO_BLOCKSIZE - 1) / ISO_BLOCKSIZE; + iso9660_stat_free(p_statbuf2); + } + } continue; } if (!is_symlink) @@ -822,7 +832,6 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) } else if (strcmp(psz_path, "/firmware") == 0) { // Special handling for ISOs that use symlinks for /firmware/ (e.g. Debian non-free) // TODO: Do we want to do this for all file symlinks? - char target_path[256]; static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); p_statbuf = iso9660_ifs_stat_translate(p_iso, target_path); if (p_statbuf != NULL) { diff --git a/src/rufus.rc b/src/rufus.rc index 66147440..4b757140 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2083" +CAPTION "Rufus 4.3.2084" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2083,0 - PRODUCTVERSION 4,3,2083,0 + FILEVERSION 4,3,2084,0 + PRODUCTVERSION 4,3,2084,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2083" + VALUE "FileVersion", "4.3.2084" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2083" + VALUE "ProductVersion", "4.3.2084" END END BLOCK "VarFileInfo" From 45a5f22d436b78c64cf42aee2709663cb62f8f4b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 10 Oct 2023 22:22:45 +0100 Subject: [PATCH 25/53] [process] move the search for conflicting process to a background thread * Removes the annoyance of having to wait for the process search to complete before media creation can start. * Also update the "Process Hacker" references to its new "System Informer" name. --- src/drive.c | 7 +- src/license.h | 6 +- src/process.c | 733 ++++++++++++++++++++++++++++++++++---------------- src/process.h | 29 +- src/rufus.c | 126 ++------- src/rufus.h | 10 +- src/rufus.rc | 10 +- src/stdio.c | 4 +- 8 files changed, 568 insertions(+), 357 deletions(-) diff --git a/src/drive.c b/src/drive.c index e015a287..b432a5ef 100644 --- a/src/drive.c +++ b/src/drive.c @@ -172,8 +172,7 @@ static HANDLE GetHandle(char* Path, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWr uprintf("Warning: Could not obtain exclusive rights. Retrying with write sharing enabled..."); bWriteShare = TRUE; // Try to report the process that is locking the drive - // We also use bit 6 as a flag to indicate that SearchProcess was called. - access_mask = SearchProcess(DevPath, SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE) | 0x40; + access_mask = GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x07, FALSE); } Sleep(DRIVE_ACCESS_TIMEOUT / DRIVE_ACCESS_RETRIES); } @@ -203,9 +202,7 @@ static HANDLE GetHandle(char* Path, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWr uprintf("Could not lock access to %s: %s", Path, WindowsErrorString()); // See if we can report the processes are accessing the drive if (!IS_ERROR(FormatStatus) && (access_mask == 0)) - // Double the search process timeout here, as Windows is so bloated with processes - // that 10 seconds has become way too small to get much of any results these days... - access_mask = SearchProcess(DevPath, 2 * SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE); + access_mask = GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x07, FALSE); // Try to continue if the only access rights we saw were for read-only if ((access_mask & 0x07) != 0x01) safe_closehandle(hDrive); diff --git a/src/license.h b/src/license.h index f01d407b..378e59a6 100644 --- a/src/license.h +++ b/src/license.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Licensing Data - * Copyright © 2011-2015 Pete Batard + * Copyright © 2011-2023 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -89,8 +89,8 @@ const char* additional_copyrights = "https://www.codeguru.com/forum/showthread.php?p=1951973\\line\n" "Public Domain\\line\n" "\\line\n" -"Handle search & process enumeration from Process Hacker by wj32 & dmex:\\line\n" -"https://processhacker.sourceforge.io/\\line\n" +"Handle search & process enumeration from System Informer by wj32 & dmex:\\line\n" +"https://systeminformer.sourceforge.io/\\line\n" "GNU General Public License (GPL) v3 or later\\line\n" "\\line\n" "Decompression support from BusyBox/Bled:\\line\n" diff --git a/src/process.c b/src/process.c index 4ffe9478..fe9575dd 100644 --- a/src/process.c +++ b/src/process.c @@ -2,8 +2,8 @@ * Rufus: The Reliable USB Formatting Utility * Process search functionality * - * Modified from Process Hacker: - * https://github.com/processhacker2/processhacker2/ + * Modified from System Informer (a.k.a. Process Hacker): + * https://github.com/winsiderss/systeminformer * Copyright © 2017-2023 Pete Batard * Copyright © 2017 dmex * Copyright © 2009-2016 wj32 @@ -31,6 +31,7 @@ #include #include "rufus.h" +#include "drive.h" #include "process.h" #include "missing.h" #include "msapi_utf8.h" @@ -53,10 +54,10 @@ PF_TYPE_DECL(NTAPI, NTSTATUS, NtAdjustPrivilegesToken, (HANDLE, BOOLEAN, PTOKEN_ PF_TYPE_DECL(NTAPI, NTSTATUS, NtClose, (HANDLE)); static PVOID PhHeapHandle = NULL; -static wchar_t* _wHandleName; -static BOOL _bPartialMatch, _bIgnoreSelf, _bQuiet; -static BYTE access_mask; -extern StrArray BlockingProcess; +static HANDLE hSearchProcessThread = NULL; +static BlockingProcess blocking_process = { 0 }; + +extern StrArray BlockingProcessList; /* * Convert an NT Status to an error message @@ -110,7 +111,6 @@ char* NtStatusError(NTSTATUS Status) { } } - static NTSTATUS PhCreateHeap(VOID) { NTSTATUS status = STATUS_SUCCESS; @@ -155,7 +155,6 @@ static NTSTATUS PhDestroyHeap(VOID) * \param Size The number of bytes to allocate. * * \return A pointer to the allocated block of memory. - * */ static PVOID PhAllocate(SIZE_T Size) { @@ -173,7 +172,6 @@ static PVOID PhAllocate(SIZE_T Size) * Frees a block of memory allocated with PhAllocate(). * * \param Memory A pointer to a block of memory. - * */ static VOID PhFree(PVOID Memory) { @@ -423,10 +421,18 @@ out: return wcmdline; } + +/** + * The search process thread. + * Note: Avoid using uprintf statements here, as it may lock the thread. + * + * \param param The thread parameters. + * + * \return A thread exit code. + */ static DWORD WINAPI SearchProcessThread(LPVOID param) { - const char *access_rights_str[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" }; - char tmp[MAX_PATH]; + BOOL bInitSuccess = FALSE; NTSTATUS status = STATUS_SUCCESS; PSYSTEM_HANDLE_INFORMATION_EX handles = NULL; POBJECT_NAME_INFORMATION buffer = NULL; @@ -434,276 +440,533 @@ static DWORD WINAPI SearchProcessThread(LPVOID param) ULONG_PTR pid[2]; ULONG_PTR last_access_denied_pid = 0; ULONG bufferSize; - USHORT wHandleNameLen; + wchar_t** wHandleName = NULL; + USHORT* wHandleNameLen = NULL; HANDLE dupHandle = NULL; HANDLE processHandle = NULL; - BOOLEAN bFound = FALSE, bGotCmdLine, verbose = !_bQuiet; + HANDLE hLock = NULL; + BOOLEAN bFound = FALSE, bGotCmdLine; ULONG access_rights = 0; DWORD size; - char cmdline[MAX_PATH] = { 0 }; wchar_t wexe_path[MAX_PATH], *wcmdline; - int cur_pid; + uint64_t start_time; + char cmdline[MAX_PATH] = { 0 }, tmp[64]; + int cur_pid, j, nHandles = 0; - PF_INIT_OR_SET_STATUS(NtQueryObject, Ntdll); - PF_INIT_OR_SET_STATUS(NtDuplicateObject, NtDll); - PF_INIT_OR_SET_STATUS(NtClose, NtDll); + PF_INIT_OR_OUT(NtQueryObject, Ntdll); + PF_INIT_OR_OUT(NtDuplicateObject, NtDll); + PF_INIT_OR_OUT(NtClose, NtDll); - StrArrayClear(&BlockingProcess); + // Initialize the blocking process struct + memset(&blocking_process, 0, sizeof(blocking_process)); + hLock = CreateMutexA(NULL, TRUE, NULL); + if (hLock == NULL) + goto out; + blocking_process.hStart = CreateEventA(NULL, TRUE, FALSE, NULL); + if (blocking_process.hStart == NULL) + goto out; + if (!ReleaseMutex(hLock)) + goto out; + // Only assign the mutex handle once our init is complete + blocking_process.hLock = hLock; - if (NT_SUCCESS(status)) - status = PhCreateHeap(); + if (!NT_SUCCESS(PhCreateHeap())) + goto out; - if (NT_SUCCESS(status)) - status = PhEnumHandlesEx(&handles); - - if (!NT_SUCCESS(status)) { - uprintf("Warning: Could not enumerate process handles: %s", NtStatusError(status)); + // Wait until we are signaled active one way or another + if (!blocking_process.bActive && + (WaitForSingleObject(blocking_process.hStart, INFINITE) != WAIT_OBJECT_0)) { goto out; } - pid[0] = (ULONG_PTR)0; - cur_pid = 1; - - wHandleNameLen = (USHORT)wcslen(_wHandleName); - - bufferSize = 0x200; - buffer = PhAllocate(bufferSize); - if (buffer == NULL) - goto out; - - for (i = 0; ; i++) { - ULONG attempts = 8; - PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = NULL; - - // We are seeing reports of application crashes due to access - // violation exceptions here, so, since this is not critical code, - // we add an exception handler to ignore them. - TRY_AND_HANDLE( - EXCEPTION_ACCESS_VIOLATION, - { handleInfo = (i < handles->NumberOfHandles) ? &handles->Handles[i] : NULL; }, - { continue; } - ); - - if ((dupHandle != NULL) && (processHandle != NtCurrentProcess())) { - pfNtClose(dupHandle); - dupHandle = NULL; + bInitSuccess = TRUE; + while (blocking_process.bActive) { + // Get a lock to our data + if (WaitForSingleObject(hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) + goto out; + // No handles to check => just sleep for a while + if (blocking_process.nHandles == 0) { + ReleaseMutex(hLock); + Sleep(500); + continue; } - - // Update the current handle's process PID and compare against last - // Note: Be careful about not trying to overflow our list! - TRY_AND_HANDLE( - EXCEPTION_ACCESS_VIOLATION, - { pid[cur_pid] = (handleInfo != NULL) ? handleInfo->UniqueProcessId : -1; }, - { continue; } - ); - - if (pid[0] != pid[1]) { - cur_pid = (cur_pid + 1) % 2; - - // If we're switching process and found a match, print it - if (bFound) { - static_sprintf (tmp, "â— [%06u] %s (%s)", (uint32_t)pid[cur_pid], cmdline, access_rights_str[access_rights & 0x7]); - // tmp may contain a '%' so don't feed it as a naked format string - vuprintf("%s", tmp); - StrArrayAdd(&BlockingProcess, tmp, TRUE); - bFound = FALSE; - access_rights = 0; + // Work on our own copy of the handle names so we don't have to hold the + // mutex for string comparison. Update only if the version has changed. + if (blocking_process.nVersion[0] != blocking_process.nVersion[1]) { + assert(blocking_process.wHandleName != NULL && blocking_process.nHandles != 0); + if (blocking_process.wHandleName == NULL || blocking_process.nHandles == 0) { + ReleaseMutex(hLock); + goto out; } - - // Close the previous handle - if (processHandle != NULL) { - if (processHandle != NtCurrentProcess()) - pfNtClose(processHandle); - processHandle = NULL; + if (wHandleName != NULL) { + for (i = 0; i < nHandles; i++) + free(wHandleName[i]); + free(wHandleName); } - } - - CHECK_FOR_USER_CANCEL; - - // Exit loop condition - if (i >= handles->NumberOfHandles) - break; - - if (handleInfo == NULL) - continue; - - // Don't bother with processes we can't access - if (handleInfo->UniqueProcessId == last_access_denied_pid) - continue; - - // Filter out handles that aren't opened with Read (bit 0), Write (bit 1) or Execute (bit 5) access - if ((handleInfo->GrantedAccess & 0x23) == 0) - continue; - - // Open the process to which the handle we are after belongs, if not already opened - if (pid[0] != pid[1]) { - status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - (HANDLE)handleInfo->UniqueProcessId); - // There exists some processes we can't access - if (!NT_SUCCESS(status)) { - uuprintf("SearchProcess: Could not open process %ld: %s", - handleInfo->UniqueProcessId, NtStatusError(status)); - processHandle = NULL; - if (status == STATUS_ACCESS_DENIED) { - last_access_denied_pid = handleInfo->UniqueProcessId; + safe_free(wHandleNameLen); + nHandles = blocking_process.nHandles; + wHandleName = calloc(nHandles, sizeof(wchar_t*)); + if (wHandleName == NULL) { + ReleaseMutex(hLock); + goto out; + } + wHandleNameLen = calloc(nHandles, sizeof(USHORT)); + if (wHandleNameLen == NULL) { + ReleaseMutex(hLock); + goto out; + } + for (i = 0; i < nHandles; i++) { + wHandleName[i] = wcsdup(blocking_process.wHandleName[i]); + wHandleNameLen[i] = (USHORT)wcslen(blocking_process.wHandleName[i]); + if (wHandleName[i] == NULL) { + ReleaseMutex(hLock); + goto out; } - continue; } + blocking_process.nVersion[1] = blocking_process.nVersion[0]; + blocking_process.nPass = 0; + } + ReleaseMutex(hLock); + + start_time = GetTickCount64(); + // Get a list of all opened handles + if (!NT_SUCCESS(PhEnumHandlesEx(&handles))) { + Sleep(1000); + continue; } - // Now duplicate this handle onto our own process, so that we can access its properties - if (processHandle == NtCurrentProcess()) { - if (_bIgnoreSelf) + pid[0] = (ULONG_PTR)0; + cur_pid = 1; + bufferSize = 0x200; + buffer = PhAllocate(bufferSize); + if (buffer == NULL) + goto out; + + for (i = 0; blocking_process.bActive; i++) { + ULONG attempts = 8; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = NULL; + + // We are seeing reports of application crashes due to access + // violation exceptions here, so, since this is not critical code, + // we add an exception handler to ignore them. + TRY_AND_HANDLE( + EXCEPTION_ACCESS_VIOLATION, + { handleInfo = (i < handles->NumberOfHandles) ? &handles->Handles[i] : NULL; }, + { continue; } + ); + + if ((dupHandle != NULL) && (processHandle != NtCurrentProcess())) { + pfNtClose(dupHandle); + dupHandle = NULL; + } + + // Update the current handle's process PID and compare against last + // Note: Be careful about not trying to overflow our list! + TRY_AND_HANDLE( + EXCEPTION_ACCESS_VIOLATION, + { pid[cur_pid] = (handleInfo != NULL) ? handleInfo->UniqueProcessId : -1; }, + { continue; } + ); + + if (pid[0] != pid[1]) { + cur_pid = (cur_pid + 1) % 2; + + // If we're switching process and found a match, store it + if (bFound) { + if (WaitForSingleObject(hLock, SEARCH_PROCESS_LOCK_TIMEOUT) == WAIT_OBJECT_0) { + ProcessEntry* pe = blocking_process.Process; + // Prune entries that have not been detected for a few passes + for (j = 0; j < MAX_BLOCKING_PROCESSES; j++) + if (pe[j].pid != 0 && pe[j].seen_on_pass < blocking_process.nPass - 1) + pe[j].pid = 0; + // Try to reuse an existing entry for the current pid + for (j = 0; (j < MAX_BLOCKING_PROCESSES) && (pe[j].pid != pid[cur_pid]); j++); + if (j == MAX_BLOCKING_PROCESSES) + for (j = 0; (j < MAX_BLOCKING_PROCESSES) && (pe[j].pid != 0); j++); + if (j != MAX_BLOCKING_PROCESSES) { + pe[j].pid = pid[cur_pid]; + pe[j].access_rights = access_rights & 0x7; + pe[j].seen_on_pass = blocking_process.nPass; + static_strcpy(pe[j].cmdline, cmdline); + } else if (usb_debug) { + OutputDebugStringA("SearchProcessThread: No empty slot!\n"); + } + ReleaseMutex(hLock); + } + bFound = FALSE; + access_rights = 0; + } + + // Close the previous handle + if (processHandle != NULL) { + if (processHandle != NtCurrentProcess()) + pfNtClose(processHandle); + processHandle = NULL; + } + } + + // Exit thread condition + if (!blocking_process.bActive) + goto out; + + // Exit loop condition + if (i >= handles->NumberOfHandles) + break; + + if (handleInfo == NULL) + continue; + + // Don't bother with processes we can't access + if (handleInfo->UniqueProcessId == last_access_denied_pid) + continue; + + // Filter out handles that aren't opened with Read (bit 0), Write (bit 1) or Execute (bit 5) access + if ((handleInfo->GrantedAccess & 0x23) == 0) + continue; + + // Open the process to which the handle we are after belongs, if not already opened + if (pid[0] != pid[1]) { + status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + (HANDLE)handleInfo->UniqueProcessId); + // There exists some processes we can't access + if (!NT_SUCCESS(status)) { + processHandle = NULL; + if (status == STATUS_ACCESS_DENIED) { + last_access_denied_pid = handleInfo->UniqueProcessId; + } + continue; + } + } + + // Now duplicate this handle onto our own process, so that we can access its properties + if (processHandle == NtCurrentProcess()) continue; - dupHandle = (HANDLE)handleInfo->HandleValue; - } else { status = pfNtDuplicateObject(processHandle, (HANDLE)handleInfo->HandleValue, NtCurrentProcess(), &dupHandle, 0, 0, 0); if (!NT_SUCCESS(status)) continue; - } - // Filter non-storage handles. We're not interested in them and they make NtQueryObject() freeze - if (GetFileType(dupHandle) != FILE_TYPE_DISK) - continue; + // Filter non-storage handles. We're not interested in them and they make NtQueryObject() freeze + if (GetFileType(dupHandle) != FILE_TYPE_DISK) + continue; - // A loop is needed because the I/O subsystem likes to give us the wrong return lengths... - do { - ULONG returnSize; - // TODO: We might potentially still need a timeout on ObjectName queries, as PH does... - status = pfNtQueryObject(dupHandle, ObjectNameInformation, buffer, bufferSize, &returnSize); - if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH || - status == STATUS_BUFFER_TOO_SMALL) { - uuprintf("SearchProcess: Realloc from %d to %d", bufferSize, returnSize); - bufferSize = returnSize; - PhFree(buffer); - buffer = PhAllocate(bufferSize); - } else { - break; + // A loop is needed because the I/O subsystem likes to give us the wrong return lengths... + do { + ULONG returnSize; + // TODO: We might potentially still need a timeout on ObjectName queries, as PH does... + status = pfNtQueryObject(dupHandle, ObjectNameInformation, buffer, bufferSize, &returnSize); + if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH || + status == STATUS_BUFFER_TOO_SMALL) { + bufferSize = returnSize; + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } else { + break; + } + } while (--attempts); + if (!NT_SUCCESS(status)) + continue; + + for (j = 0; j < nHandles; j++) { + // Don't bother comparing if length of our handle string is larger than the current data + if (wHandleNameLen[j] > buffer->Name.Length) + continue; + // Match against our target string(s) + if (wcsncmp(wHandleName[j], buffer->Name.Buffer, wHandleNameLen[j]) == 0) + break; + } + if (j == nHandles) + continue; + bFound = TRUE; + + // Keep a mask of all the access rights being used + access_rights |= handleInfo->GrantedAccess; + // The Executable bit is in a place we don't like => reposition it + if (access_rights & 0x20) + access_rights = (access_rights & 0x03) | 0x04; + access_rights &= 0x07; + + // Where possible, try to get the full command line + bGotCmdLine = FALSE; + size = MAX_PATH; + wcmdline = GetProcessCommandLine(processHandle); + if (wcmdline != NULL) { + bGotCmdLine = TRUE; + wchar_to_utf8_no_alloc(wcmdline, cmdline, sizeof(cmdline)); + free(wcmdline); + } + + // If we couldn't get the full commandline, try to get the executable path + if (!bGotCmdLine) + bGotCmdLine = (GetModuleFileNameExU(processHandle, 0, cmdline, MAX_PATH - 1) != 0); + + // The above may not work on all Windows version, so fall back to QueryFullProcessImageName + if (!bGotCmdLine) { + bGotCmdLine = (QueryFullProcessImageNameW(processHandle, 0, wexe_path, &size) != FALSE); + if (bGotCmdLine) + wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline)); + } + + // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses + // '\Device\Harddisk#\Partition#\' instead drive letters + if (!bGotCmdLine) { + bGotCmdLine = (GetProcessImageFileNameW(processHandle, wexe_path, MAX_PATH) != 0); + if (bGotCmdLine) + wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline)); + } + + // Complete failure => Just craft a default process name that includes the PID + if (!bGotCmdLine) { + static_sprintf(cmdline, "Unknown_Process_%" PRIu64, + (ULONGLONG)handleInfo->UniqueProcessId); } - } while (--attempts); - if (!NT_SUCCESS(status)) { - uuprintf("SearchProcess: NtQueryObject failed for handle %X of process %ld: %s", - handleInfo->HandleValue, handleInfo->UniqueProcessId, NtStatusError(status)); - continue; - } - - // Don't bother comparing if we are looking for full match and the length is different - if ((!_bPartialMatch) && (wHandleNameLen != buffer->Name.Length)) - continue; - - // Likewise, if we are looking for a partial match and the current length is smaller - if ((_bPartialMatch) && (wHandleNameLen > buffer->Name.Length)) - continue; - - // Match against our target string - if (wcsncmp(_wHandleName, buffer->Name.Buffer, wHandleNameLen) != 0) - continue; - - // If we are here, we have a process accessing our target! - bFound = TRUE; - - // Keep a mask of all the access rights being used - access_rights |= handleInfo->GrantedAccess; - // The Executable bit is in a place we don't like => reposition it - if (access_rights & 0x20) - access_rights = (access_rights & 0x03) | 0x04; - access_mask |= (BYTE) (access_rights & 0x7) + 0x80; // Bit 7 is always set if a process was found - - // If this is the very first process we find, print a header - if (cmdline[0] == 0) - vuprintf("WARNING: The following process(es) or service(s) are accessing %S:", _wHandleName); - - // Where possible, try to get the full command line - bGotCmdLine = FALSE; - size = MAX_PATH; - wcmdline = GetProcessCommandLine(processHandle); - if (wcmdline != NULL) { - bGotCmdLine = TRUE; - wchar_to_utf8_no_alloc(wcmdline, cmdline, sizeof(cmdline)); - free(wcmdline); - } - - // If we couldn't get the full commandline, try to get the executable path - if (!bGotCmdLine) - bGotCmdLine = (GetModuleFileNameExU(processHandle, 0, cmdline, MAX_PATH - 1) != 0); - - // The above may not work on all Windows version, so fall back to QueryFullProcessImageName - if (!bGotCmdLine) { - bGotCmdLine = (QueryFullProcessImageNameW(processHandle, 0, wexe_path, &size) != FALSE); - if (bGotCmdLine) - wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline)); - } - - // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses - // '\Device\Harddisk#\Partition#\' instead drive letters - if (!bGotCmdLine) { - bGotCmdLine = (GetProcessImageFileNameW(processHandle, wexe_path, MAX_PATH) != 0); - if (bGotCmdLine) - wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline)); - } - - // Complete failure => Just craft a default process name that includes the PID - if (!bGotCmdLine) { - static_sprintf(cmdline, "Unknown_Process_%" PRIu64, - (ULONGLONG)handleInfo->UniqueProcessId); } + PhFree(buffer); + PhFree(handles); + // We are the only ones updating the counter so no need for lock + blocking_process.nPass++; + // In extended debug mode, notify how much time our search took to the debug facility + static_sprintf(tmp, "Process search run #%d completed in %llu ms\n", + blocking_process.nPass, GetTickCount64() - start_time); + if (usb_debug) + OutputDebugStringA(tmp); + Sleep(1000); } out: - if (cmdline[0] != 0) - vuprintf("You should close these applications before attempting to reformat the drive."); - else - vuprintf("NOTE: Could not identify the process(es) or service(s) accessing %S", _wHandleName); + if (!bInitSuccess) + uprintf("Warning: Could not start process handle enumerator!"); + + if (wHandleName != NULL) { + for (i = 0; i < nHandles; i++) + free(wHandleName[i]); + free(wHandleName); + } + safe_free(wHandleNameLen); - PhFree(buffer); - PhFree(handles); PhDestroyHeap(); + if ((hLock != NULL) && (hLock != INVALID_HANDLE_VALUE) && + (WaitForSingleObject(hLock, 1000) == WAIT_OBJECT_0)) { + blocking_process.hLock = NULL; + blocking_process.bActive = FALSE; + for (i = 0; i < blocking_process.nHandles; i++) + free(blocking_process.wHandleName[i]); + safe_free(blocking_process.wHandleName); + safe_closehandle(blocking_process.hStart); + ReleaseMutex(hLock); + } + safe_closehandle(hLock); + ExitThread(0); } /** - * Search all the processes and list the ones that have a specific handle open. + * Start the process search thread. * - * \param HandleName The name of the handle to look for. - * \param dwTimeOut The maximum amounf of time (ms) that may be spent searching - * \param bPartialMatch Whether partial matches should be allowed. - * \param bIgnoreSelf Whether the current process should be listed. - * \param bQuiet Prints minimal output. + * \return TRUE on success, FALSE otherwise. * - * \return a byte containing the cumulated access rights (f----xwr) from all the handles found - * with bit 7 ('f') also set if at least one process was found. */ -BYTE SearchProcess(char* HandleName, DWORD dwTimeOut, BOOL bPartialMatch, BOOL bIgnoreSelf, BOOL bQuiet) +BOOL StartProcessSearch(void) { - HANDLE handle; - DWORD res = 0; + int i; - _wHandleName = utf8_to_wchar(HandleName); - _bPartialMatch = bPartialMatch; - _bIgnoreSelf = bIgnoreSelf; - _bQuiet = bQuiet; - access_mask = 0x00; + if (hSearchProcessThread != NULL) + return TRUE; - assert(_wHandleName != NULL); - - handle = CreateThread(NULL, 0, SearchProcessThread, NULL, 0, NULL); - if (handle == NULL) { - uprintf("Warning: Unable to create conflicting process search thread"); - goto out; + hSearchProcessThread = CreateThread(NULL, 0, SearchProcessThread, NULL, 0, NULL); + if (hSearchProcessThread == NULL) { + uprintf("Failed to start process search thread: %s", WindowsErrorString()); + return FALSE; } - res = WaitForSingleObjectWithMessages(handle, dwTimeOut); - if (res == WAIT_TIMEOUT) { - // Timeout - kill the thread - TerminateThread(handle, 0); - uprintf("Search for conflicting processes was interrupted due to timeout"); - } else if (res != WAIT_OBJECT_0) { - TerminateThread(handle, 0); - uprintf("Warning: Failed to wait for conflicting process search thread %s", WindowsErrorString()); + SetThreadPriority(SearchProcessThread, THREAD_PRIORITY_LOWEST); + + // Wait until we have hLock + for (i = 0; (i < 50) && (blocking_process.hLock == NULL); i++) + Sleep(100); + if (i >= 50) { + uprintf("Failed to start process search thread: hLock init failure!"); + TerminateThread(hSearchProcessThread, 0); + CloseHandle(hSearchProcessThread); + hSearchProcessThread = NULL; + return FALSE; } + + return TRUE; +} + +/** + * Stop the process search thread.. + * + */ +void StopProcessSearch(void) +{ + if (hSearchProcessThread == NULL) + return; + + // No need for a lock on this one + blocking_process.bActive = FALSE; + if (WaitForSingleObject(hSearchProcessThread, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) { + uprintf("Process search thread did not exit within timeout - forcefully terminating it!"); + TerminateThread(hSearchProcessThread, 0); + CloseHandle(hSearchProcessThread); + } + hSearchProcessThread = NULL; +} + +/** + * Set up the handles that the process search will run against. + * + * \param DeviceNum The device number for the currently selected drive. + * + * \return TRUE on success, FALSE otherwise. + * + */ +BOOL SetProcessSearch(DWORD DeviceNum) +{ + char* PhysicalPath = NULL, DevPath[MAX_PATH]; + char drive_letter[27], drive_name[] = "?:"; + uint32_t i, nHandles = 0; + wchar_t** wHandleName = NULL; + + if (hSearchProcessThread == NULL) { + uprintf("Process search thread is not started!"); + return FALSE; + } + + assert(blocking_process.hLock != NULL); + + // Populate the handle names + wHandleName = calloc(MAX_NUM_HANDLES, sizeof(wchar_t*)); + if (wHandleName == NULL) + return FALSE; + // Physical drive handle name + PhysicalPath = GetPhysicalName(DeviceNum); + if (QueryDosDeviceA(&PhysicalPath[4], DevPath, sizeof(DevPath)) != 0) + wHandleName[nHandles++] = utf8_to_wchar(DevPath); + free(PhysicalPath); + // Logical drive(s) handle name(s) + GetDriveLetters(DeviceNum, drive_letter); + for (i = 0; nHandles < MAX_NUM_HANDLES && drive_letter[i]; i++) { + drive_name[0] = drive_letter[i]; + if (QueryDosDeviceA(drive_name, DevPath, sizeof(DevPath)) != 0) + wHandleName[nHandles++] = utf8_to_wchar(DevPath); + } + if (WaitForSingleObject(blocking_process.hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) { + uprintf("Could not obtain process search lock"); + free(wHandleName); + nHandles = 0; + return FALSE; + } + + if (blocking_process.wHandleName != NULL) + for (i = 0; i < blocking_process.nHandles; i++) + free(blocking_process.wHandleName[i]); + free(blocking_process.wHandleName); + blocking_process.wHandleName = wHandleName; + blocking_process.nHandles = nHandles; + blocking_process.nVersion[0]++; + blocking_process.bActive = TRUE; + if (!SetEvent(blocking_process.hStart)) + uprintf("Could not send start event to process search: %s", WindowsErrorString); + return ReleaseMutex(blocking_process.hLock); +} + +/** + * Check whether the corresponding PID is that of a running process. + * + * \param pid The PID of the process to check. + * + * \return TRUE if the process is detected as currently running, FALSE otherwise. + * + */ +static BOOL IsProcessRunning(uint64_t pid) +{ + HANDLE hProcess = NULL; + DWORD dwExitCode; + BOOL ret = FALSE; + NTSTATUS status; + + PF_INIT_OR_OUT(NtClose, NtDll); + + status = PhOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, (HANDLE)pid); + if (!NT_SUCCESS(status) || (hProcess == NULL)) + return FALSE; + if (GetExitCodeProcess(hProcess, &dwExitCode)) + ret = (dwExitCode == STILL_ACTIVE); + pfNtClose(hProcess); out: - free(_wHandleName); - return access_mask; + return ret; +} + +/** + * Report the result of the process search. + * + * \param timeout Maximum time that should be spend in this function before aborting (in ms). + * \param access_mask Desired access mask (x = 0x4, w = 0x2, r = 0x1). + * \param bIgnoreStaleProcesses Whether to ignore processes that are no longer active. + * + * \return The combined access mask of all the matching processes found. + * The BlockingProcessList string array is also updated with the results. + * + */ +BYTE GetProcessSearch(uint32_t timeout, uint8_t access_mask, BOOL bIgnoreStaleProcesses) +{ + const char* access_rights_str[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" }; + char tmp[MAX_PATH]; + int i, j; + uint32_t elapsed = 0; + BYTE returned_mask = 0; + + StrArrayClear(&BlockingProcessList); + if (hSearchProcessThread == NULL) { + uprintf("Process search thread is not started!"); + return 0; + } + + assert(blocking_process.hLock != NULL); + if (blocking_process.hLock == NULL) + return 0; + +retry: + if (WaitForSingleObject(blocking_process.hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) + return 0; + + // Make sure we have at least one pass with the current handles in order to report them. + // If we have a timeout, wait until timeout has elapsed to give up. + if ((blocking_process.nVersion[0] != blocking_process.nVersion[1]) || + (blocking_process.nPass < 1)) { + ReleaseMutex(blocking_process.hLock); + if (elapsed < timeout) { + Sleep(100); + elapsed += 100; + goto retry; + } + if (timeout != 0) + uprintf("Timeout while retrieving conflicting process list"); + return 0; + } + + for (i = 0, j = 0; i < MAX_BLOCKING_PROCESSES; i++) { + if (blocking_process.Process[i].pid == 0) + continue; + if ((blocking_process.Process[i].access_rights & access_mask) == 0) + continue; + if (bIgnoreStaleProcesses && !IsProcessRunning(blocking_process.Process[i].pid)) + continue; + returned_mask |= blocking_process.Process[i].access_rights; + static_sprintf(tmp, "â— [%llu] %s (%s)", blocking_process.Process[i].pid, blocking_process.Process[i].cmdline, + access_rights_str[blocking_process.Process[i].access_rights & 0x7]); + StrArrayAdd(&BlockingProcessList, tmp, TRUE); + if (j++ == 0) + uprintf("WARNING: The following application(s) or service(s) are accessing the drive:"); + // tmp may contain a '%' so don't feed it as a naked format string + uprintf("%s", tmp); + } + if (j != 0) + uprintf("You should close these applications before retrying the operation."); + ReleaseMutex(blocking_process.hLock); + + return returned_mask & access_mask; } /** diff --git a/src/process.h b/src/process.h index 5ff0210d..fcaa61cb 100644 --- a/src/process.h +++ b/src/process.h @@ -2,9 +2,9 @@ * Rufus: The Reliable USB Formatting Utility * Process search functionality * - * Modified from Process Hacker: - * https://github.com/processhacker2/processhacker2/ - * Copyright © 2017-2019 Pete Batard + * Modified from System Informer (a.k.a. Process Hacker): + * https://github.com/winsiderss/systeminformer + * Copyright © 2017-2023 Pete Batard * Copyright © 2017 dmex * Copyright © 2009-2016 wj32 * @@ -25,6 +25,7 @@ #include #include #include +#include #pragma once @@ -294,3 +295,25 @@ typedef struct _RTL_HEAP_PARAMETERS #define SE_TIME_ZONE_PRIVILEGE (34L) #define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE (35L) #define SE_MAX_WELL_KNOWN_PRIVILEGE SE_CREATE_SYMBOLIC_LINK_PRIVILEGE + +#define MAX_NUM_HANDLES 16 +#define MAX_BLOCKING_PROCESSES 16 +#define SEARCH_PROCESS_LOCK_TIMEOUT 2000 + +typedef struct { + uint64_t pid; // PID of the process + uint8_t access_rights; // rwx access rights + uint32_t seen_on_pass; // nPass value of when this process was last detected + char cmdline[MAX_PATH]; // Command line for the process +} ProcessEntry; + +typedef struct { + BOOL bActive; // Indicates whether the search for processes is currently active + uint32_t nVersion[2]; // Version indicator for the handle name list. + uint32_t nHandles; // Number of handle names in the list below + wchar_t** wHandleName; // Handle names we search against + HANDLE hLock; // Global lock to request for modifying this structure + HANDLE hStart; // Event indicating that the search for processes can be started + ProcessEntry Process[MAX_BLOCKING_PROCESSES]; // Fixed size process list + uint32_t nPass; // Incremental counter of how many passes we ran +} BlockingProcess; diff --git a/src/rufus.c b/src/rufus.c index f3eda853..5c877ac7 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -141,7 +141,7 @@ char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; char msgbox[1024], msgbox_title[32], *ini_file = NULL, *image_path = NULL, *short_image_path; char *archive_path = NULL, image_option_txt[128], *fido_url = NULL, *save_image_type = NULL; -StrArray BlockingProcess, ImageList; +StrArray BlockingProcessList, ImageList; // Number of steps for each FS for FCC_STRUCTURE_PROGRESS const int nb_steps[FS_MAX] = { 5, 5, 12, 1, 10, 1, 1, 1, 1 }; const char* flash_type[BADLOCKS_PATTERN_TYPES] = { "SLC", "MLC", "TLC" }; @@ -2105,7 +2105,7 @@ static void InitDialog(HWND hDlg) IGNORE_RETVAL(ComboBox_SetCurSel(hDiskID, 0)); // Create the string arrays - StrArrayCreate(&BlockingProcess, 16); + StrArrayCreate(&BlockingProcessList, 16); StrArrayCreate(&ImageList, 16); // Set various checkboxes CheckDlgButton(hDlg, IDC_QUICK_FORMAT, BST_CHECKED); @@ -2169,85 +2169,6 @@ static void PrintStatusTimeout(const char* str, BOOL val) PrintStatus(STATUS_MSG_TIMEOUT, (val)?MSG_250:MSG_251, str); } -// Check for conflicting processes accessing the drive. -// If bPrompt is true, ask the user whether they want to proceed. -// dwTimeOut is the maximum amount of time we allow for this call to execute (in ms) -// If bPrompt is false, the return value is the amount of time remaining before -// dwTimeOut would expire (or zero if we spent more than dwTimeout in this procedure). -// If bPrompt is true, the return value is 0 on error, dwTimeOut on success. -DWORD CheckDriveAccess(DWORD dwTimeOut, BOOL bPrompt) -{ - uint32_t i, j; - DWORD ret = 0, proceed = TRUE; - BYTE access_mask; - char *PhysicalPath = NULL, DevPath[MAX_PATH]; - char drive_letter[27], drive_name[] = "?:"; - char title[128]; - uint64_t start_time = GetTickCount64(), cur_time, end_time = start_time + dwTimeOut; - - // Get the current selected device - DWORD DeviceNum = (DWORD)ComboBox_GetCurItemData(hDeviceList); - if ((DeviceNum < 0x80) || (DeviceNum == (DWORD)-1)) - return FALSE; - - // "Checking for conflicting processes..." - if (bPrompt) - PrintInfo(0, MSG_278); - - // Search for any blocking processes against the physical drive - PhysicalPath = GetPhysicalName(DeviceNum); - if (QueryDosDeviceA(&PhysicalPath[4], DevPath, sizeof(DevPath)) != 0) { - access_mask = SearchProcess(DevPath, dwTimeOut, TRUE, TRUE, TRUE); - CHECK_FOR_USER_CANCEL; - if (access_mask != 0) { - proceed = FALSE; - uprintf("Found potentially blocking process(es) against %s:", &PhysicalPath[4]); - for (j = 0; j < BlockingProcess.Index; j++) - // BlockingProcess.String[j] may contain a '%' so don't feed it as a naked format string - uprintf("%s", BlockingProcess.String[j]); - } - } - - // Search for any blocking processes against the logical volume(s) - GetDriveLetters(DeviceNum, drive_letter); - for (i = 0; drive_letter[i]; i++) { - drive_name[0] = drive_letter[i]; - if (QueryDosDeviceA(drive_name, DevPath, sizeof(DevPath)) != 0) { - StrArrayClear(&BlockingProcess); - cur_time = GetTickCount64(); - if (cur_time >= end_time) - break; - access_mask = SearchProcess(DevPath, (DWORD)(end_time - cur_time), TRUE, TRUE, TRUE); - CHECK_FOR_USER_CANCEL; - // Ignore if all we have is read-only - if ((access_mask & 0x06) || (access_mask == 0x80)) { - proceed = FALSE; - uprintf("Found potentially blocking process(es) against %s", drive_name); - for (j = 0; j < BlockingProcess.Index; j++) - // BlockingProcess.String[j] may contain a '%' so don't feed it as a naked format string - uprintf("%s", BlockingProcess.String[j]); - } - } - } - - // Prompt the user if we detected blocking processes - if (bPrompt && !proceed) { - ComboBox_GetTextU(hDeviceList, title, sizeof(title)); - proceed = Notification(MSG_WARNING_QUESTION, NULL, NULL, title, lmprintf(MSG_132)); - } - if (bPrompt) { - ret = proceed ? dwTimeOut : 0; - } else { - ret = (DWORD)(GetTickCount64() - start_time); - ret = (dwTimeOut > ret) ? (dwTimeOut - ret) : 0; - } - -out: - PrintInfo(0, MSG_210); - free(PhysicalPath); - return ret; -} - /* * Main dialog callback */ @@ -2358,7 +2279,8 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA SHChangeNotifyDeregister(ulRegister); PostQuitMessage(0); ClearDrives(); - StrArrayDestroy(&BlockingProcess); + StopProcessSearch(); + StrArrayDestroy(&BlockingProcessList); StrArrayDestroy(&ImageList); DestroyAllTooltips(); DestroyWindow(hLogDialog); @@ -2447,12 +2369,19 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA if (HIWORD(wParam) != CBN_SELCHANGE) break; nb_devices = ComboBox_GetCount(hDeviceList); - PrintStatusDebug(0, (nb_devices==1)?MSG_208:MSG_209, nb_devices); + PrintStatusDebug(0, (nb_devices == 1) ? MSG_208 : MSG_209, nb_devices); PopulateProperties(); nDeviceIndex = ComboBox_GetCurSel(hDeviceList); DeviceNum = (nDeviceIndex == CB_ERR) ? 0 : (DWORD)ComboBox_GetItemData(hDeviceList, nDeviceIndex); SendMessage(hMainDialog, WM_COMMAND, (CBN_SELCHANGE_INTERNAL << 16) | IDC_FILE_SYSTEM, ComboBox_GetCurSel(hFileSystem)); + if (nb_devices == 0) { + // No need to run the process search if no device is selected + StopProcessSearch(); + } else if (!StartProcessSearch() || !SetProcessSearch(DeviceNum)) { + uprintf("Failed to start conflicting process search"); + StopProcessSearch(); + } break; case IDC_IMAGE_OPTION: if (HIWORD(wParam) != CBN_SELCHANGE) @@ -2654,7 +2583,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA EnableControls(FALSE, FALSE); FormatStatus = 0; LastWriteError = 0; - StrArrayClear(&BlockingProcess); + StrArrayClear(&BlockingProcessList); no_confirmation_on_cancel = FALSE; SendMessage(hMainDialog, UM_PROGRESS_INIT, 0, 0); selection_default = (int)ComboBox_GetCurItemData(hBootType); @@ -3049,8 +2978,15 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA } } - if (!CheckDriveAccess(SEARCH_PROCESS_TIMEOUT, TRUE)) - goto aborted_start; + // Detect processes that have write (0x2) or exec (0x4) permissions against our drive. + // Ideally, exec should be no big deal, but Windows complains on USB ejection if a + // process such as cmd.exe holds exec rights, so we follow suit. + if (GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x06, TRUE)) { + char title[128]; + ComboBox_GetTextU(hDeviceList, title, sizeof(title)); + if (!Notification(MSG_WARNING_QUESTION, NULL, NULL, title, lmprintf(MSG_132))) + goto aborted_start; + } GetWindowTextU(hDeviceList, tmp, ARRAYSIZE(tmp)); if (MessageBoxExU(hMainDialog, lmprintf(MSG_003, tmp), @@ -3132,8 +3068,9 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA PrintInfo(0, MSG_212); MessageBeep(MB_ICONERROR); FlashTaskbar(dialog_handle); - if (BlockingProcess.Index > 0) { - ListDialog(lmprintf(MSG_042), lmprintf(MSG_055), BlockingProcess.String, BlockingProcess.Index); + GetProcessSearch(0, 0x07, FALSE); + if (BlockingProcessList.Index > 0) { + ListDialog(lmprintf(MSG_042), lmprintf(MSG_055), BlockingProcessList.String, BlockingProcessList.Index); } else { if (WindowsVersion.Version >= WINDOWS_10) { // Try to detect if 'Controlled Folder Access' is enabled on Windows 10 or later. See also: @@ -3831,18 +3768,7 @@ extern int TestHashes(void); // Ctrl-T => Alternate Test mode that doesn't require a full rebuild if ((ctrl_without_focus || ((GetKeyState(VK_CONTROL) & 0x8000) && (msg.message == WM_KEYDOWN))) && (msg.wParam == 'T')) { -// TestHashes(); - dll_resolver_t resolver = { 0 }; - resolver.path = "C:\\Windows\\System32\\Dism\\FfuProvider.dll"; - resolver.count = 3; - resolver.name = calloc(resolver.count, sizeof(char*)); - resolver.address = calloc(resolver.count, sizeof(uint32_t)); - resolver.name[0] = "FfuCaptureImage"; - resolver.name[1] = "FfuApplyImage"; - resolver.name[2] = "FfuMountImage"; - uprintf("Got %d resolved addresses", ResolveDllAddress(&resolver)); - free(resolver.name); - free(resolver.address); + TestHashes(); continue; } #endif diff --git a/src/rufus.h b/src/rufus.h index e1fc1cf8..803fb432 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -87,8 +87,8 @@ #define STATUS_MSG_TIMEOUT 3500 // How long should cheat mode messages appear for on the status bar #define WRITE_RETRIES 4 #define WRITE_TIMEOUT 5000 // How long we should wait between write retries (in ms) -#define SEARCH_PROCESS_TIMEOUT 10000 // How long we should search for conflicting processes before giving up (in ms) -#define NET_SESSION_TIMEOUT 3500 // How long we should wait to connect, send or receive internet data +#define SEARCH_PROCESS_TIMEOUT 5000 // How long we should wait to get the conflicting process data (in ms) +#define NET_SESSION_TIMEOUT 3500 // How long we should wait to connect, send or receive internet data (in ms) #define FS_DEFAULT FS_FAT32 #define SINGLE_CLUSTERSIZE_DEFAULT 0x00000100 #define BADLOCKS_PATTERN_TYPES 5 @@ -729,8 +729,10 @@ extern char* ToLocaleName(DWORD lang_id); extern void SetAlertPromptMessages(void); extern BOOL SetAlertPromptHook(void); extern void ClrAlertPromptHook(void); -extern DWORD CheckDriveAccess(DWORD dwTimeOut, BOOL bPrompt); -extern BYTE SearchProcess(char* HandleName, DWORD dwTimeout, BOOL bPartialMatch, BOOL bIgnoreSelf, BOOL bQuiet); +extern BOOL StartProcessSearch(void); +extern void StopProcessSearch(void); +extern BOOL SetProcessSearch(DWORD DeviceNum); +extern BYTE GetProcessSearch(uint32_t timeout, uint8_t access_mask, BOOL bIgnoreStaleProcesses); extern BOOL EnablePrivileges(void); extern void FlashTaskbar(HANDLE handle); extern DWORD WaitForSingleObjectWithMessages(HANDLE hHandle, DWORD dwMilliseconds); diff --git a/src/rufus.rc b/src/rufus.rc index 4b757140..c6afc22d 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2084" +CAPTION "Rufus 4.3.2085" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2084,0 - PRODUCTVERSION 4,3,2084,0 + FILEVERSION 4,3,2085,0 + PRODUCTVERSION 4,3,2085,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2084" + VALUE "FileVersion", "4.3.2085" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2084" + VALUE "ProductVersion", "4.3.2085" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index 95ba5634..8f0b6470 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -562,8 +562,8 @@ BOOL WriteFileWithRetry(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr break; if (nTry < nNumRetries) { uprintf("Retrying in %d seconds...", WRITE_TIMEOUT / 1000); - // Don't sit idly but use the downtime to check for conflicting processes... - Sleep(CheckDriveAccess(WRITE_TIMEOUT, FALSE)); + // TODO: Call GetProcessSearch() here? + Sleep(WRITE_TIMEOUT); } } if (SCODE_CODE(GetLastError()) == ERROR_SUCCESS) From 0bd38abd4e691ca7a464c965cf986d6e2d59548f Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 11 Oct 2023 20:46:46 +0100 Subject: [PATCH 26/53] [syslinux] improve support for Syslinux based Slax ISOs * For some weird reason appending the base directory to the root syslinux.cfg we create does not appear to work with Slax. So we now always patch ldlinux.sys to include the base directory. * Also add an exception to move the /slax/boot/EFI directory to /EFI. * It should be noted that, as of slax-64bit-slackware-15.0.3.iso, the Slax UEFI Syslinux bootloaders appear to be broken (since creating a media without using Rufus at all per the Slax documentation does *not* produce a USB drive that was bootable in UEFI mode on 2 of the machines I tried). * Also clean up some iso.c code and fix some unreachable code in ntfssect.c. * Closes #2336. * Closes #2338. --- src/iso.c | 27 ++++++++++++++++++++------- src/rufus.rc | 10 +++++----- src/syslinux.c | 16 +++++++++++----- src/syslinux/libinstaller/syslxmod.c | 13 +++++++------ src/syslinux/win/ntfssect.c | 2 +- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/iso.c b/src/iso.c index 75831516..ddc16807 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1096,7 +1096,7 @@ out: if ((iso9660_ifs_read_pvd(p_iso, &pvd)) && (_stat64U(src_iso, &stat) == 0)) img_report.mismatch_size = (int64_t)(iso9660_get_pvd_space_size(&pvd)) * ISO_BLOCKSIZE - stat.st_size; // Remove trailing spaces from the label - for (k=(int)safe_strlen(img_report.label)-1; ((k>0)&&(isspaceU(img_report.label[k]))); k--) + for (k = (int)safe_strlen(img_report.label) - 1; ((k > 0) && (isspaceU(img_report.label[k]))); k--) img_report.label[k] = 0; // We use the fact that UDF_BLOCKSIZE and ISO_BLOCKSIZE are the same here img_report.projected_size = total_blocks * ISO_BLOCKSIZE; @@ -1106,9 +1106,9 @@ out: if (!IsStrArrayEmpty(config_path)) { // Set the img_report.cfg_path string to maximum length, so that we don't have to // do a special case for StrArray entry 0. - memset(img_report.cfg_path, '_', sizeof(img_report.cfg_path)-1); - img_report.cfg_path[sizeof(img_report.cfg_path)-1] = 0; - for (i=0; i0) && (img_report.cfg_path[j]!='/'); j--); + for (j=safe_strlen(img_report.cfg_path); (j > 0) && (img_report.cfg_path[j] != '/'); j--); if (safe_strnicmp(img_report.cfg_path, isolinux_path.String[i], j) == 0) { static_strcpy(img_report.sl_version_ext, ext); img_report.sl_version = sl_version; @@ -1197,7 +1197,7 @@ out: ExtractISOFile(src_iso, path, tmp_sif, FILE_ATTRIBUTE_NORMAL); tmp = get_token_data_file("OsLoadOptions", tmp_sif); if (tmp != NULL) { - for (i=0; i= 5)); - PrintInfoDebug(0, MSG_234, (boot_type == BT_IMAGE)?img_report.sl_version_str:embedded_sl_version_str[use_v5?1:0]); + PrintInfoDebug(0, MSG_234, (boot_type == BT_IMAGE) ? img_report.sl_version_str : embedded_sl_version_str[use_v5?1:0]); /* 4K sector size workaround */ SECTOR_SHIFT = 0; @@ -287,8 +287,14 @@ BOOL InstallSyslinux(DWORD drive_index, char drive_letter, int file_system) goto out; } - /* Patch ldlinux.sys and the boot sector */ - if (syslinux_patch(sectors, nsectors, 0, 0, NULL, NULL) < 0) { + /* Set the base directory and patch ldlinux.sys and the boot sector */ + for (i = (int)strlen(img_report.cfg_path); (i > 0) && (img_report.cfg_path[i] != '/'); i--); + if (i > 0) + img_report.cfg_path[i] = 0; + w = syslinux_patch(sectors, nsectors, 0, 0, img_report.cfg_path, NULL); + if (i > 0) + img_report.cfg_path[i] = '/'; + if (w < 0) { uprintf("Could not patch Syslinux files."); uprintf("WARNING: This could be caused by your firewall having modified downloaded content, such as 'ldlinux.sys'..."); goto out; diff --git a/src/syslinux/libinstaller/syslxmod.c b/src/syslinux/libinstaller/syslxmod.c index 23a10217..19e1fa82 100644 --- a/src/syslinux/libinstaller/syslxmod.c +++ b/src/syslinux/libinstaller/syslxmod.c @@ -28,6 +28,7 @@ #include "syslinux.h" #include "syslxint.h" +extern void uprintf(const char* format, ...); /* * Generate sector extents @@ -161,8 +162,8 @@ int syslinux_patch(const sector_t *sectp, int nsectors, #if 0 if (nsect > nptrs) { /* Not necessarily an error in this case, but a general problem */ - fprintf(stderr, "Insufficient extent space, build error!\n"); - exit(1); + uprintf("syslinux_patch: Insufficient extent space, build error!\n"); + return -1; } #endif @@ -178,8 +179,8 @@ int syslinux_patch(const sector_t *sectp, int nsectors, if (subdir) { int sublen = strlen(subdir) + 1; if (get_16_sl(&epa->dirlen) < sublen) { - fprintf(stderr, "Subdirectory path too long... aborting install!\n"); - exit(1); + uprintf("syslinux_patch: Subdirectory path too long... aborting install!"); + return -1; } memcpy_to_sl(slptr(boot_image, &epa->diroffset), subdir, sublen); } @@ -188,8 +189,8 @@ int syslinux_patch(const sector_t *sectp, int nsectors, if (subvol) { int sublen = strlen(subvol) + 1; if (get_16_sl(&epa->subvollen) < sublen) { - fprintf(stderr, "Subvol name too long... aborting install!\n"); - exit(1); + uprintf("syslinux_patch: Subvol name too long... aborting install!"); + return -1; } memcpy_to_sl(slptr(boot_image, &epa->subvoloffset), subvol, sublen); } diff --git a/src/syslinux/win/ntfssect.c b/src/syslinux/win/ntfssect.c index 15d2709c..5c076499 100644 --- a/src/syslinux/win/ntfssect.c +++ b/src/syslinux/win/ntfssect.c @@ -126,10 +126,10 @@ static DWORD NtfsSectGetVolumeHandle( M_ERR("Unable to open volume handle!"); goto err_handle; } + CloseHandle(VolumeInfo->Handle); return ERROR_SUCCESS; - CloseHandle(VolumeInfo->Handle); err_handle: return rc; From e9d588a6e05ff08265fa0b7e87ffa9da07ebba37 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 12 Oct 2023 17:32:20 +0100 Subject: [PATCH 27/53] [iso] add symlink support for target file systems that support it * For now that means only NTFS. And we only do that for ISO-9660/Rock Ridge images. --- src/iso.c | 112 ++++++++++++++++++++++++++++++----------------- src/msapi_utf8.h | 10 +++++ src/rufus.rc | 10 ++--- 3 files changed, 87 insertions(+), 45 deletions(-) diff --git a/src/iso.c b/src/iso.c index ddc16807..ef07807d 100644 --- a/src/iso.c +++ b/src/iso.c @@ -122,7 +122,7 @@ static const char* stupid_antivirus = " NOTE: This is usually caused by a poorl const char* old_c32_name[NB_OLD_C32] = OLD_C32_NAMES; static const int64_t old_c32_threshold[NB_OLD_C32] = OLD_C32_THRESHOLD; static uint8_t joliet_level = 0; -static uint64_t total_blocks, nb_blocks; +static uint64_t total_blocks, extra_blocks, nb_blocks; static BOOL scan_only = FALSE; static StrArray config_path, isolinux_path, modified_path; static char symlinked_syslinux[MAX_PATH]; @@ -311,20 +311,20 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const } // Check for PE (XP) specific files in "/i386", "/amd64" or "/minint" - for (i=0; iis_old_c32[i]) img_report.has_old_c32[i] = TRUE; } @@ -706,7 +706,7 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) HANDLE file_handle = NULL; DWORD buf_size, wr_size, err; EXTRACT_PROPS props; - BOOL is_symlink, is_identical, free_p_statbuf = FALSE; + BOOL is_symlink, is_identical, create_file, free_p_statbuf = FALSE; int length, r = 1; char psz_fullpath[MAX_PATH], *psz_basename = NULL, *psz_sanpath = NULL; char tmp[128], target_path[256]; @@ -798,7 +798,7 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); iso9660_stat_t *p_statbuf2 = iso9660_ifs_stat_translate(p_iso, target_path); if (p_statbuf2 != NULL) { - total_blocks += (p_statbuf2->total_size + ISO_BLOCKSIZE - 1) / ISO_BLOCKSIZE; + extra_blocks += (p_statbuf2->total_size + ISO_BLOCKSIZE - 1) / ISO_BLOCKSIZE; iso9660_stat_free(p_statbuf2); } } @@ -821,8 +821,26 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) psz_sanpath = sanitize_filename(psz_fullpath, &is_identical); if (!is_identical) uprintf(" File name sanitized to '%s'", psz_sanpath); + create_file = TRUE; if (is_symlink) { - if (file_length == 0) { + if (fs_type == FS_NTFS) { + // Replicate symlinks if NTFS is being used + static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); + iso9660_stat_t* p_statbuf2 = iso9660_ifs_stat_translate(p_iso, target_path); + if (p_statbuf2 != NULL) { + to_windows_path(psz_fullpath); + to_windows_path(p_statbuf->rr.psz_symlink); + uprintf("Symlinking: %s%s âž” %s", psz_fullpath, + (p_statbuf2->type == _STAT_DIR) ? "\\" : "", p_statbuf->rr.psz_symlink); + if (!CreateSymbolicLinkU(psz_fullpath, p_statbuf->rr.psz_symlink, + (p_statbuf2->type == _STAT_DIR) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)) + uprintf(" Could not create symlink: %s", WindowsErrorString()); + to_unix_path(p_statbuf->rr.psz_symlink); + to_unix_path(psz_fullpath); + iso9660_stat_free(p_statbuf2); + create_file = FALSE; + } + } else if (file_length == 0) { if ((safe_stricmp(p_statbuf->filename, "syslinux") == 0) && // Special handling for ISOs that have a syslinux → isolinux symbolic link (e.g. Knoppix) (safe_stricmp(p_statbuf->rr.psz_symlink, "isolinux") == 0)) { @@ -846,45 +864,58 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) goto out; } } else { - // TODO: Ideally, we'd want to create a text file that contains the target link - print_extracted_file(psz_fullpath, file_length); + print_extracted_file(psz_fullpath, safe_strlen(p_statbuf->rr.psz_symlink)); uprintf(" Ignoring Rock Ridge symbolic link to '%s'", p_statbuf->rr.psz_symlink); } + } else { + uuprintf("Unexpected symlink length: %d", file_length); + create_file = FALSE; } } - file_handle = CreatePreallocatedFile(psz_sanpath, GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, file_length); - if (file_handle == INVALID_HANDLE_VALUE) { - err = GetLastError(); - uprintf(" Unable to create file: %s", WindowsErrorString()); - if (((err == ERROR_ACCESS_DENIED) || (err == ERROR_INVALID_HANDLE)) && - (safe_strcmp(&psz_sanpath[3], autorun_name) == 0)) - uprintf(stupid_antivirus); - else - goto out; - } else for (i = 0; file_length > 0; i++) { - if (FormatStatus) goto out; - memset(buf, 0, ISO_BLOCKSIZE); - lsn = p_statbuf->lsn + (lsn_t)i; - if (iso9660_iso_seek_read(p_iso, buf, lsn, 1) != ISO_BLOCKSIZE) { - uprintf(" Error reading ISO9660 file %s at LSN %lu", - psz_iso_name, (long unsigned int)lsn); - goto out; + if (create_file) { + file_handle = CreatePreallocatedFile(psz_sanpath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, file_length); + if (file_handle == INVALID_HANDLE_VALUE) { + err = GetLastError(); + uprintf(" Unable to create file: %s", WindowsErrorString()); + if (((err == ERROR_ACCESS_DENIED) || (err == ERROR_INVALID_HANDLE)) && + (safe_strcmp(&psz_sanpath[3], autorun_name) == 0)) + uprintf(stupid_antivirus); + else + goto out; + } else if (is_symlink) { + // Create a text file that contains the target link + ISO_BLOCKING(r = WriteFileWithRetry(file_handle, p_statbuf->rr.psz_symlink, + (DWORD)safe_strlen(p_statbuf->rr.psz_symlink), &wr_size, WRITE_RETRIES)); + if (!r) { + uprintf(" Error writing file: %s", WindowsErrorString()); + goto out; + } + } else for (i = 0; file_length > 0; i++) { + if (FormatStatus) goto out; + memset(buf, 0, ISO_BLOCKSIZE); + lsn = p_statbuf->lsn + (lsn_t)i; + if (iso9660_iso_seek_read(p_iso, buf, lsn, 1) != ISO_BLOCKSIZE) { + uprintf(" Error reading ISO9660 file %s at LSN %lu", + psz_iso_name, (long unsigned int)lsn); + goto out; + } + buf_size = (DWORD)MIN(file_length, ISO_BLOCKSIZE); + ISO_BLOCKING(r = WriteFileWithRetry(file_handle, buf, buf_size, &wr_size, WRITE_RETRIES)); + if (!r) { + uprintf(" Error writing file: %s", WindowsErrorString()); + goto out; + } + file_length -= ISO_BLOCKSIZE; + if (nb_blocks++ % PROGRESS_THRESHOLD == 0) + UpdateProgressWithInfo(OP_FILE_COPY, MSG_231, nb_blocks, total_blocks + + ((fs_type != FS_NTFS) ? extra_blocks : 0)); } - buf_size = (DWORD)MIN(file_length, ISO_BLOCKSIZE); - ISO_BLOCKING(r = WriteFileWithRetry(file_handle, buf, buf_size, &wr_size, WRITE_RETRIES)); - if (!r) { - uprintf(" Error writing file: %s", WindowsErrorString()); - goto out; + if (preserve_timestamps) { + LPFILETIME ft = to_filetime(mktime(&p_statbuf->tm)); + if (!SetFileTime(file_handle, ft, ft, ft)) + uprintf(" Could not set timestamp: %s", WindowsErrorString()); } - file_length -= ISO_BLOCKSIZE; - if (nb_blocks++ % PROGRESS_THRESHOLD == 0) - UpdateProgressWithInfo(OP_FILE_COPY, MSG_231, nb_blocks, total_blocks); - } - if (preserve_timestamps) { - LPFILETIME ft = to_filetime(mktime(&p_statbuf->tm)); - if (!SetFileTime(file_handle, ft, ft, ft)) - uprintf(" Could not set timestamp: %s", WindowsErrorString()); } if (free_p_statbuf) iso9660_stat_free(p_statbuf); @@ -1014,6 +1045,7 @@ BOOL ExtractISO(const char* src_iso, const char* dest_dir, BOOL scan) uprintf("ISO analysis:"); SendMessage(hMainDialog, UM_PROGRESS_INIT, PBS_MARQUEE, 0); total_blocks = 0; + extra_blocks = 0; has_ldlinux_c32 = FALSE; // String array of all isolinux/syslinux locations StrArrayCreate(&config_path, 8); diff --git a/src/msapi_utf8.h b/src/msapi_utf8.h index 3762ea4e..67f96a24 100644 --- a/src/msapi_utf8.h +++ b/src/msapi_utf8.h @@ -1233,6 +1233,16 @@ static __inline BOOL MoveFileExU(const char* lpExistingFileName, const char* lpN return ret; } +static __inline BOOL CreateSymbolicLinkU(const char* lpSymlinkFileName, const char* lpTargetFileName, DWORD dwFlags) +{ + wconvert(lpSymlinkFileName); + wconvert(lpTargetFileName); + BOOL ret = CreateSymbolicLinkW(wlpSymlinkFileName, wlpTargetFileName, dwFlags); + wfree(lpTargetFileName); + wfree(lpSymlinkFileName); + return ret; +} + // The following expects PropertyBuffer to contain a single Unicode string static __inline BOOL SetupDiGetDeviceRegistryPropertyU(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize) diff --git a/src/rufus.rc b/src/rufus.rc index 1e04eef1..62375b1f 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2086" +CAPTION "Rufus 4.3.2087" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2086,0 - PRODUCTVERSION 4,3,2086,0 + FILEVERSION 4,3,2087,0 + PRODUCTVERSION 4,3,2087,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2086" + VALUE "FileVersion", "4.3.2087" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2086" + VALUE "ProductVersion", "4.3.2087" END END BLOCK "VarFileInfo" From 1630e912d4e0568ed43aaa82cf106b7723208595 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 12 Oct 2023 18:28:56 +0100 Subject: [PATCH 28/53] [iso] add exception for Mint's LMDE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mint have decided to make their installation rely on a working /live/ âž” /casper/ symlink for LMDE thereby breaking the promise of File System Transposition that all Debian derivatives should have. * Because of this, trying to use FAT32 with LMDE will fail, as reported in linuxmint/live-installer#152. * Therefore, now that we can replicate symlinks on NTFS, we add an exception to always enforce the use of NTFS for LMDE. --- src/iso.c | 132 +++++++++++++++++++++++++++------------------------ src/rufus.c | 23 +++++---- src/rufus.h | 3 +- src/rufus.rc | 10 ++-- 4 files changed, 90 insertions(+), 78 deletions(-) diff --git a/src/iso.c b/src/iso.c index ef07807d..7f7535e1 100644 --- a/src/iso.c +++ b/src/iso.c @@ -464,6 +464,62 @@ static void fix_config(const char* psz_fullpath, const char* psz_path, const cha free(src); } +// This updates the MD5SUMS/md5sum.txt file that some distros (Ubuntu, Mint...) +// use to validate the media. Because we may alter some of the validated files +// to add persistence and whatnot, we need to alter the MD5 list as a result. +// The format of the file is expected to always be " " on +// individual lines. +static void update_md5sum(void) +{ + BOOL display_header = TRUE; + intptr_t pos; + uint32_t i, j, size, md5_size; + uint8_t* buf = NULL, sum[16]; + char md5_path[64], * md5_data = NULL, * str_pos; + + if (!img_report.has_md5sum) + goto out; + + assert(img_report.has_md5sum <= ARRAYSIZE(md5sum_name)); + if (img_report.has_md5sum > ARRAYSIZE(md5sum_name)) + goto out; + + static_sprintf(md5_path, "%s\\%s", psz_extract_dir, md5sum_name[img_report.has_md5sum - 1]); + md5_size = read_file(md5_path, (uint8_t**)&md5_data); + if (md5_size == 0) + goto out; + + for (i = 0; i < modified_path.Index; i++) { + str_pos = strstr(md5_data, &modified_path.String[i][2]); + if (str_pos == NULL) + // File is not listed in md5 sums + continue; + if (display_header) { + uprintf("Updating %s:", md5_path); + display_header = FALSE; + } + uprintf("â— %s", &modified_path.String[i][2]); + pos = str_pos - md5_data; + size = read_file(modified_path.String[i], &buf); + if (size == 0) + continue; + HashBuffer(HASH_MD5, buf, size, sum); + free(buf); + while ((pos > 0) && (md5_data[pos - 1] != '\n')) + pos--; + for (j = 0; j < 16; j++) { + md5_data[pos + 2 * j] = ((sum[j] >> 4) < 10) ? ('0' + (sum[j] >> 4)) : ('a' - 0xa + (sum[j] >> 4)); + md5_data[pos + 2 * j + 1] = ((sum[j] & 15) < 10) ? ('0' + (sum[j] & 15)) : ('a' - 0xa + (sum[j] & 15)); + } + } + + write_file(md5_path, md5_data, md5_size); + free(md5_data); + +out: + StrArrayDestroy(&modified_path); +} + static void print_extracted_file(char* psz_fullpath, uint64_t file_length) { size_t nul_pos; @@ -644,62 +700,6 @@ out: return 1; } -// This updates the MD5SUMS/md5sum.txt file that some distros (Ubuntu, Mint...) -// use to validate the media. Because we may alter some of the validated files -// to add persistence and whatnot, we need to alter the MD5 list as a result. -// The format of the file is expected to always be " " on -// individual lines. -static void update_md5sum(void) -{ - BOOL display_header = TRUE; - intptr_t pos; - uint32_t i, j, size, md5_size; - uint8_t *buf = NULL, sum[16]; - char md5_path[64], *md5_data = NULL, *str_pos; - - if (!img_report.has_md5sum) - goto out; - - assert(img_report.has_md5sum <= ARRAYSIZE(md5sum_name)); - if (img_report.has_md5sum > ARRAYSIZE(md5sum_name)) - goto out; - - static_sprintf(md5_path, "%s\\%s", psz_extract_dir, md5sum_name[img_report.has_md5sum - 1]); - md5_size = read_file(md5_path, (uint8_t**)&md5_data); - if (md5_size == 0) - goto out; - - for (i = 0; i < modified_path.Index; i++) { - str_pos = strstr(md5_data, &modified_path.String[i][2]); - if (str_pos == NULL) - // File is not listed in md5 sums - continue; - if (display_header) { - uprintf("Updating %s:", md5_path); - display_header = FALSE; - } - uprintf("â— %s", &modified_path.String[i][2]); - pos = str_pos - md5_data; - size = read_file(modified_path.String[i], &buf); - if (size == 0) - continue; - HashBuffer(HASH_MD5, buf, size, sum); - free(buf); - while ((pos > 0) && (md5_data[pos - 1] != '\n')) - pos--; - for (j = 0; j < 16; j++) { - md5_data[pos + 2 * j] = ((sum[j] >> 4) < 10) ? ('0' + (sum[j] >> 4)) : ('a' - 0xa + (sum[j] >> 4)); - md5_data[pos + 2 * j + 1] = ((sum[j] & 15) < 10) ? ('0' + (sum[j] & 15)) : ('a' - 0xa + (sum[j] & 15)); - } - } - - write_file(md5_path, md5_data, md5_size); - free(md5_data); - -out: - StrArrayDestroy(&modified_path); -} - // Returns 0 on success, >0 on error, <0 to ignore current dir static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) { @@ -793,13 +793,19 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path) } else { file_length = p_statbuf->total_size; if (check_iso_props(psz_path, file_length, psz_basename, psz_fullpath, &props)) { - // Add symlink duplicated files to total_size at scantime - if (is_symlink && (file_length == 0) && (strcmp(psz_path, "/firmware") == 0)) { - static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); - iso9660_stat_t *p_statbuf2 = iso9660_ifs_stat_translate(p_iso, target_path); - if (p_statbuf2 != NULL) { - extra_blocks += (p_statbuf2->total_size + ISO_BLOCKSIZE - 1) / ISO_BLOCKSIZE; - iso9660_stat_free(p_statbuf2); + if (is_symlink && (file_length == 0)) { + // Add symlink duplicated files to total_size at scantime + if ((strcmp(psz_path, "/firmware") == 0)) { + static_sprintf(target_path, "%s/%s", psz_path, p_statbuf->rr.psz_symlink); + iso9660_stat_t* p_statbuf2 = iso9660_ifs_stat_translate(p_iso, target_path); + if (p_statbuf2 != NULL) { + extra_blocks += (p_statbuf2->total_size + ISO_BLOCKSIZE - 1) / ISO_BLOCKSIZE; + iso9660_stat_free(p_statbuf2); + } + } else if ((strcmp(p_statbuf->filename, "live") == 0) && + (strcmp(p_statbuf->rr.psz_symlink, "casper") == 0)) { + // Mint LMDE requires working symbolic links and therefore requires the use of NTFS + img_report.needs_ntfs = TRUE; } } continue; diff --git a/src/rufus.c b/src/rufus.c index 5c877ac7..6d719b0b 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -184,8 +184,8 @@ static void SetAllowedFileSystems(void) break; case BT_IMAGE: allowed_filesystem[FS_NTFS] = TRUE; - // Don't allow anything besides NTFS if the image has a >4GB file - if ((image_path != NULL) && (img_report.has_4GB_file)) + // Don't allow anything besides NTFS if the image has a >4GB file or explicitly requires NTFS + if ((image_path != NULL) && (img_report.has_4GB_file || img_report.needs_ntfs)) break; if (!HAS_WINDOWS(img_report) || (target_type != TT_BIOS) || allow_dual_uefi_bios) { if (!HAS_WINTOGO(img_report) || (ComboBox_GetCurItemData(hImageOption) != IMOP_WIN_TO_GO)) { @@ -1134,13 +1134,18 @@ static void DisplayISOProps(void) (img_report.wininst_version >> 16) & 0xff, (img_report.wininst_version >> 8) & 0xff, (img_report.wininst_version >= SPECIAL_WIM_VERSION) ? "+": ""); } - PRINT_ISO_PROP(img_report.has_symlinks, - " Note: This ISO uses symbolic links, which will not be replicated due to file system"); - PRINT_ISO_PROP((img_report.has_symlinks == SYMLINKS_RR), - " limitations. Because of this, some features from this image may not work..."); - PRINT_ISO_PROP((img_report.has_symlinks == SYMLINKS_UDF), - " limitations. Because of this, the size required for the target media may be much\r\n" - " larger than size of the ISO..."); + if (img_report.needs_ntfs) { + uprintf(" Note: This ISO uses symbolic links and was not designed to work without them.\r\n" + " Because of this, only NTFS will be allowed as the target file system."); + } else { + PRINT_ISO_PROP(img_report.has_symlinks, + " Note: This ISO uses symbolic links, which may not be replicated due to file system"); + PRINT_ISO_PROP((img_report.has_symlinks == SYMLINKS_RR), + " limitations. Because of this, some features from this image may not work..."); + PRINT_ISO_PROP((img_report.has_symlinks == SYMLINKS_UDF), + " limitations. Because of this, the size required for the target media may be much\r\n" + " larger than size of the ISO..."); + } } // Insert the image name into the Boot selection dropdown and (re)populate the Image option dropdown diff --git a/src/rufus.h b/src/rufus.h index 803fb432..6d2822df 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -412,11 +412,12 @@ typedef struct { BOOLEAN has_old_c32[NB_OLD_C32]; BOOLEAN has_old_vesamenu; BOOLEAN has_efi_syslinux; - BOOLEAN needs_syslinux_overwrite; BOOLEAN has_grub4dos; uint8_t has_grub2; BOOLEAN has_compatresources_dll; BOOLEAN has_kolibrios; + BOOLEAN needs_syslinux_overwrite; + BOOLEAN needs_ntfs; BOOLEAN uses_casper; BOOLEAN uses_minint; uint8_t compression_type; diff --git a/src/rufus.rc b/src/rufus.rc index 62375b1f..f8baf63d 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2087" +CAPTION "Rufus 4.3.2088" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2087,0 - PRODUCTVERSION 4,3,2087,0 + FILEVERSION 4,3,2088,0 + PRODUCTVERSION 4,3,2088,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2087" + VALUE "FileVersion", "4.3.2088" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2087" + VALUE "ProductVersion", "4.3.2088" END END BLOCK "VarFileInfo" From 8edb487ac9b4457de4f63ff089ddf33e00750948 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 12 Oct 2023 19:46:10 +0100 Subject: [PATCH 29/53] [misc] update ChangeLog for 4.3 BETA * Also minor code cleanups and improvements. --- ChangeLog.txt | 10 ++++++++++ src/process.c | 6 +++++- src/process.h | 1 + src/rufus.h | 2 +- src/rufus.rc | 10 +++++----- src/stdfn.c | 30 +++++++++++++++--------------- src/stdio.c | 4 +--- 7 files changed, 38 insertions(+), 25 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 0cb2abaf..08db102c 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,13 @@ +o Version 4.3 (2023.10.??) + Add support for Rock Ridge symlink preservation when NTFS is used + Add an exception to enforce NTFS for Linux Mint's LMDE + Add an expert feature to restrict a Windows installation to S Mode + Fix persistence support for Debian 12 when booted in BIOS mode + Fix a regression that prevented the opening of .vhd images + Update UEFI:NTFS to report a more explicit error on bootmgr security issues + Improve the search for conflicting processes by running it in a background thread + Improve support for Slax Linux + o Version 4.2 (2023.07.26) Add detection and warning for UEFI revoked bootloaders (including ones revoked through SkuSiPolicy.p7b) Add ZIP64 support, to extract .zip images that are larger than 4 GB diff --git a/src/process.c b/src/process.c index fe9575dd..c9fc9d06 100644 --- a/src/process.c +++ b/src/process.c @@ -975,6 +975,10 @@ retry: * be convenient for our usage (since we might be looking for processes preventing * us to open said target in exclusive mode). * + * At least on Windows 11, this no longer seems to work as querying a logical or + * physical volume seems to return almost ALL the processes that are running, + * including the ones that are not actually accessing the handle. + * * \param HandleName The name of the handle to look for. * * \return TRUE if processes were found, FALSE otherwise. @@ -992,7 +996,7 @@ BOOL SearchProcessAlt(char* HandleName) goto out; // Note that the access rights being used with CreateFile() might matter... - searchHandle = CreateFileA(HandleName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + searchHandle = CreateFileA(HandleName, FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); status = PhQueryProcessesUsingVolumeOrFile(searchHandle, &info); diff --git a/src/process.h b/src/process.h index fcaa61cb..3c367ca5 100644 --- a/src/process.h +++ b/src/process.h @@ -45,6 +45,7 @@ #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) #define STATUS_OBJECT_PATH_INVALID ((NTSTATUS)0xC0000039L) #define STATUS_SHARING_VIOLATION ((NTSTATUS)0xC0000043L) +#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) #define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) #define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL) diff --git a/src/rufus.h b/src/rufus.h index 6d2822df..b1db118e 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -827,7 +827,7 @@ out: if (pf##proc == NULL) {uprintf("Unable to locate %s() in '%s.dll': %s", \ #proc, #name, WindowsErrorString()); goto out;} } while(0) #define PF_INIT_OR_SET_STATUS(proc, name) do {PF_INIT(proc, name); \ - if ((pf##proc == NULL) && (NT_SUCCESS(status))) status = STATUS_NOT_IMPLEMENTED; } while(0) + if ((pf##proc == NULL) && (NT_SUCCESS(status))) status = STATUS_PROCEDURE_NOT_FOUND; } while(0) #if defined(_MSC_VER) #define TRY_AND_HANDLE(exception, TRY_CODE, EXCEPTION_CODE) __try TRY_CODE \ __except (GetExceptionCode() == exception ? EXCEPTION_EXECUTE_HANDLER : \ diff --git a/src/rufus.rc b/src/rufus.rc index f8baf63d..2d91ece5 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2088" +CAPTION "Rufus 4.3.2089" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2088,0 - PRODUCTVERSION 4,3,2088,0 + FILEVERSION 4,3,2089,0 + PRODUCTVERSION 4,3,2089,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2088" + VALUE "FileVersion", "4.3.2089" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2088" + VALUE "ProductVersion", "4.3.2089" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index ea9953bf..d4076364 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -534,7 +534,7 @@ void StrArrayCreate(StrArray* arr, uint32_t initial_size) arr->Max = initial_size; arr->Index = 0; arr->String = (char**)calloc(arr->Max, sizeof(char*)); if (arr->String == NULL) - uprintf("Could not allocate string array\n"); + uprintf("Could not allocate string array"); } int32_t StrArrayAdd(StrArray* arr, const char* str, BOOL duplicate) @@ -548,13 +548,13 @@ int32_t StrArrayAdd(StrArray* arr, const char* str, BOOL duplicate) arr->String = (char**)realloc(arr->String, arr->Max*sizeof(char*)); if (arr->String == NULL) { free(old_table); - uprintf("Could not reallocate string array\n"); + uprintf("Could not reallocate string array"); return -1; } } arr->String[arr->Index] = (duplicate)?safe_strdup(str):(char*)str; if (arr->String[arr->Index] == NULL) { - uprintf("Could not store string in array\n"); + uprintf("Could not store string in array"); return -1; } return arr->Index++; @@ -577,7 +577,7 @@ void StrArrayClear(StrArray* arr) uint32_t i; if ((arr == NULL) || (arr->String == NULL)) return; - for (i=0; iIndex; i++) { + for (i = 0; i < arr->Index; i++) { safe_free(arr->String[i]); } arr->Index = 0; @@ -601,13 +601,13 @@ static PSID GetSID(void) { char* psid_string = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { - uprintf("OpenProcessToken failed: %s\n", WindowsErrorString()); + uprintf("OpenProcessToken failed: %s", WindowsErrorString()); return NULL; } if (!GetTokenInformation(token, TokenUser, tu, 0, &len)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - uprintf("GetTokenInformation (pre) failed: %s\n", WindowsErrorString()); + uprintf("GetTokenInformation (pre) failed: %s", WindowsErrorString()); return NULL; } tu = (TOKEN_USER*)calloc(1, len); @@ -623,11 +623,11 @@ static PSID GetSID(void) { * The workaround? Convert to string then back to PSID */ if (!ConvertSidToStringSidA(tu->User.Sid, &psid_string)) { - uprintf("Unable to convert SID to string: %s\n", WindowsErrorString()); + uprintf("Unable to convert SID to string: %s", WindowsErrorString()); ret = NULL; } else { if (!ConvertStringSidToSidA(psid_string, &ret)) { - uprintf("Unable to convert string back to SID: %s\n", WindowsErrorString()); + uprintf("Unable to convert string back to SID: %s", WindowsErrorString()); ret = NULL; } // MUST use LocalFree() @@ -635,7 +635,7 @@ static PSID GetSID(void) { } } else { ret = NULL; - uprintf("GetTokenInformation (real) failed: %s\n", WindowsErrorString()); + uprintf("GetTokenInformation (real) failed: %s", WindowsErrorString()); } free(tu); return ret; @@ -662,7 +662,7 @@ BOOL FileIO(enum file_io_type io_type, char* path, char** buffer, DWORD* size) s_attr.lpSecurityDescriptor = &s_desc; sa = &s_attr; } else { - uprintf("Could not set security descriptor: %s\n", WindowsErrorString()); + uprintf("Could not set security descriptor: %s", WindowsErrorString()); } switch (io_type) { @@ -696,7 +696,7 @@ BOOL FileIO(enum file_io_type io_type, char* path, char** buffer, DWORD* size) *size = GetFileSize(handle, NULL); *buffer = (char*)malloc(*size); if (*buffer == NULL) { - uprintf("Could not allocate buffer for reading file\n"); + uprintf("Could not allocate buffer for reading file"); goto out; } r = ReadFile(handle, *buffer, *size, size, NULL); @@ -742,12 +742,12 @@ unsigned char* GetResource(HMODULE module, char* name, char* type, const char* d res = FindResourceA(module, name, type); if (res == NULL) { - uprintf("Could not locate resource '%s': %s\n", desc, WindowsErrorString()); + uprintf("Could not locate resource '%s': %s", desc, WindowsErrorString()); goto out; } res_handle = LoadResource(module, res); if (res_handle == NULL) { - uprintf("Could not load resource '%s': %s\n", desc, WindowsErrorString()); + uprintf("Could not load resource '%s': %s", desc, WindowsErrorString()); goto out; } res_len = SizeofResource(module, res); @@ -757,12 +757,12 @@ unsigned char* GetResource(HMODULE module, char* name, char* type, const char* d *len = res_len; p = (unsigned char*)calloc(*len, 1); if (p == NULL) { - uprintf("Could not allocate resource '%s'\n", desc); + uprintf("Could not allocate resource '%s'", desc); goto out; } memcpy(p, LockResource(res_handle), min(res_len, *len)); if (res_len > *len) - uprintf("WARNING: Resource '%s' was truncated by %d bytes!\n", desc, res_len - *len); + uprintf("WARNING: Resource '%s' was truncated by %d bytes!", desc, res_len - *len); } else { p = (unsigned char*)LockResource(res_handle); } diff --git a/src/stdio.c b/src/stdio.c index 8f0b6470..493345cf 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -83,7 +83,6 @@ void uprintf(const char *format, ...) *p++ = '\n'; *p = '\0'; - // Yay, Windows 10 *FINALLY* added actual Unicode support for OutputDebugStringW()! wbuf = utf8_to_wchar(buf); // Send output to Windows debug facility OutputDebugStringW(wbuf); @@ -92,7 +91,6 @@ void uprintf(const char *format, ...) Edit_SetSel(hLog, MAX_LOG_SIZE, MAX_LOG_SIZE); Edit_ReplaceSel(hLog, wbuf); // Make sure the message scrolls into view - // (Or see code commented in LogProc:WM_SHOWWINDOW for a less forceful scroll) Edit_Scroll(hLog, Edit_GetLineCount(hLog), 0); } free(wbuf); @@ -608,7 +606,7 @@ DWORD WaitForSingleObjectWithMessages(HANDLE hHandle, DWORD dwMilliseconds) } #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) +#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) #define FILE_ATTRIBUTE_VALID_FLAGS 0x00007FB7 #define NtCurrentPeb() (NtCurrentTeb()->ProcessEnvironmentBlock) #define RtlGetProcessHeap() (NtCurrentPeb()->Reserved4[1]) // NtCurrentPeb()->ProcessHeap, mangled due to deficiencies in winternl.h From 020e0b7c3a65f93196ffe151597bf7ff6281c8e4 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 19 Oct 2023 10:31:19 +0100 Subject: [PATCH 30/53] Rufus 4.3 (Build 2090) * Also fix a CodeQL warning in process.c --- ChangeLog.txt | 4 ++-- res/appstore/listing/listing.csv | 16 ++++++++-------- src/process.c | 2 +- src/rufus.rc | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 08db102c..c6808655 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,5 @@ -o Version 4.3 (2023.10.??) - Add support for Rock Ridge symlink preservation when NTFS is used +o Version 4.3 (2023.10.19) + Add support for Rock Ridge symbolic links preservation when NTFS is used Add an exception to enforce NTFS for Linux Mint's LMDE Add an expert feature to restrict a Windows installation to S Mode Fix persistence support for Debian 12 when booted in BIOS mode diff --git a/res/appstore/listing/listing.csv b/res/appstore/listing/listing.csv index 737b0c90..d3451c8e 100644 --- a/res/appstore/listing/listing.csv +++ b/res/appstore/listing/listing.csv @@ -114,14 +114,14 @@ • Trang web chính thức: https://rufus.ie • Mã nguồn: https://github.com/pbatard/rufus • Nhật ký thay đổi: https://github.com/pbatard/rufus/blob/master/ChangeLog.txt" -"ReleaseNotes","3","Text","• Add detection and warning for UEFI revoked bootloaders (including ones revoked through SkuSiPolicy.p7b) -• Add ZIP64 support, to extract .zip images that are larger than 4 GB -• Add saving and restoring current drive to/from compressed VHDX image -• Add saving and restoring current drive to/from compressed FFU (Full Flash Update) image [EXPERIMENTAL] -• Fix a crash when trying to open Windows ISOs, with the MinGW compiled x86 32-bit version -• Fix an issue where ISOs that contain a boot image with an 'EFI' label are not be detected bootable -• Increase the ISO → ESP limit for Debian 12 netinst images -• Ensure that the main partition size is aligned to the cluster size",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"ReleaseNotes","3","Text","• Add support for Rock Ridge symbolic links preservation when NTFS is used +• Add an exception to enforce NTFS for Linux Mint's LMDE +• Add an expert feature to restrict a Windows installation to S Mode +• Fix persistence support for Debian 12 when booted in BIOS mode +• Fix a regression that prevented the opening of .vhd images +• Update UEFI:NTFS to report a more explicit error on bootmgr security issues +• Improve the search for conflicting processes by running it in a background thread +• Improve support for Slax Linux",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "Title","4","Text","Rufus",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "ShortTitle","5","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "SortTitle","6","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/process.c b/src/process.c index c9fc9d06..00f8e670 100644 --- a/src/process.c +++ b/src/process.c @@ -867,7 +867,7 @@ BOOL SetProcessSearch(DWORD DeviceNum) blocking_process.nVersion[0]++; blocking_process.bActive = TRUE; if (!SetEvent(blocking_process.hStart)) - uprintf("Could not send start event to process search: %s", WindowsErrorString); + uprintf("Could not signal start event to process search: %s", WindowsErrorString()); return ReleaseMutex(blocking_process.hLock); } diff --git a/src/rufus.rc b/src/rufus.rc index 2d91ece5..544ee160 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2089" +CAPTION "Rufus 4.3.2090" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2089,0 - PRODUCTVERSION 4,3,2089,0 + FILEVERSION 4,3,2090,0 + PRODUCTVERSION 4,3,2090,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2089" + VALUE "FileVersion", "4.3.2090" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2089" + VALUE "ProductVersion", "4.3.2090" END END BLOCK "VarFileInfo" From 58c56eb398dcc2d4168d4d4dee221197c109f32c Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 27 Oct 2023 22:43:50 +0200 Subject: [PATCH 31/53] [vhd] fix a crash when saving .ffu images with release version * Also update Rufus next to 4.4 --- configure | 20 ++++++++++---------- configure.ac | 2 +- src/rufus.rc | 12 ++++++------ src/vhd.c | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/configure b/configure index 69835a42..06fbccde 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for rufus 4.3. +# Generated by GNU Autoconf 2.71 for rufus 4.4. # # Report bugs to . # @@ -611,8 +611,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='rufus' PACKAGE_TARNAME='rufus' -PACKAGE_VERSION='4.3' -PACKAGE_STRING='rufus 4.3' +PACKAGE_VERSION='4.4' +PACKAGE_STRING='rufus 4.4' PACKAGE_BUGREPORT='https://github.com/pbatard/rufus/issues' PACKAGE_URL='https://rufus.ie' @@ -1269,7 +1269,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures rufus 4.3 to adapt to many kinds of systems. +\`configure' configures rufus 4.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1336,7 +1336,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of rufus 4.3:";; + short | recursive ) echo "Configuration of rufus 4.4:";; esac cat <<\_ACEOF @@ -1428,7 +1428,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -rufus configure 4.3 +rufus configure 4.4 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1504,7 +1504,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by rufus $as_me 4.3, which was +It was created by rufus $as_me 4.4, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2767,7 +2767,7 @@ fi # Define the identity of the package. PACKAGE='rufus' - VERSION='4.3' + VERSION='4.4' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -5309,7 +5309,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by rufus $as_me 4.3, which was +This file was extended by rufus $as_me 4.4, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -5365,7 +5365,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -rufus config.status 4.3 +rufus config.status 4.4 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index d1f2b960..be4e41d4 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([rufus], [4.3], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) +AC_INIT([rufus], [4.4], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) AM_INIT_AUTOMAKE([-Wno-portability foreign no-dist no-dependencies]) AC_CONFIG_SRCDIR([src/rufus.c]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/src/rufus.rc b/src/rufus.rc index 544ee160..b865bf40 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2090" +CAPTION "Rufus 4.4.2091" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2090,0 - PRODUCTVERSION 4,3,2090,0 + FILEVERSION 4,4,2091,0 + PRODUCTVERSION 4,4,2091,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2090" + VALUE "FileVersion", "4.4.2091" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" - VALUE "OriginalFilename", "rufus-4.3.exe" + VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2090" + VALUE "ProductVersion", "4.4.2091" END END BLOCK "VarFileInfo" diff --git a/src/vhd.c b/src/vhd.c index 410ed97c..60508262 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -1085,9 +1085,9 @@ static DWORD WINAPI FfuSaveImageThread(void* param) { DWORD r; IMG_SAVE* img_save = (IMG_SAVE*)param; - char cmd[MAX_PATH + 128], *letter = "", *label; + char cmd[MAX_PATH + 128], letters[27], *label; - GetDriveLabel(SelectedDrive.DeviceNumber, letter, &label, TRUE); + GetDriveLabel(SelectedDrive.DeviceNumber, letters, &label, TRUE); static_sprintf(cmd, "dism /Capture-Ffu /CaptureDrive:%s /ImageFile:\"%s\" " "/Name:\"%s\" /Description:\"Created by %s (%s)\"", img_save->DevicePath, img_save->ImagePath, label, APPLICATION_NAME, RUFUS_URL); From e0b5c6d96f24ae1d4bd2c8a124e3ffe1d04abc54 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 9 Nov 2023 17:39:38 +0000 Subject: [PATCH 32/53] [misc] improve the code related to the commandline hogger deletion * Also small additional code improvements --- _sign.cmd | 2 +- src/rufus.c | 32 ++++++++++++++++---------------- src/rufus.rc | 10 +++++----- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/_sign.cmd b/_sign.cmd index e8c149b3..ca68f4f1 100644 --- a/_sign.cmd +++ b/_sign.cmd @@ -1,2 +1,2 @@ @echo off -"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\signtool" sign /v /sha1 3dbc3a2a0e9ce8803b422cfdbc60acd33164965d /fd SHA256 /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 %1 +"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\signtool" sign /v /sha1 3dbc3a2a0e9ce8803b422cfdbc60acd33164965d /fd SHA256 /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 %* diff --git a/src/rufus.c b/src/rufus.c index 6d719b0b..ba376861 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -3364,8 +3364,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine // and the automated VS2019 package building process doesn't like renaming the .exe // right under its nose (else we would use the same trick as for portable vs regular) // we use yet another workaround to detect if we are running the AppStore version... - static_sprintf(ini_path, "%srufus.app", app_dir); - if (PathFileExistsU(ini_path)) { + static_sprintf(tmp_path, "%srufus.app", app_dir); + if (PathFileExistsU(tmp_path)) { appstore_version = TRUE; goto skip_args_processing; } @@ -3507,7 +3507,7 @@ skip_args_processing: static_strcpy(app_data_dir, app_dir); fclose(fd); } - uprintf("Will use settings from %s", (ini_file != NULL)?"INI file":"registry"); + uprintf("Will use settings from %s", (ini_file != NULL) ? "INI file" : "registry"); // Use the locale specified by the settings, if any tmp = ReadSettingStr(SETTING_LOCALE); @@ -3629,7 +3629,7 @@ skip_args_processing: // Prevent 2 applications from running at the same time, unless "/W" is passed as an option // in which case we wait for the mutex to be relinquished - if ((safe_strlen(lpCmdLine)==2) && (lpCmdLine[0] == '/') && (lpCmdLine[1] == 'W')) + if ((safe_strlen(lpCmdLine) == 2) && (lpCmdLine[0] == '/') && (lpCmdLine[1] == 'W')) wait_for_mutex = 150; // Try to acquire the mutex for 15 seconds mutex = CreateMutexA(NULL, TRUE, "Global/" APPLICATION_NAME); for (;(wait_for_mutex>0) && (mutex != NULL) && (GetLastError() == ERROR_ALREADY_EXISTS); wait_for_mutex--) { @@ -3653,9 +3653,8 @@ skip_args_processing: IGNORE_RETVAL(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); // Some dialogs have Rich Edit controls and won't display without this - if (GetLibraryHandle("Riched20") == NULL) { - uprintf("Could not load RichEdit library - some dialogs may not display: %s\n", WindowsErrorString()); - } + if (GetLibraryHandle("Riched20") == NULL) + uprintf("Could not load RichEdit library - some dialogs may not display: %s", WindowsErrorString()); // Increase the application privileges (SE_DEBUG_PRIVILEGE), so that we can report // the Windows Services preventing access to the disk or volume we want to format. @@ -4101,10 +4100,14 @@ extern int TestHashes(void); } out: + _chdirU(app_dir); // Destroy the hogger mutex first, so that the cmdline app can exit and we can delete it - if (attached_console && !disable_hogger) { + if (hogmutex != NULL) { ReleaseMutex(hogmutex); safe_closehandle(hogmutex); + // Unconditional delete with retry, just in case... + for (i = 0; (!DeleteFileA(cmdline_hogger)) && (i <= 10); i++) + Sleep(200); } // Kill the update check thread if running if (update_check_thread != NULL) @@ -4126,7 +4129,8 @@ out: safe_free(fido_script); safe_free(pe256ssp); if (argv != NULL) { - for (i=0; i Date: Thu, 9 Nov 2023 18:06:06 +0000 Subject: [PATCH 33/53] [iso] add BIOS support for Artix Linux * Unfortunately, the Artix maintainers decided *NOT* to include the fat GRUB module for UEFI, so this will only work for BIOS boot... --- src/iso.c | 3 ++- src/rufus.rc | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/iso.c b/src/iso.c index 7f7535e1..cac0ab1a 100644 --- a/src/iso.c +++ b/src/iso.c @@ -394,9 +394,10 @@ static void fix_config(const char* psz_fullpath, const char* psz_path, const cha //'linux' but '$linux'... and we have to add a workaround for that. // Then, newer Arch and derivatives added an extra "search --label ..." command // in their GRUB conf, which we need to cater for in supplement of the kernel line. + // Then Artix called in and decided they would use a "for kopt ..." loop. // Finally, we're just shoving the known isolinux/syslinux tokens in there to process // all config files equally. - static const char* cfg_token[] = { "options", "append", "linux", "linuxefi", "$linux", "search" }; + static const char* cfg_token[] = { "options", "append", "linux", "linuxefi", "$linux", "search", "for"}; iso_label = replace_char(img_report.label, ' ', "\\x20"); usb_label = replace_char(img_report.usb_label, ' ', "\\x20"); if ((iso_label != NULL) && (usb_label != NULL)) { diff --git a/src/rufus.rc b/src/rufus.rc index d1316992..d2909e36 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2092" +CAPTION "Rufus 4.4.2093" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2092,0 - PRODUCTVERSION 4,4,2092,0 + FILEVERSION 4,4,2093,0 + PRODUCTVERSION 4,4,2093,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2092" + VALUE "FileVersion", "4.4.2093" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2092" + VALUE "ProductVersion", "4.4.2093" END END BLOCK "VarFileInfo" From b6d14d46df64f6d9692e103b3f12e8c6c3fe1c42 Mon Sep 17 00:00:00 2001 From: johnloopi <147723143+johnloopi@users.noreply.github.com> Date: Sat, 28 Oct 2023 18:19:03 +0200 Subject: [PATCH 34/53] [loc] Fixed typo in Norwegian translation * Closes #2351 --- res/loc/po/nb-NO.po | 2 +- res/loc/rufus.loc | 2 +- src/rufus.rc | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/res/loc/po/nb-NO.po b/res/loc/po/nb-NO.po index 00c66be3..bb19d079 100644 --- a/res/loc/po/nb-NO.po +++ b/res/loc/po/nb-NO.po @@ -1839,7 +1839,7 @@ msgstr "Ikke gi Windows To Go tilgang til interne harddisker" #. • MSG_333 msgid "Create a local account with username:" -msgstr "Lag en lokal brukerkongot med med brukernavnet:" +msgstr "Lag en lokal brukerkonto med brukernavnet:" #. • MSG_334 msgid "Set regional options to the same values as this user's" diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 6456538f..2fc43bf6 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -9575,7 +9575,7 @@ t MSG_329 "Fjern krav om 4GB+ RAM, Secure Boot og TPM 2.0" t MSG_330 "Fjern krav om en Microsoft konto" t MSG_331 "SlÃ¥ av all datainnsamling (hopp over spørsmÃ¥lene)" t MSG_332 "Ikke gi Windows To Go tilgang til interne harddisker" -t MSG_333 "Lag en lokal brukerkongot med med brukernavnet:" +t MSG_333 "Lag en lokal brukerkonto med brukernavnet:" t MSG_334 "Sett regionale instillinger til det samme som den aktive brukerkontoen" t MSG_335 "Deaktiver BitLocker kryptering av enheten" t MSG_336 "Behold log" diff --git a/src/rufus.rc b/src/rufus.rc index d2909e36..f8d1391a 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2093" +CAPTION "Rufus 4.4.2094" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2093,0 - PRODUCTVERSION 4,4,2093,0 + FILEVERSION 4,4,2094,0 + PRODUCTVERSION 4,4,2094,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2093" + VALUE "FileVersion", "4.4.2094" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2093" + VALUE "ProductVersion", "4.4.2094" END END BLOCK "VarFileInfo" From 965d82c425f109dd2a7276487f007ae522eeb36d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:24:40 +0000 Subject: [PATCH 35/53] bump dessant/lock-threads from 4 to 5 (#2359) Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 4 to 5. - [Release notes](https://github.com/dessant/lock-threads/releases) - [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md) - [Commits](https://github.com/dessant/lock-threads/compare/v4...v5) --- updated-dependencies: - dependency-name: dessant/lock-threads dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 7be2799c..ef9e5f1a 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,7 +9,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} issue-inactive-days: '90' From 51569d9e13b7a055e78eea875270481440774828 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 8 Jan 2024 14:01:30 +0000 Subject: [PATCH 36/53] [misc] silence Coverity warnings * Also update copyright year and improve uprintf error handling * Also bump GitHub Actions dependencies. Note that we do NOT want to update to upload-artifact v4 because it BREAKS the creation of artifacts from matrix. See: https://github.com/actions/upload-artifact#v4---whats-new * Closes #2382 * Closes #2383 --- .github/workflows/codeql.yml | 4 ++-- src/localization_data.sh | 2 +- src/process.c | 11 +++++++---- src/rufus.rc | 12 ++++++------ src/stdio.c | 19 +++++++++++++++---- src/stdlg.c | 4 ++-- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6fc37b76..279e1899 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp @@ -56,4 +56,4 @@ jobs: run: msbuild ${{env.SOLUTION_FILE_PATH}} /m /p:Configuration=${{ env.BUILD_CONFIGURATION}},Platform=${{ env.TARGET_PLATFORM }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/src/localization_data.sh b/src/localization_data.sh index c2a9ce71..b9568e34 100755 --- a/src/localization_data.sh +++ b/src/localization_data.sh @@ -12,7 +12,7 @@ cat > cmd.sed <<\_EOF 1i /*\ * Rufus: The Reliable USB Formatting Utility\ * Localization tables - autogenerated from resource.h\ - * Copyright © 2013-2023 Pete Batard \ + * Copyright © 2013-2024 Pete Batard \ *\ * This program is free software: you can redistribute it and/or modify\ * it under the terms of the GNU General Public License as published by\ diff --git a/src/process.c b/src/process.c index 00f8e670..9511808e 100644 --- a/src/process.c +++ b/src/process.c @@ -4,7 +4,7 @@ * * Modified from System Informer (a.k.a. Process Hacker): * https://github.com/winsiderss/systeminformer - * Copyright © 2017-2023 Pete Batard + * Copyright © 2017-2024 Pete Batard * Copyright © 2017 dmex * Copyright © 2009-2016 wj32 * @@ -589,6 +589,7 @@ static DWORD WINAPI SearchProcessThread(LPVOID param) pe[j].seen_on_pass = blocking_process.nPass; static_strcpy(pe[j].cmdline, cmdline); } else if (usb_debug) { + // coverity[dont_call] OutputDebugStringA("SearchProcessThread: No empty slot!\n"); } ReleaseMutex(hLock); @@ -726,10 +727,12 @@ static DWORD WINAPI SearchProcessThread(LPVOID param) // We are the only ones updating the counter so no need for lock blocking_process.nPass++; // In extended debug mode, notify how much time our search took to the debug facility - static_sprintf(tmp, "Process search run #%d completed in %llu ms\n", - blocking_process.nPass, GetTickCount64() - start_time); - if (usb_debug) + if (usb_debug) { + static_sprintf(tmp, "Process search run #%d completed in %llu ms\n", + blocking_process.nPass, GetTickCount64() - start_time); + // coverity[dont_call] OutputDebugStringA(tmp); + } Sleep(1000); } diff --git a/src/rufus.rc b/src/rufus.rc index f8d1391a..fda20577 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2094" +CAPTION "Rufus 4.4.2096" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2094,0 - PRODUCTVERSION 4,4,2094,0 + FILEVERSION 4,4,2096,0 + PRODUCTVERSION 4,4,2096,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2094" + VALUE "FileVersion", "4.4.2096" VALUE "InternalName", "Rufus" - VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" + VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2094" + VALUE "ProductVersion", "4.4.2096" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index 493345cf..bb9a682f 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Standard User I/O Routines (logging, status, error, etc.) - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * Copyright © 2020 Mattiwatti * * This program is free software: you can redistribute it and/or modify @@ -85,6 +85,7 @@ void uprintf(const char *format, ...) wbuf = utf8_to_wchar(buf); // Send output to Windows debug facility + // coverity[dont_call] OutputDebugStringW(wbuf); if ((hLog != NULL) && (hLog != INVALID_HANDLE_VALUE)) { // Send output to our log Window @@ -100,6 +101,7 @@ void uprintfs(const char* str) { wchar_t* wstr; wstr = utf8_to_wchar(str); + // coverity[dont_call] OutputDebugStringW(wstr); if ((hLog != NULL) && (hLog != INVALID_HANDLE_VALUE)) { Edit_SetSel(hLog, MAX_LOG_SIZE, MAX_LOG_SIZE); @@ -257,11 +259,20 @@ const char *WindowsErrorString(void) &err_string[presize], (DWORD)(sizeof(err_string)-strlen(err_string)), NULL); if (size == 0) { format_error = GetLastError(); - if ((format_error) && (format_error != ERROR_MR_MID_NOT_FOUND) && (format_error != ERROR_MUI_FILE_NOT_LOADED)) + switch (format_error) { + case ERROR_SUCCESS: + static_sprintf(err_string, "[0x%08lX] (No Windows Error String)", error_code); + break; + case ERROR_MR_MID_NOT_FOUND: + case ERROR_MUI_FILE_NOT_FOUND: + case ERROR_MUI_FILE_NOT_LOADED: + static_sprintf(err_string, "[0x%08lX] (NB: This system was unable to provide an English error message)", error_code); + break; + default: static_sprintf(err_string, "[0x%08lX] (FormatMessage error code 0x%08lX)", error_code, format_error); - else - static_sprintf(err_string, "[0x%08lX] (No Windows Error String)", error_code); + break; + } } else { // Microsoft may suffix CRLF to error messages, which we need to remove... assert(presize > 2); diff --git a/src/stdlg.c b/src/stdlg.c index 012a38b6..11450198 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Standard Dialog Routines (Browse for folder, About, etc) - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -386,7 +386,7 @@ INT_PTR CALLBACK AboutCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lP ResizeButtonHeight(hDlg, IDOK); static_sprintf(about_blurb, about_blurb_format, lmprintf(MSG_174|MSG_RTF), lmprintf(MSG_175|MSG_RTF, rufus_version[0], rufus_version[1], rufus_version[2]), - "Copyright © 2011-2023 Pete Batard", + "Copyright © 2011-2024 Pete Batard", lmprintf(MSG_176|MSG_RTF), lmprintf(MSG_177|MSG_RTF), lmprintf(MSG_178|MSG_RTF)); for (i = 0; i < ARRAYSIZE(hEdit); i++) { hEdit[i] = GetDlgItem(hDlg, edit_id[i]); From ebe01cc7b691cf1014d21dbd719ed209ac5d647e Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 8 Jan 2024 16:43:52 +0000 Subject: [PATCH 37/53] [dev] filter out Microsoft Dev Drives * Microsoft Dev Drives are VHDs consisting of a small MSR followed by a large (50 GB or more) ReFS partition. See https://learn.microsoft.com/en-us/windows/dev-drive/. * Closes #2395. --- src/dev.c | 6 +++++- src/drive.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/drive.h | 3 ++- src/rufus.rc | 10 +++++----- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/dev.c b/src/dev.c index 856883f5..d70631cb 100644 --- a/src/dev.c +++ b/src/dev.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Device detection and enumeration - * Copyright © 2014-2023 Pete Batard + * Copyright © 2014-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -933,6 +933,10 @@ BOOL GetDevices(DWORD devnum) uprintf("To use such a card, check 'List USB Hard Drives' under 'advanced drive properties'"); safe_free(devint_detail_data); break; + } else if (props.is_VHD && IsMsDevDrive(drive_index)) { + uprintf("Device eliminated because it was detected as a Microsoft Dev Drive"); + safe_free(devint_detail_data); + break; } // Windows 10 19H1 mounts a 'PortableBaseLayer' for its Windows Sandbox feature => unlist those if (safe_strcmp(label, windows_sandbox_vhd_label) == 0) { diff --git a/src/drive.c b/src/drive.c index b432a5ef..bea54e79 100644 --- a/src/drive.c +++ b/src/drive.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Drive access function calls - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -2645,3 +2645,42 @@ const char* GetGPTPartitionType(const GUID* guid) for (i = 0; (i < ARRAYSIZE(gpt_type)) && !CompareGUID(guid, gpt_type[i].guid); i++); return (i < ARRAYSIZE(gpt_type)) ? gpt_type[i].name : GuidToString(guid, TRUE); } + +/* + * Detect Microsoft Dev Drives, which are VHDs consisting of a small MSR followed by a large + * (50 GB or more) ReFS partition. See https://learn.microsoft.com/en-us/windows/dev-drive/. + * NB: Despite the option being proposed, I have *NOT* been able to create MBR-based Dev Drives. + */ +BOOL IsMsDevDrive(DWORD DriveIndex) +{ + BOOL r, ret = FALSE; + DWORD size = 0; + HANDLE hPhysical = INVALID_HANDLE_VALUE; + BYTE layout[4096] = { 0 }; + PDRIVE_LAYOUT_INFORMATION_EX DriveLayout = (PDRIVE_LAYOUT_INFORMATION_EX)(void*)layout; + + hPhysical = GetPhysicalHandle(DriveIndex, FALSE, FALSE, TRUE); + if (hPhysical == INVALID_HANDLE_VALUE) + goto out; + + r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, + NULL, 0, layout, sizeof(layout), &size, NULL); + if (!r || size <= 0) + goto out; + + if (DriveLayout->PartitionStyle != PARTITION_STYLE_GPT) + goto out; + if (DriveLayout->PartitionCount != 2) + goto out; + if (!CompareGUID(&DriveLayout->PartitionEntry[0].Gpt.PartitionType, &PARTITION_MICROSOFT_RESERVED)) + goto out; + if (!CompareGUID(&DriveLayout->PartitionEntry[1].Gpt.PartitionType, &PARTITION_MICROSOFT_DATA)) + goto out; + if (DriveLayout->PartitionEntry[1].PartitionLength.QuadPart < 20 * GB) + goto out; + ret = (strcmp(GetFsName(hPhysical, DriveLayout->PartitionEntry[1].StartingOffset), "ReFS") == 0); + +out: + safe_closehandle(hPhysical); + return ret; +} diff --git a/src/drive.h b/src/drive.h index 840faf86..36752572 100644 --- a/src/drive.h +++ b/src/drive.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Drive access function calls - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -421,3 +421,4 @@ BOOL RefreshLayout(DWORD DriveIndex); BOOL GetOpticalMedia(IMG_SAVE* img_save); uint64_t GetEspOffset(DWORD DriveIndex); BOOL ToggleEsp(DWORD DriveIndex, uint64_t PartitionOffset); +BOOL IsMsDevDrive(DWORD DriveIndex); diff --git a/src/rufus.rc b/src/rufus.rc index fda20577..f51b008f 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2096" +CAPTION "Rufus 4.4.2097" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2096,0 - PRODUCTVERSION 4,4,2096,0 + FILEVERSION 4,4,2097,0 + PRODUCTVERSION 4,4,2097,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2096" + VALUE "FileVersion", "4.4.2097" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2096" + VALUE "ProductVersion", "4.4.2097" END END BLOCK "VarFileInfo" From 70e87482c1f5c0058c64e0f7e60f51551886b6f1 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 10 Jan 2024 12:53:07 +0000 Subject: [PATCH 38/53] [misc] add some more Windows edition names * Closes #2380 * Also fix a typo in the Norwegian translation, with thanks to @Legendarion * Closes #2397 --- res/loc/po/nb-NO.po | 6 +++--- res/loc/rufus.loc | 2 +- src/rufus.rc | 10 +++++----- src/stdfn.c | 14 +++++++++++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/res/loc/po/nb-NO.po b/res/loc/po/nb-NO.po index bb19d079..263f6801 100644 --- a/res/loc/po/nb-NO.po +++ b/res/loc/po/nb-NO.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 3.22\n" "Report-Msgid-Bugs-To: pete@akeo.ie\n" "POT-Creation-Date: 2023-04-17 13:44+0100\n" -"PO-Revision-Date: 2023-04-17 13:46+0100\n" +"PO-Revision-Date: 2024-01-10 12:20+0000\n" "Last-Translator: \n" "Language-Team: \n" "Language: nb_NO\n" @@ -13,7 +13,7 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "X-Rufus-LanguageName: Norwegian (Norsk)\n" "X-Rufus-LCID: 0x0414\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.4.2\n" #. • IDD_DIALOG → IDS_DRIVE_PROPERTIES_TXT msgid "Drive Properties" @@ -803,7 +803,7 @@ msgstr "" #. • MSG_117 msgid "Standard Windows installation" -msgstr "Standard Windows instalasjon" +msgstr "Standard Windows installasjon" #. • MSG_118 #. diff --git a/res/loc/rufus.loc b/res/loc/rufus.loc index 2fc43bf6..4bb45c9d 100644 --- a/res/loc/rufus.loc +++ b/res/loc/rufus.loc @@ -9365,7 +9365,7 @@ t MSG_113 "Stort UDF-volum" t MSG_114 "Dette bildet bruker Syslinux %s%s men dette programmet inkluderer kun installsjonsfiler for Syslinux %s%s.\n\nNye versjoner av Syslinux er ikke kompatible med hverandre, og dermed ikke mulig for Rufus Ã¥ inkludere alle, 2 ekstra filer mÃ¥ lastes ned fra Internett ('ldlinux.sys' og 'ldlinux.bss'):\n- Velg 'Ja' For Ã¥ koble til internett og laste ned disse filene\n- Velg 'Nei' for Ã¥ avbryte\n\nOBS: Filene vil bli lastet ned i det aktuelle programmets mappe og vil bli brukt automatisk hvis de er tilstede." t MSG_115 "Nedlasting pÃ¥krevd" t MSG_116 "Dette bildet bruker Grub %s, men programmet inkluderer kun installasjonsfiler forGrub %s.\n\nForskjellige versjoner av Grub er muligens ikke kompatible med hverandre, og er ikke mulig Ã¥ inkludere dem alle, Rufus vil prøve Ã¥ finne en versjon av Grub installasjonfilen ('core.img') som passer til ditt bilde:\n- Velg 'Ja' for Ã¥ koble til internett og forsøke Ã¥ laste den ned\n- Velg 'Nei' for Ã¥ bruke den innebygde versjonen uansett\n- Velg 'Avbryt' for Ã¥ avbryte\n\nOBS: Filen vil bli lastet ned i den aktuelle program-mappen og vil bli brukt automatisk hvis tilgjengelig. Hvis ingen treff kan bli funnet, den innebygde versjonen vil bli brukt." -t MSG_117 "Standard Windows instalasjon" +t MSG_117 "Standard Windows installasjon" t MSG_119 "avanserte stasjonsegenskaper" t MSG_120 "avanserte formatalternativer" t MSG_121 "Vis %s" diff --git a/src/rufus.rc b/src/rufus.rc index f51b008f..0402b6f5 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2097" +CAPTION "Rufus 4.4.2098" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2097,0 - PRODUCTVERSION 4,4,2097,0 + FILEVERSION 4,4,2098,0 + PRODUCTVERSION 4,4,2098,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2097" + VALUE "FileVersion", "4.4.2098" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2097" + VALUE "ProductVersion", "4.4.2098" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index d4076364..f23019c0 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Standard Windows function calls - * Copyright © 2013-2023 Pete Batard + * Copyright © 2013-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -302,10 +302,18 @@ static const char* GetEdition(DWORD ProductType) case 0x000000A5: return "Pro for Education N"; case 0x000000AB: return "Enterprise G"; // I swear Microsoft are just making up editions... case 0x000000AC: return "Enterprise G N"; + case 0x000000B2: return "Cloud"; + case 0x000000B3: return "Cloud N"; case 0x000000B6: return "Home OS"; - case 0x000000B7: return "Cloud E"; - case 0x000000B8: return "Cloud E N"; + case 0x000000B7: case 0x000000CB: return "Cloud E"; + case 0x000000B9: return "IoT OS"; + case 0x000000BA: case 0x000000CA: return "Cloud E N"; + case 0x000000BB: return "IoT Edge OS"; + case 0x000000BC: return "IoT Enterprise"; case 0x000000BD: return "Lite"; + case 0x000000BF: return "IoT Enterprise S"; + case 0x000000C0: case 0x000000C2: case 0x000000C3: case 0x000000C4: case 0x000000C5: case 0x000000C6: return "XBox"; + case 0x000000C7: case 0x000000C8: case 0x00000196: case 0x00000197: case 0x00000198: return "Azure Server"; case 0xABCDABCD: return "(Unlicensed)"; default: static_sprintf(unknown_edition_str, "(Unknown Edition 0x%02X)", (uint32_t)ProductType); From 2cebf914fd1473c82f266dbc0f1376ee5b900ba6 Mon Sep 17 00:00:00 2001 From: Fred <8153358+slackingfred@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:31:41 -0800 Subject: [PATCH 39/53] [fat] align start of data region to MB * Closes #2387 --- src/format_fat32.c | 9 ++++++++- src/rufus.rc | 10 +++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/format_fat32.c b/src/format_fat32.c index 451f6605..230a09ac 100644 --- a/src/format_fat32.c +++ b/src/format_fat32.c @@ -175,6 +175,7 @@ BOOL FormatLargeFAT32(DWORD DriveIndex, uint64_t PartitionOffset, DWORD ClusterS DWORD BytesPerSect = 0; DWORD SectorsPerCluster = 0; DWORD TotalSectors = 0; + DWORD AlignSectors = 0; DWORD SystemAreaSize = 0; DWORD UserAreaSize = 0; ULONGLONG qTotalSectors = 0; @@ -295,7 +296,6 @@ BOOL FormatLargeFAT32(DWORD DriveIndex, uint64_t PartitionOffset, DWORD ClusterS SectorsPerCluster = ClusterSize / BytesPerSect; pFAT32BootSect->bSecPerClus = (BYTE)SectorsPerCluster; - pFAT32BootSect->wRsvdSecCnt = (WORD)ReservedSectCount; pFAT32BootSect->bNumFATs = (BYTE)NumFATs; pFAT32BootSect->wRootEntCnt = 0; pFAT32BootSect->wTotSec16 = 0; @@ -310,6 +310,13 @@ BOOL FormatLargeFAT32(DWORD DriveIndex, uint64_t PartitionOffset, DWORD ClusterS FatSize = GetFATSizeSectors(pFAT32BootSect->dTotSec32, pFAT32BootSect->wRsvdSecCnt, pFAT32BootSect->bSecPerClus, pFAT32BootSect->bNumFATs, BytesPerSect); + // Update reserved sector count so that the start of data region is aligned to a MB boundary + SystemAreaSize = ReservedSectCount + NumFATs * FatSize; + AlignSectors = (1 * MB) / BytesPerSect; + SystemAreaSize = (SystemAreaSize + AlignSectors - 1) / AlignSectors * AlignSectors; + ReservedSectCount = SystemAreaSize - NumFATs * FatSize; + + pFAT32BootSect->wRsvdSecCnt = (WORD)ReservedSectCount; pFAT32BootSect->dFATSz32 = FatSize; pFAT32BootSect->wExtFlags = 0; pFAT32BootSect->wFSVer = 0; diff --git a/src/rufus.rc b/src/rufus.rc index 0402b6f5..9737005a 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2098" +CAPTION "Rufus 4.4.2099" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2098,0 - PRODUCTVERSION 4,4,2098,0 + FILEVERSION 4,4,2099,0 + PRODUCTVERSION 4,4,2099,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2098" + VALUE "FileVersion", "4.4.2099" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2098" + VALUE "ProductVersion", "4.4.2099" END END BLOCK "VarFileInfo" From ae6732c07b0802ebf96c3b34a791eb9c5f089f5d Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 16 Jan 2024 17:21:32 +0000 Subject: [PATCH 40/53] [iso] add basic El-Torito image parsing to libcdio * Based on El-Torito specs found at https://pdos.csail.mit.edu/6.828/2014/readings/boot-cdrom.pdf. * Follows 7-zip's virtual '[BOOT]/#...' naming conventions (though we don't check for the full name). * Limited to 8 NoEmul images. --- src/libcdio/cdio/iso9660.h | 35 ++++++++++++++++++++++- src/libcdio/iso9660/iso9660_fs.c | 49 +++++++++++++++++++++++++++++++- src/rufus.rc | 10 +++---- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/libcdio/cdio/iso9660.h b/src/libcdio/cdio/iso9660.h index 04308909..6812e040 100644 --- a/src/libcdio/cdio/iso9660.h +++ b/src/libcdio/cdio/iso9660.h @@ -162,6 +162,7 @@ extern enum iso_vd_enum_s { extern const char ISO_STANDARD_ID[sizeof("CD001")-1]; #define ISO_STANDARD_ID "CD001" +#define EL_TORITO_ID "EL TORITO SPECIFICATION\0\0\0\0\0\0\0\0\0" #define CDIO_EXTENT_BLOCKS(size) ((size + (ISO_BLOCKSIZE - 1)) / ISO_BLOCKSIZE) @@ -506,6 +507,37 @@ struct iso9660_svd_s { typedef struct iso9660_svd_s iso9660_svd_t; +/*! + \brief ISO-9660 Boot Record Volume Descriptor. + */ +struct iso9660_brvd_s { + uint8_t type; /**< ISO_VD_BOOT_RECORD - 0 */ + char id[5]; /**< ISO_STANDARD_ID "CD001" */ + uint8_t version; /**< value 1 for El Torito */ + char system_id[ISO_MAX_SYSTEM_ID]; /**< Boot system ID */ + uint8_t unused1[32]; /**< unused - value 0 */ + uint32_t boot_catalog_sector; /**< first sector of boot catalog */ + uint8_t unused2[1973]; /**< Unused - value 0 */ +} GNUC_PACKED; + +typedef struct iso9660_brvd_s iso9660_brvd_t; + +/*! + \brief ISO-9660 Boot Record Volume Descriptor. + */ +struct iso9660_br_s { + uint8_t boot_id; /**< Boot indicator - 0x88 */ + uint8_t media_type; /**< Boot media type - 0 for no emul. */ + uint16_t load_seg; /**< Load segment for x86 */ + uint8_t system_type; /**< System type - 0 for x86 */ + uint8_t unused1; + uint16_t num_sectors; /**< Sector count of the image */ + uint32_t image_lsn; /**< Start address of the image */ + uint8_t unused2[20]; +} GNUC_PACKED; + +typedef struct iso9660_br_s iso9660_br_t; + PRAGMA_END_PACKED /*! \brief A data type for a list of ISO9660 @@ -582,7 +614,8 @@ extern enum iso_extension_enum_s { ISO_EXTENSION_JOLIET_LEVEL2 = 0x02, ISO_EXTENSION_JOLIET_LEVEL3 = 0x04, ISO_EXTENSION_ROCK_RIDGE = 0x08, - ISO_EXTENSION_HIGH_SIERRA = 0x10 + ISO_EXTENSION_HIGH_SIERRA = 0x10, + ISO_EXTENSION_EL_TORITO = 0x20 } iso_extension_enums; diff --git a/src/libcdio/iso9660/iso9660_fs.c b/src/libcdio/iso9660/iso9660_fs.c index bb896a53..fc05a401 100644 --- a/src/libcdio/iso9660/iso9660_fs.c +++ b/src/libcdio/iso9660/iso9660_fs.c @@ -59,6 +59,8 @@ #include "_cdio_stdio.h" #include "cdio_private.h" +#define MAX_BOOT_IMAGES 8 + /** Implementation of iso9660_t type */ struct _iso9660_s { cdio_header_t header; /**< Internal header - MUST come first. */ @@ -83,6 +85,10 @@ struct _iso9660_s { be CDIO_CD_FRAMESIZE_RAW (2352) or M2RAW_SECTOR_SIZE (2336). */ + struct { + uint32_t lsn; /**< Start LSN of an El-Torito bootable image */ + uint32_t num_sectors; /**< Number of sectors of a bootable image */ + } boot_img[MAX_BOOT_IMAGES]; int i_fuzzy_offset; /**< Adjustment in bytes to make ISO_STANDARD_ID ("CD001") come out as ISO_PVD_SECTOR (frame 16). Normally this should be 0 @@ -505,7 +511,8 @@ iso9660_ifs_read_superblock (iso9660_t *p_iso, iso_extension_mask_t iso_extension_mask) { iso9660_svd_t p_svd; /* Secondary volume descriptor. */ - int i; + iso9660_brvd_t* p_brvd = (iso9660_brvd_t*)&p_svd; /* Boot record volume descriptor. */ + int i, j, k; if (!p_iso || !iso9660_ifs_read_pvd(p_iso, &(p_iso->pvd))) return false; @@ -516,6 +523,24 @@ iso9660_ifs_read_superblock (iso9660_t *p_iso, for (i=1; (0 != iso9660_iso_seek_read (p_iso, &p_svd, ISO_PVD_SECTOR+i, 1)); i++) { if (ISO_VD_END == from_711(p_svd.type) ) /* Last SVD */ break; + if (iso_extension_mask & ISO_EXTENSION_EL_TORITO) { + /* Check for an El-Torito boot volume descriptor */ + if (ISO_VD_BOOT_RECORD == from_711(p_svd.type) && + (memcmp(p_brvd->system_id, EL_TORITO_ID, ISO_MAX_SYSTEM_ID) == 0)) { + /* Perform very basic parsing of boot entries to fill an image table */ + iso9660_br_t br[ISO_BLOCKSIZE / sizeof(iso9660_br_t)]; + if (iso9660_iso_seek_read(p_iso, &br, p_brvd->boot_catalog_sector, 1) == ISO_BLOCKSIZE) { + for (j = 0, k = 0; + j < (ISO_BLOCKSIZE / sizeof(iso9660_br_t)) && k < MAX_BOOT_IMAGES; + j++) { + if (br[j].boot_id == 0x88 && br[j].media_type == 0) { + p_iso->boot_img[k].lsn = br[j].image_lsn; + p_iso->boot_img[k++].num_sectors = br[j].num_sectors; + } + } + } + } + } if ( ISO_VD_SUPPLEMENTARY == from_711(p_svd.type) ) { /* We're only interested in Joliet => make sure the SVD isn't overwritten */ if (p_iso->u_joliet_level == 0) @@ -1441,6 +1466,28 @@ iso9660_fs_stat_translate (CdIo_t *p_cdio, const char psz_path[]) iso9660_stat_t * iso9660_ifs_stat_translate (iso9660_t *p_iso, const char psz_path[]) { + /* Special case for virtual El-Torito boot images ('[BOOT]/#.img') */ + if (psz_path && (strncmp(psz_path, "[BOOT]/", 7) == 0)) { + int index = psz_path[7] - '0'; + iso9660_stat_t* p_stat; + if (strlen(psz_path) < 8) + return NULL; + if ((psz_path[7] < '0') || (psz_path[7] > '0' + MAX_BOOT_IMAGES - 1)) + return NULL; + if (p_iso->boot_img[index].lsn == 0 || p_iso->boot_img[index].num_sectors == 0) + return NULL; + p_stat = calloc(1, sizeof(iso9660_stat_t) + strlen(psz_path)); + if (!p_stat) { + cdio_warn("Couldn't calloc(1, %d)", (int)sizeof(iso9660_stat_t)); + return NULL; + } + p_stat->lsn = p_iso->boot_img[index].lsn; + p_stat->total_size = p_iso->boot_img[index].num_sectors * ISO_BLOCKSIZE; + p_stat->type = _STAT_FILE; + strcpy(p_stat->filename, psz_path); + return p_stat; + } + return fs_stat_translate(p_iso, (stat_root_t *) _ifs_stat_root, (stat_traverse_t *) _fs_iso_stat_traverse, psz_path); diff --git a/src/rufus.rc b/src/rufus.rc index 9737005a..a0533336 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2099" +CAPTION "Rufus 4.4.2100" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2099,0 - PRODUCTVERSION 4,4,2099,0 + FILEVERSION 4,4,2100,0 + PRODUCTVERSION 4,4,2100,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2099" + VALUE "FileVersion", "4.4.2100" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2099" + VALUE "ProductVersion", "4.4.2100" END END BLOCK "VarFileInfo" From 710bfe7f4de094031f13ffe2b9ccc4b9dab94f92 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 16 Jan 2024 17:27:37 +0000 Subject: [PATCH 41/53] [iso] work around ISOs that use broken symbolic links for UEFI bootloaders * Per linuxmint/linuxmint#622 some ISOs may have a /EFI/boot/bootx64.efi that is a symbolic to a nonexisting file. * This is originally due to a Debian bug that was fixed in: https://salsa.debian.org/live-team/live-build/-/commit/5bff71fea2dd54adcd6c428d3f1981734079a2f7 * Work around this by trying to extract a working bootx64.efi from the El-Torito image. * Also improve DumpFatDir() to not replace already existing files. --- src/iso.c | 26 +++++++++++++++++++++++--- src/rufus.h | 2 +- src/rufus.rc | 10 +++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/iso.c b/src/iso.c index cac0ab1a..0ddda128 100644 --- a/src/iso.c +++ b/src/iso.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * ISO file extraction - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * Based on libcdio's iso & udf samples: * Copyright © 2003-2014 Rocky Bernstein * @@ -292,6 +292,16 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const } } } + // Linux Mint Edge 21.2/Mint 21.3 have an invalid /EFI/boot/bootx64.efi + // because it's a symbolic link to a file that does not exist on the media. + // This is originally due to a Debian bug that was fixed in: + // https://salsa.debian.org/live-team/live-build/-/commit/5bff71fea2dd54adcd6c428d3f1981734079a2f7 + // Because of this, if we detect a small bootx64.efi file, we assert that it's a + // broken link and try to extract a "good" version from the El-Torito image. + if ((safe_stricmp(psz_basename, efi_bootname[2]) == 0) && (file_length < 100)) { + img_report.has_efi |= 0x4000; + static_strcpy(img_report.efi_img_path, "[BOOT]/1-Boot-NoEmul.img"); + } } if (psz_dirname != NULL) { @@ -1277,8 +1287,16 @@ out: SendMessage(hMainDialog, UM_PROGRESS_EXIT, 0, 0); } else { // Solus and other ISOs only provide EFI boot files in a FAT efi.img - if (img_report.has_efi == 0x8000) + // Also work around ISOs that have a borked symbolic link for bootx64.efi. + // See https://github.com/linuxmint/linuxmint/issues/622. + if (img_report.has_efi & 0xc000) { + if (img_report.has_efi & 0x4000) { + uprintf("Broken UEFI bootloader detected - Applying workaround:"); + static_sprintf(path, "%s\\EFI\\boot\\bootx64.efi", dest_dir); + DeleteFileU(path); + } DumpFatDir(dest_dir, 0); + } if (HAS_SYSLINUX(img_report)) { static_sprintf(path, "%s\\syslinux.cfg", dest_dir); // Create a /syslinux.cfg (if none exists) that points to the existing isolinux cfg @@ -1463,6 +1481,8 @@ try_iso: out: safe_closehandle(file_handle); + if (r == 0) + DeleteFileU(dest_file); iso9660_stat_free(p_statbuf); udf_dirent_free(p_udf_root); udf_dirent_free(p_udf_file); @@ -1727,7 +1747,7 @@ BOOL DumpFatDir(const char* path, int32_t cluster) } if (!DumpFatDir(target, dirpos.cluster)) goto out; - } else { + } else if (!PathFileExistsU(target)) { // Need to figure out if it's a .conf file (Damn you Solus!!) EXTRACT_PROPS props = { 0 }; size_t len = strlen(name); diff --git a/src/rufus.h b/src/rufus.h index b1db118e..d20b0e29 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -1,6 +1,6 @@ /* * Rufus: The Reliable USB Formatting Utility - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/rufus.rc b/src/rufus.rc index a0533336..5484cbb6 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2100" +CAPTION "Rufus 4.4.2101" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2100,0 - PRODUCTVERSION 4,4,2100,0 + FILEVERSION 4,4,2101,0 + PRODUCTVERSION 4,4,2101,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2100" + VALUE "FileVersion", "4.4.2101" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2100" + VALUE "ProductVersion", "4.4.2101" END END BLOCK "VarFileInfo" From fff39c56e85d63096e7a7518433165955210879c Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 17 Jan 2024 14:10:46 +0000 Subject: [PATCH 42/53] [misc] fix UEFI:NTFS partition not being added when needed in MBR mode * Also add support for SD card readers identifying themselves as SDXC. --- src/dev.c | 2 +- src/format.c | 5 +++-- src/iso.c | 2 +- src/rufus.rc | 10 +++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/dev.c b/src/dev.c index d70631cb..998afb90 100644 --- a/src/dev.c +++ b/src/dev.c @@ -465,7 +465,7 @@ BOOL GetDevices(DWORD devnum) // Oh, and we also have card devices (e.g. 'SCSI\DiskO2Micro_SD_...') under the SCSI enumerator... const char* scsi_disk_prefix = "SCSI\\Disk"; const char* scsi_card_name[] = { - "_SD_", "_SDHC_", "_MMC_", "_MS_", "_MSPro_", "_xDPicture_", "_O2Media_" + "_SD_", "_SDHC_", "_SDXC_", "_MMC_", "_MS_", "_MSPro_", "_xDPicture_", "_O2Media_" }; const char* usb_speed_name[USB_SPEED_MAX] = { "USB", "USB 1.0", "USB 1.1", "USB 2.0", "USB 3.0", "USB 3.1" }; const char* windows_sandbox_vhd_label = "PortableBaseLayer"; diff --git a/src/format.c b/src/format.c index b9017a37..16a5313d 100644 --- a/src/format.c +++ b/src/format.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Formatting function calls - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1463,7 +1463,8 @@ DWORD WINAPI FormatThread(void* param) extra_partitions = XP_ESP | XP_MSR; else if ( ((fs_type == FS_NTFS) || (fs_type == FS_EXFAT)) && ((boot_type == BT_UEFI_NTFS) || ((boot_type == BT_IMAGE) && IS_EFI_BOOTABLE(img_report) && - ((target_type == TT_UEFI) || (windows_to_go) || (allow_dual_uefi_bios) || (img_report.has_4GB_file)))) ) + ((target_type == TT_UEFI) || (windows_to_go) || (allow_dual_uefi_bios) || + (img_report.has_4GB_file) || (img_report.needs_ntfs)))) ) extra_partitions = XP_UEFI_NTFS; else if ((boot_type == BT_IMAGE) && !write_as_image && HAS_PERSISTENCE(img_report) && persistence_size) extra_partitions = XP_CASPER; diff --git a/src/iso.c b/src/iso.c index 0ddda128..e03dd9b1 100644 --- a/src/iso.c +++ b/src/iso.c @@ -298,7 +298,7 @@ static BOOL check_iso_props(const char* psz_dirname, int64_t file_length, const // https://salsa.debian.org/live-team/live-build/-/commit/5bff71fea2dd54adcd6c428d3f1981734079a2f7 // Because of this, if we detect a small bootx64.efi file, we assert that it's a // broken link and try to extract a "good" version from the El-Torito image. - if ((safe_stricmp(psz_basename, efi_bootname[2]) == 0) && (file_length < 100)) { + if ((safe_stricmp(psz_basename, efi_bootname[2]) == 0) && (file_length < 256)) { img_report.has_efi |= 0x4000; static_strcpy(img_report.efi_img_path, "[BOOT]/1-Boot-NoEmul.img"); } diff --git a/src/rufus.rc b/src/rufus.rc index 5484cbb6..e117105e 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2101" +CAPTION "Rufus 4.4.2102" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2101,0 - PRODUCTVERSION 4,4,2101,0 + FILEVERSION 4,4,2102,0 + PRODUCTVERSION 4,4,2102,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2101" + VALUE "FileVersion", "4.4.2102" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2101" + VALUE "ProductVersion", "4.4.2102" END END BLOCK "VarFileInfo" From b63f9ae93c22fa6bc7e59072ad33285c301304e4 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 17 Jan 2024 14:11:50 +0000 Subject: [PATCH 43/53] Rufus 4.4 (Build 2103) --- ChangeLog.txt | 9 +++++++++ res/appstore/listing/listing.csv | 17 ++++++++--------- src/rufus.rc | 10 +++++----- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index c6808655..e0ff1875 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,12 @@ +o Version 4.4 (2024.01.17) + Add workaround for distros that use broken symbolic links as their UEFI bootloaders (such as Mint 21.3) + Add support for GRUB 2.12 + Fix a crash when saving .ffu images + Fix UEFI:NTFS partition not being added, in MBR mode, for some Linux ISOs + Prevent Microsoft Dev Drives from being listed + Improve support for SDXC card readers + Improve Large FAT32 formatting by aligning start of data regions to 1 MB (courtesy of Fred) + o Version 4.3 (2023.10.19) Add support for Rock Ridge symbolic links preservation when NTFS is used Add an exception to enforce NTFS for Linux Mint's LMDE diff --git a/res/appstore/listing/listing.csv b/res/appstore/listing/listing.csv index d3451c8e..d74efa47 100644 --- a/res/appstore/listing/listing.csv +++ b/res/appstore/listing/listing.csv @@ -114,21 +114,20 @@ • Trang web chính thức: https://rufus.ie • Mã nguồn: https://github.com/pbatard/rufus • Nhật ký thay đổi: https://github.com/pbatard/rufus/blob/master/ChangeLog.txt" -"ReleaseNotes","3","Text","• Add support for Rock Ridge symbolic links preservation when NTFS is used -• Add an exception to enforce NTFS for Linux Mint's LMDE -• Add an expert feature to restrict a Windows installation to S Mode -• Fix persistence support for Debian 12 when booted in BIOS mode -• Fix a regression that prevented the opening of .vhd images -• Update UEFI:NTFS to report a more explicit error on bootmgr security issues -• Improve the search for conflicting processes by running it in a background thread -• Improve support for Slax Linux",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"ReleaseNotes","3","Text","• Add workaround for distros that use broken symbolic links as their UEFI bootloaders (such as Mint 21.3) +• Add support for GRUB 2.12 +• Fix a crash when saving .ffu images +• Fix UEFI:NTFS partition not being added, in MBR mode, for some Linux ISOs +• Prevent Microsoft Dev Drives from being listed +• Improve support for SDXC card readers +• Improve Large FAT32 formatting by aligning start of data regions to 1 MB (courtesy of Fred)",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "Title","4","Text","Rufus",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "ShortTitle","5","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "SortTitle","6","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "VoiceTitle","7","Text","",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "ShortDescription","8","Text","","Rufus - The Reliable USB Formatting Utility","Rufus - أداة ÙØ±Ù…تة الـ USB جديرة بالثقة","Rufus - Ðадеждната USB форматираща програма","Rufus - å¯é çš„ USB æ ¼å¼åŒ–工具","Rufus - 快速å¯é çš„ USB æ ¼å¼åŒ–工具","Rufus - Pouzdan alat za formatiranje USB-a","Rufus - Spolehlivý program pro formátování USB","Rufus - Det pÃ¥lidelige USB-formateringsværktøj","Rufus - de betrouwbare USB-formatteertool","Rufus - Luotettava USB-alustusohjelma","Rufus - L'utilitaire de formatage USB fiable","Rufus - Das zuverlässige USB-Formatierungstool","Rufus - Μία αξιόπιστη εφαÏμογή διαμόÏφωσης USB","Rufus - הכלי ל×תחול USB ×”×מין ביותר","Rufus - A megbízható USB-formázó segédprogram","Rufus - Utilitas Pemformatan USB yang Handal","Rufus - Utility affidabile per la formattazione di unità USB","Rufus - 信頼性ã®é«˜ã„ USB フォーマット ユーティリティ","Rufus - 신뢰할 수 있는 USB í¬ë§· 유틸리티","Rufus - uzticama un vienkÄrÅ¡a USB formatēšanas utilÄ«ta","Rufus - patikima USB formatavimo priemonÄ—","Rufus - Utiliti pemformatan USB yang dipercayai","Rufus - Det pÃ¥litelige USB-formateringsprogrammet","RufusØŒ ابزاری کاربردی Ùˆ قابل‌اطمینان برای ÙØ±Ù…ت کردن درایوهای USB","Rufus - niezawodne narzÄ™dzie do formatowania USB","Rufus - O Utilitário de Formatação USB Confiável","Rufus - O utilitário de confiança para formatação USB","Rufus - Instrumentul de încredere pentru formatări USB","Rufus - ÐÐ°Ð´ÐµÐ¶Ð½Ð°Ñ ÑƒÑ‚Ð¸Ð»Ð¸Ñ‚Ð° Ð´Ð»Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ USB","Rufus - Pouzdan Alat Za Formatiranje USB diska","Rufus - Spoľahlivý program pre formátovanie USB","Rufus - zanesljivi pripomoÄek za USB formatiranje","Rufus, la herramienta de formateo de USBs en la que puedes confiar","Rufus - Det pÃ¥litliga verktyget för USB-formatering","Rufus - ยูทิลิตี้à¸à¸²à¸£à¸Ÿà¸­à¸£à¹Œà¹à¸¡à¸• USB ที่ไว้ใจได้","Rufus - Güvenilir USB Biçimlendirme Programı","Rufus - надійна утиліта Ð´Ð»Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ USB-накопичувачів","Rufus - Tiện ích Äịnh dạng USB Äáng tin cậy" "DevStudio","9","Text","Pete Batard",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -"CopyrightTrademarkInformation","12","Text","© 2011-2023 Pete Batard",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"CopyrightTrademarkInformation","12","Text","© 2011-2024 Pete Batard",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "AdditionalLicenseTerms","13","Text","https://www.gnu.org/licenses/gpl-3.0.html","This application is licensed under the terms of the GNU Public License (GPL) version 3. See https://www.gnu.org/licenses/gpl-3.0.en.html for details.","هذا التطبيق Ù…ÙØ±Ø®Øµ بموجب شروط رخصة جنو (GNU) العمومية (GPL) الإصدار 3. راجع https://www.gnu.org/licenses/gpl-3.0.ar.html لمزيد من Ø§Ù„ØªÙØ§ØµÙŠÙ„.","Тази програма е лицензирана Ñпоред уÑловиÑта на GNU Public License (GPL) верÑÐ¸Ñ 3. diff --git a/src/rufus.rc b/src/rufus.rc index e117105e..fae2d7d8 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2102" +CAPTION "Rufus 4.4.2103" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2102,0 - PRODUCTVERSION 4,4,2102,0 + FILEVERSION 4,4,2103,0 + PRODUCTVERSION 4,4,2103,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2102" + VALUE "FileVersion", "4.4.2103" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2102" + VALUE "ProductVersion", "4.4.2103" END END BLOCK "VarFileInfo" From f6fd520d2a0342c94560902e8f707e7ef090e5fe Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 17 Jan 2024 20:42:59 +0000 Subject: [PATCH 44/53] [appstore] prevent packaging of ALPHA or BETA versions * Also add package version override --- .github/workflows/mingw.yml | 8 ++++++-- .github/workflows/vs2022.yml | 8 ++++++-- res/appstore/packme.cmd | 35 +++++++++++++++++++++++++++++------ src/rufus.rc | 10 +++++----- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 39779c9c..1f2e02bb 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -63,14 +63,18 @@ jobs: shell: bash if: ${{ !startsWith(github.ref, 'refs/tags/') }} # This ONLY works if the shell is bash or if using $env:GITHUB_OUTPUT - run: echo "option=--enable-alpha" >> $GITHUB_OUTPUT + run: | + echo "option=--enable-alpha" >> $GITHUB_OUTPUT + sed -b -i 's/VALUE "InternalName", "Rufus"/VALUE "InternalName", "Rufus (ALPHA)"/' ./src/rufus.rc - name: Set BETA id: set_beta shell: bash if: ${{ startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'BETA') }} # This ONLY works if the shell is bash or if using $env:GITHUB_OUTPUT - run: echo "option=--enable-beta" >> $GITHUB_OUTPUT + run: | + echo "option=--enable-beta" >> $GITHUB_OUTPUT + sed -b -i 's/VALUE "InternalName", "Rufus"/VALUE "InternalName", "Rufus (BETA)"/' ./src/rufus.rc - name: Build run: | diff --git a/.github/workflows/vs2022.yml b/.github/workflows/vs2022.yml index 82ac2bde..b82a70b2 100644 --- a/.github/workflows/vs2022.yml +++ b/.github/workflows/vs2022.yml @@ -54,14 +54,18 @@ jobs: shell: bash if: ${{ !startsWith(github.ref, 'refs/tags/') }} # This ONLY works if the shell is bash or if using $env:GITHUB_OUTPUT - run: echo "option=/DALPHA" >> $GITHUB_OUTPUT + run: | + echo "option=/DALPHA" >> $GITHUB_OUTPUT + sed -b -i 's/VALUE "InternalName", "Rufus"/VALUE "InternalName", "Rufus (ALPHA)"/' ./src/rufus.rc - name: Set BETA id: set_beta shell: bash if: ${{ startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'BETA') }} # This ONLY works if the shell is bash or if using $env:GITHUB_OUTPUT - run: echo "option=/DBETA" >> $GITHUB_OUTPUT + run: | + echo "option=/DBETA" >> $GITHUB_OUTPUT + sed -b -i 's/VALUE "InternalName", "Rufus"/VALUE "InternalName", "Rufus (BETA)"/' ./src/rufus.rc - name: Build shell: cmd diff --git a/res/appstore/packme.cmd b/res/appstore/packme.cmd index 095eacd9..1ecd6f19 100644 --- a/res/appstore/packme.cmd +++ b/res/appstore/packme.cmd @@ -3,6 +3,9 @@ @echo off setlocal EnableExtensions DisableDelayedExpansion +rem if set, this will override the version for the package +rem set VERSION_OVERRIDE=4.4.2104.0 + goto main :ReplaceTokenInFile @@ -66,13 +69,33 @@ for %%a in (%ARCHS%) do ( ) ) +rem exiftool.exe can't be installed in the Windows system directories... +if not exist exiftool.exe ( + echo exiftool.exe must exist in this directory + goto out +) + +rem Make sure we're not trying to create a package from an ALPHA or BETA version! +exiftool -s3 -*InternalName* rufus_x64.exe | findstr /C:"ALPHA" 1>nul && ( + echo Alpha version detected - ABORTED + goto out +) +exiftool -s3 -*InternalName* rufus_x64.exe | findstr /C:"BETA" 1>nul && ( + echo Beta version detected - ABORTED + goto out +) + rem Populate the version from the executable -set target=%~dp0rufus_x64.exe -set target=%target:\=\\% -wmic datafile where "name='%target%'" get version | find /v "Version" > version.txt -set /p VERSION= version.txt + set /p VERSION= Date: Wed, 24 Jan 2024 17:51:40 +0000 Subject: [PATCH 45/53] [iso] improve El Torito image handling * Update to latest libcdio proposal and fix incorrect image size. * Also remove unnecessary calls in packme.cmd. --- res/appstore/packme.cmd | 4 +- src/libcdio/cdio/iso9660.h | 29 +++++----- src/libcdio/cdio/util.h | 6 ++ src/libcdio/driver/util.c | 29 ++++++++++ src/libcdio/iso9660/iso9660_fs.c | 99 ++++++++++++++++++++++++-------- src/rufus.rc | 10 ++-- 6 files changed, 130 insertions(+), 47 deletions(-) diff --git a/res/appstore/packme.cmd b/res/appstore/packme.cmd index 1ecd6f19..04b8f0dc 100644 --- a/res/appstore/packme.cmd +++ b/res/appstore/packme.cmd @@ -86,16 +86,14 @@ exiftool -s3 -*InternalName* rufus_x64.exe | findstr /C:"BETA" 1>nul && ( ) rem Populate the version from the executable -setlocal EnableDelayedExpansion if "%VERSION_OVERRIDE%"=="" ( exiftool -s3 -*FileVersionNumber* rufus_x64.exe > version.txt set /p VERSION= Copyright (C) 2000 Herbert Valerio Riedel @@ -27,7 +27,7 @@ * filesystem library; applications include this. * * See also the ISO-9660 specification. The freely available European - * equivalant standard is called ECMA-119. + * equivalent standard is called ECMA-119. */ @@ -78,7 +78,7 @@ typedef char dchar_t; /*! See section 7.4.1 */ program; things are done this way so that in a debugger one can to refer to the enumeration value names such as in a debugger expression and get something. With the more common a \#define - mechanism, the name/value assocation is lost at run time. + mechanism, the name/value association is lost at run time. */ extern enum iso_enum1_s { ISO_PVD_SECTOR = 16, /**< Sector of Primary Volume Descriptor. */ @@ -93,8 +93,9 @@ extern enum iso_enum1_s { preparer id. */ MAX_ISOPATHNAME = 255, /**< Maximum number of characters in the entire ISO 9660 filename. */ - ISO_BLOCKSIZE = 2048 /**< Number of bytes in an ISO 9660 block. */ - + ISO_BLOCKSIZE = 2048, /**< Number of bytes in an ISO 9660 block. */ + VIRTUAL_SECTORSIZE = 512 /**< Number of bytes in an El Torito virtual + image sector */ } iso_enums1; /*! An enumeration for some of the ISO_* \#defines below. This isn't @@ -396,7 +397,7 @@ typedef struct iso9660_pvd_s iso9660_pvd_t; /*! \brief ISO-9660 Supplementary Volume Descriptor. - This is used for Joliet Extentions and is almost the same as the + This is used for Joliet Extensions and is almost the same as the the primary descriptor but two unused fields, "unused1" and "unused3 become "flags and "escape_sequences" respectively. */ @@ -531,7 +532,7 @@ struct iso9660_br_s { uint16_t load_seg; /**< Load segment for x86 */ uint8_t system_type; /**< System type - 0 for x86 */ uint8_t unused1; - uint16_t num_sectors; /**< Sector count of the image */ + uint16_t num_sectors; /**< Virtual sectors count of the image */ uint32_t image_lsn; /**< Start address of the image */ uint8_t unused2[20]; } GNUC_PACKED; @@ -582,7 +583,7 @@ struct iso9660_stat_s { /* big endian!! */ /* Multi-extent aware size, in bytes. It is guaranteed that the bytes are stored as gapless string in a - continguous sequence of blocks. I.e. they can be read sequentially + contiguous sequence of blocks. I.e. they can be read sequentially starting at iso9660_stat_s.lsn. Data files which do not fulfil this promise cause a warning message and are not represented by this type of struct. @@ -671,7 +672,7 @@ typedef struct _iso9660_s iso9660_t; contained in a file format that libiso9660 doesn't know natively (or knows imperfectly.) - Some tolerence allowed for positioning the ISO 9660 image. We scan + Some tolerance allowed for positioning the ISO 9660 image. We scan for STANDARD_ID and use that to set the eventual offset to adjust by (as long as that is <= i_fuzz). @@ -683,7 +684,7 @@ typedef struct _iso9660_s iso9660_t; uint16_t i_fuzz); /*! - Open an ISO 9660 image for reading with some tolerence for positioning + Open an ISO 9660 image for reading with some tolerance for positioning of the ISO9660 image. We scan for ISO_STANDARD_ID and use that to set the eventual offset to adjust by (as long as that is <= i_fuzz). @@ -799,7 +800,7 @@ typedef struct _iso9660_s iso9660_t; tm will reported in GMT. */ bool iso9660_get_dtime (const iso9660_dtime_t *idr_date, bool b_localtime, - /*out*/ struct tm *tm); + /*out*/ struct tm *p_tm); /*! @@ -890,7 +891,7 @@ typedef struct _iso9660_s iso9660_t; /*! Take psz_path and a version number and turn that into a ISO-9660 - pathname. (That's just the pathname followd by ";" and the version + pathname. (That's just the pathname followed by ";" and the version number. For example, mydir/file.ext -> MYDIR/FILE.EXT;1 for version 1. The resulting ISO-9660 pathname is returned. */ @@ -1235,7 +1236,7 @@ lsn_t iso9660_get_dir_extent(const iso9660_dir_t *p_idr); @param p_iso the ISO-9660 file image to get data from - @param u_file_limit the maximimum number of (non-rock-ridge) files + @param u_file_limit the maximum number of (non-rock-ridge) files to consider before giving up and returning "dunno". "dunno" can also be returned if there was some error encountered @@ -1311,7 +1312,7 @@ lsn_t iso9660_get_dir_extent(const iso9660_dir_t *p_idr); void iso9660_set_evd (void *pd); /*! - Return true if ISO 9660 image has extended attrributes (XA). + Return true if ISO 9660 image has extended attributes (XA). */ bool iso9660_ifs_is_xa (const iso9660_t * p_iso); diff --git a/src/libcdio/cdio/util.h b/src/libcdio/cdio/util.h index 9cca8f9f..82a22bbf 100644 --- a/src/libcdio/cdio/util.h +++ b/src/libcdio/cdio/util.h @@ -105,6 +105,12 @@ _cdio_memdup (const void *mem, size_t count); char * _cdio_strdup_upper (const char str[]); +int +_cdio_stricmp(const char str1[], const char str2[]); + +int +_cdio_strnicmp(const char str1[], const char str2[], size_t count); + /*! Duplicate path and make it platform compliant. Typically needed for MinGW/MSYS where a "/c/..." path must be translated to "c:/..." for use with fopen(), etc. Returned string must be freed by the caller diff --git a/src/libcdio/driver/util.c b/src/libcdio/driver/util.c index 5adf3adc..d494d71a 100644 --- a/src/libcdio/driver/util.c +++ b/src/libcdio/driver/util.c @@ -142,6 +142,35 @@ _cdio_strdup_upper (const char str[]) return new_str; } +int +_cdio_stricmp (const char str1[], const char str2[]) +{ + if (str1 && str2) { + int c1, c2; + do { + c1 = tolower((unsigned char)*str1++); + c2 = tolower((unsigned char)*str2++); + } while (c1 == c2 && c1 != '\0'); + return c1 - c2; + } else return (str1 != str2); +} + +int +_cdio_strnicmp(const char str1[], const char str2[], size_t count) +{ + if (str1 && str2) { + int c1 = 0, c2 = 0; + size_t i; + for (i = 0; i < count; i++) { + c1 = tolower((unsigned char)*str1++); + c2 = tolower((unsigned char)*str2++); + if (c1 != c2 || c1 == '\0') + break; + } + return c1 - c2; + } else return (str1 != str2); +} + /* Convert MinGW/MSYS paths that start in "/c/..." to "c:/..." so that they can be used with fopen(), stat(), etc. Returned string must be freed by the caller using cdio_free().*/ diff --git a/src/libcdio/iso9660/iso9660_fs.c b/src/libcdio/iso9660/iso9660_fs.c index fc05a401..d0244279 100644 --- a/src/libcdio/iso9660/iso9660_fs.c +++ b/src/libcdio/iso9660/iso9660_fs.c @@ -1,6 +1,7 @@ /* - Copyright (C) 2003-2008, 2011-2015, 2017 Rocky Bernstein - Copyright (C) 2018, 2020 Pete Batard + Copyright (C) 2003-2008, 2011-2015, 2017, 2024 + Rocky Bernstein + Copyright (C) 2018, 2020, 2024 Pete Batard Copyright (C) 2018 Thomas Schmitt Copyright (C) 2001 Herbert Valerio Riedel @@ -59,6 +60,7 @@ #include "_cdio_stdio.h" #include "cdio_private.h" +/* Maximum number of El-Torito boot images we keep an index for */ #define MAX_BOOT_IMAGES 8 /** Implementation of iso9660_t type */ @@ -87,7 +89,8 @@ struct _iso9660_s { */ struct { uint32_t lsn; /**< Start LSN of an El-Torito bootable image */ - uint32_t num_sectors; /**< Number of sectors of a bootable image */ + uint32_t num_sectors; /**< Number of virtual sectors occupied by the + bootable image */ } boot_img[MAX_BOOT_IMAGES]; int i_fuzzy_offset; /**< Adjustment in bytes to make ISO_STANDARD_ID ("CD001") come out as ISO_PVD_SECTOR @@ -527,7 +530,7 @@ iso9660_ifs_read_superblock (iso9660_t *p_iso, /* Check for an El-Torito boot volume descriptor */ if (ISO_VD_BOOT_RECORD == from_711(p_svd.type) && (memcmp(p_brvd->system_id, EL_TORITO_ID, ISO_MAX_SYSTEM_ID) == 0)) { - /* Perform very basic parsing of boot entries to fill an image table */ + /* Perform basic parsing of boot entries to fill an image table */ iso9660_br_t br[ISO_BLOCKSIZE / sizeof(iso9660_br_t)]; if (iso9660_iso_seek_read(p_iso, &br, p_brvd->boot_catalog_sector, 1) == ISO_BLOCKSIZE) { for (j = 0, k = 0; @@ -823,7 +826,7 @@ _iso9660_is_rock_ridge_enabled(void* p_image) /*! Convert a directory record name to a 0-terminated string. One of parameters alloc_result and cpy_result should be non-NULL to take - the result. + the result. */ static bool _iso9660_recname_to_cstring(const char *src, size_t src_len, @@ -1261,7 +1264,7 @@ _fs_iso_stat_traverse (iso9660_t *p_iso, const iso9660_stat_t *_root, { unsigned offset = 0; uint8_t *_dirbuf = NULL; - uint32_t blocks; + uint32_t blocks; int ret, cmp; iso9660_stat_t *p_stat = NULL; iso9660_dir_t *p_iso9660_dir = NULL; @@ -1466,26 +1469,32 @@ iso9660_fs_stat_translate (CdIo_t *p_cdio, const char psz_path[]) iso9660_stat_t * iso9660_ifs_stat_translate (iso9660_t *p_iso, const char psz_path[]) { - /* Special case for virtual El-Torito boot images ('[BOOT]/#.img') */ - if (psz_path && (strncmp(psz_path, "[BOOT]/", 7) == 0)) { - int index = psz_path[7] - '0'; - iso9660_stat_t* p_stat; - if (strlen(psz_path) < 8) - return NULL; - if ((psz_path[7] < '0') || (psz_path[7] > '0' + MAX_BOOT_IMAGES - 1)) - return NULL; - if (p_iso->boot_img[index].lsn == 0 || p_iso->boot_img[index].num_sectors == 0) - return NULL; - p_stat = calloc(1, sizeof(iso9660_stat_t) + strlen(psz_path)); - if (!p_stat) { - cdio_warn("Couldn't calloc(1, %d)", (int)sizeof(iso9660_stat_t)); - return NULL; + /* Special case for virtual El-Torito boot images ('/[BOOT]/#-Boot-NoEmul.img') */ + if (psz_path && p_iso && p_iso->boot_img[0].lsn != 0) { + /* Work on a path without leading slash */ + const char* path = (psz_path[0] == '/') ? &psz_path[1] : psz_path; + if ((_cdio_strnicmp(path, "[BOOT]/", 7) == 0) && + (_cdio_stricmp(&path[8], "-Boot-NoEmul.img") == 0)) { + int index = path[7] - '0'; + iso9660_stat_t* p_stat; + if (strlen(path) < 24) + return NULL; + cdio_assert(MAX_BOOT_IMAGES <= 10); + if ((path[7] < '0') || (path[7] > '0' + MAX_BOOT_IMAGES - 1)) + return NULL; + if (p_iso->boot_img[index].lsn == 0 || p_iso->boot_img[index].num_sectors == 0) + return NULL; + p_stat = calloc(1, sizeof(iso9660_stat_t) + strlen(path)); + if (!p_stat) { + cdio_warn("Couldn't calloc(1, %d)", (int)sizeof(iso9660_stat_t)); + return NULL; + } + p_stat->lsn = p_iso->boot_img[index].lsn; + p_stat->total_size = p_iso->boot_img[index].num_sectors * VIRTUAL_SECTORSIZE; + p_stat->type = _STAT_FILE; + strcpy(p_stat->filename, path); + return p_stat; } - p_stat->lsn = p_iso->boot_img[index].lsn; - p_stat->total_size = p_iso->boot_img[index].num_sectors * ISO_BLOCKSIZE; - p_stat->type = _STAT_FILE; - strcpy(p_stat->filename, psz_path); - return p_stat; } return fs_stat_translate(p_iso, (stat_root_t *) _ifs_stat_root, @@ -1623,6 +1632,7 @@ iso9660_fs_readdir (CdIo_t *p_cdio, const char psz_path[]) CdioISO9660FileList_t * iso9660_ifs_readdir (iso9660_t *p_iso, const char psz_path[]) { + int i; iso9660_dir_t *p_iso9660_dir; iso9660_stat_t *p_iso9660_stat = NULL; iso9660_stat_t *p_stat; @@ -1630,6 +1640,30 @@ iso9660_ifs_readdir (iso9660_t *p_iso, const char psz_path[]) if (!p_iso) return NULL; if (!psz_path) return NULL; + /* List the virtual El-Torito images */ + if (p_iso->boot_img[0].lsn != 0) { + const char* path = (psz_path[0] == '/') ? &psz_path[1] : psz_path; + if (_cdio_strnicmp(path, "[BOOT]", 6) == 0 && (path[6] == '\0' || path[6] == '/')) { + CdioList_t* retval = _cdio_list_new(); + for (i = 0; i < MAX_BOOT_IMAGES && p_iso->boot_img[i].lsn != 0; i++) { + p_iso9660_stat = calloc(1, sizeof(iso9660_stat_t) + 18); + if (!p_iso9660_stat) { + cdio_warn("Couldn't calloc(1, %d)", (int)sizeof(iso9660_stat_t) + 18); + break; + } + strcpy(p_iso9660_stat->filename, "#-Boot-NoEmul.img"); + p_iso9660_stat->filename[0] = '0' + i; + p_iso9660_stat->type = _STAT_FILE; + p_iso9660_stat->lsn = p_iso->boot_img[i].lsn; + p_iso9660_stat->total_size = p_iso->boot_img[i].num_sectors * VIRTUAL_SECTORSIZE; + iso9660_get_ltime(&p_iso->pvd.creation_date, &p_iso9660_stat->tm); + _cdio_list_append(retval, p_iso9660_stat); + p_iso9660_stat = NULL; + } + return retval; + } + } + p_stat = iso9660_ifs_stat (p_iso, psz_path); if (!p_stat) return NULL; @@ -1647,6 +1681,21 @@ iso9660_ifs_readdir (iso9660_t *p_iso, const char psz_path[]) const size_t dirbuf_len = blocks * ISO_BLOCKSIZE; bool skip_following_extents = false; + /* Add the virtual El-Torito "[BOOT]" directory to root */ + if (p_iso->boot_img[0].lsn != 0) { + if (psz_path[0] == '\0' || (psz_path[0] == '/' && psz_path[1] == '\0')) { + p_iso9660_stat = calloc(1, sizeof(iso9660_stat_t) + 7); + if (p_iso9660_stat) { + strcpy(p_iso9660_stat->filename, "[BOOT]"); + p_iso9660_stat->type = _STAT_DIR; + p_iso9660_stat->lsn = ISO_PVD_SECTOR + 1; + iso9660_get_ltime(&p_iso->pvd.creation_date, &p_iso9660_stat->tm); + _cdio_list_append(retval, p_iso9660_stat); + p_iso9660_stat = NULL; + } + } + } + if (!dirbuf_len) { cdio_warn("Invalid directory buffer sector size %u", blocks); diff --git a/src/rufus.rc b/src/rufus.rc index 68fe3875..f9872d92 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2104" +CAPTION "Rufus 4.4.2105" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2104,0 - PRODUCTVERSION 4,4,2104,0 + FILEVERSION 4,4,2105,0 + PRODUCTVERSION 4,4,2105,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2104" + VALUE "FileVersion", "4.4.2105" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2104" + VALUE "ProductVersion", "4.4.2105" END END BLOCK "VarFileInfo" From c2b2624b62c8eb2622d547c735331c47abb21eb4 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sat, 3 Feb 2024 18:38:17 +0000 Subject: [PATCH 46/53] [vhd] improve handing of user selected filename on save to VHD/FFU * Use of '*.*' as pattern in file save dialog could lead to assert and crash, so we now try to derive the type of image to be saved from the file extension. We also did not properly handle user cancellation in the file save dialog. * Also update iso9660/iso9660_fs.c to latest proposal of El Torito image handling. * Also add a couple asserts in the hash table functions so that, if these ever get triggered we will pick them from Windows Store reports, and clean up code. --- src/libcdio/iso9660/iso9660_fs.c | 34 ++++++++++++++++++++++++++++---- src/rufus.rc | 10 +++++----- src/stdfn.c | 16 ++++++++------- src/vhd.c | 27 +++++++++++++++---------- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/libcdio/iso9660/iso9660_fs.c b/src/libcdio/iso9660/iso9660_fs.c index d0244279..b6e66a99 100644 --- a/src/libcdio/iso9660/iso9660_fs.c +++ b/src/libcdio/iso9660/iso9660_fs.c @@ -60,7 +60,7 @@ #include "_cdio_stdio.h" #include "cdio_private.h" -/* Maximum number of El-Torito boot images we keep an index for */ +/* Maximum number of El-Torito boot images we keep an index for */ #define MAX_BOOT_IMAGES 8 /** Implementation of iso9660_t type */ @@ -532,13 +532,39 @@ iso9660_ifs_read_superblock (iso9660_t *p_iso, (memcmp(p_brvd->system_id, EL_TORITO_ID, ISO_MAX_SYSTEM_ID) == 0)) { /* Perform basic parsing of boot entries to fill an image table */ iso9660_br_t br[ISO_BLOCKSIZE / sizeof(iso9660_br_t)]; - if (iso9660_iso_seek_read(p_iso, &br, p_brvd->boot_catalog_sector, 1) == ISO_BLOCKSIZE) { + if (iso9660_iso_seek_read(p_iso, &br, p_brvd->boot_catalog_sector, 1) == + ISO_BLOCKSIZE) { for (j = 0, k = 0; j < (ISO_BLOCKSIZE / sizeof(iso9660_br_t)) && k < MAX_BOOT_IMAGES; j++) { if (br[j].boot_id == 0x88 && br[j].media_type == 0) { - p_iso->boot_img[k].lsn = br[j].image_lsn; - p_iso->boot_img[k++].num_sectors = br[j].num_sectors; + p_iso->boot_img[k].lsn = uint32_from_le(br[j].image_lsn); + p_iso->boot_img[k++].num_sectors = uint16_from_le(br[j].num_sectors); + } + } + /* Special case for non specs-compliant images that do follow the UEFI */ + /* specs and that use size 0 or 1 for images larger than 0xffff virtual */ + /* sectors, in which case the image runs to the end of the volume (per */ + /* UEFI specs) or to the next LSN (as seen with some software). */ + cdio_assert(ISO_BLOCKSIZE / VIRTUAL_SECTORSIZE == 4); + for (j = 0; j < MAX_BOOT_IMAGES; j++) { + uint32_t next_lsn = from_733(p_iso->pvd.volume_space_size); + if (p_iso->boot_img[j].lsn == 0) + continue; + /* Find the closest LSN after the one from this image */ + cdio_assert(p_iso->boot_img[j].lsn < next_lsn); + for (k = 0; k < MAX_BOOT_IMAGES; k++) { + if (p_iso->boot_img[k].lsn > p_iso->boot_img[j].lsn && + p_iso->boot_img[k].lsn < next_lsn) + next_lsn = p_iso->boot_img[k].lsn; + } + /* If the image has a sector size of 0 or 1 and theres' more than */ + /* 0xffff sectors to the next LSN, assume it needs expansion. */ + if (p_iso->boot_img[j].num_sectors <= 1 && + (next_lsn - p_iso->boot_img[j].lsn) >= 0x4000) { + p_iso->boot_img[j].num_sectors = + (next_lsn - p_iso->boot_img[j].lsn) * 4; + cdio_warn("Auto-expanding the size of %d-Boot-NoEmul.img", j); } } } diff --git a/src/rufus.rc b/src/rufus.rc index f9872d92..22e2b668 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.4.2105" +CAPTION "Rufus 4.4.2106" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,4,2105,0 - PRODUCTVERSION 4,4,2105,0 + FILEVERSION 4,4,2106,0 + PRODUCTVERSION 4,4,2106,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.4.2105" + VALUE "FileVersion", "4.4.2106" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.4.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.4.2105" + VALUE "ProductVersion", "4.4.2106" END END BLOCK "VarFileInfo" diff --git a/src/stdfn.c b/src/stdfn.c index f23019c0..dd9d4547 100644 --- a/src/stdfn.c +++ b/src/stdfn.c @@ -80,8 +80,9 @@ BOOL htab_create(uint32_t nel, htab_table* htab) if (htab == NULL) { return FALSE; } + assert(htab->table == NULL); if (htab->table != NULL) { - uprintf("warning: htab_create() was called with a non empty table"); + uprintf("Warning: htab_create() was called with a non empty table"); return FALSE; } @@ -96,7 +97,7 @@ BOOL htab_create(uint32_t nel, htab_table* htab) // allocate memory and zero out. htab->table = (htab_entry*)calloc(htab->size + 1, sizeof(htab_entry)); if (htab->table == NULL) { - uprintf("could not allocate space for hash table\n"); + uprintf("Could not allocate space for hash table"); return FALSE; } @@ -166,7 +167,7 @@ uint32_t htab_hash(char* str, htab_table* htab) // existing hash return idx; } - // uprintf("hash collision ('%s' vs '%s')\n", str, htab->table[idx].str); + // uprintf("Hash collision ('%s' vs '%s')", str, htab->table[idx].str); // Second hash function, as suggested in [Knuth] hval2 = 1 + hval % (htab->size - 2); @@ -196,19 +197,20 @@ uint32_t htab_hash(char* str, htab_table* htab) // Not found => New entry // If the table is full return an error + assert(htab->filled < htab->size); if (htab->filled >= htab->size) { - uprintf("hash table is full (%d entries)", htab->size); + uprintf("Hash table is full (%d entries)", htab->size); return 0; } safe_free(htab->table[idx].str); htab->table[idx].used = hval; - htab->table[idx].str = (char*) malloc(safe_strlen(str)+1); + htab->table[idx].str = (char*) malloc(safe_strlen(str) + 1); if (htab->table[idx].str == NULL) { - uprintf("could not duplicate string for hash table\n"); + uprintf("Could not duplicate string for hash table"); return 0; } - memcpy(htab->table[idx].str, str, safe_strlen(str)+1); + memcpy(htab->table[idx].str, str, safe_strlen(str) + 1); ++htab->filled; return idx; diff --git a/src/vhd.c b/src/vhd.c index 60508262..9340079b 100644 --- a/src/vhd.c +++ b/src/vhd.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Virtual Disk Handling functions - * Copyright © 2013-2023 Pete Batard + * Copyright © 2013-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1111,6 +1111,7 @@ void VhdSaveImage(void) char filename[128]; char path[MAX_PATH]; int DriveIndex = ComboBox_GetCurSel(hDeviceList); + enum { image_type_vhd = 1, image_type_vhdx = 2, image_type_ffu = 3 }; static EXT_DECL(img_ext, filename, __VA_GROUP__("*.vhd", "*.vhdx", "*.ffu"), __VA_GROUP__(lmprintf(MSG_343), lmprintf(MSG_342), lmprintf(MSG_344))); ULARGE_INTEGER free_space; @@ -1123,20 +1124,26 @@ void VhdSaveImage(void) img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex); img_save.DevicePath = GetPhysicalName(img_save.DeviceNum); // FFU support requires GPT - if (!has_ffu_support || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) - img_ext.count = 2; - for (i = 1; i <= (UINT)img_ext.count && (safe_strcmp(&_img_ext_x[i - 1][2], save_image_type) != 0); i++); + img_ext.count = (!has_ffu_support || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT) ? 2 : 3; + for (i = 1; i <= (UINT)img_ext.count && (safe_strcmp(save_image_type , &_img_ext_x[i - 1][2]) != 0); i++); if (i > (UINT)img_ext.count) - i = 2; + i = image_type_vhdx; img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, &i); - assert(i > 0 && i <= (UINT)img_ext.count); - save_image_type = (char*) &_img_ext_x[i - 1][2]; - WriteSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE, save_image_type); + if (img_save.ImagePath == NULL) + goto out; + for (i = 1; i <= (UINT)img_ext.count && (strstr(img_save.ImagePath, &_img_ext_x[i - 1][1]) == NULL); i++); + if (i > (UINT)img_ext.count) { + uprintf("Warning: Can not determine image type from extension - Saving to uncompressed VHD."); + i = image_type_vhd; + } else { + save_image_type = (char*)&_img_ext_x[i - 1][2]; + WriteSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE, save_image_type); + } switch (i) { - case 1: + case image_type_vhd: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; break; - case 3: + case image_type_ffu: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_FFU; break; default: From ac9a3f42d86416330cb789518c350850541d363b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 6 Feb 2024 20:02:36 +0000 Subject: [PATCH 47/53] [misc] fix revoked bootloaders message does not display when using MBR * Also update version to rufus-next --- configure | 20 ++++++++++---------- configure.ac | 2 +- src/rufus.c | 6 +++--- src/rufus.rc | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/configure b/configure index 06fbccde..be3e20e3 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for rufus 4.4. +# Generated by GNU Autoconf 2.71 for rufus 4.5. # # Report bugs to . # @@ -611,8 +611,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='rufus' PACKAGE_TARNAME='rufus' -PACKAGE_VERSION='4.4' -PACKAGE_STRING='rufus 4.4' +PACKAGE_VERSION='4.5' +PACKAGE_STRING='rufus 4.5' PACKAGE_BUGREPORT='https://github.com/pbatard/rufus/issues' PACKAGE_URL='https://rufus.ie' @@ -1269,7 +1269,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures rufus 4.4 to adapt to many kinds of systems. +\`configure' configures rufus 4.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1336,7 +1336,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of rufus 4.4:";; + short | recursive ) echo "Configuration of rufus 4.5:";; esac cat <<\_ACEOF @@ -1428,7 +1428,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -rufus configure 4.4 +rufus configure 4.5 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1504,7 +1504,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by rufus $as_me 4.4, which was +It was created by rufus $as_me 4.5, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2767,7 +2767,7 @@ fi # Define the identity of the package. PACKAGE='rufus' - VERSION='4.4' + VERSION='4.5' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -5309,7 +5309,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by rufus $as_me 4.4, which was +This file was extended by rufus $as_me 4.5, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -5365,7 +5365,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -rufus config.status 4.4 +rufus config.status 4.5 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index be4e41d4..6f1ec2a1 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([rufus], [4.4], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) +AC_INIT([rufus], [4.5], [https://github.com/pbatard/rufus/issues], [rufus], [https://rufus.ie]) AM_INIT_AUTOMAKE([-Wno-portability foreign no-dist no-dependencies]) AC_CONFIG_SRCDIR([src/rufus.c]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/src/rufus.c b/src/rufus.c index ba376861..952711d8 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1,6 +1,6 @@ /* * Rufus: The Reliable USB Formatting Utility - * Copyright © 2011-2023 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1636,7 +1636,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) } // Check UEFI bootloaders for revocation - if (target_type == TT_UEFI) { + if (IS_EFI_BOOTABLE(img_report)) { // coverity[swapped_arguments] if (GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, tmp) != 0) { // ExtractISOFile() is case sensitive, so we must use @@ -1731,7 +1731,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param) if ((partition_type == PARTITION_STYLE_MBR) && HAS_SYSLINUX(img_report)) { if (SL_MAJOR(img_report.sl_version) < 5) { IGNORE_RETVAL(_chdirU(app_data_dir)); - for (i=0; i Date: Wed, 7 Feb 2024 19:24:17 +0000 Subject: [PATCH 48/53] [net] fix Fido script not being able to be launched again on user cancel * Also update workflows to latest setup-msbuild * Closes #2419 --- .github/workflows/codeql.yml | 2 +- .github/workflows/coverity.yml | 2 +- .github/workflows/vs2022.yml | 2 +- src/net.c | 3 ++- src/rufus.rc | 10 +++++----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 279e1899..040896d5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: languages: cpp - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.1 + uses: microsoft/setup-msbuild@v2 with: msbuild-architecture: x64 diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index a04a4c74..ebab4ea7 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -44,7 +44,7 @@ jobs: run: echo "${{github.workspace}}/cov-analysis-win64/bin" >> $GITHUB_PATH - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.1 + uses: microsoft/setup-msbuild@v2 - name: Build with Coverity run: | diff --git a/.github/workflows/vs2022.yml b/.github/workflows/vs2022.yml index b82a70b2..781c85e6 100644 --- a/.github/workflows/vs2022.yml +++ b/.github/workflows/vs2022.yml @@ -45,7 +45,7 @@ jobs: submodules: recursive - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 with: msbuild-architecture: x64 diff --git a/src/net.c b/src/net.c index 43f96d94..4c47ecc8 100644 --- a/src/net.c +++ b/src/net.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Networking functionality (web file download, check for update, etc.) - * Copyright © 2012-2023 Pete Batard + * Copyright © 2012-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -848,6 +848,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) uprintf("Script signature is valid ✓"); #endif + FormatStatus = 0; dwExitCode = RunCommand(cmdline, app_data_dir, TRUE); uprintf("Exited download script with code: %d", dwExitCode); if ((dwExitCode == 0) && PeekNamedPipe(hPipe, NULL, dwPipeSize, NULL, &dwAvail, NULL) && (dwAvail != 0)) { diff --git a/src/rufus.rc b/src/rufus.rc index c2594a7a..8096334e 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2107" +CAPTION "Rufus 4.5.2108" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2107,0 - PRODUCTVERSION 4,5,2107,0 + FILEVERSION 4,5,2108,0 + PRODUCTVERSION 4,5,2108,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2107" + VALUE "FileVersion", "4.5.2108" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2107" + VALUE "ProductVersion", "4.5.2108" END END BLOCK "VarFileInfo" From 164d4b0ab0c1e0245606482b948f4e32f4249a7b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Wed, 7 Feb 2024 19:49:41 +0000 Subject: [PATCH 49/53] [misc] update workflows to use upload-artifact@v4 * See actions/upload-artifact#483, actions/upload-artifact#472#issuecomment-1861571655 and ultimately https://github.com/actions/upload-artifact/blob/main/merge/README.md. --- .github/workflows/mingw.yml | 15 +++++++++++++-- .github/workflows/vs2022.yml | 15 +++++++++++++-- src/rufus.rc | 10 +++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 1f2e02bb..c56eacc6 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -96,7 +96,18 @@ jobs: - name: Upload artifacts if: ${{ github.event_name == 'push' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: MinGW + name: ${{ matrix.sys }} path: ./*.exe + + Extra-Step-To-Merge-Artifacts-Thanks-To-Upload-Artifact-v4-Breaking-Backwards-Compatibility: + runs-on: windows-latest + needs: MinGW-Build + steps: + - name: Merge Artifacts + uses: actions/upload-artifact/merge@v4 + if: ${{ github.event_name == 'push' }} + with: + name: MinGW + delete-merged: true \ No newline at end of file diff --git a/.github/workflows/vs2022.yml b/.github/workflows/vs2022.yml index 781c85e6..249f7ad4 100644 --- a/.github/workflows/vs2022.yml +++ b/.github/workflows/vs2022.yml @@ -86,10 +86,21 @@ jobs: curl --request POST --url https://www.virustotal.com/api/v3/monitor/items --header 'x-apikey: ${{ secrets.VIRUSTOTAL_API_KEY }}' --form path='/rufus_${{ matrix.TARGET_PLATFORM }}.exe' --form file=@./rufus_${{ matrix.TARGET_PLATFORM }}.exe - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ github.event_name == 'push' }} with: - name: VS2022 + name: ${{ matrix.TARGET_PLATFORM }} path: | ./*.exe ./*.pdb + + Extra-Step-To-Merge-Artifacts-Thanks-To-Upload-Artifact-v4-Breaking-Backwards-Compatibility: + runs-on: windows-latest + needs: VS2022-Build + steps: + - name: Merge Artifacts + uses: actions/upload-artifact/merge@v4 + if: ${{ github.event_name == 'push' }} + with: + name: VS2022 + delete-merged: true diff --git a/src/rufus.rc b/src/rufus.rc index 8096334e..3df360bd 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2108" +CAPTION "Rufus 4.5.2109" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2108,0 - PRODUCTVERSION 4,5,2108,0 + FILEVERSION 4,5,2109,0 + PRODUCTVERSION 4,5,2109,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2108" + VALUE "FileVersion", "4.5.2109" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2108" + VALUE "ProductVersion", "4.5.2109" END END BLOCK "VarFileInfo" From 0f23c4718421c8bd7dd4810b7ef759267d535f83 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 8 Feb 2024 14:17:03 +0000 Subject: [PATCH 50/53] [misc] refactor partition creation --- src/drive.c | 388 +++++++++++++++++++++++--------------------------- src/drive.h | 14 +- src/format.c | 53 ++++--- src/missing.h | 5 +- src/rufus.rc | 10 +- src/wue.c | 6 +- 6 files changed, 229 insertions(+), 247 deletions(-) diff --git a/src/drive.c b/src/drive.c index bea54e79..d558befd 100644 --- a/src/drive.c +++ b/src/drive.c @@ -74,7 +74,7 @@ PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryVolumeInformationFile, (HANDLE, PIO_STATUS_ RUFUS_DRIVE_INFO SelectedDrive; extern BOOL write_as_esp; extern windows_version_t WindowsVersion; -uint64_t partition_offset[PI_MAX]; +int partition_index[PI_MAX]; uint64_t persistence_size = 0; /* @@ -392,7 +392,7 @@ char* AltGetLogicalName(DWORD DriveIndex, uint64_t PartitionOffset, BOOL bKeepTr if (PartitionOffset == 0) { i = 0; } else if (matching_drive) { - for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.PartitionOffset[i]); i++); + for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.Partition[i].Offset); i++); if (i >= MAX_PARTITIONS) { suprintf("Error: Could not find a partition at offset %lld on this disk", PartitionOffset); goto out; @@ -428,11 +428,11 @@ char* GetExtPartitionName(DWORD DriveIndex, uint64_t PartitionOffset) if (DriveIndex != SelectedDrive.DeviceNumber) goto out; CheckDriveIndex(DriveIndex); - for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.PartitionOffset[i]); i++); + for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.Partition[i].Offset); i++); if (i >= MAX_PARTITIONS) goto out; static_sprintf(volume_name, "\\\\.\\PhysicalDrive%lu %I64u %I64u", DriveIndex, - SelectedDrive.PartitionOffset[i], SelectedDrive.PartitionSize[i]); + SelectedDrive.Partition[i].Offset, SelectedDrive.Partition[i].Size); ret = safe_strdup(volume_name); out: return ret; @@ -1868,8 +1868,7 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys return FALSE; SelectedDrive.nPartitions = 0; - memset(SelectedDrive.PartitionOffset, 0, sizeof(SelectedDrive.PartitionOffset)); - memset(SelectedDrive.PartitionSize, 0, sizeof(SelectedDrive.PartitionSize)); + memset(SelectedDrive.Partition, 0, sizeof(SelectedDrive.Partition)); // Populate the filesystem data FileSystemName[0] = 0; volume_name = GetLogicalName(DriveIndex, 0, TRUE, FALSE); @@ -1961,8 +1960,8 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys } } if (i < MAX_PARTITIONS) { - SelectedDrive.PartitionOffset[i] = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[i] = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart; + SelectedDrive.Partition[i].Offset = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart; + SelectedDrive.Partition[i].Size = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart; } suprintf(" Type: %s (0x%02x)\r\n Detected File System: %s\r\n" " Size: %s (%lld bytes)\r\n Start Sector: %lld, Boot: %s", @@ -1992,12 +1991,13 @@ BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSys DriveLayout->Gpt.MaxPartitionCount, DriveLayout->Gpt.StartingUsableOffset.QuadPart, DriveLayout->Gpt.UsableLength.QuadPart); for (i = 0; i < DriveLayout->PartitionCount; i++) { if (i < MAX_PARTITIONS) { - SelectedDrive.PartitionOffset[i] = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[i] = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart; + SelectedDrive.Partition[i].Offset = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart; + SelectedDrive.Partition[i].Size = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart; + wcscpy(SelectedDrive.Partition[i].Name, DriveLayout->PartitionEntry[i].Gpt.Name); } SelectedDrive.nPartitions++; isUefiNtfs = (wcscmp(DriveLayout->PartitionEntry[i].Gpt.Name, L"UEFI:NTFS") == 0); - suprintf("Partition %d%s:\r\n Type: %s", i+1, isUefiNtfs ? " (UEFI:NTFS)" : "", + suprintf("Partition %d%s:\r\n Type: %s", i + 1, isUefiNtfs ? " (UEFI:NTFS)" : "", GetGPTPartitionType(&DriveLayout->PartitionEntry[i].Gpt.PartitionType)); if (DriveLayout->PartitionEntry[i].Gpt.Name[0] != 0) suprintf(" Name: '%S'", DriveLayout->PartitionEntry[i].Gpt.Name); @@ -2232,15 +2232,17 @@ BOOL RemountVolume(char* drive_name, BOOL bSilent) * (especially IOCTL_DISK_UPDATE_PROPERTIES is *USELESS*), and therefore the OS will try to * read the file system data at an old location, even if the partition has just been deleted. */ -static BOOL ClearPartition(HANDLE hDrive, LARGE_INTEGER offset, DWORD size) +static BOOL ClearPartition(HANDLE hDrive, uint64_t offset, DWORD size) { BOOL r = FALSE; uint8_t* buffer = calloc(size, 1); + LARGE_INTEGER li_offset; if (buffer == NULL) return FALSE; - if (!SetFilePointerEx(hDrive, offset, NULL, FILE_BEGIN)) { + li_offset.QuadPart = offset; + if (!SetFilePointerEx(hDrive, li_offset, NULL, FILE_BEGIN)) { free(buffer); return FALSE; } @@ -2260,17 +2262,14 @@ static BOOL ClearPartition(HANDLE hDrive, LARGE_INTEGER offset, DWORD size) BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL mbr_uefi_marker, uint8_t extra_partitions) { const char* PartitionTypeName[] = { "MBR", "GPT", "SFD" }; - const wchar_t *extra_part_name = L"", *main_part_name = write_as_esp ? L"EFI System Partition" : L"Main Data Partition"; - const LONGLONG main_part_size = write_as_esp ? MAX_ISO_TO_ESP_SIZE * MB : SelectedDrive.DiskSize; const LONGLONG bytes_per_track = ((LONGLONG)SelectedDrive.SectorsPerTrack) * SelectedDrive.SectorSize; const DWORD size_to_clear = MAX_SECTORS_TO_CLEAR * SelectedDrive.SectorSize; uint8_t* buffer; + uint64_t last_offset = SelectedDrive.DiskSize; size_t uefi_ntfs_size = 0; + DWORD pi = 0, mi, i, size, bufsize; CREATE_DISK CreateDisk = { PARTITION_STYLE_RAW, { { 0 } } }; DRIVE_LAYOUT_INFORMATION_EX4 DriveLayoutEx = { 0 }; - BOOL r; - DWORD i, size, bufsize, pn = 0; - LONGLONG main_part_size_in_sectors, extra_part_size_in_tracks = 0; // Go for a 260 MB sized ESP by default to keep everyone happy, including 4K sector users: // https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/configure-uefigpt-based-hard-drive-partitions // and folks using MacOS: https://github.com/pbatard/rufus/issues/979 @@ -2288,17 +2287,19 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m if (extra_partitions & XP_UEFI_NTFS) { uefi_ntfs_size = GetResourceSize(hMainInstance, MAKEINTRESOURCEA(IDR_UEFI_NTFS), _RT_RCDATA, "uefi-ntfs.img"); - if (uefi_ntfs_size == 0) + if (uefi_ntfs_size == 0) { + uprintf("Could not access embedded 'uefi-ntfs.img'"); return FALSE; + } } - memset(partition_offset, 0, sizeof(partition_offset)); - memset(SelectedDrive.PartitionOffset, 0, sizeof(SelectedDrive.PartitionOffset)); - memset(SelectedDrive.PartitionSize, 0, sizeof(SelectedDrive.PartitionSize)); - // Compute the start offset of our first partition + memset(partition_index, 0, sizeof(partition_index)); + memset(SelectedDrive.Partition, 0, sizeof(SelectedDrive.Partition)); + + // Compute the starting offset of the first partition if ((partition_style == PARTITION_STYLE_GPT) || (!IsChecked(IDC_OLD_BIOS_FIXES))) { // Go with the MS 1 MB wastage at the beginning... - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = MB; + SelectedDrive.Partition[pi].Offset = 1 * MB; } else { // Some folks appear to think that 'Fixes for old BIOSes' is some kind of magic // wand and are adamant to try to apply them when creating *MODERN* VHD drives. @@ -2308,11 +2309,10 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // CHS sizes that IBM imparted upon us. Long story short, we now align to a // cylinder size that is itself aligned to the cluster size. // If this actually breaks old systems, please send your complaints to IBM. - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = - ((bytes_per_track + (ClusterSize - 1)) / ClusterSize) * ClusterSize; + SelectedDrive.Partition[pi].Offset = HI_ALIGN_X_TO_Y(bytes_per_track, ClusterSize); // GRUB2 no longer fits in the usual 31½ KB that the above computation provides // so just unconditionally double that size and get on with it. - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart *= 2; + SelectedDrive.Partition[pi].Offset *= 2; } // Having the ESP up front may help (and is the Microsoft recommended way) but this @@ -2321,22 +2321,16 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m if (((SelectedDrive.MediaType == FixedMedia) || (WindowsVersion.BuildNumber > 15000)) && (extra_partitions & XP_ESP)) { assert(partition_style == PARTITION_STYLE_GPT); - extra_part_name = L"EFI System Partition"; - DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = esp_size; - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_GENERIC_ESP; - uprintf("â— Creating %S (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart, - SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE)); - IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId)); - wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, extra_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name)); - // Zero the first sectors from this partition to avoid file system caching issues - if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear)) - uprintf("Could not zero %S: %s", extra_part_name, WindowsErrorString()); - SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart; - partition_offset[PI_ESP] = SelectedDrive.PartitionOffset[pn]; - pn++; - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn - 1].StartingOffset.QuadPart + - DriveLayoutEx.PartitionEntry[pn - 1].PartitionLength.QuadPart; + partition_index[PI_ESP] = pi; + wcscpy(SelectedDrive.Partition[pi].Name, L"EFI System Partition"); + SelectedDrive.Partition[pi].Size = esp_size; + SelectedDrive.Partition[pi + 1].Offset = SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size; + // Align next partition to track and cluster + SelectedDrive.Partition[pi + 1].Offset = HI_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); + if (ClusterSize % SelectedDrive.SectorSize == 0) + SelectedDrive.Partition[pi + 1].Offset = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); + assert(SelectedDrive.Partition[pi + 1].Offset >= SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size); + pi++; // Clear the extra partition we processed extra_partitions &= ~(XP_ESP); } @@ -2344,192 +2338,165 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // If required, set the MSR partition (GPT only - must be created before the data part) if (extra_partitions & XP_MSR) { assert(partition_style == PARTITION_STYLE_GPT); - extra_part_name = L"Microsoft Reserved Partition"; - DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = 128*MB; - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_MICROSOFT_RESERVED; - uprintf("â— Creating %S (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart, - SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE)); - IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId)); - wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, extra_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name)); - // Zero the first sectors from this partition to avoid file system caching issues - if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear)) - uprintf("Could not zero %S: %s", extra_part_name, WindowsErrorString()); - SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart; - pn++; - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn-1].StartingOffset.QuadPart + - DriveLayoutEx.PartitionEntry[pn-1].PartitionLength.QuadPart; - // Clear the extra partition we processed + wcscpy(SelectedDrive.Partition[pi].Name, L"Microsoft Reserved Partition"); + SelectedDrive.Partition[pi].Size = 128 * MB; + SelectedDrive.Partition[pi + 1].Offset = SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size; + SelectedDrive.Partition[pi + 1].Offset = HI_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, bytes_per_track); + if (ClusterSize % SelectedDrive.SectorSize == 0) + SelectedDrive.Partition[pi + 1].Offset = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[pi + 1].Offset, ClusterSize); + assert(SelectedDrive.Partition[pi + 1].Offset >= SelectedDrive.Partition[pi].Offset + SelectedDrive.Partition[pi].Size); + pi++; extra_partitions &= ~(XP_MSR); } - // Set our main data partition - if (write_as_esp) { - // Align ESP to 64 MB while leaving at least 32 MB free space - esp_size = max(esp_size, ((((LONGLONG)img_report.projected_size / MB) + 96) / 64) * 64 * MB); - main_part_size_in_sectors = (esp_size - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart) / - SelectedDrive.SectorSize; - } else { - main_part_size_in_sectors = (main_part_size - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart) / - // Need 33 sectors at the end for secondary GPT - SelectedDrive.SectorSize - ((partition_style == PARTITION_STYLE_GPT) ? 33 : 0); - } + // Reserve an entry for the main partition + partition_index[PI_MAIN] = pi++; + // Shorthand for the main index. + mi = partition_index[PI_MAIN]; + wcscpy(SelectedDrive.Partition[mi].Name, write_as_esp ? L"EFI System Partition" : L"Main Data Partition"); + + if (extra_partitions) { // Adjust the size according to extra partitions (which we always align to a track) - if (extra_partitions & XP_ESP) { - extra_part_name = L"EFI System"; - extra_part_size_in_tracks = (esp_size + bytes_per_track - 1) / bytes_per_track; - } else if (extra_partitions & XP_UEFI_NTFS) { - extra_part_name = L"UEFI:NTFS"; - extra_part_size_in_tracks = (max(MIN_EXTRA_PART_SIZE, uefi_ntfs_size) + bytes_per_track - 1) / bytes_per_track; - } else if ((extra_partitions & XP_CASPER)) { + // TODO: Should we align these to cluster as well? + if (extra_partitions & XP_PERSISTENCE) { assert(persistence_size != 0); - extra_part_name = L"Linux Persistence"; - extra_part_size_in_tracks = persistence_size / bytes_per_track; - } else if (extra_partitions & XP_COMPAT) { - extra_part_name = L"BIOS Compatibility"; - extra_part_size_in_tracks = 1; // One track for the extra partition - } else { - assert(FALSE); + partition_index[PI_CASPER] = pi; + wcscpy(SelectedDrive.Partition[pi].Name, L"Linux Persistence"); + SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(persistence_size, bytes_per_track); } - // NB: Because we already subtracted the backup GPT size from the main partition size and - // this extra partition is indexed on main size, it does not overflow into the backup GPT. - main_part_size_in_sectors = ((main_part_size_in_sectors / SelectedDrive.SectorsPerTrack) - - extra_part_size_in_tracks) * SelectedDrive.SectorsPerTrack; + if (extra_partitions & XP_ESP) { + partition_index[PI_ESP] = pi; + wcscpy(SelectedDrive.Partition[pi].Name, L"EFI System Partition"); + SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(esp_size, bytes_per_track); + } else if (extra_partitions & XP_UEFI_NTFS) { + partition_index[PI_UEFI_NTFS] = pi; + wcscpy(SelectedDrive.Partition[pi].Name, L"UEFI:NTFS"); + SelectedDrive.Partition[pi++].Size = HI_ALIGN_X_TO_Y(uefi_ntfs_size, bytes_per_track); + } else if (extra_partitions & XP_COMPAT) { + wcscpy(SelectedDrive.Partition[pi].Name, L"BIOS Compatibility"); + SelectedDrive.Partition[pi++].Size = bytes_per_track; // One track for the extra partition + } + assert(pi <= MAX_PARTITIONS); } + + // Compute the offsets of the extra partitions (which we always align to a track) + last_offset = SelectedDrive.DiskSize; + if (partition_style == PARTITION_STYLE_GPT) + last_offset -= 33 * SelectedDrive.SectorSize; + for (i = pi - 1; i > mi; i--) { + assert(SelectedDrive.Partition[i].Size < last_offset); + SelectedDrive.Partition[i].Offset = LO_ALIGN_X_TO_Y(last_offset - SelectedDrive.Partition[i].Size, bytes_per_track); + last_offset = SelectedDrive.Partition[i].Offset; + } + + // With the above, Compute the main partition size (which we align to a track) + assert(last_offset > SelectedDrive.Partition[mi].Offset); + SelectedDrive.Partition[mi].Size = LO_ALIGN_X_TO_Y(last_offset - SelectedDrive.Partition[mi].Offset, bytes_per_track); // Try to make sure that the main partition size is a multiple of the cluster size // This can be especially important when trying to capture an NTFS partition as FFU, as, when // the NTFS partition is aligned to cluster size, the FFU capture parses the NTFS allocated // map to only record clusters that are in use, whereas, if not aligned, the FFU capture uses // a full sector by sector scan of the NTFS partition and records any non-zero garbage, which // may include garbage leftover data from a previous reformat... - if (ClusterSize % SelectedDrive.SectorSize == 0) { - main_part_size_in_sectors = (((main_part_size_in_sectors * SelectedDrive.SectorSize) / - ClusterSize) * ClusterSize) / SelectedDrive.SectorSize; - } - if (main_part_size_in_sectors <= 0) { - uprintf("Error: Invalid %S size", main_part_name); + if (ClusterSize % SelectedDrive.SectorSize == 0) + SelectedDrive.Partition[mi].Size = LO_ALIGN_X_TO_Y(SelectedDrive.Partition[mi].Size, ClusterSize); + if (SelectedDrive.Partition[mi].Size <= 0) { + uprintf("Error: Invalid %S size", SelectedDrive.Partition[mi].Name); return FALSE; } - uprintf("â— Creating %S (offset: %lld, size: %s)", main_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart, - SizeToHumanReadable(main_part_size_in_sectors * SelectedDrive.SectorSize, TRUE, FALSE)); - // Zero the beginning of this partition to avoid conflicting leftovers - if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear)) - uprintf("Could not zero %S: %s", main_part_name, WindowsErrorString()); - DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = main_part_size_in_sectors * SelectedDrive.SectorSize; - if (partition_style == PARTITION_STYLE_MBR) { - DriveLayoutEx.PartitionEntry[pn].Mbr.BootIndicator = (boot_type != BT_NON_BOOTABLE); - switch (file_system) { - case FS_FAT16: - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x0e; // FAT16 LBA - break; - case FS_NTFS: - case FS_EXFAT: - case FS_UDF: - case FS_REFS: - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x07; - break; - case FS_EXT2: - case FS_EXT3: - case FS_EXT4: - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x83; - break; - case FS_FAT32: - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x0c; // FAT32 LBA - break; - default: - uprintf("Unsupported file system"); - return FALSE; - } - } else { - if (write_as_esp) - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_GENERIC_ESP; - else if (IS_EXT(file_system)) - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_LINUX_DATA; - else - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_MICROSOFT_DATA; - IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId)); - wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, main_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name)); - } - SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart; - partition_offset[PI_MAIN] = SelectedDrive.PartitionOffset[pn]; - pn++; - - // Set the optional extra partition - if (extra_partitions) { - // Should end on a track boundary - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn-1].StartingOffset.QuadPart + - DriveLayoutEx.PartitionEntry[pn-1].PartitionLength.QuadPart; - DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = (extra_partitions & XP_UEFI_NTFS) ? uefi_ntfs_size : - extra_part_size_in_tracks * bytes_per_track; - uprintf("â— Creating %S Partition (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart, - SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE)); - SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart; - SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart; - if (extra_partitions & XP_CASPER) - partition_offset[PI_CASPER] = SelectedDrive.PartitionOffset[pn]; - else if (extra_partitions & XP_ESP) - partition_offset[PI_ESP] = SelectedDrive.PartitionOffset[pn]; - - if (partition_style == PARTITION_STYLE_GPT) { - if (extra_partitions & XP_ESP) - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_GENERIC_ESP; - else if (extra_partitions & XP_CASPER) - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_LINUX_DATA; - else - DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_MICROSOFT_DATA; - if (extra_partitions & XP_UEFI_NTFS) { + // Build the DriveLayoutEx table + for (i = 0; i < pi; i++) { + uprintf("â— Creating %S%s (offset: %lld, size: %s)", SelectedDrive.Partition[i].Name, + (wcsstr(SelectedDrive.Partition[i].Name, L"Partition") == NULL) ? " Partition" : "", + SelectedDrive.Partition[i].Offset, + SizeToHumanReadable(SelectedDrive.Partition[i].Size, TRUE, FALSE)); + // Zero the first sectors of the partition to avoid file system caching issues + if (!ClearPartition(hDrive, SelectedDrive.Partition[i].Offset, + (DWORD)MIN(size_to_clear, SelectedDrive.Partition[i].Size))) + uprintf("Could not zero %S: %s", SelectedDrive.Partition[i].Name, WindowsErrorString()); + DriveLayoutEx.PartitionEntry[i].PartitionStyle = partition_style; + DriveLayoutEx.PartitionEntry[i].StartingOffset.QuadPart = SelectedDrive.Partition[i].Offset; + DriveLayoutEx.PartitionEntry[i].PartitionLength.QuadPart = SelectedDrive.Partition[i].Size; + DriveLayoutEx.PartitionEntry[i].PartitionNumber = i + 1; + DriveLayoutEx.PartitionEntry[i].RewritePartition = TRUE; + if (partition_style == PARTITION_STYLE_MBR) { + if (i == mi) { + DriveLayoutEx.PartitionEntry[i].Mbr.BootIndicator = (boot_type != BT_NON_BOOTABLE); + switch (file_system) { + case FS_FAT16: + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0x0e; // FAT16 LBA + break; + case FS_NTFS: + case FS_EXFAT: + case FS_UDF: + case FS_REFS: + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0x07; + break; + case FS_EXT2: + case FS_EXT3: + case FS_EXT4: + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0x83; + break; + case FS_FAT32: + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0x0c; // FAT32 LBA + break; + default: + uprintf("Unsupported file system"); + return FALSE; + } + } + // May override the the type of main partition if write_as_esp is active + if ((wcscmp(SelectedDrive.Partition[i].Name, L"EFI System Partition") == 0) || + (wcscmp(SelectedDrive.Partition[i].Name, L"UEFI:NTFS") == 0)) + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0xef; + else if (wcscmp(SelectedDrive.Partition[i].Name, L"Linux Persistence") == 0) + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = 0x83; + else if (wcscmp(SelectedDrive.Partition[i].Name, L"BIOS Compatibility") == 0) + DriveLayoutEx.PartitionEntry[i].Mbr.PartitionType = RUFUS_EXTRA_PARTITION_TYPE; + } else { + assert(partition_style == PARTITION_STYLE_GPT); + if (wcscmp(SelectedDrive.Partition[i].Name, L"UEFI:NTFS") == 0) { + DriveLayoutEx.PartitionEntry[i].Gpt.PartitionType = PARTITION_GENERIC_ESP; // Prevent a drive letter from being assigned to the UEFI:NTFS partition - DriveLayoutEx.PartitionEntry[pn].Gpt.Attributes = GPT_BASIC_DATA_ATTRIBUTE_NO_DRIVE_LETTER; + DriveLayoutEx.PartitionEntry[i].Gpt.Attributes = GPT_BASIC_DATA_ATTRIBUTE_NO_DRIVE_LETTER; #if !defined(_DEBUG) // Also make the partition read-only for release versions - DriveLayoutEx.PartitionEntry[pn].Gpt.Attributes += GPT_BASIC_DATA_ATTRIBUTE_READ_ONLY; + DriveLayoutEx.PartitionEntry[i].Gpt.Attributes += GPT_BASIC_DATA_ATTRIBUTE_READ_ONLY; #endif - } - IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId)); - wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, (extra_partitions & XP_ESP) ? L"EFI System Partition" : extra_part_name, - ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name)); - } else { - if (extra_partitions & (XP_UEFI_NTFS | XP_ESP)) { - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0xef; - } else if (extra_partitions & XP_CASPER) { - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x83; - } else if (extra_partitions & XP_COMPAT) { - DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = RUFUS_EXTRA_PARTITION_TYPE; - // Set the one track compatibility partition to be all hidden sectors - DriveLayoutEx.PartitionEntry[pn].Mbr.HiddenSectors = SelectedDrive.SectorsPerTrack; - } else { - assert(FALSE); - } + } else if (wcscmp(SelectedDrive.Partition[i].Name, L"EFI System Partition") == 0) + DriveLayoutEx.PartitionEntry[i].Gpt.PartitionType = PARTITION_GENERIC_ESP; + else if (wcscmp(SelectedDrive.Partition[i].Name, L"Linux Persistence") == 0) + DriveLayoutEx.PartitionEntry[i].Gpt.PartitionType = PARTITION_LINUX_DATA; + else if (wcscmp(SelectedDrive.Partition[i].Name, L"Microsoft Reserved Partition") == 0) + DriveLayoutEx.PartitionEntry[i].Gpt.PartitionType = PARTITION_MICROSOFT_RESERVED; + else + DriveLayoutEx.PartitionEntry[i].Gpt.PartitionType = PARTITION_MICROSOFT_DATA; + IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[i].Gpt.PartitionId)); + wcscpy(DriveLayoutEx.PartitionEntry[i].Gpt.Name, SelectedDrive.Partition[i].Name); } - - // We need to write the UEFI:NTFS partition before we refresh the disk - if (extra_partitions & XP_UEFI_NTFS) { - uprintf("Writing %S data...", extra_part_name); - if (!SetFilePointerEx(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, NULL, FILE_BEGIN)) { - uprintf("Could not set position"); - return FALSE; - } - buffer = GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_UEFI_NTFS), _RT_RCDATA, "uefi-ntfs.img", &bufsize, FALSE); - if (buffer == NULL) { - uprintf("Could not access source image"); - return FALSE; - } - if(!WriteFileWithRetry(hDrive, buffer, bufsize, &size, WRITE_RETRIES)) { - uprintf("Write error: %s", WindowsErrorString()); - return FALSE; - } - } - pn++; } - // Initialize the remaining partition data - for (i = 0; i < pn; i++) { - DriveLayoutEx.PartitionEntry[i].PartitionNumber = i + 1; - DriveLayoutEx.PartitionEntry[i].PartitionStyle = partition_style; - DriveLayoutEx.PartitionEntry[i].RewritePartition = TRUE; + // We need to write the UEFI:NTFS partition before we refresh the disk + if (extra_partitions & XP_UEFI_NTFS) { + LARGE_INTEGER li; + uprintf("Writing UEFI:NTFS data...", SelectedDrive.Partition[partition_index[PI_UEFI_NTFS]].Name); + li.QuadPart = SelectedDrive.Partition[partition_index[PI_UEFI_NTFS]].Offset; + if (!SetFilePointerEx(hDrive, li, NULL, FILE_BEGIN)) { + uprintf(" Could not set position"); + return FALSE; + } + buffer = GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_UEFI_NTFS), _RT_RCDATA, + "uefi-ntfs.img", &bufsize, FALSE); + if (buffer == NULL) { + uprintf(" Could not access source image"); + return FALSE; + } + if(!WriteFileWithRetry(hDrive, buffer, bufsize, &size, WRITE_RETRIES)) { + uprintf(" Write error: %s", WindowsErrorString()); + return FALSE; + } } switch (partition_style) { @@ -2558,7 +2525,7 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m CreateDisk.Gpt.MaxPartitionCount = MAX_PARTITIONS; DriveLayoutEx.PartitionStyle = PARTITION_STYLE_GPT; - DriveLayoutEx.PartitionCount = pn; + DriveLayoutEx.PartitionCount = pi; // At the very least, a GPT disk has 34 reserved sectors at the beginning and 33 at the end. DriveLayoutEx.Type.Gpt.StartingUsableOffset.QuadPart = 34 * SelectedDrive.SectorSize; DriveLayoutEx.Type.Gpt.UsableLength.QuadPart = SelectedDrive.DiskSize - (34+33) * SelectedDrive.SectorSize; @@ -2569,8 +2536,7 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // If you don't call IOCTL_DISK_CREATE_DISK, the IOCTL_DISK_SET_DRIVE_LAYOUT_EX call will fail size = sizeof(CreateDisk); - r = DeviceIoControl(hDrive, IOCTL_DISK_CREATE_DISK, (BYTE*)&CreateDisk, size, NULL, 0, &size, NULL); - if (!r) { + if (!DeviceIoControl(hDrive, IOCTL_DISK_CREATE_DISK, (BYTE*)&CreateDisk, size, NULL, 0, &size, NULL)) { uprintf("Could not reset disk: %s", WindowsErrorString()); return FALSE; } @@ -2578,9 +2544,9 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // "The goggles, they do nothing!" RefreshDriveLayout(hDrive); - size = sizeof(DriveLayoutEx) - ((partition_style == PARTITION_STYLE_GPT)?((4-pn)*sizeof(PARTITION_INFORMATION_EX)):0); - r = DeviceIoControl(hDrive, IOCTL_DISK_SET_DRIVE_LAYOUT_EX, (BYTE*)&DriveLayoutEx, size, NULL, 0, &size, NULL); - if (!r) { + size = sizeof(DriveLayoutEx) - ((partition_style == PARTITION_STYLE_GPT) ? + ((4 - pi) * sizeof(PARTITION_INFORMATION_EX)) : 0); + if (!DeviceIoControl(hDrive, IOCTL_DISK_SET_DRIVE_LAYOUT_EX, (BYTE*)&DriveLayoutEx, size, NULL, 0, &size, NULL)) { uprintf("Could not set drive layout: %s", WindowsErrorString()); return FALSE; } diff --git a/src/drive.h b/src/drive.h index 36752572..9ec5c68d 100644 --- a/src/drive.h +++ b/src/drive.h @@ -36,12 +36,13 @@ #define XP_ESP 0x02 #define XP_UEFI_NTFS 0x04 #define XP_COMPAT 0x08 -#define XP_CASPER 0x10 +#define XP_PERSISTENCE 0x10 #define PI_MAIN 0 #define PI_ESP 1 #define PI_CASPER 2 -#define PI_MAX 3 +#define PI_UEFI_NTFS 3 +#define PI_MAX 4 // The following should match VDS_FSOF_FLAGS as much as possible #define FP_FORCE 0x00000001 @@ -362,8 +363,11 @@ typedef struct { MEDIA_TYPE MediaType; int PartitionStyle; int nPartitions; // number of partitions we actually care about - uint64_t PartitionOffset[MAX_PARTITIONS]; - uint64_t PartitionSize[MAX_PARTITIONS]; + struct { + wchar_t Name[36]; + uint64_t Offset; + uint64_t Size; + } Partition[MAX_PARTITIONS]; int FSType; char proposed_label[16]; BOOL has_protective_mbr; @@ -374,7 +378,7 @@ typedef struct { } ClusterSize[FS_MAX]; } RUFUS_DRIVE_INFO; extern RUFUS_DRIVE_INFO SelectedDrive; -extern uint64_t partition_offset[PI_MAX]; +extern int partition_index[PI_MAX]; BOOL SetAutoMount(BOOL enable); BOOL GetAutoMount(BOOL* enabled); diff --git a/src/format.c b/src/format.c index 16a5313d..e8430272 100644 --- a/src/format.c +++ b/src/format.c @@ -972,7 +972,7 @@ static BOOL WriteSBR(HANDLE hPhysicalDrive) } // Ensure that we have sufficient space for the SBR - max_size = (DWORD)SelectedDrive.PartitionOffset[0]; + max_size = (DWORD)SelectedDrive.Partition[0].Offset; if (br_size + size > max_size) { uprintf(" SBR size is too large - You may need to uncheck 'Add fixes for old BIOSes'."); if (sub_type == BT_MAX) @@ -1456,23 +1456,32 @@ DWORD WINAPI FormatThread(void* param) large_drive = (SelectedDrive.DiskSize > (1*TB)); if (large_drive) uprintf("Notice: Large drive detected (may produce short writes)"); + // Find out if we need to add any extra partitions + extra_partitions = 0; + if ((boot_type == BT_IMAGE) && !write_as_image && HAS_PERSISTENCE(img_report) && persistence_size) + extra_partitions |= XP_PERSISTENCE; + // According to Microsoft, every GPT disk (we RUN Windows from) must have an MSR due to not having hidden sectors + // https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-and-gpt-faq#disks-that-require-an-msr if ((windows_to_go) && (target_type == TT_UEFI) && (partition_type == PARTITION_STYLE_GPT)) - // According to Microsoft, every GPT disk (we RUN Windows from) must have an MSR due to not having hidden sectors - // https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-and-gpt-faq#disks-that-require-an-msr - extra_partitions = XP_ESP | XP_MSR; - else if ( ((fs_type == FS_NTFS) || (fs_type == FS_EXFAT)) && - ((boot_type == BT_UEFI_NTFS) || ((boot_type == BT_IMAGE) && IS_EFI_BOOTABLE(img_report) && - ((target_type == TT_UEFI) || (windows_to_go) || (allow_dual_uefi_bios) || - (img_report.has_4GB_file) || (img_report.needs_ntfs)))) ) - extra_partitions = XP_UEFI_NTFS; - else if ((boot_type == BT_IMAGE) && !write_as_image && HAS_PERSISTENCE(img_report) && persistence_size) - extra_partitions = XP_CASPER; - else if (IsChecked(IDC_OLD_BIOS_FIXES)) - extra_partitions = XP_COMPAT; + extra_partitions |= XP_ESP | XP_MSR; + // If we have a bootable image with UEFI bootloaders and the target file system is NTFS or exFAT + // or the UEFI:NTFS option is selected, we add the UEFI:NTFS partition... + else if (((boot_type == BT_IMAGE) && IS_EFI_BOOTABLE(img_report)) && ((fs_type == FS_NTFS) || (fs_type == FS_EXFAT)) || + (boot_type == BT_UEFI_NTFS)) { + extra_partitions |= XP_UEFI_NTFS; + // ...but only if we're not dealing with a Windows image in installer mode with target + // system set to BIOS and without dual BIOS+UEFI boot enabled. + if ((boot_type == BT_IMAGE) && HAS_BOOTMGR_BIOS(img_report) && (!windows_to_go) && + (target_type == TT_BIOS) && (!allow_dual_uefi_bios)) + extra_partitions &= ~XP_UEFI_NTFS; + } + if (IsChecked(IDC_OLD_BIOS_FIXES)) + extra_partitions |= XP_COMPAT; + // On pre 1703 platforms (and even on later ones), anything with ext2/ext3 doesn't sit // too well with Windows. Same with ESPs. Relaxing our locking rules seems to help... - if ((extra_partitions & (XP_ESP | XP_CASPER)) || IS_EXT(fs_type)) + if ((extra_partitions & (XP_ESP | XP_PERSISTENCE)) || IS_EXT(fs_type)) actual_lock_drive = FALSE; // Windows 11 is a lot more proactive in locking ESPs and MSRs than previous versions // were, meaning that we also can't lock the drive without incurring errors... @@ -1710,13 +1719,13 @@ DWORD WINAPI FormatThread(void* param) Sleep(200); if (write_as_esp || write_as_ext) { // Can't format ESPs or ext2/ext3 partitions unless we mount them ourselves - volume_name = AltMountVolume(DriveIndex, partition_offset[PI_MAIN], FALSE); + volume_name = AltMountVolume(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset, FALSE); if (volume_name == NULL) { FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_ASSIGN_LETTER); goto out; } } else { - if (!WaitForLogical(DriveIndex, partition_offset[PI_MAIN])) { + if (!WaitForLogical(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset)) { uprintf("Logical drive was not found - aborting"); if (!IS_ERROR(FormatStatus)) FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_TIMEOUT; @@ -1727,12 +1736,12 @@ DWORD WINAPI FormatThread(void* param) // Format Casper partition if required. Do it before we format anything with // a file system that Windows will recognize, to avoid concurrent access. - if (extra_partitions & XP_CASPER) { + if (extra_partitions & XP_PERSISTENCE) { uint32_t ext_version = ReadSetting32(SETTING_USE_EXT_VERSION); if ((ext_version < 2) || (ext_version > 4)) ext_version = 3; uprintf("Using %s-like method to enable persistence", img_report.uses_casper ? "Ubuntu" : "Debian"); - if (!FormatPartition(DriveIndex, partition_offset[PI_CASPER], 0, FS_EXT2 + (ext_version - 2), + if (!FormatPartition(DriveIndex, SelectedDrive.Partition[partition_index[PI_CASPER]].Offset, 0, FS_EXT2 + (ext_version - 2), img_report.uses_casper ? "casper-rw" : "persistence", (img_report.uses_casper ? 0 : FP_CREATE_PERSISTENCE_CONF) | (IsChecked(IDC_QUICK_FORMAT) ? FP_QUICK : 0))) { @@ -1757,7 +1766,7 @@ DWORD WINAPI FormatThread(void* param) if (write_as_esp) Flags |= FP_LARGE_FAT32; - ret = FormatPartition(DriveIndex, partition_offset[PI_MAIN], ClusterSize, fs_type, label, Flags); + ret = FormatPartition(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset, ClusterSize, fs_type, label, Flags); if (!ret) { // Error will be set by FormatPartition() in FormatStatus uprintf("Format error: %s", StrError(FormatStatus, TRUE)); @@ -1790,7 +1799,7 @@ DWORD WINAPI FormatThread(void* param) // Try to continue CHECK_FOR_USER_CANCEL; - volume_name = GetLogicalName(DriveIndex, partition_offset[PI_MAIN], TRUE, TRUE); + volume_name = GetLogicalName(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset, TRUE, TRUE); if (volume_name == NULL) { uprintf("Could not get volume name"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_NO_VOLUME_ID; @@ -1848,7 +1857,7 @@ DWORD WINAPI FormatThread(void* param) } else { // We still have a lock, which we need to modify the volume boot record // => no need to reacquire the lock... - hLogicalVolume = GetLogicalHandle(DriveIndex, partition_offset[PI_MAIN], FALSE, TRUE, FALSE); + hLogicalVolume = GetLogicalHandle(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset, FALSE, TRUE, FALSE); if ((hLogicalVolume == INVALID_HANDLE_VALUE) || (hLogicalVolume == NULL)) { uprintf("Could not re-mount volume for partition boot record access"); FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_OPEN_FAILED; @@ -1999,7 +2008,7 @@ out: } } if (IS_ERROR(FormatStatus)) { - volume_name = GetLogicalName(DriveIndex, partition_offset[PI_MAIN], TRUE, TRUE); + volume_name = GetLogicalName(DriveIndex, SelectedDrive.Partition[partition_index[PI_MAIN]].Offset, TRUE, TRUE); if (volume_name != NULL) { if (MountVolume(drive_name, volume_name)) uprintf("Re-mounted volume as %c: after error", toupper(drive_name[0])); diff --git a/src/missing.h b/src/missing.h index 54d6e342..88dc116c 100644 --- a/src/missing.h +++ b/src/missing.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Constants and defines missing from various toolchains -* Copyright © 2016-2022 Pete Batard +* Copyright © 2016-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,6 +31,9 @@ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif +#define LO_ALIGN_X_TO_Y(x, y) (((x) / (y)) * (y)) +#define HI_ALIGN_X_TO_Y(x, y) ((((x) + (y) - 1) / (y)) * (y)) + #if defined(__GNUC__) #define ALIGNED(m) __attribute__ ((__aligned__(m))) #elif defined(_MSC_VER) diff --git a/src/rufus.rc b/src/rufus.rc index 3df360bd..abe32f55 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2109" +CAPTION "Rufus 4.5.2110" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2109,0 - PRODUCTVERSION 4,5,2109,0 + FILEVERSION 4,5,2110,0 + PRODUCTVERSION 4,5,2110,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2109" + VALUE "FileVersion", "4.5.2110" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2109" + VALUE "ProductVersion", "4.5.2110" END END BLOCK "VarFileInfo" diff --git a/src/wue.c b/src/wue.c index d81fb25a..091fa3fe 100644 --- a/src/wue.c +++ b/src/wue.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Windows User Experience - * Copyright © 2022-2023 Pete Batard + * Copyright © 2022-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -690,14 +690,14 @@ BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp) // VDS cannot list ESP volumes (talk about allegedly improving on the old disk and volume APIs, only to // completely neuter it) and IVdsDiskPartitionMF::FormatPartitionEx(), which is what you are supposed to // use for ESPs, explicitly states: "This method cannot be used to format removable media." - if (!FormatPartition(DriveIndex, partition_offset[PI_ESP], cluster_size, FS_FAT32, "", + if (!FormatPartition(DriveIndex, SelectedDrive.Partition[partition_index[PI_ESP]].Offset, cluster_size, FS_FAT32, "", FP_QUICK | FP_FORCE | FP_LARGE_FAT32 | FP_NO_BOOT)) { uprintf("Could not format EFI System Partition"); return FALSE; } Sleep(200); // Need to have the ESP mounted to invoke bcdboot - ms_efi = AltMountVolume(DriveIndex, partition_offset[PI_ESP], FALSE); + ms_efi = AltMountVolume(DriveIndex, SelectedDrive.Partition[partition_index[PI_ESP]].Offset, FALSE); if (ms_efi == NULL) { FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_ASSIGN_LETTER); return FALSE; From 15e3886499d19de6842001e6a5fad944b77427f8 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 9 Feb 2024 16:59:28 +0000 Subject: [PATCH 51/53] [iso] enable persistence support for Linux Mint * Mint users sure are lucky that one of them *lied their way through* pretending that persistence actually used to work with previous version of Mint, when it never did, because they got us going through a whole refactor of the partition creation process just so we could make Mint persistence work. * Closes #2428. * Also fix a Coverity warning. --- src/drive.c | 2 +- src/iso.c | 5 +++++ src/rufus.rc | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/drive.c b/src/drive.c index d558befd..c4f56cf8 100644 --- a/src/drive.c +++ b/src/drive.c @@ -2383,7 +2383,7 @@ BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL m // Compute the offsets of the extra partitions (which we always align to a track) last_offset = SelectedDrive.DiskSize; if (partition_style == PARTITION_STYLE_GPT) - last_offset -= 33 * SelectedDrive.SectorSize; + last_offset -= 33ULL * SelectedDrive.SectorSize; for (i = pi - 1; i > mi; i--) { assert(SelectedDrive.Partition[i].Size < last_offset); SelectedDrive.Partition[i].Offset = LO_ALIGN_X_TO_Y(last_offset - SelectedDrive.Partition[i].Size, bytes_per_track); diff --git a/src/iso.c b/src/iso.c index e03dd9b1..f59fb149 100644 --- a/src/iso.c +++ b/src/iso.c @@ -380,6 +380,11 @@ static void fix_config(const char* psz_fullpath, const char* psz_path, const cha // Ubuntu 23.04 uses GRUB only with the above and does not use "maybe-ubiquity" uprintf(" Added 'persistent' kernel option"); modified = TRUE; + } else if (replace_in_token_data(src, props->is_grub_cfg ? "linux" : "append", + "boot=casper", "boot=casper persistent", TRUE) != NULL) { + // Linux Mint uses boot=casper. + uprintf(" Added 'persistent' kernel option"); + modified = TRUE; } else if (replace_in_token_data(src, props->is_grub_cfg ? "linux" : "append", "boot=live", "boot=live persistence", TRUE) != NULL) { // Debian & derivatives are assumed to use 'boot=live' in diff --git a/src/rufus.rc b/src/rufus.rc index abe32f55..46642bd6 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2110" +CAPTION "Rufus 4.5.2111" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2110,0 - PRODUCTVERSION 4,5,2110,0 + FILEVERSION 4,5,2111,0 + PRODUCTVERSION 4,5,2111,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2110" + VALUE "FileVersion", "4.5.2111" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2110" + VALUE "ProductVersion", "4.5.2111" END END BLOCK "VarFileInfo" From 172888ac324cfe0af8a7b7deb55f85ca431aabd3 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Fri, 9 Feb 2024 17:00:42 +0000 Subject: [PATCH 52/53] [dos] fix a CodeQL warning and harmonize code --- src/dos_locale.c | 56 ++++++++++++++++++++++++------------------------ src/rufus.rc | 10 ++++----- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/dos_locale.c b/src/dos_locale.c index 8a4dace9..4260b604 100644 --- a/src/dos_locale.c +++ b/src/dos_locale.c @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * DOS keyboard locale setup - * Copyright © 2011-2021 Pete Batard + * Copyright © 2011-2024 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -197,8 +197,8 @@ static kb_list fd_kb_list[] = { static int ms_get_kbdrv(const char* kb) { unsigned int i, j; - for (i=0; i= 0); - uprintf("Will use DOS keyboard '%s' [%s]\n", kb, kb_to_hr(kb)); + uprintf("Will use DOS keyboard '%s' [%s]", kb, kb_to_hr(kb)); // Now get a codepage cp = GetOEMCP(); @@ -994,16 +994,16 @@ BOOL SetDOSLocale(const char* path, BOOL bFreeDOS) (char*)&actual_cp, sizeof(actual_cp))) cp = actual_cp; } - egadrv = bFreeDOS?fd_get_ega(cp):ms_get_ega(cp); + egadrv = bFreeDOS ? fd_get_ega(cp) : ms_get_ega(cp); if (egadrv == NULL) { // We need to use the fallback CP from the keyboard we got above, as 437 is not always available - uprintf("Unable to find an EGA file with codepage %d [%s]\n", cp, cp_to_hr(cp)); + uprintf("Unable to find an EGA file with codepage %d [%s]", cp, cp_to_hr(cp)); cp = kbdrv_data[kbdrv].default_cp; - egadrv = bFreeDOS?"ega.cpx":"ega.cpi"; + egadrv = bFreeDOS ? "ega.cpx" : "ega.cpi"; } else if (bFreeDOS) { cp = fd_upgrade_cp(cp); } - uprintf("Will use codepage %d [%s]\n", cp, cp_to_hr(cp)); + uprintf("Will use codepage %d [%s]", cp, cp_to_hr(cp)); if ((cp == 437) && (strcmp(kb, "us") == 0)) { // Nothing much to do if US/US - just notify in autoexec.bat @@ -1011,14 +1011,14 @@ BOOL SetDOSLocale(const char* path, BOOL bFreeDOS) static_strcat(filename, "\\AUTOEXEC.BAT"); fd = fopen(filename, "w+"); if (fd == NULL) { - uprintf("Unable to create 'AUTOEXEC.BAT': %s.\n", WindowsErrorString()); + uprintf("Unable to create 'AUTOEXEC.BAT': %s", WindowsErrorString()); return FALSE; } fprintf(fd, "@echo off\n"); fprintf(fd, "set PATH=.;\\;\\LOCALE\n"); fprintf(fd, "echo Using %s keyboard with %s codepage [%d]\n", kb_to_hr("us"), cp_to_hr(437), 437); fclose(fd); - uprintf("Successfully wrote 'AUTOEXEC.BAT'\n"); + uprintf("Successfully wrote 'AUTOEXEC.BAT'"); return TRUE; } @@ -1027,7 +1027,7 @@ BOOL SetDOSLocale(const char* path, BOOL bFreeDOS) static_strcat(filename, "\\CONFIG.SYS"); fd = fopen(filename, "w+"); if (fd == NULL) { - uprintf("Unable to create 'CONFIG.SYS': %s.\n", WindowsErrorString()); + uprintf("Unable to create 'CONFIG.SYS': %s.", WindowsErrorString()); return FALSE; } if (bFreeDOS) { @@ -1045,14 +1045,14 @@ BOOL SetDOSLocale(const char* path, BOOL bFreeDOS) bFreeDOS?"MENU ":"MENUITEM=", bFreeDOS?')':',', kb_to_hr("us"), cp_to_hr(437), 437); fprintf(fd, "%s", bFreeDOS?"MENU\n12?\n":"[1]\ndevice=\\locale\\display.sys con=(ega,,1)\n[2]\n"); fclose(fd); - uprintf("Successfully wrote 'CONFIG.SYS'\n"); + uprintf("Successfully wrote 'CONFIG.SYS'"); // AUTOEXEC.BAT static_strcpy(filename, path); static_strcat(filename, "\\AUTOEXEC.BAT"); fd = fopen(filename, "w+"); if (fd == NULL) { - uprintf("Unable to create 'AUTOEXEC.BAT': %s.\n", WindowsErrorString()); + uprintf("Unable to create 'AUTOEXEC.BAT': %s", WindowsErrorString()); return FALSE; } fprintf(fd, "@echo off\n"); @@ -1066,7 +1066,7 @@ BOOL SetDOSLocale(const char* path, BOOL bFreeDOS) fprintf(fd, "keyb %s,,\\locale\\%s\n", kb, kbdrv_data[kbdrv].name); fprintf(fd, ":2\n"); fclose(fd); - uprintf("Successfully wrote 'AUTOEXEC.BAT'\n"); + uprintf("Successfully wrote 'AUTOEXEC.BAT'"); return TRUE; } diff --git a/src/rufus.rc b/src/rufus.rc index 46642bd6..6e05a6cc 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2111" +CAPTION "Rufus 4.5.2112" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2111,0 - PRODUCTVERSION 4,5,2111,0 + FILEVERSION 4,5,2112,0 + PRODUCTVERSION 4,5,2112,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2111" + VALUE "FileVersion", "4.5.2112" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2111" + VALUE "ProductVersion", "4.5.2112" END END BLOCK "VarFileInfo" From 026afa7e3de84748a46af5e08fbaa5eeb16ffd22 Mon Sep 17 00:00:00 2001 From: Bella Zhang Date: Sun, 28 Jan 2024 16:43:26 +0800 Subject: [PATCH 53/53] [iso] add Circle Linux to the list of Red-Hat derivatives * Closes #2414. --- src/rufus.c | 3 ++- src/rufus.rc | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rufus.c b/src/rufus.c index 952711d8..4f55dfe2 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -1261,7 +1261,8 @@ DWORD WINAPI ImageScanThread(LPVOID param) const char* redhat8_derivative[] = { "^AlmaLinux-[8-9].*", // AlmaLinux 8.x and 9.x "^Fedora.*-3[3-9].*", // Fedora 33-39 - "^CentOS.*-[8-9].*", // CentOS and CentOS Stream 8.and 9.x + "^CentOS.*-[8-9].*", // CentOS and CentOS Stream 8.x and 9.x + "^Circle.*-[8-9].*", // Circle Linux 8.x.and 9.x "^OL-[8-9].*", // Oracle Linux 8.x and 9.x "^RHEL-[8-9].*", // Red Hat 8.x and 9.x "^Rocky-[8-9].*", // Rocky Linux 8.x and 9.x diff --git a/src/rufus.rc b/src/rufus.rc index 6e05a6cc..cd00d562 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.5.2112" +CAPTION "Rufus 4.5.2113" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,5,2112,0 - PRODUCTVERSION 4,5,2112,0 + FILEVERSION 4,5,2113,0 + PRODUCTVERSION 4,5,2113,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.5.2112" + VALUE "FileVersion", "4.5.2113" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2024 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.5.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.5.2112" + VALUE "ProductVersion", "4.5.2113" END END BLOCK "VarFileInfo"

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 delta 119913 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%0Dq- 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